diff --git a/.github/actions/build-chainlink-image/action.yml b/.github/actions/build-chainlink-image/action.yml new file mode 100644 index 00000000000..d5839cc79bf --- /dev/null +++ b/.github/actions/build-chainlink-image/action.yml @@ -0,0 +1,48 @@ +name: Build Chainlink Image +description: A composite action that allows building and publishing the Chainlink image for integration testing + +inputs: + tag_suffix: + description: The suffix to append to the image tag (usually blank or "-plugins") + default: "" + dockerfile: + description: The path to the Dockerfile to use (usually core/chainlink.Dockerfile or plugins/chainlink.Dockerfile) + default: core/chainlink.Dockerfile + git_commit_sha: + description: The git commit sha to use for the image tag + default: ${{ github.sha }} + GRAFANA_CLOUD_BASIC_AUTH: + description: "grafana cloud basic auth" + GRAFANA_CLOUD_HOST: + description: "grafana cloud hostname" + AWS_REGION: + description: "AWS region to use for ECR" + AWS_ROLE_TO_ASSUME: + description: "AWS role to assume for ECR" + +runs: + using: composite + steps: + - name: Check if image exists + id: check-image + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + with: + repository: chainlink + tag: ${{ inputs.git_commit_sha }}${{ inputs.tag_suffix }} + AWS_REGION: ${{ inputs.AWS_REGION }} + AWS_ROLE_TO_ASSUME: ${{ inputs.AWS_ROLE_TO_ASSUME }} + - name: Build Image + if: steps.check-image.outputs.exists == 'false' + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + with: + cl_repo: smartcontractkit/chainlink + cl_ref: ${{ inputs.git_commit_sha }} + cl_dockerfile: ${{ inputs.dockerfile }} + push_tag: ${{ env.CHAINLINK_IMAGE }}:${{ inputs.git_commit_sha }}${{ inputs.tag_suffix }} + QA_AWS_REGION: ${{ inputs.AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ inputs.AWS_ROLE_TO_ASSUME }} + - name: Print Chainlink Image Built + shell: sh + run: | + echo "### Chainlink node image tag used for this test run :link:" >>$GITHUB_STEP_SUMMARY + echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY diff --git a/.github/actions/build-sign-publish-chainlink/action.yml b/.github/actions/build-sign-publish-chainlink/action.yml index 19b5c733e67..8c79f651afd 100644 --- a/.github/actions/build-sign-publish-chainlink/action.yml +++ b/.github/actions/build-sign-publish-chainlink/action.yml @@ -13,6 +13,12 @@ inputs: description: Path to the Dockerfile (relative to the repo root) default: core/chainlink.Dockerfile required: false + dockerhub_username: + description: Username for Docker Hub to avoid rate limits when pulling public images + required: false + dockerhub_password: + description: Password for Docker Hub to avoid rate limits when pulling public images + required: false ecr-hostname: description: The ECR registry scope default: public.ecr.aws @@ -28,7 +34,7 @@ inputs: required: false git-commit-sha: description: Git commit SHA used as metadata when building the application (appears in logs) - default: ${{ github.sha }} + default: ${{ github.event.pull_request.head.sha || github.sha }} required: false aws-role-to-assume: description: The AWS role to assume as the CD user, if any. Used in configuring the docker/login-action @@ -67,7 +73,7 @@ runs: using: composite steps: - name: Set shared variables - shell: sh + shell: bash # See https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#multiline-strings run: | SHARED_IMAGES=${{ inputs.ecr-hostname }}/${{ inputs.ecr-image-name }} @@ -99,7 +105,7 @@ runs: - if: inputs.publish == 'true' # Log in to AWS for publish to ECR name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 + uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: role-to-assume: ${{ inputs.aws-role-to-assume }} role-duration-seconds: ${{ inputs.aws-role-duration-seconds }} @@ -112,11 +118,13 @@ runs: registry: ${{ inputs.ecr-hostname }} - name: Setup Docker Buildx - uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0 + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - name: Generate docker metadata for root image id: meta-root - uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v4.4.4 + uses: docker/metadata-action@2c0bd771b40637d97bf205cbccdd294a32112176 # v4.5.0 + env: + DOCKER_METADATA_PR_HEAD_SHA: "true" with: # list of Docker images to use as base name for tags images: ${{ env.shared-images }} @@ -126,9 +134,17 @@ runs: type=semver,pattern={{version}},suffix=${{ inputs.ecr-tag-suffix }}-root type=sha,format=short,suffix=${{ inputs.ecr-tag-suffix }}-root + # To avoid rate limiting from Docker Hub, we login with a paid user account. + - name: Login to Docker Hub + if: inputs.dockerhub_username && inputs.dockerhub_password + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + username: ${{ inputs.dockerhub_username }} + password: ${{ inputs.dockerhub_password }} + - name: Build and push root docker image id: buildpush-root - uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # v3.2.0 + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 with: push: ${{ inputs.publish }} context: . @@ -150,7 +166,9 @@ runs: - name: Generate docker metadata for non-root image id: meta-nonroot - uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v4.4.4 + uses: docker/metadata-action@2c0bd771b40637d97bf205cbccdd294a32112176 # v4.5.0 + env: + DOCKER_METADATA_PR_HEAD_SHA: "true" with: flavor: | latest=auto @@ -159,9 +177,17 @@ runs: images: ${{ env.shared-images }} tags: ${{ env.shared-tag-list }} + # To avoid rate limiting from Docker Hub, we login with a paid user account. + - name: Login to Docker Hub + if: inputs.dockerhub_username && inputs.dockerhub_password + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + username: ${{ inputs.dockerhub_username }} + password: ${{ inputs.dockerhub_password }} + - name: Build and push non-root docker image id: buildpush-nonroot - uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # v3.2.0 + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 with: push: ${{ inputs.publish }} context: . diff --git a/.github/actions/build-test-image/action.yml b/.github/actions/build-test-image/action.yml index c4b39b4d7af..b7a7948d2cf 100644 --- a/.github/actions/build-test-image/action.yml +++ b/.github/actions/build-test-image/action.yml @@ -15,7 +15,7 @@ inputs: required: false suites: description: The test suites to build into the image - default: chaos migration performance reorg smoke soak benchmark + default: chaos migration performance reorg smoke soak benchmark load/automationv2_1 required: false QA_AWS_ROLE_TO_ASSUME: description: The AWS role to assume as the CD user, if any. Used in configuring the docker/login-action @@ -30,9 +30,69 @@ inputs: runs: using: composite steps: + + # Base Test Image Logic + - name: Get CTF Version + id: version + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/mod-version@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + with: + go-project-path: ./integration-tests + module-name: github.com/smartcontractkit/chainlink-testing-framework + enforce-semantic-tag: false + - name: Get CTF sha + if: steps.version.outputs.is_semantic == 'false' + id: short_sha + env: + VERSION: ${{ steps.version.outputs.version }} + shell: bash + run: | + short_sha="${VERSION##*-}" + echo "short sha is: ${short_sha}" + echo "short_sha=${short_sha}" >> "$GITHUB_OUTPUT" + - name: Checkout chainlink-testing-framework + if: steps.version.outputs.is_semantic == 'false' + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + repository: smartcontractkit/chainlink-testing-framework + ref: main + fetch-depth: 0 + path: ctf + - name: Get long sha + if: steps.version.outputs.is_semantic == 'false' + id: long_sha + env: + SHORT_SHA: ${{ steps.short_sha.outputs.short_sha }} + shell: bash + run: | + cd ctf + long_sha=$(git rev-parse ${SHORT_SHA}) + echo "sha is: ${long_sha}" + echo "long_sha=${long_sha}" >> "$GITHUB_OUTPUT" + - name: Check if test base image exists + if: steps.version.outputs.is_semantic == 'false' + id: check-base-image + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + with: + repository: ${{ inputs.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ inputs.QA_AWS_REGION }}.amazonaws.com/test-base-image + tag: ${{ steps.long_sha.outputs.long_sha }} + AWS_REGION: ${{ inputs.QA_AWS_REGION }} + AWS_ROLE_TO_ASSUME: ${{ inputs.QA_AWS_ROLE_TO_ASSUME }} + - name: Build Base Image + if: steps.version.outputs.is_semantic == 'false' && steps.check-base-image.outputs.exists == 'false' + uses: smartcontractkit/chainlink-github-actions/docker/build-push@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + env: + BASE_IMAGE_NAME: ${{ inputs.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ inputs.QA_AWS_REGION }}.amazonaws.com/test-base-image:${{ steps.long_sha.outputs.long_sha }} + with: + tags: ${{ env.BASE_IMAGE_NAME }} + file: ctf/k8s/Dockerfile.base + AWS_REGION: ${{ inputs.QA_AWS_REGION }} + AWS_ROLE_TO_ASSUME: ${{ inputs.QA_AWS_ROLE_TO_ASSUME }} + # End Base Image Logic + + # Test Runner Logic - name: Check if image exists id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@00c6214deb10a3f374c6d3430c32c5202015d463 # v2.2.12 + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: repository: ${{ inputs.repository }} tag: ${{ inputs.tag }} @@ -40,7 +100,7 @@ runs: AWS_ROLE_TO_ASSUME: ${{ inputs.QA_AWS_ROLE_TO_ASSUME }} - name: Build and Publish Test Runner if: steps.check-image.outputs.exists == 'false' - uses: smartcontractkit/chainlink-github-actions/docker/build-push@00c6214deb10a3f374c6d3430c32c5202015d463 # v2.2.12 + uses: smartcontractkit/chainlink-github-actions/docker/build-push@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: tags: | ${{ inputs.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ inputs.QA_AWS_REGION }}.amazonaws.com/${{ inputs.repository }}:${{ inputs.tag }} @@ -48,7 +108,7 @@ runs: file: ./integration-tests/test.Dockerfile build-args: | BASE_IMAGE=${{ inputs.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ inputs.QA_AWS_REGION }}.amazonaws.com/test-base-image - IMAGE_VERSION=v0.38.2 + IMAGE_VERSION=${{ steps.long_sha.outputs.long_sha || steps.version.outputs.version }} SUITES="${{ inputs.suites }}" AWS_REGION: ${{ inputs.QA_AWS_REGION }} AWS_ROLE_TO_ASSUME: ${{ inputs.QA_AWS_ROLE_TO_ASSUME }} @@ -60,3 +120,4 @@ runs: run: | echo "### ${INPUTS_REPOSITORY} image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY echo "\`${INPUTS_TAG}\`" >>$GITHUB_STEP_SUMMARY + # End Test Runner Logic diff --git a/.github/actions/delete-deployments/action.yml b/.github/actions/delete-deployments/action.yml index 20b7d0eefe5..5fc7ef0287b 100644 --- a/.github/actions/delete-deployments/action.yml +++ b/.github/actions/delete-deployments/action.yml @@ -29,11 +29,11 @@ inputs: runs: using: composite steps: - - uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd #v2.2.4 + - uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd # v2.2.4 with: version: ^8.0.0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 with: node-version: "18" cache: "pnpm" diff --git a/.github/actions/golangci-lint/action.yml b/.github/actions/golangci-lint/action.yml index 5c4882e2684..055960ff282 100644 --- a/.github/actions/golangci-lint/action.yml +++ b/.github/actions/golangci-lint/action.yml @@ -30,7 +30,7 @@ inputs: runs: using: composite steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup Go uses: ./.github/actions/setup-go with: @@ -53,12 +53,12 @@ runs: - name: golangci-lint uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 with: - version: v1.55.0 + version: v1.55.2 # We already cache these directories in setup-go skip-pkg-cache: true skip-build-cache: true # only-new-issues is only applicable to PRs, otherwise it is always set to false - only-new-issues: true + only-new-issues: false # disabled for PRs due to unreliability args: --out-format colored-line-number,checkstyle:golangci-lint-report.xml working-directory: ${{ inputs.go-directory }} - name: Store lint report artifact @@ -69,7 +69,7 @@ runs: path: ${{ inputs.go-directory }}/golangci-lint-report.xml - name: Collect Metrics if: always() - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ inputs.gc-basic-auth }} hostname: ${{ inputs.gc-host }} diff --git a/.github/actions/goreleaser-build-sign-publish/README.md b/.github/actions/goreleaser-build-sign-publish/README.md index 49edfb25d51..d6bf7e6fd4a 100644 --- a/.github/actions/goreleaser-build-sign-publish/README.md +++ b/.github/actions/goreleaser-build-sign-publish/README.md @@ -25,9 +25,9 @@ jobs: MACOS_SDK_VERSION: 12.3 steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Configure aws credentials - uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 + uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: role-to-assume: ${{ secrets.aws-role-arn }} role-duration-seconds: ${{ secrets.aws-role-dur-sec }} diff --git a/.github/actions/goreleaser-build-sign-publish/action.yml b/.github/actions/goreleaser-build-sign-publish/action.yml index a9f32337221..b2d42c1234e 100644 --- a/.github/actions/goreleaser-build-sign-publish/action.yml +++ b/.github/actions/goreleaser-build-sign-publish/action.yml @@ -65,7 +65,7 @@ runs: using: composite steps: - name: Setup docker buildx - uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0 + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - name: Set up qemu uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0 - name: Setup go diff --git a/.github/actions/setup-nodejs/action.yaml b/.github/actions/setup-nodejs/action.yaml index 4e1740032b6..1bb529b421c 100644 --- a/.github/actions/setup-nodejs/action.yaml +++ b/.github/actions/setup-nodejs/action.yaml @@ -7,11 +7,11 @@ description: Setup pnpm for contracts runs: using: composite steps: - - uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd #v2.2.4 + - uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd # v2.2.4 with: version: ^7.0.0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 with: node-version: "16" cache: "pnpm" diff --git a/.github/actions/split-tests/action.yaml b/.github/actions/split-tests/action.yaml index fc96c4da25f..684fd6a2bd7 100644 --- a/.github/actions/split-tests/action.yaml +++ b/.github/actions/split-tests/action.yaml @@ -15,7 +15,7 @@ runs: with: version: ^7.0.0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 with: node-version: "16" cache: "pnpm" diff --git a/.github/actions/version-file-bump/action.yml b/.github/actions/version-file-bump/action.yml index 73a374762d6..20832174003 100644 --- a/.github/actions/version-file-bump/action.yml +++ b/.github/actions/version-file-bump/action.yml @@ -31,7 +31,7 @@ runs: current_version=$(head -n1 ./VERSION) echo "current_version=${current_version}" | tee -a "$GITHUB_OUTPUT" - name: Compare semantic versions - uses: smartcontractkit/chainlink-github-actions/semver-compare@v2.2.0 + uses: smartcontractkit/chainlink-github-actions/semver-compare@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 id: compare with: version1: ${{ steps.get-current-version.outputs.current_version }} diff --git a/.github/tracing/README.md b/.github/tracing/README.md index 6988383ca7b..feba31feb65 100644 --- a/.github/tracing/README.md +++ b/.github/tracing/README.md @@ -1,5 +1,51 @@ # Distributed Tracing -These config files are for an OTEL collector, grafana Tempo, and a grafana UI instance to run as containers on the same network. +As part of the LOOP plugin effort, we've added distributed tracing to the core node. This is helpful for initial development and maintenance of LOOPs, but will also empower product teams building on top of core. + +## Dev environment + +One way to generate traces locally today is with the OCR2 basic smoke test. + +1. navigate to `.github/tracing/` and then run `docker compose --file local-smoke-docker-compose.yaml up` +2. setup a local docker registry at `127.0.0.1:5000` (https://www.docker.com/blog/how-to-use-your-own-registry-2/) +3. run `make build_push_plugin_docker_image` in `chainlink/integration-tests/Makefile` +4. run `SELECTED_NETWORKS=SIMULATED CHAINLINK_IMAGE="127.0.0.1:5000/chainlink" CHAINLINK_VERSION="develop" go test -run TestOCRv2Basic ./smoke/ocr2_test.go` +5. navigate to `localhost:3000/explore` in a web browser to query for traces + +Core and the median plugins are instrumented with open telemetry traces, which are sent to the OTEL collector and forwarded to the Tempo backend. The grafana UI can then read the trace data from the Tempo backend. + + + +## CI environment + +Another way to generate traces is by enabling traces for PRs. This will instrument traces for `TestOCRv2Basic` in the CI run. -A localhost client can send gRPC calls to the server. The gRPC server is instrumented with open telemetry traces, which are sent to the OTEL collector and forwarded to the Tempo backend. The grafana UI can then read the trace data from the Tempo backend. \ No newline at end of file +1. Cut a PR in the core repo +2. Add the `enable tracing` label to the PR +3. Navigate to `Integration Tests / ETH Smoke Tests ocr2-plugins (pull_request)` details +4. Navigate to the summary of the integration tests +5. After the test completes, the generated trace data will be saved as an artifact, currently called `trace-data` +6. Download the artifact to this directory (`chainlink/.github/tracing`) +7. `docker compose --file local-smoke-docker-compose.yaml up` +8. Run `sh replay.sh` to replay those traces to the otel-collector container that was spun up in the last step. +9. navigate to `localhost:3000/explore` in a web browser to query for traces + +The artifact is not json encoded - each individual line is a well formed and complete json object. + + +## Production and NOPs environments + +In a production environment, we suggest coupling the lifecycle of nodes and otel-collectors. A best practice is to deploy the otel-collector alongside your node, using infrastructure as code (IAC) to automate deployments and certificate lifecycles. While there are valid use cases for using `Tracing.Mode = unencrypted`, we have set the default encryption setting to `Tracing.Mode = tls`. Externally deployed otel-collectors can not be used with `Tracing.Mode = unencrypted`. i.e. If `Tracing.Mode = unencrypted` and an external URI is detected for `Tracing.CollectorTarget` node configuration will fail to validate and the node will not boot. The node requires a valid encryption mode and collector target to send traces. + +Once traces reach the otel-collector, the rest of the observability pipeline is flexible. We recommend deploying (through automation) centrally managed Grafana Tempo and Grafana UI instances to receive from one or many otel-collector instances. Always use networking best practices and encrypt trace data, especially at network boundaries. + +## Configuration +This folder contains the following config files: +* otel-collector-ci.yaml +* otel-collector-dev.yaml +* tempo.yaml +* grafana-datasources.yaml + +These config files are for an OTEL collector, grafana Tempo, and a grafana UI instance to run as containers on the same network. +`otel-collector-dev.yaml` is the configuration for dev (i.e. your local machine) environments, and forwards traces from the otel collector to the grafana tempo instance on the same network. +`otel-collector-ci.yaml` is the configuration for the CI runs, and exports the trace data to the artifact from the github run. \ No newline at end of file diff --git a/.github/tracing/local-smoke-docker-compose.yaml b/.github/tracing/local-smoke-docker-compose.yaml index e0e60a675e5..744ba88ef69 100644 --- a/.github/tracing/local-smoke-docker-compose.yaml +++ b/.github/tracing/local-smoke-docker-compose.yaml @@ -6,9 +6,11 @@ services: image: otel/opentelemetry-collector:0.61.0 command: [ "--config=/etc/otel-collector.yaml" ] volumes: - - ./otel-collector.yaml:/etc/otel-collector.yaml + - ./otel-collector-dev.yaml:/etc/otel-collector.yaml + - ../../integration-tests/smoke/traces/trace-data.json:/etc/trace-data.json # local trace data stored consistent with smoke/logs ports: - "4317:4317" # otlp grpc + - "3100:3100" depends_on: - tempo networks: diff --git a/.github/tracing/otel-collector-ci.yaml b/.github/tracing/otel-collector-ci.yaml new file mode 100644 index 00000000000..0bf123d29b5 --- /dev/null +++ b/.github/tracing/otel-collector-ci.yaml @@ -0,0 +1,22 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" + http: + endpoint: "0.0.0.0:3100" +exporters: + file: + path: /tracing/trace-data.json + otlp: + endpoint: tempo:4317 + tls: + insecure: true +service: + telemetry: + logs: + level: "debug" # Set log level to debug + pipelines: + traces: + receivers: [otlp] + exporters: [file,otlp] \ No newline at end of file diff --git a/.github/tracing/otel-collector.yaml b/.github/tracing/otel-collector-dev.yaml similarity index 67% rename from .github/tracing/otel-collector.yaml rename to .github/tracing/otel-collector-dev.yaml index fb8721cba20..dd059127b81 100644 --- a/.github/tracing/otel-collector.yaml +++ b/.github/tracing/otel-collector-dev.yaml @@ -3,12 +3,17 @@ receivers: protocols: grpc: endpoint: "0.0.0.0:4317" + http: + endpoint: "0.0.0.0:3100" exporters: otlp: endpoint: tempo:4317 tls: insecure: true service: + telemetry: + logs: + level: "debug" # Set log level to debug pipelines: traces: receivers: [otlp] diff --git a/.github/tracing/replay.sh b/.github/tracing/replay.sh new file mode 100644 index 00000000000..b2e564567c4 --- /dev/null +++ b/.github/tracing/replay.sh @@ -0,0 +1,6 @@ +# Read JSON file and loop through each trace +while IFS= read -r trace; do + curl -X POST http://localhost:3100/v1/traces \ + -H "Content-Type: application/json" \ + -d "$trace" +done < "trace-data" diff --git a/.github/workflows/automation-benchmark-tests.yml b/.github/workflows/automation-benchmark-tests.yml index 3d7466faedd..efe6d2eb59d 100644 --- a/.github/workflows/automation-benchmark-tests.yml +++ b/.github/workflows/automation-benchmark-tests.yml @@ -6,7 +6,7 @@ on: description: Chainlink image version to use required: true type: string - default: 2.5.0 + default: 2.6.0 chainlinkImage: description: Chainlink image repo to use required: true @@ -24,6 +24,8 @@ on: - OPTIMISM_GOERLI - MUMBAI - SEPOLIA + - BASE_GOERLI + - ARBITRUM_SEPOLIA TestInputs: description: TestInputs required: false @@ -55,7 +57,7 @@ jobs: id-token: write contents: read name: ${{ inputs.network }} Automation Benchmark Test - runs-on: ubuntu-latest + runs-on: ubuntu20.04-16cores-64GB env: SELECTED_NETWORKS: ${{ inputs.network }} SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} @@ -97,7 +99,7 @@ jobs: done done <<< "$EVM_HTTP_URLS" - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ env.REF_NAME }} - name: Build Test Image @@ -106,8 +108,9 @@ jobs: QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + suites: benchmark load/automationv2_1 chaos reorg - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 env: DETACH_RUNNER: true TEST_SUITE: benchmark @@ -128,7 +131,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/automation-load-tests.yml b/.github/workflows/automation-load-tests.yml new file mode 100644 index 00000000000..b19ac8fd24c --- /dev/null +++ b/.github/workflows/automation-load-tests.yml @@ -0,0 +1,105 @@ +name: Automation Load Test +on: + workflow_dispatch: + inputs: + chainlinkVersion: + description: Chainlink image version to use + required: true + type: string + default: 2.6.0 + chainlinkImage: + description: Chainlink image repo to use + required: true + type: string + default: public.ecr.aws/chainlink/chainlink + network: + description: Network to run tests on + required: true + type: choice + options: + - SIMULATED + TestInputs: + description: TestInputs + required: false + type: string + slackMemberID: + description: Notifies test results (Not your @) + required: true + default: U02Q14G80TY + type: string + +jobs: + automation_load: + environment: integration + permissions: + checks: write + pull-requests: write + id-token: write + contents: read + name: ${{ inputs.network }} Automation Load Test + runs-on: ubuntu20.04-16cores-64GB + env: + SELECTED_NETWORKS: ${{ inputs.network }} + SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} + SLACK_CHANNEL: C03KJ5S7KEK + TEST_INPUTS: ${{ inputs.TestInputs }} + CHAINLINK_ENV_USER: ${{ github.actor }} + REF_NAME: ${{ github.head_ref || github.ref_name }} + steps: + - name: Setup Push Tag + shell: bash + run: | + echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY + echo "\`${{ inputs.chainlinkVersion }}\`" >>$GITHUB_STEP_SUMMARY + echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY + echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY + + - name: Add mask + run: | + SLACK_USER=$(jq -r '.inputs.slackMemberID' $GITHUB_EVENT_PATH) + echo ::add-mask::$SLACK_USER + echo SLACK_USER=$SLACK_USER >> $GITHUB_ENV + - name: Checkout the repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ env.REF_NAME }} + - name: Build Test Image + uses: ./.github/actions/build-test-image + with: + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + suites: benchmark load/automationv2_1 chaos reorg + - name: Run Tests + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + env: + RR_CPU: 4000m + RR_MEM: 4Gi + DETACH_RUNNER: true + TEST_SUITE: automationv2_1 + TEST_ARGS: -test.timeout 720h + ENV_JOB_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests:${{ github.sha }} + INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com + PYROSCOPE_SERVER: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + with: + test_command_to_run: cd integration-tests && go test -timeout 1h -v -run TestLogTrigger ./load/automationv2_1 -count=1 + test_download_vendor_packages_command: make gomod + cl_repo: ${{ inputs.chainlinkImage }} + cl_image_tag: ${{ inputs.chainlinkVersion }} + token: ${{ secrets.GITHUB_TOKEN }} + should_cleanup: false + go_mod_path: ./integration-tests/go.mod + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + - name: Collect Metrics + if: always() + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 + with: + basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} + this-job-name: ${{ inputs.network }} Automation Load Test + test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' + continue-on-error: true diff --git a/.github/workflows/automation-ondemand-tests.yml b/.github/workflows/automation-ondemand-tests.yml index 032670c39a3..016a10252be 100644 --- a/.github/workflows/automation-ondemand-tests.yml +++ b/.github/workflows/automation-ondemand-tests.yml @@ -46,20 +46,20 @@ jobs: - name: Collect Metrics if: inputs.chainlinkImage == '' id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} this-job-name: Build Chainlink Image ${{ matrix.image.name }} continue-on-error: true - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.head_ref || github.ref_name }} - name: Check if image exists if: inputs.chainlinkImage == '' id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: repository: chainlink tag: ${{ github.sha }}${{ matrix.image.tag-suffix }} @@ -67,7 +67,7 @@ jobs: AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - name: Build Image if: steps.check-image.outputs.exists == 'false' && inputs.chainlinkImage == '' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: cl_repo: smartcontractkit/chainlink cl_ref: ${{ github.sha }} @@ -91,14 +91,14 @@ jobs: steps: - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} this-job-name: Build Test Image continue-on-error: true - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.head_ref || github.ref_name }} - name: Build Test Image @@ -115,7 +115,7 @@ jobs: pull-requests: write id-token: write contents: read - needs: [ build-chainlink, build-test-image ] + needs: [build-chainlink, build-test-image] env: CHAINLINK_COMMIT_SHA: ${{ github.sha }} CHAINLINK_ENV_USER: ${{ github.actor }} @@ -149,7 +149,7 @@ jobs: name: Automation On Demand ${{ matrix.tests.name }} Test steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.head_ref || github.ref_name }} - name: Determine build to use @@ -172,7 +172,7 @@ jobs: echo "version=${{ inputs.chainlinkVersionUpdate }}" >>$GITHUB_OUTPUT fi - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 env: PYROSCOPE_SERVER: ${{ matrix.tests.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 PYROSCOPE_ENVIRONMENT: ${{ matrix.tests.pyroscope_env }} @@ -182,7 +182,7 @@ jobs: UPGRADE_VERSION: ${{ steps.determine-build.outputs.upgrade_version }} UPGRADE_IMAGE: ${{ steps.determine-build.outputs.upgrade_image }} with: - test_command_to_run: make test_need_operator_assets && cd ./integration-tests && go test -timeout 60m -count=1 -json -test.parallel=${{ matrix.tests.nodes }} ${{ matrix.tests.command }} 2>&1 | tee /tmp/gotest.log | gotestfmt + test_command_to_run: cd ./integration-tests && go test -timeout 60m -count=1 -json -test.parallel=${{ matrix.tests.nodes }} ${{ matrix.tests.command }} 2>&1 | tee /tmp/gotest.log | gotestfmt test_download_vendor_packages_command: cd ./integration-tests && go mod download cl_repo: ${{ steps.determine-build.outputs.image }} cl_image_tag: ${{ steps.determine-build.outputs.version }} @@ -205,7 +205,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/build-publish-develop.yml b/.github/workflows/build-publish-develop.yml index cba8edba3e2..b8859722378 100644 --- a/.github/workflows/build-publish-develop.yml +++ b/.github/workflows/build-publish-develop.yml @@ -31,7 +31,7 @@ jobs: name: push-chainlink-develop ${{ matrix.image.name }} steps: - name: Checkout repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ env.GIT_REF }} # When this is ran from manual workflow_dispatch, the github.sha may be @@ -52,11 +52,13 @@ jobs: ecr-image-name: chainlink ecr-tag-suffix: ${{ matrix.image.tag-suffix }} dockerfile: ${{ matrix.image.dockerfile }} + dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} + dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} git-commit-sha: ${{ steps.git-ref.outputs.checked-out || github.sha }} - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/build-publish-pr.yml b/.github/workflows/build-publish-pr.yml new file mode 100644 index 00000000000..a7186ee5a11 --- /dev/null +++ b/.github/workflows/build-publish-pr.yml @@ -0,0 +1,44 @@ +name: "Build and Publish from PR" + +## +# This workflow builds and publishes a Docker image for Chainlink from a PR. +# It doesn't use an environment, has its own special IAM role, does not sign +# the image, and publishes to a special ECR repo. +## + +on: + pull_request: + +jobs: + build-publish-untrusted: + if: ${{ ! startsWith(github.ref_name, 'release/') }} + runs-on: ubuntu-20.04 + permissions: + id-token: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Build and publish chainlink image + uses: ./.github/actions/build-sign-publish-chainlink + with: + publish: true + aws-role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_PUBLISH_PR_ARN }} + aws-role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS_DEFAULT }} + aws-region: ${{ secrets.AWS_REGION }} + sign-images: false + ecr-hostname: ${{ secrets.AWS_SDLC_ECR_HOSTNAME }} + ecr-image-name: crib-chainlink-untrusted + dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} + dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} + + - name: Collect Metrics + if: always() + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 + with: + basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} + this-job-name: build-publish-untrusted + continue-on-error: true diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 9aa7b9accc5..de33663d88d 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -1,23 +1,24 @@ -name: 'Build Chainlink and Publish' +name: "Build Chainlink and Publish" on: # Mimics old circleci behaviour push: tags: - - 'v*' + - "v*" branches: - master - - 'release/**' + - "release/**" jobs: checks: - name: 'Checks' + name: "Checks" runs-on: ubuntu-20.04 steps: - name: Checkout repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Check for VERSION file bump on tags - if: ${{ startsWith(github.ref, 'refs/tags/v') }} + # Avoids checking VERSION file bump on forks. + if: ${{ github.repository == 'smartcontractkit/chainlink' && startsWith(github.ref, 'refs/tags/v') }} uses: ./.github/actions/version-file-bump with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -32,7 +33,7 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Build, sign and publish chainlink image uses: ./.github/actions/build-sign-publish-chainlink @@ -42,15 +43,17 @@ jobs: aws-role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} aws-region: ${{ secrets.AWS_REGION }} sign-images: true - sign-method: 'keypair' + sign-method: "keypair" cosign-private-key: ${{ secrets.COSIGN_PRIVATE_KEY }} cosign-public-key: ${{ secrets.COSIGN_PUBLIC_KEY }} cosign-password: ${{ secrets.COSIGN_PASSWORD }} + dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} + dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} verify-signature: true - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9931137d637..6282e2168d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: 'Build Chainlink' +name: "Build Chainlink" on: pull_request: @@ -7,22 +7,23 @@ on: - master jobs: - build-chainlink: runs-on: ubuntu-20.04 steps: - name: Checkout repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Build chainlink image uses: ./.github/actions/build-sign-publish-chainlink with: + dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} + dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} publish: false sign-images: false - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index a8e6e266a96..c7ca0b880c2 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -31,7 +31,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/ci-chaincli.yml b/.github/workflows/ci-chaincli.yml index 97225e46557..fd58d08005c 100644 --- a/.github/workflows/ci-chaincli.yml +++ b/.github/workflows/ci-chaincli.yml @@ -14,7 +14,7 @@ jobs: name: chaincli-lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Golang Lint uses: ./.github/actions/golangci-lint with: diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index d6991bb4e67..fd8dfed88ab 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -22,12 +22,20 @@ jobs: name: lint runs-on: ubuntu20.04-8cores-32GB steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Golang Lint uses: ./.github/actions/golangci-lint with: gc-basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} gc-host: ${{ secrets.GRAFANA_CLOUD_HOST }} + - name: Notify Slack + if: ${{ failure() && (github.event_name == 'merge_group' || github.event.branch == 'develop')}} + uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + with: + channel-id: "#team-core" + slack-message: "golangci-lint failed: ${{ job.html_url }}\n${{ format('https://github.com/smartcontractkit/chainlink/actions/runs/{0}', github.run_id) }}" core: strategy: @@ -40,9 +48,9 @@ jobs: CL_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/chainlink_test?sslmode=disable steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 - name: Setup NodeJS uses: ./.github/actions/setup-nodejs with: @@ -76,7 +84,7 @@ jobs: run: ./tools/bin/${{ matrix.cmd }} ./... - name: Print Filtered Test Results if: ${{ failure() && matrix.cmd == 'go_core_tests' }} - uses: smartcontractkit/chainlink-github-actions/go/go-test-results-parsing@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/go/go-test-results-parsing@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: results-file: ./output.txt output-file: ./output-short.txt @@ -99,7 +107,7 @@ jobs: ./coverage.txt ./postgres_logs.txt - name: Notify Slack - if: ${{ failure() && matrix.cmd == 'go_core_race_tests' && github.event.schedule != '' }} + if: ${{ failure() && matrix.cmd == 'go_core_race_tests' && (github.event_name == 'merge_group' || github.event.branch == 'develop') }} uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 env: SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} @@ -109,7 +117,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -126,9 +134,9 @@ jobs: CL_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/chainlink_test?sslmode=disable steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 - name: Setup NodeJS uses: ./.github/actions/setup-nodejs with: @@ -156,12 +164,18 @@ jobs: GRAFANA_CLOUD_BASIC_AUTH: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} GRAFANA_CLOUD_HOST: ${{ secrets.GRAFANA_CLOUD_HOST }} GITHUB_EVENT_PATH: ${{ github.event_path }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REPO: ${{ github.repository }} + GITHUB_RUN_ID: ${{ github.run_id }} run: | ./runner \ -grafana_auth=$GRAFANA_CLOUD_BASIC_AUTH \ -grafana_host=$GRAFANA_CLOUD_HOST \ -gh_sha=$GITHUB_SHA \ -gh_event_path=$GITHUB_EVENT_PATH \ + -gh_event_name=$GITHUB_EVENT_NAME \ + -gh_run_id=$GITHUB_RUN_ID \ + -gh_repo=$GITHUB_REPO \ -command=./tools/bin/go_core_tests \ `ls -R ./artifacts/go_core_tests*/output.txt` - name: Store logs artifacts @@ -179,7 +193,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: fetch-depth: 0 # fetches all history for all tags and branches to provide more metadata for sonar reports - name: Download all workflow run artifacts @@ -203,7 +217,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -223,7 +237,7 @@ jobs: run: | echo "## \`skip-smoke-tests\` label is active, skipping E2E smoke tests" >>$GITHUB_STEP_SUMMARY exit 0 - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: fetch-depth: 0 - name: Setup Go @@ -241,7 +255,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 86f2515e26d..8bc066f408c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Go if: ${{ matrix.language == 'go' }} @@ -45,7 +45,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/delete-deployments.yml b/.github/workflows/delete-deployments.yml index a3d3100516d..dc3c17852b1 100644 --- a/.github/workflows/delete-deployments.yml +++ b/.github/workflows/delete-deployments.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Clean up integration environment uses: ./.github/actions/delete-deployments @@ -24,7 +24,7 @@ jobs: - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index ebfe1b947a9..dbf08895757 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -11,7 +11,7 @@ jobs: changes: ${{ steps.changes.outputs.src }} steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 id: changes with: @@ -25,7 +25,7 @@ jobs: needs: [changes] steps: - name: Check out code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Go if: needs.changes.outputs.src == 'true' @@ -47,7 +47,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/goreleaser-build-publish-develop.yml b/.github/workflows/goreleaser-build-publish-develop.yml index 9e9b088033e..514067fd85d 100644 --- a/.github/workflows/goreleaser-build-publish-develop.yml +++ b/.github/workflows/goreleaser-build-publish-develop.yml @@ -18,9 +18,9 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Configure aws credentials - uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 + uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN }} role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} @@ -39,7 +39,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -55,9 +55,9 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Configure aws credentials - uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 + uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN_GATI }} role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} diff --git a/.github/workflows/helm-publish.yml b/.github/workflows/helm-publish.yml index e80d758d810..6ea46e6a52d 100644 --- a/.github/workflows/helm-publish.yml +++ b/.github/workflows/helm-publish.yml @@ -12,10 +12,10 @@ jobs: contents: read steps: - name: Checkout repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Configure aws credentials - uses: aws-actions/configure-aws-credentials@50ac8dd1e1b10d09dac7b8727528b91bed831ac0 # v3.0.2 + uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN_GATI }} role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} diff --git a/.github/workflows/integration-chaos-tests.yml b/.github/workflows/integration-chaos-tests.yml index a9aa2c35df1..10c62810996 100644 --- a/.github/workflows/integration-chaos-tests.yml +++ b/.github/workflows/integration-chaos-tests.yml @@ -27,10 +27,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Check if image exists id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: repository: chainlink tag: ${{ github.sha }} @@ -38,7 +38,7 @@ jobs: AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - name: Build Image if: steps.check-image.outputs.exists == 'false' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: cl_repo: smartcontractkit/chainlink cl_ref: ${{ github.sha }} @@ -53,7 +53,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Build Test Image uses: ./.github/actions/build-test-image with: @@ -79,7 +79,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -99,7 +99,7 @@ jobs: steps: - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -107,11 +107,11 @@ jobs: test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' continue-on-error: true - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: - test_command_to_run: make test_need_operator_assets && cd integration-tests && go test -timeout 1h -count=1 -json -test.parallel 11 ./chaos 2>&1 | tee /tmp/gotest.log | gotestfmt + test_command_to_run: cd integration-tests && go test -timeout 1h -count=1 -json -test.parallel 11 ./chaos 2>&1 | tee /tmp/gotest.log | gotestfmt test_download_vendor_packages_command: cd ./integration-tests && go mod download cl_repo: ${{ env.CHAINLINK_IMAGE }} cl_image_tag: ${{ github.sha }} diff --git a/.github/workflows/integration-staging-tests.yml b/.github/workflows/integration-staging-tests.yml index a6fda178d50..2abb9a3cfae 100644 --- a/.github/workflows/integration-staging-tests.yml +++ b/.github/workflows/integration-staging-tests.yml @@ -50,7 +50,7 @@ jobs: WASP_LOG_LEVEL: info steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: fetch-depth: 0 - name: Run E2E soak tests diff --git a/.github/workflows/integration-tests-publish.yml b/.github/workflows/integration-tests-publish.yml index c71b83d1c4d..176947a092d 100644 --- a/.github/workflows/integration-tests-publish.yml +++ b/.github/workflows/integration-tests-publish.yml @@ -5,9 +5,11 @@ on: push: branches: - develop + workflow_dispatch: env: ECR_TAG: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests:develop + CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink jobs: publish-integration-test-image: @@ -16,24 +18,80 @@ jobs: id-token: write contents: read name: Publish Integration Test Image - runs-on: ubuntu-latest + runs-on: ubuntu20.04-16cores-64GB steps: - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} this-job-name: Publish Integration Test Image continue-on-error: true - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Setup Other Tags If Not Workflow Dispatch + id: tags + if: github.event_name != 'workflow_dispatch' + run: | + echo "other_tags=${ECR_TAG}" >> $GITHUB_OUTPUT - name: Build Image uses: ./.github/actions/build-test-image with: - other_tags: ${{ env.ECR_TAG }} + other_tags: ${{ steps.tags.outputs.other_tags }} QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + - name: Notify Slack + # Only run this notification for merge to develop failures + if: failure() && github.event_name != 'workflow_dispatch' + uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + with: + channel-id: "#team-test-tooling-internal" + slack-message: ":x: :mild-panic-intensifies: Publish Integration Test Image failed: ${{ job.html_url }}\n${{ format('https://github.com/smartcontractkit/chainlink/actions/runs/{0}', github.run_id) }}" + build-chainlink-image: + environment: integration + # Only run this build for workflow_dispatch + if: github.event_name == 'workflow_dispatch' + permissions: + id-token: write + contents: read + strategy: + matrix: + image: + - name: "" + dockerfile: core/chainlink.Dockerfile + tag-suffix: "" + # uncomment in the future if we end up needing to soak test the plugins image + # - name: (plugins) + # dockerfile: plugins/chainlink.Dockerfile + # tag-suffix: -plugins + name: Build Chainlink Image ${{ matrix.image.name }} + runs-on: ubuntu20.04-8cores-32GB + steps: + - name: Collect Metrics + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 + with: + basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} + this-job-name: Build Chainlink Image ${{ matrix.image.name }} + continue-on-error: true + - name: Checkout the repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ github.sha }} + - name: Build Chainlink Image + uses: ./.github/actions/build-chainlink-image + with: + tag_suffix: ${{ matrix.image.tag-suffix }} + dockerfile: ${{ matrix.image.dockerfile }} + git_commit_sha: ${{ github.sha }} + GRAFANA_CLOUD_BASIC_AUTH: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + GRAFANA_CLOUD_HOST: ${{ secrets.GRAFANA_CLOUD_HOST }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index b1a1e663569..e51d783a6ed 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -2,12 +2,10 @@ name: Integration Tests on: merge_group: pull_request: - schedule: - - cron: "0 0 * * *" - # - cron: "0 * * * *" # DEBUG: Run every hour to nail down flakes push: tags: - "*" + workflow_dispatch: # Only run 1 of this workflow at a time per PR concurrency: @@ -24,13 +22,41 @@ env: MOD_CACHE_VERSION: 2 jobs: + enforce-ctf-version: + name: Enforce CTF Version + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Check Merge Group Condition + id: condition-check + run: | + echo "Checking event condition..." + SHOULD_ENFORCE="false" + if [[ "$GITHUB_EVENT_NAME" == "merge_group" ]]; then + echo "We are in a merge_group event, now check if we are on the develop branch" + target_branch=$(cat $GITHUB_EVENT_PATH | jq -r .merge_group.base_ref) + if [[ "$target_branch" == "refs/heads/develop" ]]; then + echo "We are on the develop branch, we should enforce ctf version" + SHOULD_ENFORCE="true" + fi + fi + echo "should we enforce ctf version = $SHOULD_ENFORCE" + echo "should-enforce=$SHOULD_ENFORCE" >> $GITHUB_OUTPUT + - name: Enforce CTF Version + if: steps.condition-check.outputs.should-enforce == 'true' + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/mod-version@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + with: + go-project-path: ./integration-tests + module-name: github.com/smartcontractkit/chainlink-testing-framework + enforce-semantic-tag: "true" changes: environment: integration name: Check Paths That Require Tests To Run runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 id: changes with: @@ -45,7 +71,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -53,6 +79,37 @@ jobs: continue-on-error: true outputs: src: ${{ steps.changes.outputs.src }} + + build-lint-integration-tests: + name: Build and Lint integration-tests + runs-on: ubuntu20.04-16cores-64GB + steps: + - name: Checkout the repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Setup Go + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-go@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + with: + test_download_vendor_packages_command: cd ./integration-tests && go mod download + go_mod_path: ./integration-tests/go.mod + cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} + cache_restore_only: "true" + - name: Build Go + run: | + cd ./integration-tests + go build ./... + SELECTED_NETWORKS=SIMULATED go test -run=^# ./... + - name: Lint Go + uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 + with: + version: v1.55.2 + # We already cache these directories in setup-go + skip-pkg-cache: true + skip-build-cache: true + # only-new-issues is only applicable to PRs, otherwise it is always set to false + only-new-issues: false # disabled for PRs due to unreliability + args: --out-format colored-line-number,checkstyle:golangci-lint-report.xml + working-directory: ./integration-tests + build-chainlink: environment: integration permissions: @@ -69,45 +126,32 @@ jobs: tag-suffix: -plugins name: Build Chainlink Image ${{ matrix.image.name }} runs-on: ubuntu20.04-16cores-64GB - needs: [changes] + needs: [changes, enforce-ctf-version] steps: - name: Collect Metrics if: needs.changes.outputs.src == 'true' id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} this-job-name: Build Chainlink Image ${{ matrix.image.name }} continue-on-error: true - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Check if image exists + - name: Build Chainlink Image if: needs.changes.outputs.src == 'true' - id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: ./.github/actions/build-chainlink-image with: - repository: chainlink - tag: ${{ github.sha }}${{ matrix.image.tag-suffix }} + tag_suffix: ${{ matrix.image.tag-suffix }} + dockerfile: ${{ matrix.image.dockerfile }} + git_commit_sha: ${{ github.sha }} + GRAFANA_CLOUD_BASIC_AUTH: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + GRAFANA_CLOUD_HOST: ${{ secrets.GRAFANA_CLOUD_HOST }} AWS_REGION: ${{ secrets.QA_AWS_REGION }} AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - name: Build Image - if: steps.check-image.outputs.exists == 'false' && needs.changes.outputs.src == 'true' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 - with: - cl_repo: smartcontractkit/chainlink - cl_ref: ${{ github.sha }} - cl_dockerfile: ${{ matrix.image.dockerfile }} - push_tag: ${{ env.CHAINLINK_IMAGE }}:${{ github.sha }}${{ matrix.image.tag-suffix }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - name: Print Chainlink Image Built - if: needs.changes.outputs.src == 'true' - run: | - echo "### Chainlink node image tag used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY build-test-image: if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'schedule' || contains(join(github.event.pull_request.labels.*.name, ' '), 'build-test-image') @@ -122,14 +166,14 @@ jobs: - name: Collect Metrics if: needs.changes.outputs.src == 'true' id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} this-job-name: Build Test Image continue-on-error: true - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - name: Build Test Image @@ -153,7 +197,7 @@ jobs: echo "## \`skip-smoke-tests\` label is active, skipping E2E smoke tests" >>$GITHUB_STEP_SUMMARY exit 0 - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Compare Test Lists run: | cd ./integration-tests @@ -169,14 +213,14 @@ jobs: echo "MATRIX_JSON=${COMBINED_ARRAY}" >> $GITHUB_ENV eth-smoke-tests-matrix-automation: - if: ${{ !contains(join(github.event.pull_request.labels.*.name, ' '), 'skip-smoke-tests') }} + if: ${{ !(contains(join(github.event.pull_request.labels.*.name, ' '), 'skip-smoke-tests') || (github.event_name == 'workflow_dispatch' && !inputs.simulatedNetwork)) }} environment: integration permissions: checks: write pull-requests: write id-token: write contents: read - needs: [build-chainlink, changes, compare-tests] + needs: [build-chainlink, changes, compare-tests, build-lint-integration-tests] env: SELECTED_NETWORKS: SIMULATED,SIMULATED_1,SIMULATED_2 CHAINLINK_COMMIT_SHA: ${{ github.sha }} @@ -190,7 +234,7 @@ jobs: name: ETH Smoke Tests ${{ matrix.product.name }} steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - name: Build Go Test Command @@ -205,13 +249,13 @@ jobs: ## Run this step when changes that require tests to be run are made - name: Run Tests if: needs.changes.outputs.src == 'true' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 env: PYROSCOPE_SERVER: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 PYROSCOPE_ENVIRONMENT: ${{ matrix.product.pyroscope_env }} PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} with: - test_command_to_run: make test_need_operator_assets && cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=${{ matrix.product.nodes }} ${{ steps.build-go-test-command.outputs.run_command }} 2>&1 | tee /tmp/gotest.log | gotestfmt + test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=${{ matrix.product.nodes }} ${{ steps.build-go-test-command.outputs.run_command }} 2>&1 | tee /tmp/gotest.log | gotestfmt test_download_vendor_packages_command: cd ./integration-tests && go mod download cl_repo: ${{ env.CHAINLINK_IMAGE }} cl_image_tag: ${{ github.sha }} @@ -224,11 +268,11 @@ jobs: cache_restore_only: "true" QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + QA_KUBECONFIG: "" - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -237,14 +281,14 @@ jobs: continue-on-error: true eth-smoke-tests-matrix: - if: ${{ !contains(join(github.event.pull_request.labels.*.name, ' '), 'skip-smoke-tests') }} + if: ${{ !(contains(join(github.event.pull_request.labels.*.name, ' '), 'skip-smoke-tests') || (github.event_name == 'workflow_dispatch' && !inputs.simulatedNetwork)) }} environment: integration permissions: checks: write pull-requests: write id-token: write contents: read - needs: [build-chainlink, changes] + needs: [build-chainlink, changes, build-lint-integration-tests] env: SELECTED_NETWORKS: SIMULATED,SIMULATED_1,SIMULATED_2 CHAINLINK_COMMIT_SHA: ${{ github.sha }} @@ -289,7 +333,7 @@ jobs: pyroscope_env: ci-smoke-vrf2-evm-simulated - name: vrfv2plus nodes: 1 - os: ubuntu-latest + os: ubuntu20.04-8cores-32GB pyroscope_env: ci-smoke-vrf2plus-evm-simulated - name: forwarder_ocr nodes: 1 @@ -303,7 +347,7 @@ jobs: name: ETH Smoke Tests ${{ matrix.product.name }}${{ matrix.product.tag_suffix }} steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - name: Build Go Test Command @@ -340,77 +384,43 @@ jobs: # Create network docker network create --driver bridge tracing - # Start Grafana - cd ./.github/tracing - docker run -d --network=tracing --name=grafana -p 3000:3000 -v $PWD/grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml -e GF_AUTH_ANONYMOUS_ENABLED=true -e GF_AUTH_ANONYMOUS_ORG_ROLE=Admin -e GF_AUTH_DISABLE_LOGIN_FORM=true -e GF_FEATURE_TOGGLES_ENABLE=traceqlEditor grafana/grafana:9.4.3 + # Make trace directory + cd integration-tests/smoke/ + mkdir ./traces + chmod -R 777 ./traces - # Start Tempo - docker run -d --network=tracing --name=tempo -v ./tempo.yaml:/etc/tempo.yaml -v $PWD/tempo-data:/tmp/tempo grafana/tempo:latest -config.file=/etc/tempo.yaml + # Switch directory + cd ../../.github/tracing - # Start OpenTelemetry Collector - docker run -d --network=tracing --name=otel-collector -v $PWD/otel-collector.yaml:/etc/otel-collector.yaml -p 4317:4317 otel/opentelemetry-collector:0.61.0 --config=/etc/otel-collector.yaml + # Create a Docker volume for traces + # docker volume create otel-traces - - name: Generate port - id: generate-port - if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' - env: - GITHUB_PR_NUMBER: ${{ github.event.number }} - run: | - PORT_BASE=3001 - MAX_PORT=8000 - - # Use PR number as offset. Given GitHub PRs are incremental, this guarantees uniqueness for at least 5000 PRs. - OFFSET=$GITHUB_PR_NUMBER - echo "PR Number: $OFFSET" - - # Ensure that we don't exceed the max port - if (( OFFSET > (MAX_PORT - PORT_BASE) )); then - OFFSET=$((OFFSET % (MAX_PORT - PORT_BASE))) - fi - - # Map the offset to the port range - REMOTE_PORT=$((PORT_BASE + OFFSET)) - echo "REMOTE_PORT=$REMOTE_PORT" >> $GITHUB_OUTPUT - - name: Reverse SSH Tunneling - if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' - env: - TRACING_SSH_KEY: ${{ secrets.TRACING_SSH_KEY }} - TRACING_SSH_SERVER: ${{ secrets.TRACING_SSH_SERVER }} - REMOTE_PORT: ${{ steps.generate-port.outputs.REMOTE_PORT }} + # Start OpenTelemetry Collector + # Note the user must be set to the same user as the runner for the trace data to be accessible + docker run -d --network=tracing --name=otel-collector \ + -v $PWD/otel-collector-ci.yaml:/etc/otel-collector.yaml \ + -v $PWD/../../integration-tests/smoke/traces:/tracing \ + --user "$(id -u):$(id -g)" \ + -p 4317:4317 otel/opentelemetry-collector:0.88.0 --config=/etc/otel-collector.yaml + - name: Locate Docker Volume + id: locate-volume + if: false run: | - eval $(ssh-agent) - echo "test" - echo "$TRACING_SSH_KEY" | wc -c - echo "$TRACING_SSH_KEY" | tr -d '\r' | wc -c - echo "$TRACING_SSH_KEY" | tr -d '\r' | base64 --decode | ssh-add - - # f: background process - # N: do not execute a remote command - # R: remote port forwarding - ssh -o StrictHostKeyChecking=no -f -N -R $REMOTE_PORT:127.0.0.1:3000 user-gha@$TRACING_SSH_SERVER - echo "To view Grafana locally:" - echo "ssh -N -L 8000:localhost:$REMOTE_PORT user-gha@$TRACING_SSH_SERVER" - echo "Then visit http://localhost:8000 in a browser." - echo "If you are unable to connect, check with the security team that you have access to the tracing server." - - name: Show Grafana Logs + echo "VOLUME_PATH=$(docker volume inspect --format '{{ .Mountpoint }}' otel-traces)" >> $GITHUB_OUTPUT + - name: Show Otel-Collector Logs if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' run: | - docker logs grafana - docker logs tempo docker logs otel-collector - - name: Set sleep time to use in future steps - if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' - run: | - echo "SLEEP_TIME=2400" >> "$GITHUB_ENV" ## Run this step when changes that require tests to be run are made - name: Run Tests if: needs.changes.outputs.src == 'true' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 env: PYROSCOPE_SERVER: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 PYROSCOPE_ENVIRONMENT: ${{ matrix.product.pyroscope_env }} PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} with: - test_command_to_run: make test_need_operator_assets && cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=${{ matrix.product.nodes }} ${{ steps.build-go-test-command.outputs.run_command }} 2>&1 | tee /tmp/gotest.log | gotestfmt + test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=${{ matrix.product.nodes }} ${{ steps.build-go-test-command.outputs.run_command }} 2>&1 | tee /tmp/gotest.log | gotestfmt test_download_vendor_packages_command: cd ./integration-tests && go mod download cl_repo: ${{ env.CHAINLINK_IMAGE }} cl_image_tag: ${{ github.sha }}${{ matrix.product.tag_suffix }} @@ -424,11 +434,11 @@ jobs: cache_restore_only: "true" QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + QA_KUBECONFIG: "" ## Run this step when changes that do not need the test to run are made - name: Run Setup if: needs.changes.outputs.src == 'false' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-run-tests-environment@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-run-tests-environment@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: test_download_vendor_packages_command: cd ./integration-tests && go mod download go_mod_path: ./integration-tests/go.mod @@ -437,21 +447,31 @@ jobs: QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + - name: Show Otel-Collector Logs + if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' + run: | + docker logs otel-collector - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} - this-job-name: ETH Smoke Tests ${{ matrix.product.name }} + this-job-name: ETH Smoke Tests ${{ matrix.product.name }}${{ matrix.product.tag_suffix }} test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' continue-on-error: true - - name: Keep action running to view traces + - name: Permissions on traces if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' run: | - echo "Sleeping for $SLEEP_TIME seconds..." - sleep $SLEEP_TIME + ls -l ./integration-tests/smoke/traces + - name: Upload Trace Data + if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' + uses: actions/upload-artifact@v3 + with: + name: trace-data + path: ./integration-tests/smoke/traces/trace-data.json + ### Used to check the required checks box when the matrix completes eth-smoke-tests: @@ -468,7 +488,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -484,7 +504,7 @@ jobs: steps: - name: Checkout repo if: ${{ github.event_name == 'pull_request' }} - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: 🧼 Clean up Environment if: ${{ github.event_name == 'pull_request' }} @@ -496,7 +516,7 @@ jobs: - name: Collect Metrics if: ${{ github.event_name == 'pull_request' }} id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -513,11 +533,11 @@ jobs: continue-on-error: true steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - name: Run Setup - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-go@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-go@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: test_download_vendor_packages_command: | cd ./integration-tests @@ -552,7 +572,7 @@ jobs: TEST_SUITE: migration steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - name: Get Latest Version @@ -565,9 +585,9 @@ jobs: run: | echo "Running migration tests from version '${{ steps.get_latest_version.outputs.latest_version }}' to: '${{ github.sha }}'" - name: Run Migration Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: - test_command_to_run: make test_need_operator_assets && cd ./integration-tests && go test -timeout 30m -count=1 -json ./migration 2>&1 | tee /tmp/gotest.log | gotestfmt + test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json ./migration 2>&1 | tee /tmp/gotest.log | gotestfmt test_download_vendor_packages_command: cd ./integration-tests && go mod download cl_repo: ${{ env.CHAINLINK_IMAGE }} cl_image_tag: ${{ steps.get_latest_version.outputs.latest_version }} @@ -591,7 +611,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -608,18 +628,26 @@ jobs: sha: ${{ steps.getsha.outputs.sha }} steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + - name: Setup Go + uses: ./.github/actions/setup-go + with: + only-modules: "true" - name: Get the sha from go mod id: getshortsha run: | sol_ver=$(go list -m -json github.com/smartcontractkit/chainlink-solana | jq -r .Version) + if [ -z "${sol_ver}" ]; then + echo "Error: could not get the solana version from the go.mod file, look above for error(s)" + exit 1 + fi short_sha="${sol_ver##*-}" echo "short sha is: ${short_sha}" echo "short_sha=${short_sha}" >> "$GITHUB_OUTPUT" - name: Checkout solana - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: repository: smartcontractkit/chainlink-solana ref: develop @@ -630,6 +658,10 @@ jobs: run: | cd solanapath full_sha=$(git rev-parse ${{steps.getshortsha.outputs.short_sha}}) + if [ -z "${full_sha}" ]; then + echo "Error: could not get the full sha from the short sha using git, look above for error(s)" + exit 1 + fi echo "sha is: ${full_sha}" echo "sha=${full_sha}" >> "$GITHUB_OUTPUT" @@ -642,7 +674,7 @@ jobs: projectserum_version: ${{ steps.psversion.outputs.projectserum_version }} steps: - name: Checkout the solana repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: repository: smartcontractkit/chainlink-solana ref: ${{ needs.get_solana_sha.outputs.sha }} @@ -665,7 +697,7 @@ jobs: steps: - name: Check if image exists id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: repository: chainlink-solana-tests tag: ${{ needs.get_solana_sha.outputs.sha }} @@ -697,13 +729,15 @@ jobs: - name: Collect Metrics if: needs.changes.outputs.src == 'true' id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} this-job-name: Solana Build Artifacts continue-on-error: true - name: Checkout the solana repo + # Use v3.6.0 because the custom runner (container configured above) + # doesn't have node20 installed which is required for versions >=4 uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: repository: smartcontractkit/chainlink-solana @@ -736,7 +770,7 @@ jobs: - name: Collect Metrics if: needs.changes.outputs.src == 'true' && needs.solana-test-image-exists.outputs.exists == 'false' id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -744,10 +778,10 @@ jobs: continue-on-error: true - name: Checkout the repo if: needs.changes.outputs.src == 'true' && needs.solana-test-image-exists.outputs.exists == 'false' - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: repository: smartcontractkit/chainlink-solana - ref: a28100b7f2954604a8ca2ff9ec7bccc6ec952953 # swtich back to this after the next solana release${{ needs.get_solana_sha.outputs.sha }} + ref: ${{ needs.get_solana_sha.outputs.sha }} - name: Build Test Image if: needs.changes.outputs.src == 'true' && needs.solana-test-image-exists.outputs.exists == 'false' uses: ./.github/actions/build-test-image @@ -760,7 +794,7 @@ jobs: - run: echo "this exists so we don't have to run anything else if the build is skipped" if: needs.changes.outputs.src == 'false' || needs.solana-test-image-exists.outputs.exists == 'true' - solana-smoke-tests-matrix: + solana-smoke-tests: if: ${{ !contains(join(github.event.pull_request.labels.*.name, ' '), 'skip-smoke-tests') }} environment: integration permissions: @@ -768,14 +802,7 @@ jobs: pull-requests: write id-token: write contents: read - strategy: - matrix: - image: - - name: (legacy) - tag-suffix: "" - - name: (plugins) - tag-suffix: -plugins - name: Solana Smoke Tests ${{ matrix.image.name }} + name: Solana Smoke Tests runs-on: ubuntu20.04-16cores-64GB needs: [ @@ -794,26 +821,28 @@ jobs: - name: Collect Metrics if: needs.changes.outputs.src == 'true' id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} - this-job-name: Solana Smoke Tests ${{ matrix.image.name }} + this-job-name: Solana Smoke Tests test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' continue-on-error: true - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: repository: smartcontractkit/chainlink-solana ref: a28100b7f2954604a8ca2ff9ec7bccc6ec952953 # temporarily using specific commit for release branch ${{ needs.get_solana_sha.outputs.sha }} - name: Run Setup if: needs.changes.outputs.src == 'true' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-run-tests-environment@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-run-tests-environment@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: go_mod_path: ./integration-tests/go.mod cache_restore_only: true cache_key_id: core-solana-e2e-${{ env.MOD_CACHE_VERSION }} aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} + dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} @@ -835,11 +864,11 @@ jobs: docker rm "$CONTAINER_ID" - name: Run Tests if: needs.changes.outputs.src == 'true' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: test_command_to_run: export ENV_JOB_IMAGE=${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-solana-tests:${{ needs.get_solana_sha.outputs.sha }} && make test_smoke cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }}${{ matrix.image.tag-suffix }} + cl_image_tag: ${{ github.sha }} artifacts_location: /home/runner/work/chainlink-solana/chainlink-solana/integration-tests/logs publish_check_name: Solana Smoke Test Results go_mod_path: ./integration-tests/go.mod @@ -848,7 +877,7 @@ jobs: aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + QA_KUBECONFIG: "" run_setup: false - name: Upload test log uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 @@ -858,221 +887,3 @@ jobs: path: /tmp/gotest.log retention-days: 7 continue-on-error: true - - ### Used to check the required checks box when the matrix completes - solana-smoke-tests: - if: always() - runs-on: ubuntu-latest - name: Solana Smoke Tests - needs: [solana-smoke-tests-matrix] - steps: - - name: Check smoke test matrix status - if: needs.solana-smoke-tests-matrix.result != 'success' - run: exit 1 - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 - with: - basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} - this-job-name: Solana Smoke Tests - matrix-aggregator-status: ${{ needs.solana-smoke-tests-matrix.result }} - continue-on-error: true - ### End Solana Section - - ### Start Live Testnet Section - - testnet-smoke-tests-matrix: - if: ${{ github.event_name == 'schedule' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) }} ## Only run live tests on new tags and nightly - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink] - env: - SELECTED_NETWORKS: ${{ matrix.testnet }} - CHAINLINK_COMMIT_SHA: ${{ github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - TEST_LOG_LEVEL: debug - EVM_KEYS: ${{ secrets.QA_EVM_KEYS }} - - OPTIMISM_GOERLI_URLS: ${{ secrets.QA_OPTIMISM_GOERLI_URLS }} - OPTIMISM_GOERLI_HTTP_URLS: ${{ secrets.QA_OPTIMISM_GOERLI_HTTP_URLS }} - - ARBITRUM_GOERLI_URLS: ${{ secrets.QA_ARBITRUM_GOERLI_URLS }} - ARBITRUM_GOERLI_HTTP_URLS: ${{ secrets.QA_ARBITRUM_GOERLI_HTTP_URLS }} - strategy: - fail-fast: false - matrix: - # NOTE: If changing this matrix, make sure to update the matrix in the testnet-smoke-tests-notify job to be the same - # otherwise reporting will be broken. Getting a single matrix for multiple jobs is a pain - # https://github.com/orgs/community/discussions/26284#discussioncomment-3251198 - testnet: [OPTIMISM_GOERLI, ARBITRUM_GOERLI] - name: Live Testnet Smoke Tests ${{ matrix.testnet }} - runs-on: ubuntu-latest - steps: - - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - with: - ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - ## Only run OCR smoke test for now - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 - env: - PYROSCOPE_SERVER: ${{ secrets.QA_PYROSCOPE_INSTANCE }} - PYROSCOPE_ENVIRONMENT: ci-smoke-ocr-evm-${{ matrix.testnet }} # TODO: Only for OCR for now - PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - with: - test_command_to_run: make test_need_operator_assets && cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=1 ./smoke/ocr_test.go 2>&1 | tee /tmp/gotest.log | gotestfmt - test_download_vendor_packages_command: cd ./integration-tests && go mod download - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_location: ./integration-tests/smoke/logs - publish_check_name: ${{ matrix.testnet }} OCR Smoke Test Results - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 - with: - basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} - this-job-name: Live Testnet Smoke Tests ${{ matrix.testnet }} - test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - continue-on-error: true - - testnet-smoke-tests-notify: - name: Live Testnet Start Slack Thread - if: ${{ always() && needs.testnet-smoke-tests-matrix.result != 'skipped' && needs.testnet-smoke-tests-matrix.result != 'cancelled' }} - environment: integration - outputs: - thread_ts: ${{ steps.slack.outputs.thread_ts }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - runs-on: ubuntu-latest - needs: testnet-smoke-tests-matrix - steps: - - name: Debug Result - run: echo ${{needs.testnet-smoke-tests-matrix.result}} - - name: Main Slack Notification - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 - id: slack - with: - channel-id: ${{ secrets.QA_SLACK_CHANNEL }} - payload: | - { - "attachments": [ - { - "color": "${{ needs.testnet-smoke-tests-matrix.result == 'success' && '#2E7D32' || '#C62828' }}", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "Live Smoke Test Results ${{ needs.testnet-smoke-tests-matrix.result == 'success' && ':white_check_mark:' || ':x:'}}", - "emoji": true - } - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|${{ github.ref_name }}> | <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}> | <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run>" - } - } - ] - } - ] - } - env: - SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - - testnet-smoke-tests-results: - name: Post Live Testnet Smoke Test Results - if: ${{ always() && needs.testnet-smoke-tests-matrix.result != 'skipped' && needs.testnet-smoke-tests-matrix.result != 'cancelled' }} - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - runs-on: ubuntu-latest - needs: testnet-smoke-tests-notify - strategy: - fail-fast: false - matrix: - # NOTE: If changing this matrix, make sure to update the matrix in the testnet-smoke-tests-matrix job to be the same - # otherwise reporting will be broken. Getting a single matrix for multiple jobs is a pain - # https://github.com/orgs/community/discussions/26284#discussioncomment-3251198 - testnet: [OPTIMISM_GOERLI, ARBITRUM_GOERLI] - steps: - - name: Get Results - id: test-results - run: | - echo "Querying test results" - - echo "status=$(curl \ - -H "Authorization: Bearer ${{ github.token }}" \ - 'https://api.github.com/repos/${{github.repository}}/actions/runs/${{ github.run_id }}/jobs' \ - | jq -r '.jobs[] | select(.name == "Live Testnet Smoke Tests ${{ matrix.testnet}}").steps[] | select(.name == "Run Tests").conclusion')" >> $GITHUB_OUTPUT - - echo "status=$(curl \ - -H "Authorization: Bearer ${{ github.token }}" \ - 'https://api.github.com/repos/${{github.repository}}/actions/runs/${{ github.run_id }}/jobs' \ - | jq -r '.jobs[] | select(.name == "Live Testnet Smoke Tests ${{ matrix.testnet}}").steps[] | select(.name == "Run Tests").conclusion')" - echo "thread_ts=${{ needs.testnet-smoke-tests-notify.outputs.thread_ts }}" - - - name: Test Details - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 - with: - channel-id: ${{ secrets.QA_SLACK_CHANNEL }} - payload: | - { - "thread_ts": "${{ needs.testnet-smoke-tests-notify.outputs.thread_ts }}", - "attachments": [ - { - "color": "${{ steps.test-results.outputs.status == 'success' && '#2E7D32' || '#C62828' }}", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "${{ matrix.testnet }} Smoke Test Results ${{ steps.test-results.outputs.status == 'success' && ':white_check_mark:' || ':x:'}}", - "emoji": true - } - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "OCR ${{ steps.test-results.outputs.status == 'success' && ':white_check_mark:' || ':x:'}}" - } - } - ] - } - ] - } - env: - SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - - ### End Live Testnet Section diff --git a/.github/workflows/lint-gh-workflows.yml b/.github/workflows/lint-gh-workflows.yml index 1220f3a745f..f1a3cc20803 100644 --- a/.github/workflows/lint-gh-workflows.yml +++ b/.github/workflows/lint-gh-workflows.yml @@ -7,13 +7,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out Code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Run actionlint uses: reviewdog/action-actionlint@82693e9e3b239f213108d6e412506f8b54003586 # v1.39.1 - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/live-testnet-tests.yml b/.github/workflows/live-testnet-tests.yml new file mode 100644 index 00000000000..f174e8847bd --- /dev/null +++ b/.github/workflows/live-testnet-tests.yml @@ -0,0 +1,385 @@ +name: Live Testnet Tests +on: + schedule: + - cron: "0 0 * * *" # Run nightly + push: + tags: + - "*" + workflow_dispatch: + +env: + CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink + INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com + MOD_CACHE_VERSION: 2 + CHAINLINK_NODE_FUNDING: .1 + + CHAINLINK_COMMIT_SHA: ${{ github.sha }} + CHAINLINK_ENV_USER: ${{ github.actor }} + TEST_LOG_LEVEL: debug + EVM_KEYS: ${{ secrets.QA_EVM_KEYS }} + + SEPOLIA_URLS: ${{ secrets.QA_SEPOLIA_URLS }} + SEPOLIA_HTTP_URLS: ${{ secrets.QA_SEPOLIA_HTTP_URLS }} + + OPTIMISM_GOERLI_URLS: ${{ secrets.QA_OPTIMISM_GOERLI_URLS }} + OPTIMISM_GOERLI_HTTP_URLS: ${{ secrets.QA_OPTIMISM_GOERLI_HTTP_URLS }} + + ARBITRUM_GOERLI_URLS: ${{ secrets.QA_ARBITRUM_GOERLI_URLS }} + ARBITRUM_GOERLI_HTTP_URLS: ${{ secrets.QA_ARBITRUM_GOERLI_HTTP_URLS }} + +jobs: + build-chainlink: + environment: integration + permissions: + id-token: write + contents: read + name: Build Chainlink Image + runs-on: ubuntu20.04-16cores-64GB + steps: + - name: Collect Metrics + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 + with: + basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} + this-job-name: Build Chainlink Image + continue-on-error: true + - name: Checkout the repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + - name: Build Chainlink Image + uses: ./.github/actions/build-chainlink-image + with: + tag_suffix: "" + dockerfile: core/chainlink.Dockerfile + git_commit_sha: ${{ github.sha }} + GRAFANA_CLOUD_BASIC_AUTH: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + GRAFANA_CLOUD_HOST: ${{ secrets.GRAFANA_CLOUD_HOST }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + + # TODO: Re-enable when we have secrets properly configured + # sepolia-smoke-tests: + # environment: integration + # permissions: + # checks: write + # pull-requests: write + # id-token: write + # contents: read + # needs: [build-chainlink] + # env: + # SELECTED_NETWORKS: SEPOLIA + # strategy: + # max-parallel: 1 + # fail-fast: false + # matrix: + # include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations + # - product: OCR + # test: TestOCRBasic + # - product: Automation + # test: TestAutomationBasic/registry_2_0 + # name: Sepolia ${{ matrix.product }} Tests + # runs-on: ubuntu-latest + # steps: + # - name: Checkout the repo + # uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + # with: + # ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + # - name: Run Tests + # uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + # env: + # PYROSCOPE_SERVER: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + # PYROSCOPE_ENVIRONMENT: ci-smoke-${{ matrix.product }}-sepolia + # PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + # with: + # test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=1 -run ${{ matrix.test }} ./smoke 2>&1 | tee /tmp/gotest.log | gotestfmt + # test_download_vendor_packages_command: cd ./integration-tests && go mod download + # cl_repo: ${{ env.CHAINLINK_IMAGE }} + # cl_image_tag: ${{ github.sha }} + # aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + # dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} + # dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} + # artifacts_location: ./integration-tests/smoke/logs + # publish_check_name: Seplia ${{ matrix.product }} Smoke Test Results + # token: ${{ secrets.GITHUB_TOKEN }} + # go_mod_path: ./integration-tests/go.mod + # cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} + # cache_restore_only: "true" + # QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + # QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + # QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + # - name: Collect Metrics + # if: always() + # id: collect-gha-metrics + # uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 + # with: + # basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + # hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} + # this-job-name: Sepolia ${{ matrix.product }} Tests + # test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' + # continue-on-error: true + + optimism-goerli-smoke-tests: + environment: integration + permissions: + checks: write + pull-requests: write + id-token: write + contents: read + needs: [build-chainlink] + env: + SELECTED_NETWORKS: OPTIMISM_GOERLI + strategy: + fail-fast: false + max-parallel: 1 + matrix: + include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations + - product: OCR + test: TestOCRBasic + - product: Automation + test: TestAutomationBasic/registry_2_0 + name: Optimism Goerli ${{ matrix.product }} Tests + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + - name: Run Tests + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + env: + PYROSCOPE_SERVER: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + PYROSCOPE_ENVIRONMENT: ci-smoke-${{ matrix.product }}-optimism-goerli + PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + with: + test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=1 -run ${{ matrix.test }} ./smoke 2>&1 | tee /tmp/gotest.log | gotestfmt + test_download_vendor_packages_command: cd ./integration-tests && go mod download + cl_repo: ${{ env.CHAINLINK_IMAGE }} + cl_image_tag: ${{ github.sha }} + aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} + dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} + artifacts_location: ./integration-tests/smoke/logs + publish_check_name: Seplia ${{ matrix.product }} Smoke Test Results + token: ${{ secrets.GITHUB_TOKEN }} + go_mod_path: ./integration-tests/go.mod + cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} + cache_restore_only: "true" + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + - name: Collect Metrics + if: always() + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 + with: + basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} + this-job-name: Optimism Goerli ${{ matrix.product }} Tests + test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' + continue-on-error: true + + arbitrum-goerli-smoke-tests: + environment: integration + permissions: + checks: write + pull-requests: write + id-token: write + contents: read + needs: [build-chainlink] + env: + SELECTED_NETWORKS: ARBITRUM_GOERLI + strategy: + max-parallel: 1 + fail-fast: false + matrix: + include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations + - product: OCR + test: TestOCRBasic + - product: Automation + test: TestAutomationBasic/registry_2_0 + name: Arbitrum Goerli ${{ matrix.product }} Tests + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + - name: Run Tests + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 + env: + PYROSCOPE_SERVER: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + PYROSCOPE_ENVIRONMENT: ci-smoke-${{ matrix.product }}-arbitrum-goerli + PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + with: + test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=1 -run ${{ matrix.test }} ./smoke 2>&1 | tee /tmp/gotest.log | gotestfmt + test_download_vendor_packages_command: cd ./integration-tests && go mod download + cl_repo: ${{ env.CHAINLINK_IMAGE }} + cl_image_tag: ${{ github.sha }} + aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} + dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} + artifacts_location: ./integration-tests/smoke/logs + publish_check_name: Arbitrum Goerli ${{ matrix.product }} Smoke Test Results + token: ${{ secrets.GITHUB_TOKEN }} + go_mod_path: ./integration-tests/go.mod + cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} + cache_restore_only: "true" + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + - name: Collect Metrics + if: always() + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 + with: + basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} + this-job-name: Arbitrum Goerli ${{ matrix.product }} Tests + test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' + continue-on-error: true + + testnet-smoke-tests-notify: + name: Start Slack Thread + if: ${{ always() && needs.*.result != 'skipped' && needs.*.result != 'cancelled' }} + environment: integration + outputs: + thread_ts: ${{ steps.slack.outputs.thread_ts }} + permissions: + checks: write + pull-requests: write + id-token: write + contents: read + runs-on: ubuntu-latest + needs: [optimism-goerli-smoke-tests, arbitrum-goerli-smoke-tests] + steps: + - name: Debug Result + run: echo ${{ join(needs.*.result, ',') }} + - name: Main Slack Notification + uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + id: slack + with: + channel-id: ${{ secrets.QA_SLACK_CHANNEL }} + payload: | + { + "attachments": [ + { + "color": "${{ contains(join(needs.*.result, ','), 'failure') && '#C62828' || '#2E7D32' }}", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "Live Smoke Test Results ${{ contains(join(needs.*.result, ','), 'failure') && ':x:' || ':white_check_mark:'}}", + "emoji": true + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "<${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|${{ github.ref_name }}> | <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}> | <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run>" + } + } + ] + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + + testnet-smoke-tests-results: + name: Post Test Results for ${{ matrix.network }} + if: ${{ always() && needs.*.result != 'skipped' && needs.*.result != 'cancelled' }} + environment: integration + permissions: + checks: write + pull-requests: write + id-token: write + contents: read + runs-on: ubuntu-latest + needs: testnet-smoke-tests-notify + strategy: + fail-fast: false + matrix: + network: [Optimism Goerli, Arbitrum Goerli] + steps: + - name: Get Results + id: test-results + run: | + # I feel like there's some clever, fully jq way to do this, but I ain't got the motivation to figure it out + echo "Querying test results" + + PARSED_RESULTS=$(curl \ + -H "Authorization: Bearer ${{ github.token }}" \ + 'https://api.github.com/repos/${{github.repository}}/actions/runs/${{ github.run_id }}/jobs' \ + | jq -r --arg pattern "${{ matrix.network }} (?\\w+) Tests" '.jobs[] + | select(.name | test($pattern)) as $job + | $job.steps[] + | select(.name == "Run Tests") + | { conclusion: (if .conclusion == "success" then ":white_check_mark:" else ":x:" end), product: ("*" + ($job.name | capture($pattern).product) + "*") }') + + echo "Parsed Results:" + echo $PARSED_RESULTS + + ALL_SUCCESS=true + for row in $(echo "$PARSED_RESULTS" | jq -s | jq -r '.[] | select(.conclusion != ":white_check_mark:")'); do + success=false + break + done + + echo all_success=$ALL_SUCCESS >> $GITHUB_OUTPUT + + FORMATTED_RESULTS=$(echo $PARSED_RESULTS | jq -s '[.[] + | { + conclusion: .conclusion, + product: .product + } + ] + | map("{\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": \"\(.product): \(.conclusion)\"}}") + | join(",")') + + echo "Formatted Results:" + echo $FORMATTED_RESULTS + + # Cleans out backslashes and quotes from jq + CLEAN_RESULTS=$(echo "$FORMATTED_RESULTS" | sed 's/\\\"/"/g' | sed 's/^"//;s/"$//') + + echo "Clean Results" + echo $CLEAN_RESULTS + + echo results=$CLEAN_RESULTS >> $GITHUB_OUTPUT + + - name: Test Details + uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + with: + channel-id: ${{ secrets.QA_SLACK_CHANNEL }} + payload: | + { + "thread_ts": "${{ needs.testnet-smoke-tests-notify.outputs.thread_ts }}", + "attachments": [ + { + "color": "${{ steps.test-results.outputs.all_success && '#2E7D32' || '#C62828' }}", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "${{ matrix.network }} ${{ steps.test-results.outputs.all_success && ':white_check_mark:' || ':x: Notifying <@U01Q4N37KFG>'}}", + "emoji": true + } + }, + { + "type": "divider" + }, + ${{ steps.test-results.outputs.results }} + ] + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} diff --git a/.github/workflows/on-demand-log-poller.yml b/.github/workflows/on-demand-log-poller.yml new file mode 100644 index 00000000000..42f901ec304 --- /dev/null +++ b/.github/workflows/on-demand-log-poller.yml @@ -0,0 +1,87 @@ +name: On Demand Log Poller Consistency Test +on: + workflow_dispatch: + inputs: + contracts: + description: Number of test contracts + default: "2" + required: true + eventsPerTx: + description: Number of events to emit per transaction + default: "10" + required: true + useFinalityTag: + description: Use finality tag + default: "false" + required: true + loadDuration: + description: Load duration (e.g. 10s, 10m, 1h) + default: "10m" + required: true + chainlinkImage: + description: Chainlink image to use + default: "public.ecr.aws/chainlink/chainlink" + required: true + chainlinkVersion: + description: Chainlink version to use + default: "2.7.0-beta1" + required: true + selectedNetworks: + type: choice + options: + - "SIMULATED" + - "SEPOLIA" + - "MUMBAI" + fundingPrivateKey: + description: Private funding key (Skip for Simulated) + required: true + type: string + wsURL: + description: WS URL for the network (Skip for Simulated) + required: true + type: string + httpURL: + description: HTTP URL for the network (Skip for Simulated) + required: true + type: string + +jobs: + test: + env: + CONTRACTS: ${{ inputs.contracts }} + EVENTS_PER_TX: ${{ inputs.eventsPerTx }} + LOAD_DURATION: ${{ inputs.loadDuration }} + USE_FINALITY_TAG: ${{ inputs.useFinalityTag }} + CHAINLINK_IMAGE: ${{ inputs.chainlinkImage }} + CHAINLINK_VERSION: ${{ inputs.chainlinkVersion }} + SELECTED_NETWORKS: ${{ inputs.selectedNetworks }} + REF_NAME: ${{ github.head_ref || github.ref_name }} + runs-on: ubuntu20.04-8cores-32GB + steps: + - name: Get Inputs + run: | + EVM_URLS=$(jq -r '.inputs.wsURL' $GITHUB_EVENT_PATH) + EVM_HTTP_URLS=$(jq -r '.inputs.httpURL' $GITHUB_EVENT_PATH) + EVM_KEYS=$(jq -r '.inputs.fundingPrivateKey' $GITHUB_EVENT_PATH) + + echo ::add-mask::$EVM_URLS + echo ::add-mask::$EVM_HTTP_URLS + echo ::add-mask::$EVM_KEYS + + echo EVM_URLS=$EVM_URLS >> $GITHUB_ENV + echo EVM_HTTP_URLS=$EVM_HTTP_URLS >> $GITHUB_ENV + echo EVM_KEYS=$EVM_KEYS >> $GITHUB_ENV + - name: Checkout the repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ env.REF_NAME }} + - name: Setup Go + uses: actions/setup-go@v3 + with: + go-version-file: "integration-tests/go.mod" + cache: true + - name: Run tests + run: | + cd integration-tests + go mod download + go test -v -timeout 5h -v -count=1 -run ^TestLogPollerFromEnv$ ./reorg/log_poller_maybe_reorg_test.go diff --git a/.github/workflows/on-demand-ocr-soak-test.yml b/.github/workflows/on-demand-ocr-soak-test.yml index cd141142268..567d9510de9 100644 --- a/.github/workflows/on-demand-ocr-soak-test.yml +++ b/.github/workflows/on-demand-ocr-soak-test.yml @@ -20,10 +20,16 @@ on: - "BSC_TESTNET" - "SCROLL_SEPOLIA" - "SCROLL_MAINNET" - - "MUMBAI" + - "POLYGON_MUMBAI" - "POLYGON_MAINNET" - "LINEA_GOERLI" - "LINEA_MAINNET" + - "FANTOM_TESTNET" + - "FANTOM_MAINNET" + - "KROMA_MAINNET" + - "KROMA_SEPOLIA" + - "WEMIX_TESTNET" + - "WEMIX_MAINNET" fundingPrivateKey: description: Private funding key (Skip for Simulated) required: false @@ -48,7 +54,7 @@ on: chainlinkVersion: description: Container image version for the Chainlink nodes required: true - default: "1.11.0" + default: "2.7.0" testDuration: description: Duration of the test (time string) required: false @@ -87,7 +93,7 @@ jobs: steps: - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -110,7 +116,7 @@ jobs: echo EVM_KEYS=$EVM_KEYS >> $GITHUB_ENV echo SLACK_USER=$SLACK_USER >> $GITHUB_ENV - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ env.REF_NAME }} - name: Setup Push Tag @@ -127,7 +133,7 @@ jobs: QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 env: DETACH_RUNNER: true TEST_SUITE: soak diff --git a/.github/workflows/on-demand-vrfv2-performance-test.yml b/.github/workflows/on-demand-vrfv2-performance-test.yml new file mode 100644 index 00000000000..100fdf73e61 --- /dev/null +++ b/.github/workflows/on-demand-vrfv2-performance-test.yml @@ -0,0 +1,136 @@ +name: On Demand VRFV2 Performance Test +on: + workflow_dispatch: + inputs: + network: + description: Network to run tests on + type: choice + options: + - "ETHEREUM_MAINNET" + - "SIMULATED" + - "SEPOLIA" + - "OPTIMISM_MAINNET" + - "OPTIMISM_GOERLI" + - "ARBITRUM_MAINNET" + - "ARBITRUM_GOERLI" + - "ARBITRUM_SEPOLIA" + - "BSC_MAINNET" + - "BSC_TESTNET" + - "POLYGON_MAINNET" + - "POLYGON_MUMBAI" + - "AVALANCHE_FUJI" + - "AVALANCHE_MAINNET" + fundingPrivateKey: + description: Private funding key (Skip for Simulated) + required: false + type: string + wsURL: + description: WS URL for the network (Skip for Simulated) + required: false + type: string + httpURL: + description: HTTP URL for the network (Skip for Simulated) + required: false + type: string + chainlinkImage: + description: Container image location for the Chainlink nodes + required: true + default: public.ecr.aws/chainlink/chainlink + chainlinkVersion: + description: Container image version for the Chainlink nodes + required: true + default: "2.6.0" + performanceTestType: + description: Performance Test Type of test to run + type: choice + options: + - "Soak" + - "Load" + - "Stress" + - "Spike" + testDuration: + description: Duration of the test (time string) + required: true + default: 5m + useExistingEnv: + description: Set `true` to use existing environment or `false` to deploy CL node and all contracts + required: false + default: false + configBase64: + description: TOML config in base64 (Needed when overriding config or providing contract addresses for existing env) + required: false +jobs: + vrfv2_performance_test: + name: ${{ inputs.network }} VRFV2 Performance Test + environment: integration + runs-on: ubuntu20.04-8cores-32GB + permissions: + checks: write + pull-requests: write + id-token: write + contents: read + env: + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_TOKEN: ${{ secrets.LOKI_TOKEN }} + SELECTED_NETWORKS: ${{ inputs.network }} + TEST_TYPE: ${{ inputs.performanceTestType }} + VRFV2_TEST_DURATION: ${{ inputs.testDuration }} + VRFV2_USE_EXISTING_ENV: ${{ inputs.useExistingEnv }} + CONFIG: ${{ inputs.configBase64 }} + TEST_LOG_LEVEL: debug + REF_NAME: ${{ github.head_ref || github.ref_name }} + CHAINLINK_IMAGE: ${{ inputs.chainlinkImage }} + CHAINLINK_VERSION: ${{ inputs.chainlinkVersion }} + SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} + SLACK_CHANNEL: ${{ secrets.QA_VRF_SLACK_CHANNEL }} + WASP_LOG_LEVEL: info + steps: + - name: Collect Metrics + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 + with: + basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} + this-job-name: ${{ inputs.network }} VRFV2 Performance Test + continue-on-error: true + - name: Setup Push Tag + shell: bash + run: | + echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY + echo "\`${{ inputs.chainlinkVersion }}\`" >>$GITHUB_STEP_SUMMARY + echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY + echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY + - name: Get Inputs + run: | + EVM_URLS=$(jq -r '.inputs.wsURL' $GITHUB_EVENT_PATH) + EVM_HTTP_URLS=$(jq -r '.inputs.httpURL' $GITHUB_EVENT_PATH) + EVM_KEYS=$(jq -r '.inputs.fundingPrivateKey' $GITHUB_EVENT_PATH) + + echo ::add-mask::$EVM_URLS + echo ::add-mask::$EVM_HTTP_URLS + echo ::add-mask::$EVM_KEYS + + echo EVM_URLS=$EVM_URLS >> $GITHUB_ENV + echo EVM_HTTP_URLS=$EVM_HTTP_URLS >> $GITHUB_ENV + echo EVM_KEYS=$EVM_KEYS >> $GITHUB_ENV + + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + - name: Run Tests + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@7d541cbbca52d45b8a718257af86d9cf49774d1f # v2.2.15 + with: + test_command_to_run: cd ./integration-tests && go test -v -count=1 -timeout 24h -run TestVRFV2Performance/vrfv2_performance_test ./load/vrfv2 + test_download_vendor_packages_command: cd ./integration-tests && go mod download + cl_repo: ${{ inputs.chainlinkImage }} + cl_image_tag: ${{ inputs.chainlinkVersion }} + aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + artifacts_name: vrf-test-logs + artifacts_location: ./integration-tests/load/vrfv2/logs/ + token: ${{ secrets.GITHUB_TOKEN }} + go_mod_path: ./integration-tests/go.mod + should_cleanup: false + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} diff --git a/.github/workflows/on-demand-vrfv2plus-load-test.yml b/.github/workflows/on-demand-vrfv2plus-performance-test.yml similarity index 76% rename from .github/workflows/on-demand-vrfv2plus-load-test.yml rename to .github/workflows/on-demand-vrfv2plus-performance-test.yml index f4ca096d5ce..238f40de882 100644 --- a/.github/workflows/on-demand-vrfv2plus-load-test.yml +++ b/.github/workflows/on-demand-vrfv2plus-performance-test.yml @@ -1,4 +1,4 @@ -name: On Demand VRFV2 Plus Load Test +name: On Demand VRFV2 Plus Performance Test on: workflow_dispatch: inputs: @@ -13,10 +13,11 @@ on: - "OPTIMISM_GOERLI" - "ARBITRUM_MAINNET" - "ARBITRUM_GOERLI" + - "ARBITRUM_SEPOLIA" - "BSC_MAINNET" - "BSC_TESTNET" - "POLYGON_MAINNET" - - "MUMBAI" + - "POLYGON_MUMBAI" - "AVALANCHE_FUJI" - "AVALANCHE_MAINNET" fundingPrivateKey: @@ -49,18 +50,18 @@ on: - "Spike" testDuration: description: Duration of the test (time string) - required: false - default: 1m + required: true + default: 5m useExistingEnv: description: Set `true` to use existing environment or `false` to deploy CL node and all contracts required: false - default: false + default: "false" configBase64: description: TOML config in base64 (Needed when overriding config or providing contract addresses for existing env) required: false jobs: - vrfv2plus_load_test: - name: ${{ inputs.network }} VRFV2 Plus Load Test + vrfv2plus_performance_test: + name: ${{ inputs.network }} VRFV2 Plus Performance Test environment: integration runs-on: ubuntu20.04-8cores-32GB permissions: @@ -80,16 +81,25 @@ jobs: REF_NAME: ${{ github.head_ref || github.ref_name }} CHAINLINK_IMAGE: ${{ inputs.chainlinkImage }} CHAINLINK_VERSION: ${{ inputs.chainlinkVersion }} + SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} + SLACK_CHANNEL: ${{ secrets.QA_VRF_SLACK_CHANNEL }} WASP_LOG_LEVEL: info steps: - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} - this-job-name: ${{ inputs.network }} VRFV2 Plus Load Test + this-job-name: ${{ inputs.network }} VRFV2 Plus Performance Test continue-on-error: true + - name: Setup Push Tag + shell: bash + run: | + echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY + echo "\`${{ inputs.chainlinkVersion }}\`" >>$GITHUB_STEP_SUMMARY + echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY + echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY - name: Get Inputs run: | EVM_URLS=$(jq -r '.inputs.wsURL' $GITHUB_EVENT_PATH) @@ -105,19 +115,19 @@ jobs: echo EVM_KEYS=$EVM_KEYS >> $GITHUB_ENV - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: fetch-depth: 0 - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: - test_command_to_run: cd ./integration-tests && go test -v -count=1 -timeout 6h -run TestVRFV2PlusLoad/vrfv2plus_soak_test ./load/vrfv2plus + test_command_to_run: cd ./integration-tests && go test -v -count=1 -timeout 24h -run TestVRFV2PlusPerformance/vrfv2plus_performance_test ./load/vrfv2plus test_download_vendor_packages_command: cd ./integration-tests && go mod download cl_repo: ${{ inputs.chainlinkImage }} cl_image_tag: ${{ inputs.chainlinkVersion }} aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} artifacts_name: vrf-test-logs - artifacts_location: ./integration-tests/load/logs/ + artifacts_location: ./integration-tests/load/vrfv2plus/logs/ token: ${{ secrets.GITHUB_TOKEN }} go_mod_path: ./integration-tests/go.mod should_cleanup: false diff --git a/.github/workflows/operator-ui-cd.yml b/.github/workflows/operator-ui-cd.yml index 4f6e82e07b3..bd589da728f 100644 --- a/.github/workflows/operator-ui-cd.yml +++ b/.github/workflows/operator-ui-cd.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Update version id: update @@ -25,7 +25,7 @@ jobs: run: ./operator_ui/check.sh - name: Assume role capable of dispatching action - uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 + uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: role-to-assume: ${{ secrets.AWS_OIDC_CHAINLINK_CI_AUTO_PR_TOKEN_ISSUER_ROLE_ARN }} role-duration-seconds: ${{ secrets.aws-role-duration-seconds }} @@ -39,7 +39,7 @@ jobs: url: ${{ secrets.AWS_INFRA_RELENG_TOKEN_ISSUER_LAMBDA_URL }} - name: Open PR - uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 # v4.2.4 + uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2 with: title: Update Operator UI from ${{ steps.update.outputs.current_tag }} to ${{ steps.update.outputs.latest_tag }} token: ${{ steps.get-gh-token.outputs.access-token }} @@ -50,7 +50,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/operator-ui-ci.yml b/.github/workflows/operator-ui-ci.yml index 8ced3b222cf..2ce85bc1322 100644 --- a/.github/workflows/operator-ui-ci.yml +++ b/.github/workflows/operator-ui-ci.yml @@ -23,7 +23,7 @@ jobs: continue-on-error: true - name: Assume role capable of dispatching action - uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 + uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: role-to-assume: ${{ secrets.AWS_OIDC_CHAINLINK_CI_OPERATOR_UI_ACCESS_TOKEN_ISSUER_ROLE_ARN }} role-duration-seconds: 3600 diff --git a/.github/workflows/performance-tests.yml b/.github/workflows/performance-tests.yml index b79d8dfea24..4b6dd1a2280 100644 --- a/.github/workflows/performance-tests.yml +++ b/.github/workflows/performance-tests.yml @@ -18,20 +18,20 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 + uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: aws-region: ${{ secrets.QA_AWS_REGION }} role-to-assume: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} role-duration-seconds: 3600 - name: Login to Amazon ECR id: login-ecr - uses: aws-actions/amazon-ecr-login@v1 + uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@ecf95283f03858871ff00b787d79c419715afc34 # v2.7.0 + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - name: Build and Push - uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # v3.2.0 + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 with: context: . file: core/chainlink.Dockerfile @@ -42,7 +42,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -55,9 +55,9 @@ jobs: needs: build-chainlink steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@eccde1970eca69f079d3efb3409938a72ade8497 # v2.2.13 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e865e376b8c2d594028c8d645dd6c47169b72974 # v2.2.16 with: test_command_to_run: cd integration-tests && go test -timeout 1h -count=1 -json -test.parallel 10 ./performance 2>&1 | tee /tmp/gotest.log | gotestfmt test_download_vendor_packages_command: make gomod @@ -79,7 +79,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml index e847216eb07..585bed41c6a 100644 --- a/.github/workflows/readme.yml +++ b/.github/workflows/readme.yml @@ -31,7 +31,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/sigscanner.yml b/.github/workflows/sigscanner.yml index 285a63cacbe..de34766d2c8 100644 --- a/.github/workflows/sigscanner.yml +++ b/.github/workflows/sigscanner.yml @@ -26,7 +26,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/solidity-foundry.yml b/.github/workflows/solidity-foundry.yml index 590165c8e7a..7f6fa4f482e 100644 --- a/.github/workflows/solidity-foundry.yml +++ b/.github/workflows/solidity-foundry.yml @@ -12,7 +12,7 @@ jobs: changes: ${{ steps.changes.outputs.src }} steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 id: changes with: @@ -34,14 +34,15 @@ jobs: matrix: product: [vrf, automation, llo-feeds, functions, shared] needs: [changes] - if: needs.changes.outputs.changes == 'true' - name: Tests + name: Foundry Tests ${{ matrix.product }} # See https://github.com/foundry-rs/foundry/issues/3827 runs-on: ubuntu-22.04 + # The if statements for steps after checkout repo is workaround for + # passing required check for PRs that don't have filtered changes. steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: submodules: recursive @@ -49,15 +50,18 @@ jobs: # and not native Foundry. This is to make sure the dependencies # stay in sync. - name: Setup NodeJS + if: needs.changes.outputs.changes == 'true' uses: ./.github/actions/setup-nodejs - name: Install Foundry + if: needs.changes.outputs.changes == 'true' uses: foundry-rs/foundry-toolchain@v1 with: # Has to match the `make foundry` version. - version: nightly-5be158ba6dc7c798a6f032026fe60fc01686b33b + version: nightly-09fe3e041369a816365a020f715ad6f94dbce9f2 - name: Run Forge build + if: needs.changes.outputs.changes == 'true' run: | forge --version forge build @@ -67,6 +71,7 @@ jobs: FOUNDRY_PROFILE: ${{ matrix.product }} - name: Run Forge tests + if: needs.changes.outputs.changes == 'true' run: | forge test -vvv id: test @@ -75,7 +80,7 @@ jobs: FOUNDRY_PROFILE: ${{ matrix.product }} - name: Run Forge snapshot - if: ${{ !contains(fromJson('["vrf"]'), matrix.product) && !contains(fromJson('["automation"]'), matrix.product) }} + if: ${{ !contains(fromJson('["vrf"]'), matrix.product) && !contains(fromJson('["automation"]'), matrix.product) && needs.changes.outputs.changes == 'true' }} run: | forge snapshot --nmt "testFuzz_\w{1,}?" --check gas-snapshots/${{ matrix.product }}.gas-snapshot id: snapshot @@ -84,9 +89,9 @@ jobs: FOUNDRY_PROFILE: ${{ matrix.product }} - name: Collect Metrics - if: always() + if: needs.changes.outputs.changes == 'true' id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/solidity-hardhat.yml b/.github/workflows/solidity-hardhat.yml index 2e25e52753d..129f37c0de6 100644 --- a/.github/workflows/solidity-hardhat.yml +++ b/.github/workflows/solidity-hardhat.yml @@ -19,7 +19,7 @@ jobs: changes: ${{ steps.changes.outputs.src }} steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 id: changes with: @@ -28,6 +28,7 @@ jobs: - 'contracts/src/!(v0.8/(llo-feeds|ccip)/**)/**/*' - 'contracts/test/**/*' - 'contracts/package.json' + - 'contracts/pnpm-lock.yaml' - 'contracts/hardhat.config.ts' - 'contracts/ci.json' - '.github/workflows/solidity-hardhat.yml' @@ -39,7 +40,7 @@ jobs: splits: ${{ steps.split.outputs.splits }} steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Generate splits id: split uses: ./.github/actions/split-tests @@ -47,7 +48,7 @@ jobs: config: ./contracts/ci.json - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -65,7 +66,7 @@ jobs: runs-on: ubuntu20.04-4cores-16GB steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup NodeJS uses: ./.github/actions/setup-nodejs - name: Setup Hardhat @@ -89,7 +90,7 @@ jobs: path: ./contracts/coverage-${{ matrix.split.idx }}.json - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -103,7 +104,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup NodeJS uses: ./.github/actions/setup-nodejs - name: Make coverage directory @@ -130,7 +131,7 @@ jobs: runs-on: ubuntu20.04-4cores-16GB steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup NodeJS uses: ./.github/actions/setup-nodejs - name: Setup Hardhat @@ -144,7 +145,7 @@ jobs: run: pnpm test -- $SPLIT - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -169,7 +170,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index b2537a5c9a7..90429a8c526 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -16,14 +16,30 @@ jobs: changes: ${{ steps.changes.outputs.src }} steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 id: changes with: + list-files: "csv" filters: | src: - 'contracts/**/*' - '.github/workflows/solidity.yml' + - '.github/workflows/solidity-foundry.yml' + old_sol: + - 'contracts/src/v0.4/**/*' + - 'contracts/src/v0.5/**/*' + - 'contracts/src/v0.6/**/*' + - 'contracts/src/v0.7/**/*' + + - name: Fail if read-only files have changed + if: ${{ steps.changes.outputs.old_sol == 'true' }} + run: | + echo "One or more read-only Solidity file(s) has changed." + for file in ${{ steps.changes.outputs.old_sol_files }}; do + echo "$file was changed" + done + exit 1 prepublish-test: needs: [changes] @@ -32,7 +48,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup NodeJS uses: ./.github/actions/setup-nodejs - name: Run Prepublish test @@ -40,7 +56,7 @@ jobs: run: pnpm prepublishOnly - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -54,9 +70,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Checkout diff-so-fancy - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: repository: so-fancy/diff-so-fancy ref: a673cb4d2707f64d92b86498a2f5f71c8e2643d5 # v1.4.3 @@ -84,33 +100,38 @@ jobs: run: gh pr comment -b 'Go solidity wrappers are out-of-date, regenerate them via the `make wrappers-all` command' - name: Collect Metrics id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} this-job-name: Native Compilation continue-on-error: true + # The if statements for steps after checkout repo is a workaround for + # passing required check for PRs that don't have filtered changes. lint: defaults: run: working-directory: contracts needs: [changes] - if: needs.changes.outputs.changes == 'true' - name: Lint ${{ fromJSON('["(skipped)", ""]')[needs.changes.outputs.changes == 'true'] }} + name: Solidity Lint runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup NodeJS + if: needs.changes.outputs.changes == 'true' uses: ./.github/actions/setup-nodejs - name: Run pnpm lint + if: needs.changes.outputs.changes == 'true' run: pnpm lint - name: Run solhint + if: needs.changes.outputs.changes == 'true' run: pnpm solhint - name: Collect Metrics + if: needs.changes.outputs.changes == 'true' id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} @@ -126,7 +147,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup NodeJS if: needs.changes.outputs.changes == 'true' uses: ./.github/actions/setup-nodejs @@ -136,7 +157,7 @@ jobs: - name: Collect Metrics if: needs.changes.outputs.changes == 'true' id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.github/workflows/sync-develop-from-smartcontractkit-chainlink.yml b/.github/workflows/sync-develop-from-smartcontractkit-chainlink.yml index 97037652034..d27acceca68 100644 --- a/.github/workflows/sync-develop-from-smartcontractkit-chainlink.yml +++ b/.github/workflows/sync-develop-from-smartcontractkit-chainlink.yml @@ -10,7 +10,7 @@ jobs: name: Sync runs-on: ubuntu-latest steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: develop if: env.GITHUB_REPOSITORY != 'smartcontractkit/chainlink' @@ -30,7 +30,7 @@ jobs: - name: Collect Metrics if: always() id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@f4d2fcbe12e9e44921e0171d24085ab7d2a30bc9 # v2.0.1 + uses: smartcontractkit/push-gha-metrics-action@d1618b772a97fd87e6505de97b872ee0b1f1729a # v2.0.2 with: basic-auth: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_CLOUD_HOST }} diff --git a/.gitignore b/.gitignore index decea4a68a7..3f016503a81 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ tools/clroot/db.sqlite3-wal .idea .vscode/ *.iml +debug.env # codeship *.aes @@ -54,9 +55,6 @@ golangci-lint-output.txt # can be left behind by tests core/cmd/vrfkey1 -# left behind by webpack - compiled frontend assets -core/web/assets - # Integration Tests integration-tests/**/logs/ tests-*.xml @@ -64,7 +62,7 @@ tests-*.xml tmp-manifest-*.yaml ztarrepo.tar.gz **/test-ledger/* -__debug_bin +__debug_bin* # goreleaser builds cosign.* @@ -78,6 +76,9 @@ MacOSX* contracts/yarn.lock - # Ignore DevSpace cache and log folder .devspace/ +go.work* + +# This sometimes shows up for some reason +tools/flakeytests/coverage.txt diff --git a/.goreleaser.develop.yaml b/.goreleaser.develop.yaml index 53c927c9f97..20312491651 100644 --- a/.goreleaser.develop.yaml +++ b/.goreleaser.develop.yaml @@ -10,8 +10,6 @@ env: before: hooks: - - go mod tidy - - make operator-ui - ./tools/bin/goreleaser_utils before_hook # See https://goreleaser.com/customization/build/ diff --git a/.tool-versions b/.tool-versions index 87910cf6d6f..c60396ccb86 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,7 +1,7 @@ -golang 1.21.1 +golang 1.21.4 mockery 2.35.4 nodejs 16.16.0 postgres 13.3 helm 3.10.3 zig 0.10.1 -golangci-lint 1.55.0 +golangci-lint 1.55.2 diff --git a/CODEOWNERS b/CODEOWNERS index 8ec42ed6dd4..e34d3ea1bef 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -71,25 +71,40 @@ core/scripts/gateway @bolekk @pinebit /operator-ui/ @DeividasK @jkongie # Contracts -/contracts/ @se3000 @connorwstein @RensR +/contracts/ @RensR -/contracts/srv/v0.8/automation @smartcontractkit/keepers +# First we match on project names to catch files like the compilation scripts, +# gas snapshots and other files not places in the project directories. +# This could give some false positives, so afterwards we match on the project directories +# to ensure the entire directory is always owned by the correct team. + +/contracts/**/*shared* @RensR /contracts/**/*keeper* @smartcontractkit/keepers /contracts/**/*upkeep* @smartcontractkit/keepers /contracts/**/*automation* @smartcontractkit/keepers -/contracts/gas-snapshots/automation.gas-snapshot @smartcontractkit/keepers - -/contracts/src/v0.8/functions @smartcontractkit/functions /contracts/**/*functions* @smartcontractkit/functions -/contracts/gas-snapshots/functions.gas-snapshot @smartcontractkit/functions +/contracts/**/*llo-feeds* @austinborn @Fletch153 +/contracts/**/*vrf* @smartcontractkit/vrf-team +/contracts/**/*l2ep* @simsonraj +/contracts/**/*operatorforwarder* @essamhassan +/contracts/src/v0.8/automation @smartcontractkit/keepers +/contracts/src/v0.8/functions @smartcontractkit/functions +# TODO: interfaces folder, folder should be removed and files moved to the correct folders +/contracts/src/v0.8/l2ep @simsonraj /contracts/src/v0.8/llo-feeds @austinborn @Fletch153 -/contracts/gas-snapshots/llo-feeds.gas-snapshot @austinborn @Fletch153 - +# TODO: mocks folder, folder should be removed and files moved to the correct folders +/contracts/src/v0.8/operatorforwarder @essamhassan +/contracts/src/v0.8/shared @RensR +# TODO: tests folder, folder should be removed and files moved to the correct folders +# TODO: transmission folder, owner should be found /contracts/src/v0.8/vrf @smartcontractkit/vrf-team -/contracts/**/*vrf* @smartcontractkit/vrf-team -/contracts/src/v0.8/l2ep @simsonraj + + +# At the end, match any files missed by the patterns above +/contracts/scripts/native_solc_compile_all_events_mock @smartcontractkit/functions + # Tests /integration-tests/ @smartcontractkit/test-tooling-team @@ -104,6 +119,7 @@ core/scripts/gateway @bolekk @pinebit /.github/workflows/performance-tests.yml @smartcontractkit/test-tooling-team /.github/workflows/automation-ondemand-tests.yml @smartcontractkit/keepers /.github/workflows/automation-benchmark-tests.yml @smartcontractkit/keepers +/.github/workflows/automation-load-tests.yml @smartcontractkit/keepers /core/chainlink.Dockerfile @smartcontractkit/prodsec-public diff --git a/GNUmakefile b/GNUmakefile index 957df96ce45..6cd5ab7143e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -57,9 +57,9 @@ chainlink-test: operator-ui ## Build a test build of chainlink binary. chainlink-local-start: ./chainlink -c /etc/node-secrets-volume/default.toml -c /etc/node-secrets-volume/overrides.toml -secrets /etc/node-secrets-volume/secrets.toml node start -d -p /etc/node-secrets-volume/node-password -a /etc/node-secrets-volume/apicredentials --vrfpassword=/etc/node-secrets-volume/apicredentials -.PHONY: install-median -install-median: ## Build & install the chainlink-median binary. - go install $(GOFLAGS) ./plugins/cmd/chainlink-median +.PHONY: install-medianpoc +install-medianpoc: ## Build & install the chainlink-medianpoc binary. + go install $(GOFLAGS) ./plugins/cmd/chainlink-medianpoc .PHONY: docker ## Build the chainlink docker image docker: @@ -75,7 +75,7 @@ docker-plugins: .PHONY: operator-ui operator-ui: ## Fetch the frontend - ./operator_ui/install.sh + go generate ./core/web .PHONY: abigen abigen: ## Build & install abigen. @@ -127,10 +127,6 @@ telemetry-protobuf: $(telemetry-protobuf) ## Generate telemetry protocol buffers --go-wsrpc_opt=paths=source_relative \ ./core/services/synchronization/telem/*.proto -.PHONY: test_need_operator_assets -test_need_operator_assets: ## Add blank file in web assets if operator ui has not been built - [ -f "./core/web/assets/index.html" ] || mkdir ./core/web/assets && touch ./core/web/assets/index.html - .PHONY: config-docs config-docs: ## Generate core node configuration documentation go run ./core/config/docs/cmd/generate -o ./docs/ @@ -138,7 +134,7 @@ config-docs: ## Generate core node configuration documentation .PHONY: golangci-lint golangci-lint: ## Run golangci-lint for all issues. [ -d "./golangci-lint" ] || mkdir ./golangci-lint && \ - docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:v1.55.0 golangci-lint run --max-issues-per-linter 0 --max-same-issues 0 > ./golangci-lint/$(shell date +%Y-%m-%d_%H:%M:%S).txt + docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:v1.55.2 golangci-lint run --max-issues-per-linter 0 --max-same-issues 0 > ./golangci-lint/$(shell date +%Y-%m-%d_%H:%M:%S).txt GORELEASER_CONFIG ?= .goreleaser.yaml diff --git a/VERSION b/VERSION index 37c2961c243..834f2629538 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.7.2 +2.8.0 diff --git a/charts/chainlink-cluster/Chart.yaml b/charts/chainlink-cluster/Chart.yaml index bfea29c82ec..127f5b6e326 100644 --- a/charts/chainlink-cluster/Chart.yaml +++ b/charts/chainlink-cluster/Chart.yaml @@ -2,4 +2,9 @@ apiVersion: v1 name: chainlink-cluster description: Chainlink nodes cluster version: 0.1.3 -appVersion: '2.6.0' \ No newline at end of file +appVersion: "2.6.0" +dependencies: + - name: mockserver + version: "5.14.0" + repository: "@mockserver" + condition: mockserver.enabled \ No newline at end of file diff --git a/charts/chainlink-cluster/README.md b/charts/chainlink-cluster/README.md index f7d4c45fa5f..5fb55536635 100644 --- a/charts/chainlink-cluster/README.md +++ b/charts/chainlink-cluster/README.md @@ -34,7 +34,7 @@ If you don't need a build use devspace deploy --skip-build ``` -Connect to your environment +Connect to your environment, by replacing container with label `node-1` with your local repository files ``` devspace dev -p node make chainlink @@ -55,10 +55,8 @@ Destroy the cluster devspace purge ``` -If you need to run some system level tests inside k8s use `runner` profile: -``` -devspace dev -p runner -``` +## Running load tests +Check this [doc](../../integration-tests/load/ocr/README.md) If you used `devspace dev ...` always use `devspace reset pods` to switch the pods back @@ -66,8 +64,6 @@ If you used `devspace dev ...` always use `devspace reset pods` to switch the po If you need to debug CL node that is already deployed change `dev.app.container` and `dev.app.labelSelector` in [devspace.yaml](devspace.yaml) if they are not default and run: ``` devspace dev -p node -or -devspace dev -p runner ``` ## Automatic file sync @@ -78,14 +74,14 @@ After that all the changes will be synced automatically Check `.profiles` to understand what is uploaded in profiles `runner` and `node` # Helm -If you would like to use `helm` directly, please uncomment data in `values-raw-helm.yaml` +If you would like to use `helm` directly, please uncomment data in `values.yaml` ## Install from local files ``` -helm install -f values-raw-helm.yaml cl-cluster . +helm install -f values.yaml cl-cluster . ``` Forward all apps (in another terminal) ``` -sudo kubefwd svc +sudo kubefwd svc -n cl-cluster ``` Then you can connect and run your tests @@ -103,7 +99,7 @@ kubectl config set-context --current --namespace cl-cluster Install ``` -helm install -f values-raw-helm.yaml cl-cluster chainlink-cluster/chainlink-cluster --version v0.1.2 +helm install -f values.yaml cl-cluster . ``` ## Create a new release @@ -117,4 +113,18 @@ helm test cl-cluster ## Uninstall ``` helm uninstall cl-cluster -``` \ No newline at end of file +``` + +# Grafana dashboard +We are using [Grabana]() lib to create dashboards programmatically +``` +export GRAFANA_URL=... +export GRAFANA_TOKEN=... +export LOKI_DATA_SOURCE_NAME=Loki +export PROMETHEUS_DATA_SOURCE_NAME=Thanos +export DASHBOARD_FOLDER=CRIB +export DASHBOARD_NAME=ChainlinkCluster + +cd dashboard/cmd && go run dashboard_deploy.go +``` +Open Grafana folder `CRIB` and find dashboard `ChainlinkCluster` \ No newline at end of file diff --git a/charts/chainlink-cluster/connect.toml b/charts/chainlink-cluster/connect.toml new file mode 100644 index 00000000000..1f49b5a6e37 --- /dev/null +++ b/charts/chainlink-cluster/connect.toml @@ -0,0 +1,12 @@ +namespace = "cl-cluster" +network_name = "geth" +network_chain_id = 1337 +network_private_key = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +network_ws_url = "ws://geth:8546" +network_http_url = "http://geth:8544" +cl_nodes_num = 6 +cl_node_url_template = "http://app-node-%d:6688" +cl_node_internal_dns_record_template = "app-node-%d" +cl_node_user = "notreal@fakeemail.ch" +cl_node_password = "fj293fbBnlQ!f9vNs" +mockserver_url = "http://mockserver:1080" \ No newline at end of file diff --git a/charts/chainlink-cluster/dashboard/cmd/dashboard_deploy.go b/charts/chainlink-cluster/dashboard/cmd/dashboard_deploy.go new file mode 100644 index 00000000000..93619fe6148 --- /dev/null +++ b/charts/chainlink-cluster/dashboard/cmd/dashboard_deploy.go @@ -0,0 +1,49 @@ +package main + +import ( + "os" + + "github.com/smartcontractkit/chainlink/v2/dashboard/dashboard" + "github.com/smartcontractkit/wasp" +) + +func main() { + name := os.Getenv("DASHBOARD_NAME") + if name == "" { + panic("DASHBOARD_NAME must be provided") + } + ldsn := os.Getenv("LOKI_DATA_SOURCE_NAME") + if ldsn == "" { + panic("DATA_SOURCE_NAME must be provided") + } + os.Setenv("DATA_SOURCE_NAME", ldsn) + pdsn := os.Getenv("PROMETHEUS_DATA_SOURCE_NAME") + if ldsn == "" { + panic("DATA_SOURCE_NAME must be provided") + } + dbf := os.Getenv("DASHBOARD_FOLDER") + if dbf == "" { + panic("DASHBOARD_FOLDER must be provided") + } + grafanaURL := os.Getenv("GRAFANA_URL") + if grafanaURL == "" { + panic("GRAFANA_URL must be provided") + } + grafanaToken := os.Getenv("GRAFANA_TOKEN") + if grafanaToken == "" { + panic("GRAFANA_TOKEN must be provided") + } + // if you'll use this dashboard base in other projects, you can add your own opts here to extend it + db, err := dashboard.NewCLClusterDashboard(6, name, ldsn, pdsn, dbf, grafanaURL, grafanaToken, nil) + if err != nil { + panic(err) + } + // here we are extending load testing dashboard with core metrics, for example + wdb, err := wasp.NewDashboard(nil, db.Opts()) + if err != nil { + panic(err) + } + if _, err := wdb.Deploy(); err != nil { + panic(err) + } +} diff --git a/charts/chainlink-cluster/dashboard/dashboard.go b/charts/chainlink-cluster/dashboard/dashboard.go new file mode 100644 index 00000000000..19a596b63e9 --- /dev/null +++ b/charts/chainlink-cluster/dashboard/dashboard.go @@ -0,0 +1,963 @@ +package dashboard + +import ( + "context" + "fmt" + "net/http" + + "github.com/K-Phoen/grabana" + "github.com/K-Phoen/grabana/dashboard" + "github.com/K-Phoen/grabana/logs" + "github.com/K-Phoen/grabana/row" + "github.com/K-Phoen/grabana/stat" + "github.com/K-Phoen/grabana/target/prometheus" + "github.com/K-Phoen/grabana/timeseries" + "github.com/K-Phoen/grabana/timeseries/axis" + "github.com/K-Phoen/grabana/variable/query" + "github.com/pkg/errors" +) + +/* +Use ripgrep to get the full list +rg -oU ".*promauto.*\n.*Name: \"(.*)\"" -r '$1' > metrics.txt + +duplicates? + +common/client/node.go:pool_rpc_node_verifies +common/client/node.go:pool_rpc_node_verifies_failed +common/client/node.go:pool_rpc_node_verifies_success +common/client/node_fsm.go:pool_rpc_node_num_transitions_to_alive +common/client/node_fsm.go:pool_rpc_node_num_transitions_to_in_sync +common/client/node_fsm.go:pool_rpc_node_num_transitions_to_out_of_sync +common/client/node_fsm.go:pool_rpc_node_num_transitions_to_unreachable +common/client/node_fsm.go:pool_rpc_node_num_transitions_to_invalid_chain_id +common/client/node_fsm.go:pool_rpc_node_num_transitions_to_unusable +common/client/node_lifecycle.go:pool_rpc_node_highest_seen_block +common/client/node_lifecycle.go:pool_rpc_node_num_seen_blocks +common/client/node_lifecycle.go:pool_rpc_node_polls_total +common/client/node_lifecycle.go:pool_rpc_node_polls_failed +common/client/node_lifecycle.go:pool_rpc_node_polls_success + +covered + +core/logger/prometheus.go:log_warn_count +core/logger/prometheus.go:log_error_count +core/logger/prometheus.go:log_critical_count +core/logger/prometheus.go:log_panic_count +core/logger/prometheus.go:log_fatal_count +common/client/multi_node.go:multi_node_states +common/txmgr/broadcaster.go:tx_manager_time_until_tx_broadcast +common/txmgr/confirmer.go:tx_manager_num_gas_bumps +common/txmgr/confirmer.go:tx_manager_gas_bump_exceeds_limit +common/txmgr/confirmer.go:tx_manager_num_confirmed_transactions +common/txmgr/confirmer.go:tx_manager_num_successful_transactions +common/txmgr/confirmer.go:tx_manager_num_tx_reverted +common/txmgr/confirmer.go:tx_manager_fwd_tx_count +common/txmgr/confirmer.go:tx_manager_tx_attempt_count +common/txmgr/confirmer.go:tx_manager_time_until_tx_confirmed +common/txmgr/confirmer.go:tx_manager_blocks_until_tx_confirmed +common/headtracker/head_tracker.go:head_tracker_current_head +common/headtracker/head_tracker.go:head_tracker_very_old_head +common/headtracker/head_listener.go:head_tracker_heads_received +common/headtracker/head_listener.go:head_tracker_connection_errors +core/chains/evm/client/node_fsm.go:evm_pool_rpc_node_num_transitions_to_alive +core/chains/evm/client/node_fsm.go:evm_pool_rpc_node_num_transitions_to_in_sync +core/chains/evm/client/node_fsm.go:evm_pool_rpc_node_num_transitions_to_out_of_sync +core/chains/evm/client/node_fsm.go:evm_pool_rpc_node_num_transitions_to_unreachable +core/chains/evm/client/node_fsm.go:evm_pool_rpc_node_num_transitions_to_invalid_chain_id +core/chains/evm/client/node_fsm.go:evm_pool_rpc_node_num_transitions_to_unusable +core/services/promreporter/prom_reporter.go:unconfirmed_transactions +core/services/promreporter/prom_reporter.go:max_unconfirmed_tx_age +core/services/promreporter/prom_reporter.go:max_unconfirmed_blocks +core/services/promreporter/prom_reporter.go:pipeline_runs_queued +core/services/promreporter/prom_reporter.go:pipeline_task_runs_queued +core/services/pipeline/task.bridge.go:bridge_latency_seconds +core/services/pipeline/task.bridge.go:bridge_errors_total +core/services/pipeline/task.bridge.go:bridge_cache_hits_total +core/services/pipeline/task.bridge.go:bridge_cache_errors_total +core/services/pipeline/task.http.go:pipeline_task_http_fetch_time +core/services/pipeline/task.http.go:pipeline_task_http_response_body_size +core/services/pipeline/task.eth_call.go:pipeline_task_eth_call_execution_time +core/services/pipeline/runner.go:pipeline_task_execution_time +core/services/pipeline/runner.go:pipeline_run_errors +core/services/pipeline/runner.go:pipeline_run_total_time_to_completion +core/services/pipeline/runner.go:pipeline_tasks_total_finished +core/chains/evm/client/node.go:evm_pool_rpc_node_dials_total +core/chains/evm/client/node.go:evm_pool_rpc_node_dials_failed +core/chains/evm/client/node.go:evm_pool_rpc_node_dials_success +core/chains/evm/client/node.go:evm_pool_rpc_node_verifies +core/chains/evm/client/node.go:evm_pool_rpc_node_verifies_failed +core/chains/evm/client/node.go:evm_pool_rpc_node_verifies_success +core/chains/evm/client/node.go:evm_pool_rpc_node_calls_total +core/chains/evm/client/node.go:evm_pool_rpc_node_calls_failed +core/chains/evm/client/node.go:evm_pool_rpc_node_calls_success +core/chains/evm/client/node.go:evm_pool_rpc_node_rpc_call_time +core/chains/evm/client/pool.go:evm_pool_rpc_node_states +core/utils/mailbox_prom.go:mailbox_load_percent +core/services/pg/stats.go:db_conns_max +core/services/pg/stats.go:db_conns_open +core/services/pg/stats.go:db_conns_used +core/services/pg/stats.go:db_wait_count +core/services/pg/stats.go:db_wait_time_seconds +core/chains/evm/client/node_lifecycle.go:evm_pool_rpc_node_highest_seen_block +core/chains/evm/client/node_lifecycle.go:evm_pool_rpc_node_num_seen_blocks +core/chains/evm/client/node_lifecycle.go:evm_pool_rpc_node_polls_total +core/chains/evm/client/node_lifecycle.go:evm_pool_rpc_node_polls_failed +core/chains/evm/client/node_lifecycle.go:evm_pool_rpc_node_polls_success +core/services/relay/evm/config_poller.go:ocr2_failed_rpc_contract_calls +core/services/feeds/service.go:feeds_job_proposal_requests +core/services/feeds/service.go:feeds_job_proposal_count +core/services/ocrcommon/prom.go:bridge_json_parse_values +core/services/ocrcommon/prom.go:ocr_median_values +core/chains/evm/logpoller/observability.go:log_poller_query_dataset_size + +not-covered and product specific (definitions/usage should be moved to plugins) + +mercury + +core/services/relay/evm/mercury/types/types.go:mercury_price_feed_missing +core/services/relay/evm/mercury/types/types.go:mercury_price_feed_errors +core/services/relay/evm/mercury/queue.go:mercury_transmit_queue_load +core/services/relay/evm/mercury/v1/data_source.go:mercury_insufficient_blocks_count +core/services/relay/evm/mercury/v1/data_source.go:mercury_zero_blocks_count +core/services/relay/evm/mercury/wsrpc/client.go:mercury_transmit_timeout_count +core/services/relay/evm/mercury/wsrpc/client.go:mercury_dial_count +core/services/relay/evm/mercury/wsrpc/client.go:mercury_dial_success_count +core/services/relay/evm/mercury/wsrpc/client.go:mercury_dial_error_count +core/services/relay/evm/mercury/wsrpc/client.go:mercury_connection_reset_count +core/services/relay/evm/mercury/transmitter.go:mercury_transmit_success_count +core/services/relay/evm/mercury/transmitter.go:mercury_transmit_duplicate_count +core/services/relay/evm/mercury/transmitter.go:mercury_transmit_connection_error_count +core/services/relay/evm/mercury/transmitter.go:mercury_transmit_queue_delete_error_count +core/services/relay/evm/mercury/transmitter.go:mercury_transmit_queue_insert_error_count +core/services/relay/evm/mercury/transmitter.go:mercury_transmit_queue_push_error_count +core/services/relay/evm/mercury/transmitter.go:mercury_transmit_server_error_count + +functions + +core/services/gateway/connectionmanager.go:gateway_heartbeats_sent +core/services/gateway/gateway.go:gateway_request +core/services/gateway/handlers/functions/handler.functions.go:gateway_functions_handler_error +core/services/gateway/handlers/functions/handler.functions.go:gateway_functions_secrets_set_success +core/services/gateway/handlers/functions/handler.functions.go:gateway_functions_secrets_set_failure +core/services/gateway/handlers/functions/handler.functions.go:gateway_functions_secrets_list_success +core/services/gateway/handlers/functions/handler.functions.go:gateway_functions_secrets_list_failure +core/services/functions/external_adapter_client.go:functions_external_adapter_client_latency +core/services/functions/external_adapter_client.go:functions_external_adapter_client_errors_total +core/services/functions/listener.go:functions_request_received +core/services/functions/listener.go:functions_request_internal_error +core/services/functions/listener.go:functions_request_computation_error +core/services/functions/listener.go:functions_request_computation_success +core/services/functions/listener.go:functions_request_timeout +core/services/functions/listener.go:functions_request_confirmed +core/services/functions/listener.go:functions_request_computation_result_size +core/services/functions/listener.go:functions_request_computation_error_size +core/services/functions/listener.go:functions_request_computation_duration +core/services/functions/listener.go:functions_request_pruned +core/services/ocr2/plugins/functions/reporting.go:functions_reporting_plugin_restarts +core/services/ocr2/plugins/functions/reporting.go:functions_reporting_plugin_query +core/services/ocr2/plugins/functions/reporting.go:functions_reporting_plugin_observation +core/services/ocr2/plugins/functions/reporting.go:functions_reporting_plugin_report +core/services/ocr2/plugins/functions/reporting.go:functions_reporting_plugin_report_num_observations +core/services/ocr2/plugins/functions/reporting.go:functions_reporting_plugin_accept +core/services/ocr2/plugins/functions/reporting.go:functions_reporting_plugin_transmit +core/services/ocr2/plugins/s4/prometheus.go:s4_reporting_plugin_query +core/services/ocr2/plugins/s4/prometheus.go:s4_reporting_plugin_observation +core/services/ocr2/plugins/s4/prometheus.go:s4_reporting_plugin_report +core/services/ocr2/plugins/s4/prometheus.go:s4_reporting_plugin_accept +core/services/ocr2/plugins/s4/prometheus.go:s4_reporting_plugin_query_byte_size +core/services/ocr2/plugins/s4/prometheus.go:s4_reporting_plugin_query_rows_count +core/services/ocr2/plugins/s4/prometheus.go:s4_reporting_plugin_observation_rows_count +core/services/ocr2/plugins/s4/prometheus.go:s4_reporting_plugin_report_rows_count +core/services/ocr2/plugins/s4/prometheus.go:s4_reporting_plugin_wrong_sig_count +core/services/ocr2/plugins/s4/prometheus.go:s4_reporting_plugin_expired_rows + +vrf + +core/services/vrf/vrfcommon/metrics.go:vrf_request_queue_size +core/services/vrf/vrfcommon/metrics.go:vrf_processed_request_count +core/services/vrf/vrfcommon/metrics.go:vrf_dropped_request_count +core/services/vrf/vrfcommon/metrics.go:vrf_duplicate_requests +core/services/vrf/vrfcommon/metrics.go:vrf_request_time_between_sims +core/services/vrf/vrfcommon/metrics.go:vrf_request_time_until_initial_sim + +keeper +core/services/keeper/upkeep_executer.go:keeper_check_upkeep_execution_time +*/ + +const ( + ErrFailedToCreateDashboard = "failed to create dashboard" + ErrFailedToCreateFolder = "failed to create folder" +) + +// CLClusterDashboard is a dashboard for a Chainlink cluster +type CLClusterDashboard struct { + Nodes int + Name string + LokiDataSourceName string + PrometheusDataSourceName string + Folder string + GrafanaURL string + GrafanaToken string + opts []dashboard.Option + extendedOpts []dashboard.Option + builder dashboard.Builder +} + +// NewCLClusterDashboard returns a new dashboard for a Chainlink cluster, can be used as a base for more complex plugin based dashboards +func NewCLClusterDashboard(nodes int, name, ldsn, pdsn, dbf, grafanaURL, grafanaToken string, opts []dashboard.Option) (*CLClusterDashboard, error) { + db := &CLClusterDashboard{ + Nodes: nodes, + Name: name, + Folder: dbf, + LokiDataSourceName: ldsn, + PrometheusDataSourceName: pdsn, + GrafanaURL: grafanaURL, + GrafanaToken: grafanaToken, + extendedOpts: opts, + } + if err := db.generate(); err != nil { + return db, err + } + return db, nil +} + +func (m *CLClusterDashboard) Opts() []dashboard.Option { + return m.opts +} + +// logsRowOption returns a row option for a node's logs with name and instance selector +func (m *CLClusterDashboard) logsRowOption(name, q string) row.Option { + return row.WithLogs( + name, + logs.DataSource(m.LokiDataSourceName), + logs.Span(12), + logs.Height("300px"), + logs.Transparent(), + logs.WithLokiTarget(q), + ) +} + +func (m *CLClusterDashboard) logsRowOptionsForNodes(nodes int) []row.Option { + opts := make([]row.Option, 0) + for i := 1; i <= nodes; i++ { + opts = append(opts, row.WithLogs( + fmt.Sprintf("Node %d", i), + logs.DataSource(m.LokiDataSourceName), + logs.Span(12), + logs.Height("300px"), + logs.Transparent(), + logs.WithLokiTarget(fmt.Sprintf(`{namespace="${namespace}", app="app", instance="node-%d", container="node"}`, i)), + )) + } + return opts +} + +// timeseriesRowOption returns a row option for a timeseries with name, axis unit, query and legend template +func (m *CLClusterDashboard) timeseriesRowOption(name, axisUnit, query, legendTemplate string) row.Option { + var tsq timeseries.Option + if legendTemplate != "" { + tsq = timeseries.WithPrometheusTarget( + query, + prometheus.Legend(legendTemplate), + ) + } else { + tsq = timeseries.WithPrometheusTarget(query) + } + var au timeseries.Option + if axisUnit != "" { + au = timeseries.Axis( + axis.Unit(axisUnit), + ) + } else { + au = timeseries.Axis() + } + return row.WithTimeSeries( + name, + timeseries.Span(6), + timeseries.Height("300px"), + timeseries.DataSource(m.PrometheusDataSourceName), + au, + tsq, + ) +} + +// statRowOption returns a row option for a stat with name, prometheus target and legend template +func (m *CLClusterDashboard) statRowOption(name, target, legend string) row.Option { + return row.WithStat( + name, + stat.Transparent(), + stat.DataSource(m.PrometheusDataSourceName), + stat.Text(stat.TextValueAndName), + stat.Orientation(stat.OrientationVertical), + stat.TitleFontSize(12), + stat.ValueFontSize(20), + stat.Span(12), + stat.Height("100px"), + stat.WithPrometheusTarget(target, prometheus.Legend(legend)), + ) +} + +// generate generates the dashboard, adding extendedOpts to the default options +func (m *CLClusterDashboard) generate() error { + opts := []dashboard.Option{ + dashboard.AutoRefresh("10s"), + dashboard.Tags([]string{"generated"}), + dashboard.VariableAsQuery( + "namespace", + query.DataSource(m.LokiDataSourceName), + query.Multiple(), + query.IncludeAll(), + query.Request(fmt.Sprintf("label_values(%s)", "namespace")), + query.Sort(query.NumericalAsc), + ), + dashboard.Row( + "Cluster health", + row.Collapse(), + m.statRowOption( + "App Version", + `version{namespace="${namespace}"}`, + "{{pod}} - {{version}}", + ), + row.WithTimeSeries( + "Restarts", + timeseries.Span(12), + timeseries.Height("200px"), + timeseries.DataSource(m.PrometheusDataSourceName), + timeseries.WithPrometheusTarget( + `sum(increase(kube_pod_container_status_restarts_total{namespace=~"${namespace}"}[5m])) by (pod)`, + prometheus.Legend("{{pod}}"), + ), + ), + row.WithTimeSeries( + "Service Components Health", + timeseries.Span(12), + timeseries.Height("200px"), + timeseries.DataSource(m.PrometheusDataSourceName), + timeseries.WithPrometheusTarget( + `health{namespace="${namespace}"}`, + prometheus.Legend("{{pod}} - {{service_id}}"), + ), + ), + row.WithTimeSeries( + "ETH Balance", + timeseries.Span(12), + timeseries.Height("200px"), + timeseries.DataSource(m.PrometheusDataSourceName), + timeseries.WithPrometheusTarget( + `eth_balance{namespace="${namespace}"}`, + prometheus.Legend("{{pod}} - {{account}}"), + ), + ), + ), + // HeadTracker + dashboard.Row("Head tracker", + row.Collapse(), + m.timeseriesRowOption( + "Head tracker current head", + "Block", + `head_tracker_current_head{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Head tracker very old head", + "Block", + `head_tracker_very_old_head{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Head tracker heads received", + "Block", + `head_tracker_heads_received{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Head tracker connection errors", + "Errors", + `head_tracker_connection_errors{namespace="${namespace}"}`, + "{{ pod }}", + ), + ), + dashboard.Row("LogPoller", + row.Collapse(), + m.timeseriesRowOption( + "LogPoller Query Dataset Size", + "", + `log_poller_query_dataset_size{namespace="${namespace}"}`, + "{{ pod }}", + ), + ), + dashboard.Row("OCRCommon", + row.Collapse(), + m.timeseriesRowOption( + "Bridge JSON Parse Values", + "", + `bridge_json_parse_values{namespace="${namespace}"}`, + "{{ pod }} JobID: {{ job_id }}", + ), + m.timeseriesRowOption( + "OCR Median Values", + "", + `ocr_median_values{namespace="${namespace}"}`, + "{{pod}} JobID: {{ job_id }}", + ), + ), + dashboard.Row("Relay Config Poller", + row.Collapse(), + m.timeseriesRowOption( + "Relay Config Poller RPC Contract Calls", + "", + `ocr2_failed_rpc_contract_calls{namespace="${namespace}"}`, + "{{ pod }}", + ), + ), + dashboard.Row("Feeds Jobs", + row.Collapse(), + m.timeseriesRowOption( + "Feeds Job Proposal Requests", + "", + `feeds_job_proposal_requests{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Feeds Job Proposal Count", + "", + `feeds_job_proposal_count{namespace="${namespace}"}`, + "{{ pod }}", + ), + ), + dashboard.Row("Mailbox", + row.Collapse(), + m.timeseriesRowOption( + "Mailbox Load Percent", + "", + `mailbox_load_percent{namespace="${namespace}"}`, + "{{ pod }} {{ name }}", + ), + ), + dashboard.Row("Multi Node States", + row.Collapse(), + m.timeseriesRowOption( + "Multi Node States", + "", + `multi_node_states{namespace="${namespace}"}`, + "{{ pod }}", + ), + ), + dashboard.Row("Block History Estimator", + row.Collapse(), + m.timeseriesRowOption( + "Gas Updater All Gas Price Percentiles", + "", + `gas_updater_all_gas_price_percentiles{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Gas Updater All Tip Cap Percentiles", + "", + `gas_updater_all_tip_cap_percentiles{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Gas Updater Set Gas Price", + "", + `gas_updater_set_gas_price{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Gas Updater Set Tip Cap", + "", + `gas_updater_set_tip_cap{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Gas Updater Current Base Fee", + "", + `gas_updater_current_base_fee{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Block History Estimator Connectivity Failure Count", + "", + `block_history_estimator_connectivity_failure_count{namespace="${namespace}"}`, + "{{ pod }}", + ), + ), + // PromReporter + dashboard.Row("Prom Reporter", + row.Collapse(), + m.timeseriesRowOption( + "Unconfirmed Transactions", + "Tx", + `unconfirmed_transactions{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Unconfirmed TX Age", + "Sec", + `max_unconfirmed_tx_age{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "Unconfirmed TX Blocks", + "Blocks", + `max_unconfirmed_blocks{namespace="${namespace}"}`, + "{{ pod }}", + ), + ), + dashboard.Row("TX Manager", + row.Collapse(), + m.timeseriesRowOption( + "TX Manager Time Until TX Broadcast", + "", + `tx_manager_time_until_tx_broadcast{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "TX Manager Num Gas Bumps", + "", + `tx_manager_num_gas_bumps{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "TX Manager Num Gas Bumps Exceeds Limit", + "", + `tx_manager_gas_bump_exceeds_limit{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "TX Manager Num Confirmed Transactions", + "", + `tx_manager_num_confirmed_transactions{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "TX Manager Num Successful Transactions", + "", + `tx_manager_num_successful_transactions{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "TX Manager Num Reverted Transactions", + "", + `tx_manager_num_tx_reverted{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "TX Manager Num Fwd Transactions", + "", + `tx_manager_fwd_tx_count{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "TX Manager Num Transactions Attempts", + "", + `tx_manager_tx_attempt_count{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "TX Manager Time Until TX Confirmed", + "", + `tx_manager_time_until_tx_confirmed{namespace="${namespace}"}`, + "{{ pod }}", + ), + m.timeseriesRowOption( + "TX Manager Block Until TX Confirmed", + "", + `tx_manager_blocks_until_tx_confirmed{namespace="${namespace}"}`, + "{{ pod }}", + ), + ), + // DON report metrics + dashboard.Row("DON Report metrics", + row.Collapse(), + m.timeseriesRowOption( + "Plugin Query() count", + "Count", + `sum(rate(ocr2_reporting_plugin_query_count{namespace="${namespace}", app="app"}[$__rate_interval])) by (service)`, + "", + ), + m.timeseriesRowOption( + "Plugin Observation() time (95th)", + "Sec", + `histogram_quantile(0.95, sum(rate(ocr2_reporting_plugin_observation_time_bucket{namespace="${namespace}", app="app"}[$__rate_interval])) by (le, service)) / 1e9`, + "", + ), + m.timeseriesRowOption( + "Plugin ShouldAcceptReport() time (95th)", + "Sec", + `histogram_quantile(0.95, sum(rate(ocr2_reporting_plugin_should_accept_report_time_bucket{namespace="${namespace}", app="app"}[$__rate_interval])) by (le, service)) / 1e9`, + "", + ), + m.timeseriesRowOption( + "Plugin Report() time (95th)", + "Sec", + `histogram_quantile(0.95, sum(rate(ocr2_reporting_plugin_report_time_bucket{namespace="${namespace}", app="app"}[$__rate_interval])) by (le, service)) / 1e9`, + "", + ), + m.timeseriesRowOption( + "Plugin ShouldTransmitReport() time (95th)", + "Sec", + `histogram_quantile(0.95, sum(rate(ocr2_reporting_plugin_should_transmit_report_time_bucket{namespace="${namespace}", app="app"}[$__rate_interval])) by (le, service)) / 1e9`, + "", + ), + ), + dashboard.Row( + "EVM Pool Lifecycle", + row.Collapse(), + m.timeseriesRowOption( + "EVM Pool Highest Seen Block", + "Block", + `evm_pool_rpc_node_highest_seen_block{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool Num Seen Blocks", + "Block", + `evm_pool_rpc_node_num_seen_blocks{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool Node Polls Total", + "Block", + `evm_pool_rpc_node_polls_total{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool Node Polls Failed", + "Block", + `evm_pool_rpc_node_polls_failed{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool Node Polls Success", + "Block", + `evm_pool_rpc_node_polls_success{namespace="${namespace}"}`, + "{{pod}}", + ), + ), + dashboard.Row( + "DB Connection Metrics (App)", + row.Collapse(), + m.timeseriesRowOption( + "DB Connections MAX", + "Conn", + `db_conns_max{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "DB Connections Open", + "Conn", + `db_conns_open{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "DB Connections Used", + "Conn", + `db_conns_used{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "DB Connections Wait", + "Conn", + `db_conns_wait{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "DB Wait Count", + "", + `db_wait_count{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "DB Wait time", + "Sec", + `db_wait_time_seconds{namespace="${namespace}"}`, + "{{pod}}", + ), + ), + dashboard.Row( + "EVM Pool RPC Node Metrics (App)", + row.Collapse(), + m.timeseriesRowOption( + "EVM Pool RPC Node Calls Success", + "", + `evm_pool_rpc_node_calls_success{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node Calls Total", + "", + `evm_pool_rpc_node_calls_total{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node Dials Success", + "", + `evm_pool_rpc_node_dials_success{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node Dials Failed", + "", + `evm_pool_rpc_node_dials_failed{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node Dials Total", + "", + `evm_pool_rpc_node_dials_total{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node Dials Failed", + "", + `evm_pool_rpc_node_dials_failed{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Total Transitions to Alive", + "", + `evm_pool_rpc_node_num_transitions_to_alive{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Total Transitions to In Sync", + "", + `evm_pool_rpc_node_num_transitions_to_in_sync{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Total Transitions to Out of Sync", + "", + `evm_pool_rpc_node_num_transitions_to_out_of_sync{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Total Transitions to Unreachable", + "", + `evm_pool_rpc_node_num_transitions_to_unreachable{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Total Transitions to invalid Chain ID", + "", + `evm_pool_rpc_node_num_transitions_to_invalid_chain_id{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Total Transitions to unusable", + "", + `evm_pool_rpc_node_num_transitions_to_unusable{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node Polls Success", + "", + `evm_pool_rpc_node_polls_success{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node Polls Total", + "", + `evm_pool_rpc_node_polls_total{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node States", + "", + `evm_pool_rpc_node_states{namespace="${namespace}"}`, + "{{pod}} - {{evmChainID}} - {{state}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node Verifies Total", + "", + `evm_pool_rpc_node_verifies{namespace="${namespace}"}`, + "{{pod}} - {{evmChainID}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node Verifies Success", + "", + `evm_pool_rpc_node_verifies_success{namespace="${namespace}"}`, + "{{pod}} - {{evmChainID}}", + ), + m.timeseriesRowOption( + "EVM Pool RPC Node Verifies Failed", + "", + `evm_pool_rpc_node_verifies_failed{namespace="${namespace}"}`, + "{{pod}} - {{evmChainID}}", + ), + ), + dashboard.Row( + "EVM Pool RPC Node Latencies (App)", + row.Collapse(), + m.timeseriesRowOption( + "EVM Pool RPC Node Calls Latency 0.95 quantile", + "ms", + `histogram_quantile(0.95, sum(rate(evm_pool_rpc_node_rpc_call_time_bucket{namespace="${namespace}"}[$__rate_interval])) by (le, rpcCallName)) / 1e6`, + "{{pod}}", + ), + ), + dashboard.Row( + "Pipeline Metrics (Runner)", + row.Collapse(), + m.timeseriesRowOption( + "Pipeline Task Execution Time", + "Sec", + `pipeline_task_execution_time{namespace="${namespace}"} / 1e6`, + "{{ pod }} JobID: {{ job_id }}", + ), + m.timeseriesRowOption( + "Pipeline Run Errors", + "", + `pipeline_run_errors{namespace="${namespace}"}`, + "{{ pod }} JobID: {{ job_id }}", + ), + m.timeseriesRowOption( + "Pipeline Run Total Time to Completion", + "Sec", + `pipeline_run_total_time_to_completion{namespace="${namespace}"} / 1e6`, + "{{ pod }} JobID: {{ job_id }}", + ), + m.timeseriesRowOption( + "Pipeline Tasks Total Finished", + "", + `pipeline_tasks_total_finished{namespace="${namespace}"}`, + "{{ pod }} JobID: {{ job_id }}", + ), + ), + dashboard.Row( + "Pipeline Metrics (ETHCall)", + row.Collapse(), + m.timeseriesRowOption( + "Pipeline Task ETH Call Execution Time", + "Sec", + `pipeline_task_eth_call_execution_time{namespace="${namespace}"}`, + "{{pod}}", + ), + ), + dashboard.Row( + "Pipeline Metrics (HTTP)", + row.Collapse(), + m.timeseriesRowOption( + "Pipeline Task HTTP Fetch Time", + "Sec", + `pipeline_task_http_fetch_time{namespace="${namespace}"} / 1e6`, + "{{pod}}", + ), + m.timeseriesRowOption( + "Pipeline Task HTTP Response Body Size", + "Bytes", + `pipeline_task_http_response_body_size{namespace="${namespace}"}`, + "{{pod}}", + ), + ), + dashboard.Row( + "Pipeline Metrics (Bridge)", + row.Collapse(), + m.timeseriesRowOption( + "Pipeline Bridge Latency", + "Sec", + `bridge_latency_seconds{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "Pipeline Bridge Errors Total", + "", + `bridge_errors_total{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "Pipeline Bridge Cache Hits Total", + "", + `bridge_cache_hits_total{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "Pipeline Bridge Cache Errors Total", + "", + `bridge_cache_errors_total{namespace="${namespace}"}`, + "{{pod}}", + ), + ), + dashboard.Row( + "Pipeline Metrics", + row.Collapse(), + m.timeseriesRowOption( + "Pipeline Runs Queued", + "", + `pipeline_runs_queued{namespace="${namespace}"}`, + "{{pod}}", + ), + m.timeseriesRowOption( + "Pipeline Runs Tasks Queued", + "", + `pipeline_task_runs_queued{namespace="${namespace}"}`, + "{{pod}}", + ), + ), + } + logOptsFinal := make([]row.Option, 0) + logOptsFinal = append( + logOptsFinal, + row.Collapse(), + row.WithTimeSeries( + "Log Counters", + timeseries.Span(12), + timeseries.Height("200px"), + timeseries.DataSource(m.PrometheusDataSourceName), + timeseries.WithPrometheusTarget( + `log_panic_count{namespace="${namespace}"}`, + prometheus.Legend("{{pod}} - panic"), + ), + timeseries.WithPrometheusTarget( + `log_fatal_count{namespace="${namespace}"}`, + prometheus.Legend("{{pod}} - fatal"), + ), + timeseries.WithPrometheusTarget( + `log_critical_count{namespace="${namespace}"}`, + prometheus.Legend("{{pod}} - critical"), + ), + timeseries.WithPrometheusTarget( + `log_warn_count{namespace="${namespace}"}`, + prometheus.Legend("{{pod}} - warn"), + ), + timeseries.WithPrometheusTarget( + `log_error_count{namespace="${namespace}"}`, + prometheus.Legend("{{pod}} - error"), + ), + ), + m.logsRowOption("All errors", ` + {namespace="${namespace}", app="app", container="node"} + | json + | level="error" + | line_format "{{ .instance }} {{ .level }} {{ .ts }} {{ .logger }} {{ .caller }} {{ .msg }} {{ .version }} {{ .nodeTier }} {{ .nodeName }} {{ .node }} {{ .evmChainID }} {{ .nodeOrder }} {{ .mode }} {{ .nodeState }} {{ .sentryEventID }} {{ .stacktrace }}"`), + ) + logOptsFinal = append(logOptsFinal, m.logsRowOptionsForNodes(m.Nodes)...) + logRowOpts := dashboard.Row( + "Logs", + logOptsFinal..., + ) + opts = append(opts, logRowOpts) + opts = append(opts, m.extendedOpts...) + builder, err := dashboard.New( + "Chainlink Cluster Dashboard", + opts..., + ) + m.opts = opts + m.builder = builder + return err +} + +// Deploy deploys the dashboard to Grafana +func (m *CLClusterDashboard) Deploy(ctx context.Context) error { + client := grabana.NewClient(&http.Client{}, m.GrafanaURL, grabana.WithAPIToken(m.GrafanaToken)) + folder, err := client.FindOrCreateFolder(ctx, m.Folder) + if err != nil { + return errors.Wrap(err, ErrFailedToCreateFolder) + } + if _, err := client.UpsertDashboard(ctx, folder, m.builder); err != nil { + return errors.Wrap(err, ErrFailedToCreateDashboard) + } + return nil +} diff --git a/charts/chainlink-cluster/devspace.yaml b/charts/chainlink-cluster/devspace.yaml index 54b5f9f01e9..cb4c8bfce49 100644 --- a/charts/chainlink-cluster/devspace.yaml +++ b/charts/chainlink-cluster/devspace.yaml @@ -32,6 +32,7 @@ images: deployments: app: helm: + releaseName: "app" chart: name: cl-cluster path: . @@ -39,26 +40,56 @@ deployments: # they can be defined the same way in values.yml # devspace merging this "values" and "values.yml" before deploy values: - runner: - image: ${DEVSPACE_IMAGE} - stateful: false - geth: - version: v1.12.0 - wsrpc-port: 8546 - httprpc-port: 8544 - networkid: 1337 - blocktime: 1 - mockserver: - port: 1080 - db: - stateful: false + podSecurityContext: + fsGroup: 999 + chainlink: + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 14933 + runAsGroup: 999 web_port: 6688 - p2p_port: 8090 + p2p_port: 6690 nodes: - name: node-1 image: ${DEVSPACE_IMAGE} version: latest + # override default config per node + # for example, use OCRv2 P2P setup, the whole config + # toml: | + # RootDir = './clroot' + # [Log] + # JSONConsole = true + # Level = 'debug' + # [WebServer] + # AllowOrigins = '*' + # SecureCookies = false + # SessionTimeout = '999h0m0s' + # [OCR2] + # Enabled = true + # [P2P] + # [P2P.V2] + # Enabled = false + # AnnounceAddresses = [] + # DefaultBootstrappers = [] + # DeltaDial = '15s' + # DeltaReconcile = '1m0s' + # ListenAddresses = [] + # [[EVM]] + # ChainID = '1337' + # MinContractPayment = '0' + # [[EVM.Nodes]] + # Name = 'node-0' + # WSURL = 'ws://geth:8546' + # HTTPURL = 'http://geth:8544' + # [WebServer.TLS] + # HTTPSPort = 0 + # or use overridesToml to override some part of configuration + # overridesToml: | - name: node-2 image: ${DEVSPACE_IMAGE} version: latest @@ -68,10 +99,131 @@ deployments: - name: node-4 image: ${DEVSPACE_IMAGE} version: latest - podAnnotations: { } - nodeSelector: { } - tolerations: [ ] - affinity: { } + - name: node-5 + image: ${DEVSPACE_IMAGE} + version: latest + - name: node-6 + image: ${DEVSPACE_IMAGE} + version: latest + resources: + requests: + cpu: 350m + memory: 1024Mi + limits: + cpu: 350m + memory: 1024Mi + + # each CL node have a dedicated PostgreSQL 11.15 + # use StatefulSet by setting: + # + # stateful: true + # capacity 10Gi + # + # if you are running long tests + db: + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + stateful: false + resources: + requests: + cpu: 1 + memory: 1024Mi + limits: + cpu: 1 + memory: 1024Mi + # default cluster shipped with latest Geth ( dev mode by default ) + geth: + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + version: v1.12.0 + wsrpc-port: 8546 + httprpc-port: 8544 + networkid: 1337 + blocktime: 1 + resources: + requests: + cpu: 1 + memory: 1024Mi + limits: + cpu: 1 + memory: 1024Mi + # mockserver is https://www.mock-server.com/where/kubernetes.html + # used to stub External Adapters + mockserver: + # image: "mockserver/mockserver" + # version: "mockserver-5.15.0" + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + enabled: true + releasenameOverride: mockserver + app: + runAsUser: 999 + readOnlyRootFilesystem: false + port: 1080 + resources: + requests: + cpu: 1 + memory: 1024Mi + limits: + cpu: 1 + memory: 1024Mi + runner: + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + stateful: false + resources: + requests: + cpu: 1 + memory: 512Mi + limits: + cpu: 1 + memory: 512Mi + affinity: { } + tolerations: [ ] + nodeSelector: { } + ingress: + enabled: false + className: "" + hosts: [ ] + tls: [ ] + annotations: { } + service: + type: NodePort + port: 8080 + + + # monitoring.coreos.com/v1 PodMonitor for each node + prometheusMonitor: true + + # deployment placement, standard helm stuff + podAnnotations: + nodeSelector: + tolerations: + affinity: profiles: # this replaces only "runner" pod, usable when you'd like to run some system level tests inside k8s diff --git a/charts/chainlink-cluster/go.mod b/charts/chainlink-cluster/go.mod new file mode 100644 index 00000000000..951fb1d2e3c --- /dev/null +++ b/charts/chainlink-cluster/go.mod @@ -0,0 +1,190 @@ +module github.com/smartcontractkit/chainlink/v2/dashboard + +go 1.21 + +require ( + github.com/K-Phoen/grabana v0.21.19 + github.com/pkg/errors v0.9.1 + github.com/smartcontractkit/wasp v0.3.6 +) + +require ( + github.com/K-Phoen/sdk v0.12.3 // indirect + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go v1.44.217 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee // indirect + github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dennwc/varint v1.0.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/edsrzf/mmap-go v1.1.0 // indirect + github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/fatih/color v1.14.1 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.8.1 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.21.4 // indirect + github.com/go-openapi/errors v0.20.3 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/loads v0.21.2 // indirect + github.com/go-openapi/spec v0.20.8 // indirect + github.com/go-openapi/strfmt v0.21.3 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/validate v0.22.1 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/go-resty/resty/v2 v2.7.0 // indirect + github.com/goccy/go-json v0.9.11 // indirect + github.com/gogo/googleapis v1.4.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/gogo/status v1.1.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.1.2 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/gosimple/slug v1.13.1 // indirect + github.com/gosimple/unidecode v1.0.1 // indirect + github.com/grafana/dskit v0.0.0-20230201083518-528d8a7d52f2 // indirect + github.com/grafana/loki v1.6.2-0.20231017135925-990ac685e6a6 // indirect + github.com/grafana/loki/pkg/push v0.0.0-20230127102416-571f88bc5765 // indirect + github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect + github.com/hashicorp/consul/api v1.20.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hclog v1.4.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack v0.5.5 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/golang-lru v0.6.0 // indirect + github.com/hashicorp/memberlist v0.5.0 // indirect + github.com/hashicorp/serf v0.10.1 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/julienschmidt/httprouter v1.3.0 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/miekg/dns v1.1.51 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e // indirect + github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/alertmanager v0.25.0 // indirect + github.com/prometheus/client_golang v1.15.1 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/common/sigv4 v0.1.0 // indirect + github.com/prometheus/exporter-toolkit v0.9.1 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/prometheus v0.43.1-0.20230327151049-211ae4f1f0a2 // indirect + github.com/rs/zerolog v1.29.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect + github.com/sercand/kuberesolver/v4 v4.0.0 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/testify v1.8.3 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + github.com/weaveworks/common v0.0.0-20230411130259-f7d83a041205 // indirect + github.com/weaveworks/promrus v1.2.0 // indirect + go.etcd.io/etcd/api/v3 v3.5.7 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect + go.etcd.io/etcd/client/v3 v3.5.7 // indirect + go.mongodb.org/mongo-driver v1.11.2 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 // indirect + go.opentelemetry.io/otel v1.14.0 // indirect + go.opentelemetry.io/otel/metric v0.37.0 // indirect + go.opentelemetry.io/otel/trace v1.14.0 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/goleak v1.2.1 // indirect + go.uber.org/multierr v1.9.0 // indirect + go.uber.org/ratelimit v0.2.0 // indirect + go.uber.org/zap v1.21.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20230307190834-24139beb5833 // indirect + golang.org/x/mod v0.9.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.7.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect + google.golang.org/grpc v1.57.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.26.2 // indirect + k8s.io/apimachinery v0.26.2 // indirect + k8s.io/client-go v0.26.2 // indirect + k8s.io/klog/v2 v2.90.1 // indirect + k8s.io/kube-openapi v0.0.0-20230303024457-afdc3dddf62d // indirect + k8s.io/utils v0.0.0-20230308161112-d77c459e9343 // indirect + nhooyr.io/websocket v1.8.7 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) + +replace ( + // Fixes go mod tidy issue for ambiguous imports from go-ethereum + // See https://github.com/ugorji/go/issues/279 + github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.22.1 + + github.com/go-kit/log => github.com/go-kit/log v0.2.1 + + // replicating the replace directive on cosmos SDK + github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 + + // until merged upstream: https://github.com/hashicorp/go-plugin/pull/257 + github.com/hashicorp/go-plugin => github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 + + // until merged upstream: https://github.com/mwitkow/grpc-proxy/pull/69 + github.com/mwitkow/grpc-proxy => github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f + + github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.43.1-0.20230327151049-211ae4f1f0a2 + github.com/sercand/kuberesolver/v4 => github.com/sercand/kuberesolver/v5 v5.1.1 +) diff --git a/charts/chainlink-cluster/go.sum b/charts/chainlink-cluster/go.sum new file mode 100644 index 00000000000..4a7704cac49 --- /dev/null +++ b/charts/chainlink-cluster/go.sum @@ -0,0 +1,2126 @@ +cloud.google.com/go v0.26.0/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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +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.44.3/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.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.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +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.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +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/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +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/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +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/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +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= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= +github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +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.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= +github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= +github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc= +github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= +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/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= +github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= +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.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/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/K-Phoen/grabana v0.21.19 h1:tJjRO8nN9JrFjLoQGtOB9P5ILoqENZZGAtt3nK+Ry2Y= +github.com/K-Phoen/grabana v0.21.19/go.mod h1:B7gxVxacQUgHWmgqduf4WPZoKYHO1mvZnRVCoyQiwdw= +github.com/K-Phoen/sdk v0.12.3 h1:ScutEQASc9VEKJCm3OjIMD82BIS9B2XtNg3gEf6Gs+M= +github.com/K-Phoen/sdk v0.12.3/go.mod h1:qmM0wO23CtoDux528MXPpYvS4XkRWkWX6rvX9Za8EVU= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE= +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/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= +github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.44.217 h1:FcWC56MRl+k756aH3qeMQTylSdeJ58WN0iFz3fkyRz0= +github.com/aws/aws-sdk-go v1.44.217/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +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/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee h1:BnPxIde0gjtTnc9Er7cxvBk8DHLWhEux0SxayC8dP6I= +github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 h1:SjZ2GvvOononHOpK84APFuMvxqsk3tEIaKH/z4Rpu3g= +github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8/go.mod h1:uEyr4WpAH4hio6LFriaPkL938XnrvLpNPmQHBdrmbIE= +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/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +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/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/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-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= +github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= +github.com/digitalocean/godo v1.97.0 h1:p9w1yCcWMZcxFSLPToNGXA96WfUVLXqoHti6GzVomL4= +github.com/digitalocean/godo v1.97.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY= +github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= +github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +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.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f h1:7T++XKzy4xg7PKy+bM+Sa9/oe1OC88yz2hXQUISoXfA= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +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.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +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= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +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-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/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +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.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= +github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= +github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= +github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc= +github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= +github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro= +github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= +github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= +github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= +github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= +github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= +github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +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.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= +github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/status v1.0.3/go.mod h1:SavQ51ycCLnc7dGyJxp8YAmudx8xqiVrRf+6IXRsugc= +github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= +github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +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/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +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/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 v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/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/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/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/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.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/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/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/martian/v3 v3.3.2/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-20201218002935-b9804c9f04c2/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-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/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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +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/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gophercloud/gophercloud v1.2.0 h1:1oXyj4g54KBg/kFtCdMM6jtxSzeIyg8wv4z1HoGPp1E= +github.com/gophercloud/gophercloud v1.2.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= +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/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +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/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q= +github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= +github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= +github.com/grafana/dskit v0.0.0-20230201083518-528d8a7d52f2 h1:IOks+FXJ6iO/pfbaVEf4efNw+YzYBYNCkCabyrbkFTM= +github.com/grafana/dskit v0.0.0-20230201083518-528d8a7d52f2/go.mod h1:zj+5BNZAVmQafV583uLTAOzRr963KPdEm4d6NPmtbwg= +github.com/grafana/loki v1.6.2-0.20231017135925-990ac685e6a6 h1:V5PspEXlSlNh22sMyGkgfSOVVLTsSmhbmsp1VPt8Fdc= +github.com/grafana/loki v1.6.2-0.20231017135925-990ac685e6a6/go.mod h1:+aWr7OBDuZMT+p0rKmLfW5saO2m3YOGBnt++IlgLhVk= +github.com/grafana/loki/pkg/push v0.0.0-20230127102416-571f88bc5765 h1:VXitROTlmZtLzvokNe8ZbUKpmwldM4Hy1zdNRO32jKU= +github.com/grafana/loki/pkg/push v0.0.0-20230127102416-571f88bc5765/go.mod h1:DhJMrd2QInI/1CNtTN43BZuTmkccdizW1jZ+F6aHkhY= +github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww= +github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= +github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= +github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= +github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= +github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= +github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I= +github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +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-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +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-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= +github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +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-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +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-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4= +github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/nomad/api v0.0.0-20230308192510-48e7d70fcd4b h1:EkuSTU8c/63q4LMayj8ilgg/4I5PXDFVcnqKfs9qcwI= +github.com/hashicorp/nomad/api v0.0.0-20230308192510-48e7d70fcd4b/go.mod h1:bKUb1ytds5KwUioHdvdq9jmrDqCThv95si0Ub7iNeBg= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/hetznercloud/hcloud-go v1.41.0 h1:KJGFRRc68QiVu4PrEP5BmCQVveCP2CM26UGQUKGpIUs= +github.com/hetznercloud/hcloud-go v1.41.0/go.mod h1:NaHg47L6C77mngZhwBG652dTAztYrsZ2/iITJKhQkHA= +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.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/ionos-cloud/sdk-go/v6 v6.1.4 h1:BJHhFA8Q1SZC7VOXqKKr2BV2ysQ2/4hlk1e4hZte7GY= +github.com/ionos-cloud/sdk-go/v6 v6.1.4/go.mod h1:Ox3W0iiEz0GHnfY9e5LmAxwklsxguuNFEUSu0gVRTME= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +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 v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +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/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= +github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= +github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= +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/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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +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/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/linode/linodego v1.14.1 h1:uGxQyy0BidoEpLGdvfi4cPgEW+0YUFsEGrLEhcTfjNc= +github.com/linode/linodego v1.14.1/go.mod h1:NJlzvlNtdMRRkXb0oN6UWzUkj6t+IBsyveHgZ5Ppjyk= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +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.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/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +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.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +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/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo= +github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +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/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-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +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.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc= +github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg= +github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= +github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= +github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= +github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/ovh/go-ovh v1.3.0 h1:mvZaddk4E4kLcXhzb+cxBsMPYp2pHqiQpWYkInsuZPQ= +github.com/ovh/go-ovh v1.3.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/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/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/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/prometheus/alertmanager v0.25.0 h1:vbXKUR6PYRiZPRIKfmXaG+dmCKG52RtPL4Btl8hQGvg= +github.com/prometheus/alertmanager v0.25.0/go.mod h1:MEZ3rFVHqKZsw7IcNS/m4AWZeXThmJhumpiWR4eHU/w= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +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.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/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.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +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.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= +github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= +github.com/prometheus/exporter-toolkit v0.8.2/go.mod h1:00shzmJL7KxcsabLWcONwpyNEuWhREOnFqZW7vadFS0= +github.com/prometheus/exporter-toolkit v0.9.1 h1:cNkC01riqiOS+kh3zdnNwRsbe/Blh0WwK3ij5rPJ9Sw= +github.com/prometheus/exporter-toolkit v0.9.1/go.mod h1:iFlTmFISCix0vyuyBmm0UqOUCTao9+RsAsKJP3YM9ec= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +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.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/prometheus v0.43.1-0.20230327151049-211ae4f1f0a2 h1:i5hmbBzR+VeL5pPl1ZncsJ1bpg3SO66bwkE1msJBsMA= +github.com/prometheus/prometheus v0.43.1-0.20230327151049-211ae4f1f0a2/go.mod h1:Mm42Acga98xgA+u5yTaC3ki3i0rJEJWFpbdHN7q2trk= +github.com/pyroscope-io/client v0.6.0 h1:rcUFgcnfmuyVYDYT+4d0zfqc8YedOyruHSsUb9ImaBw= +github.com/pyroscope-io/client v0.6.0/go.mod h1:4h21iOU4pUOq0prKyDlvYRL+SCKsBc5wKiEtV+rJGqU= +github.com/pyroscope-io/godeltaprof v0.1.2 h1:MdlEmYELd5w+lvIzmZvXGNMVzW2Qc9jDMuJaPOR75g4= +github.com/pyroscope-io/godeltaprof v0.1.2/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +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.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= +github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14 h1:yFl3jyaSVLNYXlnNYM5z2pagEk1dYQhfr1p20T1NyKY= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sercand/kuberesolver/v5 v5.1.1 h1:CYH+d67G0sGBj7q5wLK61yzqJJ8gLLC8aeprPTHb6yY= +github.com/sercand/kuberesolver/v5 v5.1.1/go.mod h1:Fs1KbKhVRnB2aDWN12NjKCB+RgYMWZJ294T3BtmVCpQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.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.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartcontractkit/wasp v0.3.6 h1:1TLWfrTzqZwNvyyoKzPZ8FLQat2lNz640eM+mMh2YxM= +github.com/smartcontractkit/wasp v0.3.6/go.mod h1:L/cyUGfpaWxy/2twOVJLRt2mySJEIqGrFj9nyvRLpSo= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/uber/jaeger-client-go v2.28.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= +github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= +github.com/weaveworks/common v0.0.0-20230411130259-f7d83a041205 h1:gjb7t9LCnRu14LHubyLIgrE+EYlAaREiPn/VknV7R3s= +github.com/weaveworks/common v0.0.0-20230411130259-f7d83a041205/go.mod h1:O9wmSPNVSuqxzUZPFlHnPQ8xnyvx0qBnKGFfGbj95uY= +github.com/weaveworks/promrus v1.2.0 h1:jOLf6pe6/vss4qGHjXmGz4oDJQA+AOCqEL3FvvZGz7M= +github.com/weaveworks/promrus v1.2.0/go.mod h1:SaE82+OJ91yqjrE1rsvBWVzNZKcHYFtMUyS1+Ogs/KA= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +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.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY= +go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA= +go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg= +go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY= +go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4= +go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw= +go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= +go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= +go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= +go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw= +go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= +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.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/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 h1:lE9EJyw3/JhrjWH/hEy9FptnalDQgj7vpbgC2KCCCxE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0/go.mod h1:pcQ3MM3SWvrA71U4GDqv9UFDJ3HQsW7y5ZO3tDTlUdI= +go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= +go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= +go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs= +go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s= +go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= +go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= +go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/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-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +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-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +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-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s= +golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +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/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +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/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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/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/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +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-20181114220301-adae6a3d119a/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-20190213061140-3a22650c66bd/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-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-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-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-20190921015927-1a5e07d1ff72/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-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-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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +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-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-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-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-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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-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-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +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-20190412183630-56d357773e84/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20181116152217-5ac8a444bdc5/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-20190222072716-a9d3bda3a223/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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/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-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/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-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-20200122134326-e047566fdf82/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-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-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20210104204734-6f8348627aad/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-20210225134936-a50acf3fe073/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-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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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-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-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-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-20211210111614-af8b64212486/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/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-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-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/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-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/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-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +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-20190927191325-030b2cf1153e/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-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-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-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-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-20200501065659-ab2804fb9c9d/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-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-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/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.0.0-20210108195828-e2f9c7f1fc8e/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.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +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= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +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.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.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +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/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/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-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-20200115191322-ca5a22157cba/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-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/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-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-20201109203340-2640f1f9cdfb/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-20210108203827-ffc7fda8c3d7/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-20210226172003-ab064af71705/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-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +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-20211118181313-81c1377c94b1/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-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M= +google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= +google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ= +google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +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.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.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.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.57.2 h1:uw37EN34aMFFXB2QPW7Tq6tdTbind1GpRxw5aOX3a5k= +google.golang.org/grpc v1.57.2/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +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.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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-20200605160147-a5ece683394c/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= +k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= +k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= +k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= +k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= +k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230303024457-afdc3dddf62d h1:VcFq5n7wCJB2FQMCIHfC+f+jNcGgNMar1uKd6rVlifU= +k8s.io/kube-openapi v0.0.0-20230303024457-afdc3dddf62d/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY= +k8s.io/utils v0.0.0-20230308161112-d77c459e9343 h1:m7tbIjXGcGIAtpmQr7/NAi7RsWoW3E7Zcm4jI1HicTc= +k8s.io/utils v0.0.0-20230308161112-d77c459e9343/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +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/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/charts/chainlink-cluster/templates/chainlink-cm.yaml b/charts/chainlink-cluster/templates/chainlink-cm.yaml index fa9c2c2657d..b33e29df4b5 100644 --- a/charts/chainlink-cluster/templates/chainlink-cm.yaml +++ b/charts/chainlink-cluster/templates/chainlink-cm.yaml @@ -26,16 +26,25 @@ data: AllowOrigins = '*' SecureCookies = false SessionTimeout = '999h0m0s' + [Feature] + FeedsManager = true + LogPoller = true + UICSAKeys = true [OCR] Enabled = true + DefaultTransactionQueueDepth = 0 [P2P] - [P2P.V1] + [P2P.V2] Enabled = true - ListenIP = '0.0.0.0' - ListenPort = 6690 + ListenAddresses = ['0.0.0.0:6690'] + AnnounceAddresses = ['0.0.0.0:6690'] + DeltaDial = '500ms' + DeltaReconcile = '5s' [[EVM]] ChainID = '1337' MinContractPayment = '0' + AutoCreateKey = true + FinalityDepth = 1 [[EVM.Nodes]] Name = 'node-0' WSURL = 'ws://geth:8546' diff --git a/charts/chainlink-cluster/templates/chainlink-deployment.yaml b/charts/chainlink-cluster/templates/chainlink-db-deployment.yaml similarity index 59% rename from charts/chainlink-cluster/templates/chainlink-deployment.yaml rename to charts/chainlink-cluster/templates/chainlink-db-deployment.yaml index 16665916f59..f335130ea9f 100644 --- a/charts/chainlink-cluster/templates/chainlink-deployment.yaml +++ b/charts/chainlink-cluster/templates/chainlink-db-deployment.yaml @@ -6,10 +6,10 @@ kind: StatefulSet kind: Deployment {{ end }} metadata: - name: {{ $.Release.Name }}-{{ $cfg.name }} + name: {{ $.Release.Name }}-{{ $cfg.name }}-db spec: {{ if $.Values.db.stateful }} - serviceName: {{ $.Release.Name }}-{{ $cfg.name }}-service + serviceName: {{ $.Release.Name }}-db-${{ $cfg.name }} podManagementPolicy: Parallel volumeClaimTemplates: - metadata: @@ -23,30 +23,37 @@ spec: {{ end }} selector: matchLabels: - app: {{ $.Release.Name }} - instance: {{ $cfg.name }} + app: {{ $.Release.Name }}-db + instance: {{ $cfg.name }}-db release: {{ $.Release.Name }} template: metadata: labels: - app: {{ $.Release.Name }} - instance: {{ $cfg.name }} + app: {{ $.Release.Name }}-db + instance: {{ $cfg.name }}-db release: {{ $.Release.Name }} {{- range $key, $value := $.Values.labels }} {{ $key }}: {{ $value | quote }} {{- end }} annotations: prometheus.io/scrape: 'true' + app.kubernetes.io/managed-by: "Helm" + meta.helm.sh/release-namespace: "{{ $.Release.Namespace }}" {{- range $key, $value := $.Values.podAnnotations }} {{ $key }}: {{ $value | quote }} {{- end }} spec: volumes: + # TODO: breakout this config map into a separate one for the db. - name: {{ $.Release.Name }}-{{ $cfg.name }}-cm configMap: name: {{ $.Release.Name }}-{{ $cfg.name }}-cm + securityContext: + {{- toYaml $.Values.db.podSecurityContext | nindent 8 }} containers: - name: chainlink-db + securityContext: + {{- toYaml $.Values.db.securityContext | nindent 12 }} image: {{ default "postgres:11.15" $.Values.db.image }} command: - docker-entrypoint.sh @@ -112,60 +119,11 @@ spec: - mountPath: /docker-entrypoint-initdb.d/init.sql name: {{ $.Release.Name }}-{{ $cfg.name }}-cm subPath: init.sql - {{ if $.Values.db.stateful }} - volumeMounts: + {{ if $.Values.db.stateful }} - mountPath: /var/lib/postgresql/data name: postgres subPath: postgres-db - {{ end }} - - name: node - image: {{ default "public.ecr.aws/chainlink/chainlink" $cfg.image }} - imagePullPolicy: Always - command: ["bash", "-c", "while ! pg_isready --host 0.0.0.0 --port 5432; do echo \"waiting for database to start\"; sleep 1; done && chainlink -c /etc/node-secrets-volume/default.toml -c /etc/node-secrets-volume/overrides.toml -secrets /etc/node-secrets-volume/secrets.toml node start -d -p /etc/node-secrets-volume/node-password -a /etc/node-secrets-volume/apicredentials --vrfpassword=/etc/node-secrets-volume/apicredentials"] - ports: - - name: access - containerPort: {{ $.Values.chainlink.web_port }} - - name: p2p - containerPort: {{ $.Values.chainlink.p2p_port }} - env: - - name: CL_DATABASE_URL - value: postgresql://postgres:verylongdatabasepassword@0.0.0.0/chainlink?sslmode=disable - - name: CL_DEV - value: "false" - volumeMounts: - - name: {{ $.Release.Name }}-{{ $cfg.name }}-cm - mountPath: /etc/node-secrets-volume/ - livenessProbe: - httpGet: - path: /health - port: {{ $.Values.chainlink.web_port }} - initialDelaySeconds: 1 - periodSeconds: 5 - timeoutSeconds: 10 - readinessProbe: - httpGet: - path: /health - port: {{ $.Values.chainlink.web_port }} - initialDelaySeconds: 1 - periodSeconds: 5 - timeoutSeconds: 10 - startupProbe: - httpGet: - path: / - port: {{ $.Values.chainlink.web_port }} - initialDelaySeconds: 15 - periodSeconds: 5 - failureThreshold: 20 - {{ if (hasKey $.Values.chainlink "resources") }} - resources: - requests: - memory: {{ default "1024Mi" $.Values.chainlink.resources.requests.memory }} - cpu: {{ default "500m" $.Values.chainlink.resources.requests.cpu }} - limits: - memory: {{ default "1024Mi" $.Values.chainlink.resources.limits.memory }} - cpu: {{ default "500m" $.Values.chainlink.resources.limits.cpu }} - {{ else }} - {{ end }} + {{ end }} {{- with $.Values.nodeSelector }} nodeSelector: {{ toYaml . | indent 8 }} @@ -179,4 +137,4 @@ spec: {{ toYaml . | indent 8 }} {{- end }} --- -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/chainlink-cluster/templates/chainlink-db-service.yaml b/charts/chainlink-cluster/templates/chainlink-db-service.yaml new file mode 100644 index 00000000000..f27bd9eab20 --- /dev/null +++ b/charts/chainlink-cluster/templates/chainlink-db-service.yaml @@ -0,0 +1,16 @@ +{{- range $cfg := .Values.chainlink.nodes }} +apiVersion: v1 +kind: Service +metadata: + name: {{ $.Release.Name }}-db-{{ $cfg.name }} +spec: + selector: + app: {{ $.Release.Name }}-db + instance: {{ $cfg.name }}-db + release: {{ $.Release.Name }} + ports: + - protocol: TCP + port: 5432 + targetPort: 5432 +--- +{{- end }} \ No newline at end of file diff --git a/charts/chainlink-cluster/templates/chainlink-node-deployment.yaml b/charts/chainlink-cluster/templates/chainlink-node-deployment.yaml new file mode 100644 index 00000000000..463453aff93 --- /dev/null +++ b/charts/chainlink-cluster/templates/chainlink-node-deployment.yaml @@ -0,0 +1,97 @@ +{{- range $cfg := .Values.chainlink.nodes }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Release.Name }}-{{ $cfg.name }} +spec: + selector: + matchLabels: + app: {{ $.Release.Name }} + instance: {{ $cfg.name }} + release: {{ $.Release.Name }} + template: + metadata: + labels: + app: {{ $.Release.Name }} + instance: {{ $cfg.name }} + release: {{ $.Release.Name }} + {{- range $key, $value := $.Values.labels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + annotations: + prometheus.io/scrape: 'true' + {{- range $key, $value := $.Values.podAnnotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + spec: + volumes: + - name: {{ $.Release.Name }}-{{ $cfg.name }}-cm + configMap: + name: {{ $.Release.Name }}-{{ $cfg.name }}-cm + securityContext: + {{- toYaml $.Values.chainlink.podSecurityContext | nindent 8 }} + containers: + - name: node + securityContext: + {{- toYaml $.Values.chainlink.securityContext | nindent 12 }} + image: {{ default "public.ecr.aws/chainlink/chainlink" $cfg.image }} + imagePullPolicy: Always + command: ["bash", "-c", "while ! pg_isready -U postgres --host {{ $.Release.Name }}-db-{{ $cfg.name }} --port 5432; do echo \"waiting for database to start\"; sleep 1; done && chainlink -c /etc/node-secrets-volume/default.toml -c /etc/node-secrets-volume/overrides.toml -secrets /etc/node-secrets-volume/secrets.toml node start -d -p /etc/node-secrets-volume/node-password -a /etc/node-secrets-volume/apicredentials --vrfpassword=/etc/node-secrets-volume/apicredentials"] + ports: + - name: access + containerPort: {{ $.Values.chainlink.web_port }} + - name: p2p + containerPort: {{ $.Values.chainlink.p2p_port }} + env: + - name: CL_DATABASE_URL + value: postgresql://postgres:verylongdatabasepassword@{{ $.Release.Name }}-db-{{ $cfg.name }}/chainlink?sslmode=disable + - name: CL_DEV + value: "false" + volumeMounts: + - name: {{ $.Release.Name }}-{{ $cfg.name }}-cm + mountPath: /etc/node-secrets-volume/ + livenessProbe: + httpGet: + path: /health + port: {{ $.Values.chainlink.web_port }} + initialDelaySeconds: 1 + periodSeconds: 5 + timeoutSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: {{ $.Values.chainlink.web_port }} + initialDelaySeconds: 1 + periodSeconds: 5 + timeoutSeconds: 10 + startupProbe: + httpGet: + path: / + port: {{ $.Values.chainlink.web_port }} + initialDelaySeconds: 15 + periodSeconds: 5 + failureThreshold: 20 + {{ if (hasKey $.Values.chainlink "resources") }} + resources: + requests: + memory: {{ default "1024Mi" $.Values.chainlink.resources.requests.memory }} + cpu: {{ default "500m" $.Values.chainlink.resources.requests.cpu }} + limits: + memory: {{ default "1024Mi" $.Values.chainlink.resources.limits.memory }} + cpu: {{ default "500m" $.Values.chainlink.resources.limits.cpu }} + {{ else }} + {{ end }} +{{- with $.Values.nodeSelector }} + nodeSelector: + {{ toYaml . | indent 8 }} +{{- end }} +{{- with $.Values.affinity }} + affinity: + {{ toYaml . | indent 8 }} +{{- end }} +{{- with $.Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} +{{- end }} +--- +{{- end }} diff --git a/charts/chainlink-cluster/templates/chainlink-service.yaml b/charts/chainlink-cluster/templates/chainlink-node-service.yaml similarity index 93% rename from charts/chainlink-cluster/templates/chainlink-service.yaml rename to charts/chainlink-cluster/templates/chainlink-node-service.yaml index 24c96c909ea..7a3b70efb4f 100644 --- a/charts/chainlink-cluster/templates/chainlink-service.yaml +++ b/charts/chainlink-cluster/templates/chainlink-node-service.yaml @@ -12,7 +12,7 @@ spec: port: {{ $.Values.chainlink.p2p_port }} targetPort: {{ $.Values.chainlink.p2p_port }} selector: - app: {{ $.Release.Name }} + instance: {{ $cfg.name }} type: ClusterIP --- {{- end }} \ No newline at end of file diff --git a/charts/chainlink-cluster/templates/chainlink-pod-monitor.yaml b/charts/chainlink-cluster/templates/chainlink-pod-monitor.yaml index 4a74ff6c454..2cd9c3df2b6 100644 --- a/charts/chainlink-cluster/templates/chainlink-pod-monitor.yaml +++ b/charts/chainlink-cluster/templates/chainlink-pod-monitor.yaml @@ -1,16 +1,18 @@ -{{- range $cfg := .Values.chainlink.nodes }} {{- if $.Values.prometheusMonitor }} apiVersion: monitoring.coreos.com/v1 kind: PodMonitor metadata: + name: {{ $.Release.Name }}-pod-monitor labels: release: grafana-agent spec: - selector: - matchLabels: - release: {{ $.Release.Name }}-{{ $cfg.name }} + namespaceSelector: + matchNames: + - "cl-cluster" podMetricsEndpoints: - port: access - {{- end }} ---- -{{- end }} \ No newline at end of file + selector: + matchLabels: + app: {{ $.Release.Name }} +{{- end }} +--- \ No newline at end of file diff --git a/charts/chainlink-cluster/templates/geth-config-map.yaml b/charts/chainlink-cluster/templates/geth-config-map.yaml index f3369ba580f..022d9f2ea61 100644 --- a/charts/chainlink-cluster/templates/geth-config-map.yaml +++ b/charts/chainlink-cluster/templates/geth-config-map.yaml @@ -50,9 +50,9 @@ data: password.txt: | init.sh: | #!/bin/bash - if [ ! -d /root/.ethereum/keystore ]; then - echo "/root/.ethereum/keystore not found, running 'geth init'..." - geth init /root/ethconfig/genesis.json + if [ ! -d /app/.ethereum/keystore ]; then + echo "/app/.ethereum/keystore not found, running 'geth init'..." + geth init /app/ethconfig/genesis.json echo "...done!" fi diff --git a/charts/chainlink-cluster/templates/geth-deployment.yaml b/charts/chainlink-cluster/templates/geth-deployment.yaml index 11fb0cbee22..6948c4df288 100644 --- a/charts/chainlink-cluster/templates/geth-deployment.yaml +++ b/charts/chainlink-cluster/templates/geth-deployment.yaml @@ -22,31 +22,39 @@ spec: - name: configmap-volume configMap: name: geth-cm + - name: devchain-volume + emptyDir: {} + securityContext: + {{- toYaml $.Values.geth.podSecurityContext | nindent 8 }} containers: - name: geth-network + securityContext: + {{- toYaml $.Values.geth.securityContext | nindent 12 }} image: "{{ default "ethereum/client-go" .Values.geth.image }}:{{ default "stable" .Values.geth.version }}" - command: [ "sh", "./root/init.sh" ] + command: [ "sh", "/app/init.sh" ] volumeMounts: + - name: devchain-volume + mountPath: /app/.ethereum/devchain - name : configmap-volume - mountPath: /root/init.sh + mountPath: /app/init.sh subPath: init.sh - name: configmap-volume - mountPath: /root/config + mountPath: /app/config - name: configmap-volume - mountPath: /root/.ethereum/devchain/keystore/key1 + mountPath: /app/.ethereum/devchain/keystore/key1 subPath: key1 - name: configmap-volume - mountPath: /root/.ethereum/devchain/keystore/key2 + mountPath: /app/.ethereum/devchain/keystore/key2 subPath: key2 - name: configmap-volume - mountPath: /root/.ethereum/devchain/keystore/key3 + mountPath: /app/.ethereum/devchain/keystore/key3 subPath: key3 args: - '--dev' - '--password' - - '/root/config/password.txt' + - '/app/config/password.txt' - '--datadir' - - '/root/.ethereum/devchain' + - '/app/.ethereum/devchain' - '--unlock' - '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266' - '--unlock' diff --git a/charts/chainlink-cluster/templates/mockserver.yaml b/charts/chainlink-cluster/templates/mockserver.yaml deleted file mode 100755 index 96f9582435f..00000000000 --- a/charts/chainlink-cluster/templates/mockserver.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Release.Name }}-mockserver - labels: - app: {{ .Release.Name }}-mockserver -spec: - replicas: {{ .Values.replicaCount }} - selector: - matchLabels: - app: {{ .Release.Name }}-mockserver - template: - metadata: -{{- if .Values.podAnnotations }} - annotations: -{{ toYaml .Values.podAnnotations | indent 8 }} -{{- end }} - name: {{ .Release.Name }}-mockserver - labels: - app: {{ .Release.Name }}-mockserver - spec: - containers: - - name: {{ .Release.Name }}-mockserver - image: {{ default "mockserver/mockserver" .Values.mockserver.image }}:{{ default "mockserver-5.15.0" .Values.mockserver.version }} - imagePullPolicy: IfNotPresent - securityContext: - runAsUser: 65534 # nonroot - readOnlyRootFilesystem: false - ports: - - name: serviceport - containerPort: {{ .Values.mockserver.port }} - protocol: TCP - env: - - name: LOG_LEVEL - value: "DEBUG" - - name: SERVER_PORT - value: {{ .Values.mockserver.port | quote }} - {{ if (hasKey $.Values.chainlink "resources") }} - resources: - requests: - memory: {{ default "1024Mi" $.Values.chainlink.resources.requests.memory }} - cpu: {{ default "500m" $.Values.chainlink.resources.requests.cpu }} - limits: - memory: {{ default "1024Mi" $.Values.chainlink.resources.limits.memory }} - cpu: {{ default "500m" $.Values.chainlink.resources.limits.cpu }} - {{ else }} - {{ end }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{ toYaml . | indent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{ toYaml . | indent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{ toYaml . | indent 8 }} - {{- end }} ---- \ No newline at end of file diff --git a/charts/chainlink-cluster/templates/runner-deployment.yaml b/charts/chainlink-cluster/templates/runner-deployment.yaml index 5d9025b41c5..9d80ac1bfab 100644 --- a/charts/chainlink-cluster/templates/runner-deployment.yaml +++ b/charts/chainlink-cluster/templates/runner-deployment.yaml @@ -22,8 +22,12 @@ spec: annotations: prometheus.io/scrape: 'true' spec: + securityContext: + {{- toYaml $.Values.runner.podSecurityContext | nindent 8 }} containers: - name: runner + securityContext: + {{- toYaml $.Values.runner.securityContext | nindent 12 }} image: {{ default "public.ecr.aws/chainlink/chainlink" .Values.runner.image }} imagePullPolicy: Always command: [ "/bin/bash", "-c", "--" ] diff --git a/charts/chainlink-cluster/values-raw-helm.yaml b/charts/chainlink-cluster/values-raw-helm.yaml deleted file mode 100644 index 006515f0a33..00000000000 --- a/charts/chainlink-cluster/values-raw-helm.yaml +++ /dev/null @@ -1,117 +0,0 @@ -# override resources for keys "chainlink", "db", or "geth" if needed -# resources: -# requests: -# cpu: 350m -# memory: 1024Mi -# limits: -# cpu: 350m -# memory: 1024Mi -# images can be overriden for the same keys: -# image: ethereum/client-go -# version: stable -chainlink: - web_port: 6688 - p2p_port: 8090 - nodes: - - name: node-1 - image: "public.ecr.aws/chainlink/chainlink:latest" - # override default config per node - # for example, use OCRv2 P2P setup, the whole config -# toml: | -# RootDir = './clroot' -# [Log] -# JSONConsole = true -# Level = 'debug' -# [WebServer] -# AllowOrigins = '*' -# SecureCookies = false -# SessionTimeout = '999h0m0s' -# [OCR2] -# Enabled = true -# [P2P] -# [P2P.V2] -# Enabled = false -# AnnounceAddresses = [] -# DefaultBootstrappers = [] -# DeltaDial = '15s' -# DeltaReconcile = '1m0s' -# ListenAddresses = [] -# [[EVM]] -# ChainID = '1337' -# MinContractPayment = '0' -# [[EVM.Nodes]] -# Name = 'node-0' -# WSURL = 'ws://geth:8546' -# HTTPURL = 'http://geth:8544' -# [WebServer.TLS] -# HTTPSPort = 0 - - name: node-2 - - name: node-3 - - name: node-4 - resources: - requests: - cpu: 350m - memory: 1024Mi - limits: - cpu: 350m - memory: 1024Mi - -# each CL node have a dedicated PostgreSQL 11.15 -# use StatefulSet by setting: -# -# stateful: true -# capacity 10Gi -# -# if you are running long tests -db: - stateful: false - resources: - requests: - cpu: 1 - memory: 1024Mi - limits: - cpu: 1 - memory: 1024Mi -# default cluster shipped with latest Geth ( dev mode by default ) -geth: - version: v1.12.0 - wsrpc-port: 8546 - httprpc-port: 8544 - networkid: 1337 - blocktime: 1 - resources: - requests: - cpu: 1 - memory: 1024Mi - limits: - cpu: 1 - memory: 1024Mi -# mockserver is https://www.mock-server.com/where/kubernetes.html -# used to stub External Adapters -mockserver: - port: 1080 - resources: - requests: - cpu: 1 - memory: 1024Mi - limits: - cpu: 1 - memory: 1024Mi -runner: - stateful: false - resources: - requests: - cpu: 1 - memory: 512Mi - limits: - cpu: 1 - memory: 512Mi - -# monitoring.coreos.com/v1 PodMonitor for each node -prometheusMonitor: false - -# deployment placement, standard helm stuff -podAnnotations: -nodeSelector: -tolerations: -affinity: diff --git a/charts/chainlink-cluster/values.yaml b/charts/chainlink-cluster/values.yaml new file mode 100644 index 00000000000..eb93e6cefcf --- /dev/null +++ b/charts/chainlink-cluster/values.yaml @@ -0,0 +1,178 @@ +# override resources for keys "chainlink", "db", or "geth" if needed +# resources: +# requests: +# cpu: 350m +# memory: 1024Mi +# limits: +# cpu: 350m +# memory: 1024Mi +# images can be overriden for the same keys: +# image: ethereum/client-go +# version: stable +chainlink: + podSecurityContext: + fsGroup: 14933 + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 14933 + runAsGroup: 14933 + web_port: 6688 + p2p_port: 6690 + nodes: + - name: node-1 + image: "public.ecr.aws/chainlink/chainlink:latest" + # override default config per node + # for example, use OCRv2 P2P setup, the whole config + # toml: | + # RootDir = './clroot' + # [Log] + # JSONConsole = true + # Level = 'debug' + # [WebServer] + # AllowOrigins = '*' + # SecureCookies = false + # SessionTimeout = '999h0m0s' + # [OCR2] + # Enabled = true + # [P2P] + # [P2P.V2] + # Enabled = false + # AnnounceAddresses = [] + # DefaultBootstrappers = [] + # DeltaDial = '15s' + # DeltaReconcile = '1m0s' + # ListenAddresses = [] + # [[EVM]] + # ChainID = '1337' + # MinContractPayment = '0' + # [[EVM.Nodes]] + # Name = 'node-0' + # WSURL = 'ws://geth:8546' + # HTTPURL = 'http://geth:8544' + # [WebServer.TLS] + # HTTPSPort = 0 + # or use overridesToml to override some part of configuration + # overridesToml: | + - name: node-2 + - name: node-3 + - name: node-4 + - name: node-5 + - name: node-6 + resources: + requests: + cpu: 350m + memory: 1024Mi + limits: + cpu: 350m + memory: 1024Mi + +# each CL node have a dedicated PostgreSQL 11.15 +# use StatefulSet by setting: +# +# stateful: true +# capacity 10Gi +# +# if you are running long tests +db: + podSecurityContext: + fsGroup: 999 + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + stateful: false + resources: + requests: + cpu: 1 + memory: 1024Mi + limits: + cpu: 1 + memory: 1024Mi +# default cluster shipped with latest Geth ( dev mode by default ) +geth: + podSecurityContext: + fsGroup: 999 + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + version: v1.12.0 + wsrpc-port: 8546 + httprpc-port: 8544 + networkid: 1337 + blocktime: 1 + resources: + requests: + cpu: 1 + memory: 1024Mi + limits: + cpu: 1 + memory: 1024Mi +# mockserver is https://www.mock-server.com/where/kubernetes.html +# used to stub External Adapters +mockserver: + enabled: true + releasenameOverride: mockserver + app: + runAsUser: 999 + readOnlyRootFilesystem: false + port: 1080 + resources: + requests: + cpu: 1 + memory: 1024Mi + limits: + cpu: 1 + memory: 1024Mi +runner: + podSecurityContext: + fsGroup: 999 + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + stateful: false + resources: + requests: + cpu: 1 + memory: 512Mi + limits: + cpu: 1 + memory: 512Mi + affinity: {} + tolerations: [] + nodeSelector: {} + ingress: + enabled: false + className: "" + hosts: [] + tls: [] + annotations: {} + service: + type: NodePort + port: 8080 + +# monitoring.coreos.com/v1 PodMonitor for each node +prometheusMonitor: true + +# deployment placement, standard helm stuff +podAnnotations: +nodeSelector: +tolerations: +affinity: diff --git a/common/client/mock_hashable_test.go b/common/client/mock_hashable_test.go new file mode 100644 index 00000000000..d9f1670c073 --- /dev/null +++ b/common/client/mock_hashable_test.go @@ -0,0 +1,18 @@ +package client + +import "cmp" + +// Hashable - simple implementation of types.Hashable interface to be used as concrete type in tests +type Hashable string + +func (h Hashable) Cmp(c Hashable) int { + return cmp.Compare(h, c) +} + +func (h Hashable) String() string { + return string(h) +} + +func (h Hashable) Bytes() []byte { + return []byte(h) +} diff --git a/common/client/mock_head_test.go b/common/client/mock_head_test.go new file mode 100644 index 00000000000..1b69eedf438 --- /dev/null +++ b/common/client/mock_head_test.go @@ -0,0 +1,58 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package client + +import ( + big "math/big" + + mock "github.com/stretchr/testify/mock" +) + +// mockHead is an autogenerated mock type for the Head type +type mockHead struct { + mock.Mock +} + +// BlockDifficulty provides a mock function with given fields: +func (_m *mockHead) BlockDifficulty() *big.Int { + ret := _m.Called() + + var r0 *big.Int + if rf, ok := ret.Get(0).(func() *big.Int); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + return r0 +} + +// BlockNumber provides a mock function with given fields: +func (_m *mockHead) BlockNumber() int64 { + ret := _m.Called() + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + return r0 +} + +// newMockHead creates a new instance of mockHead. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockHead(t interface { + mock.TestingT + Cleanup(func()) +}) *mockHead { + mock := &mockHead{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/common/client/mock_node_client_test.go b/common/client/mock_node_client_test.go new file mode 100644 index 00000000000..7c8eb69171f --- /dev/null +++ b/common/client/mock_node_client_test.go @@ -0,0 +1,168 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package client + +import ( + context "context" + + types "github.com/smartcontractkit/chainlink/v2/common/types" + mock "github.com/stretchr/testify/mock" +) + +// mockNodeClient is an autogenerated mock type for the NodeClient type +type mockNodeClient[CHAIN_ID types.ID, HEAD Head] struct { + mock.Mock +} + +// ChainID provides a mock function with given fields: ctx +func (_m *mockNodeClient[CHAIN_ID, HEAD]) ChainID(ctx context.Context) (CHAIN_ID, error) { + ret := _m.Called(ctx) + + var r0 CHAIN_ID + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (CHAIN_ID, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) CHAIN_ID); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(CHAIN_ID) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientVersion provides a mock function with given fields: _a0 +func (_m *mockNodeClient[CHAIN_ID, HEAD]) ClientVersion(_a0 context.Context) (string, error) { + ret := _m.Called(_a0) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) string); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Close provides a mock function with given fields: +func (_m *mockNodeClient[CHAIN_ID, HEAD]) Close() { + _m.Called() +} + +// Dial provides a mock function with given fields: ctx +func (_m *mockNodeClient[CHAIN_ID, HEAD]) 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 +} + +// DialHTTP provides a mock function with given fields: +func (_m *mockNodeClient[CHAIN_ID, HEAD]) DialHTTP() 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 +} + +// DisconnectAll provides a mock function with given fields: +func (_m *mockNodeClient[CHAIN_ID, HEAD]) DisconnectAll() { + _m.Called() +} + +// SetAliveLoopSub provides a mock function with given fields: _a0 +func (_m *mockNodeClient[CHAIN_ID, HEAD]) SetAliveLoopSub(_a0 types.Subscription) { + _m.Called(_a0) +} + +// Subscribe provides a mock function with given fields: ctx, channel, args +func (_m *mockNodeClient[CHAIN_ID, HEAD]) Subscribe(ctx context.Context, channel chan<- HEAD, args ...interface{}) (types.Subscription, error) { + var _ca []interface{} + _ca = append(_ca, ctx, channel) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + var r0 types.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, chan<- HEAD, ...interface{}) (types.Subscription, error)); ok { + return rf(ctx, channel, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, chan<- HEAD, ...interface{}) types.Subscription); ok { + r0 = rf(ctx, channel, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, chan<- HEAD, ...interface{}) error); ok { + r1 = rf(ctx, channel, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SubscribersCount provides a mock function with given fields: +func (_m *mockNodeClient[CHAIN_ID, HEAD]) SubscribersCount() 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 +} + +// UnsubscribeAllExceptAliveLoop provides a mock function with given fields: +func (_m *mockNodeClient[CHAIN_ID, HEAD]) UnsubscribeAllExceptAliveLoop() { + _m.Called() +} + +// newMockNodeClient creates a new instance of mockNodeClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockNodeClient[CHAIN_ID types.ID, HEAD Head](t interface { + mock.TestingT + Cleanup(func()) +}) *mockNodeClient[CHAIN_ID, HEAD] { + mock := &mockNodeClient[CHAIN_ID, HEAD]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/common/client/mock_node_selector_test.go b/common/client/mock_node_selector_test.go new file mode 100644 index 00000000000..e7b8d9ecb8d --- /dev/null +++ b/common/client/mock_node_selector_test.go @@ -0,0 +1,57 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package client + +import ( + types "github.com/smartcontractkit/chainlink/v2/common/types" + mock "github.com/stretchr/testify/mock" +) + +// mockNodeSelector is an autogenerated mock type for the NodeSelector type +type mockNodeSelector[CHAIN_ID types.ID, HEAD Head, RPC NodeClient[CHAIN_ID, HEAD]] struct { + mock.Mock +} + +// Name provides a mock function with given fields: +func (_m *mockNodeSelector[CHAIN_ID, HEAD, RPC]) Name() 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 +} + +// Select provides a mock function with given fields: +func (_m *mockNodeSelector[CHAIN_ID, HEAD, RPC]) Select() Node[CHAIN_ID, HEAD, RPC] { + ret := _m.Called() + + var r0 Node[CHAIN_ID, HEAD, RPC] + if rf, ok := ret.Get(0).(func() Node[CHAIN_ID, HEAD, RPC]); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(Node[CHAIN_ID, HEAD, RPC]) + } + } + + return r0 +} + +// newMockNodeSelector creates a new instance of mockNodeSelector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockNodeSelector[CHAIN_ID types.ID, HEAD Head, RPC NodeClient[CHAIN_ID, HEAD]](t interface { + mock.TestingT + Cleanup(func()) +}) *mockNodeSelector[CHAIN_ID, HEAD, RPC] { + mock := &mockNodeSelector[CHAIN_ID, HEAD, RPC]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/common/client/mock_node_test.go b/common/client/mock_node_test.go new file mode 100644 index 00000000000..ea0e7d1a120 --- /dev/null +++ b/common/client/mock_node_test.go @@ -0,0 +1,195 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package client + +import ( + context "context" + big "math/big" + + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink/v2/common/types" +) + +// mockNode is an autogenerated mock type for the Node type +type mockNode[CHAIN_ID types.ID, HEAD Head, RPC NodeClient[CHAIN_ID, HEAD]] struct { + mock.Mock +} + +// Close provides a mock function with given fields: +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) 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 +} + +// ConfiguredChainID provides a mock function with given fields: +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) ConfiguredChainID() CHAIN_ID { + ret := _m.Called() + + var r0 CHAIN_ID + if rf, ok := ret.Get(0).(func() CHAIN_ID); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(CHAIN_ID) + } + + return r0 +} + +// Name provides a mock function with given fields: +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) Name() 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 +} + +// Order provides a mock function with given fields: +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) Order() 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 +} + +// RPC provides a mock function with given fields: +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) RPC() RPC { + ret := _m.Called() + + var r0 RPC + if rf, ok := ret.Get(0).(func() RPC); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(RPC) + } + + return r0 +} + +// Start provides a mock function with given fields: _a0 +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) 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 +} + +// State provides a mock function with given fields: +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) State() nodeState { + ret := _m.Called() + + var r0 nodeState + if rf, ok := ret.Get(0).(func() nodeState); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(nodeState) + } + + return r0 +} + +// StateAndLatest provides a mock function with given fields: +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) StateAndLatest() (nodeState, int64, *big.Int) { + ret := _m.Called() + + var r0 nodeState + var r1 int64 + var r2 *big.Int + if rf, ok := ret.Get(0).(func() (nodeState, int64, *big.Int)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() nodeState); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(nodeState) + } + + if rf, ok := ret.Get(1).(func() int64); ok { + r1 = rf() + } else { + r1 = ret.Get(1).(int64) + } + + if rf, ok := ret.Get(2).(func() *big.Int); ok { + r2 = rf() + } else { + if ret.Get(2) != nil { + r2 = ret.Get(2).(*big.Int) + } + } + + return r0, r1, r2 +} + +// String provides a mock function with given fields: +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) String() 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 +} + +// SubscribersCount provides a mock function with given fields: +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) SubscribersCount() 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 +} + +// UnsubscribeAllExceptAliveLoop provides a mock function with given fields: +func (_m *mockNode[CHAIN_ID, HEAD, RPC]) UnsubscribeAllExceptAliveLoop() { + _m.Called() +} + +// newMockNode creates a new instance of mockNode. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockNode[CHAIN_ID types.ID, HEAD Head, RPC NodeClient[CHAIN_ID, HEAD]](t interface { + mock.TestingT + Cleanup(func()) +}) *mockNode[CHAIN_ID, HEAD, RPC] { + mock := &mockNode[CHAIN_ID, HEAD, RPC]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/common/client/mock_rpc_test.go b/common/client/mock_rpc_test.go new file mode 100644 index 00000000000..d5e8db82836 --- /dev/null +++ b/common/client/mock_rpc_test.go @@ -0,0 +1,608 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package client + +import ( + big "math/big" + + assets "github.com/smartcontractkit/chainlink-common/pkg/assets" + + context "context" + + feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" + + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink/v2/common/types" +) + +// mockRPC is an autogenerated mock type for the RPC type +type mockRPC[CHAIN_ID types.ID, SEQ types.Sequence, ADDR types.Hashable, BLOCK_HASH types.Hashable, TX interface{}, TX_HASH types.Hashable, EVENT interface{}, EVENT_OPS interface{}, TX_RECEIPT types.Receipt[TX_HASH, BLOCK_HASH], FEE feetypes.Fee, HEAD types.Head[BLOCK_HASH]] struct { + mock.Mock +} + +// BalanceAt provides a mock function with given fields: ctx, accountAddress, blockNumber +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) BalanceAt(ctx context.Context, accountAddress ADDR, blockNumber *big.Int) (*big.Int, error) { + ret := _m.Called(ctx, accountAddress, blockNumber) + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ADDR, *big.Int) (*big.Int, error)); ok { + return rf(ctx, accountAddress, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, ADDR, *big.Int) *big.Int); ok { + r0 = rf(ctx, accountAddress, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ADDR, *big.Int) error); ok { + r1 = rf(ctx, accountAddress, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BatchCallContext provides a mock function with given fields: ctx, b +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) BatchCallContext(ctx context.Context, b []interface{}) error { + ret := _m.Called(ctx, b) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []interface{}) error); ok { + r0 = rf(ctx, b) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// BlockByHash provides a mock function with given fields: ctx, hash +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) BlockByHash(ctx context.Context, hash BLOCK_HASH) (HEAD, error) { + ret := _m.Called(ctx, hash) + + var r0 HEAD + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, BLOCK_HASH) (HEAD, error)); ok { + return rf(ctx, hash) + } + if rf, ok := ret.Get(0).(func(context.Context, BLOCK_HASH) HEAD); ok { + r0 = rf(ctx, hash) + } else { + r0 = ret.Get(0).(HEAD) + } + + if rf, ok := ret.Get(1).(func(context.Context, BLOCK_HASH) error); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockByNumber provides a mock function with given fields: ctx, number +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) BlockByNumber(ctx context.Context, number *big.Int) (HEAD, error) { + ret := _m.Called(ctx, number) + + var r0 HEAD + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (HEAD, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) HEAD); ok { + r0 = rf(ctx, number) + } else { + r0 = ret.Get(0).(HEAD) + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CallContext provides a mock function with given fields: ctx, result, method, args +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, ctx, result, method) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, interface{}, string, ...interface{}) error); ok { + r0 = rf(ctx, result, method, args...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CallContract provides a mock function with given fields: ctx, msg, blockNumber +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) CallContract(ctx context.Context, msg interface{}, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, msg, blockNumber) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, interface{}, *big.Int) ([]byte, error)); ok { + return rf(ctx, msg, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, interface{}, *big.Int) []byte); ok { + r0 = rf(ctx, msg, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, interface{}, *big.Int) error); ok { + r1 = rf(ctx, msg, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ChainID provides a mock function with given fields: ctx +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) ChainID(ctx context.Context) (CHAIN_ID, error) { + ret := _m.Called(ctx) + + var r0 CHAIN_ID + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (CHAIN_ID, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) CHAIN_ID); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(CHAIN_ID) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientVersion provides a mock function with given fields: _a0 +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) ClientVersion(_a0 context.Context) (string, error) { + ret := _m.Called(_a0) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) string); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Close provides a mock function with given fields: +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) Close() { + _m.Called() +} + +// CodeAt provides a mock function with given fields: ctx, account, blockNumber +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) CodeAt(ctx context.Context, account ADDR, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, account, blockNumber) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ADDR, *big.Int) ([]byte, error)); ok { + return rf(ctx, account, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, ADDR, *big.Int) []byte); ok { + r0 = rf(ctx, account, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ADDR, *big.Int) error); ok { + r1 = rf(ctx, account, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Dial provides a mock function with given fields: ctx +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) 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 +} + +// DialHTTP provides a mock function with given fields: +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) DialHTTP() 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 +} + +// DisconnectAll provides a mock function with given fields: +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) DisconnectAll() { + _m.Called() +} + +// EstimateGas provides a mock function with given fields: ctx, call +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) EstimateGas(ctx context.Context, call interface{}) (uint64, error) { + ret := _m.Called(ctx, call) + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, interface{}) (uint64, error)); ok { + return rf(ctx, call) + } + if rf, ok := ret.Get(0).(func(context.Context, interface{}) uint64); ok { + r0 = rf(ctx, call) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, interface{}) error); ok { + r1 = rf(ctx, call) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FilterEvents provides a mock function with given fields: ctx, query +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) FilterEvents(ctx context.Context, query EVENT_OPS) ([]EVENT, error) { + ret := _m.Called(ctx, query) + + var r0 []EVENT + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, EVENT_OPS) ([]EVENT, error)); ok { + return rf(ctx, query) + } + if rf, ok := ret.Get(0).(func(context.Context, EVENT_OPS) []EVENT); ok { + r0 = rf(ctx, query) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]EVENT) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, EVENT_OPS) error); ok { + r1 = rf(ctx, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// LINKBalance provides a mock function with given fields: ctx, accountAddress, linkAddress +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) LINKBalance(ctx context.Context, accountAddress ADDR, linkAddress ADDR) (*assets.Link, error) { + ret := _m.Called(ctx, accountAddress, linkAddress) + + var r0 *assets.Link + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ADDR, ADDR) (*assets.Link, error)); ok { + return rf(ctx, accountAddress, linkAddress) + } + if rf, ok := ret.Get(0).(func(context.Context, ADDR, ADDR) *assets.Link); ok { + r0 = rf(ctx, accountAddress, linkAddress) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*assets.Link) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ADDR, ADDR) error); ok { + r1 = rf(ctx, accountAddress, linkAddress) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// LatestBlockHeight provides a mock function with given fields: _a0 +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) LatestBlockHeight(_a0 context.Context) (*big.Int, error) { + ret := _m.Called(_a0) + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PendingSequenceAt provides a mock function with given fields: ctx, addr +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) PendingSequenceAt(ctx context.Context, addr ADDR) (SEQ, error) { + ret := _m.Called(ctx, addr) + + var r0 SEQ + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ADDR) (SEQ, error)); ok { + return rf(ctx, addr) + } + if rf, ok := ret.Get(0).(func(context.Context, ADDR) SEQ); ok { + r0 = rf(ctx, addr) + } else { + r0 = ret.Get(0).(SEQ) + } + + if rf, ok := ret.Get(1).(func(context.Context, ADDR) error); ok { + r1 = rf(ctx, addr) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SendEmptyTransaction provides a mock function with given fields: ctx, newTxAttempt, seq, gasLimit, fee, fromAddress +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) SendEmptyTransaction(ctx context.Context, newTxAttempt func(SEQ, uint32, FEE, ADDR) (interface{}, error), seq SEQ, gasLimit uint32, fee FEE, fromAddress ADDR) (string, error) { + ret := _m.Called(ctx, newTxAttempt, seq, gasLimit, fee, fromAddress) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, func(SEQ, uint32, FEE, ADDR) (interface{}, error), SEQ, uint32, FEE, ADDR) (string, error)); ok { + return rf(ctx, newTxAttempt, seq, gasLimit, fee, fromAddress) + } + if rf, ok := ret.Get(0).(func(context.Context, func(SEQ, uint32, FEE, ADDR) (interface{}, error), SEQ, uint32, FEE, ADDR) string); ok { + r0 = rf(ctx, newTxAttempt, seq, gasLimit, fee, fromAddress) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, func(SEQ, uint32, FEE, ADDR) (interface{}, error), SEQ, uint32, FEE, ADDR) error); ok { + r1 = rf(ctx, newTxAttempt, seq, gasLimit, fee, fromAddress) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SendTransaction provides a mock function with given fields: ctx, tx +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) SendTransaction(ctx context.Context, tx TX) error { + ret := _m.Called(ctx, tx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, TX) error); ok { + r0 = rf(ctx, tx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SequenceAt provides a mock function with given fields: ctx, accountAddress, blockNumber +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) SequenceAt(ctx context.Context, accountAddress ADDR, blockNumber *big.Int) (SEQ, error) { + ret := _m.Called(ctx, accountAddress, blockNumber) + + var r0 SEQ + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ADDR, *big.Int) (SEQ, error)); ok { + return rf(ctx, accountAddress, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, ADDR, *big.Int) SEQ); ok { + r0 = rf(ctx, accountAddress, blockNumber) + } else { + r0 = ret.Get(0).(SEQ) + } + + if rf, ok := ret.Get(1).(func(context.Context, ADDR, *big.Int) error); ok { + r1 = rf(ctx, accountAddress, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetAliveLoopSub provides a mock function with given fields: _a0 +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) SetAliveLoopSub(_a0 types.Subscription) { + _m.Called(_a0) +} + +// SimulateTransaction provides a mock function with given fields: ctx, tx +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) SimulateTransaction(ctx context.Context, tx TX) error { + ret := _m.Called(ctx, tx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, TX) error); ok { + r0 = rf(ctx, tx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Subscribe provides a mock function with given fields: ctx, channel, args +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) Subscribe(ctx context.Context, channel chan<- HEAD, args ...interface{}) (types.Subscription, error) { + var _ca []interface{} + _ca = append(_ca, ctx, channel) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + var r0 types.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, chan<- HEAD, ...interface{}) (types.Subscription, error)); ok { + return rf(ctx, channel, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, chan<- HEAD, ...interface{}) types.Subscription); ok { + r0 = rf(ctx, channel, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, chan<- HEAD, ...interface{}) error); ok { + r1 = rf(ctx, channel, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SubscribersCount provides a mock function with given fields: +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) SubscribersCount() 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 +} + +// TokenBalance provides a mock function with given fields: ctx, accountAddress, tokenAddress +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) TokenBalance(ctx context.Context, accountAddress ADDR, tokenAddress ADDR) (*big.Int, error) { + ret := _m.Called(ctx, accountAddress, tokenAddress) + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ADDR, ADDR) (*big.Int, error)); ok { + return rf(ctx, accountAddress, tokenAddress) + } + if rf, ok := ret.Get(0).(func(context.Context, ADDR, ADDR) *big.Int); ok { + r0 = rf(ctx, accountAddress, tokenAddress) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ADDR, ADDR) error); ok { + r1 = rf(ctx, accountAddress, tokenAddress) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TransactionByHash provides a mock function with given fields: ctx, txHash +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) TransactionByHash(ctx context.Context, txHash TX_HASH) (TX, error) { + ret := _m.Called(ctx, txHash) + + var r0 TX + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, TX_HASH) (TX, error)); ok { + return rf(ctx, txHash) + } + if rf, ok := ret.Get(0).(func(context.Context, TX_HASH) TX); ok { + r0 = rf(ctx, txHash) + } else { + r0 = ret.Get(0).(TX) + } + + if rf, ok := ret.Get(1).(func(context.Context, TX_HASH) error); ok { + r1 = rf(ctx, txHash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TransactionReceipt provides a mock function with given fields: ctx, txHash +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) TransactionReceipt(ctx context.Context, txHash TX_HASH) (TX_RECEIPT, error) { + ret := _m.Called(ctx, txHash) + + var r0 TX_RECEIPT + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, TX_HASH) (TX_RECEIPT, error)); ok { + return rf(ctx, txHash) + } + if rf, ok := ret.Get(0).(func(context.Context, TX_HASH) TX_RECEIPT); ok { + r0 = rf(ctx, txHash) + } else { + r0 = ret.Get(0).(TX_RECEIPT) + } + + if rf, ok := ret.Get(1).(func(context.Context, TX_HASH) error); ok { + r1 = rf(ctx, txHash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnsubscribeAllExceptAliveLoop provides a mock function with given fields: +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) UnsubscribeAllExceptAliveLoop() { + _m.Called() +} + +// newMockRPC creates a new instance of mockRPC. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockRPC[CHAIN_ID types.ID, SEQ types.Sequence, ADDR types.Hashable, BLOCK_HASH types.Hashable, TX interface{}, TX_HASH types.Hashable, EVENT interface{}, EVENT_OPS interface{}, TX_RECEIPT types.Receipt[TX_HASH, BLOCK_HASH], FEE feetypes.Fee, HEAD types.Head[BLOCK_HASH]](t interface { + mock.TestingT + Cleanup(func()) +}) *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD] { + mock := &mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/common/client/mock_send_only_client_test.go b/common/client/mock_send_only_client_test.go new file mode 100644 index 00000000000..481b2602ea3 --- /dev/null +++ b/common/client/mock_send_only_client_test.go @@ -0,0 +1,72 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package client + +import ( + context "context" + + types "github.com/smartcontractkit/chainlink/v2/common/types" + mock "github.com/stretchr/testify/mock" +) + +// mockSendOnlyClient is an autogenerated mock type for the sendOnlyClient type +type mockSendOnlyClient[CHAIN_ID types.ID] struct { + mock.Mock +} + +// ChainID provides a mock function with given fields: _a0 +func (_m *mockSendOnlyClient[CHAIN_ID]) ChainID(_a0 context.Context) (CHAIN_ID, error) { + ret := _m.Called(_a0) + + var r0 CHAIN_ID + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (CHAIN_ID, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) CHAIN_ID); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(CHAIN_ID) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Close provides a mock function with given fields: +func (_m *mockSendOnlyClient[CHAIN_ID]) Close() { + _m.Called() +} + +// DialHTTP provides a mock function with given fields: +func (_m *mockSendOnlyClient[CHAIN_ID]) DialHTTP() 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 +} + +// newMockSendOnlyClient creates a new instance of mockSendOnlyClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockSendOnlyClient[CHAIN_ID types.ID](t interface { + mock.TestingT + Cleanup(func()) +}) *mockSendOnlyClient[CHAIN_ID] { + mock := &mockSendOnlyClient[CHAIN_ID]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/common/client/mock_send_only_node_test.go b/common/client/mock_send_only_node_test.go new file mode 100644 index 00000000000..524d7d8a6c5 --- /dev/null +++ b/common/client/mock_send_only_node_test.go @@ -0,0 +1,127 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package client + +import ( + context "context" + + types "github.com/smartcontractkit/chainlink/v2/common/types" + mock "github.com/stretchr/testify/mock" +) + +// mockSendOnlyNode is an autogenerated mock type for the SendOnlyNode type +type mockSendOnlyNode[CHAIN_ID types.ID, RPC sendOnlyClient[CHAIN_ID]] struct { + mock.Mock +} + +// Close provides a mock function with given fields: +func (_m *mockSendOnlyNode[CHAIN_ID, RPC]) 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 +} + +// ConfiguredChainID provides a mock function with given fields: +func (_m *mockSendOnlyNode[CHAIN_ID, RPC]) ConfiguredChainID() CHAIN_ID { + ret := _m.Called() + + var r0 CHAIN_ID + if rf, ok := ret.Get(0).(func() CHAIN_ID); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(CHAIN_ID) + } + + return r0 +} + +// Name provides a mock function with given fields: +func (_m *mockSendOnlyNode[CHAIN_ID, RPC]) Name() 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 +} + +// RPC provides a mock function with given fields: +func (_m *mockSendOnlyNode[CHAIN_ID, RPC]) RPC() RPC { + ret := _m.Called() + + var r0 RPC + if rf, ok := ret.Get(0).(func() RPC); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(RPC) + } + + return r0 +} + +// Start provides a mock function with given fields: _a0 +func (_m *mockSendOnlyNode[CHAIN_ID, RPC]) 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 +} + +// State provides a mock function with given fields: +func (_m *mockSendOnlyNode[CHAIN_ID, RPC]) State() nodeState { + ret := _m.Called() + + var r0 nodeState + if rf, ok := ret.Get(0).(func() nodeState); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(nodeState) + } + + return r0 +} + +// String provides a mock function with given fields: +func (_m *mockSendOnlyNode[CHAIN_ID, RPC]) String() 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 +} + +// newMockSendOnlyNode creates a new instance of mockSendOnlyNode. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockSendOnlyNode[CHAIN_ID types.ID, RPC sendOnlyClient[CHAIN_ID]](t interface { + mock.TestingT + Cleanup(func()) +}) *mockSendOnlyNode[CHAIN_ID, RPC] { + mock := &mockSendOnlyNode[CHAIN_ID, RPC]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/common/chains/client/models.go b/common/client/models.go similarity index 84% rename from common/chains/client/models.go rename to common/client/models.go index ebe7bb7576d..bd974f901fc 100644 --- a/common/chains/client/models.go +++ b/common/client/models.go @@ -1,5 +1,9 @@ package client +import ( + "fmt" +) + type SendTxReturnCode int // SendTxReturnCode is a generalized client error that dictates what should be the next action, depending on the RPC error response. @@ -15,3 +19,21 @@ const ( ExceedsMaxFee // Attempt's fee was higher than the node's limit and got rejected. FeeOutOfValidRange // This error is returned when we use a fee price suggested from an RPC, but the network rejects the attempt due to an invalid range(mostly used by L2 chains). Retry by requesting a new suggested fee price. ) + +type NodeTier int + +const ( + Primary = NodeTier(iota) + Secondary +) + +func (n NodeTier) String() string { + switch n { + case Primary: + return "primary" + case Secondary: + return "secondary" + default: + return fmt.Sprintf("NodeTier(%d)", n) + } +} diff --git a/common/client/multi_node.go b/common/client/multi_node.go new file mode 100644 index 00000000000..db5380e91f5 --- /dev/null +++ b/common/client/multi_node.go @@ -0,0 +1,639 @@ +package client + +import ( + "context" + "fmt" + "math/big" + "sync" + "time" + + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/utils" + + "github.com/smartcontractkit/chainlink/v2/common/config" + feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +var ( + // PromMultiNodeRPCNodeStates reports current RPC node state + PromMultiNodeRPCNodeStates = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "multi_node_states", + Help: "The number of RPC nodes currently in the given state for the given chain", + }, []string{"network", "chainId", "state"}) + ErroringNodeError = fmt.Errorf("no live nodes available") +) + +// MultiNode is a generalized multi node client interface that includes methods to interact with different chains. +// It also handles multiple node RPC connections simultaneously. +type MultiNode[ + CHAIN_ID types.ID, + SEQ types.Sequence, + ADDR types.Hashable, + BLOCK_HASH types.Hashable, + TX any, + TX_HASH types.Hashable, + EVENT any, + EVENT_OPS any, + TX_RECEIPT types.Receipt[TX_HASH, BLOCK_HASH], + FEE feetypes.Fee, + HEAD types.Head[BLOCK_HASH], + RPC_CLIENT RPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD], +] interface { + clientAPI[ + CHAIN_ID, + SEQ, + ADDR, + BLOCK_HASH, + TX, + TX_HASH, + EVENT, + EVENT_OPS, + TX_RECEIPT, + FEE, + HEAD, + ] + Close() error + NodeStates() map[string]string + SelectNodeRPC() (RPC_CLIENT, error) + + BatchCallContextAll(ctx context.Context, b []any) error + ConfiguredChainID() CHAIN_ID + IsL2() bool +} + +type multiNode[ + CHAIN_ID types.ID, + SEQ types.Sequence, + ADDR types.Hashable, + BLOCK_HASH types.Hashable, + TX any, + TX_HASH types.Hashable, + EVENT any, + EVENT_OPS any, + TX_RECEIPT types.Receipt[TX_HASH, BLOCK_HASH], + FEE feetypes.Fee, + HEAD types.Head[BLOCK_HASH], + RPC_CLIENT RPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD], +] struct { + services.StateMachine + nodes []Node[CHAIN_ID, HEAD, RPC_CLIENT] + sendonlys []SendOnlyNode[CHAIN_ID, RPC_CLIENT] + chainID CHAIN_ID + chainType config.ChainType + lggr logger.Logger + selectionMode string + noNewHeadsThreshold time.Duration + nodeSelector NodeSelector[CHAIN_ID, HEAD, RPC_CLIENT] + leaseDuration time.Duration + leaseTicker *time.Ticker + chainFamily string + reportInterval time.Duration + + activeMu sync.RWMutex + activeNode Node[CHAIN_ID, HEAD, RPC_CLIENT] + + chStop services.StopChan + wg sync.WaitGroup + + sendOnlyErrorParser func(err error) SendTxReturnCode +} + +func NewMultiNode[ + CHAIN_ID types.ID, + SEQ types.Sequence, + ADDR types.Hashable, + BLOCK_HASH types.Hashable, + TX any, + TX_HASH types.Hashable, + EVENT any, + EVENT_OPS any, + TX_RECEIPT types.Receipt[TX_HASH, BLOCK_HASH], + FEE feetypes.Fee, + HEAD types.Head[BLOCK_HASH], + RPC_CLIENT RPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD], +]( + l logger.Logger, + selectionMode string, + leaseDuration time.Duration, + noNewHeadsThreshold time.Duration, + nodes []Node[CHAIN_ID, HEAD, RPC_CLIENT], + sendonlys []SendOnlyNode[CHAIN_ID, RPC_CLIENT], + chainID CHAIN_ID, + chainType config.ChainType, + chainFamily string, + sendOnlyErrorParser func(err error) SendTxReturnCode, +) MultiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT] { + nodeSelector := newNodeSelector(selectionMode, nodes) + + lggr := logger.Named(l, "MultiNode") + lggr = logger.With(lggr, "chainID", chainID.String()) + + // Prometheus' default interval is 15s, set this to under 7.5s to avoid + // aliasing (see: https://en.wikipedia.org/wiki/Nyquist_frequency) + const reportInterval = 6500 * time.Millisecond + c := &multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]{ + nodes: nodes, + sendonlys: sendonlys, + chainID: chainID, + chainType: chainType, + lggr: lggr, + selectionMode: selectionMode, + noNewHeadsThreshold: noNewHeadsThreshold, + nodeSelector: nodeSelector, + chStop: make(services.StopChan), + leaseDuration: leaseDuration, + chainFamily: chainFamily, + sendOnlyErrorParser: sendOnlyErrorParser, + reportInterval: reportInterval, + } + + c.lggr.Debugf("The MultiNode is configured to use NodeSelectionMode: %s", selectionMode) + + return c +} + +// Dial starts every node in the pool +// +// Nodes handle their own redialing and runloops, so this function does not +// return any error if the nodes aren't available +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) Dial(ctx context.Context) error { + return c.StartOnce("MultiNode", func() (merr error) { + if len(c.nodes) == 0 { + return errors.Errorf("no available nodes for chain %s", c.chainID.String()) + } + var ms services.MultiStart + for _, n := range c.nodes { + if n.ConfiguredChainID().String() != c.chainID.String() { + return ms.CloseBecause(errors.Errorf("node %s has configured chain ID %s which does not match multinode configured chain ID of %s", n.String(), n.ConfiguredChainID().String(), c.chainID.String())) + } + rawNode, ok := n.(*node[CHAIN_ID, HEAD, RPC_CLIENT]) + 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 = c.nLiveNodes + } + // node will handle its own redialing and automatic recovery + if err := ms.Start(ctx, n); err != nil { + return err + } + } + for _, s := range c.sendonlys { + if s.ConfiguredChainID().String() != c.chainID.String() { + return ms.CloseBecause(errors.Errorf("sendonly node %s has configured chain ID %s which does not match multinode configured chain ID of %s", s.String(), s.ConfiguredChainID().String(), c.chainID.String())) + } + if err := ms.Start(ctx, s); err != nil { + return err + } + } + c.wg.Add(1) + go c.runLoop() + + if c.leaseDuration.Seconds() > 0 && c.selectionMode != NodeSelectionModeRoundRobin { + c.lggr.Infof("The MultiNode will switch to best node every %s", c.leaseDuration.String()) + c.wg.Add(1) + go c.checkLeaseLoop() + } else { + c.lggr.Info("Best node switching is disabled") + } + + return nil + }) +} + +// Close tears down the MultiNode and closes all nodes +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) Close() error { + return c.StopOnce("MultiNode", func() error { + close(c.chStop) + c.wg.Wait() + + return services.CloseAll(services.MultiCloser(c.nodes), services.MultiCloser(c.sendonlys)) + }) +} + +// SelectNodeRPC returns an RPC of an active node. If there are no active nodes it returns an error. +// Call this method from your chain-specific client implementation to access any chain-specific rpc calls. +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) SelectNodeRPC() (rpc RPC_CLIENT, err error) { + n, err := c.selectNode() + if err != nil { + return rpc, err + } + return n.RPC(), nil + +} + +// selectNode returns the active Node, if it is still nodeStateAlive, otherwise it selects a new one from the NodeSelector. +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) selectNode() (node Node[CHAIN_ID, HEAD, RPC_CLIENT], err error) { + c.activeMu.RLock() + node = c.activeNode + c.activeMu.RUnlock() + if node != nil && node.State() == nodeStateAlive { + return // still alive + } + + // select a new one + c.activeMu.Lock() + defer c.activeMu.Unlock() + node = c.activeNode + if node != nil && node.State() == nodeStateAlive { + return // another goroutine beat us here + } + + c.activeNode = c.nodeSelector.Select() + + if c.activeNode == nil { + logger.Criticalw(c.lggr, "No live RPC nodes available", "NodeSelectionMode", c.nodeSelector.Name()) + errmsg := fmt.Errorf("no live nodes available for chain %s", c.chainID.String()) + c.SvcErrBuffer.Append(errmsg) + err = ErroringNodeError + } + + return c.activeNode, err +} + +// nLiveNodes returns the number of currently alive nodes, as well as the highest block number and greatest total difficulty. +// totalDifficulty will be 0 if all nodes return nil. +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) nLiveNodes() (nLiveNodes int, blockNumber int64, totalDifficulty *big.Int) { + totalDifficulty = big.NewInt(0) + for _, n := range c.nodes { + if s, num, td := n.StateAndLatest(); s == nodeStateAlive { + nLiveNodes++ + if num > blockNumber { + blockNumber = num + } + if td != nil && td.Cmp(totalDifficulty) > 0 { + totalDifficulty = td + } + } + } + return +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) checkLease() { + bestNode := c.nodeSelector.Select() + for _, n := range c.nodes { + // Terminate client subscriptions. Services are responsible for reconnecting, which will be routed to the new + // best node. Only terminate connections with more than 1 subscription to account for the aliveLoop subscription + if n.State() == nodeStateAlive && n != bestNode && n.SubscribersCount() > 1 { + c.lggr.Infof("Switching to best node from %q to %q", n.String(), bestNode.String()) + n.UnsubscribeAllExceptAliveLoop() + } + } + + c.activeMu.Lock() + if bestNode != c.activeNode { + c.activeNode = bestNode + } + c.activeMu.Unlock() +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) checkLeaseLoop() { + defer c.wg.Done() + c.leaseTicker = time.NewTicker(c.leaseDuration) + defer c.leaseTicker.Stop() + + for { + select { + case <-c.leaseTicker.C: + c.checkLease() + case <-c.chStop: + return + } + } +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) runLoop() { + defer c.wg.Done() + + c.report() + + monitor := time.NewTicker(utils.WithJitter(c.reportInterval)) + defer monitor.Stop() + + for { + select { + case <-monitor.C: + c.report() + case <-c.chStop: + return + } + } +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) report() { + type nodeWithState struct { + Node string + State string + } + + var total, dead int + counts := make(map[nodeState]int) + nodeStates := make([]nodeWithState, len(c.nodes)) + for i, n := range c.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] + PromMultiNodeRPCNodeStates.WithLabelValues(c.chainFamily, c.chainID.String(), state.String()).Set(float64(count)) + } + + live := total - dead + logger.Tracew(c.lggr, fmt.Sprintf("MultiNode state: %d/%d nodes are alive", live, total), "nodeStates", nodeStates) + if total == dead { + rerr := fmt.Errorf("no primary nodes available: 0/%d nodes are alive", total) + logger.Criticalw(c.lggr, rerr.Error(), "nodeStates", nodeStates) + c.SvcErrBuffer.Append(rerr) + } else if dead > 0 { + c.lggr.Errorw(fmt.Sprintf("At least one primary node is dead: %d/%d nodes are alive", live, total), "nodeStates", nodeStates) + } +} + +// ClientAPI methods +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) BalanceAt(ctx context.Context, account ADDR, blockNumber *big.Int) (*big.Int, error) { + n, err := c.selectNode() + if err != nil { + return nil, err + } + return n.RPC().BalanceAt(ctx, account, blockNumber) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) BatchCallContext(ctx context.Context, b []any) error { + n, err := c.selectNode() + if err != nil { + return err + } + return n.RPC().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 (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) BatchCallContextAll(ctx context.Context, b []any) error { + var wg sync.WaitGroup + defer wg.Wait() + + main, selectionErr := c.selectNode() + var all []SendOnlyNode[CHAIN_ID, RPC_CLIENT] + for _, n := range c.nodes { + all = append(all, n) + } + all = append(all, c.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[CHAIN_ID, RPC_CLIENT]) { + defer wg.Done() + err := n.RPC().BatchCallContext(ctx, b) + if err != nil { + c.lggr.Debugw("Secondary node BatchCallContext failed", "err", err) + } else { + logger.Trace(c.lggr, "Secondary node BatchCallContext success") + } + }(n) + } + + if selectionErr != nil { + return selectionErr + } + return main.RPC().BatchCallContext(ctx, b) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) BlockByHash(ctx context.Context, hash BLOCK_HASH) (h HEAD, err error) { + n, err := c.selectNode() + if err != nil { + return h, err + } + return n.RPC().BlockByHash(ctx, hash) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) BlockByNumber(ctx context.Context, number *big.Int) (h HEAD, err error) { + n, err := c.selectNode() + if err != nil { + return h, err + } + return n.RPC().BlockByNumber(ctx, number) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { + n, err := c.selectNode() + if err != nil { + return err + } + return n.RPC().CallContext(ctx, result, method, args...) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) CallContract( + ctx context.Context, + attempt interface{}, + blockNumber *big.Int, +) (rpcErr []byte, extractErr error) { + n, err := c.selectNode() + if err != nil { + return rpcErr, err + } + return n.RPC().CallContract(ctx, attempt, blockNumber) +} + +// ChainID makes a direct RPC call. In most cases it should be better to use the configured chain id instead by +// calling ConfiguredChainID. +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) ChainID(ctx context.Context) (id CHAIN_ID, err error) { + n, err := c.selectNode() + if err != nil { + return id, err + } + return n.RPC().ChainID(ctx) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) ChainType() config.ChainType { + return c.chainType +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) CodeAt(ctx context.Context, account ADDR, blockNumber *big.Int) (code []byte, err error) { + n, err := c.selectNode() + if err != nil { + return code, err + } + return n.RPC().CodeAt(ctx, account, blockNumber) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) ConfiguredChainID() CHAIN_ID { + return c.chainID +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) EstimateGas(ctx context.Context, call any) (gas uint64, err error) { + n, err := c.selectNode() + if err != nil { + return gas, err + } + return n.RPC().EstimateGas(ctx, call) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) FilterEvents(ctx context.Context, query EVENT_OPS) (e []EVENT, err error) { + n, err := c.selectNode() + if err != nil { + return e, err + } + return n.RPC().FilterEvents(ctx, query) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) IsL2() bool { + return c.ChainType().IsL2() +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) LatestBlockHeight(ctx context.Context) (h *big.Int, err error) { + n, err := c.selectNode() + if err != nil { + return h, err + } + return n.RPC().LatestBlockHeight(ctx) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) LINKBalance(ctx context.Context, accountAddress ADDR, linkAddress ADDR) (b *assets.Link, err error) { + n, err := c.selectNode() + if err != nil { + return b, err + } + return n.RPC().LINKBalance(ctx, accountAddress, linkAddress) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) NodeStates() (states map[string]string) { + states = make(map[string]string) + for _, n := range c.nodes { + states[n.Name()] = n.State().String() + } + for _, s := range c.sendonlys { + states[s.Name()] = s.State().String() + } + return +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) PendingSequenceAt(ctx context.Context, addr ADDR) (s SEQ, err error) { + n, err := c.selectNode() + if err != nil { + return s, err + } + return n.RPC().PendingSequenceAt(ctx, addr) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) SendEmptyTransaction( + ctx context.Context, + newTxAttempt func(seq SEQ, feeLimit uint32, fee FEE, fromAddress ADDR) (attempt any, err error), + seq SEQ, + gasLimit uint32, + fee FEE, + fromAddress ADDR, +) (txhash string, err error) { + n, err := c.selectNode() + if err != nil { + return txhash, err + } + return n.RPC().SendEmptyTransaction(ctx, newTxAttempt, seq, gasLimit, fee, fromAddress) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) SendTransaction(ctx context.Context, tx TX) error { + main, nodeError := c.selectNode() + var all []SendOnlyNode[CHAIN_ID, RPC_CLIENT] + for _, n := range c.nodes { + all = append(all, n) + } + all = append(all, c.sendonlys...) + for _, n := range all { + if n == main { + // main node is used at the end for the return value + continue + } + // Parallel send to all other nodes with ignored return value + // Async - we do not want to block the main thread with secondary nodes + // in case they are unreliable/slow. + // It is purely a "best effort" send. + // Resource is not unbounded because the default context has a timeout. + ok := c.IfNotStopped(func() { + // Must wrap inside IfNotStopped to avoid waitgroup racing with Close + c.wg.Add(1) + go func(n SendOnlyNode[CHAIN_ID, RPC_CLIENT]) { + defer c.wg.Done() + + txErr := n.RPC().SendTransaction(ctx, tx) + c.lggr.Debugw("Sendonly node sent transaction", "name", n.String(), "tx", tx, "err", txErr) + sendOnlyError := c.sendOnlyErrorParser(txErr) + if sendOnlyError != Successful { + c.lggr.Warnw("RPC returned error", "name", n.String(), "tx", tx, "err", txErr) + } + }(n) + }) + if !ok { + c.lggr.Debug("Cannot send transaction on sendonly node; MultiNode is stopped", "node", n.String()) + } + } + if nodeError != nil { + return nodeError + } + return main.RPC().SendTransaction(ctx, tx) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) SequenceAt(ctx context.Context, account ADDR, blockNumber *big.Int) (s SEQ, err error) { + n, err := c.selectNode() + if err != nil { + return s, err + } + return n.RPC().SequenceAt(ctx, account, blockNumber) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) SimulateTransaction(ctx context.Context, tx TX) error { + n, err := c.selectNode() + if err != nil { + return err + } + return n.RPC().SimulateTransaction(ctx, tx) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) Subscribe(ctx context.Context, channel chan<- HEAD, args ...interface{}) (s types.Subscription, err error) { + n, err := c.selectNode() + if err != nil { + return s, err + } + return n.RPC().Subscribe(ctx, channel, args...) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) TokenBalance(ctx context.Context, account ADDR, tokenAddr ADDR) (b *big.Int, err error) { + n, err := c.selectNode() + if err != nil { + return b, err + } + return n.RPC().TokenBalance(ctx, account, tokenAddr) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) TransactionByHash(ctx context.Context, txHash TX_HASH) (tx TX, err error) { + n, err := c.selectNode() + if err != nil { + return tx, err + } + return n.RPC().TransactionByHash(ctx, txHash) +} + +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) TransactionReceipt(ctx context.Context, txHash TX_HASH) (txr TX_RECEIPT, err error) { + n, err := c.selectNode() + if err != nil { + return txr, err + } + return n.RPC().TransactionReceipt(ctx, txHash) +} diff --git a/common/client/multi_node_test.go b/common/client/multi_node_test.go new file mode 100644 index 00000000000..229f1320a14 --- /dev/null +++ b/common/client/multi_node_test.go @@ -0,0 +1,635 @@ +package client + +import ( + "fmt" + big "math/big" + "math/rand" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +type multiNodeRPCClient RPC[types.ID, *big.Int, Hashable, Hashable, any, Hashable, any, any, + types.Receipt[Hashable, Hashable], Hashable, types.Head[Hashable]] + +type testMultiNode struct { + *multiNode[types.ID, *big.Int, Hashable, Hashable, any, Hashable, any, any, + types.Receipt[Hashable, Hashable], Hashable, types.Head[Hashable], multiNodeRPCClient] +} + +type multiNodeOpts struct { + logger logger.Logger + selectionMode string + leaseDuration time.Duration + noNewHeadsThreshold time.Duration + nodes []Node[types.ID, types.Head[Hashable], multiNodeRPCClient] + sendonlys []SendOnlyNode[types.ID, multiNodeRPCClient] + chainID types.ID + chainType config.ChainType + chainFamily string + sendOnlyErrorParser func(err error) SendTxReturnCode +} + +func newTestMultiNode(t *testing.T, opts multiNodeOpts) testMultiNode { + if opts.logger == nil { + opts.logger = logger.Test(t) + } + + result := NewMultiNode[types.ID, *big.Int, Hashable, Hashable, any, Hashable, any, any, + types.Receipt[Hashable, Hashable], Hashable, types.Head[Hashable], multiNodeRPCClient](opts.logger, + opts.selectionMode, opts.leaseDuration, opts.noNewHeadsThreshold, opts.nodes, opts.sendonlys, + opts.chainID, opts.chainType, opts.chainFamily, opts.sendOnlyErrorParser) + return testMultiNode{ + result.(*multiNode[types.ID, *big.Int, Hashable, Hashable, any, Hashable, any, any, + types.Receipt[Hashable, Hashable], Hashable, types.Head[Hashable], multiNodeRPCClient]), + } +} + +func newMultiNodeRPCClient(t *testing.T) *mockRPC[types.ID, *big.Int, Hashable, Hashable, any, Hashable, any, any, + types.Receipt[Hashable, Hashable], Hashable, types.Head[Hashable]] { + return newMockRPC[types.ID, *big.Int, Hashable, Hashable, any, Hashable, any, any, + types.Receipt[Hashable, Hashable], Hashable, types.Head[Hashable]](t) +} + +func newHealthyNode(t *testing.T, chainID types.ID) *mockNode[types.ID, types.Head[Hashable], multiNodeRPCClient] { + return newNodeWithState(t, chainID, nodeStateAlive) +} + +func newNodeWithState(t *testing.T, chainID types.ID, state nodeState) *mockNode[types.ID, types.Head[Hashable], multiNodeRPCClient] { + node := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + node.On("ConfiguredChainID").Return(chainID).Once() + node.On("Start", mock.Anything).Return(nil).Once() + node.On("Close").Return(nil).Once() + node.On("State").Return(state).Maybe() + node.On("String").Return(fmt.Sprintf("healthy_node_%d", rand.Int())).Maybe() + return node +} +func TestMultiNode_Dial(t *testing.T) { + t.Parallel() + + newMockNode := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient] + newMockSendOnlyNode := newMockSendOnlyNode[types.ID, multiNodeRPCClient] + + t.Run("Fails without nodes", func(t *testing.T) { + t.Parallel() + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: types.RandomID(), + }) + err := mn.Dial(tests.Context(t)) + assert.EqualError(t, err, fmt.Sprintf("no available nodes for chain %s", mn.chainID.String())) + }) + t.Run("Fails with wrong node's chainID", func(t *testing.T) { + t.Parallel() + node := newMockNode(t) + multiNodeChainID := types.NewIDFromInt(10) + nodeChainID := types.NewIDFromInt(11) + node.On("ConfiguredChainID").Return(nodeChainID).Twice() + const nodeName = "nodeName" + node.On("String").Return(nodeName).Once() + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: multiNodeChainID, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node}, + }) + err := mn.Dial(tests.Context(t)) + assert.EqualError(t, err, fmt.Sprintf("node %s has configured chain ID %s which does not match multinode configured chain ID of %s", nodeName, nodeChainID, mn.chainID)) + }) + t.Run("Fails if node fails", func(t *testing.T) { + t.Parallel() + node := newMockNode(t) + chainID := types.RandomID() + node.On("ConfiguredChainID").Return(chainID).Once() + expectedError := errors.New("failed to start node") + node.On("Start", mock.Anything).Return(expectedError).Once() + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node}, + }) + err := mn.Dial(tests.Context(t)) + assert.EqualError(t, err, expectedError.Error()) + }) + + t.Run("Closes started nodes on failure", func(t *testing.T) { + t.Parallel() + chainID := types.RandomID() + node1 := newHealthyNode(t, chainID) + node2 := newMockNode(t) + node2.On("ConfiguredChainID").Return(chainID).Once() + expectedError := errors.New("failed to start node") + node2.On("Start", mock.Anything).Return(expectedError).Once() + + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node1, node2}, + }) + err := mn.Dial(tests.Context(t)) + assert.EqualError(t, err, expectedError.Error()) + }) + t.Run("Fails with wrong send only node's chainID", func(t *testing.T) { + t.Parallel() + multiNodeChainID := types.NewIDFromInt(10) + node := newHealthyNode(t, multiNodeChainID) + sendOnly := newMockSendOnlyNode(t) + sendOnlyChainID := types.NewIDFromInt(11) + sendOnly.On("ConfiguredChainID").Return(sendOnlyChainID).Twice() + const sendOnlyName = "sendOnlyNodeName" + sendOnly.On("String").Return(sendOnlyName).Once() + + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: multiNodeChainID, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node}, + sendonlys: []SendOnlyNode[types.ID, multiNodeRPCClient]{sendOnly}, + }) + err := mn.Dial(tests.Context(t)) + assert.EqualError(t, err, fmt.Sprintf("sendonly node %s has configured chain ID %s which does not match multinode configured chain ID of %s", sendOnlyName, sendOnlyChainID, mn.chainID)) + }) + + newHealthySendOnly := func(t *testing.T, chainID types.ID) *mockSendOnlyNode[types.ID, multiNodeRPCClient] { + node := newMockSendOnlyNode(t) + node.On("ConfiguredChainID").Return(chainID).Once() + node.On("Start", mock.Anything).Return(nil).Once() + node.On("Close").Return(nil).Once() + return node + } + t.Run("Fails on send only node failure", func(t *testing.T) { + t.Parallel() + chainID := types.NewIDFromInt(10) + node := newHealthyNode(t, chainID) + sendOnly1 := newHealthySendOnly(t, chainID) + sendOnly2 := newMockSendOnlyNode(t) + sendOnly2.On("ConfiguredChainID").Return(chainID).Once() + expectedError := errors.New("failed to start send only node") + sendOnly2.On("Start", mock.Anything).Return(expectedError).Once() + + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node}, + sendonlys: []SendOnlyNode[types.ID, multiNodeRPCClient]{sendOnly1, sendOnly2}, + }) + err := mn.Dial(tests.Context(t)) + assert.EqualError(t, err, expectedError.Error()) + }) + t.Run("Starts successfully with healthy nodes", func(t *testing.T) { + t.Parallel() + chainID := types.NewIDFromInt(10) + node := newHealthyNode(t, chainID) + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node}, + sendonlys: []SendOnlyNode[types.ID, multiNodeRPCClient]{newHealthySendOnly(t, chainID)}, + }) + defer func() { assert.NoError(t, mn.Close()) }() + err := mn.Dial(tests.Context(t)) + require.NoError(t, err) + selectedNode, err := mn.selectNode() + require.NoError(t, err) + assert.Equal(t, node, selectedNode) + }) +} + +func TestMultiNode_Report(t *testing.T) { + t.Parallel() + t.Run("Dial starts periodical reporting", func(t *testing.T) { + t.Parallel() + chainID := types.RandomID() + node1 := newHealthyNode(t, chainID) + node2 := newNodeWithState(t, chainID, nodeStateOutOfSync) + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node1, node2}, + logger: lggr, + }) + mn.reportInterval = tests.TestInterval + defer func() { assert.NoError(t, mn.Close()) }() + err := mn.Dial(tests.Context(t)) + require.NoError(t, err) + tests.AssertLogCountEventually(t, observedLogs, "At least one primary node is dead: 1/2 nodes are alive", 2) + }) + t.Run("Report critical error on all node failure", func(t *testing.T) { + t.Parallel() + chainID := types.RandomID() + node := newNodeWithState(t, chainID, nodeStateOutOfSync) + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node}, + logger: lggr, + }) + mn.reportInterval = tests.TestInterval + defer func() { assert.NoError(t, mn.Close()) }() + err := mn.Dial(tests.Context(t)) + require.NoError(t, err) + tests.AssertLogCountEventually(t, observedLogs, "no primary nodes available: 0/1 nodes are alive", 2) + err = mn.Healthy() + require.Error(t, err) + assert.Contains(t, err.Error(), "no primary nodes available: 0/1 nodes are alive") + }) +} + +func TestMultiNode_CheckLease(t *testing.T) { + t.Parallel() + t.Run("Round robin disables lease check", func(t *testing.T) { + t.Parallel() + chainID := types.RandomID() + node := newHealthyNode(t, chainID) + lggr, observedLogs := logger.TestObserved(t, zap.InfoLevel) + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + logger: lggr, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node}, + }) + defer func() { assert.NoError(t, mn.Close()) }() + err := mn.Dial(tests.Context(t)) + require.NoError(t, err) + tests.RequireLogMessage(t, observedLogs, "Best node switching is disabled") + }) + t.Run("Misconfigured lease check period won't start", func(t *testing.T) { + t.Parallel() + chainID := types.RandomID() + node := newHealthyNode(t, chainID) + lggr, observedLogs := logger.TestObserved(t, zap.InfoLevel) + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeHighestHead, + chainID: chainID, + logger: lggr, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node}, + leaseDuration: 0, + }) + defer func() { assert.NoError(t, mn.Close()) }() + err := mn.Dial(tests.Context(t)) + require.NoError(t, err) + tests.RequireLogMessage(t, observedLogs, "Best node switching is disabled") + }) + t.Run("Lease check updates active node", func(t *testing.T) { + t.Parallel() + chainID := types.RandomID() + node := newHealthyNode(t, chainID) + node.On("SubscribersCount").Return(int32(2)) + node.On("UnsubscribeAllExceptAliveLoop") + bestNode := newHealthyNode(t, chainID) + nodeSelector := newMockNodeSelector[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + nodeSelector.On("Select").Return(bestNode) + lggr, observedLogs := logger.TestObserved(t, zap.InfoLevel) + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeHighestHead, + chainID: chainID, + logger: lggr, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node, bestNode}, + leaseDuration: tests.TestInterval, + }) + defer func() { assert.NoError(t, mn.Close()) }() + mn.nodeSelector = nodeSelector + err := mn.Dial(tests.Context(t)) + require.NoError(t, err) + tests.AssertLogEventually(t, observedLogs, fmt.Sprintf("Switching to best node from %q to %q", node.String(), bestNode.String())) + tests.AssertEventually(t, func() bool { + mn.activeMu.RLock() + active := mn.activeNode + mn.activeMu.RUnlock() + return bestNode == active + }) + }) + t.Run("NodeStates returns proper states", func(t *testing.T) { + t.Parallel() + chainID := types.NewIDFromInt(10) + nodes := map[string]nodeState{ + "node_1": nodeStateAlive, + "node_2": nodeStateUnreachable, + "node_3": nodeStateDialed, + } + + opts := multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + } + + expectedResult := map[string]string{} + for name, state := range nodes { + node := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + node.On("Name").Return(name).Once() + node.On("State").Return(state).Once() + opts.nodes = append(opts.nodes, node) + + sendOnly := newMockSendOnlyNode[types.ID, multiNodeRPCClient](t) + sendOnlyName := "send_only_" + name + sendOnly.On("Name").Return(sendOnlyName).Once() + sendOnly.On("State").Return(state).Once() + opts.sendonlys = append(opts.sendonlys, sendOnly) + + expectedResult[name] = state.String() + expectedResult[sendOnlyName] = state.String() + } + + mn := newTestMultiNode(t, opts) + states := mn.NodeStates() + assert.Equal(t, expectedResult, states) + }) +} + +func TestMultiNode_selectNode(t *testing.T) { + t.Parallel() + t.Run("Returns same node, if it's still healthy", func(t *testing.T) { + t.Parallel() + chainID := types.RandomID() + node1 := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + node1.On("State").Return(nodeStateAlive).Once() + node1.On("String").Return("node1").Maybe() + node2 := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + node2.On("String").Return("node2").Maybe() + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{node1, node2}, + }) + nodeSelector := newMockNodeSelector[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + nodeSelector.On("Select").Return(node1).Once() + mn.nodeSelector = nodeSelector + prevActiveNode, err := mn.selectNode() + require.NoError(t, err) + require.Equal(t, node1.String(), prevActiveNode.String()) + newActiveNode, err := mn.selectNode() + require.NoError(t, err) + require.Equal(t, prevActiveNode.String(), newActiveNode.String()) + + }) + t.Run("Updates node if active is not healthy", func(t *testing.T) { + t.Parallel() + chainID := types.RandomID() + oldBest := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + oldBest.On("String").Return("oldBest").Maybe() + newBest := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + newBest.On("String").Return("newBest").Maybe() + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{oldBest, newBest}, + }) + nodeSelector := newMockNodeSelector[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + nodeSelector.On("Select").Return(oldBest).Once() + mn.nodeSelector = nodeSelector + activeNode, err := mn.selectNode() + require.NoError(t, err) + require.Equal(t, oldBest.String(), activeNode.String()) + // old best died, so we should replace it + oldBest.On("State").Return(nodeStateOutOfSync).Twice() + nodeSelector.On("Select").Return(newBest).Once() + newActiveNode, err := mn.selectNode() + require.NoError(t, err) + require.Equal(t, newBest.String(), newActiveNode.String()) + + }) + t.Run("No active nodes - reports critical error", func(t *testing.T) { + t.Parallel() + chainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.InfoLevel) + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + logger: lggr, + }) + nodeSelector := newMockNodeSelector[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + nodeSelector.On("Select").Return(nil).Once() + nodeSelector.On("Name").Return("MockedNodeSelector").Once() + mn.nodeSelector = nodeSelector + node, err := mn.selectNode() + require.EqualError(t, err, ErroringNodeError.Error()) + require.Nil(t, node) + tests.RequireLogMessage(t, observedLogs, "No live RPC nodes available") + + }) +} + +func TestMultiNode_nLiveNodes(t *testing.T) { + t.Parallel() + type nodeParams struct { + BlockNumber int64 + TotalDifficulty *big.Int + State nodeState + } + testCases := []struct { + Name string + ExpectedNLiveNodes int + ExpectedBlockNumber int64 + ExpectedTotalDifficulty *big.Int + NodeParams []nodeParams + }{ + { + Name: "no nodes", + ExpectedTotalDifficulty: big.NewInt(0), + }, + { + Name: "Best node is not healthy", + ExpectedTotalDifficulty: big.NewInt(10), + ExpectedBlockNumber: 20, + ExpectedNLiveNodes: 3, + NodeParams: []nodeParams{ + { + State: nodeStateOutOfSync, + BlockNumber: 1000, + TotalDifficulty: big.NewInt(2000), + }, + { + State: nodeStateAlive, + BlockNumber: 20, + TotalDifficulty: big.NewInt(9), + }, + { + State: nodeStateAlive, + BlockNumber: 19, + TotalDifficulty: big.NewInt(10), + }, + { + State: nodeStateAlive, + BlockNumber: 11, + TotalDifficulty: nil, + }, + }, + }, + } + + chainID := types.RandomID() + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + }) + for i := range testCases { + tc := testCases[i] + t.Run(tc.Name, func(t *testing.T) { + for _, params := range tc.NodeParams { + node := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + node.On("StateAndLatest").Return(params.State, params.BlockNumber, params.TotalDifficulty) + mn.nodes = append(mn.nodes, node) + } + + nNodes, blockNum, td := mn.nLiveNodes() + assert.Equal(t, tc.ExpectedNLiveNodes, nNodes) + assert.Equal(t, tc.ExpectedTotalDifficulty, td) + assert.Equal(t, tc.ExpectedBlockNumber, blockNum) + }) + } +} + +func TestMultiNode_BatchCallContextAll(t *testing.T) { + t.Parallel() + t.Run("Fails if failed to select active node", func(t *testing.T) { + chainID := types.RandomID() + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + }) + nodeSelector := newMockNodeSelector[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + nodeSelector.On("Select").Return(nil).Once() + nodeSelector.On("Name").Return("MockedNodeSelector").Once() + mn.nodeSelector = nodeSelector + err := mn.BatchCallContextAll(tests.Context(t), nil) + require.EqualError(t, err, ErroringNodeError.Error()) + }) + t.Run("Returns error if RPC call fails for active node", func(t *testing.T) { + chainID := types.RandomID() + rpc := newMultiNodeRPCClient(t) + expectedError := errors.New("rpc failed to do the batch call") + rpc.On("BatchCallContext", mock.Anything, mock.Anything).Return(expectedError).Once() + node := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + node.On("RPC").Return(rpc) + nodeSelector := newMockNodeSelector[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + nodeSelector.On("Select").Return(node).Once() + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + }) + mn.nodeSelector = nodeSelector + err := mn.BatchCallContextAll(tests.Context(t), nil) + require.EqualError(t, err, expectedError.Error()) + }) + t.Run("Waits for all nodes to complete the call and logs results", func(t *testing.T) { + // setup RPCs + failedRPC := newMultiNodeRPCClient(t) + failedRPC.On("BatchCallContext", mock.Anything, mock.Anything). + Return(errors.New("rpc failed to do the batch call")).Once() + okRPC := newMultiNodeRPCClient(t) + okRPC.On("BatchCallContext", mock.Anything, mock.Anything).Return(nil).Twice() + + // setup ok and failed auxiliary nodes + okNode := newMockSendOnlyNode[types.ID, multiNodeRPCClient](t) + okNode.On("RPC").Return(okRPC).Once() + failedNode := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + failedNode.On("RPC").Return(failedRPC).Once() + + // setup main node + mainNode := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + mainNode.On("RPC").Return(okRPC) + nodeSelector := newMockNodeSelector[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + nodeSelector.On("Select").Return(mainNode).Once() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: types.RandomID(), + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{failedNode, mainNode}, + sendonlys: []SendOnlyNode[types.ID, multiNodeRPCClient]{okNode}, + logger: lggr, + }) + mn.nodeSelector = nodeSelector + + err := mn.BatchCallContextAll(tests.Context(t), nil) + require.NoError(t, err) + tests.RequireLogMessage(t, observedLogs, "Secondary node BatchCallContext failed") + }) +} + +func TestMultiNode_SendTransaction(t *testing.T) { + t.Parallel() + t.Run("Fails if failed to select active node", func(t *testing.T) { + chainID := types.RandomID() + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + }) + nodeSelector := newMockNodeSelector[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + nodeSelector.On("Select").Return(nil).Once() + nodeSelector.On("Name").Return("MockedNodeSelector").Once() + mn.nodeSelector = nodeSelector + err := mn.SendTransaction(tests.Context(t), nil) + require.EqualError(t, err, ErroringNodeError.Error()) + }) + t.Run("Returns error if RPC call fails for active node", func(t *testing.T) { + chainID := types.RandomID() + rpc := newMultiNodeRPCClient(t) + expectedError := errors.New("rpc failed to do the batch call") + rpc.On("SendTransaction", mock.Anything, mock.Anything).Return(expectedError).Once() + node := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + node.On("RPC").Return(rpc) + nodeSelector := newMockNodeSelector[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + nodeSelector.On("Select").Return(node).Once() + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: chainID, + }) + mn.nodeSelector = nodeSelector + err := mn.SendTransaction(tests.Context(t), nil) + require.EqualError(t, err, expectedError.Error()) + }) + t.Run("Returns result of main node and logs secondary nodes results", func(t *testing.T) { + // setup RPCs + failedRPC := newMultiNodeRPCClient(t) + failedRPC.On("SendTransaction", mock.Anything, mock.Anything). + Return(errors.New("rpc failed to do the batch call")).Once() + okRPC := newMultiNodeRPCClient(t) + okRPC.On("SendTransaction", mock.Anything, mock.Anything).Return(nil).Twice() + + // setup ok and failed auxiliary nodes + okNode := newMockSendOnlyNode[types.ID, multiNodeRPCClient](t) + okNode.On("RPC").Return(okRPC).Once() + okNode.On("String").Return("okNode") + failedNode := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + failedNode.On("RPC").Return(failedRPC).Once() + failedNode.On("String").Return("failedNode") + + // setup main node + mainNode := newMockNode[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + mainNode.On("RPC").Return(okRPC) + nodeSelector := newMockNodeSelector[types.ID, types.Head[Hashable], multiNodeRPCClient](t) + nodeSelector.On("Select").Return(mainNode).Once() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + mn := newTestMultiNode(t, multiNodeOpts{ + selectionMode: NodeSelectionModeRoundRobin, + chainID: types.RandomID(), + nodes: []Node[types.ID, types.Head[Hashable], multiNodeRPCClient]{failedNode, mainNode}, + sendonlys: []SendOnlyNode[types.ID, multiNodeRPCClient]{okNode}, + logger: lggr, + sendOnlyErrorParser: func(err error) SendTxReturnCode { + if err != nil { + return Fatal + } + + return Successful + }, + }) + mn.nodeSelector = nodeSelector + + err := mn.SendTransaction(tests.Context(t), nil) + require.NoError(t, err) + tests.AssertLogEventually(t, observedLogs, "Sendonly node sent transaction") + tests.AssertLogEventually(t, observedLogs, "RPC returned error") + }) +} diff --git a/common/client/node.go b/common/client/node.go new file mode 100644 index 00000000000..4fad18b42cf --- /dev/null +++ b/common/client/node.go @@ -0,0 +1,285 @@ +package client + +import ( + "context" + "fmt" + "math/big" + "net/url" + "sync" + "time" + + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +const QueryTimeout = 10 * time.Second + +var errInvalidChainID = errors.New("invalid chain id") + +var ( + promPoolRPCNodeVerifies = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_verifies", + Help: "The total number of chain ID verifications for the given RPC node", + }, []string{"network", "chainID", "nodeName"}) + promPoolRPCNodeVerifiesFailed = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_verifies_failed", + Help: "The total number of failed chain ID verifications for the given RPC node", + }, []string{"network", "chainID", "nodeName"}) + promPoolRPCNodeVerifiesSuccess = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_verifies_success", + Help: "The total number of successful chain ID verifications for the given RPC node", + }, []string{"network", "chainID", "nodeName"}) +) + +type NodeConfig interface { + PollFailureThreshold() uint32 + PollInterval() time.Duration + SelectionMode() string + SyncThreshold() uint32 +} + +//go:generate mockery --quiet --name Node --structname mockNode --filename "mock_node_test.go" --inpackage --case=underscore +type Node[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +] interface { + // State returns nodeState + State() nodeState + // StateAndLatest returns nodeState with the latest received block number & total difficulty. + StateAndLatest() (nodeState, int64, *big.Int) + // Name is a unique identifier for this node. + Name() string + String() string + RPC() RPC + SubscribersCount() int32 + UnsubscribeAllExceptAliveLoop() + ConfiguredChainID() CHAIN_ID + Order() int32 + Start(context.Context) error + Close() error +} + +type node[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +] struct { + services.StateMachine + lfcLog logger.Logger + name string + id int32 + chainID CHAIN_ID + nodePoolCfg NodeConfig + noNewHeadsThreshold time.Duration + order int32 + chainFamily string + + ws url.URL + http *url.URL + + rpc RPC + + stateMu sync.RWMutex // protects state* fields + state nodeState + // Each node is tracking the last received head number and total difficulty + stateLatestBlockNumber int64 + stateLatestTotalDifficulty *big.Int + + // nodeCtx is the node lifetime's context + nodeCtx context.Context + // cancelNodeCtx cancels nodeCtx when stopping the node + cancelNodeCtx context.CancelFunc + // wg waits for subsidiary goroutines + wg sync.WaitGroup + + // nLiveNodes is a passed in function that allows this node to: + // 1. see how many live nodes there are in total, 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. + // 2. compare against the highest head (by number or difficulty) to ensure we don't fall behind too far. + nLiveNodes func() (count int, blockNumber int64, totalDifficulty *big.Int) +} + +func NewNode[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +]( + nodeCfg NodeConfig, + noNewHeadsThreshold time.Duration, + lggr logger.Logger, + wsuri url.URL, + httpuri *url.URL, + name string, + id int32, + chainID CHAIN_ID, + nodeOrder int32, + rpc RPC, + chainFamily string, +) Node[CHAIN_ID, HEAD, RPC] { + n := new(node[CHAIN_ID, HEAD, RPC]) + n.name = name + n.id = id + n.chainID = chainID + n.nodePoolCfg = nodeCfg + n.noNewHeadsThreshold = noNewHeadsThreshold + n.ws = wsuri + n.order = nodeOrder + if httpuri != nil { + n.http = httpuri + } + n.nodeCtx, n.cancelNodeCtx = context.WithCancel(context.Background()) + lggr = logger.Named(lggr, "Node") + lggr = logger.With(lggr, + "nodeTier", Primary.String(), + "nodeName", name, + "node", n.String(), + "chainID", chainID, + "nodeOrder", n.order, + ) + n.lfcLog = logger.Named(lggr, "Lifecycle") + n.stateLatestBlockNumber = -1 + n.rpc = rpc + n.chainFamily = chainFamily + return n +} + +func (n *node[CHAIN_ID, HEAD, RPC]) String() string { + s := fmt.Sprintf("(%s)%s:%s", Primary.String(), n.name, n.ws.String()) + if n.http != nil { + s = s + fmt.Sprintf(":%s", n.http.String()) + } + return s +} + +func (n *node[CHAIN_ID, HEAD, RPC]) ConfiguredChainID() (chainID CHAIN_ID) { + return n.chainID +} + +func (n *node[CHAIN_ID, HEAD, RPC]) Name() string { + return n.name +} + +func (n *node[CHAIN_ID, HEAD, RPC]) RPC() RPC { + return n.rpc +} + +func (n *node[CHAIN_ID, HEAD, RPC]) SubscribersCount() int32 { + return n.rpc.SubscribersCount() +} + +func (n *node[CHAIN_ID, HEAD, RPC]) UnsubscribeAllExceptAliveLoop() { + n.rpc.UnsubscribeAllExceptAliveLoop() +} + +func (n *node[CHAIN_ID, HEAD, RPC]) Close() error { + return n.StopOnce(n.name, n.close) +} + +func (n *node[CHAIN_ID, HEAD, RPC]) close() error { + defer func() { + n.wg.Wait() + n.rpc.Close() + }() + + n.stateMu.Lock() + defer n.stateMu.Unlock() + + n.cancelNodeCtx() + n.state = nodeStateClosed + return nil +} + +// 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[CHAIN_ID, HEAD, RPC]) 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[CHAIN_ID, HEAD, RPC]) start(startCtx context.Context) { + if n.state != nodeStateUndialed { + panic(fmt.Sprintf("cannot dial node with state %v", n.state)) + } + + if err := n.rpc.Dial(startCtx); err != nil { + n.lfcLog.Errorw("Dial failed: Node is unreachable", "err", err) + n.declareUnreachable() + return + } + n.setState(nodeStateDialed) + + if err := n.verify(startCtx); errors.Is(err, errInvalidChainID) { + n.lfcLog.Errorw("Verify failed: 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 + } + + n.declareAlive() +} + +// 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[CHAIN_ID, HEAD, RPC]) verify(callerCtx context.Context) (err error) { + promPoolRPCNodeVerifies.WithLabelValues(n.chainFamily, n.chainID.String(), n.name).Inc() + promFailed := func() { + promPoolRPCNodeVerifiesFailed.WithLabelValues(n.chainFamily, n.chainID.String(), n.name).Inc() + } + + st := n.State() + switch st { + case nodeStateDialed, nodeStateOutOfSync, nodeStateInvalidChainID: + default: + panic(fmt.Sprintf("cannot verify node in state %v", st)) + } + + var chainID CHAIN_ID + if chainID, err = n.rpc.ChainID(callerCtx); err != nil { + promFailed() + return errors.Wrapf(err, "failed to verify chain ID for node %s", n.name) + } else if chainID.String() != n.chainID.String() { + promFailed() + return errors.Wrapf( + errInvalidChainID, + "rpc ChainID doesn't match local chain ID: RPC ID=%s, local ID=%s, node name=%s", + chainID.String(), + n.chainID.String(), + n.name, + ) + } + + promPoolRPCNodeVerifiesSuccess.WithLabelValues(n.chainFamily, n.chainID.String(), n.name).Inc() + + return nil +} + +// disconnectAll disconnects all clients connected to the node +// WARNING: NOT THREAD-SAFE +// This must be called from within the n.stateMu lock +func (n *node[CHAIN_ID, HEAD, RPC]) disconnectAll() { + n.rpc.DisconnectAll() +} + +func (n *node[CHAIN_ID, HEAD, RPC]) Order() int32 { + return n.order +} diff --git a/common/client/node_fsm.go b/common/client/node_fsm.go new file mode 100644 index 00000000000..74a5814f773 --- /dev/null +++ b/common/client/node_fsm.go @@ -0,0 +1,265 @@ +package client + +import ( + "fmt" + "math/big" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + promPoolRPCNodeTransitionsToAlive = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_num_transitions_to_alive", + Help: transitionString(nodeStateAlive), + }, []string{"chainID", "nodeName"}) + promPoolRPCNodeTransitionsToInSync = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_num_transitions_to_in_sync", + Help: fmt.Sprintf("%s to %s", transitionString(nodeStateOutOfSync), nodeStateAlive), + }, []string{"chainID", "nodeName"}) + promPoolRPCNodeTransitionsToOutOfSync = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_num_transitions_to_out_of_sync", + Help: transitionString(nodeStateOutOfSync), + }, []string{"chainID", "nodeName"}) + promPoolRPCNodeTransitionsToUnreachable = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_num_transitions_to_unreachable", + Help: transitionString(nodeStateUnreachable), + }, []string{"chainID", "nodeName"}) + promPoolRPCNodeTransitionsToInvalidChainID = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_num_transitions_to_invalid_chain_id", + Help: transitionString(nodeStateInvalidChainID), + }, []string{"chainID", "nodeName"}) + promPoolRPCNodeTransitionsToUnusable = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_num_transitions_to_unusable", + Help: transitionString(nodeStateUnusable), + }, []string{"chainID", "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 nodeStateUnusable: + return "Unusable" + 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 + // nodeStateUnusable is a sendonly node that has an invalid URL that can never be reached + nodeStateUnusable + // 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[CHAIN_ID, HEAD, RPC]) State() nodeState { + n.stateMu.RLock() + defer n.stateMu.RUnlock() + return n.state +} + +func (n *node[CHAIN_ID, HEAD, RPC]) StateAndLatest() (nodeState, int64, *big.Int) { + n.stateMu.RLock() + defer n.stateMu.RUnlock() + return n.state, n.stateLatestBlockNumber, n.stateLatestTotalDifficulty +} + +// 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[CHAIN_ID, HEAD, RPC]) 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[CHAIN_ID, HEAD, RPC]) declareAlive() { + n.transitionToAlive(func() { + n.lfcLog.Infow("RPC Node is online", "nodeState", n.state) + n.wg.Add(1) + go n.aliveLoop() + }) +} + +func (n *node[CHAIN_ID, HEAD, RPC]) transitionToAlive(fn func()) { + promPoolRPCNodeTransitionsToAlive.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(transitionFail(n.state, nodeStateAlive)) + } + fn() +} + +// declareInSync puts a node back into Alive state, allowing it to be used by +// pool consumers again +func (n *node[CHAIN_ID, HEAD, RPC]) 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[CHAIN_ID, HEAD, RPC]) transitionToInSync(fn func()) { + promPoolRPCNodeTransitionsToAlive.WithLabelValues(n.chainID.String(), n.name).Inc() + promPoolRPCNodeTransitionsToInSync.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(transitionFail(n.state, nodeStateAlive)) + } + fn() +} + +// declareOutOfSync puts a node into OutOfSync state, disconnecting all current +// clients and making it unavailable for use until back in-sync. +func (n *node[CHAIN_ID, HEAD, RPC]) declareOutOfSync(isOutOfSync func(num int64, td *big.Int) bool) { + n.transitionToOutOfSync(func() { + n.lfcLog.Errorw("RPC Node is out of sync", "nodeState", n.state) + n.wg.Add(1) + go n.outOfSyncLoop(isOutOfSync) + }) +} + +func (n *node[CHAIN_ID, HEAD, RPC]) transitionToOutOfSync(fn func()) { + promPoolRPCNodeTransitionsToOutOfSync.WithLabelValues(n.chainID.String(), n.name).Inc() + n.stateMu.Lock() + defer n.stateMu.Unlock() + if n.state == nodeStateClosed { + return + } + switch n.state { + case nodeStateAlive: + n.disconnectAll() + n.state = nodeStateOutOfSync + default: + panic(transitionFail(n.state, nodeStateOutOfSync)) + } + fn() +} + +func (n *node[CHAIN_ID, HEAD, RPC]) declareUnreachable() { + n.transitionToUnreachable(func() { + n.lfcLog.Errorw("RPC Node is unreachable", "nodeState", n.state) + n.wg.Add(1) + go n.unreachableLoop() + }) +} + +func (n *node[CHAIN_ID, HEAD, RPC]) transitionToUnreachable(fn func()) { + promPoolRPCNodeTransitionsToUnreachable.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: + n.disconnectAll() + n.state = nodeStateUnreachable + default: + panic(transitionFail(n.state, nodeStateUnreachable)) + } + fn() +} + +func (n *node[CHAIN_ID, HEAD, RPC]) 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[CHAIN_ID, HEAD, RPC]) transitionToInvalidChainID(fn func()) { + promPoolRPCNodeTransitionsToInvalidChainID.WithLabelValues(n.chainID.String(), n.name).Inc() + n.stateMu.Lock() + defer n.stateMu.Unlock() + if n.state == nodeStateClosed { + return + } + switch n.state { + case nodeStateDialed, nodeStateOutOfSync: + n.disconnectAll() + n.state = nodeStateInvalidChainID + default: + panic(transitionFail(n.state, nodeStateInvalidChainID)) + } + fn() +} + +func transitionString(state nodeState) string { + return fmt.Sprintf("Total number of times node has transitioned to %s", state) +} + +func transitionFail(from nodeState, to nodeState) string { + return fmt.Sprintf("cannot transition from %#v to %#v", from, to) +} diff --git a/common/client/node_fsm_test.go b/common/client/node_fsm_test.go new file mode 100644 index 00000000000..87e90846699 --- /dev/null +++ b/common/client/node_fsm_test.go @@ -0,0 +1,108 @@ +package client + +import ( + "slices" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +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 newTestTransitionNode(t *testing.T, rpc *mockNodeClient[types.ID, Head]) testNode { + return newTestNode(t, testNodeOpts{rpc: rpc}) +} + +func TestUnit_Node_StateTransitions(t *testing.T) { + t.Parallel() + + t.Run("setState", func(t *testing.T) { + n := newTestTransitionNode(t, nil) + assert.Equal(t, nodeStateUndialed, n.State()) + n.setState(nodeStateAlive) + assert.Equal(t, nodeStateAlive, n.State()) + n.setState(nodeStateUndialed) + assert.Equal(t, nodeStateUndialed, n.State()) + }) + + t.Run("transitionToAlive", func(t *testing.T) { + const destinationState = nodeStateAlive + allowedStates := []nodeState{nodeStateDialed, nodeStateInvalidChainID} + rpc := newMockNodeClient[types.ID, Head](t) + testTransition(t, rpc, testNode.transitionToAlive, destinationState, allowedStates...) + }) + + t.Run("transitionToInSync", func(t *testing.T) { + const destinationState = nodeStateAlive + allowedStates := []nodeState{nodeStateOutOfSync} + rpc := newMockNodeClient[types.ID, Head](t) + testTransition(t, rpc, testNode.transitionToInSync, destinationState, allowedStates...) + }) + t.Run("transitionToOutOfSync", func(t *testing.T) { + const destinationState = nodeStateOutOfSync + allowedStates := []nodeState{nodeStateAlive} + rpc := newMockNodeClient[types.ID, Head](t) + rpc.On("DisconnectAll").Once() + testTransition(t, rpc, testNode.transitionToOutOfSync, destinationState, allowedStates...) + }) + t.Run("transitionToUnreachable", func(t *testing.T) { + const destinationState = nodeStateUnreachable + allowedStates := []nodeState{nodeStateUndialed, nodeStateDialed, nodeStateAlive, nodeStateOutOfSync, nodeStateInvalidChainID} + rpc := newMockNodeClient[types.ID, Head](t) + rpc.On("DisconnectAll").Times(len(allowedStates)) + testTransition(t, rpc, testNode.transitionToUnreachable, destinationState, allowedStates...) + }) + t.Run("transitionToInvalidChain", func(t *testing.T) { + const destinationState = nodeStateInvalidChainID + allowedStates := []nodeState{nodeStateDialed, nodeStateOutOfSync} + rpc := newMockNodeClient[types.ID, Head](t) + rpc.On("DisconnectAll").Times(len(allowedStates)) + testTransition(t, rpc, testNode.transitionToInvalidChainID, destinationState, allowedStates...) + }) +} + +func testTransition(t *testing.T, rpc *mockNodeClient[types.ID, Head], transition func(node testNode, fn func()), destinationState nodeState, allowedStates ...nodeState) { + node := newTestTransitionNode(t, rpc) + for _, allowedState := range allowedStates { + m := new(fnMock) + node.setState(allowedState) + transition(node, m.Fn) + assert.Equal(t, destinationState, node.State(), "Expected node to successfully transition from %s to %s state", allowedState, destinationState) + m.AssertCalled(t) + } + // noop on attempt to transition from Closed state + m := new(fnMock) + node.setState(nodeStateClosed) + transition(node, m.Fn) + m.AssertNotCalled(t) + assert.Equal(t, nodeStateClosed, node.State(), "Expected node to remain in closed state on transition attempt") + + for _, nodeState := range allNodeStates { + if slices.Contains(allowedStates, nodeState) || nodeState == nodeStateClosed { + continue + } + + m := new(fnMock) + node.setState(nodeState) + assert.Panics(t, func() { + transition(node, m.Fn) + }, "Expected transition from `%s` to `%s` to panic", nodeState, destinationState) + m.AssertNotCalled(t) + assert.Equal(t, nodeState, node.State(), "Expected node to remain in initial state on invalid transition") + + } +} diff --git a/common/client/node_lifecycle.go b/common/client/node_lifecycle.go new file mode 100644 index 00000000000..59a59691c83 --- /dev/null +++ b/common/client/node_lifecycle.go @@ -0,0 +1,440 @@ +package client + +import ( + "context" + "fmt" + "math" + "math/big" + "time" + + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" + + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +var ( + promPoolRPCNodeHighestSeenBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "pool_rpc_node_highest_seen_block", + Help: "The highest seen block for the given RPC node", + }, []string{"chainID", "nodeName"}) + promPoolRPCNodeNumSeenBlocks = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_num_seen_blocks", + Help: "The total number of new blocks seen by the given RPC node", + }, []string{"chainID", "nodeName"}) + promPoolRPCNodePolls = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_polls_total", + Help: "The total number of poll checks for the given RPC node", + }, []string{"chainID", "nodeName"}) + promPoolRPCNodePollsFailed = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_polls_failed", + Help: "The total number of failed poll checks for the given RPC node", + }, []string{"chainID", "nodeName"}) + promPoolRPCNodePollsSuccess = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pool_rpc_node_polls_success", + Help: "The total number of successful poll checks for the given RPC node", + }, []string{"chainID", "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(noNewHeadsThreshold time.Duration) time.Duration { + interval := noNewHeadsThreshold + if interval <= 0 || interval > QueryTimeout { + interval = QueryTimeout + } + return utils.WithJitter(interval) +} + +func (n *node[CHAIN_ID, HEAD, RPC]) setLatestReceived(blockNumber int64, totalDifficulty *big.Int) { + n.stateMu.Lock() + defer n.stateMu.Unlock() + n.stateLatestBlockNumber = blockNumber + n.stateLatestTotalDifficulty = totalDifficulty +} + +const ( + msgCannotDisable = "but cannot disable this connection because there are no other RPC endpoints, or all other RPC endpoints are dead." + msgDegradedState = "Chainlink is now operating in a degraded state and urgent action is required to resolve the issue" +) + +const rpcSubscriptionMethodNewHeads = "newHeads" + +// 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[CHAIN_ID, HEAD, RPC]) 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.noNewHeadsThreshold + pollFailureThreshold := n.nodePoolCfg.PollFailureThreshold() + pollInterval := n.nodePoolCfg.PollInterval() + + lggr := logger.Named(n.lfcLog, "Alive") + lggr = logger.With(lggr, "noNewHeadsTimeoutThreshold", noNewHeadsTimeoutThreshold, "pollInterval", pollInterval, "pollFailureThreshold", pollFailureThreshold) + logger.Tracew(lggr, "Alive loop starting", "nodeState", n.State()) + + headsC := make(chan HEAD) + sub, err := n.rpc.Subscribe(n.nodeCtx, headsC, rpcSubscriptionMethodNewHeads) + if err != nil { + lggr.Errorw("Initial subscribe for heads failed", "nodeState", n.State()) + n.declareUnreachable() + return + } + // TODO: nit fix. If multinode switches primary node before we set sub as AliveSub, sub will be closed and we'll + // falsely transition this node to unreachable state + n.rpc.SetAliveLoopSub(sub) + defer sub.Unsubscribe() + + var outOfSyncT *time.Ticker + var outOfSyncTC <-chan time.Time + if noNewHeadsTimeoutThreshold > 0 { + lggr.Debugw("Head liveness checking enabled", "nodeState", n.State()) + outOfSyncT = time.NewTicker(noNewHeadsTimeoutThreshold) + defer outOfSyncT.Stop() + outOfSyncTC = outOfSyncT.C + } 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") + } + + _, highestReceivedBlockNumber, _ := n.StateAndLatest() + var pollFailures uint32 + + for { + select { + case <-n.nodeCtx.Done(): + return + case <-pollCh: + var version string + promPoolRPCNodePolls.WithLabelValues(n.chainID.String(), n.name).Inc() + logger.Tracew(lggr, "Polling for version", "nodeState", n.State(), "pollFailures", pollFailures) + ctx, cancel := context.WithTimeout(n.nodeCtx, pollInterval) + version, err := n.RPC().ClientVersion(ctx) + cancel() + if err != nil { + // prevent overflow + if pollFailures < math.MaxUint32 { + promPoolRPCNodePollsFailed.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.Debugw("Version poll successful", "nodeState", n.State(), "clientVersion", version) + promPoolRPCNodePollsSuccess.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 { + if l, _, _ := n.nLiveNodes(); l < 2 { + logger.Criticalf(lggr, "RPC endpoint failed to respond to polls; %s %s", msgCannotDisable, msgDegradedState) + continue + } + } + n.declareUnreachable() + return + } + _, num, td := n.StateAndLatest() + if outOfSync, liveNodes := n.syncStatus(num, td); outOfSync { + // note: there must be another live node for us to be out of sync + lggr.Errorw("RPC endpoint has fallen behind", "blockNumber", num, "totalDifficulty", td, "nodeState", n.State()) + if liveNodes < 2 { + logger.Criticalf(lggr, "RPC endpoint has fallen behind; %s %s", msgCannotDisable, msgDegradedState) + continue + } + n.declareOutOfSync(n.isOutOfSync) + return + } + case bh, open := <-headsC: + if !open { + lggr.Errorw("Subscription channel unexpectedly closed", "nodeState", n.State()) + n.declareUnreachable() + return + } + promPoolRPCNodeNumSeenBlocks.WithLabelValues(n.chainID.String(), n.name).Inc() + logger.Tracew(lggr, "Got head", "head", bh) + if bh.BlockNumber() > highestReceivedBlockNumber { + promPoolRPCNodeHighestSeenBlock.WithLabelValues(n.chainID.String(), n.name).Set(float64(bh.BlockNumber())) + logger.Tracew(lggr, "Got higher block number, resetting timer", "latestReceivedBlockNumber", highestReceivedBlockNumber, "blockNumber", bh.BlockNumber(), "nodeState", n.State()) + highestReceivedBlockNumber = bh.BlockNumber() + } else { + logger.Tracew(lggr, "Ignoring previously seen block number", "latestReceivedBlockNumber", highestReceivedBlockNumber, "blockNumber", bh.BlockNumber(), "nodeState", n.State()) + } + if outOfSyncT != nil { + outOfSyncT.Reset(noNewHeadsTimeoutThreshold) + } + n.setLatestReceived(bh.BlockNumber(), bh.BlockDifficulty()) + case err := <-sub.Err(): + 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, highestReceivedBlockNumber), "nodeState", n.State(), "latestReceivedBlockNumber", highestReceivedBlockNumber, "noNewHeadsTimeoutThreshold", noNewHeadsTimeoutThreshold) + if n.nLiveNodes != nil { + if l, _, _ := n.nLiveNodes(); l < 2 { + logger.Criticalf(lggr, "RPC endpoint detected out of sync; %s %s", msgCannotDisable, msgDegradedState) + // 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.noNewHeadsThreshold)) + continue + } + } + n.declareOutOfSync(func(num int64, td *big.Int) bool { return num < highestReceivedBlockNumber }) + return + } + } +} + +func (n *node[CHAIN_ID, HEAD, RPC]) isOutOfSync(num int64, td *big.Int) (outOfSync bool) { + outOfSync, _ = n.syncStatus(num, td) + return +} + +// syncStatus returns outOfSync true if num or td is more than SyncThresold behind the best node. +// Always returns outOfSync false for SyncThreshold 0. +// liveNodes is only included when outOfSync is true. +func (n *node[CHAIN_ID, HEAD, RPC]) syncStatus(num int64, td *big.Int) (outOfSync bool, liveNodes int) { + if n.nLiveNodes == nil { + return // skip for tests + } + threshold := n.nodePoolCfg.SyncThreshold() + if threshold == 0 { + return // disabled + } + // Check against best node + ln, highest, greatest := n.nLiveNodes() + mode := n.nodePoolCfg.SelectionMode() + switch mode { + case NodeSelectionModeHighestHead, NodeSelectionModeRoundRobin, NodeSelectionModePriorityLevel: + return num < highest-int64(threshold), ln + case NodeSelectionModeTotalDifficulty: + bigThreshold := big.NewInt(int64(threshold)) + return td.Cmp(bigmath.Sub(greatest, bigThreshold)) < 0, ln + default: + panic("unrecognized NodeSelectionMode: " + mode) + } +} + +const ( + msgReceivedBlock = "Received block for RPC node, waiting until back in-sync to mark as live again" + msgInSync = "RPC node back in sync" +) + +// outOfSyncLoop takes an OutOfSync node and waits until isOutOfSync returns false to go back to live status +func (n *node[CHAIN_ID, HEAD, RPC]) outOfSyncLoop(isOutOfSync func(num int64, td *big.Int) bool) { + 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 := logger.Named(n.lfcLog, "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 + if err := n.rpc.Dial(n.nodeCtx); 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 + if err := n.verify(n.nodeCtx); err != nil { + lggr.Errorw(fmt.Sprintf("Failed to verify out-of-sync RPC node: %v", err), "err", err) + n.declareInvalidChainID() + return + } + + logger.Tracew(lggr, "Successfully subscribed to heads feed on out-of-sync RPC node", "nodeState", n.State()) + + ch := make(chan HEAD) + sub, err := n.rpc.Subscribe(n.nodeCtx, ch, rpcSubscriptionMethodNewHeads) + 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.nodeCtx.Done(): + return + case head, open := <-ch: + if !open { + lggr.Error("Subscription channel unexpectedly closed", "nodeState", n.State()) + n.declareUnreachable() + return + } + n.setLatestReceived(head.BlockNumber(), head.BlockDifficulty()) + if !isOutOfSync(head.BlockNumber(), head.BlockDifficulty()) { + // back in-sync! flip back into alive loop + lggr.Infow(fmt.Sprintf("%s: %s. Node was out-of-sync for %s", msgInSync, n.String(), time.Since(outOfSyncAt)), "blockNumber", head.BlockNumber(), "blockDifficulty", head.BlockDifficulty(), "nodeState", n.State()) + n.declareInSync() + return + } + lggr.Debugw(msgReceivedBlock, "blockNumber", head.BlockNumber(), "blockDifficulty", head.BlockDifficulty(), "nodeState", n.State()) + case <-time.After(zombieNodeCheckInterval(n.noNewHeadsThreshold)): + if n.nLiveNodes != nil { + if l, _, _ := n.nLiveNodes(); l < 1 { + logger.Critical(lggr, "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[CHAIN_ID, HEAD, RPC]) 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 := logger.Named(n.lfcLog, "Unreachable") + lggr.Debugw("Trying to revive unreachable RPC node", "nodeState", n.State()) + + dialRetryBackoff := utils.NewRedialBackoff() + + for { + select { + case <-n.nodeCtx.Done(): + return + case <-time.After(dialRetryBackoff.Duration()): + logger.Tracew(lggr, "Trying to re-dial RPC node", "nodeState", n.State()) + + err := n.rpc.Dial(n.nodeCtx) + 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(n.nodeCtx) + + 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[CHAIN_ID, HEAD, RPC]) 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 := logger.Named(n.lfcLog, "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.nodeCtx.Done(): + return + case <-time.After(chainIDRecheckBackoff.Duration()): + err := n.verify(n.nodeCtx) + 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/common/client/node_lifecycle_test.go b/common/client/node_lifecycle_test.go new file mode 100644 index 00000000000..224b79d8378 --- /dev/null +++ b/common/client/node_lifecycle_test.go @@ -0,0 +1,1071 @@ +package client + +import ( + "fmt" + big "math/big" + "sync/atomic" + "testing" + + "github.com/cometbft/cometbft/libs/rand" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "go.uber.org/zap" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink/v2/common/types" + "github.com/smartcontractkit/chainlink/v2/common/types/mocks" +) + +func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { + t.Parallel() + + newDialedNode := func(t *testing.T, opts testNodeOpts) testNode { + node := newTestNode(t, opts) + opts.rpc.On("Close").Return(nil).Once() + + node.setState(nodeStateDialed) + return node + } + + t.Run("returns on closed", func(t *testing.T) { + node := newTestNode(t, testNodeOpts{}) + node.setState(nodeStateClosed) + node.wg.Add(1) + node.aliveLoop() + + }) + t.Run("if initial subscribe fails, transitions to unreachable", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + node := newDialedNode(t, testNodeOpts{ + rpc: rpc, + }) + defer func() { assert.NoError(t, node.close()) }() + + expectedError := errors.New("failed to subscribe to rpc") + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Return(nil, expectedError).Once() + rpc.On("DisconnectAll").Once() + // might be called in unreachable loop + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")).Maybe() + node.declareAlive() + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateUnreachable + }) + + }) + t.Run("if remote RPC connection is closed transitions to unreachable", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + node := newDialedNode(t, testNodeOpts{ + rpc: rpc, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + sub := mocks.NewSubscription(t) + errChan := make(chan error) + close(errChan) + sub.On("Err").Return((<-chan error)(errChan)).Once() + sub.On("Unsubscribe").Once() + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Return(sub, nil).Once() + rpc.On("SetAliveLoopSub", sub).Once() + // disconnects all on transfer to unreachable + rpc.On("DisconnectAll").Once() + // might be called in unreachable loop + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")).Maybe() + node.declareAlive() + tests.AssertLogEventually(t, observedLogs, "Subscription was terminated") + assert.Equal(t, nodeStateUnreachable, node.State()) + }) + + newSubscribedNode := func(t *testing.T, opts testNodeOpts) testNode { + sub := mocks.NewSubscription(t) + sub.On("Err").Return((<-chan error)(nil)) + sub.On("Unsubscribe").Once() + opts.rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Return(sub, nil).Once() + opts.rpc.On("SetAliveLoopSub", sub).Once() + return newDialedNode(t, opts) + } + t.Run("Stays alive and waits for signal", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newSubscribedNode(t, testNodeOpts{ + config: testNodeConfig{}, + rpc: rpc, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + node.declareAlive() + tests.AssertLogEventually(t, observedLogs, "Head liveness checking disabled") + tests.AssertLogEventually(t, observedLogs, "Polling disabled") + assert.Equal(t, nodeStateAlive, node.State()) + }) + t.Run("stays alive while below pollFailureThreshold and resets counter on success", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + const pollFailureThreshold = 3 + node := newSubscribedNode(t, testNodeOpts{ + config: testNodeConfig{ + pollFailureThreshold: pollFailureThreshold, + pollInterval: tests.TestInterval, + }, + rpc: rpc, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + pollError := errors.New("failed to get ClientVersion") + // 1. Return error several times, but below threshold + rpc.On("ClientVersion", mock.Anything).Return("", pollError).Run(func(_ mock.Arguments) { + // stays healthy while below threshold + assert.Equal(t, nodeStateAlive, node.State()) + }).Times(pollFailureThreshold - 1) + // 2. Successful call that is expected to reset counter + rpc.On("ClientVersion", mock.Anything).Return("client_version", nil).Once() + // 3. Return error. If we have not reset the timer, we'll transition to nonAliveState + rpc.On("ClientVersion", mock.Anything).Return("", pollError).Once() + // 4. Once during the call, check if node is alive + var ensuredAlive atomic.Bool + rpc.On("ClientVersion", mock.Anything).Return("client_version", nil).Run(func(_ mock.Arguments) { + if ensuredAlive.Load() { + return + } + ensuredAlive.Store(true) + assert.Equal(t, nodeStateAlive, node.State()) + }).Once() + // redundant call to stay in alive state + rpc.On("ClientVersion", mock.Anything).Return("client_version", nil) + node.declareAlive() + tests.AssertLogCountEventually(t, observedLogs, fmt.Sprintf("Poll failure, RPC endpoint %s failed to respond properly", node.String()), pollFailureThreshold) + tests.AssertLogCountEventually(t, observedLogs, "Version poll successful", 2) + assert.True(t, ensuredAlive.Load(), "expected to ensure that node was alive") + + }) + t.Run("with threshold poll failures, transitions to unreachable", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + const pollFailureThreshold = 3 + node := newSubscribedNode(t, testNodeOpts{ + config: testNodeConfig{ + pollFailureThreshold: pollFailureThreshold, + pollInterval: tests.TestInterval, + }, + rpc: rpc, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + pollError := errors.New("failed to get ClientVersion") + rpc.On("ClientVersion", mock.Anything).Return("", pollError) + // disconnects all on transfer to unreachable + rpc.On("DisconnectAll").Once() + // might be called in unreachable loop + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")).Maybe() + node.declareAlive() + tests.AssertLogCountEventually(t, observedLogs, fmt.Sprintf("Poll failure, RPC endpoint %s failed to respond properly", node.String()), pollFailureThreshold) + tests.AssertEventually(t, func() bool { + return nodeStateUnreachable == node.State() + }) + }) + t.Run("with threshold poll failures, but we are the last node alive, forcibly keeps it alive", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + const pollFailureThreshold = 3 + node := newSubscribedNode(t, testNodeOpts{ + config: testNodeConfig{ + pollFailureThreshold: pollFailureThreshold, + pollInterval: tests.TestInterval, + }, + rpc: rpc, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + node.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { + return 1, 20, big.NewInt(10) + } + pollError := errors.New("failed to get ClientVersion") + rpc.On("ClientVersion", mock.Anything).Return("", pollError) + node.declareAlive() + tests.AssertLogEventually(t, observedLogs, fmt.Sprintf("RPC endpoint failed to respond to %d consecutive polls", pollFailureThreshold)) + assert.Equal(t, nodeStateAlive, node.State()) + }) + t.Run("when behind more than SyncThreshold, transitions to out of sync", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + const syncThreshold = 10 + node := newSubscribedNode(t, testNodeOpts{ + config: testNodeConfig{ + pollInterval: tests.TestInterval, + syncThreshold: syncThreshold, + selectionMode: NodeSelectionModeRoundRobin, + }, + rpc: rpc, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + node.stateLatestBlockNumber = 20 + node.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { + return 10, syncThreshold + node.stateLatestBlockNumber + 1, big.NewInt(10) + } + rpc.On("ClientVersion", mock.Anything).Return("", nil) + // tries to redial in outOfSync + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")).Run(func(_ mock.Arguments) { + assert.Equal(t, nodeStateOutOfSync, node.State()) + }).Once() + // disconnects all on transfer to unreachable or outOfSync + rpc.On("DisconnectAll").Maybe() + // might be called in unreachable loop + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")).Maybe() + node.declareAlive() + tests.AssertLogEventually(t, observedLogs, "Failed to dial out-of-sync RPC node") + }) + t.Run("when behind more than SyncThreshold but we are the last live node, forcibly stays alive", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + const syncThreshold = 10 + node := newSubscribedNode(t, testNodeOpts{ + config: testNodeConfig{ + pollInterval: tests.TestInterval, + syncThreshold: syncThreshold, + selectionMode: NodeSelectionModeRoundRobin, + }, + rpc: rpc, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + node.stateLatestBlockNumber = 20 + node.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { + return 1, syncThreshold + node.stateLatestBlockNumber + 1, big.NewInt(10) + } + rpc.On("ClientVersion", mock.Anything).Return("", nil) + node.declareAlive() + tests.AssertLogEventually(t, observedLogs, fmt.Sprintf("RPC endpoint has fallen behind; %s %s", msgCannotDisable, msgDegradedState)) + }) + t.Run("when behind but SyncThreshold=0, stay alive", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newSubscribedNode(t, testNodeOpts{ + config: testNodeConfig{ + pollInterval: tests.TestInterval, + syncThreshold: 0, + selectionMode: NodeSelectionModeRoundRobin, + }, + rpc: rpc, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + node.stateLatestBlockNumber = 20 + node.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { + return 1, node.stateLatestBlockNumber + 100, big.NewInt(10) + } + rpc.On("ClientVersion", mock.Anything).Return("", nil) + node.declareAlive() + tests.AssertLogCountEventually(t, observedLogs, "Version poll successful", 2) + assert.Equal(t, nodeStateAlive, node.State()) + }) + + t.Run("when no new heads received for threshold, transitions to out of sync", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + node := newSubscribedNode(t, testNodeOpts{ + config: testNodeConfig{}, + noNewHeadsThreshold: tests.TestInterval, + rpc: rpc, + }) + defer func() { assert.NoError(t, node.close()) }() + // tries to redial in outOfSync + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")).Run(func(_ mock.Arguments) { + assert.Equal(t, nodeStateOutOfSync, node.State()) + }).Once() + // disconnects all on transfer to unreachable or outOfSync + rpc.On("DisconnectAll").Maybe() + // might be called in unreachable loop + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")).Maybe() + node.declareAlive() + tests.AssertEventually(t, func() bool { + // right after outOfSync we'll transfer to unreachable due to returned error on Dial + // we check that we were in out of sync state on first Dial call + return node.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) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newSubscribedNode(t, testNodeOpts{ + config: testNodeConfig{}, + lggr: lggr, + noNewHeadsThreshold: tests.TestInterval, + rpc: rpc, + }) + defer func() { assert.NoError(t, node.close()) }() + node.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { + return 1, 20, big.NewInt(10) + } + node.declareAlive() + tests.AssertLogEventually(t, observedLogs, fmt.Sprintf("RPC endpoint detected out of sync; %s %s", msgCannotDisable, msgDegradedState)) + assert.Equal(t, nodeStateAlive, node.State()) + }) + + t.Run("rpc closed head channel", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + sub := mocks.NewSubscription(t) + sub.On("Err").Return((<-chan error)(nil)) + sub.On("Unsubscribe").Once() + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Run(func(args mock.Arguments) { + ch := args.Get(1).(chan<- Head) + close(ch) + }).Return(sub, nil).Once() + rpc.On("SetAliveLoopSub", sub).Once() + lggr, observedLogs := logger.TestObserved(t, zap.ErrorLevel) + node := newDialedNode(t, testNodeOpts{ + lggr: lggr, + config: testNodeConfig{}, + noNewHeadsThreshold: tests.TestInterval, + rpc: rpc, + }) + defer func() { assert.NoError(t, node.close()) }() + // disconnects all on transfer to unreachable or outOfSync + rpc.On("DisconnectAll").Once() + // might be called in unreachable loop + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")).Maybe() + node.declareAlive() + tests.AssertLogEventually(t, observedLogs, "Subscription channel unexpectedly closed") + assert.Equal(t, nodeStateUnreachable, node.State()) + + }) + t.Run("updates block number and difficulty on new head", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + sub := mocks.NewSubscription(t) + sub.On("Err").Return((<-chan error)(nil)) + sub.On("Unsubscribe").Once() + expectedBlockNumber := rand.Int64() + expectedDiff := big.NewInt(rand.Int64()) + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Run(func(args mock.Arguments) { + ch := args.Get(1).(chan<- Head) + go writeHeads(t, ch, head{BlockNumber: expectedBlockNumber, BlockDifficulty: expectedDiff}) + }).Return(sub, nil).Once() + rpc.On("SetAliveLoopSub", sub).Once() + node := newDialedNode(t, testNodeOpts{ + config: testNodeConfig{}, + rpc: rpc, + }) + defer func() { assert.NoError(t, node.close()) }() + node.declareAlive() + tests.AssertEventually(t, func() bool { + state, block, diff := node.StateAndLatest() + return state == nodeStateAlive && block == expectedBlockNumber == bigmath.Equal(diff, expectedDiff) + }) + }) +} + +type head struct { + BlockNumber int64 + BlockDifficulty *big.Int +} + +func writeHeads(t *testing.T, ch chan<- Head, heads ...head) { + for _, head := range heads { + h := newMockHead(t) + h.On("BlockNumber").Return(head.BlockNumber) + h.On("BlockDifficulty").Return(head.BlockDifficulty) + select { + case ch <- h: + case <-tests.Context(t).Done(): + return + } + } +} + +func setupRPCForAliveLoop(t *testing.T, rpc *mockNodeClient[types.ID, Head]) { + rpc.On("Dial", mock.Anything).Return(nil).Maybe() + aliveSubscription := mocks.NewSubscription(t) + aliveSubscription.On("Err").Return((<-chan error)(nil)).Maybe() + aliveSubscription.On("Unsubscribe").Maybe() + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Return(aliveSubscription, nil).Maybe() + rpc.On("SetAliveLoopSub", mock.Anything).Maybe() +} + +func TestUnit_NodeLifecycle_outOfSyncLoop(t *testing.T) { + t.Parallel() + + newAliveNode := func(t *testing.T, opts testNodeOpts) testNode { + node := newTestNode(t, opts) + opts.rpc.On("Close").Return(nil).Once() + // disconnects all on transfer to unreachable or outOfSync + opts.rpc.On("DisconnectAll") + node.setState(nodeStateAlive) + return node + } + + stubIsOutOfSync := func(num int64, td *big.Int) bool { + return false + } + + t.Run("returns on closed", func(t *testing.T) { + t.Parallel() + node := newTestNode(t, testNodeOpts{}) + node.setState(nodeStateClosed) + node.wg.Add(1) + node.outOfSyncLoop(stubIsOutOfSync) + }) + t.Run("on old blocks stays outOfSync and returns on close", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil).Once() + // might be called multiple times + rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil).Once() + + outOfSyncSubscription := mocks.NewSubscription(t) + outOfSyncSubscription.On("Err").Return((<-chan error)(nil)) + outOfSyncSubscription.On("Unsubscribe").Once() + heads := []head{{BlockNumber: 7}, {BlockNumber: 11}, {BlockNumber: 13}} + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Run(func(args mock.Arguments) { + ch := args.Get(1).(chan<- Head) + go writeHeads(t, ch, heads...) + }).Return(outOfSyncSubscription, nil).Once() + rpc.On("Dial", mock.Anything).Return(errors.New("failed to redial")).Maybe() + + node.declareOutOfSync(func(num int64, td *big.Int) bool { + return true + }) + tests.AssertLogCountEventually(t, observedLogs, msgReceivedBlock, len(heads)) + assert.Equal(t, nodeStateOutOfSync, node.State()) + }) + t.Run("if initial dial fails, transitions to unreachable", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + }) + defer func() { assert.NoError(t, node.close()) }() + + expectedError := errors.New("failed to dial rpc") + // might be called again in unreachable loop, so no need to set once + rpc.On("Dial", mock.Anything).Return(expectedError) + node.declareOutOfSync(stubIsOutOfSync) + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateUnreachable + }) + }) + t.Run("if fail to get chainID, transitions to invalidChainID", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil).Once() + expectedError := errors.New("failed to get chain ID") + // might be called multiple times + rpc.On("ChainID", mock.Anything).Return(types.NewIDFromInt(0), expectedError) + node.declareOutOfSync(stubIsOutOfSync) + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateInvalidChainID + }) + }) + t.Run("if chainID does not match, transitions to invalidChainID", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.NewIDFromInt(10) + rpcChainID := types.NewIDFromInt(11) + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil).Once() + // might be called multiple times + rpc.On("ChainID", mock.Anything).Return(rpcChainID, nil) + node.declareOutOfSync(stubIsOutOfSync) + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateInvalidChainID + }) + }) + t.Run("if fails to subscribe, becomes unreachable", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil).Once() + // might be called multiple times + rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil).Once() + expectedError := errors.New("failed to subscribe") + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Return(nil, expectedError) + rpc.On("Dial", mock.Anything).Return(errors.New("failed to redial")).Maybe() + node.declareOutOfSync(stubIsOutOfSync) + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateUnreachable + }) + }) + t.Run("on subscription termination becomes unreachable", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.ErrorLevel) + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil).Once() + // might be called multiple times + rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil).Once() + + sub := mocks.NewSubscription(t) + errChan := make(chan error, 1) + errChan <- errors.New("subscription was terminate") + sub.On("Err").Return((<-chan error)(errChan)) + sub.On("Unsubscribe").Once() + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Return(sub, nil).Once() + rpc.On("Dial", mock.Anything).Return(errors.New("failed to redial")).Maybe() + node.declareOutOfSync(stubIsOutOfSync) + tests.AssertLogEventually(t, observedLogs, "Subscription was terminated") + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateUnreachable + }) + }) + t.Run("becomes unreachable if head channel is closed", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.ErrorLevel) + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil).Once() + // might be called multiple times + rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil).Once() + + sub := mocks.NewSubscription(t) + sub.On("Err").Return((<-chan error)(nil)) + sub.On("Unsubscribe").Once() + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Run(func(args mock.Arguments) { + ch := args.Get(1).(chan<- Head) + close(ch) + }).Return(sub, nil).Once() + rpc.On("Dial", mock.Anything).Return(errors.New("failed to redial")).Maybe() + node.declareOutOfSync(stubIsOutOfSync) + tests.AssertLogEventually(t, observedLogs, "Subscription channel unexpectedly closed") + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateUnreachable + }) + }) + + t.Run("becomes alive if it receives a newer head", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil).Once() + // might be called multiple times + rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil).Once() + + outOfSyncSubscription := mocks.NewSubscription(t) + outOfSyncSubscription.On("Err").Return((<-chan error)(nil)) + outOfSyncSubscription.On("Unsubscribe").Once() + const highestBlock = 1000 + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Run(func(args mock.Arguments) { + ch := args.Get(1).(chan<- Head) + go writeHeads(t, ch, head{BlockNumber: highestBlock - 1}, head{BlockNumber: highestBlock}) + }).Return(outOfSyncSubscription, nil).Once() + + setupRPCForAliveLoop(t, rpc) + + node.declareOutOfSync(func(num int64, td *big.Int) bool { + return num < highestBlock + }) + tests.AssertLogEventually(t, observedLogs, msgReceivedBlock) + tests.AssertLogEventually(t, observedLogs, msgInSync) + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateAlive + }) + }) + t.Run("becomes alive if there is no other nodes", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newAliveNode(t, testNodeOpts{ + noNewHeadsThreshold: tests.TestInterval, + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + node.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { + return 0, 100, big.NewInt(200) + } + + rpc.On("Dial", mock.Anything).Return(nil).Once() + // might be called multiple times + rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil).Once() + + outOfSyncSubscription := mocks.NewSubscription(t) + outOfSyncSubscription.On("Err").Return((<-chan error)(nil)) + outOfSyncSubscription.On("Unsubscribe").Once() + rpc.On("Subscribe", mock.Anything, mock.Anything, rpcSubscriptionMethodNewHeads).Return(outOfSyncSubscription, nil).Once() + + setupRPCForAliveLoop(t, rpc) + + node.declareOutOfSync(stubIsOutOfSync) + tests.AssertLogEventually(t, observedLogs, "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") + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateAlive + }) + }) +} + +func TestUnit_NodeLifecycle_unreachableLoop(t *testing.T) { + t.Parallel() + + newAliveNode := func(t *testing.T, opts testNodeOpts) testNode { + node := newTestNode(t, opts) + opts.rpc.On("Close").Return(nil).Once() + // disconnects all on transfer to unreachable + opts.rpc.On("DisconnectAll") + + node.setState(nodeStateAlive) + return node + } + t.Run("returns on closed", func(t *testing.T) { + t.Parallel() + node := newTestNode(t, testNodeOpts{}) + node.setState(nodeStateClosed) + node.wg.Add(1) + node.unreachableLoop() + + }) + t.Run("on failed redial, keeps trying", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")) + node.declareUnreachable() + tests.AssertLogCountEventually(t, observedLogs, "Failed to redial RPC node; still unreachable", 2) + }) + t.Run("on failed chainID verification, keep trying", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil) + rpc.On("ChainID", mock.Anything).Run(func(_ mock.Arguments) { + assert.Equal(t, nodeStateDialed, node.State()) + }).Return(nodeChainID, errors.New("failed to get chain id")) + node.declareUnreachable() + tests.AssertLogCountEventually(t, observedLogs, "Failed to redial RPC node; verify failed", 2) + }) + t.Run("on chain ID mismatch transitions to invalidChainID", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.NewIDFromInt(10) + rpcChainID := types.NewIDFromInt(11) + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil) + rpc.On("ChainID", mock.Anything).Return(rpcChainID, nil) + node.declareUnreachable() + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateInvalidChainID + }) + }) + t.Run("on valid chain ID becomes alive", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + node := newAliveNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil) + rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil) + + setupRPCForAliveLoop(t, rpc) + + node.declareUnreachable() + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateAlive + }) + }) +} + +func TestUnit_NodeLifecycle_invalidChainIDLoop(t *testing.T) { + t.Parallel() + newDialedNode := func(t *testing.T, opts testNodeOpts) testNode { + node := newTestNode(t, opts) + opts.rpc.On("Close").Return(nil).Once() + opts.rpc.On("DisconnectAll") + + node.setState(nodeStateDialed) + return node + } + t.Run("returns on closed", func(t *testing.T) { + t.Parallel() + node := newTestNode(t, testNodeOpts{}) + node.setState(nodeStateClosed) + node.wg.Add(1) + node.invalidChainIDLoop() + + }) + t.Run("on failed chainID call becomes unreachable", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newDialedNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("ChainID", mock.Anything).Return(nodeChainID, errors.New("failed to get chain id")) + // for unreachable loop + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")).Maybe() + node.declareInvalidChainID() + tests.AssertLogEventually(t, observedLogs, "Unexpected error while verifying RPC node chain ID") + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateUnreachable + }) + }) + t.Run("on chainID mismatch keeps trying", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.NewIDFromInt(10) + rpcChainID := types.NewIDFromInt(11) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newDialedNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("ChainID", mock.Anything).Return(rpcChainID, nil) + node.declareInvalidChainID() + tests.AssertLogCountEventually(t, observedLogs, "Failed to verify RPC node; remote endpoint returned the wrong chain ID", 2) + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateInvalidChainID + }) + }) + t.Run("on valid chainID becomes alive", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + node := newDialedNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil) + + setupRPCForAliveLoop(t, rpc) + + node.declareInvalidChainID() + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateAlive + }) + }) +} + +func TestUnit_NodeLifecycle_start(t *testing.T) { + t.Parallel() + + newNode := func(t *testing.T, opts testNodeOpts) testNode { + node := newTestNode(t, opts) + opts.rpc.On("Close").Return(nil).Once() + + return node + } + t.Run("if fails on initial dial, becomes unreachable", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(errors.New("failed to dial")) + // disconnects all on transfer to unreachable + rpc.On("DisconnectAll") + err := node.Start(tests.Context(t)) + assert.NoError(t, err) + tests.AssertLogEventually(t, observedLogs, "Dial failed: Node is unreachable") + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateUnreachable + }) + }) + t.Run("if chainID verification fails, becomes unreachable", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + node := newNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + lggr: lggr, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil) + rpc.On("ChainID", mock.Anything).Run(func(_ mock.Arguments) { + assert.Equal(t, nodeStateDialed, node.State()) + }).Return(nodeChainID, errors.New("failed to get chain id")) + // disconnects all on transfer to unreachable + rpc.On("DisconnectAll") + err := node.Start(tests.Context(t)) + assert.NoError(t, err) + tests.AssertLogEventually(t, observedLogs, "Verify failed") + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateUnreachable + }) + }) + t.Run("on chain ID mismatch transitions to invalidChainID", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.NewIDFromInt(10) + rpcChainID := types.NewIDFromInt(11) + node := newNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil) + rpc.On("ChainID", mock.Anything).Return(rpcChainID, nil) + // disconnects all on transfer to unreachable + rpc.On("DisconnectAll") + err := node.Start(tests.Context(t)) + assert.NoError(t, err) + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateInvalidChainID + }) + }) + t.Run("on valid chain ID becomes alive", func(t *testing.T) { + t.Parallel() + rpc := newMockNodeClient[types.ID, Head](t) + nodeChainID := types.RandomID() + node := newNode(t, testNodeOpts{ + rpc: rpc, + chainID: nodeChainID, + }) + defer func() { assert.NoError(t, node.close()) }() + + rpc.On("Dial", mock.Anything).Return(nil) + rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil) + + setupRPCForAliveLoop(t, rpc) + + err := node.Start(tests.Context(t)) + assert.NoError(t, err) + tests.AssertEventually(t, func() bool { + return node.State() == nodeStateAlive + }) + }) +} + +func TestUnit_NodeLifecycle_syncStatus(t *testing.T) { + t.Parallel() + t.Run("skip if nLiveNodes is not configured", func(t *testing.T) { + node := newTestNode(t, testNodeOpts{}) + outOfSync, liveNodes := node.syncStatus(0, nil) + assert.Equal(t, false, outOfSync) + assert.Equal(t, 0, liveNodes) + }) + t.Run("skip if syncThreshold is not configured", func(t *testing.T) { + node := newTestNode(t, testNodeOpts{}) + node.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { + return + } + outOfSync, liveNodes := node.syncStatus(0, nil) + assert.Equal(t, false, outOfSync) + assert.Equal(t, 0, liveNodes) + }) + t.Run("panics on invalid selection mode", func(t *testing.T) { + node := newTestNode(t, testNodeOpts{ + config: testNodeConfig{syncThreshold: 1}, + }) + node.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { + return + } + assert.Panics(t, func() { + _, _ = node.syncStatus(0, nil) + }) + }) + t.Run("block height selection mode", func(t *testing.T) { + const syncThreshold = 10 + const highestBlock = 1000 + const nodesNum = 20 + const totalDifficulty = 3000 + testCases := []struct { + name string + blockNumber int64 + outOfSync bool + }{ + { + name: "below threshold", + blockNumber: highestBlock - syncThreshold - 1, + outOfSync: true, + }, + { + name: "equal to threshold", + blockNumber: highestBlock - syncThreshold, + outOfSync: false, + }, + { + name: "equal to highest block", + blockNumber: highestBlock, + outOfSync: false, + }, + { + name: "higher than highest block", + blockNumber: highestBlock, + outOfSync: false, + }, + } + + for _, selectionMode := range []string{NodeSelectionModeHighestHead, NodeSelectionModeRoundRobin, NodeSelectionModePriorityLevel} { + node := newTestNode(t, testNodeOpts{ + config: testNodeConfig{ + syncThreshold: syncThreshold, + selectionMode: selectionMode, + }, + }) + node.nLiveNodes = func() (int, int64, *big.Int) { + return nodesNum, highestBlock, big.NewInt(totalDifficulty) + } + for _, td := range []int64{totalDifficulty - syncThreshold - 1, totalDifficulty - syncThreshold, totalDifficulty, totalDifficulty + 1} { + for _, testCase := range testCases { + t.Run(fmt.Sprintf("%s: selectionMode: %s: total difficulty: %d", testCase.name, selectionMode, td), func(t *testing.T) { + outOfSync, liveNodes := node.syncStatus(testCase.blockNumber, big.NewInt(td)) + assert.Equal(t, nodesNum, liveNodes) + assert.Equal(t, testCase.outOfSync, outOfSync) + }) + } + } + } + + }) + t.Run("total difficulty selection mode", func(t *testing.T) { + const syncThreshold = 10 + const highestBlock = 1000 + const nodesNum = 20 + const totalDifficulty = 3000 + testCases := []struct { + name string + totalDifficulty int64 + outOfSync bool + }{ + { + name: "below threshold", + totalDifficulty: totalDifficulty - syncThreshold - 1, + outOfSync: true, + }, + { + name: "equal to threshold", + totalDifficulty: totalDifficulty - syncThreshold, + outOfSync: false, + }, + { + name: "equal to highest block", + totalDifficulty: totalDifficulty, + outOfSync: false, + }, + { + name: "higher than highest block", + totalDifficulty: totalDifficulty, + outOfSync: false, + }, + } + + node := newTestNode(t, testNodeOpts{ + config: testNodeConfig{ + syncThreshold: syncThreshold, + selectionMode: NodeSelectionModeTotalDifficulty, + }, + }) + node.nLiveNodes = func() (int, int64, *big.Int) { + return nodesNum, highestBlock, big.NewInt(totalDifficulty) + } + for _, hb := range []int64{highestBlock - syncThreshold - 1, highestBlock - syncThreshold, highestBlock, highestBlock + 1} { + for _, testCase := range testCases { + t.Run(fmt.Sprintf("%s: selectionMode: %s: highest block: %d", testCase.name, NodeSelectionModeTotalDifficulty, hb), func(t *testing.T) { + outOfSync, liveNodes := node.syncStatus(hb, big.NewInt(testCase.totalDifficulty)) + assert.Equal(t, nodesNum, liveNodes) + assert.Equal(t, testCase.outOfSync, outOfSync) + }) + } + } + + }) +} diff --git a/common/client/node_selector.go b/common/client/node_selector.go new file mode 100644 index 00000000000..45604ebe8d9 --- /dev/null +++ b/common/client/node_selector.go @@ -0,0 +1,46 @@ +package client + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +const ( + NodeSelectionModeHighestHead = "HighestHead" + NodeSelectionModeRoundRobin = "RoundRobin" + NodeSelectionModeTotalDifficulty = "TotalDifficulty" + NodeSelectionModePriorityLevel = "PriorityLevel" +) + +//go:generate mockery --quiet --name NodeSelector --structname mockNodeSelector --filename "mock_node_selector_test.go" --inpackage --case=underscore +type NodeSelector[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +] interface { + // Select returns a Node, or nil if none can be selected. + // Implementation must be thread-safe. + Select() Node[CHAIN_ID, HEAD, RPC] + // Name returns the strategy name, e.g. "HighestHead" or "RoundRobin" + Name() string +} + +func newNodeSelector[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +](selectionMode string, nodes []Node[CHAIN_ID, HEAD, RPC]) NodeSelector[CHAIN_ID, HEAD, RPC] { + switch selectionMode { + case NodeSelectionModeHighestHead: + return NewHighestHeadNodeSelector[CHAIN_ID, HEAD, RPC](nodes) + case NodeSelectionModeRoundRobin: + return NewRoundRobinSelector[CHAIN_ID, HEAD, RPC](nodes) + case NodeSelectionModeTotalDifficulty: + return NewTotalDifficultyNodeSelector[CHAIN_ID, HEAD, RPC](nodes) + case NodeSelectionModePriorityLevel: + return NewPriorityLevelNodeSelector[CHAIN_ID, HEAD, RPC](nodes) + default: + panic(fmt.Sprintf("unsupported NodeSelectionMode: %s", selectionMode)) + } +} diff --git a/common/client/node_selector_highest_head.go b/common/client/node_selector_highest_head.go new file mode 100644 index 00000000000..99a130004a9 --- /dev/null +++ b/common/client/node_selector_highest_head.go @@ -0,0 +1,41 @@ +package client + +import ( + "math" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +type highestHeadNodeSelector[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +] []Node[CHAIN_ID, HEAD, RPC] + +func NewHighestHeadNodeSelector[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +](nodes []Node[CHAIN_ID, HEAD, RPC]) NodeSelector[CHAIN_ID, HEAD, RPC] { + return highestHeadNodeSelector[CHAIN_ID, HEAD, RPC](nodes) +} + +func (s highestHeadNodeSelector[CHAIN_ID, HEAD, RPC]) Select() Node[CHAIN_ID, HEAD, RPC] { + var highestHeadNumber int64 = math.MinInt64 + var highestHeadNodes []Node[CHAIN_ID, HEAD, RPC] + for _, n := range s { + state, currentHeadNumber, _ := n.StateAndLatest() + if state == nodeStateAlive && currentHeadNumber >= highestHeadNumber { + if highestHeadNumber < currentHeadNumber { + highestHeadNumber = currentHeadNumber + highestHeadNodes = nil + } + highestHeadNodes = append(highestHeadNodes, n) + } + } + return firstOrHighestPriority(highestHeadNodes) +} + +func (s highestHeadNodeSelector[CHAIN_ID, HEAD, RPC]) Name() string { + return NodeSelectionModeHighestHead +} diff --git a/common/client/node_selector_highest_head_test.go b/common/client/node_selector_highest_head_test.go new file mode 100644 index 00000000000..6e47bbedcae --- /dev/null +++ b/common/client/node_selector_highest_head_test.go @@ -0,0 +1,176 @@ +package client + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +func TestHighestHeadNodeSelectorName(t *testing.T) { + selector := newNodeSelector[types.ID, Head, NodeClient[types.ID, Head]](NodeSelectionModeHighestHead, nil) + assert.Equal(t, selector.Name(), NodeSelectionModeHighestHead) +} + +func TestHighestHeadNodeSelector(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + + var nodes []Node[types.ID, Head, nodeClient] + + for i := 0; i < 3; i++ { + node := newMockNode[types.ID, Head, nodeClient](t) + if i == 0 { + // first node is out of sync + node.On("StateAndLatest").Return(nodeStateOutOfSync, int64(-1), nil) + } else if i == 1 { + // second node is alive, LatestReceivedBlockNumber = 1 + node.On("StateAndLatest").Return(nodeStateAlive, int64(1), nil) + } else { + // third node is alive, LatestReceivedBlockNumber = 2 (best node) + node.On("StateAndLatest").Return(nodeStateAlive, int64(2), nil) + } + node.On("Order").Maybe().Return(int32(1)) + nodes = append(nodes, node) + } + + selector := newNodeSelector[types.ID, Head, nodeClient](NodeSelectionModeHighestHead, nodes) + assert.Same(t, nodes[2], selector.Select()) + + t.Run("stick to the same node", func(t *testing.T) { + node := newMockNode[types.ID, Head, nodeClient](t) + // fourth node is alive, LatestReceivedBlockNumber = 2 (same as 3rd) + node.On("StateAndLatest").Return(nodeStateAlive, int64(2), nil) + node.On("Order").Return(int32(1)) + nodes = append(nodes, node) + + selector := newNodeSelector(NodeSelectionModeHighestHead, nodes) + assert.Same(t, nodes[2], selector.Select()) + }) + + t.Run("another best node", func(t *testing.T) { + node := newMockNode[types.ID, Head, nodeClient](t) + // fifth node is alive, LatestReceivedBlockNumber = 3 (better than 3rd and 4th) + node.On("StateAndLatest").Return(nodeStateAlive, int64(3), nil) + node.On("Order").Return(int32(1)) + nodes = append(nodes, node) + + selector := newNodeSelector(NodeSelectionModeHighestHead, nodes) + assert.Same(t, nodes[4], selector.Select()) + }) + + t.Run("nodes never update latest block number", func(t *testing.T) { + node1 := newMockNode[types.ID, Head, nodeClient](t) + node1.On("StateAndLatest").Return(nodeStateAlive, int64(-1), nil) + node1.On("Order").Return(int32(1)) + node2 := newMockNode[types.ID, Head, nodeClient](t) + node2.On("StateAndLatest").Return(nodeStateAlive, int64(-1), nil) + node2.On("Order").Return(int32(1)) + selector := newNodeSelector(NodeSelectionModeHighestHead, []Node[types.ID, Head, nodeClient]{node1, node2}) + assert.Same(t, node1, selector.Select()) + }) +} + +func TestHighestHeadNodeSelector_None(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + var nodes []Node[types.ID, Head, nodeClient] + + for i := 0; i < 3; i++ { + node := newMockNode[types.ID, Head, nodeClient](t) + if i == 0 { + // first node is out of sync + node.On("StateAndLatest").Return(nodeStateOutOfSync, int64(-1), nil) + } else { + // others are unreachable + node.On("StateAndLatest").Return(nodeStateUnreachable, int64(1), nil) + } + nodes = append(nodes, node) + } + + selector := newNodeSelector(NodeSelectionModeHighestHead, nodes) + assert.Nil(t, selector.Select()) +} + +func TestHighestHeadNodeSelectorWithOrder(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + var nodes []Node[types.ID, Head, nodeClient] + + t.Run("same head and order", func(t *testing.T) { + for i := 0; i < 3; i++ { + node := newMockNode[types.ID, Head, nodeClient](t) + node.On("StateAndLatest").Return(nodeStateAlive, int64(1), nil) + node.On("Order").Return(int32(2)) + nodes = append(nodes, node) + } + selector := newNodeSelector(NodeSelectionModeHighestHead, nodes) + //Should select the first node because all things are equal + assert.Same(t, nodes[0], selector.Select()) + }) + + t.Run("same head but different order", func(t *testing.T) { + node1 := newMockNode[types.ID, Head, nodeClient](t) + node1.On("StateAndLatest").Return(nodeStateAlive, int64(3), nil) + node1.On("Order").Return(int32(3)) + + node2 := newMockNode[types.ID, Head, nodeClient](t) + node2.On("StateAndLatest").Return(nodeStateAlive, int64(3), nil) + node2.On("Order").Return(int32(1)) + + node3 := newMockNode[types.ID, Head, nodeClient](t) + node3.On("StateAndLatest").Return(nodeStateAlive, int64(3), nil) + node3.On("Order").Return(int32(2)) + + nodes := []Node[types.ID, Head, nodeClient]{node1, node2, node3} + selector := newNodeSelector(NodeSelectionModeHighestHead, nodes) + //Should select the second node as it has the highest priority + assert.Same(t, nodes[1], selector.Select()) + }) + + t.Run("different head but same order", func(t *testing.T) { + node1 := newMockNode[types.ID, Head, nodeClient](t) + node1.On("StateAndLatest").Return(nodeStateAlive, int64(1), nil) + node1.On("Order").Maybe().Return(int32(3)) + + node2 := newMockNode[types.ID, Head, nodeClient](t) + node2.On("StateAndLatest").Return(nodeStateAlive, int64(2), nil) + node2.On("Order").Maybe().Return(int32(3)) + + node3 := newMockNode[types.ID, Head, nodeClient](t) + node3.On("StateAndLatest").Return(nodeStateAlive, int64(3), nil) + node3.On("Order").Return(int32(3)) + + nodes := []Node[types.ID, Head, nodeClient]{node1, node2, node3} + selector := newNodeSelector(NodeSelectionModeHighestHead, nodes) + //Should select the third node as it has the highest head + assert.Same(t, nodes[2], selector.Select()) + }) + + t.Run("different head and different order", func(t *testing.T) { + node1 := newMockNode[types.ID, Head, nodeClient](t) + node1.On("StateAndLatest").Return(nodeStateAlive, int64(10), nil) + node1.On("Order").Maybe().Return(int32(3)) + + node2 := newMockNode[types.ID, Head, nodeClient](t) + node2.On("StateAndLatest").Return(nodeStateAlive, int64(11), nil) + node2.On("Order").Maybe().Return(int32(4)) + + node3 := newMockNode[types.ID, Head, nodeClient](t) + node3.On("StateAndLatest").Return(nodeStateAlive, int64(11), nil) + node3.On("Order").Maybe().Return(int32(3)) + + node4 := newMockNode[types.ID, Head, nodeClient](t) + node4.On("StateAndLatest").Return(nodeStateAlive, int64(10), nil) + node4.On("Order").Maybe().Return(int32(1)) + + nodes := []Node[types.ID, Head, nodeClient]{node1, node2, node3, node4} + selector := newNodeSelector(NodeSelectionModeHighestHead, nodes) + //Should select the third node as it has the highest head and will win the priority tie-breaker + assert.Same(t, nodes[2], selector.Select()) + }) +} diff --git a/common/client/node_selector_priority_level.go b/common/client/node_selector_priority_level.go new file mode 100644 index 00000000000..45cc62de077 --- /dev/null +++ b/common/client/node_selector_priority_level.go @@ -0,0 +1,129 @@ +package client + +import ( + "math" + "sort" + "sync/atomic" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +type priorityLevelNodeSelector[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +] struct { + nodes []Node[CHAIN_ID, HEAD, RPC] + roundRobinCount []atomic.Uint32 +} + +type nodeWithPriority[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +] struct { + node Node[CHAIN_ID, HEAD, RPC] + priority int32 +} + +func NewPriorityLevelNodeSelector[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +](nodes []Node[CHAIN_ID, HEAD, RPC]) NodeSelector[CHAIN_ID, HEAD, RPC] { + return &priorityLevelNodeSelector[CHAIN_ID, HEAD, RPC]{ + nodes: nodes, + roundRobinCount: make([]atomic.Uint32, nrOfPriorityTiers(nodes)), + } +} + +func (s priorityLevelNodeSelector[CHAIN_ID, HEAD, RPC]) Select() Node[CHAIN_ID, HEAD, RPC] { + nodes := s.getHighestPriorityAliveTier() + + if len(nodes) == 0 { + return nil + } + priorityLevel := nodes[len(nodes)-1].priority + + // NOTE: Inc returns the number after addition, so we must -1 to get the "current" counter + count := s.roundRobinCount[priorityLevel].Add(1) - 1 + idx := int(count % uint32(len(nodes))) + + return nodes[idx].node +} + +func (s priorityLevelNodeSelector[CHAIN_ID, HEAD, RPC]) Name() string { + return NodeSelectionModePriorityLevel +} + +// getHighestPriorityAliveTier filters nodes that are not in state nodeStateAlive and +// returns only the highest tier of alive nodes +func (s priorityLevelNodeSelector[CHAIN_ID, HEAD, RPC]) getHighestPriorityAliveTier() []nodeWithPriority[CHAIN_ID, HEAD, RPC] { + var nodes []nodeWithPriority[CHAIN_ID, HEAD, RPC] + for _, n := range s.nodes { + if n.State() == nodeStateAlive { + nodes = append(nodes, nodeWithPriority[CHAIN_ID, HEAD, RPC]{n, n.Order()}) + } + } + + if len(nodes) == 0 { + return nil + } + + return removeLowerTiers(nodes) +} + +// removeLowerTiers take a slice of nodeWithPriority[CHAIN_ID, BLOCK_HASH, HEAD, RPC] and keeps only the highest tier +func removeLowerTiers[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +](nodes []nodeWithPriority[CHAIN_ID, HEAD, RPC]) []nodeWithPriority[CHAIN_ID, HEAD, RPC] { + sort.SliceStable(nodes, func(i, j int) bool { + return nodes[i].priority > nodes[j].priority + }) + + var nodes2 []nodeWithPriority[CHAIN_ID, HEAD, RPC] + currentPriority := nodes[len(nodes)-1].priority + + for _, n := range nodes { + if n.priority == currentPriority { + nodes2 = append(nodes2, n) + } + } + + return nodes2 +} + +// nrOfPriorityTiers calculates the total number of priority tiers +func nrOfPriorityTiers[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +](nodes []Node[CHAIN_ID, HEAD, RPC]) int32 { + highestPriority := int32(0) + for _, n := range nodes { + priority := n.Order() + if highestPriority < priority { + highestPriority = priority + } + } + return highestPriority + 1 +} + +// firstOrHighestPriority takes a list of nodes and returns the first one with the highest priority +func firstOrHighestPriority[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +](nodes []Node[CHAIN_ID, HEAD, RPC]) Node[CHAIN_ID, HEAD, RPC] { + hp := int32(math.MaxInt32) + var node Node[CHAIN_ID, HEAD, RPC] + for _, n := range nodes { + if n.Order() < hp { + hp = n.Order() + node = n + } + } + return node +} diff --git a/common/client/node_selector_priority_level_test.go b/common/client/node_selector_priority_level_test.go new file mode 100644 index 00000000000..ac84645e91c --- /dev/null +++ b/common/client/node_selector_priority_level_test.go @@ -0,0 +1,88 @@ +package client + +import ( + "testing" + + "github.com/smartcontractkit/chainlink/v2/common/types" + + "github.com/stretchr/testify/assert" +) + +func TestPriorityLevelNodeSelectorName(t *testing.T) { + selector := newNodeSelector[types.ID, Head, NodeClient[types.ID, Head]](NodeSelectionModePriorityLevel, nil) + assert.Equal(t, selector.Name(), NodeSelectionModePriorityLevel) +} + +func TestPriorityLevelNodeSelector(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + var nodes []Node[types.ID, Head, nodeClient] + n1 := newMockNode[types.ID, Head, nodeClient](t) + n1.On("State").Return(nodeStateAlive) + n1.On("Order").Return(int32(1)) + + n2 := newMockNode[types.ID, Head, nodeClient](t) + n2.On("State").Return(nodeStateAlive) + n2.On("Order").Return(int32(1)) + + n3 := newMockNode[types.ID, Head, nodeClient](t) + n3.On("State").Return(nodeStateAlive) + n3.On("Order").Return(int32(1)) + + nodes = append(nodes, n1, n2, n3) + selector := newNodeSelector(NodeSelectionModePriorityLevel, nodes) + assert.Same(t, nodes[0], selector.Select()) + assert.Same(t, nodes[1], selector.Select()) + assert.Same(t, nodes[2], selector.Select()) + assert.Same(t, nodes[0], selector.Select()) + assert.Same(t, nodes[1], selector.Select()) + assert.Same(t, nodes[2], selector.Select()) +} + +func TestPriorityLevelNodeSelector_None(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + var nodes []Node[types.ID, Head, nodeClient] + + for i := 0; i < 3; i++ { + node := newMockNode[types.ID, Head, nodeClient](t) + if i == 0 { + // first node is out of sync + node.On("State").Return(nodeStateOutOfSync) + node.On("Order").Return(int32(1)) + } else { + // others are unreachable + node.On("State").Return(nodeStateUnreachable) + node.On("Order").Return(int32(1)) + } + nodes = append(nodes, node) + } + + selector := newNodeSelector(NodeSelectionModePriorityLevel, nodes) + assert.Nil(t, selector.Select()) +} + +func TestPriorityLevelNodeSelector_DifferentOrder(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + var nodes []Node[types.ID, Head, nodeClient] + n1 := newMockNode[types.ID, Head, nodeClient](t) + n1.On("State").Return(nodeStateAlive) + n1.On("Order").Return(int32(1)) + + n2 := newMockNode[types.ID, Head, nodeClient](t) + n2.On("State").Return(nodeStateAlive) + n2.On("Order").Return(int32(2)) + + n3 := newMockNode[types.ID, Head, nodeClient](t) + n3.On("State").Return(nodeStateAlive) + n3.On("Order").Return(int32(3)) + + nodes = append(nodes, n1, n2, n3) + selector := newNodeSelector(NodeSelectionModePriorityLevel, nodes) + assert.Same(t, nodes[0], selector.Select()) + assert.Same(t, nodes[0], selector.Select()) +} diff --git a/common/client/node_selector_round_robin.go b/common/client/node_selector_round_robin.go new file mode 100644 index 00000000000..5cdad7f52ee --- /dev/null +++ b/common/client/node_selector_round_robin.go @@ -0,0 +1,50 @@ +package client + +import ( + "sync/atomic" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +type roundRobinSelector[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +] struct { + nodes []Node[CHAIN_ID, HEAD, RPC] + roundRobinCount atomic.Uint32 +} + +func NewRoundRobinSelector[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +](nodes []Node[CHAIN_ID, HEAD, RPC]) NodeSelector[CHAIN_ID, HEAD, RPC] { + return &roundRobinSelector[CHAIN_ID, HEAD, RPC]{ + nodes: nodes, + } +} + +func (s *roundRobinSelector[CHAIN_ID, HEAD, RPC]) Select() Node[CHAIN_ID, HEAD, RPC] { + var liveNodes []Node[CHAIN_ID, HEAD, RPC] + for _, n := range s.nodes { + if n.State() == nodeStateAlive { + liveNodes = append(liveNodes, n) + } + } + + nNodes := len(liveNodes) + if nNodes == 0 { + return nil + } + + // NOTE: Inc returns the number after addition, so we must -1 to get the "current" counter + count := s.roundRobinCount.Add(1) - 1 + idx := int(count % uint32(nNodes)) + + return liveNodes[idx] +} + +func (s *roundRobinSelector[CHAIN_ID, HEAD, RPC]) Name() string { + return NodeSelectionModeRoundRobin +} diff --git a/common/client/node_selector_round_robin_test.go b/common/client/node_selector_round_robin_test.go new file mode 100644 index 00000000000..e5078d858f1 --- /dev/null +++ b/common/client/node_selector_round_robin_test.go @@ -0,0 +1,61 @@ +package client + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +func TestRoundRobinNodeSelectorName(t *testing.T) { + selector := newNodeSelector[types.ID, Head, NodeClient[types.ID, Head]](NodeSelectionModeRoundRobin, nil) + assert.Equal(t, selector.Name(), NodeSelectionModeRoundRobin) +} + +func TestRoundRobinNodeSelector(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + var nodes []Node[types.ID, Head, nodeClient] + + for i := 0; i < 3; i++ { + node := newMockNode[types.ID, Head, nodeClient](t) + if i == 0 { + // first node is out of sync + node.On("State").Return(nodeStateOutOfSync) + } else { + // second & third nodes are alive + node.On("State").Return(nodeStateAlive) + } + nodes = append(nodes, node) + } + + selector := newNodeSelector(NodeSelectionModeRoundRobin, nodes) + assert.Same(t, nodes[1], selector.Select()) + assert.Same(t, nodes[2], selector.Select()) + assert.Same(t, nodes[1], selector.Select()) + assert.Same(t, nodes[2], selector.Select()) +} + +func TestRoundRobinNodeSelector_None(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + var nodes []Node[types.ID, Head, nodeClient] + + for i := 0; i < 3; i++ { + node := newMockNode[types.ID, Head, nodeClient](t) + if i == 0 { + // first node is out of sync + node.On("State").Return(nodeStateOutOfSync) + } else { + // others are unreachable + node.On("State").Return(nodeStateUnreachable) + } + nodes = append(nodes, node) + } + + selector := newNodeSelector(NodeSelectionModeRoundRobin, nodes) + assert.Nil(t, selector.Select()) +} diff --git a/common/client/node_selector_test.go b/common/client/node_selector_test.go new file mode 100644 index 00000000000..226cb67168d --- /dev/null +++ b/common/client/node_selector_test.go @@ -0,0 +1,18 @@ +package client + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +func TestNodeSelector(t *testing.T) { + // rest of the tests are located in specific node selectors tests + t.Run("panics on unknown type", func(t *testing.T) { + assert.Panics(t, func() { + _ = newNodeSelector[types.ID, Head, NodeClient[types.ID, Head]]("unknown", nil) + }) + }) +} diff --git a/common/client/node_selector_total_difficulty.go b/common/client/node_selector_total_difficulty.go new file mode 100644 index 00000000000..35491503bcc --- /dev/null +++ b/common/client/node_selector_total_difficulty.go @@ -0,0 +1,54 @@ +package client + +import ( + "math/big" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +type totalDifficultyNodeSelector[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +] []Node[CHAIN_ID, HEAD, RPC] + +func NewTotalDifficultyNodeSelector[ + CHAIN_ID types.ID, + HEAD Head, + RPC NodeClient[CHAIN_ID, HEAD], +](nodes []Node[CHAIN_ID, HEAD, RPC]) NodeSelector[CHAIN_ID, HEAD, RPC] { + return totalDifficultyNodeSelector[CHAIN_ID, HEAD, RPC](nodes) +} + +func (s totalDifficultyNodeSelector[CHAIN_ID, HEAD, RPC]) Select() Node[CHAIN_ID, HEAD, RPC] { + // NodeNoNewHeadsThreshold may not be enabled, in this case all nodes have td == nil + var highestTD *big.Int + var nodes []Node[CHAIN_ID, HEAD, RPC] + var aliveNodes []Node[CHAIN_ID, HEAD, RPC] + + for _, n := range s { + state, _, currentTD := n.StateAndLatest() + if state != nodeStateAlive { + continue + } + + aliveNodes = append(aliveNodes, n) + if currentTD != nil && (highestTD == nil || currentTD.Cmp(highestTD) >= 0) { + if highestTD == nil || currentTD.Cmp(highestTD) > 0 { + highestTD = currentTD + nodes = nil + } + nodes = append(nodes, n) + } + } + + //If all nodes have td == nil pick one from the nodes that are alive + if len(nodes) == 0 { + return firstOrHighestPriority(aliveNodes) + } + return firstOrHighestPriority(nodes) +} + +func (s totalDifficultyNodeSelector[CHAIN_ID, HEAD, RPC]) Name() string { + return NodeSelectionModeTotalDifficulty +} diff --git a/common/client/node_selector_total_difficulty_test.go b/common/client/node_selector_total_difficulty_test.go new file mode 100644 index 00000000000..5c43cdd8472 --- /dev/null +++ b/common/client/node_selector_total_difficulty_test.go @@ -0,0 +1,178 @@ +package client + +import ( + big "math/big" + "testing" + + "github.com/smartcontractkit/chainlink/v2/common/types" + + "github.com/stretchr/testify/assert" +) + +func TestTotalDifficultyNodeSelectorName(t *testing.T) { + selector := newNodeSelector[types.ID, Head, NodeClient[types.ID, Head]](NodeSelectionModeTotalDifficulty, nil) + assert.Equal(t, selector.Name(), NodeSelectionModeTotalDifficulty) +} + +func TestTotalDifficultyNodeSelector(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + var nodes []Node[types.ID, Head, nodeClient] + + for i := 0; i < 3; i++ { + node := newMockNode[types.ID, Head, nodeClient](t) + if i == 0 { + // first node is out of sync + node.On("StateAndLatest").Return(nodeStateOutOfSync, int64(-1), nil) + } else if i == 1 { + // second node is alive + node.On("StateAndLatest").Return(nodeStateAlive, int64(1), big.NewInt(7)) + } else { + // third node is alive and best + node.On("StateAndLatest").Return(nodeStateAlive, int64(2), big.NewInt(8)) + } + node.On("Order").Maybe().Return(int32(1)) + nodes = append(nodes, node) + } + + selector := newNodeSelector(NodeSelectionModeTotalDifficulty, nodes) + assert.Same(t, nodes[2], selector.Select()) + + t.Run("stick to the same node", func(t *testing.T) { + node := newMockNode[types.ID, Head, nodeClient](t) + // fourth node is alive (same as 3rd) + node.On("StateAndLatest").Return(nodeStateAlive, int64(2), big.NewInt(8)) + node.On("Order").Maybe().Return(int32(1)) + nodes = append(nodes, node) + + selector := newNodeSelector(NodeSelectionModeTotalDifficulty, nodes) + assert.Same(t, nodes[2], selector.Select()) + }) + + t.Run("another best node", func(t *testing.T) { + node := newMockNode[types.ID, Head, nodeClient](t) + // fifth node is alive (better than 3rd and 4th) + node.On("StateAndLatest").Return(nodeStateAlive, int64(3), big.NewInt(11)) + node.On("Order").Maybe().Return(int32(1)) + nodes = append(nodes, node) + + selector := newNodeSelector(NodeSelectionModeTotalDifficulty, nodes) + assert.Same(t, nodes[4], selector.Select()) + }) + + t.Run("nodes never update latest block number", func(t *testing.T) { + node1 := newMockNode[types.ID, Head, nodeClient](t) + node1.On("StateAndLatest").Return(nodeStateAlive, int64(-1), nil) + node1.On("Order").Maybe().Return(int32(1)) + node2 := newMockNode[types.ID, Head, nodeClient](t) + node2.On("StateAndLatest").Return(nodeStateAlive, int64(-1), nil) + node2.On("Order").Maybe().Return(int32(1)) + nodes := []Node[types.ID, Head, nodeClient]{node1, node2} + + selector := newNodeSelector(NodeSelectionModeTotalDifficulty, nodes) + assert.Same(t, node1, selector.Select()) + }) +} + +func TestTotalDifficultyNodeSelector_None(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + var nodes []Node[types.ID, Head, nodeClient] + + for i := 0; i < 3; i++ { + node := newMockNode[types.ID, Head, nodeClient](t) + if i == 0 { + // first node is out of sync + node.On("StateAndLatest").Return(nodeStateOutOfSync, int64(-1), nil) + } else { + // others are unreachable + node.On("StateAndLatest").Return(nodeStateUnreachable, int64(1), big.NewInt(7)) + } + nodes = append(nodes, node) + } + + selector := newNodeSelector(NodeSelectionModeTotalDifficulty, nodes) + assert.Nil(t, selector.Select()) +} + +func TestTotalDifficultyNodeSelectorWithOrder(t *testing.T) { + t.Parallel() + + type nodeClient NodeClient[types.ID, Head] + var nodes []Node[types.ID, Head, nodeClient] + + t.Run("same td and order", func(t *testing.T) { + for i := 0; i < 3; i++ { + node := newMockNode[types.ID, Head, nodeClient](t) + node.On("StateAndLatest").Return(nodeStateAlive, int64(1), big.NewInt(10)) + node.On("Order").Return(int32(2)) + nodes = append(nodes, node) + } + selector := newNodeSelector(NodeSelectionModeTotalDifficulty, nodes) + //Should select the first node because all things are equal + assert.Same(t, nodes[0], selector.Select()) + }) + + t.Run("same td but different order", func(t *testing.T) { + node1 := newMockNode[types.ID, Head, nodeClient](t) + node1.On("StateAndLatest").Return(nodeStateAlive, int64(3), big.NewInt(10)) + node1.On("Order").Return(int32(3)) + + node2 := newMockNode[types.ID, Head, nodeClient](t) + node2.On("StateAndLatest").Return(nodeStateAlive, int64(3), big.NewInt(10)) + node2.On("Order").Return(int32(1)) + + node3 := newMockNode[types.ID, Head, nodeClient](t) + node3.On("StateAndLatest").Return(nodeStateAlive, int64(3), big.NewInt(10)) + node3.On("Order").Return(int32(2)) + + nodes := []Node[types.ID, Head, nodeClient]{node1, node2, node3} + selector := newNodeSelector(NodeSelectionModeTotalDifficulty, nodes) + //Should select the second node as it has the highest priority + assert.Same(t, nodes[1], selector.Select()) + }) + + t.Run("different td but same order", func(t *testing.T) { + node1 := newMockNode[types.ID, Head, nodeClient](t) + node1.On("StateAndLatest").Return(nodeStateAlive, int64(1), big.NewInt(10)) + node1.On("Order").Maybe().Return(int32(3)) + + node2 := newMockNode[types.ID, Head, nodeClient](t) + node2.On("StateAndLatest").Return(nodeStateAlive, int64(1), big.NewInt(11)) + node2.On("Order").Maybe().Return(int32(3)) + + node3 := newMockNode[types.ID, Head, nodeClient](t) + node3.On("StateAndLatest").Return(nodeStateAlive, int64(1), big.NewInt(12)) + node3.On("Order").Return(int32(3)) + + nodes := []Node[types.ID, Head, nodeClient]{node1, node2, node3} + selector := newNodeSelector(NodeSelectionModeTotalDifficulty, nodes) + //Should select the third node as it has the highest td + assert.Same(t, nodes[2], selector.Select()) + }) + + t.Run("different head and different order", func(t *testing.T) { + node1 := newMockNode[types.ID, Head, nodeClient](t) + node1.On("StateAndLatest").Return(nodeStateAlive, int64(1), big.NewInt(100)) + node1.On("Order").Maybe().Return(int32(4)) + + node2 := newMockNode[types.ID, Head, nodeClient](t) + node2.On("StateAndLatest").Return(nodeStateAlive, int64(1), big.NewInt(110)) + node2.On("Order").Maybe().Return(int32(5)) + + node3 := newMockNode[types.ID, Head, nodeClient](t) + node3.On("StateAndLatest").Return(nodeStateAlive, int64(1), big.NewInt(110)) + node3.On("Order").Maybe().Return(int32(1)) + + node4 := newMockNode[types.ID, Head, nodeClient](t) + node4.On("StateAndLatest").Return(nodeStateAlive, int64(1), big.NewInt(105)) + node4.On("Order").Maybe().Return(int32(2)) + + nodes := []Node[types.ID, Head, nodeClient]{node1, node2, node3, node4} + selector := newNodeSelector(NodeSelectionModeTotalDifficulty, nodes) + //Should select the third node as it has the highest td and will win the priority tie-breaker + assert.Same(t, nodes[2], selector.Select()) + }) +} diff --git a/common/client/node_test.go b/common/client/node_test.go new file mode 100644 index 00000000000..7b6a38e3951 --- /dev/null +++ b/common/client/node_test.go @@ -0,0 +1,80 @@ +package client + +import ( + "net/url" + "testing" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +type testNodeConfig struct { + pollFailureThreshold uint32 + pollInterval time.Duration + selectionMode string + syncThreshold uint32 +} + +func (n testNodeConfig) PollFailureThreshold() uint32 { + return n.pollFailureThreshold +} + +func (n testNodeConfig) PollInterval() time.Duration { + return n.pollInterval +} + +func (n testNodeConfig) SelectionMode() string { + return n.selectionMode +} + +func (n testNodeConfig) SyncThreshold() uint32 { + return n.syncThreshold +} + +type testNode struct { + *node[types.ID, Head, NodeClient[types.ID, Head]] +} + +type testNodeOpts struct { + config testNodeConfig + noNewHeadsThreshold time.Duration + lggr logger.Logger + wsuri url.URL + httpuri *url.URL + name string + id int32 + chainID types.ID + nodeOrder int32 + rpc *mockNodeClient[types.ID, Head] + chainFamily string +} + +func newTestNode(t *testing.T, opts testNodeOpts) testNode { + if opts.lggr == nil { + opts.lggr = logger.Test(t) + } + + if opts.name == "" { + opts.name = "tes node" + } + + if opts.chainFamily == "" { + opts.chainFamily = "test node chain family" + } + + if opts.chainID == nil { + opts.chainID = types.RandomID() + } + + if opts.id == 0 { + opts.id = 42 + } + + nodeI := NewNode[types.ID, Head, NodeClient[types.ID, Head]](opts.config, opts.noNewHeadsThreshold, opts.lggr, + opts.wsuri, opts.httpuri, opts.name, opts.id, opts.chainID, opts.nodeOrder, opts.rpc, opts.chainFamily) + + return testNode{ + nodeI.(*node[types.ID, Head, NodeClient[types.ID, Head]]), + } +} diff --git a/common/client/send_only_node.go b/common/client/send_only_node.go new file mode 100644 index 00000000000..b63e93b703d --- /dev/null +++ b/common/client/send_only_node.go @@ -0,0 +1,186 @@ +package client + +import ( + "context" + "fmt" + "net/url" + "sync" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +//go:generate mockery --quiet --name sendOnlyClient --structname mockSendOnlyClient --filename "mock_send_only_client_test.go" --inpackage --case=underscore +type sendOnlyClient[ + CHAIN_ID types.ID, +] interface { + Close() + ChainID(context.Context) (CHAIN_ID, error) + DialHTTP() error +} + +// SendOnlyNode represents one node used as a sendonly +// +//go:generate mockery --quiet --name SendOnlyNode --structname mockSendOnlyNode --filename "mock_send_only_node_test.go" --inpackage --case=underscore +type SendOnlyNode[ + CHAIN_ID types.ID, + RPC sendOnlyClient[CHAIN_ID], +] interface { + // Start may attempt to connect to the node, but should only return error for misconfiguration - never for temporary errors. + Start(context.Context) error + Close() error + + ConfiguredChainID() CHAIN_ID + RPC() RPC + + String() string + // State returns nodeState + State() nodeState + // Name is a unique identifier for this node. + Name() string +} + +// It only supports sending transactions +// It must use an http(s) url +type sendOnlyNode[ + CHAIN_ID types.ID, + RPC sendOnlyClient[CHAIN_ID], +] struct { + services.StateMachine + + stateMu sync.RWMutex // protects state* fields + state nodeState + + rpc RPC + uri url.URL + log logger.Logger + name string + chainID CHAIN_ID + chStop services.StopChan + wg sync.WaitGroup +} + +// NewSendOnlyNode returns a new sendonly node +func NewSendOnlyNode[ + CHAIN_ID types.ID, + RPC sendOnlyClient[CHAIN_ID], +]( + lggr logger.Logger, + httpuri url.URL, + name string, + chainID CHAIN_ID, + rpc RPC, +) SendOnlyNode[CHAIN_ID, RPC] { + s := new(sendOnlyNode[CHAIN_ID, RPC]) + s.name = name + s.log = logger.Named(logger.Named(lggr, "SendOnlyNode"), name) + s.log = logger.With(s.log, + "nodeTier", "sendonly", + ) + s.rpc = rpc + s.uri = httpuri + s.chainID = chainID + s.chStop = make(chan struct{}) + return s +} + +func (s *sendOnlyNode[CHAIN_ID, RPC]) Start(ctx context.Context) error { + return s.StartOnce(s.name, func() error { + s.start(ctx) + return nil + }) +} + +// Start setups up and verifies the sendonly node +// Should only be called once in a node's lifecycle +func (s *sendOnlyNode[CHAIN_ID, RPC]) start(startCtx context.Context) { + if s.State() != nodeStateUndialed { + panic(fmt.Sprintf("cannot dial node with state %v", s.state)) + } + + err := s.rpc.DialHTTP() + if err != nil { + promPoolRPCNodeTransitionsToUnusable.WithLabelValues(s.chainID.String(), s.name).Inc() + s.log.Errorw("Dial failed: SendOnly Node is unusable", "err", err) + s.setState(nodeStateUnusable) + return + } + s.setState(nodeStateDialed) + + if s.chainID.String() == "0" { + // Skip verification if chainID is zero + s.log.Warn("sendonly rpc ChainID verification skipped") + } else { + chainID, err := s.rpc.ChainID(startCtx) + if err != nil || chainID.String() != s.chainID.String() { + promPoolRPCNodeTransitionsToUnreachable.WithLabelValues(s.chainID.String(), s.name).Inc() + if err != nil { + promPoolRPCNodeTransitionsToUnreachable.WithLabelValues(s.chainID.String(), s.name).Inc() + s.log.Errorw(fmt.Sprintf("Verify failed: %v", err), "err", err) + s.setState(nodeStateUnreachable) + } else { + promPoolRPCNodeTransitionsToInvalidChainID.WithLabelValues(s.chainID.String(), s.name).Inc() + s.log.Errorf( + "sendonly rpc ChainID doesn't match local chain ID: RPC ID=%s, local ID=%s, node name=%s", + chainID.String(), + s.chainID.String(), + s.name, + ) + s.setState(nodeStateInvalidChainID) + } + // Since it has failed, spin up the verifyLoop that will keep + // retrying until success + s.wg.Add(1) + go s.verifyLoop() + return + } + } + + promPoolRPCNodeTransitionsToAlive.WithLabelValues(s.chainID.String(), s.name).Inc() + s.setState(nodeStateAlive) + s.log.Infow("Sendonly RPC Node is online", "nodeState", s.state) +} + +func (s *sendOnlyNode[CHAIN_ID, RPC]) Close() error { + return s.StopOnce(s.name, func() error { + s.rpc.Close() + close(s.chStop) + s.wg.Wait() + s.setState(nodeStateClosed) + return nil + }) +} + +func (s *sendOnlyNode[CHAIN_ID, RPC]) ConfiguredChainID() CHAIN_ID { + return s.chainID +} + +func (s *sendOnlyNode[CHAIN_ID, RPC]) RPC() RPC { + return s.rpc +} + +func (s *sendOnlyNode[CHAIN_ID, RPC]) String() string { + return fmt.Sprintf("(%s)%s:%s", Secondary.String(), s.name, s.uri.Redacted()) +} + +func (s *sendOnlyNode[CHAIN_ID, RPC]) setState(state nodeState) (changed bool) { + s.stateMu.Lock() + defer s.stateMu.Unlock() + if s.state == state { + return false + } + s.state = state + return true +} + +func (s *sendOnlyNode[CHAIN_ID, RPC]) State() nodeState { + s.stateMu.RLock() + defer s.stateMu.RUnlock() + return s.state +} + +func (s *sendOnlyNode[CHAIN_ID, RPC]) Name() string { + return s.name +} diff --git a/common/client/send_only_node_lifecycle.go b/common/client/send_only_node_lifecycle.go new file mode 100644 index 00000000000..4d5b102b5bd --- /dev/null +++ b/common/client/send_only_node_lifecycle.go @@ -0,0 +1,67 @@ +package client + +import ( + "fmt" + "time" + + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +// verifyLoop may only be triggered once, on Start, if initial chain ID check +// fails. +// +// It will continue checking until success and then exit permanently. +func (s *sendOnlyNode[CHAIN_ID, RPC]) verifyLoop() { + defer s.wg.Done() + ctx, cancel := s.chStop.NewCtx() + defer cancel() + + backoff := utils.NewRedialBackoff() + for { + select { + case <-ctx.Done(): + return + case <-time.After(backoff.Duration()): + } + chainID, err := s.rpc.ChainID(ctx) + if err != nil { + ok := s.IfStarted(func() { + if changed := s.setState(nodeStateUnreachable); changed { + promPoolRPCNodeTransitionsToUnreachable.WithLabelValues(s.chainID.String(), s.name).Inc() + } + }) + if !ok { + return + } + s.log.Errorw(fmt.Sprintf("Verify failed: %v", err), "err", err) + continue + } else if chainID.String() != s.chainID.String() { + ok := s.IfStarted(func() { + if changed := s.setState(nodeStateInvalidChainID); changed { + promPoolRPCNodeTransitionsToInvalidChainID.WithLabelValues(s.chainID.String(), s.name).Inc() + } + }) + if !ok { + return + } + s.log.Errorf( + "sendonly rpc ChainID doesn't match local chain ID: RPC ID=%s, local ID=%s, node name=%s", + chainID.String(), + s.chainID.String(), + s.name, + ) + + continue + } + ok := s.IfStarted(func() { + if changed := s.setState(nodeStateAlive); changed { + promPoolRPCNodeTransitionsToAlive.WithLabelValues(s.chainID.String(), s.name).Inc() + } + }) + if !ok { + return + } + s.log.Infow("Sendonly RPC Node is online", "nodeState", s.state) + return + } +} diff --git a/common/client/send_only_node_test.go b/common/client/send_only_node_test.go new file mode 100644 index 00000000000..459f923cba8 --- /dev/null +++ b/common/client/send_only_node_test.go @@ -0,0 +1,139 @@ +package client + +import ( + "fmt" + "net/url" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +func TestNewSendOnlyNode(t *testing.T) { + t.Parallel() + + urlFormat := "http://user:%s@testurl.com" + password := "pass" + u, err := url.Parse(fmt.Sprintf(urlFormat, password)) + require.NoError(t, err) + redacted := fmt.Sprintf(urlFormat, "xxxxx") + lggr := logger.Test(t) + name := "TestNewSendOnlyNode" + chainID := types.RandomID() + client := newMockSendOnlyClient[types.ID](t) + + node := NewSendOnlyNode(lggr, *u, name, chainID, client) + assert.NotNil(t, node) + + // Must contain name & url with redacted password + assert.Contains(t, node.String(), fmt.Sprintf("%s:%s", name, redacted)) + assert.Equal(t, node.ConfiguredChainID(), chainID) +} + +func TestStartSendOnlyNode(t *testing.T) { + t.Parallel() + t.Run("becomes unusable if initial dial fails", func(t *testing.T) { + t.Parallel() + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + client := newMockSendOnlyClient[types.ID](t) + client.On("Close").Once() + expectedError := errors.New("some http error") + client.On("DialHTTP").Return(expectedError).Once() + s := NewSendOnlyNode(lggr, url.URL{}, t.Name(), types.RandomID(), client) + + defer func() { assert.NoError(t, s.Close()) }() + err := s.Start(tests.Context(t)) + require.NoError(t, err) + + assert.Equal(t, nodeStateUnusable, s.State()) + tests.RequireLogMessage(t, observedLogs, "Dial failed: SendOnly Node is unusable") + }) + t.Run("Default ChainID(0) produces warn and skips checks", func(t *testing.T) { + t.Parallel() + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + client := newMockSendOnlyClient[types.ID](t) + client.On("Close").Once() + client.On("DialHTTP").Return(nil).Once() + s := NewSendOnlyNode(lggr, url.URL{}, t.Name(), types.NewIDFromInt(0), client) + + defer func() { assert.NoError(t, s.Close()) }() + err := s.Start(tests.Context(t)) + require.NoError(t, err) + + assert.Equal(t, nodeStateAlive, s.State()) + tests.RequireLogMessage(t, observedLogs, "sendonly rpc ChainID verification skipped") + }) + t.Run("Can recover from chainID verification failure", func(t *testing.T) { + t.Parallel() + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + client := newMockSendOnlyClient[types.ID](t) + client.On("Close").Once() + client.On("DialHTTP").Return(nil) + expectedError := errors.New("failed to get chain ID") + chainID := types.RandomID() + const failuresCount = 2 + client.On("ChainID", mock.Anything).Return(types.RandomID(), expectedError).Times(failuresCount) + client.On("ChainID", mock.Anything).Return(chainID, nil) + + s := NewSendOnlyNode(lggr, url.URL{}, t.Name(), chainID, client) + + defer func() { assert.NoError(t, s.Close()) }() + err := s.Start(tests.Context(t)) + require.NoError(t, err) + + assert.Equal(t, nodeStateUnreachable, s.State()) + tests.AssertLogCountEventually(t, observedLogs, fmt.Sprintf("Verify failed: %v", expectedError), failuresCount) + tests.AssertEventually(t, func() bool { + return s.State() == nodeStateAlive + }) + }) + t.Run("Can recover from chainID mismatch", func(t *testing.T) { + t.Parallel() + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + client := newMockSendOnlyClient[types.ID](t) + client.On("Close").Once() + client.On("DialHTTP").Return(nil).Once() + configuredChainID := types.NewIDFromInt(11) + rpcChainID := types.NewIDFromInt(20) + const failuresCount = 2 + client.On("ChainID", mock.Anything).Return(rpcChainID, nil).Times(failuresCount) + client.On("ChainID", mock.Anything).Return(configuredChainID, nil) + s := NewSendOnlyNode(lggr, url.URL{}, t.Name(), configuredChainID, client) + + defer func() { assert.NoError(t, s.Close()) }() + err := s.Start(tests.Context(t)) + require.NoError(t, err) + + assert.Equal(t, nodeStateInvalidChainID, s.State()) + tests.AssertLogCountEventually(t, observedLogs, "sendonly rpc ChainID doesn't match local chain ID", failuresCount) + tests.AssertEventually(t, func() bool { + return s.State() == nodeStateAlive + }) + }) + t.Run("Start with Random ChainID", func(t *testing.T) { + t.Parallel() + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + client := newMockSendOnlyClient[types.ID](t) + client.On("Close").Once() + client.On("DialHTTP").Return(nil).Once() + configuredChainID := types.RandomID() + client.On("ChainID", mock.Anything).Return(configuredChainID, nil) + s := NewSendOnlyNode(lggr, url.URL{}, t.Name(), configuredChainID, client) + + defer func() { assert.NoError(t, s.Close()) }() + err := s.Start(tests.Context(t)) + assert.NoError(t, err) + tests.AssertEventually(t, func() bool { + return s.State() == nodeStateAlive + }) + assert.Equal(t, 0, observedLogs.Len()) // No warnings expected + }) +} diff --git a/common/client/types.go b/common/client/types.go new file mode 100644 index 00000000000..32d4da98b50 --- /dev/null +++ b/common/client/types.go @@ -0,0 +1,138 @@ +package client + +import ( + "context" + "math/big" + + "github.com/smartcontractkit/chainlink-common/pkg/assets" + feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +// RPC includes all the necessary methods for a multi-node client to interact directly with any RPC endpoint. +// +//go:generate mockery --quiet --name RPC --structname mockRPC --inpackage --filename "mock_rpc_test.go" --case=underscore +type RPC[ + CHAIN_ID types.ID, + SEQ types.Sequence, + ADDR types.Hashable, + BLOCK_HASH types.Hashable, + TX any, + TX_HASH types.Hashable, + EVENT any, + EVENT_OPS any, + TX_RECEIPT types.Receipt[TX_HASH, BLOCK_HASH], + FEE feetypes.Fee, + HEAD types.Head[BLOCK_HASH], + +] interface { + NodeClient[ + CHAIN_ID, + HEAD, + ] + clientAPI[ + CHAIN_ID, + SEQ, + ADDR, + BLOCK_HASH, + TX, + TX_HASH, + EVENT, + EVENT_OPS, + TX_RECEIPT, + FEE, + HEAD, + ] +} + +// Head is the interface required by the NodeClient +// +//go:generate mockery --quiet --name Head --structname mockHead --filename "mock_head_test.go" --inpackage --case=underscore +type Head interface { + BlockNumber() int64 + BlockDifficulty() *big.Int +} + +// NodeClient includes all the necessary RPC methods required by a node. +// +//go:generate mockery --quiet --name NodeClient --structname mockNodeClient --filename "mock_node_client_test.go" --inpackage --case=underscore +type NodeClient[ + CHAIN_ID types.ID, + HEAD Head, +] interface { + connection[CHAIN_ID, HEAD] + + DialHTTP() error + DisconnectAll() + Close() + ClientVersion(context.Context) (string, error) + SubscribersCount() int32 + SetAliveLoopSub(types.Subscription) + UnsubscribeAllExceptAliveLoop() +} + +// clientAPI includes all the direct RPC methods required by the generalized common client to implement its own. +type clientAPI[ + CHAIN_ID types.ID, + SEQ types.Sequence, + ADDR types.Hashable, + BLOCK_HASH types.Hashable, + TX any, + TX_HASH types.Hashable, + EVENT any, + EVENT_OPS any, // event filter query options + TX_RECEIPT types.Receipt[TX_HASH, BLOCK_HASH], + FEE feetypes.Fee, + HEAD types.Head[BLOCK_HASH], +] interface { + connection[CHAIN_ID, HEAD] + + // Account + BalanceAt(ctx context.Context, accountAddress ADDR, blockNumber *big.Int) (*big.Int, error) + TokenBalance(ctx context.Context, accountAddress ADDR, tokenAddress ADDR) (*big.Int, error) + SequenceAt(ctx context.Context, accountAddress ADDR, blockNumber *big.Int) (SEQ, error) + LINKBalance(ctx context.Context, accountAddress ADDR, linkAddress ADDR) (*assets.Link, error) + PendingSequenceAt(ctx context.Context, addr ADDR) (SEQ, error) + EstimateGas(ctx context.Context, call any) (gas uint64, err error) + + // Transactions + SendTransaction(ctx context.Context, tx TX) error + SimulateTransaction(ctx context.Context, tx TX) error + TransactionByHash(ctx context.Context, txHash TX_HASH) (TX, error) + TransactionReceipt(ctx context.Context, txHash TX_HASH) (TX_RECEIPT, error) + SendEmptyTransaction( + ctx context.Context, + newTxAttempt func(seq SEQ, feeLimit uint32, fee FEE, fromAddress ADDR) (attempt any, err error), + seq SEQ, + gasLimit uint32, + fee FEE, + fromAddress ADDR, + ) (txhash string, err error) + + // Blocks + BlockByNumber(ctx context.Context, number *big.Int) (HEAD, error) + BlockByHash(ctx context.Context, hash BLOCK_HASH) (HEAD, error) + LatestBlockHeight(context.Context) (*big.Int, error) + + // Events + FilterEvents(ctx context.Context, query EVENT_OPS) ([]EVENT, error) + + // Misc + BatchCallContext(ctx context.Context, b []any) error + CallContract( + ctx context.Context, + msg interface{}, + blockNumber *big.Int, + ) (rpcErr []byte, extractErr error) + CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error + CodeAt(ctx context.Context, account ADDR, blockNumber *big.Int) ([]byte, error) +} + +type connection[ + CHAIN_ID types.ID, + HEAD Head, +] interface { + ChainID(ctx context.Context) (CHAIN_ID, error) + Dial(ctx context.Context) error + Subscribe(ctx context.Context, channel chan<- HEAD, args ...interface{}) (types.Subscription, error) +} diff --git a/core/config/chaintype.go b/common/config/chaintype.go similarity index 82% rename from core/config/chaintype.go rename to common/config/chaintype.go index fe67b0925aa..21fb8cd297d 100644 --- a/core/config/chaintype.go +++ b/common/config/chaintype.go @@ -15,16 +15,19 @@ const ( ChainOptimismBedrock ChainType = "optimismBedrock" ChainXDai ChainType = "xdai" ChainCelo ChainType = "celo" + ChainWeMix ChainType = "wemix" + ChainKroma ChainType = "kroma" + ChainZkSync ChainType = "zksync" ) var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Join([]string{ string(ChainArbitrum), string(ChainMetis), string(ChainXDai), string(ChainOptimismBedrock), string(ChainCelo), -}, ", ")) + string(ChainKroma), string(ChainWeMix), string(ChainZkSync)}, ", ")) // IsValid returns true if the ChainType value is known or empty. func (c ChainType) IsValid() bool { switch c { - case "", ChainArbitrum, ChainMetis, ChainOptimismBedrock, ChainXDai, ChainCelo: + case "", ChainArbitrum, ChainMetis, ChainOptimismBedrock, ChainXDai, ChainCelo, ChainKroma, ChainWeMix, ChainZkSync: return true } return false diff --git a/common/fee/models.go b/common/fee/models.go index f980c73fc1b..b843cc3f055 100644 --- a/common/fee/models.go +++ b/common/fee/models.go @@ -5,9 +5,9 @@ import ( "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" "github.com/smartcontractkit/chainlink/v2/common/chains/label" - "github.com/smartcontractkit/chainlink/v2/core/logger" - bigmath "github.com/smartcontractkit/chainlink/v2/core/utils/big_math" ) var ( diff --git a/common/headtracker/head_broadcaster.go b/common/headtracker/head_broadcaster.go index 17d50ef5628..0e676f864fa 100644 --- a/common/headtracker/head_broadcaster.go +++ b/common/headtracker/head_broadcaster.go @@ -7,8 +7,10 @@ import ( "sync" "time" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -25,13 +27,13 @@ func (set callbackSet[H, BLOCK_HASH]) values() []types.HeadTrackable[H, BLOCK_HA } type HeadBroadcaster[H types.Head[BLOCK_HASH], BLOCK_HASH types.Hashable] struct { - logger logger.Logger - callbacks callbackSet[H, BLOCK_HASH] - mailbox *utils.Mailbox[H] - mutex *sync.Mutex - chClose utils.StopChan - wgDone sync.WaitGroup - utils.StartStopOnce + services.StateMachine + logger logger.Logger + callbacks callbackSet[H, BLOCK_HASH] + mailbox *utils.Mailbox[H] + mutex sync.Mutex + chClose services.StopChan + wgDone sync.WaitGroup latest H lastCallbackID int } @@ -44,13 +46,10 @@ func NewHeadBroadcaster[ lggr logger.Logger, ) *HeadBroadcaster[H, BLOCK_HASH] { return &HeadBroadcaster[H, BLOCK_HASH]{ - logger: lggr.Named("HeadBroadcaster"), - callbacks: make(callbackSet[H, BLOCK_HASH]), - mailbox: utils.NewSingleMailbox[H](), - mutex: &sync.Mutex{}, - chClose: make(chan struct{}), - wgDone: sync.WaitGroup{}, - StartStopOnce: utils.StartStopOnce{}, + logger: logger.Named(lggr, "HeadBroadcaster"), + callbacks: make(callbackSet[H, BLOCK_HASH]), + mailbox: utils.NewSingleMailbox[H](), + chClose: make(chan struct{}), } } diff --git a/common/headtracker/head_listener.go b/common/headtracker/head_listener.go index ee4969497a8..2013895d0b8 100644 --- a/common/headtracker/head_listener.go +++ b/common/headtracker/head_listener.go @@ -9,9 +9,11 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + htrktypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -35,7 +37,7 @@ type HeadListener[ config htrktypes.Config client htrktypes.Client[HTH, S, ID, BLOCK_HASH] logger logger.Logger - chStop utils.StopChan + chStop services.StopChan chHeaders chan HTH headSubscription types.Subscription connected atomic.Bool @@ -57,7 +59,7 @@ func NewHeadListener[ return &HeadListener[HTH, S, ID, BLOCK_HASH]{ config: config, client: client, - logger: lggr.Named("HeadListener"), + logger: logger.Named(lggr, "HeadListener"), chStop: chStop, } } diff --git a/common/headtracker/head_tracker.go b/common/headtracker/head_tracker.go index 1978b281d44..6e379776c0f 100644 --- a/common/headtracker/head_tracker.go +++ b/common/headtracker/head_tracker.go @@ -10,12 +10,11 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" htrktypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/config" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -40,6 +39,7 @@ type HeadTracker[ ID types.ID, BLOCK_HASH types.Hashable, ] struct { + services.StateMachine log logger.Logger headBroadcaster types.HeadBroadcaster[HTH, BLOCK_HASH] headSaver types.HeadSaver[HTH, BLOCK_HASH] @@ -52,10 +52,9 @@ type HeadTracker[ backfillMB *utils.Mailbox[HTH] broadcastMB *utils.Mailbox[HTH] headListener types.HeadListener[HTH, BLOCK_HASH] - chStop utils.StopChan + chStop services.StopChan wgDone sync.WaitGroup - utils.StartStopOnce - getNilHead func() HTH + getNilHead func() HTH } // NewHeadTracker instantiates a new HeadTracker using HeadSaver to persist new block numbers. @@ -75,7 +74,7 @@ func NewHeadTracker[ getNilHead func() HTH, ) types.HeadTracker[HTH, BLOCK_HASH] { chStop := make(chan struct{}) - lggr = lggr.Named("HeadTracker") + lggr = logger.Named(lggr, "HeadTracker") return &HeadTracker[HTH, S, ID, BLOCK_HASH]{ headBroadcaster: headBroadcaster, client: client, @@ -103,7 +102,7 @@ func (ht *HeadTracker[HTH, S, ID, BLOCK_HASH]) Start(ctx context.Context) error } if latestChain.IsValid() { ht.log.Debugw( - fmt.Sprintf("HeadTracker: Tracking logs from last block %v with hash %s", config.FriendlyNumber(latestChain.BlockNumber()), latestChain.BlockHash()), + fmt.Sprintf("HeadTracker: Tracking logs from last block %v with hash %s", latestChain.BlockNumber(), latestChain.BlockHash()), "blockNumber", latestChain.BlockNumber(), "blockHash", latestChain.BlockHash(), ) @@ -193,10 +192,12 @@ func (ht *HeadTracker[HTH, S, ID, BLOCK_HASH]) getInitialHead(ctx context.Contex func (ht *HeadTracker[HTH, S, ID, BLOCK_HASH]) handleNewHead(ctx context.Context, head HTH) error { prevHead := ht.headSaver.LatestChain() - ht.log.Debugw(fmt.Sprintf("Received new head %v", config.FriendlyNumber(head.BlockNumber())), - "blockHeight", head.BlockNumber(), + ht.log.Debugw(fmt.Sprintf("Received new head %v", head.BlockNumber()), "blockHash", head.BlockHash(), "parentHeadHash", head.GetParentHash(), + "blockTs", head.GetTimestamp(), + "blockTsUnix", head.GetTimestamp().Unix(), + "blockDifficulty", head.BlockDifficulty(), ) err := ht.headSaver.Save(ctx, head) @@ -226,7 +227,7 @@ func (ht *HeadTracker[HTH, S, ID, BLOCK_HASH]) handleNewHead(ctx context.Context prevUnFinalizedHead := prevHead.BlockNumber() - int64(ht.config.FinalityDepth()) if head.BlockNumber() < prevUnFinalizedHead { promOldHead.WithLabelValues(ht.chainID.String()).Inc() - ht.log.Criticalf("Got very old block with number %d (highest seen was %d). This is a problem and either means a very deep re-org occurred, one of the RPC nodes has gotten far out of sync, or the chain went backwards in block numbers. This node may not function correctly without manual intervention.", head.BlockNumber(), prevHead.BlockNumber()) + logger.Criticalf(ht.log, "Got very old block with number %d (highest seen was %d). This is a problem and either means a very deep re-org occurred, one of the RPC nodes has gotten far out of sync, or the chain went backwards in block numbers. This node may not function correctly without manual intervention.", head.BlockNumber(), prevHead.BlockNumber()) ht.SvcErrBuffer.Append(errors.New("got very old block")) } } @@ -309,7 +310,7 @@ func (ht *HeadTracker[HTH, S, ID, BLOCK_HASH]) backfill(ctx context.Context, hea } mark := time.Now() fetched := 0 - l := ht.log.With("blockNumber", headBlockNumber, + l := logger.With(ht.log, "blockNumber", headBlockNumber, "n", headBlockNumber-baseHeight, "fromBlockHeight", baseHeight, "toBlockHeight", headBlockNumber-1) diff --git a/common/headtracker/types/mocks/head.go b/common/headtracker/types/mocks/head.go index edda18d57e8..79c483c9978 100644 --- a/common/headtracker/types/mocks/head.go +++ b/common/headtracker/types/mocks/head.go @@ -3,8 +3,13 @@ package mocks import ( - types "github.com/smartcontractkit/chainlink/v2/common/types" + big "math/big" + mock "github.com/stretchr/testify/mock" + + time "time" + + types "github.com/smartcontractkit/chainlink/v2/common/types" ) // Head is an autogenerated mock type for the Head type @@ -12,6 +17,22 @@ type Head[BLOCK_HASH types.Hashable, CHAIN_ID types.ID] struct { mock.Mock } +// BlockDifficulty provides a mock function with given fields: +func (_m *Head[BLOCK_HASH, CHAIN_ID]) BlockDifficulty() *big.Int { + ret := _m.Called() + + var r0 *big.Int + if rf, ok := ret.Get(0).(func() *big.Int); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + return r0 +} + // BlockHash provides a mock function with given fields: func (_m *Head[BLOCK_HASH, CHAIN_ID]) BlockHash() BLOCK_HASH { ret := _m.Called() @@ -114,6 +135,20 @@ func (_m *Head[BLOCK_HASH, CHAIN_ID]) GetParentHash() BLOCK_HASH { return r0 } +// GetTimestamp provides a mock function with given fields: +func (_m *Head[BLOCK_HASH, CHAIN_ID]) GetTimestamp() time.Time { + ret := _m.Called() + + var r0 time.Time + if rf, ok := ret.Get(0).(func() time.Time); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Time) + } + + return r0 +} + // HasChainID provides a mock function with given fields: func (_m *Head[BLOCK_HASH, CHAIN_ID]) HasChainID() bool { ret := _m.Called() diff --git a/common/txmgr/broadcaster.go b/common/txmgr/broadcaster.go index 1e1c0e0cff3..54ae653f662 100644 --- a/common/txmgr/broadcaster.go +++ b/common/txmgr/broadcaster.go @@ -15,13 +15,14 @@ import ( "go.uber.org/multierr" "gopkg.in/guregu/null.v4" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" + "github.com/smartcontractkit/chainlink-common/pkg/chains/label" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/common/client" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/label" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -106,7 +107,8 @@ type Broadcaster[ SEQ types.Sequence, FEE feetypes.Fee, ] struct { - logger logger.Logger + services.StateMachine + lggr logger.Logger txStore txmgrtypes.TransactionStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, SEQ, FEE] client txmgrtypes.TransactionClient[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] txmgrtypes.TxAttemptBuilder[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] @@ -122,8 +124,6 @@ type Broadcaster[ // when Start is called autoSyncSequence bool - txInsertListener pg.Subscription - eventBroadcaster pg.EventBroadcaster processUnstartedTxsImpl ProcessUnstartedTxs[ADDR] ks txmgrtypes.KeyStore[ADDR, CHAIN_ID, SEQ] @@ -136,14 +136,11 @@ type Broadcaster[ // Each key has its own trigger triggers map[ADDR]chan struct{} - chStop utils.StopChan + chStop services.StopChan wg sync.WaitGroup initSync sync.Mutex isStarted bool - utils.StartStopOnce - - parseAddr func(string) (ADDR, error) sequenceLock sync.RWMutex nextSequenceMap map[ADDR]SEQ @@ -166,18 +163,16 @@ func NewBroadcaster[ txConfig txmgrtypes.BroadcasterTransactionsConfig, listenerConfig txmgrtypes.BroadcasterListenerConfig, keystore txmgrtypes.KeyStore[ADDR, CHAIN_ID, SEQ], - eventBroadcaster pg.EventBroadcaster, txAttemptBuilder txmgrtypes.TxAttemptBuilder[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], sequenceSyncer SequenceSyncer[ADDR, TX_HASH, BLOCK_HASH, SEQ], - logger logger.Logger, + lggr logger.Logger, checkerFactory TransmitCheckerFactory[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], autoSyncSequence bool, - parseAddress func(string) (ADDR, error), generateNextSequence types.GenerateNextSequenceFunc[SEQ], ) *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] { - logger = logger.Named("Broadcaster") + lggr = logger.Named(lggr, "Broadcaster") b := &Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{ - logger: logger, + lggr: lggr, txStore: txStore, client: client, TxAttemptBuilder: txAttemptBuilder, @@ -187,11 +182,9 @@ func NewBroadcaster[ feeConfig: feeConfig, txConfig: txConfig, listenerConfig: listenerConfig, - eventBroadcaster: eventBroadcaster, ks: keystore, checkerFactory: checkerFactory, autoSyncSequence: autoSyncSequence, - parseAddr: parseAddress, } b.processUnstartedTxsImpl = b.processUnstartedTxs @@ -201,51 +194,43 @@ func NewBroadcaster[ // Start starts Broadcaster service. // The provided context can be used to terminate Start sequence. -func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Start(_ context.Context) error { +func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Start(ctx context.Context) error { return eb.StartOnce("Broadcaster", func() (err error) { - return eb.startInternal() + return eb.startInternal(ctx) }) } // startInternal can be called multiple times, in conjunction with closeInternal. The TxMgr uses this functionality to reset broadcaster multiple times in its own lifetime. -func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) startInternal() error { +func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) startInternal(ctx context.Context) error { eb.initSync.Lock() defer eb.initSync.Unlock() if eb.isStarted { return errors.New("Broadcaster is already started") } var err error - eb.txInsertListener, err = eb.eventBroadcaster.Subscribe(pg.ChannelInsertOnTx, "") - if err != nil { - return errors.Wrap(err, "Broadcaster could not start") - } eb.enabledAddresses, err = eb.ks.EnabledAddressesForChain(eb.chainID) if err != nil { return errors.Wrap(err, "Broadcaster: failed to load EnabledAddressesForChain") } if len(eb.enabledAddresses) > 0 { - eb.logger.Debugw(fmt.Sprintf("Booting with %d keys", len(eb.enabledAddresses)), "keys", eb.enabledAddresses) + eb.lggr.Debugw(fmt.Sprintf("Booting with %d keys", len(eb.enabledAddresses)), "keys", eb.enabledAddresses) } else { - eb.logger.Warnf("Chain %s does not have any keys, no transactions will be sent on this chain", eb.chainID.String()) + eb.lggr.Warnf("Chain %s does not have any keys, no transactions will be sent on this chain", eb.chainID.String()) } eb.chStop = make(chan struct{}) eb.wg = sync.WaitGroup{} eb.wg.Add(len(eb.enabledAddresses)) eb.triggers = make(map[ADDR]chan struct{}) + eb.sequenceLock.Lock() + eb.nextSequenceMap = eb.loadNextSequenceMap(ctx, eb.enabledAddresses) + eb.sequenceLock.Unlock() for _, addr := range eb.enabledAddresses { triggerCh := make(chan struct{}, 1) eb.triggers[addr] = triggerCh go eb.monitorTxs(addr, triggerCh) } - eb.wg.Add(1) - go eb.txInsertTriggerer() - - eb.sequenceLock.Lock() - defer eb.sequenceLock.Unlock() - eb.nextSequenceMap = eb.loadNextSequenceMap(eb.enabledAddresses) - eb.isStarted = true return nil } @@ -261,10 +246,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) clos eb.initSync.Lock() defer eb.initSync.Unlock() if !eb.isStarted { - return errors.Wrap(utils.ErrAlreadyStopped, "Broadcaster is not started") - } - if eb.txInsertListener != nil { - eb.txInsertListener.Close() + return errors.Wrap(services.ErrAlreadyStopped, "Broadcaster is not started") } close(eb.chStop) eb.wg.Wait() @@ -277,7 +259,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) SetR } func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Name() string { - return eb.logger.Name() + return eb.lggr.Name() } func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) HealthReport() map[string]error { @@ -298,36 +280,12 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Trig default: } } else { - eb.logger.Debugf("Unstarted; ignoring trigger for %s", addr) - } -} - -func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) txInsertTriggerer() { - defer eb.wg.Done() - for { - select { - case ev, ok := <-eb.txInsertListener.Events(): - if !ok { - eb.logger.Debug("txInsertListener channel closed, exiting trigger loop") - return - } - addr, err := eb.parseAddr(ev.Payload) - if err != nil { - eb.logger.Errorw("failed to parse address in trigger", "err", err) - continue - } - eb.Trigger(addr) - case <-eb.chStop: - return - } + eb.lggr.Debugf("Unstarted; ignoring trigger for %s", addr) } } // Load the next sequence map using the tx table or on-chain (if not found in tx table) -func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) loadNextSequenceMap(addresses []ADDR) map[ADDR]SEQ { - ctx, cancel := eb.chStop.NewCtx() - defer cancel() - +func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) loadNextSequenceMap(ctx context.Context, addresses []ADDR) map[ADDR]SEQ { nextSequenceMap := make(map[ADDR]SEQ) for _, address := range addresses { seq, err := eb.getSequenceForAddr(ctx, address) @@ -353,7 +311,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) getS if err == nil { return seq, nil } - eb.logger.Criticalw("failed to retrieve next sequence from on-chain for address: ", "address", address.String()) + logger.Criticalw(eb.lggr, "failed to retrieve next sequence from on-chain for address: ", "address", address.String()) return seq, err } @@ -381,13 +339,13 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) moni defer cancel() if eb.autoSyncSequence { - eb.logger.Debugw("Auto-syncing sequence", "address", addr.String()) + eb.lggr.Debugw("Auto-syncing sequence", "address", addr.String()) eb.SyncSequence(ctx, addr) if ctx.Err() != nil { return } } else { - eb.logger.Debugw("Skipping sequence auto-sync", "address", addr.String()) + eb.lggr.Debugw("Skipping sequence auto-sync", "address", addr.String()) } // errorRetryCh allows retry on exponential backoff in case of timeout or @@ -400,7 +358,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) moni retryable, err := eb.processUnstartedTxsImpl(ctx, addr) if err != nil { - eb.logger.Errorw("Error occurred while handling tx queue in ProcessUnstartedTxs", "err", err) + eb.lggr.Errorw("Error occurred while handling tx queue in ProcessUnstartedTxs", "err", err) } // On retryable errors we implement exponential backoff retries. This // handles intermittent connectivity, remote RPC races, timing issues etc @@ -441,7 +399,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Sync localSequence, err := eb.GetNextSequence(ctx, addr) // Address not found in map so skip sync if err != nil { - eb.logger.Criticalw("Failed to retrieve local next sequence for address", "address", addr.String(), "err", err) + logger.Criticalw(eb.lggr, "Failed to retrieve local next sequence for address", "address", addr.String(), "err", err) return } @@ -456,16 +414,16 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Sync newNextSequence, err := eb.sequenceSyncer.Sync(ctx, addr, localSequence) if err != nil { if attempt > 5 { - eb.logger.Criticalw("Failed to sync with on-chain sequence", "address", addr.String(), "attempt", attempt, "err", err) + logger.Criticalw(eb.lggr, "Failed to sync with on-chain sequence", "address", addr.String(), "attempt", attempt, "err", err) eb.SvcErrBuffer.Append(err) } else { - eb.logger.Warnw("Failed to sync with on-chain sequence", "address", addr.String(), "attempt", attempt, "err", err) + eb.lggr.Warnw("Failed to sync with on-chain sequence", "address", addr.String(), "attempt", attempt, "err", err) } continue } // Found new sequence to use from on-chain if localSequence.String() != newNextSequence.String() { - eb.logger.Infow("Fast-forward sequence", "address", addr, "newNextSequence", newNextSequence, "oldNextSequence", localSequence) + eb.lggr.Infow("Fast-forward sequence", "address", addr, "newNextSequence", newNextSequence, "oldNextSequence", localSequence) // Set new sequence in the map eb.SetNextSequence(addr, newNextSequence) } @@ -490,7 +448,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) proc mark := time.Now() defer func() { if n > 0 { - eb.logger.Debugw("Finished processUnstartedTxs", "address", fromAddress, "time", time.Since(mark), "n", n, "id", "broadcaster") + eb.lggr.Debugw("Finished processUnstartedTxs", "address", fromAddress, "time", time.Since(mark), "n", n, "id", "broadcaster") } }() @@ -510,7 +468,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) proc if err != nil { return true, errors.Wrap(err, "CountUnstartedTransactions failed") } - eb.logger.Warnw(fmt.Sprintf(`Transaction throttling; %d transactions in-flight and %d unstarted transactions pending (maximum number of in-flight transactions is %d per key). %s`, nUnconfirmed, nUnstarted, maxInFlightTransactions, label.MaxInFlightTransactionsWarning), "maxInFlightTransactions", maxInFlightTransactions, "nUnconfirmed", nUnconfirmed, "nUnstarted", nUnstarted) + eb.lggr.Warnw(fmt.Sprintf(`Transaction throttling; %d transactions in-flight and %d unstarted transactions pending (maximum number of in-flight transactions is %d per key). %s`, nUnconfirmed, nUnstarted, maxInFlightTransactions, label.MaxInFlightTransactionsWarning), "maxInFlightTransactions", maxInFlightTransactions, "nUnconfirmed", nUnconfirmed, "nUnstarted", nUnstarted) select { case <-time.After(InFlightTransactionRecheckInterval): case <-ctx.Done(): @@ -529,13 +487,13 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) proc n++ var a txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] var retryable bool - a, _, _, retryable, err = eb.NewTxAttempt(ctx, *etx, eb.logger) + a, _, _, retryable, err = eb.NewTxAttempt(ctx, *etx, eb.lggr) if err != nil { return retryable, errors.Wrap(err, "processUnstartedTxs failed on NewAttempt") } if err := eb.txStore.UpdateTxUnstartedToInProgress(ctx, etx, &a); errors.Is(err, ErrTxRemoved) { - eb.logger.Debugw("tx removed", "txID", etx.ID, "subject", etx.Subject) + eb.lggr.Debugw("tx removed", "txID", etx.ID, "subject", etx.Subject) continue } else if err != nil { return true, errors.Wrap(err, "processUnstartedTxs failed on UpdateTxUnstartedToInProgress") @@ -579,7 +537,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) hand return errors.Wrap(err, "building transmit checker"), false } - lgr := etx.GetLogger(eb.logger.With("fee", attempt.TxFee)) + lgr := etx.GetLogger(logger.With(eb.lggr, "fee", attempt.TxFee)) // If the transmit check does not complete within the timeout, the transaction will be sent // anyway. @@ -598,19 +556,19 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) hand lgr.Infow("Sending transaction", "txAttemptID", attempt.ID, "txHash", attempt.Hash, "err", err, "meta", etx.Meta, "feeLimit", etx.FeeLimit, "attempt", attempt, "etx", etx) errType, err := eb.client.SendTransactionReturnCode(ctx, etx, attempt, lgr) - if errType != clienttypes.Fatal { + if errType != client.Fatal { etx.InitialBroadcastAt = &initialBroadcastAt etx.BroadcastAt = &initialBroadcastAt } switch errType { - case clienttypes.Fatal: + case client.Fatal: eb.SvcErrBuffer.Append(err) etx.Error = null.StringFrom(err.Error()) return eb.saveFatallyErroredTransaction(lgr, &etx), true - case clienttypes.TransactionAlreadyKnown: + case client.TransactionAlreadyKnown: fallthrough - case clienttypes.Successful: + case client.Successful: // Either the transaction was successful or one of the following four scenarios happened: // // SCENARIO 1 @@ -663,9 +621,9 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) hand // Increment sequence if successfully broadcasted eb.IncrementNextSequence(etx.FromAddress, sequence) return err, true - case clienttypes.Underpriced: + case client.Underpriced: return eb.tryAgainBumpingGas(ctx, lgr, err, etx, attempt, initialBroadcastAt) - case clienttypes.InsufficientFunds: + case client.InsufficientFunds: // NOTE: This bails out of the entire cycle and essentially "blocks" on // any transaction that gets insufficient_funds. This is OK if a // transaction with a large VALUE blocks because this always comes last @@ -675,13 +633,13 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) hand // theoretically be sent, but will instead be blocked. eb.SvcErrBuffer.Append(err) fallthrough - case clienttypes.Retryable: + case client.Retryable: return err, true - case clienttypes.FeeOutOfValidRange: + case client.FeeOutOfValidRange: return eb.tryAgainWithNewEstimation(ctx, lgr, err, etx, attempt, initialBroadcastAt) - case clienttypes.Unsupported: + case client.Unsupported: return err, false - case clienttypes.ExceedsMaxFee: + case client.ExceedsMaxFee: // Broadcaster: Note that we may have broadcast to multiple nodes and had it // accepted by one of them! It is not guaranteed that all nodes share // the same tx fee cap. That is why we must treat this as an unknown @@ -689,14 +647,14 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) hand // If there is only one RPC node, or all RPC nodes have the same // configured cap, this transaction will get stuck and keep repeating // forever until the issue is resolved. - lgr.Criticalw(`RPC node rejected this tx as outside Fee Cap`) + logger.Criticalw(lgr, `RPC node rejected this tx as outside Fee Cap`) fallthrough default: // Every error that doesn't fall under one of the above categories will be treated as Unknown. fallthrough - case clienttypes.Unknown: + case client.Unknown: eb.SvcErrBuffer.Append(err) - lgr.Criticalw(`Unknown error occurred while handling tx queue in ProcessUnstartedTxs. This chain/RPC client may not be supported. `+ + logger.Criticalw(lgr, `Unknown error occurred while handling tx queue in ProcessUnstartedTxs. This chain/RPC client may not be supported. `+ `Urgent resolution required, Chainlink is currently operating in a degraded state and may miss transactions`, "err", err, "etx", etx, "attempt", attempt) nextSequence, e := eb.client.PendingSequenceAt(ctx, etx.FromAddress) if e != nil { @@ -756,7 +714,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) next } func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) tryAgainBumpingGas(ctx context.Context, lgr logger.Logger, txError error, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], initialBroadcastAt time.Time) (err error, retryable bool) { - lgr.With( + logger.With(lgr, "sendError", txError, "attemptFee", attempt.TxFee, "maxGasPriceConfig", eb.feeConfig.MaxFeePrice(), @@ -777,7 +735,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) tryA func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) tryAgainWithNewEstimation(ctx context.Context, lgr logger.Logger, txError error, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], initialBroadcastAt time.Time) (err error, retryable bool) { if attempt.TxType == 0x2 { err = errors.Errorf("re-estimation is not supported for EIP-1559 transactions. Node returned error: %v. This is a bug", txError.Error()) - logger.Sugared(eb.logger).AssumptionViolation(err.Error()) + logger.Sugared(eb.lggr).AssumptionViolation(err.Error()) return err, false } @@ -820,12 +778,17 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) save // Now we have an errored pipeline even though the tx succeeded. This case // is relatively benign and probably nobody will ever run into it in // practice, but something to be aware of. - if etx.PipelineTaskRunID.Valid && eb.resumeCallback != nil { + if etx.PipelineTaskRunID.Valid && eb.resumeCallback != nil && etx.SignalCallback { err := eb.resumeCallback(etx.PipelineTaskRunID.UUID, nil, errors.Errorf("fatal error while sending transaction: %s", etx.Error.String)) if errors.Is(err, sql.ErrNoRows) { lgr.Debugw("callback missing or already resumed", "etxID", etx.ID) } else if err != nil { return errors.Wrap(err, "failed to resume pipeline") + } else { + // Mark tx as having completed callback + if err := eb.txStore.UpdateTxCallbackCompleted(ctx, etx.PipelineTaskRunID.UUID, eb.chainID); err != nil { + return err + } } } return eb.txStore.UpdateTxFatalError(ctx, etx) @@ -841,7 +804,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) GetN return seq, nil } - eb.logger.Infow("address not found in local next sequence map. Attempting to search and populate sequence.", "address", address.String()) + eb.lggr.Infow("address not found in local next sequence map. Attempting to search and populate sequence.", "address", address.String()) // Check if address is in the enabled address list if !slices.Contains(eb.enabledAddresses, address) { return seq, fmt.Errorf("address disabled: %s", address) diff --git a/common/txmgr/confirmer.go b/common/txmgr/confirmer.go index 31bba771410..a56768ce206 100644 --- a/common/txmgr/confirmer.go +++ b/common/txmgr/confirmer.go @@ -14,13 +14,15 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/multierr" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" + "github.com/smartcontractkit/chainlink-common/pkg/chains/label" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/common/client" commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/label" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -112,7 +114,7 @@ type Confirmer[ SEQ types.Sequence, FEE feetypes.Fee, ] struct { - utils.StartStopOnce + services.StateMachine txStore txmgrtypes.TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] lggr logger.Logger client txmgrtypes.TxmClient[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] @@ -159,7 +161,7 @@ func NewConfirmer[ lggr logger.Logger, isReceiptNil func(R) bool, ) *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { - lggr = lggr.Named("Confirmer") + lggr = logger.Named(lggr, "Confirmer") return &Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{ txStore: txStore, lggr: lggr, @@ -361,7 +363,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Che for idx, txErr := range txErrs { // Add to Unconfirm array, all tx where error wasn't TransactionAlreadyKnown. if txErr != nil { - if txCodes[idx] == clienttypes.TransactionAlreadyKnown { + if txCodes[idx] == client.TransactionAlreadyKnown { continue } } @@ -516,7 +518,8 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) bat } } - lggr := ec.lggr.Named("BatchFetchReceipts").With("blockNum", blockNum) + lggr := logger.Named(ec.lggr, "BatchFetchReceipts") + lggr = logger.With(lggr, "blockNum", blockNum) txReceipts, txErrs, err := ec.client.BatchGetReceipts(ctx, attempts) if err != nil { @@ -528,8 +531,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) bat receipt := txReceipts[i] err := txErrs[i] - l := logger.Sugared(attempt.Tx.GetLogger(lggr).With( - "txHash", attempt.Hash.String(), "txAttemptID", attempt.ID, + l := logger.Sugared(logger.With(attempt.Tx.GetLogger(lggr), "txHash", attempt.Hash.String(), "txAttemptID", attempt.ID, "txID", attempt.TxID, "err", err, "sequence", attempt.Tx.Sequence, )) @@ -550,7 +552,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) bat continue } - l = logger.Sugared(l.With("blockHash", receipt.GetBlockHash().String(), "status", receipt.GetStatus(), "transactionIndex", receipt.GetTransactionIndex())) + l = logger.Sugared(logger.With(l, "blockHash", receipt.GetBlockHash().String(), "status", receipt.GetStatus(), "transactionIndex", receipt.GetTransactionIndex())) if receipt.IsUnmined() { l.Debug("Got receipt for transaction but it's still in the mempool and not included in a block yet") @@ -818,7 +820,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) han errType, sendError := ec.client.SendTransactionReturnCode(ctx, etx, attempt, lggr) switch errType { - case clienttypes.Underpriced: + case client.Underpriced: // This should really not ever happen in normal operation since we // already bumped above the required minimum in broadcaster. ec.lggr.Warnw("Got terminally underpriced error for gas bump, this should never happen unless the remote RPC node changed its configuration on the fly, or you are using multiple RPC nodes with different minimum gas price requirements. This is not recommended", "err", sendError, "attempt", attempt) @@ -842,7 +844,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) han return errors.Wrap(err, "could not bump gas for terminally underpriced transaction") } promNumGasBumps.WithLabelValues(ec.chainID.String()).Inc() - lggr.With( + logger.With(lggr, "sendError", sendError, "maxGasPriceConfig", ec.feeConfig.MaxFeePrice(), "previousAttempt", attempt, @@ -853,17 +855,17 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) han return errors.Wrap(err, "saveReplacementInProgressAttempt failed") } return ec.handleInProgressAttempt(ctx, lggr, etx, replacementAttempt, blockHeight) - case clienttypes.ExceedsMaxFee: + case client.ExceedsMaxFee: // Confirmer: The gas price was bumped too high. This transaction attempt cannot be accepted. // Best thing we can do is to re-send the previous attempt at the old // price and discard this bumped version. fallthrough - case clienttypes.Fatal: + case client.Fatal: // WARNING: This should never happen! // Should NEVER be fatal this is an invariant violation. The // Broadcaster can never create a TxAttempt that will // fatally error. - lggr.Criticalw("Invariant violation: fatal error while re-attempting transaction", + logger.Criticalw(lggr, "Invariant violation: fatal error while re-attempting transaction", "err", sendError, "fee", attempt.TxFee, "feeLimit", etx.FeeLimit, @@ -873,20 +875,20 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) han ec.SvcErrBuffer.Append(sendError) // This will loop continuously on every new head so it must be handled manually by the node operator! return ec.txStore.DeleteInProgressAttempt(ctx, attempt) - case clienttypes.TransactionAlreadyKnown: + case client.TransactionAlreadyKnown: // Sequence too low indicated that a transaction at this sequence was confirmed already. // Mark confirmed_missing_receipt and wait for the next cycle to try to get a receipt lggr.Debugw("Sequence already used", "txAttemptID", attempt.ID, "txHash", attempt.Hash.String(), "err", sendError) timeout := ec.dbConfig.DefaultQueryTimeout() return ec.txStore.SaveConfirmedMissingReceiptAttempt(ctx, timeout, &attempt, now) - case clienttypes.InsufficientFunds: + case client.InsufficientFunds: timeout := ec.dbConfig.DefaultQueryTimeout() return ec.txStore.SaveInsufficientFundsAttempt(ctx, timeout, &attempt, now) - case clienttypes.Successful: + case client.Successful: lggr.Debugw("Successfully broadcast transaction", "txAttemptID", attempt.ID, "txHash", attempt.Hash.String()) timeout := ec.dbConfig.DefaultQueryTimeout() return ec.txStore.SaveSentAttempt(ctx, timeout, &attempt, now) - case clienttypes.Unknown: + case client.Unknown: // Every error that doesn't fall under one of the above categories will be treated as Unknown. fallthrough default: @@ -1024,7 +1026,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) mar // This operates completely orthogonal to the normal Confirmer and can result in untracked attempts! // Only for emergency usage. // This is in case of some unforeseen scenario where the node is refusing to release the lock. KISS. -func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ForceRebroadcast(seqs []SEQ, fee FEE, address ADDR, overrideGasLimit uint32) error { +func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ForceRebroadcast(ctx context.Context, seqs []SEQ, fee FEE, address ADDR, overrideGasLimit uint32) error { if len(seqs) == 0 { ec.lggr.Infof("ForceRebroadcast: No sequences provided. Skipping") return nil @@ -1033,13 +1035,13 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) For for _, seq := range seqs { - etx, err := ec.txStore.FindTxWithSequence(context.TODO(), address, seq) + etx, err := ec.txStore.FindTxWithSequence(ctx, address, seq) if err != nil { return errors.Wrap(err, "ForceRebroadcast failed") } if etx == nil { ec.lggr.Debugf("ForceRebroadcast: no tx found with sequence %s, will rebroadcast empty transaction", seq) - hashStr, err := ec.sendEmptyTransaction(context.TODO(), address, seq, overrideGasLimit, fee) + hashStr, err := ec.sendEmptyTransaction(ctx, address, seq, overrideGasLimit, fee) if err != nil { ec.lggr.Errorw("ForceRebroadcast: failed to send empty transaction", "sequence", seq, "err", err) continue @@ -1057,7 +1059,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) For } attempt.Tx = *etx // for logging ec.lggr.Debugw("Sending transaction", "txAttemptID", attempt.ID, "txHash", attempt.Hash, "err", err, "meta", etx.Meta, "feeLimit", etx.FeeLimit, "attempt", attempt) - if errCode, err := ec.client.SendTransactionReturnCode(context.TODO(), *etx, attempt, ec.lggr); errCode != clienttypes.Successful && err != nil { + if errCode, err := ec.client.SendTransactionReturnCode(ctx, *etx, attempt, ec.lggr); errCode != client.Successful && err != nil { ec.lggr.Errorw(fmt.Sprintf("ForceRebroadcast: failed to rebroadcast tx %v with sequence %v and gas limit %v: %s", etx.ID, *etx.Sequence, etx.FeeLimit, err.Error()), "err", err, "fee", attempt.TxFee) continue } @@ -1082,7 +1084,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) sen // ResumePendingTaskRuns issues callbacks to task runs that are pending waiting for receipts func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ResumePendingTaskRuns(ctx context.Context, head types.Head[BLOCK_HASH]) error { - receiptsPlus, err := ec.txStore.FindReceiptsPendingConfirmation(ctx, head.BlockNumber(), ec.chainID) + receiptsPlus, err := ec.txStore.FindTxesPendingCallback(ctx, head.BlockNumber(), ec.chainID) if err != nil { return err @@ -1104,6 +1106,10 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Res ec.lggr.Debugw("Callback: resuming tx with receipt", "output", output, "taskErr", taskErr, "pipelineTaskRunID", data.ID) if err := ec.resumeCallback(data.ID, output, taskErr); err != nil { + return fmt.Errorf("failed to resume suspended pipeline run: %w", err) + } + // Mark tx as having completed callback + if err := ec.txStore.UpdateTxCallbackCompleted(ctx, data.ID, ec.chainID); err != nil { return err } } diff --git a/common/txmgr/mocks/tx_manager.go b/common/txmgr/mocks/tx_manager.go index c01f182c9bd..27077218f6e 100644 --- a/common/txmgr/mocks/tx_manager.go +++ b/common/txmgr/mocks/tx_manager.go @@ -9,6 +9,8 @@ import ( feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" mock "github.com/stretchr/testify/mock" + null "gopkg.in/guregu/null.v4" + txmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" @@ -35,6 +37,30 @@ func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Close( return r0 } +// CountTransactionsByState provides a mock function with given fields: ctx, state +func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) CountTransactionsByState(ctx context.Context, state txmgrtypes.TxState) (uint32, error) { + ret := _m.Called(ctx, state) + + var r0 uint32 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, txmgrtypes.TxState) (uint32, error)); ok { + return rf(ctx, state) + } + if rf, ok := ret.Get(0).(func(context.Context, txmgrtypes.TxState) uint32); ok { + r0 = rf(ctx, state) + } else { + r0 = ret.Get(0).(uint32) + } + + if rf, ok := ret.Get(1).(func(context.Context, txmgrtypes.TxState) error); ok { + r1 = rf(ctx, state) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // CreateTransaction provides a mock function with given fields: ctx, txRequest func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) CreateTransaction(ctx context.Context, txRequest txmgrtypes.TxRequest[ADDR, TX_HASH]) (txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { ret := _m.Called(ctx, txRequest) @@ -59,6 +85,158 @@ func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Create return r0, r1 } +// FindEarliestUnconfirmedBroadcastTime provides a mock function with given fields: ctx +func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindEarliestUnconfirmedBroadcastTime(ctx context.Context) (null.Time, error) { + ret := _m.Called(ctx) + + var r0 null.Time + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (null.Time, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) null.Time); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(null.Time) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindEarliestUnconfirmedTxAttemptBlock provides a mock function with given fields: ctx +func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context) (null.Int, error) { + ret := _m.Called(ctx) + + var r0 null.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (null.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) null.Int); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(null.Int) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesByMetaFieldAndStates provides a mock function with given fields: ctx, metaField, metaValue, states, chainID +func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindTxesByMetaFieldAndStates(ctx context.Context, metaField string, metaValue string, states []txmgrtypes.TxState, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, metaField, metaValue, states, chainID) + + var r0 []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []txmgrtypes.TxState, *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, metaField, metaValue, states, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, []txmgrtypes.TxState, *big.Int) []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, metaField, metaValue, states, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, []txmgrtypes.TxState, *big.Int) error); ok { + r1 = rf(ctx, metaField, metaValue, states, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesWithAttemptsAndReceiptsByIdsAndState provides a mock function with given fields: ctx, ids, states, chainID +func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx context.Context, ids []big.Int, states []txmgrtypes.TxState, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, ids, states, chainID) + + var r0 []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []big.Int, []txmgrtypes.TxState, *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, ids, states, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, []big.Int, []txmgrtypes.TxState, *big.Int) []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, ids, states, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []big.Int, []txmgrtypes.TxState, *big.Int) error); ok { + r1 = rf(ctx, ids, states, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesWithMetaFieldByReceiptBlockNum provides a mock function with given fields: ctx, metaField, blockNum, chainID +func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, metaField, blockNum, chainID) + + var r0 []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, metaField, blockNum, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *big.Int) []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, metaField, blockNum, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, int64, *big.Int) error); ok { + r1 = rf(ctx, metaField, blockNum, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesWithMetaFieldByStates provides a mock function with given fields: ctx, metaField, states, chainID +func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []txmgrtypes.TxState, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, metaField, states, chainID) + + var r0 []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []txmgrtypes.TxState, *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, metaField, states, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []txmgrtypes.TxState, *big.Int) []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, metaField, states, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []txmgrtypes.TxState, *big.Int) error); ok { + r1 = rf(ctx, metaField, states, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetForwarderForEOA provides a mock function with given fields: eoa func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) GetForwarderForEOA(eoa ADDR) (ADDR, error) { ret := _m.Called(eoa) diff --git a/common/txmgr/reaper.go b/common/txmgr/reaper.go index 96bc4860d71..385a9a17c3d 100644 --- a/common/txmgr/reaper.go +++ b/common/txmgr/reaper.go @@ -5,9 +5,11 @@ import ( "sync/atomic" "time" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -20,7 +22,7 @@ type Reaper[CHAIN_ID types.ID] struct { log logger.Logger latestBlockNum atomic.Int64 trigger chan struct{} - chStop chan struct{} + chStop services.StopChan chDone chan struct{} } @@ -31,10 +33,10 @@ func NewReaper[CHAIN_ID types.ID](lggr logger.Logger, store txmgrtypes.TxHistory config, txConfig, chainID, - lggr.Named("Reaper"), + logger.Named(lggr, "Reaper"), atomic.Int64{}, make(chan struct{}, 1), - make(chan struct{}), + make(services.StopChan), make(chan struct{}), } r.latestBlockNum.Store(-1) @@ -97,7 +99,7 @@ func (r *Reaper[CHAIN_ID]) SetLatestBlockNum(latestBlockNum int64) { // ReapTxes deletes old txes func (r *Reaper[CHAIN_ID]) ReapTxes(headNum int64) error { - ctx, cancel := utils.StopChan(r.chStop).NewCtx() + ctx, cancel := r.chStop.NewCtx() defer cancel() threshold := r.txConfig.ReaperThreshold() if threshold == 0 { diff --git a/common/txmgr/resender.go b/common/txmgr/resender.go index 655de0f1135..d93f20095f1 100644 --- a/common/txmgr/resender.go +++ b/common/txmgr/resender.go @@ -6,12 +6,13 @@ import ( "fmt" "time" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" + "github.com/smartcontractkit/chainlink-common/pkg/chains/label" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/v2/common/client" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/label" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -40,11 +41,13 @@ type Resender[ ADDR types.Hashable, TX_HASH types.Hashable, BLOCK_HASH types.Hashable, + R txmgrtypes.ChainReceipt[TX_HASH, BLOCK_HASH], SEQ types.Sequence, FEE feetypes.Fee, ] struct { txStore txmgrtypes.TransactionStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, SEQ, FEE] client txmgrtypes.TransactionClient[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + tracker *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] ks txmgrtypes.KeyStore[ADDR, CHAIN_ID, SEQ] chainID CHAIN_ID interval time.Duration @@ -63,31 +66,34 @@ func NewResender[ ADDR types.Hashable, TX_HASH types.Hashable, BLOCK_HASH types.Hashable, + R txmgrtypes.ChainReceipt[TX_HASH, BLOCK_HASH], SEQ types.Sequence, FEE feetypes.Fee, ]( lggr logger.Logger, txStore txmgrtypes.TransactionStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, SEQ, FEE], client txmgrtypes.TransactionClient[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], + tracker *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], ks txmgrtypes.KeyStore[ADDR, CHAIN_ID, SEQ], pollInterval time.Duration, config txmgrtypes.ResenderChainConfig, txConfig txmgrtypes.ResenderTransactionsConfig, -) *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] { +) *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { if txConfig.ResendAfterThreshold() == 0 { panic("Resender requires a non-zero threshold") } // todo: add context to txStore https://smartcontract-it.atlassian.net/browse/BCI-1585 ctx, cancel := context.WithCancel(context.Background()) - return &Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{ + return &Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{ txStore, client, + tracker, ks, client.ConfiguredChainID(), pollInterval, config, txConfig, - lggr.Named("Resender"), + logger.Named(lggr, "Resender"), make(map[string]time.Time), ctx, cancel, @@ -96,18 +102,18 @@ func NewResender[ } // Start is a comment which satisfies the linter -func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Start() { +func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Start() { er.logger.Debugf("Enabled with poll interval of %s and age threshold of %s", er.interval, er.txConfig.ResendAfterThreshold()) go er.runLoop() } // Stop is a comment which satisfies the linter -func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Stop() { +func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Stop() { er.cancel() <-er.chDone } -func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) runLoop() { +func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) runLoop() { defer close(er.chDone) if err := er.resendUnconfirmed(); err != nil { @@ -128,16 +134,21 @@ func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) runLoop() { } } -func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) resendUnconfirmed() error { - enabledAddresses, err := er.ks.EnabledAddressesForChain(er.chainID) +func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) resendUnconfirmed() error { + resendAddresses, err := er.ks.EnabledAddressesForChain(er.chainID) if err != nil { return fmt.Errorf("Resender failed getting enabled keys for chain %s: %w", er.chainID.String(), err) } + + // Tracker currently disabled for BCI-2638; refactor required + // resendAddresses = append(resendAddresses, er.tracker.GetAbandonedAddresses()...) + ageThreshold := er.txConfig.ResendAfterThreshold() maxInFlightTransactions := er.txConfig.MaxInFlight() olderThan := time.Now().Add(-ageThreshold) var allAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] - for _, k := range enabledAddresses { + + for _, k := range resendAddresses { var attempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] attempts, err = er.txStore.FindTxAttemptsRequiringResend(er.ctx, olderThan, maxInFlightTransactions, er.chainID, k) if err != nil { @@ -175,20 +186,20 @@ func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) resendUnconfi return nil } -func logResendResult(lggr logger.Logger, codes []clienttypes.SendTxReturnCode) { +func logResendResult(lggr logger.Logger, codes []client.SendTxReturnCode) { var nNew int var nFatal int for _, c := range codes { - if c == clienttypes.Successful { + if c == client.Successful { nNew++ - } else if c == clienttypes.Fatal { + } else if c == client.Fatal { nFatal++ } } lggr.Debugw("Completed", "n", len(codes), "nNew", nNew, "nFatal", nFatal) } -func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) logStuckAttempts(attempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], fromAddress ADDR) { +func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) logStuckAttempts(attempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], fromAddress ADDR) { if time.Since(er.lastAlertTimestamps[fromAddress.String()]) >= unconfirmedTxAlertLogFrequency { oldestAttempt, exists := findOldestUnconfirmedAttempt(attempts) if exists { diff --git a/common/txmgr/test_helpers.go b/common/txmgr/test_helpers.go index 95d08c2e953..6c0c5680ea7 100644 --- a/common/txmgr/test_helpers.go +++ b/common/txmgr/test_helpers.go @@ -2,6 +2,7 @@ package txmgr import ( "context" + "time" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" ) @@ -13,8 +14,16 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) XXX ec.client = client } -func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) XXXTestStartInternal() error { - return eb.startInternal() +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) XXXTestSetTTL(ttl time.Duration) { + tr.ttl = ttl +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) XXXDeliverBlock(blockHeight int64) { + tr.mb.Deliver(blockHeight) +} + +func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) XXXTestStartInternal(ctx context.Context) error { + return eb.startInternal(ctx) } func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) XXXTestCloseInternal() error { @@ -33,7 +42,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) XXX return ec.closeInternal() } -func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) XXXTestResendUnconfirmed() error { +func (er *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) XXXTestResendUnconfirmed() error { return er.resendUnconfirmed() } diff --git a/common/txmgr/tracker.go b/common/txmgr/tracker.go new file mode 100644 index 00000000000..f143f639aa8 --- /dev/null +++ b/common/txmgr/tracker.go @@ -0,0 +1,344 @@ +package txmgr + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + "github.com/smartcontractkit/chainlink/v2/common/types" + + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +const ( + // defaultTTL is the default time to live for abandoned transactions + // After this TTL, the TXM stops tracking abandoned Txs. + defaultTTL = 6 * time.Hour + // handleTxesTimeout represents a sanity limit on how long handleTxesByState + // should take to complete + handleTxesTimeout = 10 * time.Minute +) + +// AbandonedTx is a transaction who's 'FromAddress' was removed from the KeyStore(by the Node Operator). +// Thus, any new attempts for this Tx can't be signed/created. This means no fee bumping can be done. +// However, the Tx may still have live attempts in the chain's mempool, and could get confirmed on the +// chain as-is. Thus, the TXM should not directly discard this Tx. +type AbandonedTx[ADDR types.Hashable] struct { + id int64 + fromAddress ADDR +} + +// Tracker tracks all transactions which have abandoned fromAddresses. +// The fromAddresses can be deleted by Node Operators from the KeyStore. In such cases, +// existing in-flight transactions for these fromAddresses are considered abandoned too. +// Since such Txs can still have attempts on chain's mempool, these could still be confirmed. +// This tracker just tracks such Txs for some time, in case they get confirmed as-is. +type Tracker[ + CHAIN_ID types.ID, + ADDR types.Hashable, + TX_HASH types.Hashable, + BLOCK_HASH types.Hashable, + R txmgrtypes.ChainReceipt[TX_HASH, BLOCK_HASH], + SEQ types.Sequence, + FEE feetypes.Fee, +] struct { + services.StateMachine + txStore txmgrtypes.TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] + keyStore txmgrtypes.KeyStore[ADDR, CHAIN_ID, SEQ] + chainID CHAIN_ID + lggr logger.Logger + enabledAddrs map[ADDR]bool + txCache map[int64]AbandonedTx[ADDR] + ttl time.Duration + lock sync.Mutex + mb *utils.Mailbox[int64] + wg sync.WaitGroup + isStarted bool + ctx context.Context + ctxCancel context.CancelFunc +} + +func NewTracker[ + CHAIN_ID types.ID, + ADDR types.Hashable, + TX_HASH types.Hashable, + BLOCK_HASH types.Hashable, + R txmgrtypes.ChainReceipt[TX_HASH, BLOCK_HASH], + SEQ types.Sequence, + FEE feetypes.Fee, +]( + txStore txmgrtypes.TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE], + keyStore txmgrtypes.KeyStore[ADDR, CHAIN_ID, SEQ], + chainID CHAIN_ID, + lggr logger.Logger, +) *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { + return &Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{ + txStore: txStore, + keyStore: keyStore, + chainID: chainID, + lggr: logger.Named(lggr, "TxMgrTracker"), + enabledAddrs: map[ADDR]bool{}, + txCache: map[int64]AbandonedTx[ADDR]{}, + ttl: defaultTTL, + mb: utils.NewSingleMailbox[int64](), + lock: sync.Mutex{}, + wg: sync.WaitGroup{}, + } +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Start(_ context.Context) (err error) { + tr.lggr.Info("Abandoned transaction tracking enabled") + return tr.StartOnce("Tracker", func() error { + return tr.startInternal() + }) +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) startInternal() (err error) { + tr.lock.Lock() + defer tr.lock.Unlock() + + tr.ctx, tr.ctxCancel = context.WithCancel(context.Background()) + + if err := tr.setEnabledAddresses(); err != nil { + return fmt.Errorf("failed to set enabled addresses: %w", err) + } + tr.lggr.Info("Enabled addresses set") + + if err := tr.trackAbandonedTxes(tr.ctx); err != nil { + return fmt.Errorf("failed to track abandoned txes: %w", err) + } + + tr.isStarted = true + if len(tr.txCache) == 0 { + tr.lggr.Info("no abandoned txes found, skipping runLoop") + return nil + } + + tr.lggr.Infof("%d abandoned txes found, starting runLoop", len(tr.txCache)) + tr.wg.Add(1) + go tr.runLoop() + return nil +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close() error { + tr.lock.Lock() + defer tr.lock.Unlock() + return tr.StopOnce("Tracker", func() error { + return tr.closeInternal() + }) +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) closeInternal() error { + tr.lggr.Info("stopping tracker") + if !tr.isStarted { + return fmt.Errorf("tracker not started") + } + tr.ctxCancel() + tr.wg.Wait() + tr.isStarted = false + return nil +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) runLoop() { + defer tr.wg.Done() + ttlExceeded := time.NewTicker(tr.ttl) + defer ttlExceeded.Stop() + for { + select { + case <-tr.mb.Notify(): + for { + if tr.ctx.Err() != nil { + return + } + blockHeight, exists := tr.mb.Retrieve() + if !exists { + break + } + if err := tr.HandleTxesByState(tr.ctx, blockHeight); err != nil { + tr.lggr.Errorw(fmt.Errorf("failed to handle txes by state: %w", err).Error()) + } + } + case <-ttlExceeded.C: + tr.lggr.Info("ttl exceeded") + tr.MarkAllTxesFatal(tr.ctx) + return + case <-tr.ctx.Done(): + return + } + } +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetAbandonedAddresses() []ADDR { + tr.lock.Lock() + defer tr.lock.Unlock() + + if !tr.isStarted { + return []ADDR{} + } + + abandonedAddrs := make([]ADDR, len(tr.txCache)) + for _, atx := range tr.txCache { + abandonedAddrs = append(abandonedAddrs, atx.fromAddress) + } + return abandonedAddrs +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) IsStarted() bool { + tr.lock.Lock() + defer tr.lock.Unlock() + return tr.isStarted +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) setEnabledAddresses() error { + enabledAddrs, err := tr.keyStore.EnabledAddressesForChain(tr.chainID) + if err != nil { + return fmt.Errorf("failed to get enabled addresses for chain: %w", err) + } + + if len(enabledAddrs) == 0 { + tr.lggr.Warnf("enabled address list is empty") + } + + for _, addr := range enabledAddrs { + tr.enabledAddrs[addr] = true + } + return nil +} + +// trackAbandonedTxes called once to find and insert all abandoned txes into the tracker. +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) trackAbandonedTxes(ctx context.Context) (err error) { + if tr.isStarted { + return fmt.Errorf("tracker already started") + } + + tr.lggr.Info("Retrieving non fatal transactions from txStore") + nonFatalTxes, err := tr.txStore.GetNonFatalTransactions(ctx, tr.chainID) + if err != nil { + return fmt.Errorf("failed to get non fatal txes from txStore: %w", err) + } + + // insert abandoned txes + for _, tx := range nonFatalTxes { + if !tr.enabledAddrs[tx.FromAddress] { + tr.insertTx(tx) + } + } + + if err := tr.handleTxesByState(ctx, 0); err != nil { + return fmt.Errorf("failed to handle txes by state: %w", err) + } + + return nil +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) HandleTxesByState(ctx context.Context, blockHeight int64) error { + tr.lock.Lock() + defer tr.lock.Unlock() + tr.ctx, tr.ctxCancel = context.WithTimeout(ctx, handleTxesTimeout) + defer tr.ctxCancel() + return tr.handleTxesByState(ctx, blockHeight) +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) handleTxesByState(ctx context.Context, blockHeight int64) error { + tr.lggr.Info("Handling transactions by state") + + for id, atx := range tr.txCache { + tx, err := tr.txStore.GetTxByID(ctx, atx.id) + if err != nil { + return fmt.Errorf("failed to get tx by ID: %w", err) + } + + switch tx.State { + case TxConfirmed: + if err := tr.handleConfirmedTx(tx, blockHeight); err != nil { + return fmt.Errorf("failed to handle confirmed txes: %w", err) + } + case TxConfirmedMissingReceipt, TxUnconfirmed: + // Keep tracking tx + case TxInProgress, TxUnstarted: + // Tx could never be sent on chain even once. That means that we need to sign + // an attempt to even broadcast this Tx to the chain. Since the fromAddress + // is deleted, we can't sign it. + errMsg := "The FromAddress for this Tx was deleted before this Tx could be broadcast to the chain." + if err := tr.markTxFatal(ctx, tx, errMsg); err != nil { + return fmt.Errorf("failed to mark tx as fatal: %w", err) + } + delete(tr.txCache, id) + case TxFatalError: + delete(tr.txCache, id) + default: + tr.lggr.Errorw(fmt.Sprintf("unhandled transaction state: %v", tx.State)) + } + } + + return nil +} + +// handleConfirmedTx removes a transaction from the tracker if it's been finalized on chain +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) handleConfirmedTx( + tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], + blockHeight int64, +) error { + finalized, err := tr.txStore.IsTxFinalized(tr.ctx, blockHeight, tx.ID, tr.chainID) + if err != nil { + return fmt.Errorf("failed to check if tx is finalized: %w", err) + } + + if finalized { + delete(tr.txCache, tx.ID) + } + + return nil +} + +// insertTx inserts a transaction into the tracker as an AbandonedTx +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) insertTx( + tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { + if _, contains := tr.txCache[tx.ID]; contains { + return + } + + tr.txCache[tx.ID] = AbandonedTx[ADDR]{ + id: tx.ID, + fromAddress: tx.FromAddress, + } + tr.lggr.Debugw(fmt.Sprintf("inserted tx %v", tx.ID)) +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) markTxFatal(ctx context.Context, + tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], + errMsg string) error { + tx.Error.SetValid(errMsg) + + // Set state to TxInProgress so the tracker can attempt to mark it as fatal + tx.State = TxInProgress + if err := tr.txStore.UpdateTxFatalError(ctx, tx); err != nil { + return fmt.Errorf("failed to mark tx %v as abandoned: %w", tx.ID, err) + } + return nil +} + +func (tr *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) MarkAllTxesFatal(ctx context.Context) { + tr.lock.Lock() + defer tr.lock.Unlock() + errMsg := fmt.Sprintf( + "fromAddress for this Tx was deleted, and existing attempts onchain didn't finalize within %d hours, thus this Tx was abandoned.", + int(tr.ttl.Hours())) + + for _, atx := range tr.txCache { + tx, err := tr.txStore.GetTxByID(ctx, atx.id) + if err != nil { + tr.lggr.Errorw(fmt.Errorf("failed to get tx by ID: %w", err).Error()) + continue + } + + if err := tr.markTxFatal(ctx, tx, errMsg); err != nil { + tr.lggr.Errorw(fmt.Errorf("failed to mark tx as abandoned: %w", err).Error()) + } + } +} diff --git a/common/txmgr/txmgr.go b/common/txmgr/txmgr.go index 980067b5fbf..de96ca0ff05 100644 --- a/common/txmgr/txmgr.go +++ b/common/txmgr/txmgr.go @@ -10,14 +10,13 @@ import ( "time" "github.com/google/uuid" - pkgerrors "github.com/pkg/errors" - - "github.com/smartcontractkit/chainlink-relay/pkg/services" + nullv4 "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -48,6 +47,17 @@ type TxManager[ RegisterResumeCallback(fn ResumeCallback) SendNativeToken(ctx context.Context, chainID CHAIN_ID, from, to ADDR, value big.Int, gasLimit uint32) (etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) Reset(addr ADDR, abandon bool) error + // Find transactions by a field in the TxMeta blob and transaction states + FindTxesByMetaFieldAndStates(ctx context.Context, metaField string, metaValue string, states []txmgrtypes.TxState, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) + // Find transactions with a non-null TxMeta field that was provided by transaction states + FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []txmgrtypes.TxState, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) + // Find transactions with a non-null TxMeta field that was provided and a receipt block number greater than or equal to the one provided + FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) + // Find transactions loaded with transaction attempts and receipts by transaction IDs and states + FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx context.Context, ids []big.Int, states []txmgrtypes.TxState, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) + FindEarliestUnconfirmedBroadcastTime(ctx context.Context) (nullv4.Time, error) + FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context) (nullv4.Int, error) + CountTransactionsByState(ctx context.Context, state txmgrtypes.TxState) (count uint32, err error) } type reset struct { @@ -69,7 +79,7 @@ type Txm[ SEQ types.Sequence, FEE feetypes.Fee, ] struct { - utils.StartStopOnce + services.StateMachine logger logger.Logger txStore txmgrtypes.TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] config txmgrtypes.TransactionManagerChainConfig @@ -83,14 +93,15 @@ type Txm[ reset chan reset resumeCallback ResumeCallback - chStop chan struct{} + chStop services.StopChan chSubbed chan struct{} wg sync.WaitGroup reaper *Reaper[CHAIN_ID] - resender *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + resender *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] broadcaster *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] confirmer *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] + tracker *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] fwdMgr txmgrtypes.ForwarderManager[ADDR] txAttemptBuilder txmgrtypes.TxAttemptBuilder[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] sequenceSyncer SequenceSyncer[ADDR, TX_HASH, BLOCK_HASH, SEQ] @@ -125,7 +136,8 @@ func NewTxm[ sequenceSyncer SequenceSyncer[ADDR, TX_HASH, BLOCK_HASH, SEQ], broadcaster *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], confirmer *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], - resender *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], + resender *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], + tracker *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], ) *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { b := Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{ logger: lggr, @@ -146,6 +158,7 @@ func NewTxm[ broadcaster: broadcaster, confirmer: confirmer, resender: resender, + tracker: tracker, } if txCfg.ResendAfterThreshold() <= 0 { @@ -166,16 +179,24 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Start(ctx return b.StartOnce("Txm", func() error { var ms services.MultiStart if err := ms.Start(ctx, b.broadcaster); err != nil { - return pkgerrors.Wrap(err, "Txm: Broadcaster failed to start") + return fmt.Errorf("Txm: Broadcaster failed to start: %w", err) } if err := ms.Start(ctx, b.confirmer); err != nil { - return pkgerrors.Wrap(err, "Txm: Confirmer failed to start") + return fmt.Errorf("Txm: Confirmer failed to start: %w", err) } if err := ms.Start(ctx, b.txAttemptBuilder); err != nil { - return pkgerrors.Wrap(err, "Txm: Estimator failed to start") + return fmt.Errorf("Txm: Estimator failed to start: %w", err) + } + + /* Tracker currently disabled for BCI-2638; refactor required + b.logger.Info("Txm starting tracker") + if err := ms.Start(ctx, b.tracker); err != nil { + return fmt.Errorf("Txm: Tracker failed to start: %w", err) } + */ + b.logger.Info("Txm starting runLoop") b.wg.Add(1) go b.runLoop() <-b.chSubbed @@ -190,7 +211,7 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Start(ctx if b.fwdMgr != nil { if err := ms.Start(ctx, b.fwdMgr); err != nil { - return pkgerrors.Wrap(err, "Txm: ForwarderManager failed to start") + return fmt.Errorf("Txm: ForwarderManager failed to start: %w", err) } } @@ -221,10 +242,12 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Reset(addr // - marks all pending and inflight transactions fatally errored (note: at this point all transactions are either confirmed or fatally errored) // this must not be run while Broadcaster or Confirmer are running func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) abandon(addr ADDR) (err error) { - ctx, cancel := utils.StopChan(b.chStop).NewCtx() + ctx, cancel := services.StopChan(b.chStop).NewCtx() defer cancel() - err = b.txStore.Abandon(ctx, b.chainID, addr) - return pkgerrors.Wrapf(err, "abandon failed to update txes for key %s", addr.String()) + if err = b.txStore.Abandon(ctx, b.chainID, addr); err != nil { + return fmt.Errorf("abandon failed to update txes for key %s: %w", addr.String(), err) + } + return nil } func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close() (merr error) { @@ -241,15 +264,21 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close() (m } if b.fwdMgr != nil { if err := b.fwdMgr.Close(); err != nil { - merr = errors.Join(merr, pkgerrors.Wrap(err, "Txm: failed to stop ForwarderManager")) + merr = errors.Join(merr, fmt.Errorf("Txm: failed to stop ForwarderManager: %w", err)) } } b.wg.Wait() if err := b.txAttemptBuilder.Close(); err != nil { - merr = errors.Join(merr, pkgerrors.Wrap(err, "Txm: failed to close TxAttemptBuilder")) + merr = errors.Join(merr, fmt.Errorf("Txm: failed to close TxAttemptBuilder: %w", err)) + } + + /* Tracker currently disabled for BCI-2638; refactor required + if err := b.tracker.Close(); err != nil { + merr = errors.Join(merr, fmt.Errorf("Txm: failed to close Tracker: %w", err)) } + */ return nil }) @@ -316,18 +345,20 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) runLoop() wg.Add(2) go func() { defer wg.Done() + ctx, cancel := b.chStop.NewCtx() + defer cancel() // Retry indefinitely on failure backoff := utils.NewRedialBackoff() for { select { case <-time.After(backoff.Duration()): - if err := b.broadcaster.startInternal(); err != nil { - b.logger.Criticalw("Failed to start Broadcaster", "err", err) + if err := b.broadcaster.startInternal(ctx); err != nil { + logger.Criticalw(b.logger, "Failed to start Broadcaster", "err", err) b.SvcErrBuffer.Append(err) continue } return - case <-b.chStop: + case <-ctx.Done(): stopOnce.Do(func() { stopped = true }) return } @@ -341,7 +372,7 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) runLoop() select { case <-time.After(backoff.Duration()): if err := b.confirmer.startInternal(); err != nil { - b.logger.Criticalw("Failed to start Confirmer", "err", err) + logger.Criticalw(b.logger, "Failed to start Confirmer", "err", err) b.SvcErrBuffer.Append(err) continue } @@ -362,6 +393,8 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) runLoop() b.broadcaster.Trigger(address) case head := <-b.chHeads: b.confirmer.mb.Deliver(head) + // Tracker currently disabled for BCI-2638; refactor required + // b.tracker.mb.Deliver(head.BlockNumber()) case reset := <-b.reset: // This check prevents the weird edge-case where you can select // into this block after chStop has already been closed and the @@ -380,13 +413,21 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) runLoop() // be in an Unstarted state here, if execReset exited early. // // In this case, we don't care about stopping them since they are - // already "stopped", hence the usage of utils.EnsureClosed. - if err := utils.EnsureClosed(b.broadcaster); err != nil { - b.logger.Panicw(fmt.Sprintf("Failed to Close Broadcaster: %v", err), "err", err) + // already "stopped". + err := b.broadcaster.Close() + if err != nil && (!errors.Is(err, services.ErrAlreadyStopped) || !errors.Is(err, services.ErrCannotStopUnstarted)) { + b.logger.Errorw(fmt.Sprintf("Failed to Close Broadcaster: %v", err), "err", err) + } + err = b.confirmer.Close() + if err != nil && (!errors.Is(err, services.ErrAlreadyStopped) || !errors.Is(err, services.ErrCannotStopUnstarted)) { + b.logger.Errorw(fmt.Sprintf("Failed to Close Confirmer: %v", err), "err", err) } - if err := utils.EnsureClosed(b.confirmer); err != nil { - b.logger.Panicw(fmt.Sprintf("Failed to Close Confirmer: %v", err), "err", err) + /* Tracker currently disabled for BCI-2638; refactor required + err = b.tracker.Close() + if err != nil && (!errors.Is(err, services.ErrAlreadyStopped) || !errors.Is(err, services.ErrCannotStopUnstarted)) { + b.logger.Errorw(fmt.Sprintf("Failed to Close Tracker: %v", err), "err", err) } + */ return case <-keysChanged: // This check prevents the weird edge-case where you can select @@ -399,7 +440,7 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) runLoop() } enabledAddresses, err := b.keyStore.EnabledAddressesForChain(b.chainID) if err != nil { - b.logger.Criticalf("Failed to reload key states after key change") + logger.Criticalf(b.logger, "Failed to reload key states after key change") b.SvcErrBuffer.Append(err) continue } @@ -444,7 +485,7 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CreateTran var existingTx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] existingTx, err = b.txStore.FindTxWithIdempotencyKey(ctx, *txRequest.IdempotencyKey, b.chainID) if err != nil && !errors.Is(err, sql.ErrNoRows) { - return tx, pkgerrors.Wrap(err, "Failed to search for transaction with IdempotencyKey") + return tx, fmt.Errorf("Failed to search for transaction with IdempotencyKey: %w", err) } if existingTx != nil { b.logger.Infow("Found a Tx with IdempotencyKey. Returning existing Tx without creating a new one.", "IdempotencyKey", *txRequest.IdempotencyKey) @@ -470,31 +511,40 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CreateTran txRequest.ToAddress = txRequest.ForwarderAddress txRequest.EncodedPayload = fwdPayload } else { - b.logger.Errorf("Failed to use forwarder set upstream: %s", fwdErr.Error()) + b.logger.Errorf("Failed to use forwarder set upstream: %w", fwdErr.Error()) } } err = b.txStore.CheckTxQueueCapacity(ctx, txRequest.FromAddress, b.txConfig.MaxQueued(), b.chainID) if err != nil { - return tx, pkgerrors.Wrap(err, "Txm#CreateTransaction") + return tx, fmt.Errorf("Txm#CreateTransaction: %w", err) } tx, err = b.txStore.CreateTransaction(ctx, txRequest, b.chainID) - return + if err != nil { + return tx, err + } + + // Trigger the Broadcaster to check for new transaction + b.broadcaster.Trigger(txRequest.FromAddress) + + return tx, nil } // Calls forwarderMgr to get a proper forwarder for a given EOA. func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetForwarderForEOA(eoa ADDR) (forwarder ADDR, err error) { if !b.txConfig.ForwardersEnabled() { - return forwarder, pkgerrors.Errorf("Forwarding is not enabled, to enable set Transactions.ForwardersEnabled =true") + return forwarder, fmt.Errorf("forwarding is not enabled, to enable set Transactions.ForwardersEnabled =true") } forwarder, err = b.fwdMgr.ForwarderFor(eoa) return } func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) checkEnabled(addr ADDR) error { - err := b.keyStore.CheckEnabled(addr, b.chainID) - return pkgerrors.Wrapf(err, "cannot send transaction from %s on chain ID %s", addr, b.chainID.String()) + if err := b.keyStore.CheckEnabled(addr, b.chainID); err != nil { + return fmt.Errorf("cannot send transaction from %s on chain ID %s: %w", addr, b.chainID.String(), err) + } + return nil } // SendNativeToken creates a transaction that transfers the given value of native tokens @@ -511,7 +561,45 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SendNative Strategy: NewSendEveryStrategy(), } etx, err = b.txStore.CreateTransaction(ctx, txRequest, chainID) - return etx, pkgerrors.Wrap(err, "SendNativeToken failed to insert tx") + if err != nil { + return etx, fmt.Errorf("SendNativeToken failed to insert tx: %w", err) + } + + // Trigger the Broadcaster to check for new transaction + b.broadcaster.Trigger(from) + return etx, nil +} + +func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesByMetaFieldAndStates(ctx context.Context, metaField string, metaValue string, states []txmgrtypes.TxState, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) { + txes, err = b.txStore.FindTxesByMetaFieldAndStates(ctx, metaField, metaValue, states, chainID) + return +} + +func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []txmgrtypes.TxState, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) { + txes, err = b.txStore.FindTxesWithMetaFieldByStates(ctx, metaField, states, chainID) + return +} + +func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) { + txes, err = b.txStore.FindTxesWithMetaFieldByReceiptBlockNum(ctx, metaField, blockNum, chainID) + return +} + +func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx context.Context, ids []big.Int, states []txmgrtypes.TxState, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) { + txes, err = b.txStore.FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx, ids, states, chainID) + return +} + +func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindEarliestUnconfirmedBroadcastTime(ctx context.Context) (nullv4.Time, error) { + return b.txStore.FindEarliestUnconfirmedBroadcastTime(ctx, b.chainID) +} + +func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context) (nullv4.Int, error) { + return b.txStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, b.chainID) +} + +func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountTransactionsByState(ctx context.Context, state txmgrtypes.TxState) (count uint32, err error) { + return b.txStore.CountTransactionsByState(ctx, state, b.chainID) } type NullTxManager[ @@ -568,3 +656,27 @@ func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Hea } func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) RegisterResumeCallback(fn ResumeCallback) { } +func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindTxesByMetaFieldAndStates(ctx context.Context, metaField string, metaValue string, states []txmgrtypes.TxState, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) { + return txes, errors.New(n.ErrMsg) +} +func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []txmgrtypes.TxState, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) { + return txes, errors.New(n.ErrMsg) +} +func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) { + return txes, errors.New(n.ErrMsg) +} +func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx context.Context, ids []big.Int, states []txmgrtypes.TxState, chainID *big.Int) (txes []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) { + return txes, errors.New(n.ErrMsg) +} + +func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindEarliestUnconfirmedBroadcastTime(ctx context.Context) (nullv4.Time, error) { + return nullv4.Time{}, errors.New(n.ErrMsg) +} + +func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context) (nullv4.Int, error) { + return nullv4.Int{}, errors.New(n.ErrMsg) +} + +func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) CountTransactionsByState(ctx context.Context, state txmgrtypes.TxState) (count uint32, err error) { + return count, errors.New(n.ErrMsg) +} diff --git a/common/txmgr/types/client.go b/common/txmgr/types/client.go index 6d7f1c55558..b44c41e4176 100644 --- a/common/txmgr/types/client.go +++ b/common/txmgr/types/client.go @@ -6,10 +6,10 @@ import ( "math/big" "time" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/common/client" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) // TxmClient is a superset of all the methods needed for the txm @@ -49,7 +49,7 @@ type TransactionClient[ bathSize int, lggr logger.Logger, ) ( - txCodes []clienttypes.SendTxReturnCode, + txCodes []client.SendTxReturnCode, txErrs []error, broadcastTime time.Time, successfulTxIDs []int64, @@ -59,7 +59,7 @@ type TransactionClient[ tx Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], lggr logger.Logger, - ) (clienttypes.SendTxReturnCode, error) + ) (client.SendTxReturnCode, error) SendEmptyTransaction( ctx context.Context, newTxAttempt func(seq SEQ, feeLimit uint32, fee FEE, fromAddress ADDR) (attempt TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error), diff --git a/common/txmgr/types/forwarder_manager.go b/common/txmgr/types/forwarder_manager.go index 8d58f91295e..4d70b730004 100644 --- a/common/txmgr/types/forwarder_manager.go +++ b/common/txmgr/types/forwarder_manager.go @@ -1,13 +1,13 @@ package types import ( + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/services" ) //go:generate mockery --quiet --name ForwarderManager --output ./mocks/ --case=underscore type ForwarderManager[ADDR types.Hashable] interface { - services.ServiceCtx + services.Service ForwarderFor(addr ADDR) (forwarder ADDR, err error) // Converts payload to be forwarder-friendly ConvertPayload(dest ADDR, origPayload []byte) ([]byte, error) diff --git a/common/txmgr/types/mocks/tx_attempt_builder.go b/common/txmgr/types/mocks/tx_attempt_builder.go index f49b0025ae7..0f3d3e3fba1 100644 --- a/common/txmgr/types/mocks/tx_attempt_builder.go +++ b/common/txmgr/types/mocks/tx_attempt_builder.go @@ -5,8 +5,8 @@ package mocks import ( context "context" + logger "github.com/smartcontractkit/chainlink-common/pkg/logger" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" - logger "github.com/smartcontractkit/chainlink/v2/core/logger" mock "github.com/stretchr/testify/mock" diff --git a/common/txmgr/types/mocks/tx_store.go b/common/txmgr/types/mocks/tx_store.go index 02388e40f40..df1528a4c24 100644 --- a/common/txmgr/types/mocks/tx_store.go +++ b/common/txmgr/types/mocks/tx_store.go @@ -4,10 +4,13 @@ package mocks import ( context "context" + big "math/big" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" mock "github.com/stretchr/testify/mock" + null "gopkg.in/guregu/null.v4" + time "time" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" @@ -55,6 +58,30 @@ func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close() { _m.Called() } +// CountTransactionsByState provides a mock function with given fields: ctx, state, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountTransactionsByState(ctx context.Context, state txmgrtypes.TxState, chainID CHAIN_ID) (uint32, error) { + ret := _m.Called(ctx, state, chainID) + + var r0 uint32 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, txmgrtypes.TxState, CHAIN_ID) (uint32, error)); ok { + return rf(ctx, state, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, txmgrtypes.TxState, CHAIN_ID) uint32); ok { + r0 = rf(ctx, state, chainID) + } else { + r0 = ret.Get(0).(uint32) + } + + if rf, ok := ret.Get(1).(func(context.Context, txmgrtypes.TxState, CHAIN_ID) error); ok { + r1 = rf(ctx, state, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // CountUnconfirmedTransactions provides a mock function with given fields: ctx, fromAddress, chainID func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountUnconfirmedTransactions(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (uint32, error) { ret := _m.Called(ctx, fromAddress, chainID) @@ -141,6 +168,54 @@ func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) DeleteInPro return r0 } +// FindEarliestUnconfirmedBroadcastTime provides a mock function with given fields: ctx, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindEarliestUnconfirmedBroadcastTime(ctx context.Context, chainID CHAIN_ID) (null.Time, error) { + ret := _m.Called(ctx, chainID) + + var r0 null.Time + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, CHAIN_ID) (null.Time, error)); ok { + return rf(ctx, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, CHAIN_ID) null.Time); ok { + r0 = rf(ctx, chainID) + } else { + r0 = ret.Get(0).(null.Time) + } + + if rf, ok := ret.Get(1).(func(context.Context, CHAIN_ID) error); ok { + r1 = rf(ctx, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindEarliestUnconfirmedTxAttemptBlock provides a mock function with given fields: ctx, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context, chainID CHAIN_ID) (null.Int, error) { + ret := _m.Called(ctx, chainID) + + var r0 null.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, CHAIN_ID) (null.Int, error)); ok { + return rf(ctx, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, CHAIN_ID) null.Int); ok { + r0 = rf(ctx, chainID) + } else { + r0 = ret.Get(0).(null.Int) + } + + if rf, ok := ret.Get(1).(func(context.Context, CHAIN_ID) error); ok { + r1 = rf(ctx, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // FindLatestSequence provides a mock function with given fields: ctx, fromAddress, chainId func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindLatestSequence(ctx context.Context, fromAddress ADDR, chainId CHAIN_ID) (SEQ, error) { ret := _m.Called(ctx, fromAddress, chainId) @@ -179,32 +254,6 @@ func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindNextUns return r0 } -// FindReceiptsPendingConfirmation provides a mock function with given fields: ctx, blockNum, chainID -func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindReceiptsPendingConfirmation(ctx context.Context, blockNum int64, chainID CHAIN_ID) ([]txmgrtypes.ReceiptPlus[R], error) { - ret := _m.Called(ctx, blockNum, chainID) - - var r0 []txmgrtypes.ReceiptPlus[R] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int64, CHAIN_ID) ([]txmgrtypes.ReceiptPlus[R], error)); ok { - return rf(ctx, blockNum, chainID) - } - if rf, ok := ret.Get(0).(func(context.Context, int64, CHAIN_ID) []txmgrtypes.ReceiptPlus[R]); ok { - r0 = rf(ctx, blockNum, chainID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]txmgrtypes.ReceiptPlus[R]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, int64, CHAIN_ID) error); ok { - r1 = rf(ctx, blockNum, chainID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // FindTransactionsConfirmedInBlockRange provides a mock function with given fields: ctx, highBlockNumber, lowBlockNumber, chainID func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTransactionsConfirmedInBlockRange(ctx context.Context, highBlockNumber int64, lowBlockNumber int64, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { ret := _m.Called(ctx, highBlockNumber, lowBlockNumber, chainID) @@ -361,6 +410,136 @@ func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxWithS return r0, r1 } +// FindTxesByMetaFieldAndStates provides a mock function with given fields: ctx, metaField, metaValue, states, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesByMetaFieldAndStates(ctx context.Context, metaField string, metaValue string, states []txmgrtypes.TxState, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, metaField, metaValue, states, chainID) + + var r0 []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []txmgrtypes.TxState, *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, metaField, metaValue, states, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, []txmgrtypes.TxState, *big.Int) []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, metaField, metaValue, states, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, []txmgrtypes.TxState, *big.Int) error); ok { + r1 = rf(ctx, metaField, metaValue, states, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesPendingCallback provides a mock function with given fields: ctx, blockNum, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesPendingCallback(ctx context.Context, blockNum int64, chainID CHAIN_ID) ([]txmgrtypes.ReceiptPlus[R], error) { + ret := _m.Called(ctx, blockNum, chainID) + + var r0 []txmgrtypes.ReceiptPlus[R] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, CHAIN_ID) ([]txmgrtypes.ReceiptPlus[R], error)); ok { + return rf(ctx, blockNum, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, CHAIN_ID) []txmgrtypes.ReceiptPlus[R]); ok { + r0 = rf(ctx, blockNum, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]txmgrtypes.ReceiptPlus[R]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, CHAIN_ID) error); ok { + r1 = rf(ctx, blockNum, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesWithAttemptsAndReceiptsByIdsAndState provides a mock function with given fields: ctx, ids, states, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx context.Context, ids []big.Int, states []txmgrtypes.TxState, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, ids, states, chainID) + + var r0 []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []big.Int, []txmgrtypes.TxState, *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, ids, states, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, []big.Int, []txmgrtypes.TxState, *big.Int) []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, ids, states, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []big.Int, []txmgrtypes.TxState, *big.Int) error); ok { + r1 = rf(ctx, ids, states, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesWithMetaFieldByReceiptBlockNum provides a mock function with given fields: ctx, metaField, blockNum, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, metaField, blockNum, chainID) + + var r0 []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, metaField, blockNum, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *big.Int) []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, metaField, blockNum, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, int64, *big.Int) error); ok { + r1 = rf(ctx, metaField, blockNum, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesWithMetaFieldByStates provides a mock function with given fields: ctx, metaField, states, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []txmgrtypes.TxState, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, metaField, states, chainID) + + var r0 []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []txmgrtypes.TxState, *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, metaField, states, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []txmgrtypes.TxState, *big.Int) []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, metaField, states, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []txmgrtypes.TxState, *big.Int) error); ok { + r1 = rf(ctx, metaField, states, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // FindTxsRequiringGasBump provides a mock function with given fields: ctx, address, blockNum, gasBumpThreshold, depth, chainID func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxsRequiringGasBump(ctx context.Context, address ADDR, blockNum int64, gasBumpThreshold int64, depth int64, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { ret := _m.Called(ctx, address, blockNum, gasBumpThreshold, depth, chainID) @@ -439,6 +618,58 @@ func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetInProgre return r0, r1 } +// GetNonFatalTransactions provides a mock function with given fields: ctx, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetNonFatalTransactions(ctx context.Context, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, chainID) + + var r0 []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, CHAIN_ID) []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, CHAIN_ID) error); ok { + r1 = rf(ctx, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTxByID provides a mock function with given fields: ctx, id +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetTxByID(ctx context.Context, id int64) (*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, id) + + var r0 *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) (*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetTxInProgress provides a mock function with given fields: ctx, fromAddress func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetTxInProgress(ctx context.Context, fromAddress ADDR) (*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { ret := _m.Called(ctx, fromAddress) @@ -489,6 +720,30 @@ func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) HasInProgre return r0, r1 } +// IsTxFinalized provides a mock function with given fields: ctx, blockHeight, txID, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) IsTxFinalized(ctx context.Context, blockHeight int64, txID int64, chainID CHAIN_ID) (bool, error) { + ret := _m.Called(ctx, blockHeight, txID, chainID) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, int64, CHAIN_ID) (bool, error)); ok { + return rf(ctx, blockHeight, txID, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, int64, CHAIN_ID) bool); ok { + r0 = rf(ctx, blockHeight, txID, chainID) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, int64, CHAIN_ID) error); ok { + r1 = rf(ctx, blockHeight, txID, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // LoadTxAttempts provides a mock function with given fields: ctx, etx func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) LoadTxAttempts(ctx context.Context, etx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { ret := _m.Called(ctx, etx) @@ -709,6 +964,20 @@ func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateTxAtt return r0 } +// UpdateTxCallbackCompleted provides a mock function with given fields: ctx, pipelineTaskRunRid, chainId +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateTxCallbackCompleted(ctx context.Context, pipelineTaskRunRid uuid.UUID, chainId CHAIN_ID) error { + ret := _m.Called(ctx, pipelineTaskRunRid, chainId) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID, CHAIN_ID) error); ok { + r0 = rf(ctx, pipelineTaskRunRid, chainId) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // UpdateTxFatalError provides a mock function with given fields: ctx, etx func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateTxFatalError(ctx context.Context, etx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { ret := _m.Called(ctx, etx) diff --git a/common/txmgr/types/tx.go b/common/txmgr/types/tx.go index d95f07afabc..b8a16561d88 100644 --- a/common/txmgr/types/tx.go +++ b/common/txmgr/types/tx.go @@ -13,11 +13,11 @@ import ( "github.com/pkg/errors" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" clnull "github.com/smartcontractkit/chainlink/v2/core/null" - "github.com/smartcontractkit/chainlink/v2/core/services/pg/datatypes" ) // TxStrategy controls how txes are queued and sent @@ -91,6 +91,9 @@ type TxRequest[ADDR types.Hashable, TX_HASH types.Hashable] struct { // Checker defines the check that should be run before a transaction is submitted on chain. Checker TransmitCheckerSpec[ADDR] + + // Mark tx requiring callback + SignalCallback bool } // TransmitCheckerSpec defines the check that should be performed before a transaction is submitted @@ -207,7 +210,7 @@ type Tx[ // Marshalled TxMeta // Used for additional context around transactions which you want to log // at send time. - Meta *datatypes.JSON + Meta *sqlutil.JSON Subject uuid.NullUUID ChainID CHAIN_ID @@ -216,7 +219,12 @@ type Tx[ // TransmitChecker defines the check that should be performed before a transaction is submitted on // chain. - TransmitChecker *datatypes.JSON + TransmitChecker *sqlutil.JSON + + // Marks tx requiring callback + SignalCallback bool + // Marks tx callback as signaled + CallbackCompleted bool } func (e *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) GetError() error { @@ -242,7 +250,7 @@ func (e *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) GetMeta() (*TxMeta[A // GetLogger returns a new logger with metadata fields. func (e *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) GetLogger(lgr logger.Logger) logger.Logger { - lgr = lgr.With( + lgr = logger.With(lgr, "txID", e.ID, "sequence", e.Sequence, "checker", e.TransmitChecker, @@ -256,15 +264,15 @@ func (e *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) GetLogger(lgr logger } if meta != nil { - lgr = lgr.With("jobID", meta.JobID) + lgr = logger.With(lgr, "jobID", meta.JobID) if meta.RequestTxHash != nil { - lgr = lgr.With("requestTxHash", *meta.RequestTxHash) + lgr = logger.With(lgr, "requestTxHash", *meta.RequestTxHash) } if meta.RequestID != nil { id := *meta.RequestID - lgr = lgr.With("requestID", new(big.Int).SetBytes(id.Bytes()).String()) + lgr = logger.With(lgr, "requestID", new(big.Int).SetBytes(id.Bytes()).String()) } if len(meta.RequestIDs) != 0 { @@ -272,33 +280,33 @@ func (e *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) GetLogger(lgr logger for _, id := range meta.RequestIDs { ids = append(ids, new(big.Int).SetBytes(id.Bytes()).String()) } - lgr = lgr.With("requestIDs", strings.Join(ids, ",")) + lgr = logger.With(lgr, "requestIDs", strings.Join(ids, ",")) } if meta.UpkeepID != nil { - lgr = lgr.With("upkeepID", *meta.UpkeepID) + lgr = logger.With(lgr, "upkeepID", *meta.UpkeepID) } if meta.SubID != nil { - lgr = lgr.With("subID", *meta.SubID) + lgr = logger.With(lgr, "subID", *meta.SubID) } if meta.MaxLink != nil { - lgr = lgr.With("maxLink", *meta.MaxLink) + lgr = logger.With(lgr, "maxLink", *meta.MaxLink) } if meta.FwdrDestAddress != nil { - lgr = lgr.With("FwdrDestAddress", *meta.FwdrDestAddress) + lgr = logger.With(lgr, "FwdrDestAddress", *meta.FwdrDestAddress) } if len(meta.MessageIDs) > 0 { for _, mid := range meta.MessageIDs { - lgr = lgr.With("messageID", mid) + lgr = logger.With(lgr, "messageID", mid) } } if len(meta.SeqNumbers) > 0 { - lgr = lgr.With("SeqNumbers", meta.SeqNumbers) + lgr = logger.With(lgr, "SeqNumbers", meta.SeqNumbers) } } diff --git a/common/txmgr/types/tx_attempt_builder.go b/common/txmgr/types/tx_attempt_builder.go index 887219c490e..383b6d862f0 100644 --- a/common/txmgr/types/tx_attempt_builder.go +++ b/common/txmgr/types/tx_attempt_builder.go @@ -3,10 +3,10 @@ package types import ( "context" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" ) // TxAttemptBuilder takes the base unsigned transaction + optional parameters (tx type, gas parameters) @@ -23,7 +23,7 @@ type TxAttemptBuilder[ FEE feetypes.Fee, // FEE - chain fee type ] interface { // interfaces for running the underlying estimator - services.ServiceCtx + services.Service types.HeadTrackable[HEAD, BLOCK_HASH] // NewTxAttempt builds a transaction using the configured transaction type and fee estimator (new estimation) diff --git a/common/txmgr/types/tx_store.go b/common/txmgr/types/tx_store.go index 059a87d7ab2..57ecf28d589 100644 --- a/common/txmgr/types/tx_store.go +++ b/common/txmgr/types/tx_store.go @@ -6,10 +6,10 @@ import ( "time" "github.com/google/uuid" + "gopkg.in/guregu/null.v4" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) // TxStore is a superset of all the needed persistence layer methods @@ -35,14 +35,24 @@ type TxStore[ TxHistoryReaper[CHAIN_ID] TransactionStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, SEQ, FEE] - // methods for saving & retreiving receipts - FindReceiptsPendingConfirmation(ctx context.Context, blockNum int64, chainID CHAIN_ID) (receiptsPlus []ReceiptPlus[R], err error) + // Find confirmed txes beyond the minConfirmations param that require callback but have not yet been signaled + FindTxesPendingCallback(ctx context.Context, blockNum int64, chainID CHAIN_ID) (receiptsPlus []ReceiptPlus[R], err error) + // Update tx to mark that its callback has been signaled + UpdateTxCallbackCompleted(ctx context.Context, pipelineTaskRunRid uuid.UUID, chainId CHAIN_ID) error SaveFetchedReceipts(ctx context.Context, receipts []R, chainID CHAIN_ID) (err error) // additional methods for tx store management CheckTxQueueCapacity(ctx context.Context, fromAddress ADDR, maxQueuedTransactions uint64, chainID CHAIN_ID) (err error) Close() Abandon(ctx context.Context, id CHAIN_ID, addr ADDR) error + // Find transactions by a field in the TxMeta blob and transaction states + FindTxesByMetaFieldAndStates(ctx context.Context, metaField string, metaValue string, states []TxState, chainID *big.Int) (tx []*Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) + // Find transactions with a non-null TxMeta field that was provided by transaction states + FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []TxState, chainID *big.Int) (tx []*Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) + // Find transactions with a non-null TxMeta field that was provided and a receipt block number greater than or equal to the one provided + FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) (tx []*Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) + // Find transactions loaded with transaction attempts and receipts by transaction IDs and states + FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx context.Context, ids []big.Int, states []TxState, chainID *big.Int) (tx []*Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) } // TransactionStore contains the persistence layer methods needed to manage Txs and TxAttempts @@ -55,6 +65,7 @@ type TransactionStore[ FEE feetypes.Fee, ] interface { CountUnconfirmedTransactions(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (count uint32, err error) + CountTransactionsByState(ctx context.Context, state TxState, chainID CHAIN_ID) (count uint32, err error) CountUnstartedTransactions(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (count uint32, err error) CreateTransaction(ctx context.Context, txRequest TxRequest[ADDR, TX_HASH], chainID CHAIN_ID) (tx Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) DeleteInProgressAttempt(ctx context.Context, attempt TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error @@ -70,8 +81,12 @@ type TransactionStore[ FindTxWithSequence(ctx context.Context, fromAddress ADDR, seq SEQ) (etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) FindNextUnstartedTransactionFromAddress(ctx context.Context, etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], fromAddress ADDR, chainID CHAIN_ID) error FindTransactionsConfirmedInBlockRange(ctx context.Context, highBlockNumber, lowBlockNumber int64, chainID CHAIN_ID) (etxs []*Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) + FindEarliestUnconfirmedBroadcastTime(ctx context.Context, chainID CHAIN_ID) (null.Time, error) + FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context, chainID CHAIN_ID) (null.Int, error) GetTxInProgress(ctx context.Context, fromAddress ADDR) (etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) GetInProgressTxAttempts(ctx context.Context, address ADDR, chainID CHAIN_ID) (attempts []TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) + GetNonFatalTransactions(ctx context.Context, chainID CHAIN_ID) (txs []*Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) + GetTxByID(ctx context.Context, id int64) (tx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) HasInProgressTransaction(ctx context.Context, account ADDR, chainID CHAIN_ID) (exists bool, err error) LoadTxAttempts(ctx context.Context, etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error MarkAllConfirmedMissingReceipt(ctx context.Context, chainID CHAIN_ID) (err error) @@ -85,10 +100,13 @@ type TransactionStore[ SetBroadcastBeforeBlockNum(ctx context.Context, blockNum int64, chainID CHAIN_ID) error UpdateBroadcastAts(ctx context.Context, now time.Time, etxIDs []int64) error UpdateTxAttemptInProgressToBroadcast(ctx context.Context, etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], NewAttemptState TxAttemptState) error + // Update tx to mark that its callback has been signaled + UpdateTxCallbackCompleted(ctx context.Context, pipelineTaskRunRid uuid.UUID, chainId CHAIN_ID) error UpdateTxsUnconfirmed(ctx context.Context, ids []int64) error UpdateTxUnstartedToInProgress(ctx context.Context, etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt *TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error UpdateTxFatalError(ctx context.Context, etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error UpdateTxForRebroadcast(ctx context.Context, etx Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], etxAttempt TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error + IsTxFinalized(ctx context.Context, blockHeight int64, txID int64, chainID CHAIN_ID) (finalized bool, err error) } type TxHistoryReaper[CHAIN_ID types.ID] interface { @@ -106,8 +124,6 @@ type ReceiptPlus[R any] struct { FailOnRevert bool `db:"fail_on_revert"` } -type QueryerFunc = func(tx pg.Queryer) error - type ChainReceipt[TX_HASH, BLOCK_HASH types.Hashable] interface { GetStatus() uint64 GetTxHash() TX_HASH diff --git a/common/types/head.go b/common/types/head.go index 4d339b1cddb..c363fd5d0f2 100644 --- a/common/types/head.go +++ b/common/types/head.go @@ -1,5 +1,10 @@ package types +import ( + "math/big" + "time" +) + // Head provides access to a chain's head, as needed by the TxManager. // This is a generic interface which ALL chains will implement. // @@ -8,6 +13,9 @@ type Head[BLOCK_HASH Hashable] interface { // BlockNumber is the head's block number BlockNumber() int64 + // Timestamp the time of mining of the block + GetTimestamp() time.Time + // ChainLength returns the length of the chain followed by recursively looking up parents ChainLength() uint32 @@ -24,4 +32,8 @@ type Head[BLOCK_HASH Hashable] interface { // HashAtHeight returns the hash of the block at the given height, if it is in the chain. // If not in chain, returns the zero hash HashAtHeight(blockNum int64) BLOCK_HASH + + // Returns the total difficulty of the block. For chains who do not have a concept of block + // difficulty, return 0. + BlockDifficulty() *big.Int } diff --git a/common/types/head_tracker.go b/common/types/head_tracker.go index 811298f8956..d8fa8011783 100644 --- a/common/types/head_tracker.go +++ b/common/types/head_tracker.go @@ -3,7 +3,7 @@ package types import ( "context" - "github.com/smartcontractkit/chainlink/v2/core/services" + "github.com/smartcontractkit/chainlink-common/pkg/services" ) // HeadTracker holds and stores the block experienced by a particular node in a thread safe manner. @@ -11,7 +11,7 @@ import ( // //go:generate mockery --quiet --name HeadTracker --output ../mocks/ --case=underscore type HeadTracker[H Head[BLOCK_HASH], BLOCK_HASH Hashable] interface { - services.ServiceCtx + services.Service // Backfill given a head will fill in any missing heads up to the given depth // (used for testing) Backfill(ctx context.Context, headWithChain H, depth uint) (err error) @@ -68,7 +68,7 @@ type NewHeadHandler[H Head[BLOCK_HASH], BLOCK_HASH Hashable] func(ctx context.Co // //go:generate mockery --quiet --name HeadBroadcaster --output ../mocks/ --case=underscore type HeadBroadcaster[H Head[BLOCK_HASH], BLOCK_HASH Hashable] interface { - services.ServiceCtx + services.Service BroadcastNewLongestChain(H) HeadBroadcasterRegistry[H, BLOCK_HASH] } diff --git a/common/types/mocks/head.go b/common/types/mocks/head.go index 3cb303ef267..99d2a265b44 100644 --- a/common/types/mocks/head.go +++ b/common/types/mocks/head.go @@ -3,8 +3,12 @@ package mocks import ( - types "github.com/smartcontractkit/chainlink/v2/common/types" + big "math/big" + time "time" + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink/v2/common/types" ) // Head is an autogenerated mock type for the Head type @@ -12,6 +16,22 @@ type Head[BLOCK_HASH types.Hashable] struct { mock.Mock } +// BlockDifficulty provides a mock function with given fields: +func (_m *Head[BLOCK_HASH]) BlockDifficulty() *big.Int { + ret := _m.Called() + + var r0 *big.Int + if rf, ok := ret.Get(0).(func() *big.Int); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + return r0 +} + // BlockHash provides a mock function with given fields: func (_m *Head[BLOCK_HASH]) BlockHash() BLOCK_HASH { ret := _m.Called() @@ -100,6 +120,20 @@ func (_m *Head[BLOCK_HASH]) GetParentHash() BLOCK_HASH { return r0 } +// GetTimestamp provides a mock function with given fields: +func (_m *Head[BLOCK_HASH]) GetTimestamp() time.Time { + ret := _m.Called() + + var r0 time.Time + if rf, ok := ret.Get(0).(func() time.Time); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Time) + } + + return r0 +} + // HashAtHeight provides a mock function with given fields: blockNum func (_m *Head[BLOCK_HASH]) HashAtHeight(blockNum int64) BLOCK_HASH { ret := _m.Called(blockNum) diff --git a/common/types/receipt.go b/common/types/receipt.go new file mode 100644 index 00000000000..01d5a72def5 --- /dev/null +++ b/common/types/receipt.go @@ -0,0 +1,14 @@ +package types + +import "math/big" + +type Receipt[TX_HASH Hashable, BLOCK_HASH Hashable] interface { + GetStatus() uint64 + GetTxHash() TX_HASH + GetBlockNumber() *big.Int + IsZero() bool + IsUnmined() bool + GetFeeUsed() uint64 + GetTransactionIndex() uint + GetBlockHash() BLOCK_HASH +} diff --git a/common/types/test_utils.go b/common/types/test_utils.go new file mode 100644 index 00000000000..40560f7866c --- /dev/null +++ b/common/types/test_utils.go @@ -0,0 +1,16 @@ +package types + +import ( + "math" + "math/big" + "math/rand" +) + +func RandomID() ID { + id := rand.Int63n(math.MaxInt32) + 10000 + return big.NewInt(id) +} + +func NewIDFromInt(id int64) ID { + return big.NewInt(id) +} diff --git a/contracts/.solhint.json b/contracts/.solhint.json index 3b69ca6a7f2..e66b915d679 100644 --- a/contracts/.solhint.json +++ b/contracts/.solhint.json @@ -37,6 +37,7 @@ "chainlink-solidity/prefix-immutable-variables-with-i": "warn", "chainlink-solidity/all-caps-constant-storage-variables": "warn", "chainlink-solidity/no-hardhat-imports": "warn", - "chainlink-solidity/inherited-constructor-args-not-in-contract-definition": "warn" + "chainlink-solidity/inherited-constructor-args-not-in-contract-definition": "warn", + "chainlink-solidity/explicit-returns": "warn" } } diff --git a/contracts/.solhintignore b/contracts/.solhintignore index 1a308af94b1..ba2aac1fb3a 100644 --- a/contracts/.solhintignore +++ b/contracts/.solhintignore @@ -1,8 +1,8 @@ -# 351 warnings +# 344 warnings #./src/v0.8/automation -# 27 warnings -#./src/v0.8/functions +# Ignore Functions v1.0.0 code that was frozen after audit +./src/v0.8/functions/v1_0_0 # Ignore tests, this should not be the long term plan but is OK in the short term ./src/v0.8/**/*.t.sol diff --git a/contracts/GNUmakefile b/contracts/GNUmakefile index b477164a496..f5be193249c 100644 --- a/contracts/GNUmakefile +++ b/contracts/GNUmakefile @@ -18,6 +18,11 @@ ALL_FOUNDRY_PRODUCTS = llo-feeds functions shared snapshot: ## Make a snapshot for a specific product. export FOUNDRY_PROFILE=$(FOUNDRY_PROFILE) && forge snapshot --nmt "testFuzz_\w{1,}?" --snap gas-snapshots/$(FOUNDRY_PROFILE).gas-snapshot +.PHONY: snapshot-diff +snapshot-diff: ## Make a snapshot for a specific product. + export FOUNDRY_PROFILE=$(FOUNDRY_PROFILE) && forge snapshot --nmt "testFuzz_\w{1,}?" --diff gas-snapshots/$(FOUNDRY_PROFILE).gas-snapshot + + .PHONY: snapshot-all snapshot-all: ## Make a snapshot for all products. for foundry_profile in $(ALL_FOUNDRY_PRODUCTS) ; do \ @@ -34,11 +39,11 @@ abigen: ## Build & install abigen. .PHONY: mockery mockery: $(mockery) ## Install mockery. - go install github.com/vektra/mockery/v2@v2.28.1 + go install github.com/vektra/mockery/v2@v2.35.4 .PHONY: foundry foundry: ## Install foundry. - foundryup --version nightly-5be158ba6dc7c798a6f032026fe60fc01686b33b + foundryup --version nightly-09fe3e041369a816365a020f715ad6f94dbce9f2 .PHONY: foundry-refresh foundry-refresh: foundry diff --git a/contracts/STYLE.md b/contracts/STYLE.md deleted file mode 100644 index d9692a65210..00000000000 --- a/contracts/STYLE.md +++ /dev/null @@ -1,234 +0,0 @@ -# Solidity Style Guide - -## Background - -Our starting point is the [official Solidity Style Guide](https://solidity.readthedocs.io/en/v0.8.0/style-guide.html) and [ConsenSys's Secure Development practices](https://consensys.github.io/smart-contract-best-practices/), but we deviate in some ways. We lean heavily on [Prettier](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/.prettierrc) for formatting, and if you have to set up a new Solidity project we recommend starting with [our prettier config](https://github.com/smartcontractkit/chainlink/blob/develop/.prettierrc.js). We are trying to automate as much of this styleguide with Solhint as possible. - -### Code Organization - -- Group functionality together. E.g. Declare structs, events, and helper functions near the functions that use them. This is helpful when reading code because the related pieces are localized. It is also consistent with inheritance and libraries, which are separate pieces of code designed for a specific goal. - - Why not follow the Solidity recommendation of grouping by visibility? Visibility is clearly defined next to the method signature, making it trivial to check. However, searching can be deceiving because of inherited methods. Given this inconsistency in grouping, we find it easier to read and more consistent to organize code around functionality. Additionally, we recommend testing the public interface for any Solidity contract to ensure it only exposes expected methods. - -### Delineate Unaudited Code - -- In a large repo it is worthwhile to keep code that has not yet been audited separate from the code that has been audited. This allows you to easily keep track of which files need to be reviewed. - - E.g. we keep unaudited code in a directory named `dev`. Only once it has been audited we move the audited files out of `dev` and only then is it considered safe to deploy. - -## Variables - -### Visibility - -- All contract variables should be private. Getters should be explicitly written and documented when you want to expose a variable publicly. Whether a getter function reads from storage, a constant, or calculates a value from somewhere else, that’s all implementation details that should not be exposed to the consumer by casing or other conventions. - -Examples: - -Good: - -```javascript -uint256 private s_myVar; - -function getMyVar() external view returns(uint256){ - return s_myVar; -} -``` - -Bad: - -```javascript -uint256 public s_myVar; -``` - -### Naming and Casing - -- Function arguments are named like this: `argumentName`. No leading or trailing underscores necessary. -- Storage variables prefixed with an `s_` to make it clear that they live in storage and are expensive to read and write: `s_variableName`. They should always be private, and you should write explicit getters if you want to expose a storage variable. -- Immutable variables should be prefixed with an `i_` to make it clear that they are immutable. E.g. `i_decimalPlaces`. They should always be private, and you should write explicit getters if you want to expose an immutable variable. -- Internal/private constants should be all caps with underscores: `FOO_BAR`. Like other contract variables, constants should not be public. Create getter methods if you want to publicly expose constants. -- Explicitly declare variable size: `uint256` not just `uint`. In addition to being explicit, it matches the naming used to calculate function selectors. - -Examples: - -Good: - -```javascript -uint256 private s_myVar; -uint256 private immutable i_myImmutVar; -uint256 private constant MY_CONST_VAR; - -function multiplyMyVar(uint256 multiplier) external view returns(uint256){ - return multiplier * s_myVar; -} -``` - -Bad: - -```javascript -uint private s_myVar; -uint256 private immutable myImmutVar; -uint256 private constant s_myConstVar; - -function multiplyMyVar_(uint _multiplier) external view returns(uint256){ - return _mutliplier * s_myVar; -} -``` - -### Types - -- If you are storing an address and know/expect it to be of a type(or interface), make the variable that type. This more clearly documents the behavior of this variable than the `address` type and often leads to less casting code whenever the address is used. - -Examples: - -Good: - -```javascript -import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -// . -// . -// . -AggregatorV3Interface private s_priceFeed; - -constructor(address priceFeed) { - s_priceFeed = AggregatorV3Interface(priceFeed); -} -``` - -Bad: - -```javascript -import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -// . -// . -// . -address private s_priceFeed; - -constructor(address priceFeed) { - s_priceFeed = priceFeed; -} -``` - -## Functions - -### Visibility - -- Method visibility should always be explicitly declared. Contract’s [public interfaces should be tested](https://github.com/smartcontractkit/chainlink/blob/master/contracts/test/test-helpers/helpers.ts#L221) to enforce this and make sure that internal logic is not accidentally exposed. - -### Naming - -- Function names should start with imperative verbs, not nouns or other tenses. - - `requestData` not `dataRequest` - - `approve` not `approved` -- Prefix private and internal methods with an underscore. There should never be a publicly callable method starting with an underscore. - - E.g. `_setOwner(address)` -- Prefix your public getters with `get` and your public setters with `set`. - - `getConfig` and `setConfig`. - -## Modifiers - -- Only extract a modifier once a check is duplicated in multiple places. Modifiers arguably hurt readability, so we have found that they are not worth extracting until there is duplication. -- Modifiers should be treated as if they are view functions. They should not change state, only read it. While it is possible to change state in a modifier, it is unconventional and surprising. - -### Naming - -There are two common classes of modifiers, and their name should be prefixed accordingly to quickly represent their behavior: - -- Control flow modifiers: Prefix the modifier name with `if` in the case that a modifier only enables or disables the subsequent code in the modified method, but does not revert. -- Reverting modifiers: Prefix the modifier name with `validate` in the case that a modifier reverts if a condition is not met. - -### Return Values - -- If an address is cast as a contract type, return the type, do not cast back to the address type. This prevents the consumer of the method signature from having to cast again, but presents an equivalent API for off-chain APIs. Additionally it is a more declarative API, providing more context if we return a type. - -## Events - -- Events should only be triggered on state changes. If the value is set but not changed, we prefer avoiding a log emission indicating a change. (e.g. Either do not emit a log, or name the event `ConfigSet` instead of `ConfigUpdated`.) - -### Naming - -- When possible event names should correspond to the method they are in or the action that is being taken. Events preferably follow the format , where the action performed is the past tense of the imperative verb in the method name. e.g. calling `setConfig` should emit an event called `ConfigSet`, not `ConfigUpdated` in a method named `setConfig`. - -## Errors - -### Use Custom Errors - -Whenever possible (Solidity v0.8+) use [custom errors](https://blog.soliditylang.org/2021/04/21/custom-errors/) instead of emitting strings. This saves contract code size and simultaneously provides more informative error messages. - -### Expose Errors - -It is common to call a contract and then check the call succeeded: - -```javascript -(bool success, ) = to.call(data); -require(success, "Contract call failed"); -``` - -While this may look descriptive it swallows the error. Instead bubble up the error: - -```javascript -error YourError(bytes response); - -(bool success, bytes memory response) = to.call(data); -if (!success) { revert YourError(response); } -``` - -This will cost slightly more gas to copy the response into memory, but will ultimately make contract usage more understandable and easier to debug. Whether it is worth the extra gas is a judgement call you’ll have to make based on your needs. - -The original error will not be human readable in an off-chain explorer because it is RLP hex encoded but is easily decoded with standard Solidity ABI decoding tools, or a hex to UTF-8 converter and some basic ABI knowledge. - -## Control Flow - -### `if` Statements - -Always wrap the result statement of your `if` conditions in a closure, even if it is only one line. - -Bad: - -```javascript - if (condition) statement; -``` - -Good: - -```javascript - if (condition) { statement; } -``` - -## Interfaces - -### Scope - -- Interfaces should be as concise as reasonably possible. Break it up into smaller composable interfaces when that is sensible. - -### Naming - -- Up through Solidity version 0.8: Interfaces should be named `FooInterface`, this follows our historical naming pattern. -- Starting in Solidity v0.9: Interfaces should be named `IFoo` instead of `FooInterface`. This follows the patterns of popular [libraries like OpenZeppelin’s](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L9). - -## Vendor Dependencies - -- That’s it, vendor your Solidity dependencies. Supply chain attacks are all the rage these days. There is not yet a silver bullet for best way to vendor, it depends on the size of your project and your needs. You should be as explicit as possible about where the code comes from and make sure that this is enforced in some way; e.g. reference a hash. Some options: - - NPM packages work for repos already in the JavaScript ecosystem. If you go this route you should lock to a hash of the repo or use a proxy registry like GitHub Packages. - - Git submodules are great if you don’t mind git submodules. - - Copy and paste the code into a `vendor` directory. Record attribution of the code and license in the repo along with the commit or version that you pulled the code from. - -## Common Behaviors - -### Transferring Ownership - -- When transferring control, whether it is of a token or a role in a contract, prefer "safe ownership" transfer patterns where the recipient must accept ownership. This avoids accidentally burning the control. This is also inline with the secure pattern of [prefer pull over push](https://consensys.github.io/smart-contract-best-practices/recommendations/#favor-pull-over-push-for-external-calls). - -### Use Factories - -- If you expect multiple instances of a contract to be deployed, it is probably best to [build a factory](https://www.quicknode.com/guides/solidity/how-to-create-a-smart-contract-factory-in-solidity-using-hardhat) as this allows for simpler deployments later. Additionally it reduces the burden of verifying the correctness of the contract deployment. If many people have to deploy an instance of a contract then doing so with a contract makes it much easier for verification because instead of checking the code hash and/or the compiler and maybe the source code, you only have to check that the contract was deployed through a factory. -- Factories can add some pain when deploying with immutable variables. In general it is difficult to parse those out immutable variables from internal transactions. There is nothing inherently wrong with contracts deployed in this manner but at the time of writing they may not easily verify on Etherscan. - -### Call with Exact Gas - -- `call` accepts a gas parameter, but that parameter is a ceiling on gas usage. If a transaction does not have enough gas, `call` will simply provide as much gas as it safely can. This is unintuitive and can lead to transactions failing for unexpected reasons. We have [an implementation of `callWithExactGas`](https://github.com/smartcontractkit/chainlink/blob/075f3e2caf61b8685d2dc78714f1ee39764fda17/contracts/src/v0.8/KeeperRegistry.sol#L792) to ensure the precise gas amount requested is provided. - -## Picking a Pragma - -- If a contract or library is expected to be imported by outside parties then the pragma should be kept as loose as possible without sacrificing safety. We publish versions for every minor semver version of Solidity, and maintain a corresponding set of tests for each published version. - - Examples: libraries, interfaces, abstract contracts, and contracts expected to be inherited from -- Otherwise, Solidity contracts should have a pragma which is locked to a specific version. - - Example: Most concrete contracts. -- Avoid changing pragmas after audit. Unless there is a bug that has affects your contract, then you should try to stick to a known good pragma. In practice this means we typically only support one (occasionally two) pragma for any “major”(minor by semver naming) Solidity version. diff --git a/contracts/STYLE_GUIDE.md b/contracts/STYLE_GUIDE.md new file mode 100644 index 00000000000..3868117d4b9 --- /dev/null +++ b/contracts/STYLE_GUIDE.md @@ -0,0 +1,391 @@ +# Structure + +This guide is split into two sections: [Guidelines](#guidelines) and [Rules](#rules). +Guidelines are recommendations that should be followed but are hard to enforce in an automated way. +Rules are all enforced through CI, this can be through Solhint rules or other tools. + +## Background + +Our starting point is the [official Solidity Style Guide](https://docs.soliditylang.org/en/v0.8.21/style-guide.html) and [ConsenSys's Secure Development practices](https://consensys.github.io/smart-contract-best-practices/), but we deviate in some ways. We lean heavily on [Prettier](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/.prettierrc) for formatting, and if you have to set up a new Solidity project we recommend starting with [our prettier config](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/.prettierrc). We are trying to automate as much of this styleguide with Solhint as possible. + +This guide is not meant to be applied retroactively. There is no need to rewrite existing code to adhere to this guide, and when making (small) changes in existing files, it is not required to do so in accordance to this guide if it would conflict with other practices in that existing file. Consistency is preferred. + +We will be looking into `forge fmt`, but for now we still use `prettier`. + + +# Guidelines + +## Code Organization +- Group functionality together. E.g. Declare structs, events, and helper functions near the functions that use them. This is helpful when reading code because the related pieces are localized. It is also consistent with inheritance and libraries, which are separate pieces of code designed for a specific goal. +- 🤔Why not follow the Solidity recommendation of grouping by visibility? Visibility is clearly defined next to the method signature, making it trivial to check. However, searching can be deceiving because of inherited methods. Given this inconsistency in grouping, we find it easier to read and more consistent to organize code around functionality. Additionally, we recommend testing the public interface for any Solidity contract to ensure it only exposes expected methods. +- Follow the [Solidity folder structure CLIP](https://github.com/smartcontractkit/CLIPs/tree/main/clips/2023-04-13-solidity-folder-structure) + +### Delineate Unaudited Code + +- In a large repo it is worthwhile to keep code that has not yet been audited separate from the code that has been audited. This allows you to easily keep track of which files need to be reviewed. + - E.g. we keep unaudited code in a directory named `dev` that exists within each projects folder. Only once it has been audited we move the audited files out of `dev` and only then is it considered safe to deploy. + - This `dev` folder also has implications for when code is valid for bug bounties, so be extra careful to move functionality out of a `dev` folder. + + +## comments +- Besides comment above functions/structs, comments should live everywhere a reader might be confused. + Don’t overestimate the reader of your contract, expect confusion in many places and document accordingly. + This will help massively during audits and onboarding new team members. +- Headers should be used to group functionality, the following header style and length is recommended. + - Don’t use headers for a single function, or to say “getters”. Group by functionality e.g. the `Tokens and pools` , or `fees` logic within the CCIP OnRamp. + +```solidity + // ================================================================ + // │ Tokens and pools │ + // ================================================================ + +.... + + // ================================================================ + // │ Fees │ + // ================================================================ +``` + +## Variables + +- Function arguments are named like this: `argumentName`. No leading or trailing underscores necessary. +- Names should be explicit on the unit it contains, e.g. a network fee that is charged in USD cents + +```solidity +uint256 fee; // bad +uint256 networkFee; // bad +uint256 networkFeeUSD; // bad +uint256 networkFeeUSDCents; // good +``` + +### Types + +- If you are storing an address and know/expect it to be of a type(or interface), make the variable that type. This more clearly documents the behavior of this variable than the `address` type and often leads to less casting code whenever the address is used. + +### Structs + +- All structs should be packed to have the lowest memory footprint to reduce gas usage. Even structs that will never be written to storage should be packed. + - A contract can be considered a struct; it should also be packed to reduce gas cost. +- Structs should contain struct packing comments to clearly indicate the storage slot layout + - Using the exact characters from the example below will ensure visually appealing struct packing comments. + - Notice there is no line on the unpacked last `fee` item. +- Struct should contain comments, clearly indicating the denomination of values e.g. 0.01 USD if the variable name doesn’t already do that (which it should). + - Simple tool that could help packing structs and adding comments: https://github.com/RensR/Spack + +```solidity +/// @dev Struct to hold the fee configuration for a fee token, same as the FeeTokenConfig but with +/// token included so that an array of these can be passed in to setFeeTokenConfig to set the mapping +struct FeeTokenConfigArgs { + address token; // ────────────╮ Token address + uint32 networkFeeUSD; // │ Flat network fee to charge for messages, multiples of 0.01 USD + // │ multiline comments should work like this. More fee info + uint64 gasMultiplier; // ─────╯ Price multiplier for gas costs, 1e18 based so 11e17 = 10% extra cost + uint64 premiumMultiplier; // ─╮ Multiplier for fee-token-specific premiums + bool enabled; // ─────────────╯ Whether this fee token is enabled + uint256 fee; // The flat fee the user pays in juels +} +``` +## Functions + +### Naming + +- Function names should start with imperative verbs, not nouns or other tenses. + - `requestData` not `dataRequest` + - `approve` not `approved` + - `getFeeParameters` not `feeParameters` + +- Prefix your public getters with `get` and your public setters with `set`. + - `getConfig` and `setConfig`. + +### Return Values + +- If an address is cast as a contract type, return the type, do not cast back to the address type. + This prevents the consumer of the method signature from having to cast again, but presents an equivalent API for off-chain APIs. + Additionally, it is a more declarative API, providing more context if we return a type. + +## Modifiers + +- Only extract a modifier once a check is duplicated in multiple places. Modifiers arguably hurt readability, so we have found that they are not worth extracting until there is duplication. +- Modifiers should be treated as if they are view functions. They should not change state, only read it. While it is possible to change state in a modifier, it is unconventional and surprising. +- Modifiers tend to bloat contract size because the code is duplicated wherever the modifier is used. + +## Events + +- Events should only be triggered on state changes. If the value is set but not changed, we prefer avoiding a log emission indicating a change. (e.g. Either do not emit a log, or name the event `ConfigSet` instead of `ConfigUpdated`.) +- Events should be emitted for all state changes, not emitting should be an exception +- When possible event names should correspond to the method they are in or the action that is being taken. Events preferably follow the format , where the action performed is the past tense of the imperative verb in the method name. e.g. calling `setConfig` should emit an event called `ConfigSet`, not `ConfigUpdated` in a method named `setConfig`. + + +### Expose Errors + +It is common to call a contract and then check the call succeeded: + +```solidity +(bool success, ) = to.call(data); +require(success, "Contract call failed"); +``` + +While this may look descriptive it swallows the error. Instead, bubble up the error: + +```solidity +bool success; +retData = new bytes(maxReturnBytes); +assembly { + // call and return whether we succeeded. ignore return data + // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength) + success := call(gasLimit, target, 0, add(payload, 0x20), mload(payload), 0, 0) + + // limit our copy to maxReturnBytes bytes + let toCopy := returndatasize() + if gt(toCopy, maxReturnBytes) { + toCopy := maxReturnBytes + } + // Store the length of the copied bytes + mstore(retData, toCopy) + // copy the bytes from retData[0:_toCopy] + returndatacopy(add(retData, 0x20), 0, toCopy) +} +return (success, retData); +``` + +This will cost slightly more gas to copy the response into memory, but will ultimately make contract usage more understandable and easier to debug. Whether it is worth the extra gas is a judgement call you’ll have to make based on your needs. + +The original error will not be human-readable in an off-chain explorer because it is RLP hex encoded but is easily decoded with standard Solidity ABI decoding tools, or a hex to UTF-8 converter and some basic ABI knowledge. + + +## Interfaces + +- Interfaces should be as concise as reasonably possible. Break it up into smaller composable interfaces when that is sensible. + +## Dependencies + +- Prefer not reinventing the wheel, especially if there is an Openzeppelin wheel. +- The `shared` folder can be treated as a first party dependency and it is recommend to check if some functionality might already be in there before either writing it yourself or adding a third party dependency. +- When we have reinvented the wheel already (like with ownership), it is OK to keep using these contracts. If there are clear benefits of using another standard like OZ, we can deprecate the custom implementation and start using the new standard in all new projects. Migration will not be required unless there are serious issues with the old implementation. +- When the decision is made to use a new standard, it is no longer allowed to use the old standard for new projects. + +### Vendor dependencies + +- That’s it, vendor your Solidity dependencies. Supply chain attacks are all the rage these days. There is not yet a silver bullet for best way to vendor, it depends on the size of your project and your needs. You should be as explicit as possible about where the code comes from and make sure that this is enforced in some way; e.g. reference a hash. Some options: + - NPM packages work for repos already in the JavaScript ecosystem. If you go this route you should lock to a hash of the repo or use a proxy registry like GitHub Packages. + - Copy and paste the code into a `vendor` directory. Record attribution of the code and license in the repo along with the commit or version that you pulled the code from. + - Foundry uses git submodules for its dependencies. We only use the `forge-std` lib through submodules, we don’t import any non-Foundry-testing code through this method. + + +## Common Behaviors + +### Transferring Ownership + +- When transferring control, whether it is of a token or a role in a contract, prefer "safe ownership" transfer patterns where the recipient must accept ownership. This avoids accidentally burning the control. This is also inline with the secure pattern of [prefer pull over push](https://consensys.github.io/smart-contract-best-practices/recommendations/#favor-pull-over-push-for-external-calls). + +### Call with Exact Gas + +- `call` accepts a gas parameter, but that parameter is a ceiling on gas usage. If a transaction does not have enough gas, `call` will simply provide as much gas as it safely can. This is unintuitive and can lead to transactions failing for unexpected reasons. We have [an implementation of `callWithExactGas`](https://github.com/smartcontractkit/chainlink/blob/075f3e2caf61b8685d2dc78714f1ee39764fda17/contracts/src/v0.8/KeeperRegistry.sol#L792) to ensure the precise gas amount requested is provided. + +### Sending tokens + +- Prefer [ERC20.safeTransfer](https://docs.openzeppelin.com/contracts/2.x/api/token/erc20#SafeERC20) over ERC20.transfer + +### Gas golfing + +- Golf your code. Make it cheap, within reason. + - Focus on the hot path +- Most of the cost of executing Solidity is related to reading/writing storage +- Calling other contracts will also be costly +- Common types to safely use are + - uint40 for timestamps (or uint32 if you really need the space) + - uint96 for link, as there are only 1b link tokens +- prefer `++i` over `i++` +- If you’re unsure about golfing, ask in the #tech-solidity channel + +## Testing + +- Test using Foundry. +- Aim for at least 90% *useful* coverage as a baseline, but (near) 100% is achievable in Solidity. Always 100% test the critical path. + - Make sure to test for each event emitted + - Test each reverting path +- Consider fuzzing, start with stateless (very easy in Foundry) and if that works, try stateful fuzzing. +- Consider fork testing if applicable + +### Foundry + +- Create a Foundry profile for each project folder in `foundry.toml` +- Foundry tests live in the project folder in `src`, not in the `contracts/test/` folder +- Set the block number and timestamp. It is preferred to set these values to some reasonable value close to reality. +- There should be no code between `vm.expectEmit`/`vm.expectRevert` and the function call + +## Picking a Pragma + +- If a contract or library is expected to be imported by outside parties then the pragma should be kept as loose as possible without sacrificing safety. We publish versions for every minor semver version of Solidity, and maintain a corresponding set of tests for each published version. + - Examples: libraries, interfaces, abstract contracts, and contracts expected to be inherited from +- Otherwise, Solidity contracts should have a pragma which is locked to a specific version. + - Example: Most concrete contracts. +- Avoid changing pragmas after audit. Unless there is a bug that has affects your contract, then you should try to stick to a known good pragma. In practice this means we typically only support one (occasionally two) pragma for any “major”(minor by semver naming) Solidity version. +- The current advised pragma is `0.8.19` or higher, lower versions should be avoided when starting a new project. Newer versions can be considered. +- All contracts should have a SPDX license identifier. If unsure about which one to pick, please consult with legal. Most older contracts have been MIT, but some of the newer products have been using BUSL-1.1 + + +## Versioning + +Contracts should implement the following interface + +```solidity +interface ITypeAndVersion { + function typeAndVersion() external pure returns (string memory); +} +``` + +Here are some examples of what this should look like: + +```solidity +contract AccessControlledFoo is Foo { + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables + string public constant override typeAndVersion = "AccessControlledFoo 1.0.0"; +} + +contract OffchainAggregator is ITypeAndVersion { + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables + string public constant override typeAndVersion = "OffchainAggregator 1.0.0"; + + function getData() public returns(uint256) { + return 4; + } +} + +// Next version of Aggregator contract +contract SuperDuperAggregator is ITypeAndVersion { + /// This is a new contract that has not been released yet, so we + /// add a `-dev` suffix to the typeAndVersion. + + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables + string public constant override typeAndVersion = "SuperDuperAggregator 1.1.0-dev"; + + function getData() public returns(uint256) { + return 5; + } +} +``` + +All contracts will expose a `typeAndVersion` constant. +The string has the following format: `-` with the `-dev` part only being applicable to contracts that have not been fully released. +Try to fit it into 32 bytes to keep impact on contract sizes minimal. +Solhint will complain about a public constant variable that isn’t FULL_CAPS without the solhint-disable comment. + + + + + + + + + + +# Rules + +All rules have a `rule` tag which indicated how the rule is enforced. + + +## Comments + +- Comments should be in the `//` (default) or `///` (natspec) format, not the `/* */` format. + - rule: `tbd` +- Comments should follow [NatSpec](https://docs.soliditylang.org/en/latest/natspec-format.html) + - rule: `tbd` + +## Imports + +- Imports should always be explicit + - rule: `no-global-import` +- Imports have follow the following format: + - rule: `tbd` + +```solidity +import {IInterface} from "../interfaces/IInterface.sol"; + +import {AnythingElse} from "../code/AnythingElse.sol"; + +import {ThirdPartyCode} from "../../vendor/ThirdPartyCode.sol"; +``` + +- An example would be + +```solidity +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; +import {IPool} from "../interfaces/pools/IPool.sol"; + +import {AggregateRateLimiter} from "../AggregateRateLimiter.sol"; +import {Client} from "../libraries/Client.sol"; + +import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; +``` + +## Variables + +### Visibility + +All contract variables should be private by default. Getters should be explicitly written and documented when you want to expose a variable publicly. +Whether a getter function reads from storage, a constant, or calculates a value from somewhere else, that’s all implementation details that should not be exposed to the consumer by casing or other conventions. + +rule: tbd + +### Naming and Casing + +- Storage variables prefixed with an `s_` to make it clear that they live in storage and are expensive to read and write: `s_variableName`. They should always be private, and you should write explicit getters if you want to expose a storage variable. + - rule: `chainlink-solidity/prefix-storage-variables-with-s-underscore` +- Immutable variables should be prefixed with an `i_` to make it clear that they are immutable. E.g. `i_decimalPlaces`. They should always be private, and you should write explicit getters if you want to expose an immutable variable. + - rule: `chainlink-solidity/prefix-immutable-variables-with-i` +- Internal/private constants should be all caps with underscores: `FOO_BAR`. Like other contract variables, constants should not be public. Create getter methods if you want to publicly expose constants. + - rule: `chainlink-solidity/all-caps-constant-storage-variables` +- Explicitly declare variable size: `uint256` not just `uint`. In addition to being explicit, it matches the naming used to calculate function selectors. + - rule: `explicit-types` +- Mapping should always be named if Solidity allows it (≥0.8.18) + - rule: `tbd` + + +## Functions + +### Visibility + +- Method visibility should always be explicitly declared. + - rule: `state-visibility` + +- Prefix private and internal methods with an underscore. There should never be a publicly callable method starting with an underscore. + - E.g. `_setOwner(address)` + - rule: `chainlink-solidity/prefix-internal-functions-with-underscore` + +### Return values + +- Returned values should always be explicit. Using named return values and then returning with an empty return should be avoided + - rule: `chainlink-solidity/explicit-returns` + +```solidity +// Bad +function getNum() external view returns (uint64 num) { + num = 4; + return; +} + +// Good +function getNum() external view returns (uint64 num) { + num = 4; + return num; +} + +// Good +function getNum() external view returns (uint64 num) { + return 4; +} +``` + +## Errors + +Use [custom errors](https://blog.soliditylang.org/2021/04/21/custom-errors/) instead of emitting strings. This saves contract code size and simultaneously provides more informative error messages. + +rule: `custom-errors` + +## Interfaces + +Interfaces should be named `IFoo` instead of `FooInterface`. This follows the patterns of popular [libraries like OpenZeppelin’s](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L9). + +rule: `tbd` \ No newline at end of file diff --git a/contracts/ci.json b/contracts/ci.json index dd1fbb0a88f..f1eff76513c 100644 --- a/contracts/ci.json +++ b/contracts/ci.json @@ -6,23 +6,19 @@ "dir": "cross-version", "numOfSplits": 1 }, - { - "dir": "v0.6", - "numOfSplits": 1 - }, - { - "dir": "v0.7", - "numOfSplits": 1 - }, { "dir": "v0.8", - "numOfSplits": 8, + "numOfSplits": 6, "slowTests": [ - "Keeper", - "Cron.test", - "CronUpkeep.test", + "Cron", + "CronUpkeep", + "VRFSubscriptionBalanceMonitor", "EthBalanceMonitor", - "CanaryUpkeep" + "KeeperRegistrar", + "KeeperRegistry1_2", + "KeeperRegistry1_3", + "KeeperRegistry2_0", + "KeeperRegistry2_1" ] } ] diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 1228ce3ec03..cf27c0f2a8b 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -18,9 +18,9 @@ block_number = 12345 [profile.functions] solc_version = '0.8.19' -src = 'src/v0.8/functions' -test = 'src/v0.8/functions/tests' -gas_price = 3000000000 +src = 'src/v0.8/functions/dev/v1_X' +test = 'src/v0.8/functions/tests/v1_X' +gas_price = 3_000_000_000 # 3 gwei [profile.vrf] optimizer_runs = 1000 diff --git a/contracts/gas-snapshots/functions.gas-snapshot b/contracts/gas-snapshots/functions.gas-snapshot index b6298350604..4e6cf83cd1a 100644 --- a/contracts/gas-snapshots/functions.gas-snapshot +++ b/contracts/gas-snapshots/functions.gas-snapshot @@ -1,177 +1,207 @@ -FunctionsBilling_EstimateCost:test_EstimateCost_RevertsIfGasPriceAboveCeiling() (gas: 32391) -FunctionsBilling_EstimateCost:test_EstimateCost_Success() (gas: 53182) -FunctionsBilling_EstimateCost:test_EstimateCost_SuccessLowGasPrice() (gas: 53285) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumGoerli() (gas: 14578318) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumMainnet() (gas: 14578296) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumSepolia() (gas: 14578312) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseGoerli() (gas: 14589732) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseMainnet() (gas: 14589709) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseSepolia() (gas: 14589681) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismGoerli() (gas: 14589632) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismMainnet() (gas: 14589621) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismSepolia() (gas: 14589665) +FunctionsBilling_Constructor:test_Constructor_Success() (gas: 14812) +FunctionsBilling_DeleteCommitment:test_DeleteCommitment_RevertIfNotRouter() (gas: 13282) +FunctionsBilling_DeleteCommitment:test_DeleteCommitment_Success() (gas: 15897) +FunctionsBilling_EstimateCost:test_EstimateCost_RevertsIfGasPriceAboveCeiling() (gas: 32458) +FunctionsBilling_EstimateCost:test_EstimateCost_Success() (gas: 53807) +FunctionsBilling_EstimateCost:test_EstimateCost_SuccessLowGasPrice() (gas: 53910) +FunctionsBilling_GetAdminFee:test_GetAdminFee_Success() (gas: 18226) FunctionsBilling_GetConfig:test_GetConfig_Success() (gas: 23671) +FunctionsBilling_GetDONFee:test_GetDONFee_Success() (gas: 15792) +FunctionsBilling_GetWeiPerUnitLink:test_GetWeiPerUnitLink_Success() (gas: 31773) +FunctionsBilling_OracleWithdraw:test_OracleWithdraw_RevertIfInsufficientBalance() (gas: 70128) +FunctionsBilling_OracleWithdraw:test_OracleWithdraw_RevertWithNoBalance() (gas: 106285) +FunctionsBilling_OracleWithdraw:test_OracleWithdraw_SuccessTransmitterWithBalanceNoAmountGiven() (gas: 140164) +FunctionsBilling_OracleWithdraw:test_OracleWithdraw_SuccessTransmitterWithBalanceValidAmountGiven() (gas: 142492) FunctionsBilling_OracleWithdrawAll:test_OracleWithdrawAll_RevertIfNotOwner() (gas: 13296) -FunctionsBilling_OracleWithdrawAll:test_OracleWithdrawAll_SuccessPaysTransmittersWithBalance() (gas: 146657) -FunctionsClient_FulfillRequest:test_FulfillRequest_MaximumGas() (gas: 498113) -FunctionsClient_FulfillRequest:test_FulfillRequest_MinimumGas() (gas: 199261) -FunctionsClient__SendRequest:test__SendRequest_RevertIfInvalidCallbackGasLimit() (gas: 54991) -FunctionsCoordinator_Constructor:test_Constructor_Success() (gas: 11933) -FunctionsOracle_sendRequest:testEmptyRequestDataReverts() (gas: 13452) -FunctionsOracle_setDONPublicKey:testEmptyPublicKeyReverts() (gas: 10974) -FunctionsOracle_setDONPublicKey:testOnlyOwnerReverts() (gas: 11255) -FunctionsOracle_setDONPublicKey:testSetDONPublicKeySuccess() (gas: 126453) -FunctionsOracle_setDONPublicKey:testSetDONPublicKey_gas() (gas: 97580) -FunctionsOracle_setRegistry:testEmptyPublicKeyReverts() (gas: 10635) -FunctionsOracle_setRegistry:testOnlyOwnerReverts() (gas: 10927) -FunctionsOracle_setRegistry:testSetRegistrySuccess() (gas: 35791) -FunctionsOracle_setRegistry:testSetRegistry_gas() (gas: 31987) -FunctionsOracle_typeAndVersion:testTypeAndVersionSuccess() (gas: 6905) +FunctionsBilling_OracleWithdrawAll:test_OracleWithdrawAll_SuccessPaysTransmittersWithBalance() (gas: 147278) +FunctionsBilling_UpdateConfig:test_UpdateConfig_RevertIfNotOwner() (gas: 18974) +FunctionsBilling_UpdateConfig:test_UpdateConfig_Success() (gas: 38251) +FunctionsBilling__DisperseFeePool:test__DisperseFeePool_RevertIfNotSet() (gas: 8810) +FunctionsBilling__FulfillAndBill:test__FulfillAndBill_RevertIfInvalidCommitment() (gas: 13302) +FunctionsBilling__FulfillAndBill:test__FulfillAndBill_Success() (gas: 180763) +FunctionsBilling__StartBilling:test__FulfillAndBill_HasUniqueGlobalRequestId() (gas: 398312) +FunctionsClient_Constructor:test_Constructor_Success() (gas: 7573) +FunctionsClient_FulfillRequest:test_FulfillRequest_MaximumGas() (gas: 497786) +FunctionsClient_FulfillRequest:test_FulfillRequest_MinimumGas() (gas: 198990) +FunctionsClient_HandleOracleFulfillment:test_HandleOracleFulfillment_RevertIfNotRouter() (gas: 14623) +FunctionsClient_HandleOracleFulfillment:test_HandleOracleFulfillment_Success() (gas: 22923) +FunctionsClient__SendRequest:test__SendRequest_RevertIfInvalidCallbackGasLimit() (gas: 55059) +FunctionsCoordinator_Constructor:test_Constructor_Success() (gas: 12006) +FunctionsCoordinator_GetDONPublicKey:test_GetDONPublicKey_RevertIfEmpty() (gas: 15334) +FunctionsCoordinator_GetDONPublicKey:test_GetDONPublicKey_Success() (gas: 106506) +FunctionsCoordinator_GetThresholdPublicKey:test_GetThresholdPublicKey_RevertIfEmpty() (gas: 15313) +FunctionsCoordinator_GetThresholdPublicKey:test_GetThresholdPublicKey_Success() (gas: 656362) +FunctionsCoordinator_SetDONPublicKey:test_SetDONPublicKey_RevertNotOwner() (gas: 20364) +FunctionsCoordinator_SetDONPublicKey:test_SetDONPublicKey_Success() (gas: 101285) +FunctionsCoordinator_SetThresholdPublicKey:test_SetThresholdPublicKey_RevertNotOwner() (gas: 13892) +FunctionsCoordinator_SetThresholdPublicKey:test_SetThresholdPublicKey_Success() (gas: 651054) +FunctionsCoordinator_StartRequest:test_StartRequest_RevertIfNotRouter() (gas: 22703) +FunctionsCoordinator_StartRequest:test_StartRequest_Success() (gas: 108848) +FunctionsCoordinator__IsTransmitter:test__IsTransmitter_SuccessFound() (gas: 18957) +FunctionsCoordinator__IsTransmitter:test__IsTransmitter_SuccessNotFound() (gas: 19690) FunctionsRequest_DEFAULT_BUFFER_SIZE:test_DEFAULT_BUFFER_SIZE() (gas: 246) FunctionsRequest_EncodeCBOR:test_EncodeCBOR_Success() (gas: 223) FunctionsRequest_REQUEST_DATA_VERSION:test_REQUEST_DATA_VERSION() (gas: 225) -FunctionsRouter_Constructor:test_Constructor_Success() (gas: 12073) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedCostExceedsCommitment() (gas: 169899) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInsufficientGas() (gas: 160226) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInvalidCommitment() (gas: 38092) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInvalidRequestId() (gas: 35224) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedSubscriptionBalanceInvariant() (gas: 178394) -FunctionsRouter_Fulfill:test_Fulfill_RevertIfNotCommittedCoordinator() (gas: 28063) -FunctionsRouter_Fulfill:test_Fulfill_RevertIfPaused() (gas: 153900) -FunctionsRouter_Fulfill:test_Fulfill_SuccessClientNoLongerExists() (gas: 296711) -FunctionsRouter_Fulfill:test_Fulfill_SuccessFulfilled() (gas: 310303) -FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackReverts() (gas: 2484943) -FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackRunsOutOfGas() (gas: 515428) -FunctionsRouter_GetAdminFee:test_GetAdminFee_Success() (gas: 18005) -FunctionsRouter_GetAllowListId:test_GetAllowListId_Success() (gas: 12926) -FunctionsRouter_GetConfig:test_GetConfig_Success() (gas: 37136) -FunctionsRouter_GetContractById:test_GetContractById_RevertIfRouteDoesNotExist() (gas: 13871) -FunctionsRouter_GetContractById:test_GetContractById_SuccessIfRouteExists() (gas: 17395) -FunctionsRouter_GetProposedContractById:test_GetProposedContractById_RevertIfRouteDoesNotExist() (gas: 16382) -FunctionsRouter_GetProposedContractById:test_GetProposedContractById_SuccessIfRouteExists() (gas: 23934) -FunctionsRouter_GetProposedContractSet:test_GetProposedContractSet_Success() (gas: 25958) -FunctionsRouter_IsValidCallbackGasLimit:test_IsValidCallbackGasLimit_RevertGasLimitTooBig() (gas: 28034) -FunctionsRouter_IsValidCallbackGasLimit:test_IsValidCallbackGasLimit_RevertInvalidConfig() (gas: 41004) -FunctionsRouter_IsValidCallbackGasLimit:test_IsValidCallbackGasLimit_Success() (gas: 24551) -FunctionsRouter_Pause:test_Pause_RevertIfNotOwner() (gas: 13315) -FunctionsRouter_Pause:test_Pause_Success() (gas: 20298) -FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfEmptyAddress() (gas: 14768) -FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfExceedsMaxProposal() (gas: 21670) -FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfLengthMismatch() (gas: 14647) -FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfNotNewContract() (gas: 19025) -FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfNotOwner() (gas: 23369) -FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_Success() (gas: 118456) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfConsumerNotAllowed() (gas: 59304) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfDuplicateRequestId() (gas: 192796) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfEmptyData() (gas: 29405) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfIncorrectDonId() (gas: 57926) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfInsufficientSubscriptionBalance() (gas: 186209) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfInvalidCallbackGasLimit() (gas: 50902) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfInvalidDonId() (gas: 25061) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfNoSubscription() (gas: 29111) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfPaused() (gas: 34247) -FunctionsRouter_SendRequest:test_SendRequest_Success() (gas: 284999) -FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfConsumerNotAllowed() (gas: 65800) -FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfEmptyData() (gas: 35991) -FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfIncorrectDonId() (gas: 29897) -FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfInvalidCallbackGasLimit() (gas: 57488) -FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfInvalidDonId() (gas: 27482) -FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfNoSubscription() (gas: 35696) -FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfPaused() (gas: 40766) -FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_Success() (gas: 291551) -FunctionsRouter_SendRequestToProposed:test_SendRequest_RevertIfInsufficientSubscriptionBalance() (gas: 192728) -FunctionsRouter_SetAllowListId:test_SetAllowListId_Success() (gas: 30687) -FunctionsRouter_SetAllowListId:test_UpdateConfig_RevertIfNotOwner() (gas: 13380) -FunctionsRouter_Unpause:test_Unpause_RevertIfNotOwner() (gas: 13337) -FunctionsRouter_Unpause:test_Unpause_Success() (gas: 77421) +FunctionsRouter_Constructor:test_Constructor_Success() (gas: 12007) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedCostExceedsCommitment() (gas: 167477) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInsufficientGas() (gas: 157808) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInvalidCommitment() (gas: 38115) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInvalidRequestId() (gas: 35238) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedSubscriptionBalanceInvariant() (gas: 175953) +FunctionsRouter_Fulfill:test_Fulfill_RevertIfNotCommittedCoordinator() (gas: 28086) +FunctionsRouter_Fulfill:test_Fulfill_RevertIfPaused() (gas: 151496) +FunctionsRouter_Fulfill:test_Fulfill_SuccessClientNoLongerExists() (gas: 321059) +FunctionsRouter_Fulfill:test_Fulfill_SuccessFulfilled() (gas: 334680) +FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackReverts() (gas: 2509962) +FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackRunsOutOfGas() (gas: 540441) +FunctionsRouter_GetAdminFee:test_GetAdminFee_Success() (gas: 17983) +FunctionsRouter_GetAllowListId:test_GetAllowListId_Success() (gas: 12904) +FunctionsRouter_GetConfig:test_GetConfig_Success() (gas: 37159) +FunctionsRouter_GetContractById:test_GetContractById_RevertIfRouteDoesNotExist() (gas: 13849) +FunctionsRouter_GetContractById:test_GetContractById_SuccessIfRouteExists() (gas: 17373) +FunctionsRouter_GetProposedContractById:test_GetProposedContractById_RevertIfRouteDoesNotExist() (gas: 16383) +FunctionsRouter_GetProposedContractById:test_GetProposedContractById_SuccessIfRouteExists() (gas: 23935) +FunctionsRouter_GetProposedContractSet:test_GetProposedContractSet_Success() (gas: 25936) +FunctionsRouter_IsValidCallbackGasLimit:test_IsValidCallbackGasLimit_RevertGasLimitTooBig() (gas: 28103) +FunctionsRouter_IsValidCallbackGasLimit:test_IsValidCallbackGasLimit_RevertInvalidConfig() (gas: 41093) +FunctionsRouter_IsValidCallbackGasLimit:test_IsValidCallbackGasLimit_Success() (gas: 24626) +FunctionsRouter_Pause:test_Pause_RevertIfNotOwner() (gas: 13338) +FunctionsRouter_Pause:test_Pause_Success() (gas: 20344) +FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfEmptyAddress() (gas: 14791) +FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfExceedsMaxProposal() (gas: 21693) +FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfLengthMismatch() (gas: 14670) +FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfNotNewContract() (gas: 19048) +FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfNotOwner() (gas: 23392) +FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_Success() (gas: 118479) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfConsumerNotAllowed() (gas: 59347) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfDuplicateRequestId() (gas: 193436) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfEmptyData() (gas: 29426) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfIncorrectDonId() (gas: 57925) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfInsufficientSubscriptionBalance() (gas: 186932) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfInvalidCallbackGasLimit() (gas: 50947) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfInvalidDonId() (gas: 25082) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfNoSubscription() (gas: 29132) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfPaused() (gas: 34291) +FunctionsRouter_SendRequest:test_SendRequest_Success() (gas: 286243) +FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfConsumerNotAllowed() (gas: 65843) +FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfEmptyData() (gas: 36012) +FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfIncorrectDonId() (gas: 29896) +FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfInvalidCallbackGasLimit() (gas: 57533) +FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfInvalidDonId() (gas: 27503) +FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfNoSubscription() (gas: 35717) +FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfPaused() (gas: 40810) +FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_Success() (gas: 292812) +FunctionsRouter_SendRequestToProposed:test_SendRequest_RevertIfInsufficientSubscriptionBalance() (gas: 193424) +FunctionsRouter_SetAllowListId:test_SetAllowListId_Success() (gas: 30688) +FunctionsRouter_SetAllowListId:test_UpdateConfig_RevertIfNotOwner() (gas: 13403) +FunctionsRouter_Unpause:test_Unpause_RevertIfNotOwner() (gas: 13293) +FunctionsRouter_Unpause:test_Unpause_Success() (gas: 77400) FunctionsRouter_UpdateConfig:test_UpdateConfig_RevertIfNotOwner() (gas: 24437) -FunctionsRouter_UpdateConfig:test_UpdateConfig_Success() (gas: 60653) -FunctionsRouter_UpdateContracts:test_UpdateContracts_RevertIfNotOwner() (gas: 13293) -FunctionsRouter_UpdateContracts:test_UpdateContracts_Success() (gas: 38716) -FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfNotAllowedSender() (gas: 60324) -FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfPaused() (gas: 60962) -FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfSenderBecomesBlocked() (gas: 94675) -FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfSenderIsNotNewOwner() (gas: 62691) -FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_Success() (gas: 214576) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfMaximumConsumers() (gas: 137833) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfMaximumConsumersAfterConfigUpdate() (gas: 164777) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNoSubscription() (gas: 12926) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNotAllowedSender() (gas: 57789) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNotSubscriptionOwner() (gas: 87142) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfPaused() (gas: 18051) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_Success() (gas: 95481) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNoSubscription() (gas: 15085) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNotAllowedSender() (gas: 57929) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNotSubscriptionOwner() (gas: 89316) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfPaused() (gas: 20191) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfPendingRequests() (gas: 193763) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_SuccessForfeitAllBalanceAsDeposit() (gas: 114636) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_SuccessForfeitSomeBalanceAsDeposit() (gas: 125891) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_SuccessRecieveDeposit() (gas: 311486) +FunctionsRouter_UpdateConfig:test_UpdateConfig_Success() (gas: 60676) +FunctionsRouter_UpdateContracts:test_UpdateContracts_RevertIfNotOwner() (gas: 13336) +FunctionsRouter_UpdateContracts:test_UpdateContracts_Success() (gas: 38732) +FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfNotAllowedSender() (gas: 60326) +FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfPaused() (gas: 60987) +FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfSenderBecomesBlocked() (gas: 94677) +FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfSenderIsNotNewOwner() (gas: 62693) +FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_Success() (gas: 215197) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfMaximumConsumers() (gas: 137893) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfMaximumConsumersAfterConfigUpdate() (gas: 164837) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNoSubscription() (gas: 12946) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNotAllowedSender() (gas: 57809) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNotSubscriptionOwner() (gas: 87162) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfPaused() (gas: 18094) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_Success() (gas: 95480) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNoSubscription() (gas: 15041) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNotAllowedSender() (gas: 57885) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNotSubscriptionOwner() (gas: 89272) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfPaused() (gas: 20148) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfPendingRequests() (gas: 194325) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_SuccessForfeitAllBalanceAsDeposit() (gas: 114506) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_SuccessForfeitSomeBalanceAsDeposit() (gas: 125832) +FunctionsSubscriptions_CancelSubscription_ReceiveDeposit:test_CancelSubscription_SuccessRecieveDeposit() (gas: 74973) FunctionsSubscriptions_Constructor:test_Constructor_Success() (gas: 7654) -FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_RevertIfNotAllowedSender() (gas: 28637) -FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_RevertIfPaused() (gas: 17948) -FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_Success() (gas: 351723) -FunctionsSubscriptions_GetConsumer:test_GetConsumer_Success() (gas: 16225) -FunctionsSubscriptions_GetFlags:test_GetFlags_SuccessInvalidSubscription() (gas: 13100) -FunctionsSubscriptions_GetFlags:test_GetFlags_SuccessValidSubscription() (gas: 40858) -FunctionsSubscriptions_GetSubscription:test_GetSubscription_Success() (gas: 30959) -FunctionsSubscriptions_GetSubscriptionCount:test_GetSubscriptionCount_Success() (gas: 12967) -FunctionsSubscriptions_GetSubscriptionsInRange:test_GetSubscriptionsInRange_RevertIfEndIsAfterLastSubscription() (gas: 16523) -FunctionsSubscriptions_GetSubscriptionsInRange:test_GetSubscriptionsInRange_RevertIfStartIsAfterEnd() (gas: 13436) -FunctionsSubscriptions_GetSubscriptionsInRange:test_GetSubscriptionsInRange_Success() (gas: 59568) -FunctionsSubscriptions_GetTotalBalance:test_GetTotalBalance_Success() (gas: 15032) -FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNoCalldata(uint96) (runs: 256, μ: 28401, ~: 28401) -FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNoSubscription(uint96) (runs: 256, μ: 30913, ~: 30913) -FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNotLink(uint96) (runs: 256, μ: 14248, ~: 14248) -FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfPaused(uint96) (runs: 256, μ: 35870, ~: 35870) -FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_Success(uint96) (runs: 256, μ: 59685, ~: 59685) +FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_RevertIfNotAllowedSender() (gas: 28660) +FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_RevertIfPaused() (gas: 17994) +FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_Success() (gas: 351726) +FunctionsSubscriptions_GetConsumer:test_GetConsumer_Success() (gas: 16226) +FunctionsSubscriptions_GetFlags:test_GetFlags_SuccessInvalidSubscription() (gas: 13101) +FunctionsSubscriptions_GetFlags:test_GetFlags_SuccessValidSubscription() (gas: 40903) +FunctionsSubscriptions_GetSubscription:test_GetSubscription_Success() (gas: 30937) +FunctionsSubscriptions_GetSubscriptionCount:test_GetSubscriptionCount_Success() (gas: 12968) +FunctionsSubscriptions_GetSubscriptionsInRange:test_GetSubscriptionsInRange_RevertIfEndIsAfterLastSubscription() (gas: 16547) +FunctionsSubscriptions_GetSubscriptionsInRange:test_GetSubscriptionsInRange_RevertIfStartIsAfterEnd() (gas: 13459) +FunctionsSubscriptions_GetSubscriptionsInRange:test_GetSubscriptionsInRange_Success() (gas: 59592) +FunctionsSubscriptions_GetTotalBalance:test_GetTotalBalance_Success() (gas: 15010) +FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNoCalldata(uint96) (runs: 256, μ: 43508, ~: 45548) +FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNoSubscription(uint96) (runs: 256, μ: 46020, ~: 48060) +FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNotLink(uint96) (runs: 256, μ: 14295, ~: 14295) +FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfPaused(uint96) (runs: 256, μ: 51089, ~: 53040) +FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_Success(uint96) (runs: 256, μ: 86057, ~: 89604) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfAmountMoreThanBalance() (gas: 20745) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfBalanceInvariant() (gas: 189) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfNoAmount() (gas: 15638) -FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfPaused() (gas: 20833) +FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfPaused() (gas: 20856) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_SuccessPaysRecipient() (gas: 59732) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_SuccessSetsBalanceToZero() (gas: 57701) FunctionsSubscriptions_OwnerCancelSubscription:test_OwnerCancelSubscription_RevertIfNoSubscription() (gas: 12818) FunctionsSubscriptions_OwnerCancelSubscription:test_OwnerCancelSubscription_RevertIfNotOwner() (gas: 15549) FunctionsSubscriptions_OwnerCancelSubscription:test_OwnerCancelSubscription_Success() (gas: 54867) -FunctionsSubscriptions_OwnerCancelSubscription:test_OwnerCancelSubscription_SuccessDeletesSubscription() (gas: 49624) +FunctionsSubscriptions_OwnerCancelSubscription:test_OwnerCancelSubscription_SuccessDeletesSubscription() (gas: 49607) FunctionsSubscriptions_OwnerCancelSubscription:test_OwnerCancelSubscription_SuccessSubOwnerRefunded() (gas: 50896) -FunctionsSubscriptions_OwnerCancelSubscription:test_OwnerCancelSubscription_SuccessWhenRequestInFlight() (gas: 164300) +FunctionsSubscriptions_OwnerCancelSubscription:test_OwnerCancelSubscription_SuccessWhenRequestInFlight() (gas: 164812) FunctionsSubscriptions_OwnerWithdraw:test_OwnerWithdraw_RevertIfAmountMoreThanBalance() (gas: 17924) FunctionsSubscriptions_OwnerWithdraw:test_OwnerWithdraw_RevertIfBalanceInvariant() (gas: 210) -FunctionsSubscriptions_OwnerWithdraw:test_OwnerWithdraw_RevertIfNotOwner() (gas: 15533) +FunctionsSubscriptions_OwnerWithdraw:test_OwnerWithdraw_RevertIfNotOwner() (gas: 15555) FunctionsSubscriptions_OwnerWithdraw:test_OwnerWithdraw_SuccessIfNoAmount() (gas: 37396) FunctionsSubscriptions_OwnerWithdraw:test_OwnerWithdraw_SuccessIfRecipientAddressZero() (gas: 52130) FunctionsSubscriptions_OwnerWithdraw:test_OwnerWithdraw_SuccessPaysRecipient() (gas: 54413) FunctionsSubscriptions_OwnerWithdraw:test_OwnerWithdraw_SuccessSetsBalanceToZero() (gas: 37790) -FunctionsSubscriptions_PendingRequestExists:test_PendingRequestExists_SuccessFalseIfNoPendingRequests() (gas: 15025) -FunctionsSubscriptions_PendingRequestExists:test_PendingRequestExists_SuccessTrueIfPendingRequests() (gas: 175897) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfEmptyNewOwner() (gas: 27610) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfInvalidNewOwner() (gas: 57707) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfNoSubscription() (gas: 15000) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfNotAllowedSender() (gas: 75130) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfNotSubscriptionOwner() (gas: 17959) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfPaused() (gas: 20104) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_Success() (gas: 68217) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_SuccessChangeProposedOwner() (gas: 82791) -FunctionsSubscriptions_RecoverFunds:test_OwnerCancelSubscription_RevertIfNotOwner() (gas: 15532) -FunctionsSubscriptions_RecoverFunds:test_RecoverFunds_Success(uint64) (runs: 256, μ: 41699, ~: 41704) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfInvalidConsumer() (gas: 30238) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNoSubscription() (gas: 14997) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNotAllowedSender() (gas: 57778) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNotSubscriptionOwner() (gas: 87186) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfPaused() (gas: 18004) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfPendingRequests() (gas: 191195) +FunctionsSubscriptions_PendingRequestExists:test_PendingRequestExists_SuccessFalseIfNoPendingRequests() (gas: 14981) +FunctionsSubscriptions_PendingRequestExists:test_PendingRequestExists_SuccessTrueIfPendingRequests() (gas: 176494) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfEmptyNewOwner() (gas: 27611) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfInvalidNewOwner() (gas: 57709) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfNoSubscription() (gas: 15001) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfNotAllowedSender() (gas: 75131) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfNotSubscriptionOwner() (gas: 17960) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfPaused() (gas: 20128) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_Success() (gas: 68196) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_SuccessChangeProposedOwner() (gas: 82749) +FunctionsSubscriptions_RecoverFunds:test_OwnerCancelSubscription_RevertIfNotOwner() (gas: 15554) +FunctionsSubscriptions_RecoverFunds:test_RecoverFunds_Success(uint64) (runs: 256, μ: 41717, ~: 41721) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfInvalidConsumer() (gas: 30260) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNoSubscription() (gas: 15019) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNotAllowedSender() (gas: 57800) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNotSubscriptionOwner() (gas: 87208) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfPaused() (gas: 18049) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfPendingRequests() (gas: 191858) FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_Success() (gas: 41979) -FunctionsSubscriptions_SetFlags:test_SetFlags_RevertIfNoSubscription() (gas: 12847) -FunctionsSubscriptions_SetFlags:test_SetFlags_RevertIfNotOwner() (gas: 15640) -FunctionsSubscriptions_SetFlags:test_SetFlags_Success() (gas: 35549) -FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_RevertIfPaused() (gas: 25910) -FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_RevertIfTimeoutNotExceeded() (gas: 25239) -FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_RevertInvalidRequest() (gas: 28220) -FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_Success() (gas: 57752) -FunctionsSubscriptions_createSubscription:test_CreateSubscription_RevertIfNotAllowedSender() (gas: 26368) -FunctionsSubscriptions_createSubscription:test_CreateSubscription_RevertIfPaused() (gas: 15714) -FunctionsSubscriptions_createSubscription:test_CreateSubscription_Success() (gas: 152510) +FunctionsSubscriptions_SetFlags:test_SetFlags_RevertIfNoSubscription() (gas: 12891) +FunctionsSubscriptions_SetFlags:test_SetFlags_RevertIfNotOwner() (gas: 15684) +FunctionsSubscriptions_SetFlags:test_SetFlags_Success() (gas: 35594) +FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_RevertIfPaused() (gas: 25955) +FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_RevertIfTimeoutNotExceeded() (gas: 25261) +FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_RevertInvalidRequest() (gas: 28242) +FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_Success() (gas: 57754) +FunctionsSubscriptions_createSubscription:test_CreateSubscription_RevertIfNotAllowedSender() (gas: 26390) +FunctionsSubscriptions_createSubscription:test_CreateSubscription_RevertIfPaused() (gas: 15759) +FunctionsSubscriptions_createSubscription:test_CreateSubscription_Success() (gas: 152576) FunctionsTermsOfServiceAllowList_AcceptTermsOfService:testAcceptTermsOfService_InvalidSigner_vuln() (gas: 94815) FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfAcceptorIsNotSender() (gas: 25837) FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfBlockedSender() (gas: 44348) FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfInvalidSigner() (gas: 23597) FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfRecipientContractIsNotSender() (gas: 1866530) FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfRecipientIsNotSender() (gas: 26003) -FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_SuccessIfAcceptingForContract() (gas: 1946591) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_SuccessIfAcceptingForContract() (gas: 1946547) FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_SuccessIfAcceptingForSelf() (gas: 104851) FunctionsTermsOfServiceAllowList_BlockSender:test_BlockSender_RevertIfNotOwner() (gas: 15469) FunctionsTermsOfServiceAllowList_BlockSender:test_BlockSender_Success() (gas: 51794) @@ -188,8 +218,8 @@ FunctionsTermsOfServiceAllowList_UnblockSender:test_UnblockSender_Success() (gas FunctionsTermsOfServiceAllowList_UpdateConfig:test_UpdateConfig_RevertIfNotOwner() (gas: 13727) FunctionsTermsOfServiceAllowList_UpdateConfig:test_UpdateConfig_Success() (gas: 22073) Gas_AcceptTermsOfService:test_AcceptTermsOfService_Gas() (gas: 84675) -Gas_AddConsumer:test_AddConsumer_Gas() (gas: 79067) -Gas_CreateSubscription:test_CreateSubscription_Gas() (gas: 73353) -Gas_FundSubscription:test_FundSubscription_Gas() (gas: 38501) -Gas_SendRequest:test_SendRequest_MaximumGas() (gas: 964209) -Gas_SendRequest:test_SendRequest_MinimumGas() (gas: 156929) \ No newline at end of file +Gas_AddConsumer:test_AddConsumer_Gas() (gas: 79087) +Gas_CreateSubscription:test_CreateSubscription_Gas() (gas: 73375) +Gas_FundSubscription:test_FundSubscription_Gas() (gas: 38546) +Gas_SendRequest:test_SendRequest_MaximumGas() (gas: 979631) +Gas_SendRequest:test_SendRequest_MinimumGas() (gas: 157578) \ No newline at end of file diff --git a/contracts/gas-snapshots/llo-feeds.gas-snapshot b/contracts/gas-snapshots/llo-feeds.gas-snapshot index a9877fbe33c..ad9339a3410 100644 --- a/contracts/gas-snapshots/llo-feeds.gas-snapshot +++ b/contracts/gas-snapshots/llo-feeds.gas-snapshot @@ -1,3 +1,23 @@ +ByteUtilTest:test_readAddress() (gas: 542) +ByteUtilTest:test_readAddressMultiWord() (gas: 540) +ByteUtilTest:test_readAddressWithEmptyArray() (gas: 3274) +ByteUtilTest:test_readAddressWithNotEnoughBytes() (gas: 3314) +ByteUtilTest:test_readUint192Max() (gas: 485) +ByteUtilTest:test_readUint192Min() (gas: 508) +ByteUtilTest:test_readUint192MultiWord() (gas: 486) +ByteUtilTest:test_readUint192WithEmptyArray() (gas: 3274) +ByteUtilTest:test_readUint192WithNotEnoughBytes() (gas: 3314) +ByteUtilTest:test_readUint256Max() (gas: 502) +ByteUtilTest:test_readUint256Min() (gas: 546) +ByteUtilTest:test_readUint256MultiWord() (gas: 500) +ByteUtilTest:test_readUint256WithEmptyArray() (gas: 3296) +ByteUtilTest:test_readUint256WithNotEnoughBytes() (gas: 3293) +ByteUtilTest:test_readUint32Max() (gas: 507) +ByteUtilTest:test_readUint32Min() (gas: 487) +ByteUtilTest:test_readUint32MultiWord() (gas: 552) +ByteUtilTest:test_readUint32WithEmptyArray() (gas: 3253) +ByteUtilTest:test_readUint32WithNotEnoughBytes() (gas: 3272) +ByteUtilTest:test_readZeroAddress() (gas: 519) FeeManagerProcessFeeTest:test_DiscountIsAppliedForNative() (gas: 52282) FeeManagerProcessFeeTest:test_DiscountIsReturnedForNative() (gas: 52235) FeeManagerProcessFeeTest:test_DiscountIsReturnedForNativeWithSurcharge() (gas: 78440) diff --git a/contracts/gas-snapshots/shared.gas-snapshot b/contracts/gas-snapshots/shared.gas-snapshot index cf003c5a26d..6f307d257f5 100644 --- a/contracts/gas-snapshots/shared.gas-snapshot +++ b/contracts/gas-snapshots/shared.gas-snapshot @@ -25,6 +25,22 @@ BurnMintERC677_mint:testSenderNotMinterReverts() (gas: 11195) BurnMintERC677_supportsInterface:testConstructorSuccess() (gas: 8685) BurnMintERC677_transfer:testInvalidAddressReverts() (gas: 10639) BurnMintERC677_transfer:testTransferSuccess() (gas: 39462) +CallWithExactGas__callWithExactGas:test_CallWithExactGasReceiverErrorSuccess() (gas: 66918) +CallWithExactGas__callWithExactGas:test_CallWithExactGasSafeReturnDataExactGas() (gas: 22615) +CallWithExactGas__callWithExactGas:test_NoContractReverts() (gas: 11559) +CallWithExactGas__callWithExactGas:test_NoGasForCallExactCheckReverts() (gas: 12908) +CallWithExactGas__callWithExactGas:test_NotEnoughGasForCallReverts() (gas: 13361) +CallWithExactGas__callWithExactGas:test_callWithExactGasSuccess(bytes,bytes4) (runs: 256, μ: 15477, ~: 15418) +CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractExactGasSuccess() (gas: 19147) +CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractReceiverErrorSuccess() (gas: 67096) +CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractSuccess(bytes,bytes4) (runs: 256, μ: 15675, ~: 15616) +CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NoContractSuccess() (gas: 9816) +CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NoGasForCallExactCheckReturnFalseSuccess() (gas: 9578) +CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NotEnoughGasForCallReturnsFalseSuccess() (gas: 9890) +CallWithExactGas__callWithExactGasSafeReturnData:test_CallWithExactGasSafeReturnDataExactGas() (gas: 19017) +CallWithExactGas__callWithExactGasSafeReturnData:test_NoContractReverts() (gas: 13949) +CallWithExactGas__callWithExactGasSafeReturnData:test_NoGasForCallExactCheckReverts() (gas: 13239) +CallWithExactGas__callWithExactGasSafeReturnData:test_NotEnoughGasForCallReverts() (gas: 13670) OpStackBurnMintERC677_constructor:testConstructorSuccess() (gas: 1739317) OpStackBurnMintERC677_interfaceCompatibility:testBurnCompatibility() (gas: 263373) OpStackBurnMintERC677_interfaceCompatibility:testMintCompatibility() (gas: 137957) diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 521345ffc9e..5306827b8e3 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -105,6 +105,18 @@ let config = { }, }, }, + 'src/v0.8/vrf/dev/VRFCoordinatorV2_5.sol': { + version: '0.8.6', + settings: { + optimizer: { + enabled: true, + runs: 50, // see native_solc_compile_all_vrf + }, + metadata: { + bytecodeHash: 'none', + }, + }, + }, }, }, contractSizer: { diff --git a/contracts/package.json b/contracts/package.json index 6c902926c2b..1503c822ea4 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -6,7 +6,7 @@ "license": "MIT", "private": false, "scripts": { - "test": "hardhat test", + "test": "hardhat test --parallel", "lint": "eslint --ext js,ts .", "prettier:check": "prettier '**/*' --check --ignore-unknown", "prettier:write": "prettier '**/*' --write --ignore-unknown", @@ -18,7 +18,7 @@ "prepublishOnly": "pnpm compile && ./scripts/prepublish_generate_abi_folder", "publish-beta": "pnpm publish --tag beta", "publish-prod": "npm dist-tag add @chainlink/contracts@0.8.0 latest", - "solhint": "solhint --max-warnings 442 \"./src/v0.8/**/*.sol\"" + "solhint": "solhint --max-warnings 376 \"./src/v0.8/**/*.sol\"" }, "files": [ "src/v0.8", @@ -40,46 +40,47 @@ "@ethersproject/random": "~5.7.0", "@nomicfoundation/hardhat-network-helpers": "^1.0.9", "@nomiclabs/hardhat-ethers": "^2.2.3", - "@nomiclabs/hardhat-etherscan": "^3.1.0", + "@nomiclabs/hardhat-etherscan": "^3.1.7", "@nomiclabs/hardhat-waffle": "2.0.6", "@openzeppelin/hardhat-upgrades": "1.28.0", "@openzeppelin/test-helpers": "^0.5.16", "@typechain/ethers-v5": "^7.2.0", - "@typechain/hardhat": "^5.0.0", + "@typechain/hardhat": "^7.0.0", "@types/cbor": "5.0.1", - "@types/chai": "^4.3.9", - "@types/debug": "^4.1.10", - "@types/deep-equal-in-any-order": "^1.0.2", - "@types/mocha": "^10.0.3", - "@types/node": "^16.18.58", - "@typescript-eslint/eslint-plugin": "^6.8.0", - "@typescript-eslint/parser": "^6.8.0", + "@types/chai": "^4.3.10", + "@types/debug": "^4.1.12", + "@types/deep-equal-in-any-order": "^1.0.3", + "@types/mocha": "^10.0.4", + "@types/node": "^16.18.61", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", "abi-to-sol": "^0.6.6", + "cbor": "^5.2.0", "chai": "^4.3.10", "debug": "^4.3.4", - "eslint": "^8.51.0", + "eslint": "^8.53.0", "eslint-config-prettier": "^9.0.0", "deep-equal-in-any-order": "^2.0.6", "eslint-plugin-prettier": "^5.0.1", "ethereum-waffle": "^3.4.4", "ethers": "~5.7.2", - "hardhat": "~2.18.1", - "hardhat-abi-exporter": "^2.2.1", - "hardhat-contract-sizer": "^2.5.1", + "hardhat": "~2.19.1", + "hardhat-abi-exporter": "^2.10.1", + "hardhat-contract-sizer": "^2.10.0", "hardhat-gas-reporter": "^1.0.9", "hardhat-ignore-warnings": "^0.2.6", "istanbul": "^0.4.5", "moment": "^2.29.4", - "prettier": "^3.0.3", - "prettier-plugin-solidity": "1.1.3", + "prettier": "^3.1.0", + "prettier-plugin-solidity": "1.2.0", "rlp": "^2.2.7", - "solhint": "^3.6.2", - "solhint-plugin-chainlink-solidity": "git+https://github.com/smartcontractkit/chainlink-solhint-rules.git", - "solhint-plugin-prettier": "^0.0.5", + "solhint": "^4.0.0", + "solhint-plugin-chainlink-solidity": "git+https://github.com/smartcontractkit/chainlink-solhint-rules.git#v1.2.0", + "solhint-plugin-prettier": "^0.1.0", "solidity-coverage": "^0.8.5", "ts-node": "^10.9.1", "tslib": "^2.6.2", - "typechain": "^8.2.0", + "typechain": "^8.2.1", "typescript": "^5.2.2" }, "dependencies": { diff --git a/contracts/pnpm-lock.yaml b/contracts/pnpm-lock.yaml index 9d54cfa7743..fbae71fb3d4 100644 --- a/contracts/pnpm-lock.yaml +++ b/contracts/pnpm-lock.yaml @@ -39,55 +39,58 @@ devDependencies: version: 5.7.0 '@nomicfoundation/hardhat-network-helpers': specifier: ^1.0.9 - version: 1.0.9(hardhat@2.18.1) + version: 1.0.9(hardhat@2.19.1) '@nomiclabs/hardhat-ethers': specifier: ^2.2.3 - version: 2.2.3(ethers@5.7.2)(hardhat@2.18.1) + version: 2.2.3(ethers@5.7.2)(hardhat@2.19.1) '@nomiclabs/hardhat-etherscan': - specifier: ^3.1.0 - version: 3.1.0(hardhat@2.18.1) + specifier: ^3.1.7 + version: 3.1.7(hardhat@2.19.1) '@nomiclabs/hardhat-waffle': specifier: 2.0.6 - version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3)(@types/sinon-chai@3.2.8)(ethereum-waffle@3.4.4)(ethers@5.7.2)(hardhat@2.18.1) + version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3)(@types/sinon-chai@3.2.8)(ethereum-waffle@3.4.4)(ethers@5.7.2)(hardhat@2.19.1) '@openzeppelin/hardhat-upgrades': specifier: 1.28.0 - version: 1.28.0(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.0)(ethers@5.7.2)(hardhat@2.18.1) + version: 1.28.0(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.7)(ethers@5.7.2)(hardhat@2.19.1) '@openzeppelin/test-helpers': specifier: ^0.5.16 version: 0.5.16(bn.js@4.12.0) '@typechain/ethers-v5': specifier: ^7.2.0 - version: 7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.2.0)(typescript@5.2.2) + version: 7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2)(typescript@5.2.2) '@typechain/hardhat': - specifier: ^5.0.0 - version: 5.0.0(hardhat@2.18.1)(lodash@4.17.21)(typechain@8.2.0) + specifier: ^7.0.0 + version: 7.0.0(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@typechain/ethers-v5@7.2.0)(ethers@5.7.2)(hardhat@2.19.1)(typechain@8.3.2) '@types/cbor': specifier: 5.0.1 version: 5.0.1 '@types/chai': - specifier: ^4.3.9 - version: 4.3.9 + specifier: ^4.3.10 + version: 4.3.10 '@types/debug': - specifier: ^4.1.10 - version: 4.1.10 + specifier: ^4.1.12 + version: 4.1.12 '@types/deep-equal-in-any-order': - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^1.0.3 + version: 1.0.3 '@types/mocha': - specifier: ^10.0.3 - version: 10.0.3 + specifier: ^10.0.4 + version: 10.0.4 '@types/node': - specifier: ^16.18.58 - version: 16.18.58 + specifier: ^16.18.61 + version: 16.18.61 '@typescript-eslint/eslint-plugin': - specifier: ^6.8.0 - version: 6.8.0(@typescript-eslint/parser@6.8.0)(eslint@8.51.0)(typescript@5.2.2) + specifier: ^6.11.0 + version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/parser': - specifier: ^6.8.0 - version: 6.8.0(eslint@8.51.0)(typescript@5.2.2) + specifier: ^6.11.0 + version: 6.11.0(eslint@8.53.0)(typescript@5.2.2) abi-to-sol: specifier: ^0.6.6 version: 0.6.6 + cbor: + specifier: ^5.2.0 + version: 5.2.0 chai: specifier: ^4.3.10 version: 4.3.10 @@ -98,14 +101,14 @@ devDependencies: specifier: ^2.0.6 version: 2.0.6 eslint: - specifier: ^8.51.0 - version: 8.51.0 + specifier: ^8.53.0 + version: 8.53.0 eslint-config-prettier: specifier: ^9.0.0 - version: 9.0.0(eslint@8.51.0) + version: 9.0.0(eslint@8.53.0) eslint-plugin-prettier: specifier: ^5.0.1 - version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.51.0)(prettier@3.0.3) + version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.1.0) ethereum-waffle: specifier: ^3.4.4 version: 3.4.4(typescript@5.2.2) @@ -113,17 +116,17 @@ devDependencies: specifier: ~5.7.2 version: 5.7.2 hardhat: - specifier: ~2.18.1 - version: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) + specifier: ~2.19.1 + version: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) hardhat-abi-exporter: - specifier: ^2.2.1 - version: 2.2.1(hardhat@2.18.1) + specifier: ^2.10.1 + version: 2.10.1(hardhat@2.19.1) hardhat-contract-sizer: - specifier: ^2.5.1 - version: 2.5.1(hardhat@2.18.1) + specifier: ^2.10.0 + version: 2.10.0(hardhat@2.19.1) hardhat-gas-reporter: specifier: ^1.0.9 - version: 1.0.9(hardhat@2.18.1) + version: 1.0.9(debug@4.3.4)(hardhat@2.19.1) hardhat-ignore-warnings: specifier: ^0.2.6 version: 0.2.9 @@ -134,35 +137,35 @@ devDependencies: specifier: ^2.29.4 version: 2.29.4 prettier: - specifier: ^3.0.3 - version: 3.0.3 + specifier: ^3.1.0 + version: 3.1.0 prettier-plugin-solidity: - specifier: 1.1.3 - version: 1.1.3(prettier@3.0.3) + specifier: 1.2.0 + version: 1.2.0(prettier@3.1.0) rlp: specifier: ^2.2.7 version: 2.2.7 solhint: - specifier: ^3.6.2 - version: 3.6.2 + specifier: ^4.0.0 + version: 4.0.0 solhint-plugin-chainlink-solidity: - specifier: git+https://github.com/smartcontractkit/chainlink-solhint-rules.git - version: github.com/smartcontractkit/chainlink-solhint-rules/6229ce5d3cc3e4a2454411bebc887c5ca240dcf2 + specifier: git+https://github.com/smartcontractkit/chainlink-solhint-rules.git#v1.2.0 + version: github.com/smartcontractkit/chainlink-solhint-rules/cfc50b32f95b730304a50deb2e27e88d87115874 solhint-plugin-prettier: - specifier: ^0.0.5 - version: 0.0.5(prettier-plugin-solidity@1.1.3)(prettier@3.0.3) + specifier: ^0.1.0 + version: 0.1.0(prettier-plugin-solidity@1.2.0)(prettier@3.1.0) solidity-coverage: specifier: ^0.8.5 - version: 0.8.5(hardhat@2.18.1) + version: 0.8.5(hardhat@2.19.1) ts-node: specifier: ^10.9.1 - version: 10.9.1(@types/node@16.18.58)(typescript@5.2.2) + version: 10.9.1(@types/node@16.18.61)(typescript@5.2.2) tslib: specifier: ^2.6.2 version: 2.6.2 typechain: - specifier: ^8.2.0 - version: 8.2.0(typescript@5.2.2) + specifier: ^8.2.1 + version: 8.3.2(typescript@5.2.2) typescript: specifier: ^5.2.2 version: 5.2.2 @@ -321,13 +324,13 @@ packages: deprecated: Please use @ensdomains/ens-contracts dev: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.51.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.53.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.51.0 + eslint: 8.53.0 eslint-visitor-keys: 3.4.3 dev: true @@ -336,8 +339,8 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc@2.1.2: - resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + /@eslint/eslintrc@2.1.3: + resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 @@ -353,8 +356,8 @@ packages: - supports-color dev: true - /@eslint/js@8.51.0: - resolution: {integrity: sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==} + /@eslint/js@8.53.0: + resolution: {integrity: sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -786,11 +789,11 @@ packages: '@ethersproject/properties': 5.7.0 '@ethersproject/strings': 5.7.0 - /@humanwhocodes/config-array@0.11.11: - resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} + /@humanwhocodes/config-array@0.11.13: + resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} engines: {node: '>=10.10.0'} dependencies: - '@humanwhocodes/object-schema': 1.2.1 + '@humanwhocodes/object-schema': 2.0.1 debug: 4.3.4(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: @@ -802,8 +805,8 @@ packages: engines: {node: '>=12.22'} dev: true - /@humanwhocodes/object-schema@1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + /@humanwhocodes/object-schema@2.0.1: + resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} dev: true /@jridgewell/resolve-uri@3.1.1: @@ -1034,13 +1037,13 @@ packages: - utf-8-validate dev: true - /@nomicfoundation/hardhat-network-helpers@1.0.9(hardhat@2.18.1): + /@nomicfoundation/hardhat-network-helpers@1.0.9(hardhat@2.19.1): resolution: {integrity: sha512-OXWCv0cHpwLUO2u7bFxBna6dQtCC2Gg/aN/KtJLO7gmuuA28vgmVKYFRCDUqrbjujzgfwQ2aKyZ9Y3vSmDqS7Q==} peerDependencies: hardhat: ^2.9.5 dependencies: ethereumjs-util: 7.1.5 - hardhat: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) + hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) dev: true /@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.0: @@ -1149,37 +1152,37 @@ packages: '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.0 dev: true - /@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.18.1): + /@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.19.1): resolution: {integrity: sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==} peerDependencies: ethers: ^5.0.0 hardhat: ^2.0.0 dependencies: ethers: 5.7.2 - hardhat: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) + hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) dev: true - /@nomiclabs/hardhat-etherscan@3.1.0(hardhat@2.18.1): - resolution: {integrity: sha512-JroYgfN1AlYFkQTQ3nRwFi4o8NtZF7K/qFR2dxDUgHbCtIagkUseca9L4E/D2ScUm4XT40+8PbCdqZi+XmHyQA==} + /@nomiclabs/hardhat-etherscan@3.1.7(hardhat@2.19.1): + resolution: {integrity: sha512-tZ3TvSgpvsQ6B6OGmo1/Au6u8BrAkvs1mIC/eURA3xgIfznUZBhmpne8hv7BXUzw9xNL3fXdpOYgOQlVMTcoHQ==} peerDependencies: hardhat: ^2.0.4 dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/address': 5.7.0 - cbor: 5.2.0 + cbor: 8.1.0 chalk: 2.4.2 debug: 4.3.4(supports-color@8.1.1) fs-extra: 7.0.1 - hardhat: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) + hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) lodash: 4.17.21 semver: 6.3.0 - table: 6.8.0 - undici: 5.10.0 + table: 6.8.1 + undici: 5.19.1 transitivePeerDependencies: - supports-color dev: true - /@nomiclabs/hardhat-waffle@2.0.6(@nomiclabs/hardhat-ethers@2.2.3)(@types/sinon-chai@3.2.8)(ethereum-waffle@3.4.4)(ethers@5.7.2)(hardhat@2.18.1): + /@nomiclabs/hardhat-waffle@2.0.6(@nomiclabs/hardhat-ethers@2.2.3)(@types/sinon-chai@3.2.8)(ethereum-waffle@3.4.4)(ethers@5.7.2)(hardhat@2.19.1): resolution: {integrity: sha512-+Wz0hwmJGSI17B+BhU/qFRZ1l6/xMW82QGXE/Gi+WTmwgJrQefuBs1lIf7hzQ1hLk6hpkvb/zwcNkpVKRYTQYg==} peerDependencies: '@nomiclabs/hardhat-ethers': ^2.0.0 @@ -1188,11 +1191,11 @@ packages: ethers: ^5.0.0 hardhat: ^2.0.0 dependencies: - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.18.1) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.19.1) '@types/sinon-chai': 3.2.8 ethereum-waffle: 3.4.4(typescript@5.2.2) ethers: 5.7.2 - hardhat: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) + hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) dev: true /@openzeppelin/contract-loader@0.6.3: @@ -1223,7 +1226,7 @@ packages: - encoding dev: true - /@openzeppelin/hardhat-upgrades@1.28.0(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.0)(ethers@5.7.2)(hardhat@2.18.1): + /@openzeppelin/hardhat-upgrades@1.28.0(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.7)(ethers@5.7.2)(hardhat@2.19.1): resolution: {integrity: sha512-7sb/Jf+X+uIufOBnmHR0FJVWuxEs2lpxjJnLNN6eCJCP8nD0v+Ot5lTOW2Qb/GFnh+fLvJtEkhkowz4ZQ57+zQ==} hasBin: true peerDependencies: @@ -1236,15 +1239,15 @@ packages: '@nomiclabs/harhdat-etherscan': optional: true dependencies: - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.18.1) - '@nomiclabs/hardhat-etherscan': 3.1.0(hardhat@2.18.1) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.19.1) + '@nomiclabs/hardhat-etherscan': 3.1.7(hardhat@2.19.1) '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) '@openzeppelin/platform-deploy-client': 0.8.0(debug@4.3.4) '@openzeppelin/upgrades-core': 1.30.1 chalk: 4.1.2 debug: 4.3.4(supports-color@8.1.1) ethers: 5.7.2 - hardhat: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) + hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) proper-lockfile: 4.1.2 transitivePeerDependencies: - encoding @@ -1314,6 +1317,35 @@ packages: tslib: 2.6.2 dev: true + /@pnpm/config.env-replace@1.1.0: + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + dev: true + + /@pnpm/network.ca-file@1.0.2: + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + dependencies: + graceful-fs: 4.2.10 + dev: true + + /@pnpm/npm-conf@2.2.2: + resolution: {integrity: sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==} + engines: {node: '>=12'} + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + dev: true + + /@prettier/sync@0.3.0(prettier@3.1.0): + resolution: {integrity: sha512-3dcmCyAxIcxy036h1I7MQU/uEEBq8oLwf1CE3xeze+MPlgkdlb/+w6rGR/1dhp6Hqi17fRS6nvwnOzkESxEkOw==} + peerDependencies: + prettier: ^3.0.0 + dependencies: + prettier: 3.1.0 + dev: true + /@resolver-engine/core@0.3.3: resolution: {integrity: sha512-eB8nEbKDJJBi5p5SrvrvILn4a0h42bKtbCTri3ZxCGt6UvoQyp7HnGOfki944bUjBSHKK3RvgfViHn+kqdXtnQ==} dependencies: @@ -1474,6 +1506,12 @@ packages: antlr4ts: 0.5.0-alpha.4 dev: true + /@solidity-parser/parser@0.16.2: + resolution: {integrity: sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==} + dependencies: + antlr4ts: 0.5.0-alpha.4 + dev: true + /@szmarczak/http-timer@1.1.2: resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} engines: {node: '>=6'} @@ -1625,7 +1663,7 @@ packages: typechain: 3.0.0(typescript@5.2.2) dev: true - /@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.2.0)(typescript@5.2.2): + /@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2)(typescript@5.2.2): resolution: {integrity: sha512-jfcmlTvaaJjng63QsT49MT6R1HFhtO/TBMWbyzPFSzMmVIqb2tL6prnKBs4ZJrSvmgIXWy+ttSjpaxCTq8D/Tw==} peerDependencies: '@ethersproject/abi': ^5.0.0 @@ -1641,33 +1679,39 @@ packages: ethers: 5.7.2 lodash: 4.17.21 ts-essentials: 7.0.3(typescript@5.2.2) - typechain: 8.2.0(typescript@5.2.2) + typechain: 8.3.2(typescript@5.2.2) typescript: 5.2.2 dev: true - /@typechain/hardhat@5.0.0(hardhat@2.18.1)(lodash@4.17.21)(typechain@8.2.0): - resolution: {integrity: sha512-Pqk+KdREbU6Uk3en1Z5caQpWt2bKU+KTOi+6dZwcIXJpF1wKoAwF1cbaYSQEzrG4BSUTM1rHQhW5JHSfeqpsAg==} + /@typechain/hardhat@7.0.0(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@typechain/ethers-v5@7.2.0)(ethers@5.7.2)(hardhat@2.19.1)(typechain@8.3.2): + resolution: {integrity: sha512-XB79i5ewg9Met7gMVGfgVkmypicbnI25T5clJBEooMoW2161p4zvKFpoS2O+lBppQyMrPIZkdvl2M3LMDayVcA==} peerDependencies: - hardhat: ^2.0.10 - lodash: ^4.17.15 - typechain: ^7.0.0 + '@ethersproject/abi': ^5.4.7 + '@ethersproject/providers': ^5.4.7 + '@typechain/ethers-v5': ^11.0.0 + ethers: ^5.4.7 + hardhat: ^2.9.9 + typechain: ^8.2.0 dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/providers': 5.7.2 + '@typechain/ethers-v5': 7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2)(typescript@5.2.2) + ethers: 5.7.2 fs-extra: 9.1.0 - hardhat: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) - lodash: 4.17.21 - typechain: 8.2.0(typescript@5.2.2) + hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) + typechain: 8.3.2(typescript@5.2.2) dev: true /@types/bn.js@4.11.6: resolution: {integrity: sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true /@types/bn.js@5.1.1: resolution: {integrity: sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true /@types/cacheable-request@6.0.2: @@ -1675,34 +1719,34 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 16.18.58 + '@types/node': 16.18.61 '@types/responselike': 1.0.0 dev: true /@types/cbor@5.0.1: resolution: {integrity: sha512-zVqJy2KzusZPLOgyGJDnOIbu3DxIGGqxYbEwtEEe4Z+la8jwIhOyb+GMrlHafs5tvKruwf8f8qOYP6zTvse/pw==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true - /@types/chai@4.3.9: - resolution: {integrity: sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==} + /@types/chai@4.3.10: + resolution: {integrity: sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==} dev: true /@types/concat-stream@1.6.1: resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true - /@types/debug@4.1.10: - resolution: {integrity: sha512-tOSCru6s732pofZ+sMv9o4o3Zc+Sa8l3bxd/tweTQudFn06vAzb13ZX46Zi6m6EJ+RUbRTHvgQJ1gBtSgkaUYA==} + /@types/debug@4.1.12: + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} dependencies: '@types/ms': 0.7.31 dev: true - /@types/deep-equal-in-any-order@1.0.2: - resolution: {integrity: sha512-IUjUMsroT9qb8d7ySAl5V+iT/7UsJDpIFspTLxBWxCLqeJwwptSvmjvDBEEaZt0qodMyUSoOQkLhqZAkqt6dLg==} + /@types/deep-equal-in-any-order@1.0.3: + resolution: {integrity: sha512-jT0O3hAILDKeKbdWJ9FZLD0Xdfhz7hMvfyFlRWpirjiEVr8G+GZ4kVIzPIqM6x6Rpp93TNPgOAed4XmvcuV6Qg==} dev: true /@types/events@3.0.0: @@ -1712,7 +1756,7 @@ packages: /@types/form-data@0.0.33: resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true /@types/glob@7.1.1: @@ -1720,7 +1764,7 @@ packages: dependencies: '@types/events': 3.0.0 '@types/minimatch': 3.0.3 - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true /@types/http-cache-semantics@4.0.1: @@ -1734,7 +1778,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true /@types/lru-cache@5.1.1: @@ -1748,11 +1792,11 @@ packages: /@types/mkdirp@0.5.2: resolution: {integrity: sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true - /@types/mocha@10.0.3: - resolution: {integrity: sha512-RsOPImTriV/OE4A9qKjMtk2MnXiuLLbcO3nCXK+kvq4nr0iMfFgpjaX3MPLb6f7+EL1FGSelYvuJMV6REH+ZPQ==} + /@types/mocha@10.0.4: + resolution: {integrity: sha512-xKU7bUjiFTIttpWaIZ9qvgg+22O1nmbA+HRxdlR+u6TWsGfmFdXrheJoK4fFxrHNVIOBDvDNKZG+LYBpMHpX3w==} dev: true /@types/ms@0.7.31: @@ -1762,7 +1806,7 @@ packages: /@types/node-fetch@2.6.2: resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 form-data: 3.0.1 dev: true @@ -1774,8 +1818,8 @@ packages: resolution: {integrity: sha512-7xHmXm/QJ7cbK2laF+YYD7gb5MggHIIQwqyjin3bpEGiSuvScMQ5JZZXPvRipi1MwckTQbJZROMns/JxdnIL1Q==} dev: true - /@types/node@16.18.58: - resolution: {integrity: sha512-YGncyA25/MaVtQkjWW9r0EFBukZ+JulsLcVZBlGUfIb96OBMjkoRWwQo5IEWJ8Fj06Go3GHw+bjYDitv6BaGsA==} + /@types/node@16.18.61: + resolution: {integrity: sha512-k0N7BqGhJoJzdh6MuQg1V1ragJiXTh8VUBAZTWjJ9cUq23SG0F0xavOwZbhiP4J3y20xd6jxKx+xNUhkMAi76Q==} dev: true /@types/node@8.10.66: @@ -1785,7 +1829,7 @@ packages: /@types/pbkdf2@3.1.0: resolution: {integrity: sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true /@types/prettier@2.7.1: @@ -1799,26 +1843,26 @@ packages: /@types/readable-stream@2.3.15: resolution: {integrity: sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 safe-buffer: 5.1.2 dev: true /@types/resolve@0.0.8: resolution: {integrity: sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true /@types/secp256k1@4.0.3: resolution: {integrity: sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==} dependencies: - '@types/node': 16.18.58 + '@types/node': 16.18.61 dev: true /@types/semver@7.5.0: @@ -1828,7 +1872,7 @@ packages: /@types/sinon-chai@3.2.8: resolution: {integrity: sha512-d4ImIQbT/rKMG8+AXpmcan5T2/PNeSjrYhvkwet6z0p8kzYtfgA32xzOBlbU0yqJfq+/0Ml805iFoODO0LP5/g==} dependencies: - '@types/chai': 4.3.9 + '@types/chai': 4.3.10 '@types/sinon': 10.0.13 dev: true @@ -1842,8 +1886,8 @@ packages: resolution: {integrity: sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==} dev: true - /@typescript-eslint/eslint-plugin@6.8.0(@typescript-eslint/parser@6.8.0)(eslint@8.51.0)(typescript@5.2.2): - resolution: {integrity: sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==} + /@typescript-eslint/eslint-plugin@6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha @@ -1854,13 +1898,13 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.8.0 - '@typescript-eslint/parser': 6.8.0(eslint@8.51.0)(typescript@5.2.2) - '@typescript-eslint/scope-manager': 6.8.0 - '@typescript-eslint/type-utils': 6.8.0(eslint@8.51.0)(typescript@5.2.2) - '@typescript-eslint/utils': 6.8.0(eslint@8.51.0)(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.8.0 + '@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 6.11.0 + '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.11.0 debug: 4.3.4(supports-color@8.1.1) - eslint: 8.51.0 + eslint: 8.53.0 graphemer: 1.4.0 ignore: 5.2.4 natural-compare: 1.4.0 @@ -1871,8 +1915,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.8.0(eslint@8.51.0)(typescript@5.2.2): - resolution: {integrity: sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg==} + /@typescript-eslint/parser@6.11.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -1881,27 +1925,27 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.8.0 - '@typescript-eslint/types': 6.8.0 - '@typescript-eslint/typescript-estree': 6.8.0(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.8.0 + '@typescript-eslint/scope-manager': 6.11.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.11.0 debug: 4.3.4(supports-color@8.1.1) - eslint: 8.51.0 + eslint: 8.53.0 typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@6.8.0: - resolution: {integrity: sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==} + /@typescript-eslint/scope-manager@6.11.0: + resolution: {integrity: sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.8.0 - '@typescript-eslint/visitor-keys': 6.8.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/visitor-keys': 6.11.0 dev: true - /@typescript-eslint/type-utils@6.8.0(eslint@8.51.0)(typescript@5.2.2): - resolution: {integrity: sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==} + /@typescript-eslint/type-utils@6.11.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -1910,23 +1954,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.8.0(typescript@5.2.2) - '@typescript-eslint/utils': 6.8.0(eslint@8.51.0)(typescript@5.2.2) + '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2) + '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2) debug: 4.3.4(supports-color@8.1.1) - eslint: 8.51.0 + eslint: 8.53.0 ts-api-utils: 1.0.3(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@6.8.0: - resolution: {integrity: sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==} + /@typescript-eslint/types@6.11.0: + resolution: {integrity: sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==} engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@6.8.0(typescript@5.2.2): - resolution: {integrity: sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==} + /@typescript-eslint/typescript-estree@6.11.0(typescript@5.2.2): + resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -1934,8 +1978,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.8.0 - '@typescript-eslint/visitor-keys': 6.8.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/visitor-keys': 6.11.0 debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 @@ -1946,33 +1990,37 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@6.8.0(eslint@8.51.0)(typescript@5.2.2): - resolution: {integrity: sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==} + /@typescript-eslint/utils@6.11.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.51.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) '@types/json-schema': 7.0.12 '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 6.8.0 - '@typescript-eslint/types': 6.8.0 - '@typescript-eslint/typescript-estree': 6.8.0(typescript@5.2.2) - eslint: 8.51.0 + '@typescript-eslint/scope-manager': 6.11.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2) + eslint: 8.53.0 semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@6.8.0: - resolution: {integrity: sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==} + /@typescript-eslint/visitor-keys@6.11.0: + resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.8.0 + '@typescript-eslint/types': 6.11.0 eslint-visitor-keys: 3.4.3 dev: true + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: true + /@yarnpkg/lockfile@1.1.0: resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} dev: true @@ -1998,7 +2046,7 @@ packages: source-map-support: 0.5.21 optionalDependencies: prettier: 2.8.8 - prettier-plugin-solidity: 1.1.3(prettier@2.8.8) + prettier-plugin-solidity: 1.2.0(prettier@2.8.8) transitivePeerDependencies: - supports-color dev: true @@ -2147,11 +2195,6 @@ packages: dev: true optional: true - /ansi-colors@3.2.3: - resolution: {integrity: sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==} - engines: {node: '>=6'} - dev: true - /ansi-colors@3.2.4: resolution: {integrity: sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==} engines: {node: '>=6'} @@ -2184,11 +2227,6 @@ packages: engines: {node: '>=4'} dev: true - /ansi-regex@4.1.1: - resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} - engines: {node: '>=6'} - dev: true - /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2321,17 +2359,6 @@ packages: get-intrinsic: 1.2.1 dev: true - /array.prototype.reduce@1.0.4: - resolution: {integrity: sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.3 - es-array-method-boxes-properly: 1.0.0 - is-string: 1.0.7 - dev: true - /array.prototype.reduce@1.0.6: resolution: {integrity: sha512-UW+Mz8LG/sPSU8jRDCjVr6J/ZKAGpHfwrZ6kWTG5qCxIEiXdVshqGnu5vEZA8S1y6X4aCSbQZ0/EEsfvEvBiSg==} engines: {node: '>= 0.4'} @@ -3451,11 +3478,6 @@ packages: engines: {node: '>=4'} dev: true - /camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - dev: true - /camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} @@ -3487,6 +3509,13 @@ packages: nofilter: 1.0.4 dev: true + /cbor@8.1.0: + resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} + engines: {node: '>=12.19'} + dependencies: + nofilter: 3.1.0 + dev: true + /cbor@9.0.1: resolution: {integrity: sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ==} engines: {node: '>=16'} @@ -3606,21 +3635,6 @@ packages: parse5-htmlparser2-tree-adapter: 7.0.0 dev: true - /chokidar@3.3.0: - resolution: {integrity: sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.2 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.2.0 - optionalDependencies: - fsevents: 2.1.3 - dev: true - /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -3724,14 +3738,6 @@ packages: wrap-ansi: 2.1.0 dev: true - /cliui@5.0.0: - resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==} - dependencies: - string-width: 3.1.0 - strip-ansi: 5.2.0 - wrap-ansi: 5.1.0 - dev: true - /cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: @@ -3866,6 +3872,13 @@ packages: typedarray: 0.0.6 dev: true + /config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + dev: true + /constant-case@2.0.0: resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==} dependencies: @@ -4120,7 +4133,7 @@ packages: ms: 2.0.0 dev: true - /debug@3.2.6(supports-color@6.0.0): + /debug@3.2.6: resolution: {integrity: sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==} deprecated: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797) peerDependencies: @@ -4130,7 +4143,6 @@ packages: optional: true dependencies: ms: 2.1.3 - supports-color: 6.0.0 dev: true /debug@3.2.7: @@ -4324,6 +4336,17 @@ packages: engines: {node: '>=0.4.0'} dev: true + /delete-empty@3.0.0: + resolution: {integrity: sha512-ZUyiwo76W+DYnKsL3Kim6M/UOavPdBJgDYWOmuQhYaZvJH0AXAHbUNyEDtRbBra8wqqr686+63/0azfEk1ebUQ==} + engines: {node: '>=10'} + hasBin: true + dependencies: + ansi-colors: 4.1.3 + minimist: 1.2.8 + path-starts-with: 2.0.1 + rimraf: 2.7.1 + dev: true + /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -4371,11 +4394,6 @@ packages: - supports-color dev: true - /diff@3.5.0: - resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==} - engines: {node: '>=0.3.1'} - dev: true - /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -4491,10 +4509,6 @@ packages: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - /emoji-regex@7.0.3: - resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} - dev: true - /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true @@ -4558,36 +4572,6 @@ packages: is-arrayish: 0.2.1 dev: true - /es-abstract@1.20.3: - resolution: {integrity: sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - es-to-primitive: 1.2.1 - function-bind: 1.1.1 - function.prototype.name: 1.1.5 - get-intrinsic: 1.1.3 - get-symbol-description: 1.0.0 - has: 1.0.3 - has-property-descriptors: 1.0.0 - has-symbols: 1.0.3 - internal-slot: 1.0.3 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-weakref: 1.0.2 - object-inspect: 1.12.2 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.4.3 - safe-regex-test: 1.0.0 - string.prototype.trimend: 1.0.5 - string.prototype.trimstart: 1.0.5 - unbox-primitive: 1.0.2 - dev: true - /es-abstract@1.22.2: resolution: {integrity: sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==} engines: {node: '>= 0.4'} @@ -4723,16 +4707,16 @@ packages: source-map: 0.2.0 dev: true - /eslint-config-prettier@9.0.0(eslint@8.51.0): + /eslint-config-prettier@9.0.0(eslint@8.53.0): resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.51.0 + eslint: 8.53.0 dev: true - /eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.0.0)(eslint@8.51.0)(prettier@3.0.3): + /eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.1.0): resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -4746,9 +4730,9 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 8.51.0 - eslint-config-prettier: 9.0.0(eslint@8.51.0) - prettier: 3.0.3 + eslint: 8.53.0 + eslint-config-prettier: 9.0.0(eslint@8.53.0) + prettier: 3.1.0 prettier-linter-helpers: 1.0.0 synckit: 0.8.5 dev: true @@ -4766,18 +4750,19 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.51.0: - resolution: {integrity: sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==} + /eslint@8.53.0: + resolution: {integrity: sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.51.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) '@eslint-community/regexpp': 4.8.0 - '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.51.0 - '@humanwhocodes/config-array': 0.11.11 + '@eslint/eslintrc': 2.1.3 + '@eslint/js': 8.53.0 + '@humanwhocodes/config-array': 0.11.13 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 @@ -4889,29 +4874,31 @@ packages: js-sha3: 0.5.7 dev: true - /eth-gas-reporter@0.2.25: - resolution: {integrity: sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ==} + /eth-gas-reporter@0.2.27(debug@4.3.4): + resolution: {integrity: sha512-femhvoAM7wL0GcI8ozTdxfuBtBFJ9qsyIAsmKVjlWAHUbdnnXHt+lKzz/kmldM5lA9jLuNHGwuIxorNpLbR1Zw==} peerDependencies: '@codechecks/client': ^0.1.0 peerDependenciesMeta: '@codechecks/client': optional: true dependencies: - '@ethersproject/abi': 5.7.0 '@solidity-parser/parser': 0.14.3 + axios: 1.5.1(debug@4.3.4) cli-table3: 0.5.1 colors: 1.4.0 ethereum-cryptography: 1.1.2 - ethers: 4.0.49 + ethers: 5.7.2 fs-readdir-recursive: 1.1.0 lodash: 4.17.21 markdown-table: 1.1.3 - mocha: 7.2.0 + mocha: 10.2.0 req-cwd: 2.0.0 - request: 2.88.2 - request-promise-native: 1.0.9(request@2.88.2) sha1: 1.1.1 sync-request: 6.1.0 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate dev: true /eth-json-rpc-infura@3.2.1: @@ -5627,13 +5614,6 @@ packages: locate-path: 2.0.0 dev: true - /find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} - dependencies: - locate-path: 3.0.0 - dev: true - /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -5673,13 +5653,6 @@ packages: rimraf: 3.0.2 dev: true - /flat@4.1.1: - resolution: {integrity: sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==} - hasBin: true - dependencies: - is-buffer: 2.0.5 - dev: true - /flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true @@ -5835,15 +5808,6 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true - /fsevents@2.1.3: - resolution: {integrity: sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - deprecated: '"Please update to latest v2.3 or v2.2"' - requiresBuild: true - dev: true - optional: true - /fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -5856,16 +5820,6 @@ packages: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true - /function.prototype.name@1.1.5: - resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.3 - functions-have-names: 1.2.3 - dev: true - /function.prototype.name@1.1.6: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} @@ -5894,7 +5848,7 @@ packages: bip39: 2.5.0 cachedown: 1.0.0 clone: 2.1.2 - debug: 3.2.6(supports-color@6.0.0) + debug: 3.2.6 encoding-down: 5.0.4 eth-sig-util: 3.0.0 ethereumjs-abi: 0.6.8 @@ -5907,7 +5861,7 @@ packages: heap: 0.2.6 level-sublevel: 6.6.4 levelup: 3.1.1 - lodash: 4.17.20 + lodash: 4.17.21 lru-cache: 5.1.1 merkle-patricia-tree: 3.0.0 patch-package: 6.2.2 @@ -6039,17 +5993,6 @@ packages: path-is-absolute: 1.0.1 dev: true - /glob@7.1.3: - resolution: {integrity: sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - /glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} dependencies: @@ -6241,11 +6184,6 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - /growl@1.10.5: - resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==} - engines: {node: '>=4.x'} - dev: true - /handlebars@4.7.7: resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} engines: {node: '>=0.4.7'} @@ -6273,36 +6211,42 @@ packages: har-schema: 2.0.0 dev: true - /hardhat-abi-exporter@2.2.1(hardhat@2.18.1): - resolution: {integrity: sha512-Um7+RPvJEj+OqWjPoPKlTTkO1Akr10pqpgMk8Pw2jz2wrGv5XQBGNW5aQgGVDUosYktUIWDaEhcwwFKbFsir9A==} - engines: {node: '>=12.10.0'} + /hardhat-abi-exporter@2.10.1(hardhat@2.19.1): + resolution: {integrity: sha512-X8GRxUTtebMAd2k4fcPyVnCdPa6dYK4lBsrwzKP5yiSq4i+WadWPIumaLfce53TUf/o2TnLpLOduyO1ylE2NHQ==} + engines: {node: '>=14.14.0'} peerDependencies: hardhat: ^2.0.0 dependencies: - hardhat: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) + '@ethersproject/abi': 5.7.0 + delete-empty: 3.0.0 + hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) dev: true - /hardhat-contract-sizer@2.5.1(hardhat@2.18.1): - resolution: {integrity: sha512-28yRb73e30aBVaZOOHTlHZFIdIasA/iFunIehrUviIJTubvdQjtSiQUo2wexHFtt71mQeMPP8qjw2sdbgatDnQ==} + /hardhat-contract-sizer@2.10.0(hardhat@2.19.1): + resolution: {integrity: sha512-QiinUgBD5MqJZJh1hl1jc9dNnpJg7eE/w4/4GEnrcmZJJTDbVFNe3+/3Ep24XqISSkYxRz36czcPHKHd/a0dwA==} peerDependencies: hardhat: ^2.0.0 dependencies: chalk: 4.1.2 cli-table3: 0.6.3 - hardhat: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) + hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) + strip-ansi: 6.0.1 dev: true - /hardhat-gas-reporter@1.0.9(hardhat@2.18.1): + /hardhat-gas-reporter@1.0.9(debug@4.3.4)(hardhat@2.19.1): resolution: {integrity: sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg==} peerDependencies: hardhat: ^2.0.2 dependencies: array-uniq: 1.0.3 - eth-gas-reporter: 0.2.25 - hardhat: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) + eth-gas-reporter: 0.2.27(debug@4.3.4) + hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) sha1: 1.1.1 transitivePeerDependencies: - '@codechecks/client' + - bufferutil + - debug + - utf-8-validate dev: true /hardhat-ignore-warnings@0.2.9: @@ -6313,8 +6257,8 @@ packages: solidity-comments: 0.0.2 dev: true - /hardhat@2.18.1(ts-node@10.9.1)(typescript@5.2.2): - resolution: {integrity: sha512-b55rW7Ka+fvJeg6oWuBTXoYQEUurevCCankjGNTwczwD3GnkhV9GEei7KUT+9IKmWx3lC+zyxlFxeDbg0gUoHw==} + /hardhat@2.19.1(ts-node@10.9.1)(typescript@5.2.2): + resolution: {integrity: sha512-bsWa63g1GB78ZyMN08WLhFElLPA+J+pShuKD1BFO2+88g3l+BL3R07vj9deIi9dMbssxgE714Gof1dBEDGqnCw==} hasBin: true peerDependencies: ts-node: '*' @@ -6369,7 +6313,7 @@ packages: solc: 0.7.3(debug@4.3.4) source-map-support: 0.5.21 stacktrace-parser: 0.1.10 - ts-node: 10.9.1(@types/node@16.18.58)(typescript@5.2.2) + ts-node: 10.9.1(@types/node@16.18.61)(typescript@5.2.2) tsort: 0.0.1 typescript: 5.2.2 undici: 5.19.1 @@ -6618,7 +6562,7 @@ packages: engines: {node: '>=0.8', npm: '>=1.3.7'} dependencies: assert-plus: 1.0.0 - jsprim: 1.4.1 + jsprim: 1.4.2 sshpk: 1.16.1 dev: true @@ -6729,15 +6673,6 @@ packages: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: true - /internal-slot@1.0.3: - resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.1.3 - has: 1.0.3 - side-channel: 1.0.4 - dev: true - /internal-slot@1.0.5: resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} engines: {node: '>= 0.4'} @@ -6848,12 +6783,6 @@ packages: ci-info: 2.0.0 dev: true - /is-core-module@2.10.0: - resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} - dependencies: - has: 1.0.3 - dev: true - /is-core-module@2.13.0: resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: @@ -7238,14 +7167,6 @@ packages: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true - /js-yaml@3.13.1: - resolution: {integrity: sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==} - hasBin: true - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - dev: true - /js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -7319,8 +7240,8 @@ packages: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} dev: true - /json-schema@0.2.3: - resolution: {integrity: sha512-a3xHnILGMtk+hDOqNwHzF6e2fNbiMrXZvxKQiEv2MlgQP+pjIOzqAmKYD2mDpXYE/44M7g+n9p2bKkYWDUcXCQ==} + /json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} dev: true /json-stable-stringify-without-jsonify@1.0.1: @@ -7383,13 +7304,13 @@ packages: resolution: {integrity: sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw==} dev: true - /jsprim@1.4.1: - resolution: {integrity: sha512-4Dj8Rf+fQ+/Pn7C5qeEX02op1WfOss3PKTE9Nsop3Dx+6UPxlm1dr/og7o2cRa5hNN07CACr4NFzRLtj/rjWog==} - engines: {'0': node >=0.6.0} + /jsprim@1.4.2: + resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} + engines: {node: '>=0.6.0'} dependencies: assert-plus: 1.0.0 extsprintf: 1.3.0 - json-schema: 0.2.3 + json-schema: 0.4.0 verror: 1.10.0 dev: true @@ -7452,6 +7373,13 @@ packages: graceful-fs: 4.2.10 dev: true + /latest-version@7.0.0: + resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} + engines: {node: '>=14.16'} + dependencies: + package-json: 8.1.1 + dev: true + /lcid@1.0.0: resolution: {integrity: sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==} engines: {node: '>=0.10.0'} @@ -7650,14 +7578,6 @@ packages: path-exists: 3.0.0 dev: true - /locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} - dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 - dev: true - /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -7704,13 +7624,6 @@ packages: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true - /log-symbols@3.0.0: - resolution: {integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==} - engines: {node: '>=8'} - dependencies: - chalk: 2.4.2 - dev: true - /log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} @@ -8006,12 +7919,6 @@ packages: /minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - /minimatch@3.0.4: - resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} - dependencies: - brace-expansion: 1.1.11 - dev: true - /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -8072,13 +7979,6 @@ packages: mkdirp: 1.0.4 dev: true - /mkdirp@0.5.5: - resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==} - hasBin: true - dependencies: - minimist: 1.2.6 - dev: true - /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -8126,37 +8026,6 @@ packages: yargs-unparser: 2.0.0 dev: true - /mocha@7.2.0: - resolution: {integrity: sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==} - engines: {node: '>= 8.10.0'} - hasBin: true - dependencies: - ansi-colors: 3.2.3 - browser-stdout: 1.3.1 - chokidar: 3.3.0 - debug: 3.2.6(supports-color@6.0.0) - diff: 3.5.0 - escape-string-regexp: 1.0.5 - find-up: 3.0.0 - glob: 7.1.3 - growl: 1.10.5 - he: 1.2.0 - js-yaml: 3.13.1 - log-symbols: 3.0.0 - minimatch: 3.0.4 - mkdirp: 0.5.5 - ms: 2.1.1 - node-environment-flags: 1.0.6 - object.assign: 4.1.0 - strip-json-comments: 2.0.1 - supports-color: 6.0.0 - which: 1.3.1 - wide-align: 1.1.3 - yargs: 13.3.2 - yargs-parser: 13.1.2 - yargs-unparser: 1.6.0 - dev: true - /mock-fs@4.12.0: resolution: {integrity: sha512-/P/HtrlvBxY4o/PzXY9cCNBrdylDNxg7gnrv2sMNxj+UJ2m8jSpl0/A6fuJeNAWr99ZvGWH8XCbE0vmnM5KupQ==} requiresBuild: true @@ -8325,13 +8194,6 @@ packages: lodash: 4.17.21 dev: true - /node-environment-flags@1.0.6: - resolution: {integrity: sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==} - dependencies: - object.getownpropertydescriptors: 2.1.4 - semver: 5.7.1 - dev: true - /node-fetch@1.7.3: resolution: {integrity: sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==} dependencies: @@ -8384,7 +8246,7 @@ packages: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.1 + resolve: 1.22.8 semver: 5.7.1 validate-npm-package-license: 3.0.4 dev: true @@ -8492,16 +8354,6 @@ packages: isobject: 3.0.1 dev: true - /object.assign@4.1.0: - resolution: {integrity: sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==} - engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.1.4 - function-bind: 1.1.1 - has-symbols: 1.0.3 - object-keys: 1.1.1 - dev: true - /object.assign@4.1.4: resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} engines: {node: '>= 0.4'} @@ -8512,16 +8364,6 @@ packages: object-keys: 1.1.1 dev: true - /object.getownpropertydescriptors@2.1.4: - resolution: {integrity: sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==} - engines: {node: '>= 0.8'} - dependencies: - array.prototype.reduce: 1.0.4 - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.3 - dev: true - /object.getownpropertydescriptors@2.1.7: resolution: {integrity: sha512-PrJz0C2xJ58FNn11XV2lr4Jt5Gzl94qpy9Lu0JlfEj14z88sqbSBJCBEzdlNUCzY2gburhbrwOZ5BHCmuNUy0g==} engines: {node: '>= 0.8'} @@ -8696,13 +8538,6 @@ packages: p-limit: 1.3.0 dev: true - /p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} - dependencies: - p-limit: 2.3.0 - dev: true - /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -8742,6 +8577,16 @@ packages: engines: {node: '>=6'} dev: true + /package-json@8.1.1: + resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} + engines: {node: '>=14.16'} + dependencies: + got: 12.1.0 + registry-auth-token: 5.0.2 + registry-url: 6.0.1 + semver: 7.5.4 + dev: true + /param-case@2.1.1: resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} dependencies: @@ -8916,6 +8761,11 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true + /path-starts-with@2.0.1: + resolution: {integrity: sha512-wZ3AeiRBRlNwkdUxvBANh0+esnt38DLffHDujZyRHkqkaKHTglnY2EP5UX3b8rdeiSutgO4y9NEJwXezNP5vHg==} + engines: {node: '>=8'} + dev: true + /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} requiresBuild: true @@ -9033,28 +8883,28 @@ packages: fast-diff: 1.2.0 dev: true - /prettier-plugin-solidity@1.1.3(prettier@2.8.8): - resolution: {integrity: sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==} - engines: {node: '>=12'} + /prettier-plugin-solidity@1.2.0(prettier@2.8.8): + resolution: {integrity: sha512-fgxcUZpVAP+LlRfy5JI5oaAkXGkmsje2VJ5krv/YMm+rcTZbIUwFguSw5f+WFuttMjpDm6wB4UL7WVkArEfiVA==} + engines: {node: '>=16'} peerDependencies: - prettier: '>=2.3.0 || >=3.0.0-alpha.0' + prettier: '>=2.3.0' dependencies: - '@solidity-parser/parser': 0.16.0 + '@solidity-parser/parser': 0.16.2 prettier: 2.8.8 - semver: 7.5.0 + semver: 7.5.4 solidity-comments-extractor: 0.0.7 dev: true optional: true - /prettier-plugin-solidity@1.1.3(prettier@3.0.3): - resolution: {integrity: sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==} - engines: {node: '>=12'} + /prettier-plugin-solidity@1.2.0(prettier@3.1.0): + resolution: {integrity: sha512-fgxcUZpVAP+LlRfy5JI5oaAkXGkmsje2VJ5krv/YMm+rcTZbIUwFguSw5f+WFuttMjpDm6wB4UL7WVkArEfiVA==} + engines: {node: '>=16'} peerDependencies: - prettier: '>=2.3.0 || >=3.0.0-alpha.0' + prettier: '>=2.3.0' dependencies: - '@solidity-parser/parser': 0.16.0 - prettier: 3.0.3 - semver: 7.5.0 + '@solidity-parser/parser': 0.16.2 + prettier: 3.1.0 + semver: 7.5.4 solidity-comments-extractor: 0.0.7 dev: true @@ -9064,8 +8914,8 @@ packages: hasBin: true dev: true - /prettier@3.0.3: - resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} + /prettier@3.1.0: + resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==} engines: {node: '>=14'} hasBin: true dev: true @@ -9106,6 +8956,10 @@ packages: signal-exit: 3.0.7 dev: true + /proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + dev: true + /proxy-addr@2.0.5: resolution: {integrity: sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==} engines: {node: '>= 0.10'} @@ -9294,6 +9148,16 @@ packages: unpipe: 1.0.0 dev: true + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: true + /read-pkg-up@1.0.1: resolution: {integrity: sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==} engines: {node: '>=0.10.0'} @@ -9350,13 +9214,6 @@ packages: util-deprecate: 1.0.2 dev: true - /readdirp@3.2.0: - resolution: {integrity: sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==} - engines: {node: '>= 8'} - dependencies: - picomatch: 2.3.1 - dev: true - /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -9375,7 +9232,7 @@ packages: resolution: {integrity: sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==} engines: {node: '>=0.10.0'} dependencies: - minimatch: 3.0.4 + minimatch: 3.1.2 dev: true /reduce-flatten@2.0.0: @@ -9437,6 +9294,20 @@ packages: regjsparser: 0.1.5 dev: true + /registry-auth-token@5.0.2: + resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==} + engines: {node: '>=14'} + dependencies: + '@pnpm/npm-conf': 2.2.2 + dev: true + + /registry-url@6.0.1: + resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} + engines: {node: '>=12'} + dependencies: + rc: 1.2.8 + dev: true + /regjsgen@0.2.0: resolution: {integrity: sha512-x+Y3yA24uF68m5GA+tBjbGYo64xXVJpbToBaWCoSNSc1hdk6dfctaRWrNFTVJZIIhL5GxW8zwjoixbnifnK59g==} dev: true @@ -9479,29 +9350,6 @@ packages: resolve-from: 3.0.0 dev: true - /request-promise-core@1.1.4(request@2.88.2): - resolution: {integrity: sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==} - engines: {node: '>=0.10.0'} - peerDependencies: - request: ^2.34 - dependencies: - lodash: 4.17.21 - request: 2.88.2 - dev: true - - /request-promise-native@1.0.9(request@2.88.2): - resolution: {integrity: sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==} - engines: {node: '>=0.12.0'} - deprecated: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142 - peerDependencies: - request: ^2.34 - dependencies: - request: 2.88.2 - request-promise-core: 1.1.4(request@2.88.2) - stealthy-require: 1.1.1 - tough-cookie: 2.5.0 - dev: true - /request@2.88.2: resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} engines: {node: '>= 6'} @@ -9548,10 +9396,6 @@ packages: resolution: {integrity: sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==} dev: true - /require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - dev: true - /resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} dev: true @@ -9581,15 +9425,6 @@ packages: path-parse: 1.0.7 dev: true - /resolve@1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} - hasBin: true - dependencies: - is-core-module: 2.10.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -9806,14 +9641,6 @@ packages: lru-cache: 6.0.0 dev: true - /semver@7.5.0: - resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: true - /semver@7.5.4: resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} @@ -9978,8 +9805,8 @@ packages: engines: {node: '>=8'} dev: true - /shelljs@0.8.3: - resolution: {integrity: sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==} + /shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} engines: {node: '>=4'} hasBin: true dependencies: @@ -10120,19 +9947,20 @@ packages: - debug dev: true - /solhint-plugin-prettier@0.0.5(prettier-plugin-solidity@1.1.3)(prettier@3.0.3): - resolution: {integrity: sha512-7jmWcnVshIrO2FFinIvDQmhQpfpS2rRRn3RejiYgnjIE68xO2bvrYvjqVNfrio4xH9ghOqn83tKuTzLjEbmGIA==} + /solhint-plugin-prettier@0.1.0(prettier-plugin-solidity@1.2.0)(prettier@3.1.0): + resolution: {integrity: sha512-SDOTSM6tZxZ6hamrzl3GUgzF77FM6jZplgL2plFBclj/OjKP8Z3eIPojKU73gRr0MvOS8ACZILn8a5g0VTz/Gw==} peerDependencies: - prettier: ^1.15.0 || ^2.0.0 - prettier-plugin-solidity: ^1.0.0-alpha.14 + prettier: ^3.0.0 + prettier-plugin-solidity: ^1.0.0 dependencies: - prettier: 3.0.3 + '@prettier/sync': 0.3.0(prettier@3.1.0) + prettier: 3.1.0 prettier-linter-helpers: 1.0.0 - prettier-plugin-solidity: 1.1.3(prettier@3.0.3) + prettier-plugin-solidity: 1.2.0(prettier@3.1.0) dev: true - /solhint@3.6.2: - resolution: {integrity: sha512-85EeLbmkcPwD+3JR7aEMKsVC9YrRSxd4qkXuMzrlf7+z2Eqdfm1wHWq1ffTuo5aDhoZxp2I9yF3QkxZOxOL7aQ==} + /solhint@4.0.0: + resolution: {integrity: sha512-bFViMcFvhqVd/HK3Roo7xZXX5nbujS7Bxeg5vnZc9QvH0yCWCrQ38Yrn1pbAY9tlKROc6wFr+rK1mxYgYrjZgA==} hasBin: true dependencies: '@solidity-parser/parser': 0.16.0 @@ -10146,6 +9974,7 @@ packages: glob: 8.1.0 ignore: 5.2.4 js-yaml: 4.1.0 + latest-version: 7.0.0 lodash: 4.17.21 pluralize: 8.0.0 semver: 7.5.4 @@ -10272,7 +10101,7 @@ packages: solidity-comments-win32-x64-msvc: 0.0.2 dev: true - /solidity-coverage@0.8.5(hardhat@2.18.1): + /solidity-coverage@0.8.5(hardhat@2.19.1): resolution: {integrity: sha512-6C6N6OV2O8FQA0FWA95FdzVH+L16HU94iFgg5wAFZ29UpLFkgNI/DRR2HotG1bC0F4gAc/OMs2BJI44Q/DYlKQ==} hasBin: true peerDependencies: @@ -10288,7 +10117,7 @@ packages: ghost-testrpc: 0.0.2 global-modules: 2.0.0 globby: 10.0.2 - hardhat: 2.18.1(ts-node@10.9.1)(typescript@5.2.2) + hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.2.2) jsonschema: 1.4.0 lodash: 4.17.21 mocha: 10.2.0 @@ -10297,7 +10126,7 @@ packages: recursive-readdir: 2.2.2 sc-istanbul: 0.4.6 semver: 7.5.4 - shelljs: 0.8.3 + shelljs: 0.8.5 web3-utils: 1.8.0 transitivePeerDependencies: - supports-color @@ -10439,11 +10268,6 @@ packages: engines: {node: '>= 0.8'} dev: true - /stealthy-require@1.1.1: - resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==} - engines: {node: '>=0.10.0'} - dev: true - /stream-to-pull-stream@1.7.3: resolution: {integrity: sha512-6sNyqJpr5dIOQdgNy/xcDWwDuzAsAwVzhzrWlAPAQ7Lkjx/rv0wgvxEyKwTq6FmNd5rjTrELt/CLmaSw7crMGg==} dependencies: @@ -10483,15 +10307,6 @@ packages: strip-ansi: 4.0.0 dev: true - /string-width@3.1.0: - resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==} - engines: {node: '>=6'} - dependencies: - emoji-regex: 7.0.3 - is-fullwidth-code-point: 2.0.0 - strip-ansi: 5.2.0 - dev: true - /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -10510,14 +10325,6 @@ packages: es-abstract: 1.22.2 dev: true - /string.prototype.trimend@1.0.5: - resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.3 - dev: true - /string.prototype.trimend@1.0.7: resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} dependencies: @@ -10526,14 +10333,6 @@ packages: es-abstract: 1.22.2 dev: true - /string.prototype.trimstart@1.0.5: - resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.3 - dev: true - /string.prototype.trimstart@1.0.7: resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} dependencies: @@ -10572,13 +10371,6 @@ packages: ansi-regex: 3.0.1 dev: true - /strip-ansi@5.2.0: - resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} - engines: {node: '>=6'} - dependencies: - ansi-regex: 4.1.1 - dev: true - /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -10644,13 +10436,6 @@ packages: has-flag: 3.0.0 dev: true - /supports-color@6.0.0: - resolution: {integrity: sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==} - engines: {node: '>=6'} - dependencies: - has-flag: 3.0.0 - dev: true - /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -10730,17 +10515,6 @@ packages: wordwrapjs: 4.0.1 dev: true - /table@6.8.0: - resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==} - engines: {node: '>=10.0.0'} - dependencies: - ajv: 8.11.0 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - /table@6.8.1: resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==} engines: {node: '>=10.0.0'} @@ -10982,11 +10756,11 @@ packages: glob: 7.2.3 mkdirp: 0.5.6 prettier: 2.8.8 - resolve: 1.22.1 + resolve: 1.22.8 ts-essentials: 1.0.4 dev: true - /ts-node@10.9.1(@types/node@16.18.58)(typescript@5.2.2): + /ts-node@10.9.1(@types/node@16.18.61)(typescript@5.2.2): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -11005,7 +10779,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 16.18.58 + '@types/node': 16.18.61 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -11113,8 +10887,8 @@ packages: - typescript dev: true - /typechain@8.2.0(typescript@5.2.2): - resolution: {integrity: sha512-tZqhqjxJ9xAS/Lh32jccTjMkpx7sTdUVVHAy5Bf0TIer5QFNYXotiX74oCvoVYjyxUKDK3MXHtMFzMyD3kE+jg==} + /typechain@8.3.2(typescript@5.2.2): + resolution: {integrity: sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==} hasBin: true peerDependencies: typescript: '>=4.3.0' @@ -11244,11 +11018,6 @@ packages: dev: true optional: true - /undici@5.10.0: - resolution: {integrity: sha512-c8HsD3IbwmjjbLvoZuRI26TZic+TSEe8FPMLLOkN1AfYRhdjnKBU6yL+IwcSCbdZiX4e5t0lfMDLDCqj4Sq70g==} - engines: {node: '>=12.18'} - dev: true - /undici@5.19.1: resolution: {integrity: sha512-YiZ61LPIgY73E7syxCDxxa3LV2yl3sN8spnIuTct60boiiRaE1J8mNWHO8Im2Zi/sFrPusjLlmRPrsyraSqX6A==} engines: {node: '>=12.18'} @@ -12389,10 +12158,6 @@ packages: resolution: {integrity: sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==} dev: true - /which-module@2.0.0: - resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} - dev: true - /which-typed-array@1.1.11: resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} engines: {node: '>= 0.4'} @@ -12419,12 +12184,6 @@ packages: isexe: 2.0.0 dev: true - /wide-align@1.1.3: - resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==} - dependencies: - string-width: 2.1.1 - dev: true - /window-size@0.2.0: resolution: {integrity: sha512-UD7d8HFA2+PZsbKyaOCEy8gMh1oDtHgJh1LfgjQ4zVXmYjAT/kvz3PueITKuqDiIXQe7yzpPnxX3lNc+AhQMyw==} engines: {node: '>= 0.10.0'} @@ -12460,15 +12219,6 @@ packages: strip-ansi: 3.0.1 dev: true - /wrap-ansi@5.1.0: - resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==} - engines: {node: '>=6'} - dependencies: - ansi-styles: 3.2.1 - string-width: 3.1.0 - strip-ansi: 5.2.0 - dev: true - /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -12594,10 +12344,6 @@ packages: resolution: {integrity: sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==} dev: true - /y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - dev: true - /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -12621,13 +12367,6 @@ packages: engines: {node: '>= 6'} dev: true - /yargs-parser@13.1.2: - resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==} - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - dev: true - /yargs-parser@2.4.1: resolution: {integrity: sha512-9pIKIJhnI5tonzG6OnCFlz/yln8xHYcGl+pn3xR0Vzff0vzN1PbNRaelgfgRUwZ3s4i3jvxT9WhmUGL4whnasA==} dependencies: @@ -12640,15 +12379,6 @@ packages: engines: {node: '>=10'} dev: true - /yargs-unparser@1.6.0: - resolution: {integrity: sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==} - engines: {node: '>=6'} - dependencies: - flat: 4.1.1 - lodash: 4.17.21 - yargs: 13.3.2 - dev: true - /yargs-unparser@2.0.0: resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} engines: {node: '>=10'} @@ -12659,21 +12389,6 @@ packages: is-plain-obj: 2.1.0 dev: true - /yargs@13.3.2: - resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==} - dependencies: - cliui: 5.0.0 - find-up: 3.0.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 3.1.0 - which-module: 2.0.0 - y18n: 4.0.3 - yargs-parser: 13.1.2 - dev: true - /yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} @@ -12725,8 +12440,8 @@ packages: ethereumjs-util: 6.2.1 dev: true - github.com/smartcontractkit/chainlink-solhint-rules/6229ce5d3cc3e4a2454411bebc887c5ca240dcf2: - resolution: {tarball: https://codeload.github.com/smartcontractkit/chainlink-solhint-rules/tar.gz/6229ce5d3cc3e4a2454411bebc887c5ca240dcf2} + github.com/smartcontractkit/chainlink-solhint-rules/cfc50b32f95b730304a50deb2e27e88d87115874: + resolution: {tarball: https://codeload.github.com/smartcontractkit/chainlink-solhint-rules/tar.gz/cfc50b32f95b730304a50deb2e27e88d87115874} name: '@chainlink/solhint-plugin-chainlink-solidity' - version: 1.0.1 + version: 1.2.0 dev: true diff --git a/contracts/scripts/native_solc_compile_all b/contracts/scripts/native_solc_compile_all index a2f2f1a0bc3..cf1226a2d5c 100755 --- a/contracts/scripts/native_solc_compile_all +++ b/contracts/scripts/native_solc_compile_all @@ -12,7 +12,7 @@ python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt # 6 and 7 are legacy contracts, for each other product we have a native_solc_compile_all_$product script # These scripts can be run individually, or all together with this script. # To add new CL products, simply write a native_solc_compile_all_$product script and add it to the list below. -for product in 6 7 feeds functions llo-feeds transmission vrf automation operatorforwarder logpoller events_mock shared +for product in 6 7 automation events_mock feeds functions llo-feeds logpoller operatorforwarder shared transmission vrf do $SCRIPTPATH/native_solc_compile_all_$product done diff --git a/contracts/scripts/native_solc_compile_all_6 b/contracts/scripts/native_solc_compile_all_6 index 7f8f4fa6957..f7bd60d6781 100755 --- a/contracts/scripts/native_solc_compile_all_6 +++ b/contracts/scripts/native_solc_compile_all_6 @@ -12,7 +12,7 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION @@ -20,10 +20,13 @@ export SOLC_VERSION=$SOLC_VERSION compileContract () { + local contract + contract=$(basename "$1" ".sol") + solc --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v0.6 \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.6 \ - $ROOT/contracts/src/v0.6/$1 + -o "$ROOT"/contracts/solc/v0.6/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.6 \ + "$ROOT"/contracts/src/v0.6/"$1" } compileContract Flags.sol diff --git a/contracts/scripts/native_solc_compile_all_7 b/contracts/scripts/native_solc_compile_all_7 index b2d76b3cb5f..fd64d9ffce7 100755 --- a/contracts/scripts/native_solc_compile_all_7 +++ b/contracts/scripts/native_solc_compile_all_7 @@ -12,7 +12,7 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION @@ -20,10 +20,13 @@ export SOLC_VERSION=$SOLC_VERSION compileContract () { + local contract + contract=$(basename "$1" ".sol") + solc --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v0.7 \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.7 \ - $ROOT/contracts/src/v0.7/$1 + -o "$ROOT"/contracts/solc/v0.7/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.7 \ + "$ROOT"/contracts/src/v0.7/"$1" } compileContract tests/MultiWordConsumer.sol diff --git a/contracts/scripts/native_solc_compile_all_automation b/contracts/scripts/native_solc_compile_all_automation index 414453c8482..ddf6c2c8bfa 100755 --- a/contracts/scripts/native_solc_compile_all_automation +++ b/contracts/scripts/native_solc_compile_all_automation @@ -12,7 +12,7 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION @@ -20,10 +20,13 @@ export SOLC_VERSION=$SOLC_VERSION compileContract () { - solc @openzeppelin/=$ROOT/contracts/node_modules/@openzeppelin/ --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v$SOLC_VERSION \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.8,$ROOT/contracts/node_modules\ - $ROOT/contracts/src/v0.8/$1 + local contract + contract=$(basename "$1" ".sol") + + solc @openzeppelin/="$ROOT"/contracts/node_modules/@openzeppelin/ --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ + -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8,"$ROOT"/contracts/node_modules\ + "$ROOT"/contracts/src/v0.8/"$1" } compileContract automation/upkeeps/CronUpkeepFactory.sol @@ -38,6 +41,7 @@ compileContract automation/v2_0/KeeperRegistryLogic2_0.sol compileContract automation/UpkeepTranscoder.sol compileContract automation/mocks/MockAggregatorProxy.sol compileContract automation/testhelpers/LogUpkeepCounter.sol +compileContract automation/testhelpers/SimpleLogUpkeepCounter.sol compileContract automation/mocks/KeeperRegistrar1_2Mock.sol compileContract automation/mocks/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock.sol @@ -60,8 +64,6 @@ compileContract automation/v2_1/AutomationForwarderLogic.sol compileContract automation/testhelpers/LogTriggeredStreamsLookup.sol compileContract automation/testhelpers/DummyProtocol.sol -compileContract automation/testhelpers/KeeperBase.sol -compileContract automation/testhelpers/KeeperCompatibleInterface.sol compileContract automation/testhelpers/KeeperConsumer.sol compileContract automation/testhelpers/KeeperConsumerPerformance.sol compileContract automation/testhelpers/PerformDataChecker.sol diff --git a/contracts/scripts/native_solc_compile_all_events_mock b/contracts/scripts/native_solc_compile_all_events_mock index 993530e2fa1..68e8bdfa6a9 100755 --- a/contracts/scripts/native_solc_compile_all_events_mock +++ b/contracts/scripts/native_solc_compile_all_events_mock @@ -12,17 +12,20 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION export SOLC_VERSION=$SOLC_VERSION compileContract () { + local contract + contract=$(basename "$1" ".sol") + solc --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v$SOLC_VERSION \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.8\ - $ROOT/contracts/src/v0.8/$1 + -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8\ + "$ROOT"/contracts/src/v0.8/"$1" } # This script is used to compile the contracts for the Events Mocks used in the tests. diff --git a/contracts/scripts/native_solc_compile_all_feeds b/contracts/scripts/native_solc_compile_all_feeds index 2bbd9fe869c..2c5808d4663 100755 --- a/contracts/scripts/native_solc_compile_all_feeds +++ b/contracts/scripts/native_solc_compile_all_feeds @@ -12,7 +12,7 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION @@ -20,13 +20,16 @@ export SOLC_VERSION=$SOLC_VERSION compileContract () { + local contract + contract=$(basename "$1" ".sol") + solc --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v$SOLC_VERSION \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.8\ - $ROOT/contracts/src/v0.8/$1 + -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8\ + "$ROOT"/contracts/src/v0.8/"$1" } # Aggregators -compileContract interfaces/AggregatorV2V3Interface.sol +compileContract shared/interfaces/AggregatorV2V3Interface.sol compileContract Chainlink.sol compileContract ChainlinkClient.sol diff --git a/contracts/scripts/native_solc_compile_all_llo-feeds b/contracts/scripts/native_solc_compile_all_llo-feeds index 27ef714ec1c..2caa6fb98de 100755 --- a/contracts/scripts/native_solc_compile_all_llo-feeds +++ b/contracts/scripts/native_solc_compile_all_llo-feeds @@ -11,7 +11,7 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION export SOLC_VERSION=$SOLC_VERSION @@ -19,10 +19,13 @@ export SOLC_VERSION=$SOLC_VERSION ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" compileContract () { + local contract + contract=$(basename "$1" ".sol") + solc --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v$SOLC_VERSION \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.8\ - $ROOT/contracts/src/v0.8/$1 + -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8\ + "$ROOT"/contracts/src/v0.8/"$1" } compileContract llo-feeds/Verifier.sol diff --git a/contracts/scripts/native_solc_compile_all_logpoller b/contracts/scripts/native_solc_compile_all_logpoller index 91a0606dba8..b6ac51ecedb 100755 --- a/contracts/scripts/native_solc_compile_all_logpoller +++ b/contracts/scripts/native_solc_compile_all_logpoller @@ -11,7 +11,7 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION export SOLC_VERSION=$SOLC_VERSION @@ -19,10 +19,13 @@ export SOLC_VERSION=$SOLC_VERSION ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" compileContract () { + local contract + contract=$(basename "$1" ".sol") + solc --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v$SOLC_VERSION \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.8\ - $ROOT/contracts/src/v0.8/$1 + -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8\ + "$ROOT"/contracts/src/v0.8/"$1" } diff --git a/contracts/scripts/native_solc_compile_all_ocr2vrf b/contracts/scripts/native_solc_compile_all_ocr2vrf index 42478d7ebc2..755edd34f56 100755 --- a/contracts/scripts/native_solc_compile_all_ocr2vrf +++ b/contracts/scripts/native_solc_compile_all_ocr2vrf @@ -16,7 +16,7 @@ echo "Compiling OCR2VRF contracts..." SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1; cd ../../ && pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION @@ -24,11 +24,14 @@ export SOLC_VERSION=$SOLC_VERSION compileContract () { - solc --overwrite --optimize --optimize-runs $2 --metadata-hash none \ - -o $ROOT/contracts/solc/v0.8.19 \ + local contract + contract=$(basename "$1" ".sol") + + solc --overwrite --optimize --optimize-runs "$2" --metadata-hash none \ + -o "$ROOT"/contracts/solc/v0.8.19/"$contract" \ --abi --bin \ - --allow-paths $ROOT/../$FOLDER/contracts \ - $ROOT/$1 + --allow-paths "$ROOT"/../$FOLDER/contracts \ + "$ROOT"/"$1" } # OCR2VRF diff --git a/contracts/scripts/native_solc_compile_all_operatorforwarder b/contracts/scripts/native_solc_compile_all_operatorforwarder index 3bc5cb9249f..2d455994813 100755 --- a/contracts/scripts/native_solc_compile_all_operatorforwarder +++ b/contracts/scripts/native_solc_compile_all_operatorforwarder @@ -11,17 +11,20 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION export SOLC_VERSION=$SOLC_VERSION compileContract () { - solc @openzeppelin/=$ROOT/contracts/node_modules/@openzeppelin/ --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v$SOLC_VERSION \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.8,$ROOT/contracts/node_modules\ - $ROOT/contracts/src/v0.8/$1 + local contract + contract=$(basename "$1" ".sol") + + solc @openzeppelin/="$ROOT"/contracts/node_modules/@openzeppelin/ --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ + -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8,"$ROOT"/contracts/node_modules\ + "$ROOT"/contracts/src/v0.8/"$1" } # Contracts diff --git a/contracts/scripts/native_solc_compile_all_shared b/contracts/scripts/native_solc_compile_all_shared index 705cedce7ab..9178237b8a5 100755 --- a/contracts/scripts/native_solc_compile_all_shared +++ b/contracts/scripts/native_solc_compile_all_shared @@ -11,7 +11,7 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION export SOLC_VERSION=$SOLC_VERSION @@ -19,13 +19,16 @@ export SOLC_VERSION=$SOLC_VERSION ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" compileContract () { + local contract + contract=$(basename "$1" ".sol") + solc --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v$SOLC_VERSION \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.8\ - $ROOT/contracts/src/v0.8/$1 + -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8\ + "$ROOT"/contracts/src/v0.8/"$1" } compileContract shared/token/ERC677/BurnMintERC677.sol compileContract shared/token/ERC677/LinkToken.sol compileContract shared/mocks/WERC20Mock.sol -compileContract vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/ERC20.sol +compileContract vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol diff --git a/contracts/scripts/native_solc_compile_all_transmission b/contracts/scripts/native_solc_compile_all_transmission index e08f38e2bac..281fa7aea73 100755 --- a/contracts/scripts/native_solc_compile_all_transmission +++ b/contracts/scripts/native_solc_compile_all_transmission @@ -11,17 +11,20 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION export SOLC_VERSION=$SOLC_VERSION compileContract () { + local contract + contract=$(basename "$1" ".sol") + solc --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v$SOLC_VERSION \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.8\ - $ROOT/contracts/src/v0.8/$1 + -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8\ + "$ROOT"/contracts/src/v0.8/"$1" } # Contracts diff --git a/contracts/scripts/native_solc_compile_all_vrf b/contracts/scripts/native_solc_compile_all_vrf index 80adba8e6f7..4eed35cf5bc 100755 --- a/contracts/scripts/native_solc_compile_all_vrf +++ b/contracts/scripts/native_solc_compile_all_vrf @@ -11,24 +11,30 @@ OPTIMIZE_RUNS=1000000 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../../ && pwd -P )" -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION export SOLC_VERSION=$SOLC_VERSION compileContract () { - solc @openzeppelin/=$ROOT/contracts/node_modules/@openzeppelin/ --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ - -o $ROOT/contracts/solc/v$SOLC_VERSION \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.8,$ROOT/contracts/node_modules\ - $ROOT/contracts/src/v0.8/$1 + local contract + contract=$(basename "$1" ".sol") + + solc @openzeppelin/="$ROOT"/contracts/node_modules/@openzeppelin/ --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ + -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8,"$ROOT"/contracts/node_modules\ + "$ROOT"/contracts/src/v0.8/"$1" } compileContractAltOpts () { - solc @openzeppelin/=$ROOT/contracts/node_modules/@openzeppelin/ --overwrite --optimize --optimize-runs $2 --metadata-hash none \ - -o $ROOT/contracts/solc/v$SOLC_VERSION \ - --abi --bin --allow-paths $ROOT/contracts/src/v0.8,$ROOT/contracts/node_modules\ - $ROOT/contracts/src/v0.8/$1 + local contract + contract=$(basename "$1" ".sol") + + solc @openzeppelin/="$ROOT"/contracts/node_modules/@openzeppelin/ --overwrite --optimize --optimize-runs "$2" --metadata-hash none \ + -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ + --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8,"$ROOT"/contracts/node_modules\ + "$ROOT"/contracts/src/v0.8/"$1" } # VRF @@ -73,6 +79,7 @@ compileContract vrf/dev/testhelpers/VRFConsumerV2PlusUpgradeableExample.sol compileContract vrf/dev/testhelpers/VRFV2PlusMaliciousMigrator.sol compileContract vrf/dev/libraries/VRFV2PlusClient.sol compileContract vrf/dev/testhelpers/VRFCoordinatorV2Plus_V2Example.sol +compileContract vrf/dev/BlockhashStore.sol compileContract vrf/dev/TrustedBlockhashStore.sol compileContract vrf/dev/testhelpers/VRFV2PlusLoadTestWithMetrics.sol compileContractAltOpts vrf/dev/testhelpers/VRFCoordinatorV2PlusUpgradedVersion.sol 5 diff --git a/contracts/src/v0.8/ValidatorProxy.sol b/contracts/src/v0.8/ValidatorProxy.sol index 35909ad87de..627af73b395 100644 --- a/contracts/src/v0.8/ValidatorProxy.sol +++ b/contracts/src/v0.8/ValidatorProxy.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {ConfirmedOwner} from "./shared/access/ConfirmedOwner.sol"; -import {AggregatorValidatorInterface} from "./interfaces/AggregatorValidatorInterface.sol"; +import {AggregatorValidatorInterface} from "./shared/interfaces/AggregatorValidatorInterface.sol"; import {TypeAndVersionInterface} from "./interfaces/TypeAndVersionInterface.sol"; // solhint-disable custom-errors diff --git a/contracts/src/v0.8/automation/AutomationBase.sol b/contracts/src/v0.8/automation/AutomationBase.sol index d91780a79f7..8267fbc6a4e 100644 --- a/contracts/src/v0.8/automation/AutomationBase.sol +++ b/contracts/src/v0.8/automation/AutomationBase.sol @@ -8,7 +8,8 @@ contract AutomationBase { * @notice method that allows it to be simulated via eth_call by checking that * the sender is the zero address. */ - function preventExecution() internal view { + function _preventExecution() internal view { + // solhint-disable-next-line avoid-tx-origin if (tx.origin != address(0)) { revert OnlySimulatedBackend(); } @@ -19,7 +20,7 @@ contract AutomationBase { * that the sender is the zero address. */ modifier cannotExecute() { - preventExecution(); + _preventExecution(); _; } } diff --git a/contracts/src/v0.8/automation/AutomationCompatible.sol b/contracts/src/v0.8/automation/AutomationCompatible.sol index 5634956ea70..65332436842 100644 --- a/contracts/src/v0.8/automation/AutomationCompatible.sol +++ b/contracts/src/v0.8/automation/AutomationCompatible.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "./AutomationBase.sol"; -import "./interfaces/AutomationCompatibleInterface.sol"; +import {AutomationBase} from "./AutomationBase.sol"; +import {AutomationCompatibleInterface} from "./interfaces/AutomationCompatibleInterface.sol"; abstract contract AutomationCompatible is AutomationBase, AutomationCompatibleInterface {} diff --git a/contracts/src/v0.8/automation/Chainable.sol b/contracts/src/v0.8/automation/Chainable.sol index 1b446f013a0..29ac7796c4a 100644 --- a/contracts/src/v0.8/automation/Chainable.sol +++ b/contracts/src/v0.8/automation/Chainable.sol @@ -10,29 +10,31 @@ contract Chainable { /** * @dev addresses of the next contract in the chain **have to be immutable/constant** or the system won't work */ - address private immutable FALLBACK_ADDRESS; + address private immutable i_FALLBACK_ADDRESS; /** * @param fallbackAddress the address of the next contract in the chain */ constructor(address fallbackAddress) { - FALLBACK_ADDRESS = fallbackAddress; + i_FALLBACK_ADDRESS = fallbackAddress; } /** * @notice returns the address of the next contract in the chain */ function fallbackTo() external view returns (address) { - return FALLBACK_ADDRESS; + return i_FALLBACK_ADDRESS; } /** * @notice the fallback function routes the call to the next contract in the chain * @dev most of the implementation is copied directly from OZ's Proxy contract */ + // solhint-disable payable-fallback + // solhint-disable-next-line no-complex-fallback fallback() external { // copy to memory for assembly access - address next = FALLBACK_ADDRESS; + address next = i_FALLBACK_ADDRESS; // copied directly from OZ's Proxy contract assembly { // Copy msg.data. We take full control of memory in this inline assembly diff --git a/contracts/src/v0.8/automation/ExecutionPrevention.sol b/contracts/src/v0.8/automation/ExecutionPrevention.sol index a8baf55fd22..30a823c4b8d 100644 --- a/contracts/src/v0.8/automation/ExecutionPrevention.sol +++ b/contracts/src/v0.8/automation/ExecutionPrevention.sol @@ -8,7 +8,8 @@ abstract contract ExecutionPrevention { * @notice method that allows it to be simulated via eth_call by checking that * the sender is the zero address. */ - function preventExecution() internal view { + function _preventExecution() internal view { + // solhint-disable-next-line avoid-tx-origin if (tx.origin != address(0)) { revert OnlySimulatedBackend(); } @@ -19,7 +20,7 @@ abstract contract ExecutionPrevention { * that the sender is the zero address. */ modifier cannotExecute() { - preventExecution(); + _preventExecution(); _; } } diff --git a/contracts/src/v0.8/automation/HeartbeatRequester.sol b/contracts/src/v0.8/automation/HeartbeatRequester.sol index d5802a79580..aa390738001 100644 --- a/contracts/src/v0.8/automation/HeartbeatRequester.sol +++ b/contracts/src/v0.8/automation/HeartbeatRequester.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT +// solhint-disable-next-line one-contract-per-file pragma solidity 0.8.6; -import "./../interfaces/TypeAndVersionInterface.sol"; -import "../shared/access/ConfirmedOwner.sol"; +import {TypeAndVersionInterface} from "./../interfaces/TypeAndVersionInterface.sol"; +import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; // defines some interfaces for type safety and reduces encoding/decoding // does not use the full interfaces intentionally because the requester only uses a fraction of them @@ -32,6 +33,7 @@ contract HeartbeatRequester is TypeAndVersionInterface, ConfirmedOwner { * - HeartbeatRequester 1.0.0: The requester fetches the latest aggregator address from proxy, and request a new round * using the aggregator address. */ + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables string public constant override typeAndVersion = "HeartbeatRequester 1.0.0"; constructor() ConfirmedOwner(msg.sender) {} diff --git a/contracts/src/v0.8/automation/UpkeepTranscoder.sol b/contracts/src/v0.8/automation/UpkeepTranscoder.sol index 450da8c14a1..144a96c7e77 100644 --- a/contracts/src/v0.8/automation/UpkeepTranscoder.sol +++ b/contracts/src/v0.8/automation/UpkeepTranscoder.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.0; -import "./interfaces/UpkeepTranscoderInterface.sol"; -import "../interfaces/TypeAndVersionInterface.sol"; +import {UpkeepTranscoderInterface} from "./interfaces/UpkeepTranscoderInterface.sol"; +import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; +import {UpkeepFormat} from "./UpkeepFormat.sol"; /** * @notice Transcoder for converting upkeep data from one keeper @@ -16,6 +17,7 @@ contract UpkeepTranscoder is UpkeepTranscoderInterface, TypeAndVersionInterface * @notice versions: * - UpkeepTranscoder 1.0.0: placeholder to allow new formats in the future */ + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables string public constant override typeAndVersion = "UpkeepTranscoder 1.0.0"; /** diff --git a/contracts/src/v0.8/automation/testhelpers/KeeperBase.sol b/contracts/src/v0.8/automation/testhelpers/KeeperBase.sol deleted file mode 100644 index 6fe41607f75..00000000000 --- a/contracts/src/v0.8/automation/testhelpers/KeeperBase.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -contract KeeperBase { - /** - * @notice method that allows it to be simulated via eth_call by checking that - * the sender is the zero address. - */ - function preventExecution() internal view { - require(tx.origin == address(0), "only for simulated backend"); - } - - /** - * @notice modifier that allows it to be simulated via eth_call by checking - * that the sender is the zero address. - */ - modifier cannotExecute() { - preventExecution(); - _; - } -} diff --git a/contracts/src/v0.8/automation/testhelpers/KeeperCompatibleInterface.sol b/contracts/src/v0.8/automation/testhelpers/KeeperCompatibleInterface.sol deleted file mode 100644 index 113f5ef6a55..00000000000 --- a/contracts/src/v0.8/automation/testhelpers/KeeperCompatibleInterface.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.16; - -interface KeeperCompatibleInterface { - /** - * @notice method that is simulated by the keepers to see if any work actually - * needs to be performed. This method does does not actually need to be - * executable, and since it is only ever simulated it can consume lots of gas. - * @dev To ensure that it is never called, you may want to add the - * cannotExecute modifier from KeeperBase to your implementation of this - * method. - * @param checkData specified in the upkeep registration so it is always the - * same for a registered upkeep. This can easily be broken down into specific - * arguments using `abi.decode`, so multiple upkeeps can be registered on the - * same contract and easily differentiated by the contract. - * @return upkeepNeeded boolean to indicate whether the keeper should call - * performUpkeep or not. - * @return performData bytes that the keeper should call performUpkeep with, if - * upkeep is needed. If you would like to encode data to decode later, try - * `abi.encode`. - */ - function checkUpkeep(bytes calldata checkData) external returns (bool upkeepNeeded, bytes memory performData); - - /** - * @notice method that is actually executed by the keepers, via the registry. - * The data returned by the checkUpkeep simulation will be passed into - * this method to actually be executed. - * @dev The input to this method should not be trusted, and the caller of the - * method should not even be restricted to any single registry. Anyone should - * be able call it, and the input should be validated, there is no guarantee - * that the data passed in is the performData returned from checkUpkeep. This - * could happen due to malicious keepers, racing keepers, or simply a state - * change while the performUpkeep transaction is waiting for confirmation. - * Always validate the data passed in. - * @param performData is the data which was passed back from the checkData - * simulation. If it is encoded, it can easily be decoded into other types by - * calling `abi.decode`. This data should not be trusted, and should be - * validated against the contract's current state. - */ - function performUpkeep(bytes calldata performData) external; -} diff --git a/contracts/src/v0.8/automation/testhelpers/KeeperConsumer.sol b/contracts/src/v0.8/automation/testhelpers/KeeperConsumer.sol index ba4694234a9..fb492f376c2 100644 --- a/contracts/src/v0.8/automation/testhelpers/KeeperConsumer.sol +++ b/contracts/src/v0.8/automation/testhelpers/KeeperConsumer.sol @@ -1,7 +1,7 @@ pragma solidity 0.8.16; -import "./KeeperCompatibleInterface.sol"; -import "./KeeperBase.sol"; +import "../interfaces/KeeperCompatibleInterface.sol"; +import "../KeeperBase.sol"; contract KeeperConsumer is KeeperCompatibleInterface, KeeperBase { uint public counter; diff --git a/contracts/src/v0.8/automation/testhelpers/PerformDataChecker.sol b/contracts/src/v0.8/automation/testhelpers/PerformDataChecker.sol index 03c57ea8e41..268942f931d 100644 --- a/contracts/src/v0.8/automation/testhelpers/PerformDataChecker.sol +++ b/contracts/src/v0.8/automation/testhelpers/PerformDataChecker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.16; -import "./KeeperCompatibleInterface.sol"; +import "../interfaces/KeeperCompatibleInterface.sol"; contract PerformDataChecker is KeeperCompatibleInterface { uint256 public counter; diff --git a/contracts/src/v0.8/automation/testhelpers/SimpleLogUpkeepCounter.sol b/contracts/src/v0.8/automation/testhelpers/SimpleLogUpkeepCounter.sol new file mode 100644 index 00000000000..563c1354b66 --- /dev/null +++ b/contracts/src/v0.8/automation/testhelpers/SimpleLogUpkeepCounter.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.6; + +import {ILogAutomation, Log} from "../interfaces/ILogAutomation.sol"; + +contract SimpleLogUpkeepCounter is ILogAutomation { + event PerformingUpkeep( + address indexed from, + uint256 initialBlock, + uint256 lastBlock, + uint256 previousBlock, + uint256 counter, + uint256 timeToPerform + ); + + uint256 public lastBlock; + uint256 public previousPerformBlock; + uint256 public initialBlock; + uint256 public counter; + uint256 public timeToPerform; + + constructor() { + previousPerformBlock = 0; + lastBlock = block.number; + initialBlock = 0; + counter = 0; + } + + function checkLog(Log calldata log, bytes memory) external view override returns (bool, bytes memory) { + return (true, abi.encode(log)); + } + + function performUpkeep(bytes calldata performData) external override { + if (initialBlock == 0) { + initialBlock = block.number; + } + lastBlock = block.number; + counter = counter + 1; + previousPerformBlock = lastBlock; + Log memory log = abi.decode(performData, (Log)); + timeToPerform = block.timestamp - log.timestamp; + emit PerformingUpkeep(tx.origin, initialBlock, lastBlock, previousPerformBlock, counter, timeToPerform); + } +} diff --git a/contracts/src/v0.8/automation/upkeeps/ERC20BalanceMonitor.sol b/contracts/src/v0.8/automation/upkeeps/ERC20BalanceMonitor.sol index 61a6e882d81..d2a7adc67af 100644 --- a/contracts/src/v0.8/automation/upkeeps/ERC20BalanceMonitor.sol +++ b/contracts/src/v0.8/automation/upkeeps/ERC20BalanceMonitor.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.4; import "../../shared/access/ConfirmedOwner.sol"; import "../interfaces/KeeperCompatibleInterface.sol"; -import "../../vendor/openzeppelin-solidity/v4.8.0/contracts/security/Pausable.sol"; -import "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../../vendor/openzeppelin-solidity/v4.8.3/contracts/security/Pausable.sol"; +import "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; /** * @title The ERC20BalanceMonitor contract. diff --git a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol index cff09aaebbd..9b9dc2d6b7d 100644 --- a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol +++ b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.6; +pragma solidity 0.8.19; -import "../../shared/access/ConfirmedOwner.sol"; -import "../interfaces/KeeperCompatibleInterface.sol"; -import "../../vendor/openzeppelin-solidity/v4.8.0/contracts/security/Pausable.sol"; -import "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; -import "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableMap.sol"; +import {AutomationCompatibleInterface} from "../interfaces/AutomationCompatibleInterface.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; +import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {Pausable} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/security/Pausable.sol"; interface IAggregatorProxy { function aggregator() external view returns (address); @@ -16,144 +15,140 @@ interface ILinkAvailable { function linkAvailableForPayment() external view returns (int256 availableBalance); } -/** - * @title The LinkAvailableBalanceMonitor contract. - * @notice A keeper-compatible contract that monitors target contracts for balance from a custom - * function linkAvailableForPayment() and funds them with LINK if it falls below a defined - * threshold. Also supports aggregator proxy contracts monitoring which require fetching the actual - * target contract through a predefined interface. - * @dev with 30 addresses as the MAX_PERFORM, the measured max gas usage of performUpkeep is around 2M - * therefore, we recommend an upkeep gas limit of 3M (this has a 33% margin of safety). Although, nothing - * prevents us from using 5M gas and increasing MAX_PERFORM, 30 seems like a reasonable batch size that - * is probably plenty for most needs. - * @dev with 130 addresses as the MAX_CHECK, the measured max gas usage of checkUpkeep is around 3.5M, - * which is 30% below the 5M limit. - * Note that testing conditions DO NOT match live chain gas usage, hence the margins. Change - * at your own risk!!! - * @dev some areas for improvement / acknowledgement of limitations: - * * validate that all addresses conform to interface when adding them to the watchlist - * * this is a "trusless" upkeep, meaning it does not trust the caller of performUpkeep; - we could save a fair amount of gas and re-write this upkeep for use with Automation v2.0+, - which has significantly different trust assumptions - */ -contract LinkAvailableBalanceMonitor is ConfirmedOwner, Pausable, KeeperCompatibleInterface { - using EnumerableMap for EnumerableMap.AddressToUintMap; - +/// @title The LinkAvailableBalanceMonitor contract. +/// @notice A keeper-compatible contract that monitors target contracts for balance from a custom +/// function linkAvailableForPayment() and funds them with LINK if it falls below a defined +/// threshold. Also supports aggregator proxy contracts monitoring which require fetching the actual +/// target contract through a predefined interface. +/// @dev with 30 addresses as the s_maxPerform, the measured max gas usage of performUpkeep is around 2M +/// therefore, we recommend an upkeep gas limit of 3M (this has a 33% margin of safety). Although, nothing +/// prevents us from using 5M gas and increasing s_maxPerform, 30 seems like a reasonable batch size that +/// is probably plenty for most needs. +/// @dev with 130 addresses as the s_maxCheck, the measured max gas usage of checkUpkeep is around 3.5M, +/// which is 30% below the 5M limit. +/// Note that testing conditions DO NOT match live chain gas usage, hence the margins. Change +/// at your own risk!!! +/// @dev some areas for improvement / acknowledgement of limitations: +/// validate that all addresses conform to interface when adding them to the watchlist +/// this is a "trusless" upkeep, meaning it does not trust the caller of performUpkeep; +/// we could save a fair amount of gas and re-write this upkeep for use with Automation v2.0+, +/// which has significantly different trust assumptions +contract LinkAvailableBalanceMonitor is ConfirmedOwner, Pausable, AutomationCompatibleInterface { + event BalanceUpdated(address indexed addr, uint256 oldBalance, uint256 newBalance); event FundsWithdrawn(uint256 amountWithdrawn, address payee); - event TopUpSucceeded(address indexed topUpAddress); + event UpkeepIntervalSet(uint256 oldUpkeepInterval, uint256 newUpkeepInterval); + event MaxCheckSet(uint256 oldMaxCheck, uint256 newMaxCheck); + event MaxPerformSet(uint256 oldMaxPerform, uint256 newMaxPerform); + event MinWaitPeriodSet(uint256 s_minWaitPeriodSeconds, uint256 minWaitPeriodSeconds); event TopUpBlocked(address indexed topUpAddress); + event TopUpFailed(address indexed recipient); + event TopUpSucceeded(address indexed topUpAddress); + event TopUpUpdated(address indexed addr, uint256 oldTopUpAmount, uint256 newTopUpAmount); event WatchlistUpdated(); + error InvalidAddress(address target); + error InvalidMaxCheck(uint16 maxCheck); + error InvalixMaxPerform(uint16 maxPerform); + error InvalidMinBalance(uint96 minBalance); + error InvalidTopUpAmount(uint96 topUpAmount); + error InvalidUpkeepInterval(uint8 upkeepInterval); + error InvalidLinkTokenAddress(address lt); error InvalidWatchList(); error DuplicateAddress(address duplicate); - uint256 public constant MAX_PERFORM = 5; // max number to addresses to top up in a single batch - uint256 public constant MAX_CHECK = 20; // max number of upkeeps to check (need to fit in 5M gas limit) - IERC20 public immutable LINK_TOKEN; + struct MonitoredAddress { + uint96 minBalance; + uint96 topUpAmount; + uint56 lastTopUpTimestamp; + bool isActive; + } - EnumerableMap.AddressToUintMap private s_watchList; - uint256 private s_topUpAmount; + IERC20 private immutable LINK_TOKEN; + uint256 private s_minWaitPeriodSeconds; + uint16 private s_maxPerform; + uint16 private s_maxCheck; + uint8 private s_upkeepInterval; + address[] private s_watchList; + mapping(address targetAddress => MonitoredAddress targetProperties) internal s_targets; - /** - * @param linkTokenAddress the LINK token address - * @param topUpAmount the amount of LINK to top up an aggregator with at once - */ - constructor(address linkTokenAddress, uint256 topUpAmount) ConfirmedOwner(msg.sender) { - require(linkTokenAddress != address(0)); - require(topUpAmount > 0); + /// @param linkTokenAddress the LINK token address + constructor( + address linkTokenAddress, + uint256 minWaitPeriodSeconds, + uint16 maxPerform, + uint16 maxCheck, + uint8 upkeepInterval + ) ConfirmedOwner(msg.sender) { + if (linkTokenAddress == address(0)) revert InvalidLinkTokenAddress(linkTokenAddress); LINK_TOKEN = IERC20(linkTokenAddress); - s_topUpAmount = topUpAmount; - } - - /** - * @notice Sets the list of subscriptions to watch and their funding parameters - * @param addresses the list of target addresses to watch (could be direct target or IAggregatorProxy) - * @param minBalances the list of corresponding minBalance for the target address - */ - function setWatchList(address[] calldata addresses, uint256[] calldata minBalances) external onlyOwner { - if (addresses.length != minBalances.length) { - revert InvalidWatchList(); - } - // first, remove all existing addresses from list - for (uint256 idx = s_watchList.length(); idx > 0; idx--) { - (address target, ) = s_watchList.at(idx - 1); - require(s_watchList.remove(target)); - } - // then set new addresses - for (uint256 idx = 0; idx < addresses.length; idx++) { - if (s_watchList.contains(addresses[idx])) { - revert DuplicateAddress(addresses[idx]); - } - if (addresses[idx] == address(0)) { - revert InvalidWatchList(); - } - s_watchList.set(addresses[idx], minBalances[idx]); - } - emit WatchlistUpdated(); + setMinWaitPeriodSeconds(minWaitPeriodSeconds); + setMaxPerform(maxPerform); + setMaxCheck(maxCheck); + setUpkeepInterval(upkeepInterval); } - /** - * @notice Adds addresses to the watchlist without overwriting existing members - * @param addresses the list of target addresses to watch (could be direct target or IAggregatorProxy) - * @param minBalances the list of corresponding minBalance for the target address - */ - function addToWatchList(address[] calldata addresses, uint256[] calldata minBalances) external onlyOwner { - if (addresses.length != minBalances.length) { + /// @notice Sets the list of subscriptions to watch and their funding parameters + /// @param addresses the list of target addresses to watch (could be direct target or IAggregatorProxy) + /// @param minBalances the list of corresponding minBalance for the target address + /// @param topUpAmounts the list of corresponding minTopUp for the target address + function setWatchList( + address[] calldata addresses, + uint96[] calldata minBalances, + uint96[] calldata topUpAmounts + ) external onlyOwner { + if (addresses.length != minBalances.length || addresses.length != topUpAmounts.length) { revert InvalidWatchList(); } - for (uint256 idx = 0; idx < addresses.length; idx++) { - if (s_watchList.contains(addresses[idx])) { - revert DuplicateAddress(addresses[idx]); - } - if (addresses[idx] == address(0)) { - revert InvalidWatchList(); - } - s_watchList.set(addresses[idx], minBalances[idx]); + for (uint256 idx = 0; idx < s_watchList.length; idx++) { + delete s_targets[s_watchList[idx]]; } - emit WatchlistUpdated(); - } - - /** - * @notice Removes addresses from the watchlist - * @param addresses the list of target addresses to remove from the watchlist - */ - function removeFromWatchlist(address[] calldata addresses) external onlyOwner { for (uint256 idx = 0; idx < addresses.length; idx++) { - if (!s_watchList.contains(addresses[idx])) { - revert InvalidWatchList(); - } - s_watchList.remove(addresses[idx]); + address targetAddress = addresses[idx]; + if (s_targets[targetAddress].isActive) revert DuplicateAddress(addresses[idx]); + if (addresses[idx] == address(0)) revert InvalidWatchList(); + if (topUpAmounts[idx] == 0) revert InvalidWatchList(); + s_targets[targetAddress] = MonitoredAddress({ + isActive: true, + minBalance: minBalances[idx], + topUpAmount: topUpAmounts[idx], + lastTopUpTimestamp: 0 + }); } + s_watchList = addresses; emit WatchlistUpdated(); } - /** - * @notice Gets a list of proxies that are underfunded, up to the MAX_PERFORM size - * @dev the function starts at a random index in the list to avoid biasing the first - * addresses in the list over latter ones. - * @dev the function will check at most MAX_CHECK proxies in a single call - * @dev the function returns a list with a max length of MAX_PERFORM - * @return list of target addresses which are underfunded - */ + /// @notice Gets a list of proxies that are underfunded, up to the s_maxPerform size + /// @dev the function starts at a random index in the list to avoid biasing the first + /// addresses in the list over latter ones. + /// @dev the function will check at most s_maxCheck proxies in a single call + /// @dev the function returns a list with a max length of s_maxPerform + /// @return list of target addresses which are underfunded function sampleUnderfundedAddresses() public view returns (address[] memory) { - uint256 numTargets = s_watchList.length(); - uint256 numChecked = 0; - uint256 idx = uint256(blockhash(block.number - 1)) % numTargets; // start at random index, to distribute load - uint256 numToCheck = numTargets < MAX_CHECK ? numTargets : MAX_CHECK; + uint16 maxPerform = s_maxPerform; + uint16 maxCheck = s_maxCheck; + uint256 numTargets = s_watchList.length; + uint256 idx = uint256(blockhash(block.number - (block.number % s_upkeepInterval) - 1)) % numTargets; + uint256 numToCheck = numTargets < maxCheck ? numTargets : maxCheck; uint256 numFound = 0; - address[] memory targetsToFund = new address[](MAX_PERFORM); - for (; numChecked < numToCheck; (idx, numChecked) = ((idx + 1) % numTargets, numChecked + 1)) { - (address target, uint256 minBalance) = s_watchList.at(idx); - (bool needsFunding, ) = _needsFunding(target, minBalance); - if (needsFunding) { - targetsToFund[numFound] = target; + address[] memory targetsToFund = new address[](maxPerform); + MonitoredAddress memory target; + for ( + uint256 numChecked = 0; + numChecked < numToCheck; + (idx, numChecked) = ((idx + 1) % numTargets, numChecked + 1) + ) { + address targetAddress = s_watchList[idx]; + target = s_targets[targetAddress]; + if (_needsFunding(targetAddress, target.minBalance)) { + targetsToFund[numFound] = targetAddress; numFound++; - if (numFound == MAX_PERFORM) { + if (numFound == maxPerform) { break; // max number of addresses in batch reached } } } - if (numFound != MAX_PERFORM) { + if (numFound != maxPerform) { assembly { mstore(targetsToFund, numFound) // resize array to number of valid targets } @@ -161,160 +156,172 @@ contract LinkAvailableBalanceMonitor is ConfirmedOwner, Pausable, KeeperCompatib return targetsToFund; } - /** - * @notice Send funds to the targets provided. - * @param targetAddresses the list of targets to fund - */ function topUp(address[] memory targetAddresses) public whenNotPaused { - uint256 topUpAmount = s_topUpAmount; - uint256 stopIdx = targetAddresses.length; - uint256 numCanFund = LINK_TOKEN.balanceOf(address(this)) / topUpAmount; - stopIdx = numCanFund < stopIdx ? numCanFund : stopIdx; - for (uint256 idx = 0; idx < stopIdx; idx++) { - (bool exists, uint256 minBalance) = s_watchList.tryGet(targetAddresses[idx]); - if (!exists) { - emit TopUpBlocked(targetAddresses[idx]); - continue; - } - (bool needsFunding, address target) = _needsFunding(targetAddresses[idx], minBalance); - if (!needsFunding) { - emit TopUpBlocked(targetAddresses[idx]); - continue; + MonitoredAddress memory target; + uint256 localBalance = LINK_TOKEN.balanceOf(address(this)); + for (uint256 idx = 0; idx < targetAddresses.length; idx++) { + address targetAddress = targetAddresses[idx]; + target = s_targets[targetAddress]; + if (localBalance >= target.topUpAmount && _needsFunding(targetAddress, target.minBalance)) { + bool success = LINK_TOKEN.transfer(targetAddress, target.topUpAmount); + if (success) { + localBalance -= target.topUpAmount; + target.lastTopUpTimestamp = uint56(block.timestamp); + emit TopUpSucceeded(targetAddress); + } else { + emit TopUpFailed(targetAddress); + } + } else { + emit TopUpBlocked(targetAddress); } - LINK_TOKEN.transfer(target, topUpAmount); - emit TopUpSucceeded(targetAddresses[idx]); } } - /** - * @notice Gets list of subscription ids that are underfunded and returns a keeper-compatible payload. - * @return upkeepNeeded signals if upkeep is needed - * @return performData is an abi encoded list of subscription ids that need funds - */ + /// @notice checks the target (could be direct target or IAggregatorProxy), and determines + /// if it is elligible for funding + /// @param targetAddress the target to check + /// @param minBalance minimum balance required for the target + /// @return bool whether the target needs funding or not + function _needsFunding(address targetAddress, uint256 minBalance) private view returns (bool) { + // Explicitly check if the targetAddress is the zero address + // or if it's not a contract. In both cases return with false, + // to prevent target.linkAvailableForPayment from running, + // which would revert the operation. + if (targetAddress == address(0) || targetAddress.code.length == 0) { + return false; + } + MonitoredAddress memory addressToCheck = s_targets[targetAddress]; + ILinkAvailable target; + IAggregatorProxy proxy = IAggregatorProxy(targetAddress); + try proxy.aggregator() returns (address aggregatorAddress) { + if (aggregatorAddress == address(0)) return false; + target = ILinkAvailable(aggregatorAddress); + } catch { + target = ILinkAvailable(targetAddress); + } + try target.linkAvailableForPayment() returns (int256 balance) { + if ( + balance < int256(minBalance) && addressToCheck.lastTopUpTimestamp + s_minWaitPeriodSeconds <= block.timestamp + ) { + return true; + } + } catch {} + return false; + } + + /// @notice Gets list of subscription ids that are underfunded and returns a keeper-compatible payload. + /// @return upkeepNeeded signals if upkeep is needed + /// @return performData is an abi encoded list of subscription ids that need funds function checkUpkeep( bytes calldata ) external view override whenNotPaused returns (bool upkeepNeeded, bytes memory performData) { address[] memory needsFunding = sampleUnderfundedAddresses(); - uint256 numCanFund = LINK_TOKEN.balanceOf(address(this)) / s_topUpAmount; - if (numCanFund < needsFunding.length) { - assembly { - mstore(needsFunding, numCanFund) // resize - } - } upkeepNeeded = needsFunding.length > 0; performData = abi.encode(needsFunding); return (upkeepNeeded, performData); } - /** - * @notice Called by the keeper to send funds to underfunded addresses. - * @param performData the abi encoded list of addresses to fund - */ + /// @notice Called by the keeper to send funds to underfunded addresses. + /// @param performData the abi encoded list of addresses to fund function performUpkeep(bytes calldata performData) external override { address[] memory needsFunding = abi.decode(performData, (address[])); topUp(needsFunding); } - /** - * @notice Withdraws the contract balance in the LINK token. - * @param amount the amount of the LINK to withdraw - * @param payee the address to pay - */ + /// @notice Withdraws the contract balance in the LINK token. + /// @param amount the amount of the LINK to withdraw + /// @param payee the address to pay function withdraw(uint256 amount, address payable payee) external onlyOwner { - require(payee != address(0)); + if (payee == address(0)) revert InvalidAddress(payee); LINK_TOKEN.transfer(payee, amount); emit FundsWithdrawn(amount, payee); } - /** - * @notice Sets the top up amount - */ - function setTopUpAmount(uint256 topUpAmount) external onlyOwner returns (uint256) { - require(topUpAmount > 0); - return s_topUpAmount = topUpAmount; + /// @notice Sets the minimum balance for the given target address + function setMinBalance(address target, uint96 minBalance) external onlyOwner { + if (target == address(0)) revert InvalidAddress(target); + if (minBalance == 0) revert InvalidMinBalance(minBalance); + if (!s_targets[target].isActive) revert InvalidWatchList(); + uint256 oldBalance = s_targets[target].minBalance; + s_targets[target].minBalance = minBalance; + emit BalanceUpdated(target, oldBalance, minBalance); } - /** - * @notice Sets the minimum balance for the given target address - */ - function setMinBalance(address target, uint256 minBalance) external onlyOwner returns (uint256) { - require(minBalance > 0); - (bool exists, uint256 prevMinBalance) = s_watchList.tryGet(target); - if (!exists) { - revert InvalidWatchList(); - } - s_watchList.set(target, minBalance); - return prevMinBalance; + /// @notice Sets the minimum balance for the given target address + function setTopUpAmount(address target, uint96 topUpAmount) external onlyOwner { + if (target == address(0)) revert InvalidAddress(target); + if (topUpAmount == 0) revert InvalidTopUpAmount(topUpAmount); + if (!s_targets[target].isActive) revert InvalidWatchList(); + uint256 oldTopUpAmount = s_targets[target].topUpAmount; + s_targets[target].topUpAmount = topUpAmount; + emit BalanceUpdated(target, oldTopUpAmount, topUpAmount); } - /** - * @notice Pause the contract, which prevents executing performUpkeep - */ - function pause() external onlyOwner { - _pause(); + /// @notice Update s_maxPerform + function setMaxPerform(uint16 maxPerform) public onlyOwner { + s_maxPerform = maxPerform; + emit MaxPerformSet(s_maxPerform, maxPerform); } - /** - * @notice Unpause the contract - */ - function unpause() external onlyOwner { - _unpause(); + /// @notice Update s_maxCheck + function setMaxCheck(uint16 maxCheck) public onlyOwner { + s_maxCheck = maxCheck; + emit MaxCheckSet(s_maxCheck, maxCheck); } - /** - * @notice Gets the list of subscription ids being watched - */ - function getWatchList() external view returns (address[] memory, uint256[] memory) { - uint256 len = s_watchList.length(); - address[] memory targets = new address[](len); - uint256[] memory minBalances = new uint256[](len); + /// @notice Sets the minimum wait period (in seconds) for addresses between funding + function setMinWaitPeriodSeconds(uint256 minWaitPeriodSeconds) public onlyOwner { + s_minWaitPeriodSeconds = minWaitPeriodSeconds; + emit MinWaitPeriodSet(s_minWaitPeriodSeconds, minWaitPeriodSeconds); + } - for (uint256 idx = 0; idx < len; idx++) { - (targets[idx], minBalances[idx]) = s_watchList.at(idx); - } + /// @notice Update s_upkeepInterval + function setUpkeepInterval(uint8 upkeepInterval) public onlyOwner { + if (upkeepInterval > 255) revert InvalidUpkeepInterval(upkeepInterval); + s_upkeepInterval = upkeepInterval; + emit UpkeepIntervalSet(s_upkeepInterval, upkeepInterval); + } - return (targets, minBalances); + /// @notice Gets maxPerform + function getMaxPerform() external view returns (uint16) { + return s_maxPerform; } - /** - * @notice Gets the configured top up amount - */ - function getTopUpAmount() external view returns (uint256) { - return s_topUpAmount; + /// @notice Gets maxCheck + function getMaxCheck() external view returns (uint16) { + return s_maxCheck; } - /** - * @notice Gets the configured minimum balance for the given target - */ - function getMinBalance(address target) external view returns (uint256) { - (bool exists, uint256 minBalance) = s_watchList.tryGet(target); - if (!exists) { - revert InvalidWatchList(); - } - return minBalance; + /// @notice Gets the minimum wait period + function getMinWaitPeriodSeconds() external view returns (uint256) { + return s_minWaitPeriodSeconds; } - /** - * @notice checks the target (could be direct target or IAggregatorProxy), and determines - * if it is elligible for funding - * @param targetAddress the target to check - * @param minBalance minimum balance required for the target - * @return bool whether the target needs funding or not - * @return address the address of the contract needing funding - */ - function _needsFunding(address targetAddress, uint256 minBalance) private view returns (bool, address) { - ILinkAvailable target; - IAggregatorProxy proxy = IAggregatorProxy(targetAddress); - try proxy.aggregator() returns (address aggregatorAddress) { - target = ILinkAvailable(aggregatorAddress); - } catch { - target = ILinkAvailable(targetAddress); - } - try target.linkAvailableForPayment() returns (int256 balance) { - if (balance < 0 || uint256(balance) < minBalance) { - return (true, address(target)); - } - } catch {} - return (false, address(0)); + /// @notice Gets upkeepInterval + function getUpkeepInterval() external view returns (uint8) { + return s_upkeepInterval; + } + + /// @notice Gets the list of subscription ids being watched + function getWatchList() external view returns (address[] memory) { + return s_watchList; + } + + /// @notice Gets configuration information for an address on the watchlist + function getAccountInfo( + address targetAddress + ) external view returns (bool isActive, uint256 minBalance, uint256 topUpAmount) { + MonitoredAddress memory target = s_targets[targetAddress]; + return (target.isActive, target.minBalance, target.topUpAmount); + } + + /// @notice Pause the contract, which prevents executing performUpkeep + function pause() external onlyOwner { + _pause(); + } + + /// @notice Unpause the contract + function unpause() external onlyOwner { + _unpause(); } } diff --git a/contracts/src/v0.8/automation/upkeeps/UpkeepBalanceMonitor.sol b/contracts/src/v0.8/automation/upkeeps/UpkeepBalanceMonitor.sol new file mode 100644 index 00000000000..dae17da7293 --- /dev/null +++ b/contracts/src/v0.8/automation/upkeeps/UpkeepBalanceMonitor.sol @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; +import {IAutomationRegistryConsumer} from "../interfaces/IAutomationRegistryConsumer.sol"; +import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol"; +import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +/// @title The UpkeepBalanceMonitor contract +/// @notice A keeper-compatible contract that monitors and funds Chainlink Automation upkeeps. +contract UpkeepBalanceMonitor is ConfirmedOwner, Pausable { + using EnumerableSet for EnumerableSet.AddressSet; + + event ConfigSet(Config config); + event ForwarderSet(address forwarderAddress); + event FundsWithdrawn(uint256 amountWithdrawn, address payee); + event TopUpFailed(uint256 indexed upkeepId); + event TopUpSucceeded(uint256 indexed upkeepId, uint96 amount); + event WatchListSet(address registryAddress); + + error InvalidConfig(); + error InvalidTopUpData(); + error OnlyForwarderOrOwner(); + + /// @member maxBatchSize is the maximum number of upkeeps to fund in a single transaction + /// @member minPercentage is the percentage of the upkeep's minBalance at which top-up occurs + /// @member targetPercentage is the percentage of the upkeep's minBalance to top-up to + /// @member maxTopUpAmount is the maximum amount of LINK to top-up an upkeep with + struct Config { + uint8 maxBatchSize; + uint24 minPercentage; + uint24 targetPercentage; + uint96 maxTopUpAmount; + } + + // ================================================================ + // | STORAGE | + // ================================================================ + + LinkTokenInterface private immutable LINK_TOKEN; + + mapping(address => uint256[]) s_registryWatchLists; + EnumerableSet.AddressSet s_registries; + Config private s_config; + address private s_forwarderAddress; + + // ================================================================ + // | CONSTRUCTOR | + // ================================================================ + + /// @param linkToken the Link token address + /// @param config the initial config for the contract + constructor(LinkTokenInterface linkToken, Config memory config) ConfirmedOwner(msg.sender) { + require(address(linkToken) != address(0)); + LINK_TOKEN = linkToken; + setConfig(config); + } + + // ================================================================ + // | CORE FUNCTIONALITY | + // ================================================================ + + /// @notice Gets a list of upkeeps that are underfunded + /// @return needsFunding list of underfunded upkeepIDs + /// @return registryAddresses list of registries that the upkeepIDs belong to + /// @return topUpAmounts amount to top up each upkeep + function getUnderfundedUpkeeps() public view returns (uint256[] memory, address[] memory, uint96[] memory) { + Config memory config = s_config; + uint256[] memory needsFunding = new uint256[](config.maxBatchSize); + address[] memory registryAddresses = new address[](config.maxBatchSize); + uint96[] memory topUpAmounts = new uint96[](config.maxBatchSize); + uint256 availableFunds = LINK_TOKEN.balanceOf(address(this)); + uint256 count; + for (uint256 i = 0; i < s_registries.length(); i++) { + IAutomationRegistryConsumer registry = IAutomationRegistryConsumer(s_registries.at(i)); + for (uint256 j = 0; j < s_registryWatchLists[address(registry)].length; j++) { + uint256 upkeepID = s_registryWatchLists[address(registry)][j]; + uint96 upkeepBalance = registry.getBalance(upkeepID); + uint96 minBalance = registry.getMinBalance(upkeepID); + uint96 topUpThreshold = (minBalance * config.minPercentage) / 100; + uint96 topUpAmount = ((minBalance * config.targetPercentage) / 100) - upkeepBalance; + if (topUpAmount > config.maxTopUpAmount) { + topUpAmount = config.maxTopUpAmount; + } + if (upkeepBalance <= topUpThreshold && availableFunds >= topUpAmount) { + needsFunding[count] = upkeepID; + topUpAmounts[count] = topUpAmount; + registryAddresses[count] = address(registry); + count++; + availableFunds -= topUpAmount; + } + if (count == config.maxBatchSize) { + break; + } + } + if (count == config.maxBatchSize) { + break; + } + } + if (count < config.maxBatchSize) { + assembly { + mstore(needsFunding, count) + mstore(registryAddresses, count) + mstore(topUpAmounts, count) + } + } + return (needsFunding, registryAddresses, topUpAmounts); + } + + /// @notice Called by the keeper/owner to send funds to underfunded upkeeps + /// @param upkeepIDs the list of upkeep ids to fund + /// @param registryAddresses the list of registries that the upkeepIDs belong to + /// @param topUpAmounts the list of amounts to fund each upkeep with + /// @dev We explicitly choose not to verify that input upkeepIDs are included in the watchlist. We also + /// explicity permit any amount to be sent via topUpAmounts; it does not have to meet the criteria + /// specified in getUnderfundedUpkeeps(). Here, we are relying on the security of automation's OCR to + /// secure the output of getUnderfundedUpkeeps() as the input to topUp(), and we are treating the owner + /// as a privileged user that can perform arbitrary top-ups to any upkeepID. + function topUp( + uint256[] memory upkeepIDs, + address[] memory registryAddresses, + uint96[] memory topUpAmounts + ) public whenNotPaused { + if (msg.sender != address(s_forwarderAddress) && msg.sender != owner()) revert OnlyForwarderOrOwner(); + if (upkeepIDs.length != registryAddresses.length || upkeepIDs.length != topUpAmounts.length) + revert InvalidTopUpData(); + for (uint256 i = 0; i < upkeepIDs.length; i++) { + try LINK_TOKEN.transferAndCall(registryAddresses[i], topUpAmounts[i], abi.encode(upkeepIDs[i])) returns ( + bool success + ) { + if (success) { + emit TopUpSucceeded(upkeepIDs[i], topUpAmounts[i]); + continue; + } + } catch {} + emit TopUpFailed(upkeepIDs[i]); + } + } + + // ================================================================ + // | AUTOMATION COMPATIBLE | + // ================================================================ + + /// @notice Gets list of upkeeps ids that are underfunded and returns a keeper-compatible payload. + /// @return upkeepNeeded signals if upkeep is needed, performData is an abi encoded list of subscription ids that need funds + function checkUpkeep(bytes calldata) external view returns (bool upkeepNeeded, bytes memory performData) { + ( + uint256[] memory needsFunding, + address[] memory registryAddresses, + uint96[] memory topUpAmounts + ) = getUnderfundedUpkeeps(); + upkeepNeeded = needsFunding.length > 0; + if (upkeepNeeded) { + performData = abi.encode(needsFunding, registryAddresses, topUpAmounts); + } + return (upkeepNeeded, performData); + } + + /// @notice Called by the keeper to send funds to underfunded addresses. + /// @param performData the abi encoded list of addresses to fund + function performUpkeep(bytes calldata performData) external { + (uint256[] memory upkeepIDs, address[] memory registryAddresses, uint96[] memory topUpAmounts) = abi.decode( + performData, + (uint256[], address[], uint96[]) + ); + topUp(upkeepIDs, registryAddresses, topUpAmounts); + } + + // ================================================================ + // | ADMIN | + // ================================================================ + + /// @notice Withdraws the contract balance in LINK. + /// @param amount the amount of LINK (in juels) to withdraw + /// @param payee the address to pay + function withdraw(uint256 amount, address payee) external onlyOwner { + require(payee != address(0)); + LINK_TOKEN.transfer(payee, amount); + emit FundsWithdrawn(amount, payee); + } + + /// @notice Pause the contract, which prevents executing performUpkeep. + function pause() external onlyOwner { + _pause(); + } + + /// @notice Unpause the contract. + function unpause() external onlyOwner { + _unpause(); + } + + // ================================================================ + // | SETTERS | + // ================================================================ + + /// @notice Sets the list of upkeeps to watch + /// @param registryAddress the registry that this watchlist applies to + /// @param watchlist the list of UpkeepIDs to watch + function setWatchList(address registryAddress, uint256[] calldata watchlist) external onlyOwner { + if (watchlist.length == 0) { + s_registries.remove(registryAddress); + delete s_registryWatchLists[registryAddress]; + } else { + s_registries.add(registryAddress); + s_registryWatchLists[registryAddress] = watchlist; + } + emit WatchListSet(registryAddress); + } + + /// @notice Sets the contract config + /// @param config the new config + function setConfig(Config memory config) public onlyOwner { + if ( + config.maxBatchSize == 0 || + config.minPercentage < 100 || + config.targetPercentage <= config.minPercentage || + config.maxTopUpAmount == 0 + ) { + revert InvalidConfig(); + } + s_config = config; + emit ConfigSet(config); + } + + /// @notice Sets the upkeep's forwarder contract + /// @param forwarderAddress the new forwarder + /// @dev this should only need to be called once, after registering the contract with the registry + function setForwarder(address forwarderAddress) external onlyOwner { + s_forwarderAddress = forwarderAddress; + emit ForwarderSet(forwarderAddress); + } + + // ================================================================ + // | GETTERS | + // ================================================================ + + /// @notice Gets the list of upkeeps ids being monitored + function getWatchList() external view returns (address[] memory, uint256[][] memory) { + address[] memory registryAddresses = s_registries.values(); + uint256[][] memory upkeepIDs = new uint256[][](registryAddresses.length); + for (uint256 i = 0; i < registryAddresses.length; i++) { + upkeepIDs[i] = s_registryWatchLists[registryAddresses[i]]; + } + return (registryAddresses, upkeepIDs); + } + + /// @notice Gets the contract config + function getConfig() external view returns (Config memory) { + return s_config; + } + + /// @notice Gets the upkeep's forwarder contract + function getForwarder() external view returns (address) { + return s_forwarderAddress; + } +} diff --git a/contracts/src/v0.8/automation/v1_2/KeeperRegistry1_2.sol b/contracts/src/v0.8/automation/v1_2/KeeperRegistry1_2.sol index 262b8357f7a..2fa1ee6188b 100644 --- a/contracts/src/v0.8/automation/v1_2/KeeperRegistry1_2.sol +++ b/contracts/src/v0.8/automation/v1_2/KeeperRegistry1_2.sol @@ -7,7 +7,7 @@ import "@openzeppelin/contracts/security/Pausable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "../KeeperBase.sol"; import "../../interfaces/TypeAndVersionInterface.sol"; -import "../../interfaces/AggregatorV3Interface.sol"; +import "../../shared/interfaces/AggregatorV3Interface.sol"; import "../interfaces/KeeperCompatibleInterface.sol"; import "../interfaces/v1_2/KeeperRegistryInterface1_2.sol"; import "../interfaces/MigratableKeeperRegistryInterface.sol"; diff --git a/contracts/src/v0.8/automation/v1_3/KeeperRegistryBase1_3.sol b/contracts/src/v0.8/automation/v1_3/KeeperRegistryBase1_3.sol index 6328b651671..c21f3a73912 100644 --- a/contracts/src/v0.8/automation/v1_3/KeeperRegistryBase1_3.sol +++ b/contracts/src/v0.8/automation/v1_3/KeeperRegistryBase1_3.sol @@ -8,7 +8,7 @@ import "../../vendor/@eth-optimism/contracts/v0.8.6/contracts/L2/predeploys/OVM_ import "../ExecutionPrevention.sol"; import {Config, Upkeep} from "../interfaces/v1_3/AutomationRegistryInterface1_3.sol"; import "../../shared/access/ConfirmedOwner.sol"; -import "../../interfaces/AggregatorV3Interface.sol"; +import "../../shared/interfaces/AggregatorV3Interface.sol"; import "../../shared/interfaces/LinkTokenInterface.sol"; import "../interfaces/KeeperCompatibleInterface.sol"; import "../interfaces/UpkeepTranscoderInterface.sol"; diff --git a/contracts/src/v0.8/automation/v2_0/KeeperRegistry2_0.sol b/contracts/src/v0.8/automation/v2_0/KeeperRegistry2_0.sol index 7db02e72e73..bd3c78e45a7 100644 --- a/contracts/src/v0.8/automation/v2_0/KeeperRegistry2_0.sol +++ b/contracts/src/v0.8/automation/v2_0/KeeperRegistry2_0.sol @@ -281,7 +281,7 @@ contract KeeperRegistry2_0 is uint64 offchainConfigVersion, bytes memory offchainConfig ) external override onlyOwner { - if (signers.length > maxNumOracles) revert TooManyOracles(); + if (signers.length > MAX_NUM_ORACLES) revert TooManyOracles(); if (f == 0) revert IncorrectNumberOfFaultyOracles(); if (signers.length != transmitters.length || signers.length <= 3 * f) revert IncorrectNumberOfSigners(); diff --git a/contracts/src/v0.8/automation/v2_0/KeeperRegistryBase2_0.sol b/contracts/src/v0.8/automation/v2_0/KeeperRegistryBase2_0.sol index 14e9b204475..9b78e5806ff 100644 --- a/contracts/src/v0.8/automation/v2_0/KeeperRegistryBase2_0.sol +++ b/contracts/src/v0.8/automation/v2_0/KeeperRegistryBase2_0.sol @@ -7,7 +7,7 @@ import "../../vendor/@eth-optimism/contracts/v0.8.6/contracts/L2/predeploys/OVM_ import {ArbSys} from "../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol"; import "../ExecutionPrevention.sol"; import "../../shared/access/ConfirmedOwner.sol"; -import "../../interfaces/AggregatorV3Interface.sol"; +import "../../shared/interfaces/AggregatorV3Interface.sol"; import "../../shared/interfaces/LinkTokenInterface.sol"; import "../interfaces/KeeperCompatibleInterface.sol"; import "../interfaces/UpkeepTranscoderInterface.sol"; diff --git a/contracts/src/v0.8/automation/v2_1/KeeperRegistry2_1.sol b/contracts/src/v0.8/automation/v2_1/KeeperRegistry2_1.sol index 2f96df6d572..7c88f12f5a1 100644 --- a/contracts/src/v0.8/automation/v2_1/KeeperRegistry2_1.sol +++ b/contracts/src/v0.8/automation/v2_1/KeeperRegistry2_1.sol @@ -250,7 +250,7 @@ contract KeeperRegistry2_1 is KeeperRegistryBase2_1, OCR2Abstract, Chainable, IE uint64 offchainConfigVersion, bytes memory offchainConfig ) public onlyOwner { - if (signers.length > maxNumOracles) revert TooManyOracles(); + if (signers.length > MAX_NUM_ORACLES) revert TooManyOracles(); if (f == 0) revert IncorrectNumberOfFaultyOracles(); if (signers.length != transmitters.length || signers.length <= 3 * f) revert IncorrectNumberOfSigners(); diff --git a/contracts/src/v0.8/automation/v2_1/KeeperRegistryBase2_1.sol b/contracts/src/v0.8/automation/v2_1/KeeperRegistryBase2_1.sol index f5d89de5467..f81fcef636e 100644 --- a/contracts/src/v0.8/automation/v2_1/KeeperRegistryBase2_1.sol +++ b/contracts/src/v0.8/automation/v2_1/KeeperRegistryBase2_1.sol @@ -11,7 +11,7 @@ import {StreamsLookupCompatibleInterface} from "../interfaces/StreamsLookupCompa import {ILogAutomation, Log} from "../interfaces/ILogAutomation.sol"; import {IAutomationForwarder} from "../interfaces/IAutomationForwarder.sol"; import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; -import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol"; +import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol"; import {KeeperCompatibleInterface} from "../interfaces/KeeperCompatibleInterface.sol"; import {UpkeepFormat} from "../interfaces/UpkeepTranscoderInterface.sol"; diff --git a/contracts/src/v0.8/automation/v2_1/LICENSE b/contracts/src/v0.8/automation/v2_1/LICENSE index 03ba03d7792..7d09d647f5b 100644 --- a/contracts/src/v0.8/automation/v2_1/LICENSE +++ b/contracts/src/v0.8/automation/v2_1/LICENSE @@ -12,7 +12,7 @@ Licensor: SmartContract Chainlink Limited SEZC Licensed Work: Automation v2.1 The Licensed Work is (c) 2023 SmartContract Chainlink Limited SEZC -Additional Use Grant: Any uses listed and defined at https://github.com/smartcontractkit/ocr2keepers/tree/main/pkg/v3/Automation_v2.1_Grants.md +Additional Use Grant: Any uses listed and defined at https://github.com/smartcontractkit/chainlink-automation/tree/main/pkg/v3/Automation_v2.1_Grants.md Change Date: September 12, 2027 diff --git a/contracts/src/v0.8/dev/shared/interfaces/OwnableInterface.sol b/contracts/src/v0.8/dev/shared/interfaces/OwnableInterface.sol deleted file mode 100644 index a24cbee504c..00000000000 --- a/contracts/src/v0.8/dev/shared/interfaces/OwnableInterface.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface OwnableInterface { - function owner() external returns (address); - - function transferOwnership(address recipient) external; - - function acceptOwnership() external; -} diff --git a/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol b/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol index e7958a7bcab..cb4f2f45677 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol @@ -2,13 +2,15 @@ pragma solidity ^0.8.19; import {IFunctionsSubscriptions} from "./interfaces/IFunctionsSubscriptions.sol"; -import {AggregatorV3Interface} from "../../../interfaces/AggregatorV3Interface.sol"; +import {AggregatorV3Interface} from "../../../shared/interfaces/AggregatorV3Interface.sol"; import {IFunctionsBilling} from "./interfaces/IFunctionsBilling.sol"; import {Routable} from "./Routable.sol"; import {FunctionsResponse} from "./libraries/FunctionsResponse.sol"; -import {SafeCast} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/SafeCast.sol"; +import {SafeCast} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SafeCast.sol"; + +import {ChainSpecificUtil} from "./libraries/ChainSpecificUtil.sol"; /// @title Functions Billing contract /// @notice Contract that calculates payment from users to the nodes of the Decentralized Oracle Network (DON). @@ -18,6 +20,15 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { using FunctionsResponse for FunctionsResponse.FulfillResult; uint256 private constant REASONABLE_GAS_PRICE_CEILING = 1_000_000_000_000_000; // 1 million gwei + + event RequestBilled( + bytes32 indexed requestId, + uint96 juelsPerGas, + uint256 l1FeeShareWei, + uint96 callbackCostJuels, + uint96 totalCostJuels + ); + // ================================================================ // | Request Commitment state | // ================================================================ @@ -123,10 +134,10 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { return uint256(weiPerUnitLink); } - function _getJuelsPerGas(uint256 gasPriceWei) private view returns (uint96) { - // (1e18 juels/link) * (wei/gas) / (wei/link) = juels per gas + function _getJuelsFromWei(uint256 amountWei) private view returns (uint96) { + // (1e18 juels/link) * wei / (wei/link) = juels // There are only 1e9*1e18 = 1e27 juels in existence, should not exceed uint96 (2^96 ~ 7e28) - return SafeCast.toUint96((1e18 * gasPriceWei) / getWeiPerUnitLink()); + return SafeCast.toUint96((1e18 * amountWei) / getWeiPerUnitLink()); } // ================================================================ @@ -159,8 +170,6 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { uint72 donFee, uint72 adminFee ) internal view returns (uint96) { - uint256 executionGas = s_config.gasOverheadBeforeCallback + s_config.gasOverheadAfterCallback + callbackGasLimit; - // If gas price is less than the minimum fulfillment gas price, override to using the minimum if (gasPriceWei < s_config.minimumEstimateGasPriceWei) { gasPriceWei = s_config.minimumEstimateGasPriceWei; @@ -170,11 +179,13 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { ((gasPriceWei * s_config.fulfillmentGasPriceOverEstimationBP) / 10_000); /// @NOTE: Basis Points are 1/100th of 1%, divide by 10_000 to bring back to original units - uint96 juelsPerGas = _getJuelsPerGas(gasPriceWithOverestimation); - uint256 estimatedGasReimbursement = juelsPerGas * executionGas; - uint96 fees = uint96(donFee) + uint96(adminFee); + uint256 executionGas = s_config.gasOverheadBeforeCallback + s_config.gasOverheadAfterCallback + callbackGasLimit; + uint256 l1FeeWei = ChainSpecificUtil._getCurrentTxL1GasFees(msg.data); + uint96 estimatedGasReimbursementJuels = _getJuelsFromWei((gasPriceWithOverestimation * executionGas) + l1FeeWei); + + uint96 feesJuels = uint96(donFee) + uint96(adminFee); - return SafeCast.toUint96(estimatedGasReimbursement + fees); + return estimatedGasReimbursementJuels + feesJuels; } // ================================================================ @@ -248,6 +259,7 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { /// @param requestId identifier for the request that was generated by the Registry in the beginBilling commitment /// @param response response data from DON consensus /// @param err error from DON consensus + /// @param reportBatchSize the number of fulfillments in the transmitter's report /// @return result fulfillment result /// @dev Only callable by a node that has been approved on the Coordinator /// @dev simulated offchain to determine if sufficient balance is present to fulfill the request @@ -256,21 +268,23 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { bytes memory response, bytes memory err, bytes memory onchainMetadata, - bytes memory /* offchainMetadata TODO: use in getDonFee() for dynamic billing */ + bytes memory /* offchainMetadata TODO: use in getDonFee() for dynamic billing */, + uint8 reportBatchSize ) internal returns (FunctionsResponse.FulfillResult) { FunctionsResponse.Commitment memory commitment = abi.decode(onchainMetadata, (FunctionsResponse.Commitment)); - uint96 juelsPerGas = _getJuelsPerGas(tx.gasprice); + uint256 gasOverheadWei = (commitment.gasOverheadBeforeCallback + commitment.gasOverheadAfterCallback) * tx.gasprice; + uint256 l1FeeShareWei = ChainSpecificUtil._getCurrentTxL1GasFees(msg.data) / reportBatchSize; // Gas overhead without callback - uint96 gasOverheadJuels = juelsPerGas * - (commitment.gasOverheadBeforeCallback + commitment.gasOverheadAfterCallback); + uint96 gasOverheadJuels = _getJuelsFromWei(gasOverheadWei + l1FeeShareWei); + uint96 juelsPerGas = _getJuelsFromWei(tx.gasprice); // The Functions Router will perform the callback to the client contract (FunctionsResponse.FulfillResult resultCode, uint96 callbackCostJuels) = _getRouter().fulfill( response, err, juelsPerGas, - gasOverheadJuels + commitment.donFee, // costWithoutFulfillment + gasOverheadJuels + commitment.donFee, // cost without callback or admin fee, those will be added by the Router msg.sender, commitment ); @@ -288,6 +302,13 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { // Put donFee into the pool of fees, to be split later // Saves on storage writes that would otherwise be charged to the user s_feePool += commitment.donFee; + emit RequestBilled({ + requestId: requestId, + juelsPerGas: juelsPerGas, + l1FeeShareWei: l1FeeShareWei, + callbackCostJuels: callbackCostJuels, + totalCostJuels: gasOverheadJuels + callbackCostJuels + commitment.donFee + commitment.adminFee + }); } return resultCode; @@ -353,15 +374,16 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { // All transmitters are assumed to also be observers // Pay out the DON fee to all transmitters address[] memory transmitters = _getTransmitters(); - if (transmitters.length == 0) { + uint256 numberOfTransmitters = transmitters.length; + if (numberOfTransmitters == 0) { revert NoTransmittersSet(); } - uint96 feePoolShare = s_feePool / uint96(transmitters.length); + uint96 feePoolShare = s_feePool / uint96(numberOfTransmitters); // Bounded by "maxNumOracles" on OCR2Abstract.sol - for (uint256 i = 0; i < transmitters.length; ++i) { + for (uint256 i = 0; i < numberOfTransmitters; ++i) { s_withdrawableTokens[transmitters[i]] += feePoolShare; } - s_feePool -= feePoolShare * uint96(transmitters.length); + s_feePool -= feePoolShare * uint96(numberOfTransmitters); } // Overriden in FunctionsCoordinator.sol diff --git a/contracts/src/v0.8/functions/dev/v1_X/FunctionsClient.sol b/contracts/src/v0.8/functions/dev/v1_X/FunctionsClient.sol index 6d033d4b235..4aabef01f28 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/FunctionsClient.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/FunctionsClient.sol @@ -11,7 +11,7 @@ import {FunctionsRequest} from "./libraries/FunctionsRequest.sol"; abstract contract FunctionsClient is IFunctionsClient { using FunctionsRequest for FunctionsRequest.Request; - IFunctionsRouter internal immutable i_router; + IFunctionsRouter internal immutable i_functionsRouter; event RequestSent(bytes32 indexed id); event RequestFulfilled(bytes32 indexed id); @@ -19,7 +19,7 @@ abstract contract FunctionsClient is IFunctionsClient { error OnlyRouterCanFulfill(); constructor(address router) { - i_router = IFunctionsRouter(router); + i_functionsRouter = IFunctionsRouter(router); } /// @notice Sends a Chainlink Functions request @@ -33,7 +33,7 @@ abstract contract FunctionsClient is IFunctionsClient { uint32 callbackGasLimit, bytes32 donId ) internal returns (bytes32) { - bytes32 requestId = i_router.sendRequest( + bytes32 requestId = i_functionsRouter.sendRequest( subscriptionId, data, FunctionsRequest.REQUEST_DATA_VERSION, @@ -53,7 +53,7 @@ abstract contract FunctionsClient is IFunctionsClient { /// @inheritdoc IFunctionsClient function handleOracleFulfillment(bytes32 requestId, bytes memory response, bytes memory err) external override { - if (msg.sender != address(i_router)) { + if (msg.sender != address(i_functionsRouter)) { revert OnlyRouterCanFulfill(); } _fulfillRequest(requestId, response, err); diff --git a/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol b/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol index eb0d954ae02..16e9029ce3f 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol @@ -44,7 +44,7 @@ contract FunctionsCoordinator is OCR2Base, IFunctionsCoordinator, FunctionsBilli address router, Config memory config, address linkToNativeFeed - ) OCR2Base(true) FunctionsBilling(router, config, linkToNativeFeed) {} + ) OCR2Base() FunctionsBilling(router, config, linkToNativeFeed) {} /// @inheritdoc IFunctionsCoordinator function getThresholdPublicKey() external view override returns (bytes memory) { @@ -133,30 +133,36 @@ contract FunctionsCoordinator is OCR2Base, IFunctionsCoordinator, FunctionsBilli address[MAX_NUM_ORACLES] memory /*signers*/, bytes calldata report ) internal override { - bytes32[] memory requestIds; - bytes[] memory results; - bytes[] memory errors; - bytes[] memory onchainMetadata; - bytes[] memory offchainMetadata; - (requestIds, results, errors, onchainMetadata, offchainMetadata) = abi.decode( - report, - (bytes32[], bytes[], bytes[], bytes[], bytes[]) - ); + ( + bytes32[] memory requestIds, + bytes[] memory results, + bytes[] memory errors, + bytes[] memory onchainMetadata, + bytes[] memory offchainMetadata + ) = abi.decode(report, (bytes32[], bytes[], bytes[], bytes[], bytes[])); + uint256 numberOfFulfillments = uint8(requestIds.length); if ( - requestIds.length == 0 || - requestIds.length != results.length || - requestIds.length != errors.length || - requestIds.length != onchainMetadata.length || - requestIds.length != offchainMetadata.length + numberOfFulfillments == 0 || + numberOfFulfillments != results.length || + numberOfFulfillments != errors.length || + numberOfFulfillments != onchainMetadata.length || + numberOfFulfillments != offchainMetadata.length ) { - revert ReportInvalid(); + revert ReportInvalid("Fields must be equal length"); } // Bounded by "MaxRequestBatchSize" on the Job's ReportingPluginConfig - for (uint256 i = 0; i < requestIds.length; ++i) { + for (uint256 i = 0; i < numberOfFulfillments; ++i) { FunctionsResponse.FulfillResult result = FunctionsResponse.FulfillResult( - _fulfillAndBill(requestIds[i], results[i], errors[i], onchainMetadata[i], offchainMetadata[i]) + _fulfillAndBill( + requestIds[i], + results[i], + errors[i], + onchainMetadata[i], + offchainMetadata[i], + uint8(numberOfFulfillments) // will not exceed "MaxRequestBatchSize" on the Job's ReportingPluginConfig + ) ); // Emit on successfully processing the fulfillment diff --git a/contracts/src/v0.8/functions/dev/v1_X/FunctionsRouter.sol b/contracts/src/v0.8/functions/dev/v1_X/FunctionsRouter.sol index 8daa821bb54..2e12a75679a 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/FunctionsRouter.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/FunctionsRouter.sol @@ -10,8 +10,8 @@ import {FunctionsSubscriptions} from "./FunctionsSubscriptions.sol"; import {FunctionsResponse} from "./libraries/FunctionsResponse.sol"; import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; -import {SafeCast} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/SafeCast.sol"; -import {Pausable} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/security/Pausable.sol"; +import {SafeCast} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SafeCast.sol"; +import {Pausable} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/security/Pausable.sol"; contract FunctionsRouter is IFunctionsRouter, FunctionsSubscriptions, Pausable, ITypeAndVersion, ConfirmedOwner { using FunctionsResponse for FunctionsResponse.RequestMeta; diff --git a/contracts/src/v0.8/functions/dev/v1_X/FunctionsSubscriptions.sol b/contracts/src/v0.8/functions/dev/v1_X/FunctionsSubscriptions.sol index 86e762e39c8..bcd01c1f0cf 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/FunctionsSubscriptions.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/FunctionsSubscriptions.sol @@ -7,8 +7,8 @@ import {IFunctionsBilling} from "./interfaces/IFunctionsBilling.sol"; import {FunctionsResponse} from "./libraries/FunctionsResponse.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title Functions Subscriptions contract /// @notice Contract that coordinates payment from users to the nodes of the Decentralized Oracle Network (DON). diff --git a/contracts/src/v0.8/functions/dev/v1_X/Routable.sol b/contracts/src/v0.8/functions/dev/v1_X/Routable.sol index b50b1e603f9..92e23362f9e 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/Routable.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/Routable.sol @@ -8,7 +8,7 @@ import {IOwnableFunctionsRouter} from "./interfaces/IOwnableFunctionsRouter.sol" /// as the destinations to a route (id=>contract) on the Router. /// It provides a Router getter and modifiers. abstract contract Routable is ITypeAndVersion { - IOwnableFunctionsRouter private immutable i_router; + IOwnableFunctionsRouter private immutable i_functionsRouter; error RouterMustBeSet(); error OnlyCallableByRouter(); @@ -19,17 +19,17 @@ abstract contract Routable is ITypeAndVersion { if (router == address(0)) { revert RouterMustBeSet(); } - i_router = IOwnableFunctionsRouter(router); + i_functionsRouter = IOwnableFunctionsRouter(router); } /// @notice Return the Router function _getRouter() internal view returns (IOwnableFunctionsRouter router) { - return i_router; + return i_functionsRouter; } /// @notice Reverts if called by anyone other than the router. modifier onlyRouter() { - if (msg.sender != address(i_router)) { + if (msg.sender != address(i_functionsRouter)) { revert OnlyCallableByRouter(); } _; @@ -37,7 +37,7 @@ abstract contract Routable is ITypeAndVersion { /// @notice Reverts if called by anyone other than the router owner. modifier onlyRouterOwner() { - if (msg.sender != i_router.owner()) { + if (msg.sender != i_functionsRouter.owner()) { revert OnlyCallableByRouterOwner(); } _; diff --git a/contracts/src/v0.8/functions/dev/v1_X/accessControl/TermsOfServiceAllowList.sol b/contracts/src/v0.8/functions/dev/v1_X/accessControl/TermsOfServiceAllowList.sol index fbeef3c298a..b36d063ad7d 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/accessControl/TermsOfServiceAllowList.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/accessControl/TermsOfServiceAllowList.sol @@ -7,8 +7,8 @@ import {ITypeAndVersion} from "../../../../shared/interfaces/ITypeAndVersion.sol import {ConfirmedOwner} from "../../../../shared/access/ConfirmedOwner.sol"; -import {Address} from "../../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Address.sol"; -import {EnumerableSet} from "../../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Address.sol"; +import {EnumerableSet} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol"; /// @notice A contract to handle access control of subscription management dependent on signing a Terms of Service contract TermsOfServiceAllowList is ITermsOfServiceAllowList, IAccessController, ITypeAndVersion, ConfirmedOwner { diff --git a/contracts/src/v0.8/functions/dev/v1_X/interfaces/IOwnableFunctionsRouter.sol b/contracts/src/v0.8/functions/dev/v1_X/interfaces/IOwnableFunctionsRouter.sol index 39b84a930aa..f6d7880da3e 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/interfaces/IOwnableFunctionsRouter.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/interfaces/IOwnableFunctionsRouter.sol @@ -5,6 +5,4 @@ import {IFunctionsRouter} from "./IFunctionsRouter.sol"; import {IOwnable} from "../../../../shared/interfaces/IOwnable.sol"; /// @title Chainlink Functions Router interface with Ownability. -interface IOwnableFunctionsRouter is IOwnable, IFunctionsRouter { - -} +interface IOwnableFunctionsRouter is IOwnable, IFunctionsRouter {} diff --git a/contracts/src/v0.8/functions/dev/v1_X/libraries/ChainSpecificUtil.sol b/contracts/src/v0.8/functions/dev/v1_X/libraries/ChainSpecificUtil.sol new file mode 100644 index 00000000000..574d1bf1645 --- /dev/null +++ b/contracts/src/v0.8/functions/dev/v1_X/libraries/ChainSpecificUtil.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ArbGasInfo} from "../../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol"; +import {GasPriceOracle} from "../../../../vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/L2/GasPriceOracle.sol"; + +/// @dev A library that abstracts out opcodes that behave differently across chains. +/// @dev The methods below return values that are pertinent to the given chain. +library ChainSpecificUtil { + // ------------ Start Arbitrum Constants ------------ + + /// @dev ARBGAS_ADDR is the address of the ArbGasInfo precompile on Arbitrum. + /// @dev reference: https://github.com/OffchainLabs/nitro/blob/v2.0.14/contracts/src/precompiles/ArbGasInfo.sol#L10 + address private constant ARBGAS_ADDR = address(0x000000000000000000000000000000000000006C); + ArbGasInfo private constant ARBGAS = ArbGasInfo(ARBGAS_ADDR); + + uint256 private constant ARB_MAINNET_CHAIN_ID = 42161; + uint256 private constant ARB_GOERLI_TESTNET_CHAIN_ID = 421613; + uint256 private constant ARB_SEPOLIA_TESTNET_CHAIN_ID = 421614; + + // ------------ End Arbitrum Constants ------------ + + // ------------ Start Optimism Constants ------------ + /// @dev L1_FEE_DATA_PADDING includes 35 bytes for L1 data padding for Optimism + bytes internal constant L1_FEE_DATA_PADDING = + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + /// @dev OVM_GASPRICEORACLE_ADDR is the address of the GasPriceOracle precompile on Optimism. + /// @dev reference: https://community.optimism.io/docs/developers/build/transaction-fees/#estimating-the-l1-data-fee + address private constant OVM_GASPRICEORACLE_ADDR = address(0x420000000000000000000000000000000000000F); + GasPriceOracle private constant OVM_GASPRICEORACLE = GasPriceOracle(OVM_GASPRICEORACLE_ADDR); + + uint256 private constant OP_MAINNET_CHAIN_ID = 10; + uint256 private constant OP_GOERLI_CHAIN_ID = 420; + uint256 private constant OP_SEPOLIA_CHAIN_ID = 11155420; + + /// @dev Base is a OP stack based rollup and follows the same L1 pricing logic as Optimism. + uint256 private constant BASE_MAINNET_CHAIN_ID = 8453; + uint256 private constant BASE_GOERLI_CHAIN_ID = 84531; + uint256 private constant BASE_SEPOLIA_CHAIN_ID = 84532; + + // ------------ End Optimism Constants ------------ + + /// @notice Returns the L1 fees in wei that will be paid for the current transaction, given any calldata + /// @notice for the current transaction. + /// @notice When on a known Arbitrum chain, it uses ArbGas.getCurrentTxL1GasFees to get the fees. + /// @notice On Arbitrum, the provided calldata is not used to calculate the fees. + /// @notice On Optimism, the provided calldata is passed to the GasPriceOracle predeploy + /// @notice and getL1Fee is called to get the fees. + function _getCurrentTxL1GasFees(bytes memory txCallData) internal view returns (uint256 l1FeeWei) { + uint256 chainid = block.chainid; + if (_isArbitrumChainId(chainid)) { + return ARBGAS.getCurrentTxL1GasFees(); + } else if (_isOptimismChainId(chainid)) { + return OVM_GASPRICEORACLE.getL1Fee(bytes.concat(txCallData, L1_FEE_DATA_PADDING)); + } + return 0; + } + + /// @notice Return true if and only if the provided chain ID is an Arbitrum chain ID. + function _isArbitrumChainId(uint256 chainId) internal pure returns (bool) { + return + chainId == ARB_MAINNET_CHAIN_ID || + chainId == ARB_GOERLI_TESTNET_CHAIN_ID || + chainId == ARB_SEPOLIA_TESTNET_CHAIN_ID; + } + + /// @notice Return true if and only if the provided chain ID is an Optimism (or Base) chain ID. + /// @notice Note that optimism chain id's are also OP stack chain id's. + function _isOptimismChainId(uint256 chainId) internal pure returns (bool) { + return + chainId == OP_MAINNET_CHAIN_ID || + chainId == OP_GOERLI_CHAIN_ID || + chainId == OP_SEPOLIA_CHAIN_ID || + chainId == BASE_MAINNET_CHAIN_ID || + chainId == BASE_GOERLI_CHAIN_ID || + chainId == BASE_SEPOLIA_CHAIN_ID; + } +} diff --git a/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol b/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol index dd9ea84a519..375159bf4c9 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol @@ -10,17 +10,10 @@ import {OCR2Abstract} from "./OCR2Abstract.sol"; * doc, which refers to this contract as simply the "contract". */ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { - error ReportInvalid(); + error ReportInvalid(string message); error InvalidConfig(string message); - bool internal immutable i_uniqueReports; - - constructor(bool uniqueReports) ConfirmedOwner(msg.sender) { - i_uniqueReports = uniqueReports; - } - - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - uint256 private constant maxUint32 = (1 << 32) - 1; + constructor() ConfirmedOwner(msg.sender) {} // incremented each time a new config is posted. This count is incorporated // into the config digest, to prevent replay attacks. @@ -144,12 +137,12 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { // Bounded by MAX_NUM_ORACLES in OCR2Abstract.sol for (uint256 i = 0; i < args.signers.length; i++) { + if (args.signers[i] == address(0)) revert InvalidConfig("signer must not be empty"); + if (args.transmitters[i] == address(0)) revert InvalidConfig("transmitter must not be empty"); // add new signer/transmitter addresses - // solhint-disable-next-line custom-errors - require(s_oracles[args.signers[i]].role == Role.Unset, "repeated signer address"); + if (s_oracles[args.signers[i]].role != Role.Unset) revert InvalidConfig("repeated signer address"); s_oracles[args.signers[i]] = Oracle(uint8(i), Role.Signer); - // solhint-disable-next-line custom-errors - require(s_oracles[args.transmitters[i]].role == Role.Unset, "repeated transmitter address"); + if (s_oracles[args.transmitters[i]].role != Role.Unset) revert InvalidConfig("repeated transmitter address"); s_oracles[args.transmitters[i]] = Oracle(uint8(i), Role.Transmitter); s_signers.push(args.signers[i]); s_transmitters.push(args.transmitters[i]); @@ -287,8 +280,7 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { ss.length * 32 + // 32 bytes per entry in _ss 0; // placeholder - // solhint-disable-next-line custom-errors - require(msg.data.length == expected, "calldata length mismatch"); + if (msg.data.length != expected) revert ReportInvalid("calldata length mismatch"); } /** @@ -319,30 +311,20 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { emit Transmitted(configDigest, uint32(epochAndRound >> 8)); - ConfigInfo memory configInfo = s_configInfo; - // solhint-disable-next-line custom-errors - require(configInfo.latestConfigDigest == configDigest, "configDigest mismatch"); + // The following check is disabled to allow both current and proposed routes to submit reports using the same OCR config digest + // Chainlink Functions uses globally unique request IDs. Metadata about the request is stored and checked in the Coordinator and Router + // require(configInfo.latestConfigDigest == configDigest, "configDigest mismatch"); _requireExpectedMsgDataLength(report, rs, ss); - uint256 expectedNumSignatures; - if (i_uniqueReports) { - expectedNumSignatures = (configInfo.n + configInfo.f) / 2 + 1; - } else { - expectedNumSignatures = configInfo.f + 1; - } + uint256 expectedNumSignatures = (s_configInfo.n + s_configInfo.f) / 2 + 1; - // solhint-disable-next-line custom-errors - require(rs.length == expectedNumSignatures, "wrong number of signatures"); - // solhint-disable-next-line custom-errors - require(rs.length == ss.length, "signatures out of registration"); + if (rs.length != expectedNumSignatures) revert ReportInvalid("wrong number of signatures"); + if (rs.length != ss.length) revert ReportInvalid("report rs and ss must be of equal length"); Oracle memory transmitter = s_oracles[msg.sender]; - // solhint-disable-next-line custom-errors - require( // Check that sender is authorized to report - transmitter.role == Role.Transmitter && msg.sender == s_transmitters[transmitter.index], - "unauthorized transmitter" - ); + if (transmitter.role != Role.Transmitter && msg.sender != s_transmitters[transmitter.index]) + revert ReportInvalid("unauthorized transmitter"); } address[MAX_NUM_ORACLES] memory signed; @@ -357,10 +339,8 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { for (uint256 i = 0; i < rs.length; ++i) { address signer = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]); o = s_oracles[signer]; - // solhint-disable-next-line custom-errors - require(o.role == Role.Signer, "address not authorized to sign"); - // solhint-disable-next-line custom-errors - require(signed[o.index] == address(0), "non-unique signature"); + if (o.role != Role.Signer) revert ReportInvalid("address not authorized to sign"); + if (signed[o.index] != address(0)) revert ReportInvalid("non-unique signature"); signed[o.index] = signer; signerCount += 1; } diff --git a/contracts/src/v0.8/functions/tests/v1_X/BaseTest.t.sol b/contracts/src/v0.8/functions/tests/v1_X/BaseTest.t.sol index b012732897f..ffa684e84ce 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/BaseTest.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/BaseTest.t.sol @@ -19,7 +19,7 @@ contract BaseTest is Test { // BaseTest.setUp is often called multiple times from tests' setUp due to inheritance. if (s_baseTestInitialized) return; s_baseTestInitialized = true; - // Set msg.sender to OWNER until stopPrank is called + // Set msg.sender and tx.origin to OWNER until stopPrank is called vm.startPrank(OWNER_ADDRESS, OWNER_ADDRESS); } } diff --git a/contracts/src/v0.8/functions/tests/v1_X/ChainSpecificUtil.t.sol b/contracts/src/v0.8/functions/tests/v1_X/ChainSpecificUtil.t.sol new file mode 100644 index 00000000000..5384a66d912 --- /dev/null +++ b/contracts/src/v0.8/functions/tests/v1_X/ChainSpecificUtil.t.sol @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {BaseTest} from "./BaseTest.t.sol"; +import {FunctionsClient} from "../../dev/v1_X/FunctionsClient.sol"; +import {FunctionsRouter} from "../../dev/v1_X/FunctionsRouter.sol"; +import {FunctionsSubscriptions} from "../../dev/v1_X/FunctionsSubscriptions.sol"; +import {FunctionsRequest} from "../../dev/v1_X/libraries/FunctionsRequest.sol"; +import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol"; + +import {FunctionsFulfillmentSetup} from "./Setup.t.sol"; + +import {ArbGasInfo} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol"; +import {OVM_GasPriceOracle} from "../../../vendor/@eth-optimism/contracts/v0.8.9/contracts/L2/predeploys/OVM_GasPriceOracle.sol"; + +/// @notice #_getCurrentTxL1GasFees Arbitrum +/// @dev Arbitrum gas formula = L2 Gas Price * (Gas used on L2 + Extra Buffer for L1 cost) +/// @dev where Extra Buffer for L1 cost = (L1 Estimated Cost / L2 Gas Price) +contract ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum is FunctionsFulfillmentSetup { + address private constant ARBGAS_ADDR = address(0x000000000000000000000000000000000000006C); + uint256 private constant L1_FEE_WEI = 15_818_209_764_247; + + uint96 l1FeeJuels = uint96((1e18 * L1_FEE_WEI) / uint256(LINK_ETH_RATE)); + + function setUp() public virtual override { + vm.mockCall(ARBGAS_ADDR, abi.encodeWithSelector(ArbGasInfo.getCurrentTxL1GasFees.selector), abi.encode(L1_FEE_WEI)); + } + + function test__getCurrentTxL1GasFees_SuccessWhenArbitrumMainnet() public { + // Set the chainID + vm.chainId(42161); + + // Setup sends and fulfills request #1 + FunctionsFulfillmentSetup.setUp(); + + // Check request cost estimate + uint96 expectedEstimatedTotalCostJuels = _getExpectedCostEstimate(s_requests[1].requestData.callbackGasLimit) + + l1FeeJuels; + assertEq(s_requests[1].commitment.estimatedTotalCostJuels, expectedEstimatedTotalCostJuels); + + // Check response actual cost + uint96 expectedTotalCostJuels = _getExpectedCost(5416) + l1FeeJuels; + assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels); + } + + function test__getCurrentTxL1GasFees_SuccessWhenArbitrumGoerli() public { + // Set the chainID + vm.chainId(421613); + + // Setup sends and fulfills request #1 + FunctionsFulfillmentSetup.setUp(); + + // Check request cost estimate + uint96 expectedEstimatedTotalCostJuels = _getExpectedCostEstimate(s_requests[1].requestData.callbackGasLimit) + + l1FeeJuels; + assertEq(s_requests[1].commitment.estimatedTotalCostJuels, expectedEstimatedTotalCostJuels); + + // Check response actual cost + uint96 expectedTotalCostJuels = _getExpectedCost(5416) + l1FeeJuels; + assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels); + } + + function test__getCurrentTxL1GasFees_SuccessWhenArbitrumSepolia() public { + // Set the chainID + vm.chainId(421614); + + // Setup sends and fulfills request #1 + FunctionsFulfillmentSetup.setUp(); + + // Check request cost estimate + uint96 expectedEstimatedTotalCostJuels = _getExpectedCostEstimate(s_requests[1].requestData.callbackGasLimit) + + l1FeeJuels; + assertEq(s_requests[1].commitment.estimatedTotalCostJuels, expectedEstimatedTotalCostJuels); + + // Check response actual cost + uint96 expectedTotalCostJuels = _getExpectedCost(5416) + l1FeeJuels; + assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels); + } +} + +/// @notice #_getCurrentTxL1GasFees Optimism +/// @dev Optimism gas formula = ((l2_base_fee + l2_priority_fee) * l2_gas_used) + L1 data fee +/// @dev where L1 data fee = l1_gas_price * ((count_zero_bytes(tx_data) * 4 + count_non_zero_bytes(tx_data) * 16) + fixed_overhead + noncalldata_gas) * dynamic_overhead +contract ChainSpecificUtil__getCurrentTxL1GasFees_Optimism is FunctionsFulfillmentSetup { + address private constant OVM_GASPRICEORACLE_ADDR = address(0x420000000000000000000000000000000000000F); + uint256 private constant L1_FEE_WEI = 15_818_209_764_247; + + uint96 l1FeeJuels = uint96((1e18 * L1_FEE_WEI) / uint256(LINK_ETH_RATE)); + + function setUp() public virtual override { + vm.mockCall( + OVM_GASPRICEORACLE_ADDR, + abi.encodeWithSelector(OVM_GasPriceOracle.getL1Fee.selector), + abi.encode(L1_FEE_WEI) + ); + } + + function test__getCurrentTxL1GasFees_SuccessWhenOptimismMainnet() public { + // Set the chainID + vm.chainId(10); + + // Setup sends and fulfills request #1 + FunctionsFulfillmentSetup.setUp(); + + // Check request cost estimate + uint96 expectedEstimatedTotalCostJuels = _getExpectedCostEstimate(s_requests[1].requestData.callbackGasLimit) + + l1FeeJuels; + assertEq(s_requests[1].commitment.estimatedTotalCostJuels, expectedEstimatedTotalCostJuels); + + // Check response actual cost + uint96 expectedTotalCostJuels = _getExpectedCost(5416) + l1FeeJuels; + assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels); + } + + function test__getCurrentTxL1GasFees_SuccessWhenOptimismGoerli() public { + // Set the chainID + vm.chainId(420); + + // Setup sends and fulfills request #1 + FunctionsFulfillmentSetup.setUp(); + + // Check request cost estimate + uint96 expectedEstimatedTotalCostJuels = _getExpectedCostEstimate(s_requests[1].requestData.callbackGasLimit) + + l1FeeJuels; + assertEq(s_requests[1].commitment.estimatedTotalCostJuels, expectedEstimatedTotalCostJuels); + + // Check response actual cost + uint96 expectedTotalCostJuels = _getExpectedCost(5416) + l1FeeJuels; + assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels); + } + + function test__getCurrentTxL1GasFees_SuccessWhenOptimismSepolia() public { + // Set the chainID + vm.chainId(11155420); + + // Setup sends and fulfills request #1 + FunctionsFulfillmentSetup.setUp(); + + // Check request cost estimate + uint96 expectedEstimatedTotalCostJuels = _getExpectedCostEstimate(s_requests[1].requestData.callbackGasLimit) + + l1FeeJuels; + assertEq(s_requests[1].commitment.estimatedTotalCostJuels, expectedEstimatedTotalCostJuels); + + // Check response actual cost + uint96 expectedTotalCostJuels = _getExpectedCost(5416) + l1FeeJuels; + assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels); + } +} + +/// @notice #_getCurrentTxL1GasFees Base +/// @dev Base gas formula uses Optimism formula = ((l2_base_fee + l2_priority_fee) * l2_gas_used) + L1 data fee +/// @dev where L1 data fee = l1_gas_price * ((count_zero_bytes(tx_data) * 4 + count_non_zero_bytes(tx_data) * 16) + fixed_overhead + noncalldata_gas) * dynamic_overhead +contract ChainSpecificUtil__getCurrentTxL1GasFees_Base is FunctionsFulfillmentSetup { + address private constant OVM_GASPRICEORACLE_ADDR = address(0x420000000000000000000000000000000000000F); + uint256 private constant L1_FEE_WEI = 15_818_209_764_247; + + uint96 l1FeeJuels = uint96((1e18 * L1_FEE_WEI) / uint256(LINK_ETH_RATE)); + + function setUp() public virtual override { + vm.mockCall( + OVM_GASPRICEORACLE_ADDR, + abi.encodeWithSelector(OVM_GasPriceOracle.getL1Fee.selector), + abi.encode(L1_FEE_WEI) + ); + } + + function test__getCurrentTxL1GasFees_SuccessWhenBaseMainnet() public { + // Set the chainID + vm.chainId(8453); + + // Setup sends and fulfills request #1 + FunctionsFulfillmentSetup.setUp(); + + // Check request cost estimate + uint96 expectedEstimatedTotalCostJuels = _getExpectedCostEstimate(s_requests[1].requestData.callbackGasLimit) + + l1FeeJuels; + assertEq(s_requests[1].commitment.estimatedTotalCostJuels, expectedEstimatedTotalCostJuels); + + // Check response actual cost + uint96 expectedTotalCostJuels = _getExpectedCost(5416) + l1FeeJuels; + assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels); + } + + function test__getCurrentTxL1GasFees_SuccessWhenBaseGoerli() public { + // Set the chainID + vm.chainId(84531); + + // Setup sends and fulfills request #1 + FunctionsFulfillmentSetup.setUp(); + + // Check request cost estimate + uint96 expectedEstimatedTotalCostJuels = _getExpectedCostEstimate(s_requests[1].requestData.callbackGasLimit) + + l1FeeJuels; + assertEq(s_requests[1].commitment.estimatedTotalCostJuels, expectedEstimatedTotalCostJuels); + + // Check response actual cost + uint96 expectedTotalCostJuels = _getExpectedCost(5416) + l1FeeJuels; + assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels); + } + + function test__getCurrentTxL1GasFees_SuccessWhenBaseSepolia() public { + // Set the chainID + vm.chainId(84532); + + // Setup sends and fulfills request #1 + FunctionsFulfillmentSetup.setUp(); + + // Check request cost estimate + uint96 expectedEstimatedTotalCostJuels = _getExpectedCostEstimate(s_requests[1].requestData.callbackGasLimit) + + l1FeeJuels; + assertEq(s_requests[1].commitment.estimatedTotalCostJuels, expectedEstimatedTotalCostJuels); + + // Check response actual cost + uint96 expectedTotalCostJuels = _getExpectedCost(5416) + l1FeeJuels; + assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels); + } +} diff --git a/contracts/src/v0.8/functions/tests/v1_X/FunctionsBilling.t.sol b/contracts/src/v0.8/functions/tests/v1_X/FunctionsBilling.t.sol index 14188fdc04a..739521c5305 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/FunctionsBilling.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/FunctionsBilling.t.sol @@ -4,12 +4,18 @@ pragma solidity ^0.8.19; import {FunctionsCoordinator} from "../../dev/v1_X/FunctionsCoordinator.sol"; import {FunctionsBilling} from "../../dev/v1_X/FunctionsBilling.sol"; import {FunctionsRequest} from "../../dev/v1_X/libraries/FunctionsRequest.sol"; +import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol"; +import {FunctionsSubscriptions} from "../../dev/v1_X/FunctionsSubscriptions.sol"; +import {Routable} from "../../dev/v1_X/Routable.sol"; -import {FunctionsRouterSetup, FunctionsSubscriptionSetup, FunctionsMultipleFulfillmentsSetup} from "./Setup.t.sol"; +import {FunctionsRouterSetup, FunctionsSubscriptionSetup, FunctionsClientRequestSetup, FunctionsFulfillmentSetup, FunctionsMultipleFulfillmentsSetup} from "./Setup.t.sol"; /// @notice #constructor -contract FunctionsBilling_Constructor { - +contract FunctionsBilling_Constructor is FunctionsSubscriptionSetup { + function test_Constructor_Success() public { + assertEq(address(s_functionsRouter), s_functionsCoordinator.getRouter_HARNESS()); + assertEq(address(s_linkEthFeed), s_functionsCoordinator.getLinkToNativeFeed_HARNESS()); + } } /// @notice #getConfig @@ -32,28 +38,94 @@ contract FunctionsBilling_GetConfig is FunctionsRouterSetup { } /// @notice #updateConfig -contract FunctionsBilling_UpdateConfig { +contract FunctionsBilling_UpdateConfig is FunctionsRouterSetup { + FunctionsBilling.Config internal configToSet; + + function setUp() public virtual override { + FunctionsRouterSetup.setUp(); + + configToSet = FunctionsBilling.Config({ + feedStalenessSeconds: getCoordinatorConfig().feedStalenessSeconds * 2, + gasOverheadAfterCallback: getCoordinatorConfig().gasOverheadAfterCallback * 2, + gasOverheadBeforeCallback: getCoordinatorConfig().gasOverheadBeforeCallback * 2, + requestTimeoutSeconds: getCoordinatorConfig().requestTimeoutSeconds * 2, + donFee: getCoordinatorConfig().donFee * 2, + maxSupportedRequestDataVersion: getCoordinatorConfig().maxSupportedRequestDataVersion * 2, + fulfillmentGasPriceOverEstimationBP: getCoordinatorConfig().fulfillmentGasPriceOverEstimationBP * 2, + fallbackNativePerUnitLink: getCoordinatorConfig().fallbackNativePerUnitLink * 2, + minimumEstimateGasPriceWei: getCoordinatorConfig().minimumEstimateGasPriceWei * 2 + }); + } + function test_UpdateConfig_RevertIfNotOwner() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + vm.expectRevert("Only callable by owner"); + s_functionsCoordinator.updateConfig(configToSet); + } + + event ConfigUpdated(FunctionsBilling.Config config); + + function test_UpdateConfig_Success() public { + // topic0 (function signature, always checked), NOT topic1 (false), NOT topic2 (false), NOT topic3 (false), and data (true). + bool checkTopic1 = false; + bool checkTopic2 = false; + bool checkTopic3 = false; + bool checkData = true; + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + emit ConfigUpdated(configToSet); + + s_functionsCoordinator.updateConfig(configToSet); + + FunctionsBilling.Config memory config = s_functionsCoordinator.getConfig(); + assertEq(config.feedStalenessSeconds, configToSet.feedStalenessSeconds); + assertEq(config.gasOverheadAfterCallback, configToSet.gasOverheadAfterCallback); + assertEq(config.gasOverheadBeforeCallback, configToSet.gasOverheadBeforeCallback); + assertEq(config.requestTimeoutSeconds, configToSet.requestTimeoutSeconds); + assertEq(config.donFee, configToSet.donFee); + assertEq(config.maxSupportedRequestDataVersion, configToSet.maxSupportedRequestDataVersion); + assertEq(config.fulfillmentGasPriceOverEstimationBP, configToSet.fulfillmentGasPriceOverEstimationBP); + assertEq(config.fallbackNativePerUnitLink, configToSet.fallbackNativePerUnitLink); + assertEq(config.minimumEstimateGasPriceWei, configToSet.minimumEstimateGasPriceWei); + } } /// @notice #getDONFee -contract FunctionsBilling_GetDONFee { +contract FunctionsBilling_GetDONFee is FunctionsRouterSetup { + function test_GetDONFee_Success() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + uint72 donFee = s_functionsCoordinator.getDONFee(new bytes(0)); + assertEq(donFee, s_donFee); + } } /// @notice #getAdminFee -contract FunctionsBilling_GetAdminFee { +contract FunctionsBilling_GetAdminFee is FunctionsRouterSetup { + function test_GetAdminFee_Success() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + uint72 adminFee = s_functionsCoordinator.getAdminFee(); + assertEq(adminFee, s_adminFee); + } } /// @notice #getWeiPerUnitLink -contract FunctionsBilling_GetWeiPerUnitLink { - -} +contract FunctionsBilling_GetWeiPerUnitLink is FunctionsRouterSetup { + function test_GetWeiPerUnitLink_Success() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); -/// @notice #_getJuelsPerGas -contract FunctionsBilling__GetJuelsPerGas { - // TODO: make contract internal function helper + uint256 weiPerUnitLink = s_functionsCoordinator.getWeiPerUnitLink(); + assertEq(weiPerUnitLink, uint256(LINK_ETH_RATE)); + } } /// @notice #estimateCost @@ -109,7 +181,7 @@ contract FunctionsBilling_EstimateCost is FunctionsSubscriptionSetup { callbackGasLimit, gasPriceWei ); - uint96 expectedCostEstimate = 16375000000000200; + uint96 expectedCostEstimate = 51110500000000200; assertEq(costEstimate, expectedCostEstimate); } @@ -134,7 +206,7 @@ contract FunctionsBilling_EstimateCost is FunctionsSubscriptionSetup { callbackGasLimit, gasPriceWei ); - uint96 expectedCostEstimate = 81875000000000200; + uint96 expectedCostEstimate = 255552500000000200; assertEq(costEstimate, expectedCostEstimate); } } @@ -145,28 +217,200 @@ contract FunctionsBilling__CalculateCostEstimate { } /// @notice #_startBilling -contract FunctionsBilling__StartBilling { - // TODO: make contract internal function helper -} +contract FunctionsBilling__StartBilling is FunctionsFulfillmentSetup { + function test__FulfillAndBill_HasUniqueGlobalRequestId() public { + // Variables that go into a requestId: + // - Coordinator address + // - Consumer contract + // - Subscription ID, + // - Consumer initiated requests + // - Request data + // - Request data version + // - Request callback gas limit + // - Estimated total cost in Juels + // - Request timeout timestamp + // - tx.origin + + // Request #1 has already been fulfilled by the test setup + + // Reset the nonce (initiatedRequests) by removing and re-adding the consumer + s_functionsRouter.removeConsumer(s_subscriptionId, address(s_functionsClient)); + assertEq(s_functionsRouter.getSubscription(s_subscriptionId).consumers.length, 0); + s_functionsRouter.addConsumer(s_subscriptionId, address(s_functionsClient)); + assertEq(s_functionsRouter.getSubscription(s_subscriptionId).consumers[0], address(s_functionsClient)); + + // Make Request #2 + _sendAndStoreRequest( + 2, + s_requests[1].requestData.sourceCode, + s_requests[1].requestData.secrets, + s_requests[1].requestData.args, + s_requests[1].requestData.bytesArgs, + s_requests[1].requestData.callbackGasLimit + ); -/// @notice #_computeRequestId -contract FunctionsBilling__ComputeRequestId { - // TODO: make contract internal function helper + // Request #1 and #2 should have different request IDs, because the request timeout timestamp has advanced. + // A request cannot be fulfilled in the same block, which prevents removing a consumer in the same block + assertNotEq(s_requests[1].requestId, s_requests[2].requestId); + } } /// @notice #_fulfillAndBill -contract FunctionsBilling__FulfillAndBill { - // TODO: make contract internal function helper +contract FunctionsBilling__FulfillAndBill is FunctionsClientRequestSetup { + function test__FulfillAndBill_RevertIfInvalidCommitment() public { + vm.expectRevert(); + s_functionsCoordinator.fulfillAndBill_HARNESS( + s_requests[1].requestId, + new bytes(0), + new bytes(0), + new bytes(0), // malformed commitment data + new bytes(0), + 1 + ); + } + + event RequestBilled( + bytes32 indexed requestId, + uint96 juelsPerGas, + uint256 l1FeeShareWei, + uint96 callbackCostJuels, + uint96 totalCostJuels + ); + + function test__FulfillAndBill_Success() public { + uint96 juelsPerGas = uint96((1e18 * TX_GASPRICE_START) / uint256(LINK_ETH_RATE)); + uint96 callbackCostGas = 5072; // Taken manually + uint96 callbackCostJuels = juelsPerGas * callbackCostGas; + uint96 gasOverheadJuels = juelsPerGas * + (getCoordinatorConfig().gasOverheadBeforeCallback + getCoordinatorConfig().gasOverheadAfterCallback); + + uint96 totalCostJuels = gasOverheadJuels + callbackCostJuels + s_donFee + s_adminFee; + + // topic0 (function signature, always checked), check topic1 (true), NOT topic2 (false), NOT topic3 (false), and data (true). + bool checkTopic1 = true; + bool checkTopic2 = false; + bool checkTopic3 = false; + bool checkData = true; + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + emit RequestBilled(s_requests[1].requestId, juelsPerGas, 0, callbackCostJuels, totalCostJuels); + + FunctionsResponse.FulfillResult resultCode = s_functionsCoordinator.fulfillAndBill_HARNESS( + s_requests[1].requestId, + new bytes(0), + new bytes(0), + abi.encode(s_requests[1].commitment), + new bytes(0), + 1 + ); + + assertEq(uint256(resultCode), uint256(FunctionsResponse.FulfillResult.FULFILLED)); + } } /// @notice #deleteCommitment -contract FunctionsBilling_DeleteCommitment { +contract FunctionsBilling_DeleteCommitment is FunctionsClientRequestSetup { + function test_DeleteCommitment_RevertIfNotRouter() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + vm.expectRevert(Routable.OnlyCallableByRouter.selector); + s_functionsCoordinator.deleteCommitment(s_requests[1].requestId); + } + + event CommitmentDeleted(bytes32 requestId); + + function test_DeleteCommitment_Success() public { + // Send as Router + vm.stopPrank(); + vm.startPrank(address(s_functionsRouter)); + + // topic0 (function signature, always checked), NOT topic1 (false), NOT topic2 (false), NOT topic3 (false), and data (true). + bool checkTopic1 = false; + bool checkTopic2 = false; + bool checkTopic3 = false; + bool checkData = true; + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + emit CommitmentDeleted(s_requests[1].requestId); + s_functionsCoordinator.deleteCommitment(s_requests[1].requestId); + } } /// @notice #oracleWithdraw -contract FunctionsBilling_OracleWithdraw { +contract FunctionsBilling_OracleWithdraw is FunctionsMultipleFulfillmentsSetup { + function test_OracleWithdraw_RevertWithNoBalance() public { + uint256[4] memory transmitterBalancesBefore = _getTransmitterBalances(); + _assertTransmittersAllHaveBalance(transmitterBalancesBefore, 0); + + // Send as stranger, which has no balance + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + vm.expectRevert(FunctionsSubscriptions.InvalidCalldata.selector); + + // Attempt to withdraw with no amount, which would withdraw the full balance + s_functionsCoordinator.oracleWithdraw(STRANGER_ADDRESS, 0); + + uint256[4] memory transmitterBalancesAfter = _getTransmitterBalances(); + _assertTransmittersAllHaveBalance(transmitterBalancesAfter, 0); + } + + function test_OracleWithdraw_RevertIfInsufficientBalance() public { + // Send as transmitter 1, which has transmitted 1 report + vm.stopPrank(); + vm.startPrank(NOP_TRANSMITTER_ADDRESS_1); + vm.expectRevert(FunctionsBilling.InsufficientBalance.selector); + + // Attempt to withdraw more than the Coordinator has assigned + s_functionsCoordinator.oracleWithdraw(NOP_TRANSMITTER_ADDRESS_1, s_fulfillmentCoordinatorBalance + 1); + } + + function test_OracleWithdraw_SuccessTransmitterWithBalanceValidAmountGiven() public { + uint256[4] memory transmitterBalancesBefore = _getTransmitterBalances(); + _assertTransmittersAllHaveBalance(transmitterBalancesBefore, 0); + + // Send as transmitter 1, which has transmitted 1 report + vm.stopPrank(); + vm.startPrank(NOP_TRANSMITTER_ADDRESS_1); + + uint96 expectedTransmitterBalance = s_fulfillmentCoordinatorBalance / 3; + + // Attempt to withdraw half of balance + uint96 halfBalance = expectedTransmitterBalance / 2; + s_functionsCoordinator.oracleWithdraw(NOP_TRANSMITTER_ADDRESS_1, halfBalance); + + uint256[4] memory transmitterBalancesAfter = _getTransmitterBalances(); + assertEq(transmitterBalancesAfter[0], halfBalance); + assertEq(transmitterBalancesAfter[1], 0); + assertEq(transmitterBalancesAfter[2], 0); + assertEq(transmitterBalancesAfter[3], 0); + } + + function test_OracleWithdraw_SuccessTransmitterWithBalanceNoAmountGiven() public { + uint256[4] memory transmitterBalancesBefore = _getTransmitterBalances(); + _assertTransmittersAllHaveBalance(transmitterBalancesBefore, 0); + + // Send as transmitter 1, which has transmitted 1 report + vm.stopPrank(); + vm.startPrank(NOP_TRANSMITTER_ADDRESS_1); + + // Attempt to withdraw with no amount, which will withdraw the full balance + s_functionsCoordinator.oracleWithdraw(NOP_TRANSMITTER_ADDRESS_1, 0); + + // 3 report transmissions have been made + uint96 totalDonFees = s_donFee * 3; + // 4 transmitters will share the DON fees + uint96 donFeeShare = totalDonFees / 4; + uint96 expectedTransmitterBalance = ((s_fulfillmentCoordinatorBalance - totalDonFees) / 3) + donFeeShare; + + uint256[4] memory transmitterBalancesAfter = _getTransmitterBalances(); + assertEq(transmitterBalancesAfter[0], expectedTransmitterBalance); + assertEq(transmitterBalancesAfter[1], 0); + assertEq(transmitterBalancesAfter[2], 0); + assertEq(transmitterBalancesAfter[3], 0); + } } /// @notice #oracleWithdrawAll @@ -188,37 +432,29 @@ contract FunctionsBilling_OracleWithdrawAll is FunctionsMultipleFulfillmentsSetu } function test_OracleWithdrawAll_SuccessPaysTransmittersWithBalance() public { - uint256 transmitter1BalanceBefore = s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_1); - assertEq(transmitter1BalanceBefore, 0); - uint256 transmitter2BalanceBefore = s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_2); - assertEq(transmitter2BalanceBefore, 0); - uint256 transmitter3BalanceBefore = s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_3); - assertEq(transmitter3BalanceBefore, 0); - uint256 transmitter4BalanceBefore = s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_4); - assertEq(transmitter4BalanceBefore, 0); + uint256[4] memory transmitterBalancesBefore = _getTransmitterBalances(); + _assertTransmittersAllHaveBalance(transmitterBalancesBefore, 0); s_functionsCoordinator.oracleWithdrawAll(); uint96 expectedTransmitterBalance = s_fulfillmentCoordinatorBalance / 3; - uint256 transmitter1BalanceAfter = s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_1); - assertEq(transmitter1BalanceAfter, expectedTransmitterBalance); - uint256 transmitter2BalanceAfter = s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_2); - assertEq(transmitter2BalanceAfter, expectedTransmitterBalance); - uint256 transmitter3BalanceAfter = s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_3); - assertEq(transmitter3BalanceAfter, expectedTransmitterBalance); + uint256[4] memory transmitterBalancesAfter = _getTransmitterBalances(); + assertEq(transmitterBalancesAfter[0], expectedTransmitterBalance); + assertEq(transmitterBalancesAfter[1], expectedTransmitterBalance); + assertEq(transmitterBalancesAfter[2], expectedTransmitterBalance); // Transmitter 4 has no balance - uint256 transmitter4BalanceAfter = s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_4); - assertEq(transmitter4BalanceAfter, 0); + assertEq(transmitterBalancesAfter[3], 0); } } -/// @notice #_getTransmitters -contract FunctionsBilling__GetTransmitters { - // TODO: make contract internal function helper -} - /// @notice #_disperseFeePool -contract FunctionsBilling__DisperseFeePool { - // TODO: make contract internal function helper +contract FunctionsBilling__DisperseFeePool is FunctionsRouterSetup { + function test__DisperseFeePool_RevertIfNotSet() public { + // Manually set s_feePool (at slot 11) to 1 to get past first check in _disperseFeePool + vm.store(address(s_functionsCoordinator), bytes32(uint256(11)), bytes32(uint256(1))); + + vm.expectRevert(FunctionsBilling.NoTransmittersSet.selector); + s_functionsCoordinator.disperseFeePool_HARNESS(); + } } diff --git a/contracts/src/v0.8/functions/tests/v1_X/FunctionsClient.t.sol b/contracts/src/v0.8/functions/tests/v1_X/FunctionsClient.t.sol index d6a3be16847..363827645c0 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/FunctionsClient.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/FunctionsClient.t.sol @@ -2,22 +2,23 @@ pragma solidity ^0.8.19; import {BaseTest} from "./BaseTest.t.sol"; +import {FunctionsClient} from "../../dev/v1_X/FunctionsClient.sol"; import {FunctionsRouter} from "../../dev/v1_X/FunctionsRouter.sol"; import {FunctionsSubscriptions} from "../../dev/v1_X/FunctionsSubscriptions.sol"; import {FunctionsRequest} from "../../dev/v1_X/libraries/FunctionsRequest.sol"; import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol"; -import {FunctionsSubscriptionSetup} from "./Setup.t.sol"; +import {FunctionsClientSetup, FunctionsSubscriptionSetup, FunctionsClientRequestSetup} from "./Setup.t.sol"; /// @notice #constructor -contract FunctionsClient_Constructor { - +contract FunctionsClient_Constructor is FunctionsClientSetup { + function test_Constructor_Success() public { + assertEq(address(s_functionsRouter), s_functionsClient.getRouter_HARNESS()); + } } /// @notice #_sendRequest contract FunctionsClient__SendRequest is FunctionsSubscriptionSetup { - // TODO: make contract internal function helper - function test__SendRequest_RevertIfInvalidCallbackGasLimit() public { // Build minimal valid request data string memory sourceCode = "return 'hello world';"; @@ -43,12 +44,35 @@ contract FunctionsClient__SendRequest is FunctionsSubscriptionSetup { } } -/// @notice #fulfillRequest -contract FunctionsClient_FulfillRequest { +/// @notice #handleOracleFulfillment +contract FunctionsClient_HandleOracleFulfillment is FunctionsClientRequestSetup { + function test_HandleOracleFulfillment_RevertIfNotRouter() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); -} + vm.expectRevert(FunctionsClient.OnlyRouterCanFulfill.selector); + s_functionsClient.handleOracleFulfillment(s_requests[1].requestId, new bytes(0), new bytes(0)); + } -/// @notice #handleOracleFulfillment -contract FunctionsClient_HandleOracleFulfillment { + event RequestFulfilled(bytes32 indexed id); + event ResponseReceived(bytes32 indexed requestId, bytes result, bytes err); + + function test_HandleOracleFulfillment_Success() public { + // Send as Router + vm.stopPrank(); + vm.startPrank(address(s_functionsRouter)); + // topic0 (function signature, always checked), NOT topic1 (false), NOT topic2 (false), NOT topic3 (false), and data (true). + bool checkTopic1 = false; + bool checkTopic2 = false; + bool checkTopic3 = false; + bool checkData = true; + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + emit ResponseReceived(s_requests[1].requestId, new bytes(0), new bytes(0)); + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + emit RequestFulfilled(s_requests[1].requestId); + + s_functionsClient.handleOracleFulfillment(s_requests[1].requestId, new bytes(0), new bytes(0)); + } } diff --git a/contracts/src/v0.8/functions/tests/v1_X/FunctionsCoordinator.t.sol b/contracts/src/v0.8/functions/tests/v1_X/FunctionsCoordinator.t.sol index 893aa6408b6..f6d3d41e632 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/FunctionsCoordinator.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/FunctionsCoordinator.t.sol @@ -4,9 +4,12 @@ pragma solidity ^0.8.19; import {FunctionsCoordinator} from "../../dev/v1_X/FunctionsCoordinator.sol"; import {FunctionsBilling} from "../../dev/v1_X/FunctionsBilling.sol"; import {FunctionsRequest} from "../../dev/v1_X/libraries/FunctionsRequest.sol"; +import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol"; import {FunctionsRouter} from "../../dev/v1_X/FunctionsRouter.sol"; +import {Routable} from "../../dev/v1_X/Routable.sol"; -import {FunctionsRouterSetup} from "./Setup.t.sol"; +import {BaseTest} from "./BaseTest.t.sol"; +import {FunctionsRouterSetup, FunctionsDONSetup, FunctionsSubscriptionSetup} from "./Setup.t.sol"; /// @notice #constructor contract FunctionsCoordinator_Constructor is FunctionsRouterSetup { @@ -17,48 +20,220 @@ contract FunctionsCoordinator_Constructor is FunctionsRouterSetup { } /// @notice #getThresholdPublicKey -contract FunctionsCoordinator_GetThresholdPublicKey { +contract FunctionsCoordinator_GetThresholdPublicKey is FunctionsDONSetup { + function test_GetThresholdPublicKey_RevertIfEmpty() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + // Reverts when empty + vm.expectRevert(FunctionsCoordinator.EmptyPublicKey.selector); + s_functionsCoordinator.getThresholdPublicKey(); + } + + function test_GetThresholdPublicKey_Success() public { + s_functionsCoordinator.setThresholdPublicKey(s_thresholdKey); + + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + bytes memory thresholdKey = s_functionsCoordinator.getThresholdPublicKey(); + assertEq(thresholdKey, s_thresholdKey); + } } /// @notice #setThresholdPublicKey -contract FunctionsCoordinator_SetThresholdPublicKey { +contract FunctionsCoordinator_SetThresholdPublicKey is FunctionsDONSetup { + function test_SetThresholdPublicKey_RevertNotOwner() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + vm.expectRevert("Only callable by owner"); + bytes memory newThresholdKey = new bytes(0); + s_functionsCoordinator.setThresholdPublicKey(newThresholdKey); + } + + function test_SetThresholdPublicKey_Success() public { + s_functionsCoordinator.setThresholdPublicKey(s_thresholdKey); + + bytes memory thresholdKey = s_functionsCoordinator.getThresholdPublicKey(); + assertEq(thresholdKey, s_thresholdKey); + } } /// @notice #getDONPublicKey -contract FunctionsCoordinator_GetDONPublicKey { +contract FunctionsCoordinator_GetDONPublicKey is FunctionsDONSetup { + function test_GetDONPublicKey_RevertIfEmpty() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + // Reverts when empty + vm.expectRevert(FunctionsCoordinator.EmptyPublicKey.selector); + s_functionsCoordinator.getDONPublicKey(); + } + + function test_GetDONPublicKey_Success() public { + s_functionsCoordinator.setDONPublicKey(s_donKey); + + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + bytes memory donKey = s_functionsCoordinator.getDONPublicKey(); + assertEq(donKey, s_donKey); + } } /// @notice #setDONPublicKey -contract FunctionsCoordinator__SetDONPublicKey { +contract FunctionsCoordinator_SetDONPublicKey is FunctionsDONSetup { + function test_SetDONPublicKey_RevertNotOwner() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + vm.expectRevert("Only callable by owner"); + s_functionsCoordinator.setDONPublicKey(s_donKey); + } + + function test_SetDONPublicKey_Success() public { + s_functionsCoordinator.setDONPublicKey(s_donKey); + bytes memory donKey = s_functionsCoordinator.getDONPublicKey(); + assertEq(donKey, s_donKey); + } } /// @notice #_isTransmitter -contract FunctionsCoordinator_IsTransmitter { - // TODO: make contract internal function helper +contract FunctionsCoordinator__IsTransmitter is FunctionsDONSetup { + function test__IsTransmitter_SuccessFound() public { + bool isTransmitter = s_functionsCoordinator.isTransmitter_HARNESS(NOP_TRANSMITTER_ADDRESS_1); + assertEq(isTransmitter, true); + } + + function test__IsTransmitter_SuccessNotFound() public { + bool isTransmitter = s_functionsCoordinator.isTransmitter_HARNESS(STRANGER_ADDRESS); + assertEq(isTransmitter, false); + } } -/// @notice #setNodePublicKey -contract FunctionsCoordinator_SetNodePublicKey { +/// @notice #startRequest +contract FunctionsCoordinator_StartRequest is FunctionsSubscriptionSetup { + function test_StartRequest_RevertIfNotRouter() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); -} + vm.expectRevert(Routable.OnlyCallableByRouter.selector); -/// @notice #deleteNodePublicKey -contract FunctionsCoordinator_DeleteNodePublicKey { + s_functionsCoordinator.startRequest( + FunctionsResponse.RequestMeta({ + requestingContract: address(s_functionsClient), + data: new bytes(0), + subscriptionId: s_subscriptionId, + dataVersion: FunctionsRequest.REQUEST_DATA_VERSION, + flags: bytes32(0), + callbackGasLimit: 5_500, + adminFee: s_adminFee, + initiatedRequests: 0, + completedRequests: 0, + availableBalance: s_subscriptionInitialFunding, + subscriptionOwner: OWNER_ADDRESS + }) + ); + } -} + event OracleRequest( + bytes32 indexed requestId, + address indexed requestingContract, + address requestInitiator, + uint64 subscriptionId, + address subscriptionOwner, + bytes data, + uint16 dataVersion, + bytes32 flags, + uint64 callbackGasLimit, + FunctionsResponse.Commitment commitment + ); -/// @notice #getAllNodePublicKeys -contract FunctionsCoordinator_GetAllNodePublicKeys { + function test_StartRequest_Success() public { + // Send as Router + vm.stopPrank(); + vm.startPrank(address(s_functionsRouter)); + (, , address txOrigin) = vm.readCallers(); -} + bytes memory _requestData = new bytes(0); + uint32 _callbackGasLimit = 5_500; + uint96 costEstimate = s_functionsCoordinator.estimateCost( + s_subscriptionId, + _requestData, + _callbackGasLimit, + tx.gasprice + ); + uint32 timeoutTimestamp = uint32(block.timestamp + getCoordinatorConfig().requestTimeoutSeconds); + bytes32 expectedRequestId = keccak256( + abi.encode( + address(s_functionsCoordinator), + address(s_functionsClient), + s_subscriptionId, + 1, + keccak256(_requestData), + FunctionsRequest.REQUEST_DATA_VERSION, + _callbackGasLimit, + costEstimate, + timeoutTimestamp, + txOrigin + ) + ); -/// @notice #startRequest -contract FunctionsCoordinator_StartRequest { + FunctionsResponse.Commitment memory expectedComittment = FunctionsResponse.Commitment({ + adminFee: s_adminFee, + coordinator: address(s_functionsCoordinator), + client: address(s_functionsClient), + subscriptionId: s_subscriptionId, + callbackGasLimit: _callbackGasLimit, + estimatedTotalCostJuels: costEstimate, + timeoutTimestamp: timeoutTimestamp, + requestId: expectedRequestId, + donFee: s_donFee, + gasOverheadBeforeCallback: getCoordinatorConfig().gasOverheadBeforeCallback, + gasOverheadAfterCallback: getCoordinatorConfig().gasOverheadAfterCallback + }); + // topic0 (function signature, always checked), topic1 (true), topic2 (true), NOT topic3 (false), and data (true). + vm.expectEmit(true, true, false, true); + emit OracleRequest({ + requestId: expectedRequestId, + requestingContract: address(s_functionsClient), + requestInitiator: txOrigin, + subscriptionId: s_subscriptionId, + subscriptionOwner: OWNER_ADDRESS, + data: _requestData, + dataVersion: FunctionsRequest.REQUEST_DATA_VERSION, + flags: bytes32(0), + callbackGasLimit: _callbackGasLimit, + commitment: expectedComittment + }); + + s_functionsCoordinator.startRequest( + FunctionsResponse.RequestMeta({ + requestingContract: address(s_functionsClient), + data: _requestData, + subscriptionId: s_subscriptionId, + dataVersion: FunctionsRequest.REQUEST_DATA_VERSION, + flags: bytes32(0), + callbackGasLimit: 5_500, + adminFee: s_adminFee, + initiatedRequests: 0, + completedRequests: 0, + availableBalance: s_subscriptionInitialFunding, + subscriptionOwner: OWNER_ADDRESS + }) + ); + } } /// @notice #_beforeSetConfig @@ -73,10 +248,10 @@ contract FunctionsCoordinator__GetTransmitters { /// @notice #_report contract FunctionsCoordinator__Report { - + // TODO: make contract internal function helper } /// @notice #_onlyOwner contract FunctionsCoordinator__OnlyOwner { - + // TODO: make contract internal function helper } diff --git a/contracts/src/v0.8/functions/tests/v1_X/FunctionsRequest.t.sol b/contracts/src/v0.8/functions/tests/v1_X/FunctionsRequest.t.sol index 5457a221b61..e9684d9f5b3 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/FunctionsRequest.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/FunctionsRequest.t.sol @@ -30,31 +30,19 @@ contract FunctionsRequest_EncodeCBOR is Test { } /// @notice #initializeRequest -contract FunctionsRequest_InitializeRequest is Test { - -} +contract FunctionsRequest_InitializeRequest is Test {} /// @notice #initializeRequestForInlineJavaScript -contract FunctionsRequest_InitializeRequestForInlineJavaScript is Test { - -} +contract FunctionsRequest_InitializeRequestForInlineJavaScript is Test {} /// @notice #addSecretsReference -contract FunctionsRequest_AddSecretsReference is Test { - -} +contract FunctionsRequest_AddSecretsReference is Test {} /// @notice #addDONHostedSecrets -contract FunctionsRequest_AddDONHostedSecrets is Test { - -} +contract FunctionsRequest_AddDONHostedSecrets is Test {} /// @notice #setArgs -contract FunctionsRequest_SetArgs is Test { - -} +contract FunctionsRequest_SetArgs is Test {} /// @notice #setBytesArgs -contract FunctionsRequest_SetBytesArgs is Test { - -} +contract FunctionsRequest_SetBytesArgs is Test {} diff --git a/contracts/src/v0.8/functions/tests/v1_X/FunctionsRouter.t.sol b/contracts/src/v0.8/functions/tests/v1_X/FunctionsRouter.t.sol index b9b6e1d5746..081fe2f6649 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/FunctionsRouter.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/FunctionsRouter.t.sol @@ -938,7 +938,7 @@ contract FunctionsRouter_Fulfill is FunctionsClientRequestSetup { uint32 callbackGasLimit = s_requests[requestToFulfill].requestData.callbackGasLimit; // Coordinator sends enough gas that would get through callback and payment, but fail after - uint256 gasToUse = getCoordinatorConfig().gasOverheadBeforeCallback + callbackGasLimit + 100000; + uint256 gasToUse = getCoordinatorConfig().gasOverheadBeforeCallback + callbackGasLimit + 10_000; // topic0 (function signature, always checked), topic1 (true), NOT topic2 (false), NOT topic3 (false), and data (true). bool checkTopic1RequestId = true; @@ -1221,7 +1221,7 @@ contract FunctionsRouter_Fulfill is FunctionsClientRequestSetup { emit RequestProcessed({ requestId: s_requests[requestToFulfill].requestId, subscriptionId: s_subscriptionId, - totalCostJuels: _getExpectedCost(5393), // gasUsed is manually taken + totalCostJuels: _getExpectedCost(5416), // gasUsed is manually taken transmitter: NOP_TRANSMITTER_ADDRESS_1, resultCode: FunctionsResponse.FulfillResult.FULFILLED, response: bytes(response), diff --git a/contracts/src/v0.8/functions/tests/v1_X/FunctionsSubscriptions.t.sol b/contracts/src/v0.8/functions/tests/v1_X/FunctionsSubscriptions.t.sol index 8046bf7d939..5a54bcc84ca 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/FunctionsSubscriptions.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/FunctionsSubscriptions.t.sol @@ -6,7 +6,7 @@ import {FunctionsRouter} from "../../dev/v1_X/FunctionsRouter.sol"; import {FunctionsSubscriptions} from "../../dev/v1_X/FunctionsSubscriptions.sol"; import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; import {FunctionsRouterSetup, FunctionsOwnerAcceptTermsOfServiceSetup, FunctionsClientSetup, FunctionsSubscriptionSetup, FunctionsClientRequestSetup, FunctionsFulfillmentSetup} from "./Setup.t.sol"; @@ -309,11 +309,22 @@ contract FunctionsSubscriptions_OwnerWithdraw is FunctionsFulfillmentSetup { } /// @notice #onTokenTransfer -contract FunctionsSubscriptions_OnTokenTransfer is FunctionsSubscriptionSetup { +contract FunctionsSubscriptions_OnTokenTransfer is FunctionsClientSetup { + uint64 s_subscriptionId; + + function setUp() public virtual override { + FunctionsClientSetup.setUp(); + + // Create subscription, but do not fund it + s_subscriptionId = s_functionsRouter.createSubscription(); + s_functionsRouter.addConsumer(s_subscriptionId, address(s_functionsClient)); + } + function test_OnTokenTransfer_RevertIfPaused(uint96 fundingAmount) public { // Funding amount must be less than LINK total supply - vm.assume(fundingAmount < 1_000_000_000 * 1e18); - vm.assume(fundingAmount > 0); + uint256 totalSupplyJuels = 1_000_000_000 * 1e18; + vm.assume(fundingAmount <= totalSupplyJuels); + vm.assume(fundingAmount >= 0); s_functionsRouter.pause(); vm.expectRevert("Pausable: paused"); @@ -322,8 +333,9 @@ contract FunctionsSubscriptions_OnTokenTransfer is FunctionsSubscriptionSetup { function test_OnTokenTransfer_RevertIfCallerIsNotLink(uint96 fundingAmount) public { // Funding amount must be less than LINK total supply - vm.assume(fundingAmount < 1_000_000_000 * 1e18); - vm.assume(fundingAmount > 0); + uint256 totalSupplyJuels = 1_000_000_000 * 1e18; + vm.assume(fundingAmount <= totalSupplyJuels); + vm.assume(fundingAmount >= 0); vm.expectRevert(FunctionsSubscriptions.OnlyCallableFromLink.selector); s_functionsRouter.onTokenTransfer(address(s_functionsRouter), fundingAmount, abi.encode(s_subscriptionId)); @@ -331,8 +343,9 @@ contract FunctionsSubscriptions_OnTokenTransfer is FunctionsSubscriptionSetup { function test_OnTokenTransfer_RevertIfCallerIsNoCalldata(uint96 fundingAmount) public { // Funding amount must be less than LINK total supply - vm.assume(fundingAmount < 1_000_000_000 * 1e18); - vm.assume(fundingAmount > 0); + uint256 totalSupplyJuels = 1_000_000_000 * 1e18; + vm.assume(fundingAmount <= totalSupplyJuels); + vm.assume(fundingAmount >= 0); vm.expectRevert(FunctionsSubscriptions.InvalidCalldata.selector); s_linkToken.transferAndCall(address(s_functionsRouter), fundingAmount, new bytes(0)); @@ -340,8 +353,9 @@ contract FunctionsSubscriptions_OnTokenTransfer is FunctionsSubscriptionSetup { function test_OnTokenTransfer_RevertIfCallerIsNoSubscription(uint96 fundingAmount) public { // Funding amount must be less than LINK total supply - vm.assume(fundingAmount < 1_000_000_000 * 1e18); - vm.assume(fundingAmount > 0); + uint256 totalSupplyJuels = 1_000_000_000 * 1e18; + vm.assume(fundingAmount <= totalSupplyJuels); + vm.assume(fundingAmount >= 0); vm.expectRevert(FunctionsSubscriptions.InvalidSubscription.selector); uint64 invalidSubscriptionId = 123456789; @@ -349,17 +363,15 @@ contract FunctionsSubscriptions_OnTokenTransfer is FunctionsSubscriptionSetup { } function test_OnTokenTransfer_Success(uint96 fundingAmount) public { - uint96 subscriptionBalanceBefore = s_functionsRouter.getSubscription(s_subscriptionId).balance; - // Funding amount must be less than LINK total supply - uint96 TOTAL_LINK = 1_000_000_000 * 1e18; + uint256 totalSupplyJuels = 1_000_000_000 * 1e18; // Some of the total supply is already in the subscription account - vm.assume(fundingAmount < TOTAL_LINK - subscriptionBalanceBefore); - vm.assume(fundingAmount > 0); + vm.assume(fundingAmount <= totalSupplyJuels); + vm.assume(fundingAmount >= 0); s_linkToken.transferAndCall(address(s_functionsRouter), fundingAmount, abi.encode(s_subscriptionId)); uint96 subscriptionBalanceAfter = s_functionsRouter.getSubscription(s_subscriptionId).balance; - assertEq(subscriptionBalanceBefore + fundingAmount, subscriptionBalanceAfter); + assertEq(fundingAmount, subscriptionBalanceAfter); } } @@ -1122,62 +1134,14 @@ contract FunctionsSubscriptions_CancelSubscription is FunctionsSubscriptionSetup uint256 balanceAfterWithdraw = s_linkToken.balanceOf(STRANGER_ADDRESS); assertEq(balanceBeforeWithdraw + expectedDepositWithheld, balanceAfterWithdraw); } +} - function test_CancelSubscription_SuccessRecieveDeposit() public { - // Complete 1 request = subscriptionDepositMinimumRequests - vm.recordLogs(); - bytes32 requestId = s_functionsClient.sendRequest( - s_donId, - "return 'hello world';", - new bytes(0), - new string[](0), - new bytes[](0), - s_subscriptionId, - 5500 - ); - - // Get commitment data from OracleRequest event log - Vm.Log[] memory entries = vm.getRecordedLogs(); - (, , , , , , , FunctionsResponse.Commitment memory commitment) = abi.decode( - entries[0].data, - (address, uint64, address, bytes, uint16, bytes32, uint64, FunctionsResponse.Commitment) - ); - - // Send as transmitter 1 - vm.stopPrank(); - vm.startPrank(NOP_TRANSMITTER_ADDRESS_1); - - // Build report - bytes32[] memory requestIds = new bytes32[](1); - requestIds[0] = requestId; - bytes[] memory results = new bytes[](1); - results[0] = bytes("hello world!"); - bytes[] memory errors = new bytes[](1); - // No error - bytes[] memory onchainMetadata = new bytes[](1); - onchainMetadata[0] = abi.encode(commitment); - bytes[] memory offchainMetadata = new bytes[](1); - // No offchain metadata - bytes memory report = abi.encode(requestIds, results, errors, onchainMetadata, offchainMetadata); - - // Build signers - address[31] memory signers; - signers[0] = NOP_SIGNER_ADDRESS_1; - - // Send report - vm.recordLogs(); - s_functionsCoordinator.callReportWithSigners(report, signers); - - // Get actual cost from RequestProcessed event log - Vm.Log[] memory entries2 = vm.getRecordedLogs(); - (uint96 totalCostJuels, , , , , ) = abi.decode( - entries2[2].data, - (uint96, address, FunctionsResponse.FulfillResult, bytes, bytes, bytes) - ); +/// @notice #cancelSubscription +contract FunctionsSubscriptions_CancelSubscription_ReceiveDeposit is FunctionsFulfillmentSetup { + event SubscriptionCanceled(uint64 indexed subscriptionId, address fundsRecipient, uint256 fundsAmount); - // Return to sending as owner - vm.stopPrank(); - vm.startPrank(OWNER_ADDRESS); + function test_CancelSubscription_SuccessRecieveDeposit() public { + uint96 totalCostJuels = s_fulfillmentRouterOwnerBalance + s_fulfillmentCoordinatorBalance; uint256 subscriptionOwnerBalanceBefore = s_linkToken.balanceOf(OWNER_ADDRESS); diff --git a/contracts/src/v0.8/functions/tests/v1_X/Gas.t.sol b/contracts/src/v0.8/functions/tests/v1_X/Gas.t.sol index 55ab3810b41..f2d7af54e4f 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/Gas.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/Gas.t.sol @@ -154,14 +154,6 @@ contract Gas_SendRequest is FunctionsSubscriptionSetup { /// @notice #fulfillRequest contract FunctionsClient_FulfillRequest is FunctionsClientRequestSetup { - struct Report { - bytes32[] rs; - bytes32[] ss; - bytes32 vs; - bytes report; - bytes32[3] reportContext; - } - mapping(uint256 reportNumber => Report) s_reports; FunctionsClientTestHelper s_functionsClientWithMaximumReturnData; diff --git a/contracts/src/v0.8/functions/tests/v1_X/OCR2.t.sol b/contracts/src/v0.8/functions/tests/v1_X/OCR2.t.sol index 745ad4f0ae9..3dc0db85a47 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/OCR2.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/OCR2.t.sol @@ -6,39 +6,25 @@ pragma solidity ^0.8.19; // ================================================================ /// @notice #constructor -contract OCR2Base_Constructor { - -} +contract OCR2Base_Constructor {} /// @notice #checkConfigValid -contract OCR2Base_CheckConfigValid { - -} +contract OCR2Base_CheckConfigValid {} /// @notice #latestConfigDigestAndEpoch -contract OCR2Base_LatestConfigDigestAndEpoch { - -} +contract OCR2Base_LatestConfigDigestAndEpoch {} /// @notice #setConfig -contract OCR2Base_SetConfig { - -} +contract OCR2Base_SetConfig {} /// @notice #configDigestFromConfigData -contract OCR2Base_ConfigDigestFromConfigData { - -} +contract OCR2Base_ConfigDigestFromConfigData {} /// @notice #latestConfigDetails -contract OCR2Base_LatestConfigDetails { - -} +contract OCR2Base_LatestConfigDetails {} /// @notice #transmitters -contract OCR2Base_Transmitters { - -} +contract OCR2Base_Transmitters {} /// @notice #_report contract OCR2Base__Report { @@ -46,11 +32,7 @@ contract OCR2Base__Report { } /// @notice #requireExpectedMsgDataLength -contract OCR2Base_RequireExpectedMsgDataLength { - -} +contract OCR2Base_RequireExpectedMsgDataLength {} /// @notice #transmit -contract OCR2Base_Transmit { - -} +contract OCR2Base_Transmit {} diff --git a/contracts/src/v0.8/functions/tests/v1_X/README.md b/contracts/src/v0.8/functions/tests/v1_X/README.md index 6400a28dc79..5f96532fb43 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/README.md +++ b/contracts/src/v0.8/functions/tests/v1_X/README.md @@ -5,18 +5,21 @@ First set the foundry profile to Functions: export FOUNDRY_PROFILE=functions ``` -To run all test files use: +**To run tests use**: + +All Functions test files: ``` -forge test -vv +forge test -vvv ``` To run a specific file use: ``` -forge test -vv --mp src/v0.8/functions/tests/v1_X/[File Name].t.sol +forge test -vvv --mp src/v0.8/functions/tests/v1_X/[File Name].t.sol ``` -To see coverage: -First ensure that the correct files are being evaluated. For example, if only v1 contracts are, then temporarily change the Functions profile in `./foundry.toml`. +**To see coverage**: +First ensure that the correct files are being evaluated. For example, if only v0 contracts are, then temporarily change the Functions profile in `./foundry.toml`. + ``` -forge coverage +forge coverage --ir-minimum ``` \ No newline at end of file diff --git a/contracts/src/v0.8/functions/tests/v1_X/Setup.t.sol b/contracts/src/v0.8/functions/tests/v1_X/Setup.t.sol index f603e83281c..41ee663e8b0 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/Setup.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/Setup.t.sol @@ -2,21 +2,21 @@ pragma solidity ^0.8.19; import {BaseTest} from "./BaseTest.t.sol"; -import {FunctionsRouter} from "../../dev/v1_X/FunctionsRouter.sol"; -import {FunctionsCoordinatorTestHelper} from "./testhelpers/FunctionsCoordinatorTestHelper.sol"; +import {FunctionsClientHarness} from "./testhelpers/FunctionsClientHarness.sol"; +import {FunctionsRouterHarness, FunctionsRouter} from "./testhelpers/FunctionsRouterHarness.sol"; +import {FunctionsCoordinatorHarness} from "./testhelpers/FunctionsCoordinatorHarness.sol"; import {FunctionsBilling} from "../../dev/v1_X/FunctionsBilling.sol"; import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol"; import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol"; import {TermsOfServiceAllowList} from "../../dev/v1_X/accessControl/TermsOfServiceAllowList.sol"; -import {FunctionsClientUpgradeHelper} from "./testhelpers/FunctionsClientUpgradeHelper.sol"; import {MockLinkToken} from "../../../mocks/MockLinkToken.sol"; import "forge-std/Vm.sol"; /// @notice Set up to deploy the following contracts: FunctionsRouter, FunctionsCoordinator, LINK/ETH Feed, ToS Allow List, and LINK token contract FunctionsRouterSetup is BaseTest { - FunctionsRouter internal s_functionsRouter; - FunctionsCoordinatorTestHelper internal s_functionsCoordinator; // TODO: use actual FunctionsCoordinator instead of helper + FunctionsRouterHarness internal s_functionsRouter; + FunctionsCoordinatorHarness internal s_functionsCoordinator; MockV3Aggregator internal s_linkEthFeed; TermsOfServiceAllowList internal s_termsOfServiceAllowList; MockLinkToken internal s_linkToken; @@ -36,9 +36,9 @@ contract FunctionsRouterSetup is BaseTest { function setUp() public virtual override { BaseTest.setUp(); s_linkToken = new MockLinkToken(); - s_functionsRouter = new FunctionsRouter(address(s_linkToken), getRouterConfig()); + s_functionsRouter = new FunctionsRouterHarness(address(s_linkToken), getRouterConfig()); s_linkEthFeed = new MockV3Aggregator(0, LINK_ETH_RATE); - s_functionsCoordinator = new FunctionsCoordinatorTestHelper( + s_functionsCoordinator = new FunctionsCoordinatorHarness( address(s_functionsRouter), getCoordinatorConfig(), address(s_linkEthFeed) @@ -68,8 +68,8 @@ contract FunctionsRouterSetup is BaseTest { return FunctionsBilling.Config({ feedStalenessSeconds: 24 * 60 * 60, // 1 day - gasOverheadAfterCallback: 50_000, // TODO: update - gasOverheadBeforeCallback: 100_00, // TODO: update + gasOverheadAfterCallback: 93_942, + gasOverheadBeforeCallback: 105_000, requestTimeoutSeconds: 60 * 5, // 5 minutes donFee: s_donFee, maxSupportedRequestDataVersion: 1, @@ -111,6 +111,15 @@ contract FunctionsDONSetup is FunctionsRouterSetup { uint64 internal s_offchainConfigVersion = 1; bytes internal s_offchainConfig = new bytes(0); + bytes s_thresholdKey = + vm.parseBytes( + "0x7b2247726f7570223a2250323536222c22475f626172223a22424f2f344358424575792f64547a436a612b614e774d666c2b645a77346d325036533246536b4966472f6633527547327337392b494e79642b4639326a346f586e67433657427561556a752b4a637a32377834484251343d222c2248223a224250532f72485065377941467232416c447a79395549466258776d46384666756632596d514177666e3342373844336f474845643247474536466e616f34552b4c6a4d4d5756792b464f7075686e77554f6a75427a64773d222c22484172726179223a5b22424d75546862414473337768316e67764e56792f6e3841316d42674b5a4b4c475259385937796a39695769337242502f316a32347571695869534531437554384c6f51446a386248466d384345477667517158494e62383d222c224248687974716d6e34314373322f4658416f43737548687151486236382f597930524b2b41354c6647654f645a78466f4e386c442b45656e4b587a544943784f6d3231636d535447364864484a6e336342645663714c673d222c22424d794e7a4534616e596258474d72694f52664c52634e7239766c347878654279316432452f4464335a744630546372386267567435582b2b42355967552b4b7875726e512f4d656b6857335845782b79506e4e4f584d3d222c22424d6a753272375a657a4a45545539413938746a6b6d547966796a79493735345742555835505174724a6578346d6766366130787373426d50325a7472412b55576d504e592b6d4664526b46674f7944694c53614e59453d225d7d" + ); + bytes s_donKey = + vm.parseBytes( + "0xf2f9c47363202d89aa9fa70baf783d70006fe493471ac8cfa82f1426fd09f16a5f6b32b7c4b5d5165cd147a6e513ba4c0efd39d969d6b20a8a21126f0411b9c6" + ); + function setUp() public virtual override { FunctionsRouterSetup.setUp(); @@ -136,6 +145,22 @@ contract FunctionsDONSetup is FunctionsRouterSetup { s_offchainConfig ); } + + function _getTransmitterBalances() internal view returns (uint256[4] memory balances) { + return [ + s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_1), + s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_2), + s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_3), + s_linkToken.balanceOf(NOP_TRANSMITTER_ADDRESS_4) + ]; + } + + function _assertTransmittersAllHaveBalance(uint256[4] memory balances, uint256 expectedBalance) internal { + assertEq(balances[0], expectedBalance); + assertEq(balances[1], expectedBalance); + assertEq(balances[2], expectedBalance); + assertEq(balances[3], expectedBalance); + } } /// @notice Set up to add the Coordinator and ToS Allow Contract as routes on the Router contract @@ -172,12 +197,12 @@ contract FunctionsOwnerAcceptTermsOfServiceSetup is FunctionsRoutesSetup { /// @notice Set up to deploy a consumer contract contract FunctionsClientSetup is FunctionsOwnerAcceptTermsOfServiceSetup { - FunctionsClientUpgradeHelper internal s_functionsClient; + FunctionsClientHarness internal s_functionsClient; function setUp() public virtual override { FunctionsOwnerAcceptTermsOfServiceSetup.setUp(); - s_functionsClient = new FunctionsClientUpgradeHelper(address(s_functionsRouter)); + s_functionsClient = new FunctionsClientHarness(address(s_functionsRouter)); } } @@ -200,6 +225,14 @@ contract FunctionsSubscriptionSetup is FunctionsClientSetup { /// @notice Set up to initate a minimal request and store it in s_requests[1] contract FunctionsClientRequestSetup is FunctionsSubscriptionSetup { + struct Report { + bytes32[] rs; + bytes32[] ss; + bytes32 vs; + bytes report; + bytes32[3] reportContext; + } + struct RequestData { string sourceCode; bytes secrets; @@ -215,6 +248,12 @@ contract FunctionsClientRequestSetup is FunctionsSubscriptionSetup { mapping(uint256 requestNumber => Request) s_requests; + struct Response { + uint96 totalCostJuels; + } + + mapping(uint256 requestNumber => Response) s_responses; + uint96 s_fulfillmentRouterOwnerBalance = 0; uint96 s_fulfillmentCoordinatorBalance = 0; @@ -230,7 +269,24 @@ contract FunctionsClientRequestSetup is FunctionsSubscriptionSetup { _sendAndStoreRequest(1, sourceCode, secrets, args, bytesArgs, callbackGasLimit); } - function _getExpectedCost(uint256 gasUsed) internal view returns (uint96 totalCostJuels) { + /// @notice Predicts the estimated cost (maximum cost) of a request + /// @dev Meant only for Ethereum, does not add L2 chains' L1 fee + function _getExpectedCostEstimate(uint256 callbackGas) internal view returns (uint96) { + uint256 gasPrice = TX_GASPRICE_START < getCoordinatorConfig().minimumEstimateGasPriceWei + ? getCoordinatorConfig().minimumEstimateGasPriceWei + : TX_GASPRICE_START; + uint256 gasPriceWithOverestimation = gasPrice + + ((gasPrice * getCoordinatorConfig().fulfillmentGasPriceOverEstimationBP) / 10_000); + uint96 juelsPerGas = uint96((1e18 * gasPriceWithOverestimation) / uint256(LINK_ETH_RATE)); + uint96 gasOverheadJuels = juelsPerGas * + ((getCoordinatorConfig().gasOverheadBeforeCallback + getCoordinatorConfig().gasOverheadAfterCallback)); + uint96 callbackGasCostJuels = uint96(juelsPerGas * callbackGas); + return gasOverheadJuels + s_donFee + s_adminFee + callbackGasCostJuels; + } + + /// @notice Predicts the actual cost of a request + /// @dev Meant only for Ethereum, does not add L2 chains' L1 fee + function _getExpectedCost(uint256 gasUsed) internal view returns (uint96) { uint96 juelsPerGas = uint96((1e18 * TX_GASPRICE_START) / uint256(LINK_ETH_RATE)); uint96 gasOverheadJuels = juelsPerGas * (getCoordinatorConfig().gasOverheadBeforeCallback + getCoordinatorConfig().gasOverheadAfterCallback); @@ -261,7 +317,7 @@ contract FunctionsClientRequestSetup is FunctionsSubscriptionSetup { vm.recordLogs(); - bytes32 requestId = FunctionsClientUpgradeHelper(client).sendRequest( + bytes32 requestId = FunctionsClientHarness(client).sendRequest( s_donId, sourceCode, secrets, @@ -375,7 +431,7 @@ contract FunctionsClientRequestSetup is FunctionsSubscriptionSetup { bytes memory report, bytes32[3] memory reportContext, uint256[] memory signerPrivateKeys - ) internal pure returns (bytes32[] memory rawRs, bytes32[] memory rawSs, bytes32 rawVs) { + ) internal pure returns (bytes32[] memory, bytes32[] memory, bytes32) { bytes32[] memory rs = new bytes32[](signerPrivateKeys.length); bytes32[] memory ss = new bytes32[](signerPrivateKeys.length); bytes memory vs = new bytes(signerPrivateKeys.length); @@ -392,13 +448,35 @@ contract FunctionsClientRequestSetup is FunctionsSubscriptionSetup { return (rs, ss, bytes32(vs)); } + function _buildAndSignReport( + uint256[] memory requestNumberKeys, + string[] memory results, + bytes[] memory errors + ) internal view returns (Report memory) { + (bytes memory report, bytes32[3] memory reportContext) = _buildReport(requestNumberKeys, results, errors); + + // Sign the report + // Need at least 3 signers to fulfill minimum number of: (configInfo.n + configInfo.f) / 2 + 1 + uint256[] memory signerPrivateKeys = new uint256[](3); + signerPrivateKeys[0] = NOP_SIGNER_PRIVATE_KEY_1; + signerPrivateKeys[1] = NOP_SIGNER_PRIVATE_KEY_2; + signerPrivateKeys[2] = NOP_SIGNER_PRIVATE_KEY_3; + (bytes32[] memory rawRs, bytes32[] memory rawSs, bytes32 rawVs) = _signReport( + report, + reportContext, + signerPrivateKeys + ); + + return Report({report: report, reportContext: reportContext, rs: rawRs, ss: rawSs, vs: rawVs}); + } + /// @notice Provide a response from the DON to fulfill one or more requests and store the updated balances of the DON & Admin /// @param requestNumberKeys - One or more requestNumberKeys that were used to store the request in `s_requests` of the requests, that will be added to the report /// @param results - The result that will be sent to the consumer contract's callback. For each index, e.g. result[index] or errors[index], only one of should be filled. /// @param errors - The error that will be sent to the consumer contract's callback. For each index, e.g. result[index] or errors[index], only one of should be filled. /// @param transmitter - The address that will send the `.report` transaction /// @param expectedToSucceed - Boolean representing if the report transmission is expected to produce a RequestProcessed event for every fulfillment. If not, we ignore retrieving the event log. - /// @param requestProcessedIndex - On a successful fulfillment the Router will emit a RequestProcessed event. To grab that event we must know the order at which this event was thrown in the report transmission lifecycle. This can change depending on the test setup (e.g. the Client contract gives an extra event during its callback) + /// @param requestProcessedStartIndex - On a successful fulfillment the Router will emit a RequestProcessed event. To grab that event we must know the order at which this event was thrown in the report transmission lifecycle. This can change depending on the test setup (e.g. the Client contract gives an extra event during its callback) /// @param transmitterGasToUse - Override the default amount of gas that the transmitter sends the `.report` transaction with function _reportAndStore( uint256[] memory requestNumberKeys, @@ -406,7 +484,7 @@ contract FunctionsClientRequestSetup is FunctionsSubscriptionSetup { bytes[] memory errors, address transmitter, bool expectedToSucceed, - uint8 requestProcessedIndex, + uint8 requestProcessedStartIndex, uint256 transmitterGasToUse ) internal { { @@ -415,19 +493,7 @@ contract FunctionsClientRequestSetup is FunctionsSubscriptionSetup { } } - (bytes memory report, bytes32[3] memory reportContext) = _buildReport(requestNumberKeys, results, errors); - - // Sign the report - // Need at least 3 signers to fulfill minimum number of: (configInfo.n + configInfo.f) / 2 + 1 - uint256[] memory signerPrivateKeys = new uint256[](3); - signerPrivateKeys[0] = NOP_SIGNER_PRIVATE_KEY_1; - signerPrivateKeys[1] = NOP_SIGNER_PRIVATE_KEY_2; - signerPrivateKeys[2] = NOP_SIGNER_PRIVATE_KEY_3; - (bytes32[] memory rawRs, bytes32[] memory rawSs, bytes32 rawVs) = _signReport( - report, - reportContext, - signerPrivateKeys - ); + Report memory r = _buildAndSignReport(requestNumberKeys, results, errors); // Send as transmitter vm.stopPrank(); @@ -436,26 +502,30 @@ contract FunctionsClientRequestSetup is FunctionsSubscriptionSetup { // Send report vm.recordLogs(); if (transmitterGasToUse > 0) { - s_functionsCoordinator.transmit{gas: transmitterGasToUse}(reportContext, report, rawRs, rawSs, rawVs); + s_functionsCoordinator.transmit{gas: transmitterGasToUse}(r.reportContext, r.report, r.rs, r.ss, r.vs); } else { - s_functionsCoordinator.transmit(reportContext, report, rawRs, rawSs, rawVs); + s_functionsCoordinator.transmit(r.reportContext, r.report, r.rs, r.ss, r.vs); } if (expectedToSucceed) { // Get actual cost from RequestProcessed event log (uint96 totalCostJuels, , , , , ) = abi.decode( - vm.getRecordedLogs()[requestProcessedIndex].data, + vm.getRecordedLogs()[requestProcessedStartIndex].data, (uint96, address, FunctionsResponse.FulfillResult, bytes, bytes, bytes) ); + // Store response of first request + // TODO: handle multiple requests + s_responses[requestNumberKeys[0]] = Response({totalCostJuels: totalCostJuels}); // Store profit amounts - s_fulfillmentRouterOwnerBalance += s_adminFee; + s_fulfillmentRouterOwnerBalance += s_adminFee * uint96(requestNumberKeys.length); // totalCostJuels = costWithoutCallbackJuels + adminFee + callbackGasCostJuels + // TODO: handle multiple requests s_fulfillmentCoordinatorBalance += totalCostJuels - s_adminFee; } // Return prank to Owner vm.stopPrank(); - vm.startPrank(OWNER_ADDRESS); + vm.startPrank(OWNER_ADDRESS, OWNER_ADDRESS); } /// @notice Provide a response from the DON to fulfill one or more requests and store the updated balances of the DON & Admin @@ -534,6 +604,9 @@ contract FunctionsFulfillmentSetup is FunctionsClientRequestSetup { function setUp() public virtual override { FunctionsClientRequestSetup.setUp(); + // Fast forward time by 30 seconds to simulate the DON executing the computation + vm.warp(block.timestamp + 30); + // Fulfill request 1 uint256[] memory requestNumberKeys = new uint256[](1); requestNumberKeys[0] = 1; diff --git a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientHarness.sol b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientHarness.sol new file mode 100644 index 00000000000..f0cb3965b5f --- /dev/null +++ b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientHarness.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {FunctionsClientUpgradeHelper} from "./FunctionsClientUpgradeHelper.sol"; +import {FunctionsResponse} from "../../../dev/v1_X/libraries/FunctionsResponse.sol"; + +/// @title Functions Client Test Harness +/// @notice Contract to expose internal functions for testing purposes +contract FunctionsClientHarness is FunctionsClientUpgradeHelper { + constructor(address router) FunctionsClientUpgradeHelper(router) {} + + function getRouter_HARNESS() external view returns (address) { + return address(i_functionsRouter); + } + + function sendRequest_HARNESS( + bytes memory data, + uint64 subscriptionId, + uint32 callbackGasLimit, + bytes32 donId + ) external returns (bytes32) { + return super._sendRequest(data, subscriptionId, callbackGasLimit, donId); + } +} diff --git a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientTestHelper.sol b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientTestHelper.sol index bca0f0a3fa2..c300f4d2b8a 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientTestHelper.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientTestHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.6; +pragma solidity ^0.8.19; import {ITermsOfServiceAllowList} from "../../../dev/v1_X/accessControl/interfaces/ITermsOfServiceAllowList.sol"; import {IFunctionsSubscriptions} from "../../../dev/v1_X/interfaces/IFunctionsSubscriptions.sol"; @@ -64,7 +64,7 @@ contract FunctionsClientTestHelper is FunctionsClient { uint32 callbackGasLimit = 20_000; request._initializeRequestForInlineJavaScript(sourceCode); bytes memory requestData = FunctionsRequest._encodeCBOR(request); - requestId = i_router.sendRequestToProposed( + requestId = i_functionsRouter.sendRequestToProposed( subscriptionId, requestData, FunctionsRequest.REQUEST_DATA_VERSION, @@ -76,13 +76,13 @@ contract FunctionsClientTestHelper is FunctionsClient { } function acceptTermsOfService(address acceptor, address recipient, bytes32 r, bytes32 s, uint8 v) external { - bytes32 allowListId = i_router.getAllowListId(); - ITermsOfServiceAllowList allowList = ITermsOfServiceAllowList(i_router.getContractById(allowListId)); + bytes32 allowListId = i_functionsRouter.getAllowListId(); + ITermsOfServiceAllowList allowList = ITermsOfServiceAllowList(i_functionsRouter.getContractById(allowListId)); allowList.acceptTermsOfService(acceptor, recipient, r, s, v); } function acceptSubscriptionOwnerTransfer(uint64 subscriptionId) external { - IFunctionsSubscriptions(address(i_router)).acceptSubscriptionOwnerTransfer(subscriptionId); + IFunctionsSubscriptions(address(i_functionsRouter)).acceptSubscriptionOwnerTransfer(subscriptionId); } function _fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override { @@ -98,7 +98,7 @@ contract FunctionsClientTestHelper is FunctionsClient { sendSimpleRequestWithJavaScript("somedata", s_subscriptionId, s_donId, 20_000); } if (s_doInvalidReentrantOperation) { - IFunctionsSubscriptions(address(i_router)).cancelSubscription(s_subscriptionId, msg.sender); + IFunctionsSubscriptions(address(i_functionsRouter)).cancelSubscription(s_subscriptionId, msg.sender); } emit FulfillRequestInvoked(requestId, response, err); } diff --git a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientUpgradeHelper.sol b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientUpgradeHelper.sol index a52c3009927..e0f636ee89f 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientUpgradeHelper.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientUpgradeHelper.sol @@ -82,7 +82,7 @@ contract FunctionsClientUpgradeHelper is FunctionsClient, ConfirmedOwner { uint32 callbackGasLimit, bytes32 donId ) internal returns (bytes32) { - bytes32 requestId = i_router.sendRequestToProposed( + bytes32 requestId = i_functionsRouter.sendRequestToProposed( subscriptionId, data, FunctionsRequest.REQUEST_DATA_VERSION, diff --git a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientWithEmptyCallback.sol b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientWithEmptyCallback.sol index 362b21d89ba..e5674717730 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientWithEmptyCallback.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsClientWithEmptyCallback.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.6; +pragma solidity ^0.8.19; import {FunctionsRequest} from "../../../dev/v1_X/libraries/FunctionsRequest.sol"; import {FunctionsClient} from "../../../dev/v1_X/FunctionsClient.sol"; diff --git a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsCoordinatorHarness.sol b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsCoordinatorHarness.sol new file mode 100644 index 00000000000..c1b6d5d0b14 --- /dev/null +++ b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsCoordinatorHarness.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {FunctionsCoordinator} from "../../../dev/v1_X/FunctionsCoordinator.sol"; +import {FunctionsBilling} from "../../../dev/v1_X/FunctionsBilling.sol"; +import {FunctionsResponse} from "../../../dev/v1_X/libraries/FunctionsResponse.sol"; + +/// @title Functions Coordinator Test Harness +/// @notice Contract to expose internal functions for testing purposes +contract FunctionsCoordinatorHarness is FunctionsCoordinator { + address s_linkToNativeFeed_HARNESS; + address s_router_HARNESS; + + constructor( + address router, + FunctionsBilling.Config memory config, + address linkToNativeFeed + ) FunctionsCoordinator(router, config, linkToNativeFeed) { + s_linkToNativeFeed_HARNESS = linkToNativeFeed; + s_router_HARNESS = router; + } + + function isTransmitter_HARNESS(address node) external view returns (bool) { + return super._isTransmitter(node); + } + + function beforeSetConfig_HARNESS(uint8 _f, bytes memory _onchainConfig) external { + return super._beforeSetConfig(_f, _onchainConfig); + } + + /// @dev Used by FunctionsBilling.sol + function getTransmitters_HARNESS() external view returns (address[] memory) { + return super._getTransmitters(); + } + + function report_HARNESS( + uint256 initialGas, + address transmitter, + uint8 signerCount, + address[MAX_NUM_ORACLES] memory signers, + bytes calldata report + ) external { + return super._report(initialGas, transmitter, signerCount, signers, report); + } + + function onlyOwner_HARNESS() external view { + return super._onlyOwner(); + } + + // ================================================================ + // | Functions Billing | + // ================================================================ + + function getLinkToNativeFeed_HARNESS() external view returns (address) { + return s_linkToNativeFeed_HARNESS; + } + + function getRouter_HARNESS() external view returns (address) { + return s_router_HARNESS; + } + + function calculateCostEstimate_HARNESS( + uint32 callbackGasLimit, + uint256 gasPriceWei, + uint72 donFee, + uint72 adminFee + ) external view returns (uint96) { + return super._calculateCostEstimate(callbackGasLimit, gasPriceWei, donFee, adminFee); + } + + function startBilling_HARNESS( + FunctionsResponse.RequestMeta memory request + ) external returns (FunctionsResponse.Commitment memory commitment) { + return super._startBilling(request); + } + + function fulfillAndBill_HARNESS( + bytes32 requestId, + bytes memory response, + bytes memory err, + bytes memory onchainMetadata, + bytes memory offchainMetadata, + uint8 reportBatchSize + ) external returns (FunctionsResponse.FulfillResult) { + return super._fulfillAndBill(requestId, response, err, onchainMetadata, offchainMetadata, reportBatchSize); + } + + function disperseFeePool_HARNESS() external { + return super._disperseFeePool(); + } + + // ================================================================ + // | OCR2 | + // ================================================================ + + function configDigestFromConfigData_HARNESS( + uint256 _chainId, + address _contractAddress, + uint64 _configCount, + address[] memory _signers, + address[] memory _transmitters, + uint8 _f, + bytes memory _onchainConfig, + uint64 _encodedConfigVersion, + bytes memory _encodedConfig + ) internal pure returns (bytes32) { + return + super._configDigestFromConfigData( + _chainId, + _contractAddress, + _configCount, + _signers, + _transmitters, + _f, + _onchainConfig, + _encodedConfigVersion, + _encodedConfig + ); + } +} diff --git a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsCoordinatorTestHelper.sol b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsCoordinatorTestHelper.sol index 1d883b3b29a..5e57e62e599 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsCoordinatorTestHelper.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsCoordinatorTestHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.6; +pragma solidity ^0.8.19; import {FunctionsCoordinator} from "../../../dev/v1_X/FunctionsCoordinator.sol"; import {FunctionsBilling} from "../../../dev/v1_X/FunctionsBilling.sol"; diff --git a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsRouterHarness.sol b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsRouterHarness.sol new file mode 100644 index 00000000000..7caeff498a3 --- /dev/null +++ b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsRouterHarness.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {FunctionsRouter} from "../../../dev/v1_X/FunctionsRouter.sol"; + +/// @title Functions Router Test Harness +/// @notice Contract to expose internal functions for testing purposes +contract FunctionsRouterHarness is FunctionsRouter { + constructor(address linkToken, Config memory config) FunctionsRouter(linkToken, config) {} + + function getMaxConsumers_HARNESS() external view returns (uint16) { + return super._getMaxConsumers(); + } + + function getSubscriptionDepositDetails_HARNESS() external view returns (uint16, uint72) { + return super._getSubscriptionDepositDetails(); + } + + function whenNotPaused_HARNESS() external view { + return super._whenNotPaused(); + } + + function onlyRouterOwner_HARNESS() external view { + return super._onlyRouterOwner(); + } + + function onlySenderThatAcceptedToS_HARNESS() external view { + return super._onlySenderThatAcceptedToS(); + } +} diff --git a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsSubscriptionsHarness.sol b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsSubscriptionsHarness.sol new file mode 100644 index 00000000000..2e2427f6e13 --- /dev/null +++ b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsSubscriptionsHarness.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {FunctionsSubscriptions} from "../../../dev/v1_X/FunctionsSubscriptions.sol"; + +/// @title Functions Subscriptions Test Harness +/// @notice Contract to expose internal functions for testing purposes +contract FunctionsSubscriptionsHarness is FunctionsSubscriptions { + constructor(address link) FunctionsSubscriptions(link) {} + + function markRequestInFlight_HARNESS(address client, uint64 subscriptionId, uint96 estimatedTotalCostJuels) external { + return super._markRequestInFlight(client, subscriptionId, estimatedTotalCostJuels); + } + + function pay_HARNESS( + uint64 subscriptionId, + uint96 estimatedTotalCostJuels, + address client, + uint96 adminFee, + uint96 juelsPerGas, + uint96 gasUsed, + uint96 costWithoutCallbackJuels + ) external returns (Receipt memory) { + return + super._pay( + subscriptionId, + estimatedTotalCostJuels, + client, + adminFee, + juelsPerGas, + gasUsed, + costWithoutCallbackJuels + ); + } + + function isExistingSubscription_HARNESS(uint64 subscriptionId) external view { + return super._isExistingSubscription(subscriptionId); + } + + function isAllowedConsumer_HARNESS(address client, uint64 subscriptionId) external view { + return super._isAllowedConsumer(client, subscriptionId); + } + + // Overrides + function _getMaxConsumers() internal view override returns (uint16) {} + + function _getSubscriptionDepositDetails() internal override returns (uint16, uint72) {} + + function _onlySenderThatAcceptedToS() internal override {} + + function _onlyRouterOwner() internal override {} + + function _whenNotPaused() internal override {} +} diff --git a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsTestHelper.sol b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsTestHelper.sol index e8e74e3ed74..50e90c44953 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsTestHelper.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsTestHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.6; +pragma solidity ^0.8.19; import {FunctionsRequest} from "../../../dev/v1_X/libraries/FunctionsRequest.sol"; diff --git a/contracts/src/v0.8/functions/v1_0_0/FunctionsBilling.sol b/contracts/src/v0.8/functions/v1_0_0/FunctionsBilling.sol index 1f99903c5a2..8de53dd9c04 100644 --- a/contracts/src/v0.8/functions/v1_0_0/FunctionsBilling.sol +++ b/contracts/src/v0.8/functions/v1_0_0/FunctionsBilling.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.19; import {IFunctionsSubscriptions} from "./interfaces/IFunctionsSubscriptions.sol"; -import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol"; +import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; import {IFunctionsBilling} from "./interfaces/IFunctionsBilling.sol"; import {Routable} from "./Routable.sol"; import {FunctionsResponse} from "./libraries/FunctionsResponse.sol"; -import {SafeCast} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/SafeCast.sol"; +import {SafeCast} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SafeCast.sol"; /// @title Functions Billing contract /// @notice Contract that calculates payment from users to the nodes of the Decentralized Oracle Network (DON). diff --git a/contracts/src/v0.8/functions/v1_0_0/FunctionsRouter.sol b/contracts/src/v0.8/functions/v1_0_0/FunctionsRouter.sol index dad50b042b9..9f35c4dfe51 100644 --- a/contracts/src/v0.8/functions/v1_0_0/FunctionsRouter.sol +++ b/contracts/src/v0.8/functions/v1_0_0/FunctionsRouter.sol @@ -10,8 +10,8 @@ import {FunctionsSubscriptions} from "./FunctionsSubscriptions.sol"; import {FunctionsResponse} from "./libraries/FunctionsResponse.sol"; import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; -import {SafeCast} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/SafeCast.sol"; -import {Pausable} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/security/Pausable.sol"; +import {SafeCast} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SafeCast.sol"; +import {Pausable} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/security/Pausable.sol"; contract FunctionsRouter is IFunctionsRouter, FunctionsSubscriptions, Pausable, ITypeAndVersion, ConfirmedOwner { using FunctionsResponse for FunctionsResponse.RequestMeta; diff --git a/contracts/src/v0.8/functions/v1_0_0/FunctionsSubscriptions.sol b/contracts/src/v0.8/functions/v1_0_0/FunctionsSubscriptions.sol index 7fa2368c46d..b93d2174734 100644 --- a/contracts/src/v0.8/functions/v1_0_0/FunctionsSubscriptions.sol +++ b/contracts/src/v0.8/functions/v1_0_0/FunctionsSubscriptions.sol @@ -7,8 +7,8 @@ import {IFunctionsBilling} from "./interfaces/IFunctionsBilling.sol"; import {FunctionsResponse} from "./libraries/FunctionsResponse.sol"; -import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title Functions Subscriptions contract /// @notice Contract that coordinates payment from users to the nodes of the Decentralized Oracle Network (DON). diff --git a/contracts/src/v0.8/functions/v1_0_0/accessControl/TermsOfServiceAllowList.sol b/contracts/src/v0.8/functions/v1_0_0/accessControl/TermsOfServiceAllowList.sol index a7b13577842..8a42e34cb84 100644 --- a/contracts/src/v0.8/functions/v1_0_0/accessControl/TermsOfServiceAllowList.sol +++ b/contracts/src/v0.8/functions/v1_0_0/accessControl/TermsOfServiceAllowList.sol @@ -7,8 +7,8 @@ import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; -import {Address} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Address.sol"; -import {EnumerableSet} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Address.sol"; +import {EnumerableSet} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol"; /// @notice A contract to handle access control of subscription management dependent on signing a Terms of Service contract TermsOfServiceAllowList is ITermsOfServiceAllowList, IAccessController, ITypeAndVersion, ConfirmedOwner { diff --git a/contracts/src/v0.8/functions/v1_0_0/interfaces/IOwnableFunctionsRouter.sol b/contracts/src/v0.8/functions/v1_0_0/interfaces/IOwnableFunctionsRouter.sol index 89eb48022be..c5f3d82677e 100644 --- a/contracts/src/v0.8/functions/v1_0_0/interfaces/IOwnableFunctionsRouter.sol +++ b/contracts/src/v0.8/functions/v1_0_0/interfaces/IOwnableFunctionsRouter.sol @@ -5,6 +5,4 @@ import {IFunctionsRouter} from "./IFunctionsRouter.sol"; import {IOwnable} from "../../../shared/interfaces/IOwnable.sol"; /// @title Chainlink Functions Router interface with Ownability. -interface IOwnableFunctionsRouter is IOwnable, IFunctionsRouter { - -} +interface IOwnableFunctionsRouter is IOwnable, IFunctionsRouter {} diff --git a/contracts/src/v0.8/functions/v1_1_0/FunctionsBilling.sol b/contracts/src/v0.8/functions/v1_1_0/FunctionsBilling.sol new file mode 100644 index 00000000000..ff345003741 --- /dev/null +++ b/contracts/src/v0.8/functions/v1_1_0/FunctionsBilling.sol @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IFunctionsSubscriptions} from "../v1_0_0/interfaces/IFunctionsSubscriptions.sol"; +import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; +import {IFunctionsBilling} from "../v1_0_0/interfaces/IFunctionsBilling.sol"; + +import {Routable} from "../v1_0_0/Routable.sol"; +import {FunctionsResponse} from "../v1_0_0/libraries/FunctionsResponse.sol"; + +import {SafeCast} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SafeCast.sol"; + +import {ChainSpecificUtil} from "./libraries/ChainSpecificUtil.sol"; + +/// @title Functions Billing contract +/// @notice Contract that calculates payment from users to the nodes of the Decentralized Oracle Network (DON). +abstract contract FunctionsBilling is Routable, IFunctionsBilling { + using FunctionsResponse for FunctionsResponse.RequestMeta; + using FunctionsResponse for FunctionsResponse.Commitment; + using FunctionsResponse for FunctionsResponse.FulfillResult; + + uint256 private constant REASONABLE_GAS_PRICE_CEILING = 1_000_000_000_000_000; // 1 million gwei + + event RequestBilled( + bytes32 indexed requestId, + uint96 juelsPerGas, + uint256 l1FeeShareWei, + uint96 callbackCostJuels, + uint96 totalCostJuels + ); + + // ================================================================ + // | Request Commitment state | + // ================================================================ + + mapping(bytes32 requestId => bytes32 commitmentHash) private s_requestCommitments; + + event CommitmentDeleted(bytes32 requestId); + + // ================================================================ + // | Configuration state | + // ================================================================ + + struct Config { + uint32 fulfillmentGasPriceOverEstimationBP; // ══╗ Percentage of gas price overestimation to account for changes in gas price between request and response. Held as basis points (one hundredth of 1 percentage point) + uint32 feedStalenessSeconds; // ║ How long before we consider the feed price to be stale and fallback to fallbackNativePerUnitLink. + uint32 gasOverheadBeforeCallback; // ║ Represents the average gas execution cost before the fulfillment callback. This amount is always billed for every request. + uint32 gasOverheadAfterCallback; // ║ Represents the average gas execution cost after the fulfillment callback. This amount is always billed for every request. + uint72 donFee; // ║ Additional flat fee (in Juels of LINK) that will be split between Node Operators. Max value is 2^80 - 1 == 1.2m LINK. + uint40 minimumEstimateGasPriceWei; // ║ The lowest amount of wei that will be used as the tx.gasprice when estimating the cost to fulfill the request + uint16 maxSupportedRequestDataVersion; // ═══════╝ The highest support request data version supported by the node. All lower versions should also be supported. + uint224 fallbackNativePerUnitLink; // ═══════════╗ Fallback NATIVE CURRENCY / LINK conversion rate if the data feed is stale + uint32 requestTimeoutSeconds; // ════════════════╝ How many seconds it takes before we consider a request to be timed out + } + + Config private s_config; + + event ConfigUpdated(Config config); + + error UnsupportedRequestDataVersion(); + error InsufficientBalance(); + error InvalidSubscription(); + error UnauthorizedSender(); + error MustBeSubOwner(address owner); + error InvalidLinkWeiPrice(int256 linkWei); + error PaymentTooLarge(); + error NoTransmittersSet(); + error InvalidCalldata(); + + // ================================================================ + // | Balance state | + // ================================================================ + + mapping(address transmitter => uint96 balanceJuelsLink) private s_withdrawableTokens; + // Pool together collected DON fees + // Disperse them on withdrawal or change in OCR configuration + uint96 internal s_feePool; + + AggregatorV3Interface private s_linkToNativeFeed; + + // ================================================================ + // | Initialization | + // ================================================================ + constructor(address router, Config memory config, address linkToNativeFeed) Routable(router) { + s_linkToNativeFeed = AggregatorV3Interface(linkToNativeFeed); + + updateConfig(config); + } + + // ================================================================ + // | Configuration | + // ================================================================ + + /// @notice Gets the Chainlink Coordinator's billing configuration + /// @return config + function getConfig() external view returns (Config memory) { + return s_config; + } + + /// @notice Sets the Chainlink Coordinator's billing configuration + /// @param config - See the contents of the Config struct in IFunctionsBilling.Config for more information + function updateConfig(Config memory config) public { + _onlyOwner(); + + s_config = config; + emit ConfigUpdated(config); + } + + // ================================================================ + // | Fee Calculation | + // ================================================================ + + /// @inheritdoc IFunctionsBilling + function getDONFee(bytes memory /* requestData */) public view override returns (uint72) { + return s_config.donFee; + } + + /// @inheritdoc IFunctionsBilling + function getAdminFee() public view override returns (uint72) { + return _getRouter().getAdminFee(); + } + + /// @inheritdoc IFunctionsBilling + function getWeiPerUnitLink() public view returns (uint256) { + Config memory config = s_config; + (, int256 weiPerUnitLink, , uint256 timestamp, ) = s_linkToNativeFeed.latestRoundData(); + // solhint-disable-next-line not-rely-on-time + if (config.feedStalenessSeconds < block.timestamp - timestamp && config.feedStalenessSeconds > 0) { + return config.fallbackNativePerUnitLink; + } + if (weiPerUnitLink <= 0) { + revert InvalidLinkWeiPrice(weiPerUnitLink); + } + return uint256(weiPerUnitLink); + } + + function _getJuelsFromWei(uint256 amountWei) private view returns (uint96) { + // (1e18 juels/link) * wei / (wei/link) = juels + // There are only 1e9*1e18 = 1e27 juels in existence, should not exceed uint96 (2^96 ~ 7e28) + return SafeCast.toUint96((1e18 * amountWei) / getWeiPerUnitLink()); + } + + // ================================================================ + // | Cost Estimation | + // ================================================================ + + /// @inheritdoc IFunctionsBilling + function estimateCost( + uint64 subscriptionId, + bytes calldata data, + uint32 callbackGasLimit, + uint256 gasPriceWei + ) external view override returns (uint96) { + _getRouter().isValidCallbackGasLimit(subscriptionId, callbackGasLimit); + // Reasonable ceilings to prevent integer overflows + if (gasPriceWei > REASONABLE_GAS_PRICE_CEILING) { + revert InvalidCalldata(); + } + uint72 adminFee = getAdminFee(); + uint72 donFee = getDONFee(data); + return _calculateCostEstimate(callbackGasLimit, gasPriceWei, donFee, adminFee); + } + + /// @notice Estimate the cost in Juels of LINK + // that will be charged to a subscription to fulfill a Functions request + // Gas Price can be overestimated to account for flucuations between request and response time + function _calculateCostEstimate( + uint32 callbackGasLimit, + uint256 gasPriceWei, + uint72 donFee, + uint72 adminFee + ) internal view returns (uint96) { + // If gas price is less than the minimum fulfillment gas price, override to using the minimum + if (gasPriceWei < s_config.minimumEstimateGasPriceWei) { + gasPriceWei = s_config.minimumEstimateGasPriceWei; + } + + uint256 gasPriceWithOverestimation = gasPriceWei + + ((gasPriceWei * s_config.fulfillmentGasPriceOverEstimationBP) / 10_000); + /// @NOTE: Basis Points are 1/100th of 1%, divide by 10_000 to bring back to original units + + uint256 executionGas = s_config.gasOverheadBeforeCallback + s_config.gasOverheadAfterCallback + callbackGasLimit; + uint256 l1FeeWei = ChainSpecificUtil._getCurrentTxL1GasFees(msg.data); + uint96 estimatedGasReimbursementJuels = _getJuelsFromWei((gasPriceWithOverestimation * executionGas) + l1FeeWei); + + uint96 feesJuels = uint96(donFee) + uint96(adminFee); + + return estimatedGasReimbursementJuels + feesJuels; + } + + // ================================================================ + // | Billing | + // ================================================================ + + /// @notice Initiate the billing process for an Functions request + /// @dev Only callable by the Functions Router + /// @param request - Chainlink Functions request data, see FunctionsResponse.RequestMeta for the structure + /// @return commitment - The parameters of the request that must be held consistent at response time + function _startBilling( + FunctionsResponse.RequestMeta memory request + ) internal returns (FunctionsResponse.Commitment memory commitment) { + Config memory config = s_config; + + // Nodes should support all past versions of the structure + if (request.dataVersion > config.maxSupportedRequestDataVersion) { + revert UnsupportedRequestDataVersion(); + } + + uint72 donFee = getDONFee(request.data); + uint96 estimatedTotalCostJuels = _calculateCostEstimate( + request.callbackGasLimit, + tx.gasprice, + donFee, + request.adminFee + ); + + // Check that subscription can afford the estimated cost + if ((request.availableBalance) < estimatedTotalCostJuels) { + revert InsufficientBalance(); + } + + uint32 timeoutTimestamp = uint32(block.timestamp + config.requestTimeoutSeconds); + bytes32 requestId = keccak256( + abi.encode( + address(this), + request.requestingContract, + request.subscriptionId, + request.initiatedRequests + 1, + keccak256(request.data), + request.dataVersion, + request.callbackGasLimit, + estimatedTotalCostJuels, + timeoutTimestamp, + // solhint-disable-next-line avoid-tx-origin + tx.origin + ) + ); + + commitment = FunctionsResponse.Commitment({ + adminFee: request.adminFee, + coordinator: address(this), + client: request.requestingContract, + subscriptionId: request.subscriptionId, + callbackGasLimit: request.callbackGasLimit, + estimatedTotalCostJuels: estimatedTotalCostJuels, + timeoutTimestamp: timeoutTimestamp, + requestId: requestId, + donFee: donFee, + gasOverheadBeforeCallback: config.gasOverheadBeforeCallback, + gasOverheadAfterCallback: config.gasOverheadAfterCallback + }); + + s_requestCommitments[requestId] = keccak256(abi.encode(commitment)); + + return commitment; + } + + /// @notice Finalize billing process for an Functions request by sending a callback to the Client contract and then charging the subscription + /// @param requestId identifier for the request that was generated by the Registry in the beginBilling commitment + /// @param response response data from DON consensus + /// @param err error from DON consensus + /// @param reportBatchSize the number of fulfillments in the transmitter's report + /// @return result fulfillment result + /// @dev Only callable by a node that has been approved on the Coordinator + /// @dev simulated offchain to determine if sufficient balance is present to fulfill the request + function _fulfillAndBill( + bytes32 requestId, + bytes memory response, + bytes memory err, + bytes memory onchainMetadata, + bytes memory /* offchainMetadata TODO: use in getDonFee() for dynamic billing */, + uint8 reportBatchSize + ) internal returns (FunctionsResponse.FulfillResult) { + FunctionsResponse.Commitment memory commitment = abi.decode(onchainMetadata, (FunctionsResponse.Commitment)); + + uint256 gasOverheadWei = (commitment.gasOverheadBeforeCallback + commitment.gasOverheadAfterCallback) * tx.gasprice; + uint256 l1FeeShareWei = ChainSpecificUtil._getCurrentTxL1GasFees(msg.data) / reportBatchSize; + // Gas overhead without callback + uint96 gasOverheadJuels = _getJuelsFromWei(gasOverheadWei + l1FeeShareWei); + uint96 juelsPerGas = _getJuelsFromWei(tx.gasprice); + + // The Functions Router will perform the callback to the client contract + (FunctionsResponse.FulfillResult resultCode, uint96 callbackCostJuels) = _getRouter().fulfill( + response, + err, + juelsPerGas, + gasOverheadJuels + commitment.donFee, // cost without callback or admin fee, those will be added by the Router + msg.sender, + commitment + ); + + // The router will only pay the DON on successfully processing the fulfillment + // In these two fulfillment results the user has been charged + // Otherwise, the Coordinator should hold on to the request commitment + if ( + resultCode == FunctionsResponse.FulfillResult.FULFILLED || + resultCode == FunctionsResponse.FulfillResult.USER_CALLBACK_ERROR + ) { + delete s_requestCommitments[requestId]; + // Reimburse the transmitter for the fulfillment gas cost + s_withdrawableTokens[msg.sender] = gasOverheadJuels + callbackCostJuels; + // Put donFee into the pool of fees, to be split later + // Saves on storage writes that would otherwise be charged to the user + s_feePool += commitment.donFee; + emit RequestBilled({ + requestId: requestId, + juelsPerGas: juelsPerGas, + l1FeeShareWei: l1FeeShareWei, + callbackCostJuels: callbackCostJuels, + totalCostJuels: gasOverheadJuels + callbackCostJuels + commitment.donFee + commitment.adminFee + }); + } + + return resultCode; + } + + // ================================================================ + // | Request Timeout | + // ================================================================ + + /// @inheritdoc IFunctionsBilling + /// @dev Only callable by the Router + /// @dev Used by FunctionsRouter.sol during timeout of a request + function deleteCommitment(bytes32 requestId) external override onlyRouter { + // Delete commitment + delete s_requestCommitments[requestId]; + emit CommitmentDeleted(requestId); + } + + // ================================================================ + // | Fund withdrawal | + // ================================================================ + + /// @inheritdoc IFunctionsBilling + function oracleWithdraw(address recipient, uint96 amount) external { + _disperseFeePool(); + + if (amount == 0) { + amount = s_withdrawableTokens[msg.sender]; + } else if (s_withdrawableTokens[msg.sender] < amount) { + revert InsufficientBalance(); + } + s_withdrawableTokens[msg.sender] -= amount; + IFunctionsSubscriptions(address(_getRouter())).oracleWithdraw(recipient, amount); + } + + /// @inheritdoc IFunctionsBilling + /// @dev Only callable by the Coordinator owner + function oracleWithdrawAll() external { + _onlyOwner(); + _disperseFeePool(); + + address[] memory transmitters = _getTransmitters(); + + // Bounded by "maxNumOracles" on OCR2Abstract.sol + for (uint256 i = 0; i < transmitters.length; ++i) { + uint96 balance = s_withdrawableTokens[transmitters[i]]; + if (balance > 0) { + s_withdrawableTokens[transmitters[i]] = 0; + IFunctionsSubscriptions(address(_getRouter())).oracleWithdraw(transmitters[i], balance); + } + } + } + + // Overriden in FunctionsCoordinator, which has visibility into transmitters + function _getTransmitters() internal view virtual returns (address[] memory); + + // DON fees are collected into a pool s_feePool + // When OCR configuration changes, or any oracle withdraws, this must be dispersed + function _disperseFeePool() internal { + if (s_feePool == 0) { + return; + } + // All transmitters are assumed to also be observers + // Pay out the DON fee to all transmitters + address[] memory transmitters = _getTransmitters(); + uint256 numberOfTransmitters = transmitters.length; + if (numberOfTransmitters == 0) { + revert NoTransmittersSet(); + } + uint96 feePoolShare = s_feePool / uint96(numberOfTransmitters); + // Bounded by "maxNumOracles" on OCR2Abstract.sol + for (uint256 i = 0; i < numberOfTransmitters; ++i) { + s_withdrawableTokens[transmitters[i]] += feePoolShare; + } + s_feePool -= feePoolShare * uint96(numberOfTransmitters); + } + + // Overriden in FunctionsCoordinator.sol + function _onlyOwner() internal view virtual; +} diff --git a/contracts/src/v0.8/functions/v1_1_0/FunctionsCoordinator.sol b/contracts/src/v0.8/functions/v1_1_0/FunctionsCoordinator.sol new file mode 100644 index 00000000000..0a5da643a57 --- /dev/null +++ b/contracts/src/v0.8/functions/v1_1_0/FunctionsCoordinator.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IFunctionsCoordinator} from "../v1_0_0/interfaces/IFunctionsCoordinator.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; + +import {FunctionsBilling} from "./FunctionsBilling.sol"; +import {OCR2Base} from "./ocr/OCR2Base.sol"; +import {FunctionsResponse} from "../v1_0_0/libraries/FunctionsResponse.sol"; + +/// @title Functions Coordinator contract +/// @notice Contract that nodes of a Decentralized Oracle Network (DON) interact with +contract FunctionsCoordinator is OCR2Base, IFunctionsCoordinator, FunctionsBilling { + using FunctionsResponse for FunctionsResponse.RequestMeta; + using FunctionsResponse for FunctionsResponse.Commitment; + using FunctionsResponse for FunctionsResponse.FulfillResult; + + /// @inheritdoc ITypeAndVersion + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables + string public constant override typeAndVersion = "Functions Coordinator v1.1.0"; + + event OracleRequest( + bytes32 indexed requestId, + address indexed requestingContract, + address requestInitiator, + uint64 subscriptionId, + address subscriptionOwner, + bytes data, + uint16 dataVersion, + bytes32 flags, + uint64 callbackGasLimit, + FunctionsResponse.Commitment commitment + ); + event OracleResponse(bytes32 indexed requestId, address transmitter); + + error InconsistentReportData(); + error EmptyPublicKey(); + error UnauthorizedPublicKeyChange(); + + bytes private s_donPublicKey; + bytes private s_thresholdPublicKey; + + constructor( + address router, + Config memory config, + address linkToNativeFeed + ) OCR2Base() FunctionsBilling(router, config, linkToNativeFeed) {} + + /// @inheritdoc IFunctionsCoordinator + function getThresholdPublicKey() external view override returns (bytes memory) { + if (s_thresholdPublicKey.length == 0) { + revert EmptyPublicKey(); + } + return s_thresholdPublicKey; + } + + /// @inheritdoc IFunctionsCoordinator + function setThresholdPublicKey(bytes calldata thresholdPublicKey) external override onlyOwner { + if (thresholdPublicKey.length == 0) { + revert EmptyPublicKey(); + } + s_thresholdPublicKey = thresholdPublicKey; + } + + /// @inheritdoc IFunctionsCoordinator + function getDONPublicKey() external view override returns (bytes memory) { + if (s_donPublicKey.length == 0) { + revert EmptyPublicKey(); + } + return s_donPublicKey; + } + + /// @inheritdoc IFunctionsCoordinator + function setDONPublicKey(bytes calldata donPublicKey) external override onlyOwner { + if (donPublicKey.length == 0) { + revert EmptyPublicKey(); + } + s_donPublicKey = donPublicKey; + } + + /// @dev check if node is in current transmitter list + function _isTransmitter(address node) internal view returns (bool) { + address[] memory nodes = s_transmitters; + // Bounded by "maxNumOracles" on OCR2Abstract.sol + for (uint256 i = 0; i < nodes.length; ++i) { + if (nodes[i] == node) { + return true; + } + } + return false; + } + + /// @inheritdoc IFunctionsCoordinator + function startRequest( + FunctionsResponse.RequestMeta calldata request + ) external override onlyRouter returns (FunctionsResponse.Commitment memory commitment) { + commitment = _startBilling(request); + + emit OracleRequest( + commitment.requestId, + request.requestingContract, + // solhint-disable-next-line avoid-tx-origin + tx.origin, + request.subscriptionId, + request.subscriptionOwner, + request.data, + request.dataVersion, + request.flags, + request.callbackGasLimit, + commitment + ); + + return commitment; + } + + /// @dev DON fees are pooled together. If the OCR configuration is going to change, these need to be distributed. + function _beforeSetConfig(uint8 /* _f */, bytes memory /* _onchainConfig */) internal override { + if (_getTransmitters().length > 0) { + _disperseFeePool(); + } + } + + /// @dev Used by FunctionsBilling.sol + function _getTransmitters() internal view override returns (address[] memory) { + return s_transmitters; + } + + /// @dev Report hook called within OCR2Base.sol + function _report( + uint256 /*initialGas*/, + address /*transmitter*/, + uint8 /*signerCount*/, + address[MAX_NUM_ORACLES] memory /*signers*/, + bytes calldata report + ) internal override { + ( + bytes32[] memory requestIds, + bytes[] memory results, + bytes[] memory errors, + bytes[] memory onchainMetadata, + bytes[] memory offchainMetadata + ) = abi.decode(report, (bytes32[], bytes[], bytes[], bytes[], bytes[])); + uint256 numberOfFulfillments = uint8(requestIds.length); + + if ( + numberOfFulfillments == 0 || + numberOfFulfillments != results.length || + numberOfFulfillments != errors.length || + numberOfFulfillments != onchainMetadata.length || + numberOfFulfillments != offchainMetadata.length + ) { + revert ReportInvalid("Fields must be equal length"); + } + + // Bounded by "MaxRequestBatchSize" on the Job's ReportingPluginConfig + for (uint256 i = 0; i < numberOfFulfillments; ++i) { + FunctionsResponse.FulfillResult result = FunctionsResponse.FulfillResult( + _fulfillAndBill( + requestIds[i], + results[i], + errors[i], + onchainMetadata[i], + offchainMetadata[i], + uint8(numberOfFulfillments) // will not exceed "MaxRequestBatchSize" on the Job's ReportingPluginConfig + ) + ); + + // Emit on successfully processing the fulfillment + // In these two fulfillment results the user has been charged + // Otherwise, the DON will re-try + if ( + result == FunctionsResponse.FulfillResult.FULFILLED || + result == FunctionsResponse.FulfillResult.USER_CALLBACK_ERROR + ) { + emit OracleResponse(requestIds[i], msg.sender); + } + } + } + + /// @dev Used in FunctionsBilling.sol + function _onlyOwner() internal view override { + _validateOwnership(); + } +} diff --git a/contracts/src/v0.8/functions/v1_1_0/libraries/ChainSpecificUtil.sol b/contracts/src/v0.8/functions/v1_1_0/libraries/ChainSpecificUtil.sol new file mode 100644 index 00000000000..68d346e676d --- /dev/null +++ b/contracts/src/v0.8/functions/v1_1_0/libraries/ChainSpecificUtil.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ArbGasInfo} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol"; +import {GasPriceOracle} from "../../../vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/L2/GasPriceOracle.sol"; + +/// @dev A library that abstracts out opcodes that behave differently across chains. +/// @dev The methods below return values that are pertinent to the given chain. +library ChainSpecificUtil { + // ------------ Start Arbitrum Constants ------------ + + /// @dev ARBGAS_ADDR is the address of the ArbGasInfo precompile on Arbitrum. + /// @dev reference: https://github.com/OffchainLabs/nitro/blob/v2.0.14/contracts/src/precompiles/ArbGasInfo.sol#L10 + address private constant ARBGAS_ADDR = address(0x000000000000000000000000000000000000006C); + ArbGasInfo private constant ARBGAS = ArbGasInfo(ARBGAS_ADDR); + + uint256 private constant ARB_MAINNET_CHAIN_ID = 42161; + uint256 private constant ARB_GOERLI_TESTNET_CHAIN_ID = 421613; + uint256 private constant ARB_SEPOLIA_TESTNET_CHAIN_ID = 421614; + + // ------------ End Arbitrum Constants ------------ + + // ------------ Start Optimism Constants ------------ + /// @dev L1_FEE_DATA_PADDING includes 35 bytes for L1 data padding for Optimism + bytes internal constant L1_FEE_DATA_PADDING = + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + /// @dev OVM_GASPRICEORACLE_ADDR is the address of the GasPriceOracle precompile on Optimism. + /// @dev reference: https://community.optimism.io/docs/developers/build/transaction-fees/#estimating-the-l1-data-fee + address private constant OVM_GASPRICEORACLE_ADDR = address(0x420000000000000000000000000000000000000F); + GasPriceOracle private constant OVM_GASPRICEORACLE = GasPriceOracle(OVM_GASPRICEORACLE_ADDR); + + uint256 private constant OP_MAINNET_CHAIN_ID = 10; + uint256 private constant OP_GOERLI_CHAIN_ID = 420; + uint256 private constant OP_SEPOLIA_CHAIN_ID = 11155420; + + /// @dev Base is a OP stack based rollup and follows the same L1 pricing logic as Optimism. + uint256 private constant BASE_MAINNET_CHAIN_ID = 8453; + uint256 private constant BASE_GOERLI_CHAIN_ID = 84531; + uint256 private constant BASE_SEPOLIA_CHAIN_ID = 84532; + + // ------------ End Optimism Constants ------------ + + /// @notice Returns the L1 fees in wei that will be paid for the current transaction, given any calldata + /// @notice for the current transaction. + /// @notice When on a known Arbitrum chain, it uses ArbGas.getCurrentTxL1GasFees to get the fees. + /// @notice On Arbitrum, the provided calldata is not used to calculate the fees. + /// @notice On Optimism, the provided calldata is passed to the GasPriceOracle predeploy + /// @notice and getL1Fee is called to get the fees. + function _getCurrentTxL1GasFees(bytes memory txCallData) internal view returns (uint256 l1FeeWei) { + uint256 chainid = block.chainid; + if (_isArbitrumChainId(chainid)) { + return ARBGAS.getCurrentTxL1GasFees(); + } else if (_isOptimismChainId(chainid)) { + return OVM_GASPRICEORACLE.getL1Fee(bytes.concat(txCallData, L1_FEE_DATA_PADDING)); + } + return 0; + } + + /// @notice Return true if and only if the provided chain ID is an Arbitrum chain ID. + function _isArbitrumChainId(uint256 chainId) internal pure returns (bool) { + return + chainId == ARB_MAINNET_CHAIN_ID || + chainId == ARB_GOERLI_TESTNET_CHAIN_ID || + chainId == ARB_SEPOLIA_TESTNET_CHAIN_ID; + } + + /// @notice Return true if and only if the provided chain ID is an Optimism (or Base) chain ID. + /// @notice Note that optimism chain id's are also OP stack chain id's. + function _isOptimismChainId(uint256 chainId) internal pure returns (bool) { + return + chainId == OP_MAINNET_CHAIN_ID || + chainId == OP_GOERLI_CHAIN_ID || + chainId == OP_SEPOLIA_CHAIN_ID || + chainId == BASE_MAINNET_CHAIN_ID || + chainId == BASE_GOERLI_CHAIN_ID || + chainId == BASE_SEPOLIA_CHAIN_ID; + } +} diff --git a/contracts/src/v0.8/functions/v1_1_0/ocr/OCR2Abstract.sol b/contracts/src/v0.8/functions/v1_1_0/ocr/OCR2Abstract.sol new file mode 100644 index 00000000000..4182227d645 --- /dev/null +++ b/contracts/src/v0.8/functions/v1_1_0/ocr/OCR2Abstract.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; + +abstract contract OCR2Abstract is ITypeAndVersion { + // Maximum number of oracles the offchain reporting protocol is designed for + uint256 internal constant MAX_NUM_ORACLES = 31; + + /** + * @notice triggers a new run of the offchain reporting protocol + * @param previousConfigBlockNumber block in which the previous config was set, to simplify historic analysis + * @param configDigest configDigest of this configuration + * @param configCount ordinal number of this config setting among all config settings over the life of this contract + * @param signers ith element is address ith oracle uses to sign a report + * @param transmitters ith element is address ith oracle uses to transmit a report via the transmit method + * @param f maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly + * @param onchainConfig serialized configuration used by the contract (and possibly oracles) + * @param offchainConfigVersion version of the serialization format used for "offchainConfig" parameter + * @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract + */ + event ConfigSet( + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + address[] signers, + address[] transmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig + ); + + /** + * @notice sets offchain reporting protocol configuration incl. participating oracles + * @param signers addresses with which oracles sign the reports + * @param transmitters addresses oracles use to transmit the reports + * @param f number of faulty oracles the system can tolerate + * @param onchainConfig serialized configuration used by the contract (and possibly oracles) + * @param offchainConfigVersion version number for offchainEncoding schema + * @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract + */ + function setConfig( + address[] memory signers, + address[] memory transmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external virtual; + + /** + * @notice information about current offchain reporting protocol configuration + * @return configCount ordinal number of current config, out of all configs applied to this contract so far + * @return blockNumber block at which this config was set + * @return configDigest domain-separation tag for current config (see _configDigestFromConfigData) + */ + function latestConfigDetails() + external + view + virtual + returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest); + + /** + * @notice optionally emited to indicate the latest configDigest and epoch for + which a report was successfully transmited. Alternatively, the contract may + use latestConfigDigestAndEpoch with scanLogs set to false. + */ + event Transmitted(bytes32 configDigest, uint32 epoch); + + /** + * @notice optionally returns the latest configDigest and epoch for which a + report was successfully transmitted. Alternatively, the contract may return + scanLogs set to true and use Transmitted events to provide this information + to offchain watchers. + * @return scanLogs indicates whether to rely on the configDigest and epoch + returned or whether to scan logs for the Transmitted event instead. + * @return configDigest + * @return epoch + */ + function latestConfigDigestAndEpoch() + external + view + virtual + returns (bool scanLogs, bytes32 configDigest, uint32 epoch); + + /** + * @notice transmit is called to post a new report to the contract + * @param report serialized report, which the signatures are signing. + * @param rs ith element is the R components of the ith signature on report. Must have at most maxNumOracles entries + * @param ss ith element is the S components of the ith signature on report. Must have at most maxNumOracles entries + * @param rawVs ith element is the the V component of the ith signature + */ + function transmit( + // NOTE: If these parameters are changed, expectedMsgDataLength and/or + // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly + bytes32[3] calldata reportContext, + bytes calldata report, + bytes32[] calldata rs, + bytes32[] calldata ss, + bytes32 rawVs // signatures + ) external virtual; +} diff --git a/contracts/src/v0.8/functions/v1_1_0/ocr/OCR2Base.sol b/contracts/src/v0.8/functions/v1_1_0/ocr/OCR2Base.sol new file mode 100644 index 00000000000..ea1d45ffd46 --- /dev/null +++ b/contracts/src/v0.8/functions/v1_1_0/ocr/OCR2Base.sol @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; +import {OCR2Abstract} from "./OCR2Abstract.sol"; + +/** + * @notice Onchain verification of reports from the offchain reporting protocol + * @dev For details on its operation, see the offchain reporting protocol design + * doc, which refers to this contract as simply the "contract". + */ +abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { + error ReportInvalid(string message); + error InvalidConfig(string message); + + constructor() ConfirmedOwner(msg.sender) {} + + // incremented each time a new config is posted. This count is incorporated + // into the config digest, to prevent replay attacks. + uint32 internal s_configCount; + uint32 internal s_latestConfigBlockNumber; // makes it easier for offchain systems + // to extract config from logs. + + // Storing these fields used on the hot path in a ConfigInfo variable reduces the + // retrieval of all of them to a single SLOAD. If any further fields are + // added, make sure that storage of the struct still takes at most 32 bytes. + struct ConfigInfo { + bytes32 latestConfigDigest; + uint8 f; // TODO: could be optimized by squeezing into one slot + uint8 n; + } + ConfigInfo internal s_configInfo; + + // Used for s_oracles[a].role, where a is an address, to track the purpose + // of the address, or to indicate that the address is unset. + enum Role { + // No oracle role has been set for address a + Unset, + // Signing address for the s_oracles[a].index'th oracle. I.e., report + // signatures from this oracle should ecrecover back to address a. + Signer, + // Transmission address for the s_oracles[a].index'th oracle. I.e., if a + // report is received by OCR2Aggregator.transmit in which msg.sender is + // a, it is attributed to the s_oracles[a].index'th oracle. + Transmitter + } + + struct Oracle { + uint8 index; // Index of oracle in s_signers/s_transmitters + Role role; // Role of the address which mapped to this struct + } + + mapping(address signerOrTransmitter => Oracle) internal s_oracles; + + // s_signers contains the signing address of each oracle + address[] internal s_signers; + + // s_transmitters contains the transmission address of each oracle, + // i.e. the address the oracle actually sends transactions to the contract from + address[] internal s_transmitters; + + /* + * Config logic + */ + + // Reverts transaction if config args are invalid + modifier checkConfigValid( + uint256 numSigners, + uint256 numTransmitters, + uint256 f + ) { + if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers"); + if (f == 0) revert InvalidConfig("f must be positive"); + if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration"); + if (numSigners <= 3 * f) revert InvalidConfig("faulty-oracle f too high"); + _; + } + + struct SetConfigArgs { + address[] signers; + address[] transmitters; + uint8 f; + bytes onchainConfig; + uint64 offchainConfigVersion; + bytes offchainConfig; + } + + /// @inheritdoc OCR2Abstract + function latestConfigDigestAndEpoch() + external + view + virtual + override + returns (bool scanLogs, bytes32 configDigest, uint32 epoch) + { + return (true, bytes32(0), uint32(0)); + } + + /** + * @notice sets offchain reporting protocol configuration incl. participating oracles + * @param _signers addresses with which oracles sign the reports + * @param _transmitters addresses oracles use to transmit the reports + * @param _f number of faulty oracles the system can tolerate + * @param _onchainConfig encoded on-chain contract configuration + * @param _offchainConfigVersion version number for offchainEncoding schema + * @param _offchainConfig encoded off-chain oracle configuration + */ + function setConfig( + address[] memory _signers, + address[] memory _transmitters, + uint8 _f, + bytes memory _onchainConfig, + uint64 _offchainConfigVersion, + bytes memory _offchainConfig + ) external override checkConfigValid(_signers.length, _transmitters.length, _f) onlyOwner { + SetConfigArgs memory args = SetConfigArgs({ + signers: _signers, + transmitters: _transmitters, + f: _f, + onchainConfig: _onchainConfig, + offchainConfigVersion: _offchainConfigVersion, + offchainConfig: _offchainConfig + }); + + _beforeSetConfig(args.f, args.onchainConfig); + + while (s_signers.length != 0) { + // remove any old signer/transmitter addresses + uint256 lastIdx = s_signers.length - 1; + address signer = s_signers[lastIdx]; + address transmitter = s_transmitters[lastIdx]; + delete s_oracles[signer]; + delete s_oracles[transmitter]; + s_signers.pop(); + s_transmitters.pop(); + } + + // Bounded by MAX_NUM_ORACLES in OCR2Abstract.sol + for (uint256 i = 0; i < args.signers.length; i++) { + if (args.signers[i] == address(0)) revert InvalidConfig("signer must not be empty"); + if (args.transmitters[i] == address(0)) revert InvalidConfig("transmitter must not be empty"); + // add new signer/transmitter addresses + if (s_oracles[args.signers[i]].role != Role.Unset) revert InvalidConfig("repeated signer address"); + s_oracles[args.signers[i]] = Oracle(uint8(i), Role.Signer); + if (s_oracles[args.transmitters[i]].role != Role.Unset) revert InvalidConfig("repeated transmitter address"); + s_oracles[args.transmitters[i]] = Oracle(uint8(i), Role.Transmitter); + s_signers.push(args.signers[i]); + s_transmitters.push(args.transmitters[i]); + } + s_configInfo.f = args.f; + uint32 previousConfigBlockNumber = s_latestConfigBlockNumber; + s_latestConfigBlockNumber = uint32(block.number); + s_configCount += 1; + { + s_configInfo.latestConfigDigest = _configDigestFromConfigData( + block.chainid, + address(this), + s_configCount, + args.signers, + args.transmitters, + args.f, + args.onchainConfig, + args.offchainConfigVersion, + args.offchainConfig + ); + } + s_configInfo.n = uint8(args.signers.length); + + emit ConfigSet( + previousConfigBlockNumber, + s_configInfo.latestConfigDigest, + s_configCount, + args.signers, + args.transmitters, + args.f, + args.onchainConfig, + args.offchainConfigVersion, + args.offchainConfig + ); + } + + function _configDigestFromConfigData( + uint256 _chainId, + address _contractAddress, + uint64 _configCount, + address[] memory _signers, + address[] memory _transmitters, + uint8 _f, + bytes memory _onchainConfig, + uint64 _encodedConfigVersion, + bytes memory _encodedConfig + ) internal pure returns (bytes32) { + uint256 h = uint256( + keccak256( + abi.encode( + _chainId, + _contractAddress, + _configCount, + _signers, + _transmitters, + _f, + _onchainConfig, + _encodedConfigVersion, + _encodedConfig + ) + ) + ); + uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 + uint256 prefix = 0x0001 << (256 - 16); // 0x000100..00 + return bytes32((prefix & prefixMask) | (h & ~prefixMask)); + } + + /** + * @notice information about current offchain reporting protocol configuration + * @return configCount ordinal number of current config, out of all configs applied to this contract so far + * @return blockNumber block at which this config was set + * @return configDigest domain-separation tag for current config (see __configDigestFromConfigData) + */ + function latestConfigDetails() + external + view + override + returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest) + { + return (s_configCount, s_latestConfigBlockNumber, s_configInfo.latestConfigDigest); + } + + /** + * @return list of addresses permitted to transmit reports to this contract + * @dev The list will match the order used to specify the transmitter during setConfig + */ + function transmitters() external view returns (address[] memory) { + return s_transmitters; + } + + function _beforeSetConfig(uint8 _f, bytes memory _onchainConfig) internal virtual; + + /** + * @dev hook called after the report has been fully validated + * for the extending contract to handle additional logic, such as oracle payment + * @param initialGas the amount of gas before validation + * @param transmitter the address of the account that submitted the report + * @param signers the addresses of all signing accounts + * @param report serialized report + */ + function _report( + uint256 initialGas, + address transmitter, + uint8 signerCount, + address[MAX_NUM_ORACLES] memory signers, + bytes calldata report + ) internal virtual; + + // The constant-length components of the msg.data sent to transmit. + // See the "If we wanted to call sam" example on for example reasoning + // https://solidity.readthedocs.io/en/v0.7.2/abi-spec.html + uint16 private constant TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT = + 4 + // function selector + 32 * + 3 + // 3 words containing reportContext + 32 + // word containing start location of abiencoded report value + 32 + // word containing location start of abiencoded rs value + 32 + // word containing start location of abiencoded ss value + 32 + // rawVs value + 32 + // word containing length of report + 32 + // word containing length rs + 32 + // word containing length of ss + 0; // placeholder + + function _requireExpectedMsgDataLength( + bytes calldata report, + bytes32[] calldata rs, + bytes32[] calldata ss + ) private pure { + // calldata will never be big enough to make this overflow + uint256 expected = uint256(TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT) + + report.length + // one byte pure entry in _report + rs.length * + 32 + // 32 bytes per entry in _rs + ss.length * + 32 + // 32 bytes per entry in _ss + 0; // placeholder + if (msg.data.length != expected) revert ReportInvalid("calldata length mismatch"); + } + + /** + * @notice transmit is called to post a new report to the contract + * @param report serialized report, which the signatures are signing. + * @param rs ith element is the R components of the ith signature on report. Must have at most maxNumOracles entries + * @param ss ith element is the S components of the ith signature on report. Must have at most maxNumOracles entries + * @param rawVs ith element is the the V component of the ith signature + */ + function transmit( + // NOTE: If these parameters are changed, expectedMsgDataLength and/or + // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly + bytes32[3] calldata reportContext, + bytes calldata report, + bytes32[] calldata rs, + bytes32[] calldata ss, + bytes32 rawVs // signatures + ) external override { + uint256 initialGas = gasleft(); // This line must come first + + { + // reportContext consists of: + // reportContext[0]: ConfigDigest + // reportContext[1]: 27 byte padding, 4-byte epoch and 1-byte round + // reportContext[2]: ExtraHash + bytes32 configDigest = reportContext[0]; + uint32 epochAndRound = uint32(uint256(reportContext[1])); + + emit Transmitted(configDigest, uint32(epochAndRound >> 8)); + + // The following check is disabled to allow both current and proposed routes to submit reports using the same OCR config digest + // Chainlink Functions uses globally unique request IDs. Metadata about the request is stored and checked in the Coordinator and Router + // require(configInfo.latestConfigDigest == configDigest, "configDigest mismatch"); + + _requireExpectedMsgDataLength(report, rs, ss); + + uint256 expectedNumSignatures = (s_configInfo.n + s_configInfo.f) / 2 + 1; + + if (rs.length != expectedNumSignatures) revert ReportInvalid("wrong number of signatures"); + if (rs.length != ss.length) revert ReportInvalid("report rs and ss must be of equal length"); + + Oracle memory transmitter = s_oracles[msg.sender]; + if (transmitter.role != Role.Transmitter && msg.sender != s_transmitters[transmitter.index]) + revert ReportInvalid("unauthorized transmitter"); + } + + address[MAX_NUM_ORACLES] memory signed; + uint8 signerCount = 0; + + { + // Verify signatures attached to report + bytes32 h = keccak256(abi.encodePacked(keccak256(report), reportContext)); + + Oracle memory o; + // Bounded by MAX_NUM_ORACLES in OCR2Abstract.sol + for (uint256 i = 0; i < rs.length; ++i) { + address signer = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]); + o = s_oracles[signer]; + if (o.role != Role.Signer) revert ReportInvalid("address not authorized to sign"); + if (signed[o.index] != address(0)) revert ReportInvalid("non-unique signature"); + signed[o.index] = signer; + signerCount += 1; + } + } + + _report(initialGas, msg.sender, signerCount, signed, report); + } +} diff --git a/contracts/src/v0.8/interfaces/FeedRegistryInterface.sol b/contracts/src/v0.8/interfaces/FeedRegistryInterface.sol index 1d2367d82a8..f3272174ae2 100644 --- a/contracts/src/v0.8/interfaces/FeedRegistryInterface.sol +++ b/contracts/src/v0.8/interfaces/FeedRegistryInterface.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; pragma abicoder v2; -import {AggregatorV2V3Interface} from "./AggregatorV2V3Interface.sol"; +import {AggregatorV2V3Interface} from "../shared/interfaces/AggregatorV2V3Interface.sol"; interface FeedRegistryInterface { struct Phase { diff --git a/contracts/src/v0.8/l2ep/dev/CrossDomainDelegateForwarder.sol b/contracts/src/v0.8/l2ep/dev/CrossDomainDelegateForwarder.sol index 1eb6cba932d..5dc73619afc 100644 --- a/contracts/src/v0.8/l2ep/dev/CrossDomainDelegateForwarder.sol +++ b/contracts/src/v0.8/l2ep/dev/CrossDomainDelegateForwarder.sol @@ -10,6 +10,4 @@ import {DelegateForwarderInterface} from "./interfaces/DelegateForwarderInterfac * @dev Any other L2 contract which uses this contract's address as a privileged position, * can consider that position to be held by the `l1Owner` */ -abstract contract CrossDomainDelegateForwarder is DelegateForwarderInterface, CrossDomainOwnable { - -} +abstract contract CrossDomainDelegateForwarder is DelegateForwarderInterface, CrossDomainOwnable {} diff --git a/contracts/src/v0.8/l2ep/dev/CrossDomainForwarder.sol b/contracts/src/v0.8/l2ep/dev/CrossDomainForwarder.sol index 1f9066c5054..8f218f66b80 100644 --- a/contracts/src/v0.8/l2ep/dev/CrossDomainForwarder.sol +++ b/contracts/src/v0.8/l2ep/dev/CrossDomainForwarder.sol @@ -10,6 +10,4 @@ import {ForwarderInterface} from "./interfaces/ForwarderInterface.sol"; * @dev Any other L2 contract which uses this contract's address as a privileged position, * can consider that position to be held by the `l1Owner` */ -abstract contract CrossDomainForwarder is ForwarderInterface, CrossDomainOwnable { - -} +abstract contract CrossDomainForwarder is ForwarderInterface, CrossDomainOwnable {} diff --git a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol index 6d8d31a8085..5250fbda278 100644 --- a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol +++ b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.4; import {AddressAliasHelper} from "../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; -import {AggregatorInterface} from "../../../interfaces/AggregatorInterface.sol"; -import {AggregatorV3Interface} from "../../../interfaces/AggregatorV3Interface.sol"; -import {AggregatorV2V3Interface} from "../../../interfaces/AggregatorV2V3Interface.sol"; +import {AggregatorInterface} from "../../../shared/interfaces/AggregatorInterface.sol"; +import {AggregatorV3Interface} from "../../../shared/interfaces/AggregatorV3Interface.sol"; +import {AggregatorV2V3Interface} from "../../../shared/interfaces/AggregatorV2V3Interface.sol"; import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; import {FlagsInterface} from "../interfaces/FlagsInterface.sol"; import {ArbitrumSequencerUptimeFeedInterface} from "../interfaces/ArbitrumSequencerUptimeFeedInterface.sol"; diff --git a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol index 3b5fd277e56..2043ffa6287 100644 --- a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol +++ b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {AggregatorValidatorInterface} from "../../../interfaces/AggregatorValidatorInterface.sol"; +import {AggregatorValidatorInterface} from "../../../shared/interfaces/AggregatorValidatorInterface.sol"; import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; import {SimpleWriteAccessController} from "../../../shared/access/SimpleWriteAccessController.sol"; diff --git a/contracts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol index b522a600bab..fcf6093e3cd 100644 --- a/contracts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol +++ b/contracts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import {AggregatorInterface} from "../../../interfaces/AggregatorInterface.sol"; -import {AggregatorV3Interface} from "../../../interfaces/AggregatorV3Interface.sol"; -import {AggregatorV2V3Interface} from "../../../interfaces/AggregatorV2V3Interface.sol"; +import {AggregatorInterface} from "../../../shared/interfaces/AggregatorInterface.sol"; +import {AggregatorV3Interface} from "../../../shared/interfaces/AggregatorV3Interface.sol"; +import {AggregatorV2V3Interface} from "../../../shared/interfaces/AggregatorV2V3Interface.sol"; import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; import {OptimismSequencerUptimeFeedInterface} from "./../interfaces/OptimismSequencerUptimeFeedInterface.sol"; import {SimpleReadAccessController} from "../../../shared/access/SimpleReadAccessController.sol"; diff --git a/contracts/src/v0.8/l2ep/dev/optimism/OptimismValidator.sol b/contracts/src/v0.8/l2ep/dev/optimism/OptimismValidator.sol index a955b7e92ca..e41c61a4536 100644 --- a/contracts/src/v0.8/l2ep/dev/optimism/OptimismValidator.sol +++ b/contracts/src/v0.8/l2ep/dev/optimism/OptimismValidator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {AggregatorValidatorInterface} from "../../../interfaces/AggregatorValidatorInterface.sol"; +import {AggregatorValidatorInterface} from "../../../shared/interfaces/AggregatorValidatorInterface.sol"; import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; import {OptimismSequencerUptimeFeedInterface} from "./../interfaces/OptimismSequencerUptimeFeedInterface.sol"; diff --git a/contracts/src/v0.8/llo-feeds/FeeManager.sol b/contracts/src/v0.8/llo-feeds/FeeManager.sol index 0cc8273442d..c9981045a4a 100644 --- a/contracts/src/v0.8/llo-feeds/FeeManager.sol +++ b/contracts/src/v0.8/llo-feeds/FeeManager.sol @@ -4,13 +4,13 @@ pragma solidity 0.8.16; import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; import {IFeeManager} from "./interfaces/IFeeManager.sol"; import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; -import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol"; -import {Common} from "../libraries/Common.sol"; +import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "./libraries/Common.sol"; import {IRewardManager} from "./interfaces/IRewardManager.sol"; import {IWERC20} from "../shared/interfaces/IWERC20.sol"; -import {IERC20} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC20.sol"; -import {Math} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/Math.sol"; -import {SafeERC20} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol"; +import {Math} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/Math.sol"; +import {SafeERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol"; /** diff --git a/contracts/src/v0.8/llo-feeds/RewardManager.sol b/contracts/src/v0.8/llo-feeds/RewardManager.sol index e064bff8b67..596755142e8 100644 --- a/contracts/src/v0.8/llo-feeds/RewardManager.sol +++ b/contracts/src/v0.8/llo-feeds/RewardManager.sol @@ -3,10 +3,10 @@ pragma solidity 0.8.16; import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; import {IRewardManager} from "./interfaces/IRewardManager.sol"; -import {IERC20} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC20.sol"; +import {IERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol"; import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; -import {Common} from "../libraries/Common.sol"; -import {SafeERC20} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Common} from "./libraries/Common.sol"; +import {SafeERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; /** * @title RewardManager diff --git a/contracts/src/v0.8/llo-feeds/Verifier.sol b/contracts/src/v0.8/llo-feeds/Verifier.sol index cd7e3d2bad6..3e668c09ff0 100644 --- a/contracts/src/v0.8/llo-feeds/Verifier.sol +++ b/contracts/src/v0.8/llo-feeds/Verifier.sol @@ -5,8 +5,8 @@ import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; import {IVerifier} from "./interfaces/IVerifier.sol"; import {IVerifierProxy} from "./interfaces/IVerifierProxy.sol"; import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; -import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol"; -import {Common} from "../libraries/Common.sol"; +import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "./libraries/Common.sol"; // OCR2 standard uint256 constant MAX_NUM_ORACLES = 31; diff --git a/contracts/src/v0.8/llo-feeds/VerifierProxy.sol b/contracts/src/v0.8/llo-feeds/VerifierProxy.sol index 989c5c6b81a..a35c54573c1 100644 --- a/contracts/src/v0.8/llo-feeds/VerifierProxy.sol +++ b/contracts/src/v0.8/llo-feeds/VerifierProxy.sol @@ -6,9 +6,9 @@ import {IVerifierProxy} from "./interfaces/IVerifierProxy.sol"; import {IVerifier} from "./interfaces/IVerifier.sol"; import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; import {AccessControllerInterface} from "../shared/interfaces/AccessControllerInterface.sol"; -import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol"; +import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol"; -import {Common} from "../libraries/Common.sol"; +import {Common} from "./libraries/Common.sol"; /** * The verifier proxy contract is the gateway for all report verification requests diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IFeeManager.sol b/contracts/src/v0.8/llo-feeds/interfaces/IFeeManager.sol index ed7213870e2..e006f0254eb 100644 --- a/contracts/src/v0.8/llo-feeds/interfaces/IFeeManager.sol +++ b/contracts/src/v0.8/llo-feeds/interfaces/IFeeManager.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.16; -import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol"; -import {Common} from "../../libraries/Common.sol"; +import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../libraries/Common.sol"; import {IVerifierFeeManager} from "./IVerifierFeeManager.sol"; interface IFeeManager is IERC165, IVerifierFeeManager { diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol b/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol index 68cecc4f97a..7a4d4216715 100644 --- a/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol +++ b/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.16; -import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol"; -import {Common} from "../../libraries/Common.sol"; +import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../libraries/Common.sol"; interface IRewardManager is IERC165 { /** diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol b/contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol index 7617d9a5c35..9e1e6d314cd 100644 --- a/contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol +++ b/contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.16; -import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol"; -import {Common} from "../../libraries/Common.sol"; +import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../libraries/Common.sol"; interface IVerifier is IERC165 { /** diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol b/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol index 8e490d39d1e..323b8a2cf00 100644 --- a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol +++ b/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.16; -import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol"; -import {Common} from "../../libraries/Common.sol"; +import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../libraries/Common.sol"; interface IVerifierFeeManager is IERC165 { /** diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierProxy.sol b/contracts/src/v0.8/llo-feeds/interfaces/IVerifierProxy.sol index c2665261e9a..d86bb46dd9c 100644 --- a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierProxy.sol +++ b/contracts/src/v0.8/llo-feeds/interfaces/IVerifierProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.16; -import {Common} from "../../libraries/Common.sol"; +import {Common} from "../libraries/Common.sol"; import {AccessControllerInterface} from "../../shared/interfaces/AccessControllerInterface.sol"; import {IVerifierFeeManager} from "./IVerifierFeeManager.sol"; diff --git a/contracts/src/v0.8/libraries/ByteUtil.sol b/contracts/src/v0.8/llo-feeds/libraries/ByteUtil.sol similarity index 100% rename from contracts/src/v0.8/libraries/ByteUtil.sol rename to contracts/src/v0.8/llo-feeds/libraries/ByteUtil.sol diff --git a/contracts/src/v0.8/libraries/Common.sol b/contracts/src/v0.8/llo-feeds/libraries/Common.sol similarity index 100% rename from contracts/src/v0.8/libraries/Common.sol rename to contracts/src/v0.8/llo-feeds/libraries/Common.sol diff --git a/contracts/src/v0.8/libraries/test/ByteUtilTest.t.sol b/contracts/src/v0.8/llo-feeds/test/ByteUtilTest.t.sol similarity index 99% rename from contracts/src/v0.8/libraries/test/ByteUtilTest.t.sol rename to contracts/src/v0.8/llo-feeds/test/ByteUtilTest.t.sol index 0629d0235ee..b4e87364ac9 100644 --- a/contracts/src/v0.8/libraries/test/ByteUtilTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/ByteUtilTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.16; import {Test} from "forge-std/Test.sol"; -import {ByteUtil} from "../ByteUtil.sol"; +import {ByteUtil} from "../libraries/ByteUtil.sol"; contract ByteUtilTest is Test { using ByteUtil for bytes; diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/BaseFeeManager.t.sol b/contracts/src/v0.8/llo-feeds/test/fee-manager/BaseFeeManager.t.sol index c446150406e..db0b3d8b3d9 100644 --- a/contracts/src/v0.8/llo-feeds/test/fee-manager/BaseFeeManager.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/fee-manager/BaseFeeManager.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.16; import {Test} from "forge-std/Test.sol"; import {FeeManager} from "../../FeeManager.sol"; import {RewardManager} from "../../RewardManager.sol"; -import {Common} from "../../../libraries/Common.sol"; -import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/mocks/ERC20Mock.sol"; +import {Common} from "../../libraries/Common.sol"; +import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; import {WERC20Mock} from "../../../shared/mocks/WERC20Mock.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; import {FeeManagerProxy} from "../mocks/FeeManagerProxy.sol"; diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.getFeeAndReward.t.sol b/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.getFeeAndReward.t.sol index 801a1e39925..6a24806353d 100644 --- a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.getFeeAndReward.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.getFeeAndReward.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.16; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; import "./BaseFeeManager.t.sol"; /** diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFee.t.sol b/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFee.t.sol index 9c2bd711ed5..e0093b88a4f 100644 --- a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFee.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFee.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.16; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; import "./BaseFeeManager.t.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; diff --git a/contracts/src/v0.8/llo-feeds/test/gas/Gas_VerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/test/gas/Gas_VerifierTest.t.sol index 3198576e8aa..6938437b013 100644 --- a/contracts/src/v0.8/llo-feeds/test/gas/Gas_VerifierTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/gas/Gas_VerifierTest.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.16; import {BaseTest, BaseTestWithConfiguredVerifierAndFeeManager} from "../verifier/BaseVerifierTest.t.sol"; import {SimpleWriteAccessController} from "../../../shared/access/SimpleWriteAccessController.sol"; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; contract Verifier_setConfig is BaseTest { diff --git a/contracts/src/v0.8/llo-feeds/test/mocks/ErroredVerifier.sol b/contracts/src/v0.8/llo-feeds/test/mocks/ErroredVerifier.sol index a0a404d88d7..770b7b809d0 100644 --- a/contracts/src/v0.8/llo-feeds/test/mocks/ErroredVerifier.sol +++ b/contracts/src/v0.8/llo-feeds/test/mocks/ErroredVerifier.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.16; import {IVerifier} from "../../interfaces/IVerifier.sol"; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; contract ErroredVerifier is IVerifier { function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/BaseRewardManager.t.sol b/contracts/src/v0.8/llo-feeds/test/reward-manager/BaseRewardManager.t.sol index 4a701093168..a9953d73c74 100644 --- a/contracts/src/v0.8/llo-feeds/test/reward-manager/BaseRewardManager.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/reward-manager/BaseRewardManager.t.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.16; import {Test} from "forge-std/Test.sol"; -import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/mocks/ERC20Mock.sol"; +import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; import {RewardManager} from "../../RewardManager.sol"; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; /** diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.claim.t.sol b/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.claim.t.sol index 9a3749d1dde..a6c98c03031 100644 --- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.claim.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.claim.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.16; import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; /** * @title BaseRewardManagerTest diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.general.t.sol b/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.general.t.sol index 9cee5b05c57..e7bc43dc81d 100644 --- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.general.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.general.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.16; import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; import {RewardManager} from "../../RewardManager.sol"; -import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/mocks/ERC20Mock.sol"; +import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; /** diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.setRecipients.t.sol b/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.setRecipients.t.sol index 0e45ba00da4..a8cf6260f5c 100644 --- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.setRecipients.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.setRecipients.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.16; import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; /** * @title BaseRewardManagerTest diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.updateRewardRecipients.t.sol b/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.updateRewardRecipients.t.sol index 4b3063ac016..b1836e0fb93 100644 --- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.updateRewardRecipients.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.updateRewardRecipients.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.16; import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; /** * @title BaseRewardManagerTest diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/BaseVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/test/verifier/BaseVerifierTest.t.sol index 3faa65fb52b..34e2090115d 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/BaseVerifierTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/verifier/BaseVerifierTest.t.sol @@ -3,15 +3,15 @@ pragma solidity 0.8.16; import {Test} from "forge-std/Test.sol"; import {VerifierProxy} from "../../VerifierProxy.sol"; -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; import {IVerifier} from "../../interfaces/IVerifier.sol"; import {ErroredVerifier} from "../mocks/ErroredVerifier.sol"; import {Verifier} from "../../Verifier.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; import {FeeManager} from "../../FeeManager.sol"; -import {Common} from "../../../libraries/Common.sol"; -import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/mocks/ERC20Mock.sol"; +import {Common} from "../../libraries/Common.sol"; +import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; import {WERC20Mock} from "../../../shared/mocks/WERC20Mock.sol"; import {FeeManager} from "../../FeeManager.sol"; import {RewardManager} from "../../RewardManager.sol"; diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetVerifierTest.t.sol index 3699aaf420d..17fc49979e7 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetVerifierTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetVerifierTest.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.16; import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; import {IVerifier} from "../../interfaces/IVerifier.sol"; import {VerifierProxy} from "../../VerifierProxy.sol"; -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol"; -import {Common} from "../../../libraries/Common.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../../libraries/Common.sol"; contract VerifierProxyInitializeVerifierTest is BaseTestWithConfiguredVerifierAndFeeManager { function test_revertsIfNotCorrectVerifier() public { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigFromSourceTest.t.sol b/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigFromSourceTest.t.sol index 6c5eac9b6dd..ba3acce0eec 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigFromSourceTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigFromSourceTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.16; import {BaseTest, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol"; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; contract VerifierSetConfigFromSourceTest is BaseTest { function setUp() public virtual override { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigTest.t.sol index f0b045e7f30..374a976786b 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigTest.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.16; import {BaseTest, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol"; import {Verifier} from "../../Verifier.sol"; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; contract VerifierSetConfigTest is BaseTest { function setUp() public virtual override { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierVerifyTest.t.sol b/contracts/src/v0.8/llo-feeds/test/verifier/VerifierVerifyTest.t.sol index b4fcac75d3a..34e02bcfb95 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierVerifyTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/test/verifier/VerifierVerifyTest.t.sol @@ -5,7 +5,7 @@ import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t. import {Verifier} from "../../Verifier.sol"; import {VerifierProxy} from "../../VerifierProxy.sol"; import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; -import {Common} from "../../../libraries/Common.sol"; +import {Common} from "../../libraries/Common.sol"; contract VerifierVerifyTest is BaseTestWithConfiguredVerifierAndFeeManager { bytes32[3] internal s_reportContext; diff --git a/contracts/src/v0.8/mocks/MockAggregatorValidator.sol b/contracts/src/v0.8/mocks/MockAggregatorValidator.sol index a43236de9ff..bdc935cd231 100644 --- a/contracts/src/v0.8/mocks/MockAggregatorValidator.sol +++ b/contracts/src/v0.8/mocks/MockAggregatorValidator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "../interfaces/AggregatorValidatorInterface.sol"; +import "../shared/interfaces/AggregatorValidatorInterface.sol"; contract MockAggregatorValidator is AggregatorValidatorInterface { uint8 immutable id; diff --git a/contracts/src/v0.8/operatorforwarder/dev/AuthorizedReceiver.sol b/contracts/src/v0.8/operatorforwarder/dev/AuthorizedReceiver.sol index 04d2635d583..bc5f1c0e7e4 100644 --- a/contracts/src/v0.8/operatorforwarder/dev/AuthorizedReceiver.sol +++ b/contracts/src/v0.8/operatorforwarder/dev/AuthorizedReceiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {AuthorizedReceiverInterface} from "../../interfaces/AuthorizedReceiverInterface.sol"; +import {AuthorizedReceiverInterface} from "./interfaces/AuthorizedReceiverInterface.sol"; // solhint-disable custom-errors abstract contract AuthorizedReceiver is AuthorizedReceiverInterface { diff --git a/contracts/src/v0.8/operatorforwarder/dev/Operator.sol b/contracts/src/v0.8/operatorforwarder/dev/Operator.sol index b68e2837304..c8451bf03ce 100644 --- a/contracts/src/v0.8/operatorforwarder/dev/Operator.sol +++ b/contracts/src/v0.8/operatorforwarder/dev/Operator.sol @@ -5,13 +5,13 @@ import {AuthorizedReceiver} from "./AuthorizedReceiver.sol"; import {LinkTokenReceiver} from "./LinkTokenReceiver.sol"; import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol"; -import {AuthorizedReceiverInterface} from "../../interfaces/AuthorizedReceiverInterface.sol"; +import {AuthorizedReceiverInterface} from "./interfaces/AuthorizedReceiverInterface.sol"; import {OperatorInterface} from "../../interfaces/OperatorInterface.sol"; import {IOwnable} from "../../shared/interfaces/IOwnable.sol"; import {WithdrawalInterface} from "./interfaces/WithdrawalInterface.sol"; import {OracleInterface} from "../../interfaces/OracleInterface.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -import {SafeCast} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/SafeCast.sol"; +import {SafeCast} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SafeCast.sol"; // @title The Chainlink Operator contract // @notice Node operators can deploy this contract to fulfill requests sent to them diff --git a/contracts/src/v0.8/interfaces/AuthorizedReceiverInterface.sol b/contracts/src/v0.8/operatorforwarder/dev/interfaces/AuthorizedReceiverInterface.sol similarity index 100% rename from contracts/src/v0.8/interfaces/AuthorizedReceiverInterface.sol rename to contracts/src/v0.8/operatorforwarder/dev/interfaces/AuthorizedReceiverInterface.sol diff --git a/contracts/src/v0.8/shared/access/ConfirmedOwner.sol b/contracts/src/v0.8/shared/access/ConfirmedOwner.sol index a25d92ffd67..5b0c1593ccc 100644 --- a/contracts/src/v0.8/shared/access/ConfirmedOwner.sol +++ b/contracts/src/v0.8/shared/access/ConfirmedOwner.sol @@ -3,10 +3,8 @@ pragma solidity ^0.8.0; import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol"; -/** - * @title The ConfirmedOwner contract - * @notice A contract with helpers for basic contract ownership. - */ +/// @title The ConfirmedOwner contract +/// @notice A contract with helpers for basic contract ownership. contract ConfirmedOwner is ConfirmedOwnerWithProposal { constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {} } diff --git a/contracts/src/v0.8/shared/access/ConfirmedOwnerWithProposal.sol b/contracts/src/v0.8/shared/access/ConfirmedOwnerWithProposal.sol index a6757c38869..7b68418754a 100644 --- a/contracts/src/v0.8/shared/access/ConfirmedOwnerWithProposal.sol +++ b/contracts/src/v0.8/shared/access/ConfirmedOwnerWithProposal.sol @@ -3,10 +3,8 @@ pragma solidity ^0.8.0; import {IOwnable} from "../interfaces/IOwnable.sol"; -/** - * @title The ConfirmedOwner contract - * @notice A contract with helpers for basic contract ownership. - */ +/// @title The ConfirmedOwner contract +/// @notice A contract with helpers for basic contract ownership. contract ConfirmedOwnerWithProposal is IOwnable { address private s_owner; address private s_pendingOwner; @@ -24,17 +22,12 @@ contract ConfirmedOwnerWithProposal is IOwnable { } } - /** - * @notice Allows an owner to begin transferring ownership to a new address, - * pending. - */ + /// @notice Allows an owner to begin transferring ownership to a new address. function transferOwnership(address to) public override onlyOwner { _transferOwnership(to); } - /** - * @notice Allows an ownership transfer to be completed by the recipient. - */ + /// @notice Allows an ownership transfer to be completed by the recipient. function acceptOwnership() external override { // solhint-disable-next-line custom-errors require(msg.sender == s_pendingOwner, "Must be proposed owner"); @@ -46,16 +39,12 @@ contract ConfirmedOwnerWithProposal is IOwnable { emit OwnershipTransferred(oldOwner, msg.sender); } - /** - * @notice Get the current owner - */ + /// @notice Get the current owner function owner() public view override returns (address) { return s_owner; } - /** - * @notice validate, transfer ownership, and emit relevant events - */ + /// @notice validate, transfer ownership, and emit relevant events function _transferOwnership(address to) private { // solhint-disable-next-line custom-errors require(to != msg.sender, "Cannot transfer to self"); @@ -65,17 +54,13 @@ contract ConfirmedOwnerWithProposal is IOwnable { emit OwnershipTransferRequested(s_owner, to); } - /** - * @notice validate access - */ + /// @notice validate access function _validateOwnership() internal view { // solhint-disable-next-line custom-errors require(msg.sender == s_owner, "Only callable by owner"); } - /** - * @notice Reverts if called by anyone other than the contract owner. - */ + /// @notice Reverts if called by anyone other than the contract owner. modifier onlyOwner() { _validateOwnership(); _; diff --git a/contracts/src/v0.8/shared/access/SimpleReadAccessController.sol b/contracts/src/v0.8/shared/access/SimpleReadAccessController.sol index b36fa4e4b60..f4ea905bf9d 100644 --- a/contracts/src/v0.8/shared/access/SimpleReadAccessController.sol +++ b/contracts/src/v0.8/shared/access/SimpleReadAccessController.sol @@ -3,22 +3,18 @@ pragma solidity ^0.8.0; import {SimpleWriteAccessController} from "./SimpleWriteAccessController.sol"; -/** - * @title SimpleReadAccessController - * @notice Gives access to: - * - any externally owned account (note that off-chain actors can always read - * any contract storage regardless of on-chain access control measures, so this - * does not weaken the access control while improving usability) - * - accounts explicitly added to an access list - * @dev SimpleReadAccessController is not suitable for access controlling writes - * since it grants any externally owned account access! See - * SimpleWriteAccessController for that. - */ +/// @title SimpleReadAccessController +/// @notice Gives access to: +/// - any externally owned account (note that off-chain actors can always read +/// any contract storage regardless of on-chain access control measures, so this +/// does not weaken the access control while improving usability) +/// - accounts explicitly added to an access list +/// @dev SimpleReadAccessController is not suitable for access controlling writes +/// since it grants any externally owned account access! See +/// SimpleWriteAccessController for that. contract SimpleReadAccessController is SimpleWriteAccessController { - /** - * @notice Returns the access of an address - * @param _user The address to query - */ + /// @notice Returns the access of an address + /// @param _user The address to query function hasAccess(address _user, bytes memory _calldata) public view virtual override returns (bool) { // solhint-disable-next-line avoid-tx-origin return super.hasAccess(_user, _calldata) || _user == tx.origin; diff --git a/contracts/src/v0.8/shared/access/SimpleWriteAccessController.sol b/contracts/src/v0.8/shared/access/SimpleWriteAccessController.sol index 117d2e77dd6..b431331bc84 100644 --- a/contracts/src/v0.8/shared/access/SimpleWriteAccessController.sol +++ b/contracts/src/v0.8/shared/access/SimpleWriteAccessController.sol @@ -4,13 +4,9 @@ pragma solidity ^0.8.0; import {ConfirmedOwner} from "./ConfirmedOwner.sol"; import {AccessControllerInterface} from "../interfaces/AccessControllerInterface.sol"; -/** - * @title SimpleWriteAccessController - * @notice Gives access to accounts explicitly added to an access list by the - * controller's owner. - * @dev does not make any special permissions for externally, see - * SimpleReadAccessController for that. - */ +/// @title SimpleWriteAccessController +/// @notice Gives access to accounts explicitly added to an access list by the controller's owner. +/// @dev does not make any special permissions for externally, see SimpleReadAccessController for that. contract SimpleWriteAccessController is AccessControllerInterface, ConfirmedOwner { bool public checkEnabled; mapping(address => bool) internal s_accessList; @@ -24,18 +20,14 @@ contract SimpleWriteAccessController is AccessControllerInterface, ConfirmedOwne checkEnabled = true; } - /** - * @notice Returns the access of an address - * @param _user The address to query - */ + /// @notice Returns the access of an address + /// @param _user The address to query function hasAccess(address _user, bytes memory) public view virtual override returns (bool) { return s_accessList[_user] || !checkEnabled; } - /** - * @notice Adds an address to the access list - * @param _user The address to add - */ + /// @notice Adds an address to the access list + /// @param _user The address to add function addAccess(address _user) external onlyOwner { if (!s_accessList[_user]) { s_accessList[_user] = true; @@ -44,10 +36,8 @@ contract SimpleWriteAccessController is AccessControllerInterface, ConfirmedOwne } } - /** - * @notice Removes an address from the access list - * @param _user The address to remove - */ + /// @notice Removes an address from the access list + /// @param _user The address to remove function removeAccess(address _user) external onlyOwner { if (s_accessList[_user]) { s_accessList[_user] = false; @@ -56,9 +46,7 @@ contract SimpleWriteAccessController is AccessControllerInterface, ConfirmedOwne } } - /** - * @notice makes the access check enforced - */ + /// @notice makes the access check enforced function enableAccessCheck() external onlyOwner { if (!checkEnabled) { checkEnabled = true; @@ -67,9 +55,7 @@ contract SimpleWriteAccessController is AccessControllerInterface, ConfirmedOwne } } - /** - * @notice makes the access check unenforced - */ + /// @notice makes the access check unenforced function disableAccessCheck() external onlyOwner { if (checkEnabled) { checkEnabled = false; @@ -78,9 +64,7 @@ contract SimpleWriteAccessController is AccessControllerInterface, ConfirmedOwne } } - /** - * @dev reverts if the caller does not have access - */ + /// @dev reverts if the caller does not have access modifier checkAccess() { // solhint-disable-next-line custom-errors require(hasAccess(msg.sender, msg.data), "No access"); diff --git a/contracts/src/v0.8/shared/call/CallWithExactGas.sol b/contracts/src/v0.8/shared/call/CallWithExactGas.sol new file mode 100644 index 00000000000..6716dc15953 --- /dev/null +++ b/contracts/src/v0.8/shared/call/CallWithExactGas.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice This library contains various callWithExactGas functions. All of them are +/// safe from gas bomb attacks. +/// @dev There is code duplication in this library. This is done to not leave the assembly +/// the blocks. +library CallWithExactGas { + error NoContract(); + error NoGasForCallExactCheck(); + error NotEnoughGasForCall(); + + bytes4 internal constant NO_CONTRACT_SIG = 0x0c3b563c; + bytes4 internal constant NO_GAS_FOR_CALL_EXACT_CHECK_SIG = 0xafa32a2c; + bytes4 internal constant NOT_ENOUGH_GAS_FOR_CALL_SIG = 0x37c3be29; + + /// @notice calls target address with exactly gasAmount gas and payload as calldata. + /// Accounts for gasForCallExactCheck gas that will be used by this function. Will revert + /// if the target is not a contact. Will revert when there is not enough gas to call the + /// target with gasAmount gas. + /// @dev Ignores the return data, which makes it immune to gas bomb attacks. + /// @return success whether the call succeeded + function _callWithExactGas( + bytes memory payload, + address target, + uint256 gasLimit, + uint16 gasForCallExactCheck + ) internal returns (bool success) { + assembly { + // solidity calls check that a contract actually exists at the destination, so we do the same + // Note we do this check prior to measuring gas so gasForCallExactCheck (our "cushion") + // doesn't need to account for it. + if iszero(extcodesize(target)) { + mstore(0x0, NO_CONTRACT_SIG) + revert(0x0, 0x4) + } + + let g := gas() + // Compute g -= gasForCallExactCheck and check for underflow + // The gas actually passed to the callee is _min(gasAmount, 63//64*gas available). + // We want to ensure that we revert if gasAmount > 63//64*gas available + // as we do not want to provide them with less, however that check itself costs + // gas. gasForCallExactCheck ensures we have at least enough gas to be able + // to revert if gasAmount > 63//64*gas available. + if lt(g, gasForCallExactCheck) { + mstore(0x0, NO_GAS_FOR_CALL_EXACT_CHECK_SIG) + revert(0x0, 0x4) + } + g := sub(g, gasForCallExactCheck) + // if g - g//64 <= gasAmount, revert. We subtract g//64 because of EIP-150 + if iszero(gt(sub(g, div(g, 64)), gasLimit)) { + mstore(0x0, NOT_ENOUGH_GAS_FOR_CALL_SIG) + revert(0x0, 0x4) + } + + // call and return whether we succeeded. ignore return data + // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength) + success := call(gasLimit, target, 0, add(payload, 0x20), mload(payload), 0x0, 0x0) + } + return success; + } + + /// @notice calls target address with exactly gasAmount gas and payload as calldata. + /// Account for gasForCallExactCheck gas that will be used by this function. Will revert + /// if the target is not a contact. Will revert when there is not enough gas to call the + /// target with gasAmount gas. + /// @dev Caps the return data length, which makes it immune to gas bomb attacks. + /// @dev Return data cap logic borrowed from + /// https://github.com/nomad-xyz/ExcessivelySafeCall/blob/main/src/ExcessivelySafeCall.sol. + /// @return success whether the call succeeded + /// @return retData the return data from the call, capped at maxReturnBytes bytes + /// @return gasUsed the gas used by the external call. Does not include the overhead of this function. + function _callWithExactGasSafeReturnData( + bytes memory payload, + address target, + uint256 gasLimit, + uint16 gasForCallExactCheck, + uint16 maxReturnBytes + ) internal returns (bool success, bytes memory retData, uint256 gasUsed) { + // allocate retData memory ahead of time + retData = new bytes(maxReturnBytes); + + assembly { + // solidity calls check that a contract actually exists at the destination, so we do the same + // Note we do this check prior to measuring gas so gasForCallExactCheck (our "cushion") + // doesn't need to account for it. + if iszero(extcodesize(target)) { + mstore(0x0, NO_CONTRACT_SIG) + revert(0x0, 0x4) + } + + let g := gas() + // Compute g -= gasForCallExactCheck and check for underflow + // The gas actually passed to the callee is _min(gasAmount, 63//64*gas available). + // We want to ensure that we revert if gasAmount > 63//64*gas available + // as we do not want to provide them with less, however that check itself costs + // gas. gasForCallExactCheck ensures we have at least enough gas to be able + // to revert if gasAmount > 63//64*gas available. + if lt(g, gasForCallExactCheck) { + mstore(0x0, NO_GAS_FOR_CALL_EXACT_CHECK_SIG) + revert(0x0, 0x4) + } + g := sub(g, gasForCallExactCheck) + // if g - g//64 <= gasAmount, revert. We subtract g//64 because of EIP-150 + if iszero(gt(sub(g, div(g, 64)), gasLimit)) { + mstore(0x0, NOT_ENOUGH_GAS_FOR_CALL_SIG) + revert(0x0, 0x4) + } + + // We save the gas before the call so we can calculate how much gas the call used + let gasBeforeCall := gas() + // call and return whether we succeeded. ignore return data + // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength) + success := call(gasLimit, target, 0, add(payload, 0x20), mload(payload), 0x0, 0x0) + gasUsed := sub(gasBeforeCall, gas()) + + // limit our copy to maxReturnBytes bytes + let toCopy := returndatasize() + if gt(toCopy, maxReturnBytes) { + toCopy := maxReturnBytes + } + // Store the length of the copied bytes + mstore(retData, toCopy) + // copy the bytes from retData[0:_toCopy] + returndatacopy(add(retData, 0x20), 0x0, toCopy) + } + return (success, retData, gasUsed); + } + + /// @notice Calls target address with exactly gasAmount gas and payload as calldata + /// or reverts if at least gasLimit gas is not available. + /// @dev Does not check if target is a contract. If it is not a contract, the low-level + /// call will still be made and it will succeed. + /// @dev Ignores the return data, which makes it immune to gas bomb attacks. + /// @return success whether the call succeeded + /// @return sufficientGas Whether there was enough gas to make the call + function _callWithExactGasEvenIfTargetIsNoContract( + bytes memory payload, + address target, + uint256 gasLimit, + uint16 gasForCallExactCheck + ) internal returns (bool success, bool sufficientGas) { + assembly { + let g := gas() + // Compute g -= CALL_WITH_EXACT_GAS_CUSHION and check for underflow. We + // need the cushion since the logic following the above call to gas also + // costs gas which we cannot account for exactly. So cushion is a + // conservative upper bound for the cost of this logic. + if iszero(lt(g, gasForCallExactCheck)) { + g := sub(g, gasForCallExactCheck) + // If g - g//64 <= gasAmount, we don't have enough gas. We subtract g//64 because of EIP-150. + if gt(sub(g, div(g, 64)), gasLimit) { + // Call and ignore success/return data. Note that we did not check + // whether a contract actually exists at the target address. + success := call(gasLimit, target, 0, add(payload, 0x20), mload(payload), 0x0, 0x0) + sufficientGas := true + } + } + } + return (success, sufficientGas); + } +} diff --git a/contracts/src/v0.8/interfaces/AggregatorInterface.sol b/contracts/src/v0.8/shared/interfaces/AggregatorInterface.sol similarity index 100% rename from contracts/src/v0.8/interfaces/AggregatorInterface.sol rename to contracts/src/v0.8/shared/interfaces/AggregatorInterface.sol diff --git a/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol b/contracts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol similarity index 100% rename from contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol rename to contracts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol diff --git a/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol b/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol similarity index 100% rename from contracts/src/v0.8/interfaces/AggregatorV3Interface.sol rename to contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol diff --git a/contracts/src/v0.8/interfaces/AggregatorValidatorInterface.sol b/contracts/src/v0.8/shared/interfaces/AggregatorValidatorInterface.sol similarity index 100% rename from contracts/src/v0.8/interfaces/AggregatorValidatorInterface.sol rename to contracts/src/v0.8/shared/interfaces/AggregatorValidatorInterface.sol diff --git a/contracts/src/v0.8/shared/mocks/WERC20Mock.sol b/contracts/src/v0.8/shared/mocks/WERC20Mock.sol index 02c13be9937..cee7fa7ff83 100644 --- a/contracts/src/v0.8/shared/mocks/WERC20Mock.sol +++ b/contracts/src/v0.8/shared/mocks/WERC20Mock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {ERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/ERC20.sol"; +import {ERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol"; contract WERC20Mock is ERC20 { constructor() ERC20("WERC20Mock", "WERC") {} diff --git a/contracts/src/v0.8/shared/ocr2/OCR2Abstract.sol b/contracts/src/v0.8/shared/ocr2/OCR2Abstract.sol index 1f19d0de54e..cd3f197113f 100644 --- a/contracts/src/v0.8/shared/ocr2/OCR2Abstract.sol +++ b/contracts/src/v0.8/shared/ocr2/OCR2Abstract.sol @@ -4,26 +4,20 @@ pragma solidity ^0.8.0; import {ITypeAndVersion} from "../interfaces/ITypeAndVersion.sol"; abstract contract OCR2Abstract is ITypeAndVersion { - // Maximum number of oracles the offchain reporting protocol is designed for - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - uint256 internal constant maxNumOracles = 31; - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - uint256 private constant prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - uint256 private constant prefix = 0x0001 << (256 - 16); // 0x000100..00 + uint256 internal constant MAX_NUM_ORACLES = 31; + uint256 private constant PREFIX_MASK = type(uint256).max << (256 - 16); // 0xFFFF00..00 + uint256 private constant PREFIX = 0x0001 << (256 - 16); // 0x000100..00 - /** - * @notice triggers a new run of the offchain reporting protocol - * @param previousConfigBlockNumber block in which the previous config was set, to simplify historic analysis - * @param configDigest configDigest of this configuration - * @param configCount ordinal number of this config setting among all config settings over the life of this contract - * @param signers ith element is address ith oracle uses to sign a report - * @param transmitters ith element is address ith oracle uses to transmit a report via the transmit method - * @param f maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly - * @param onchainConfig serialized configuration used by the contract (and possibly oracles) - * @param offchainConfigVersion version of the serialization format used for "offchainConfig" parameter - * @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract - */ + /// @notice triggers a new run of the offchain reporting protocol + /// @param previousConfigBlockNumber block in which the previous config was set, to simplify historic analysis + /// @param configDigest configDigest of this configuration + /// @param configCount ordinal number of this config setting among all config settings over the life of this contract + /// @param signers ith element is address ith oracle uses to sign a report + /// @param transmitters ith element is address ith oracle uses to transmit a report via the transmit method + /// @param f maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly + /// @param onchainConfig serialized configuration used by the contract (and possibly oracles) + /// @param offchainConfigVersion version of the serialization format used for "offchainConfig" parameter + /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract event ConfigSet( uint32 previousConfigBlockNumber, bytes32 configDigest, @@ -36,15 +30,13 @@ abstract contract OCR2Abstract is ITypeAndVersion { bytes offchainConfig ); - /** - * @notice sets offchain reporting protocol configuration incl. participating oracles - * @param signers addresses with which oracles sign the reports - * @param transmitters addresses oracles use to transmit the reports - * @param f number of faulty oracles the system can tolerate - * @param onchainConfig serialized configuration used by the contract (and possibly oracles) - * @param offchainConfigVersion version number for offchainEncoding schema - * @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract - */ + /// @notice sets offchain reporting protocol configuration incl. participating oracles + /// @param signers addresses with which oracles sign the reports + /// @param transmitters addresses oracles use to transmit the reports + /// @param f number of faulty oracles the system can tolerate + /// @param onchainConfig serialized configuration used by the contract (and possibly oracles) + /// @param offchainConfigVersion version number for offchainEncoding schema + /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract function setConfig( address[] memory signers, address[] memory transmitters, @@ -54,12 +46,10 @@ abstract contract OCR2Abstract is ITypeAndVersion { bytes memory offchainConfig ) external virtual; - /** - * @notice information about current offchain reporting protocol configuration - * @return configCount ordinal number of current config, out of all configs applied to this contract so far - * @return blockNumber block at which this config was set - * @return configDigest domain-separation tag for current config (see _configDigestFromConfigData) - */ + /// @notice information about current offchain reporting protocol configuration + /// @return configCount ordinal number of current config, out of all configs applied to this contract so far + /// @return blockNumber block at which this config was set + /// @return configDigest domain-separation tag for current config (see _configDigestFromConfigData) function latestConfigDetails() external view @@ -92,40 +82,34 @@ abstract contract OCR2Abstract is ITypeAndVersion { ) ) ); - return bytes32((prefix & prefixMask) | (h & ~prefixMask)); + return bytes32((PREFIX & PREFIX_MASK) | (h & ~PREFIX_MASK)); } - /** - * @notice optionally emited to indicate the latest configDigest and epoch for - which a report was successfully transmited. Alternatively, the contract may - use latestConfigDigestAndEpoch with scanLogs set to false. - */ + /// @notice optionally emitted to indicate the latest configDigest and epoch for + /// which a report was successfully transmitted. Alternatively, the contract may + /// use latestConfigDigestAndEpoch with scanLogs set to false. event Transmitted(bytes32 configDigest, uint32 epoch); - /** - * @notice optionally returns the latest configDigest and epoch for which a - report was successfully transmitted. Alternatively, the contract may return - scanLogs set to true and use Transmitted events to provide this information - to offchain watchers. - * @return scanLogs indicates whether to rely on the configDigest and epoch - returned or whether to scan logs for the Transmitted event instead. - * @return configDigest - * @return epoch - */ + /// @notice optionally returns the latest configDigest and epoch for which a + /// report was successfully transmitted. Alternatively, the contract may return + /// scanLogs set to true and use Transmitted events to provide this information + /// to offchain watchers. + /// @return scanLogs indicates whether to rely on the configDigest and epoch + /// returned or whether to scan logs for the Transmitted event instead. + /// @return configDigest + /// @return epoch function latestConfigDigestAndEpoch() external view virtual returns (bool scanLogs, bytes32 configDigest, uint32 epoch); - /** - * @notice transmit is called to post a new report to the contract - * @param reportContext [0]: ConfigDigest, [1]: 27 byte padding, 4-byte epoch and 1-byte round, [2]: ExtraHash - * @param report serialized report, which the signatures are signing. - * @param rs ith element is the R components of the ith signature on report. Must have at most maxNumOracles entries - * @param ss ith element is the S components of the ith signature on report. Must have at most maxNumOracles entries - * @param rawVs ith element is the the V component of the ith signature - */ + /// @notice transmit is called to post a new report to the contract + /// @param reportContext [0]: ConfigDigest, [1]: 27 byte padding, 4-byte epoch and 1-byte round, [2]: ExtraHash + /// @param report serialized report, which the signatures are signing. + /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries + /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries + /// @param rawVs ith element is the the V component of the ith signature function transmit( // NOTE: If these parameters are changed, expectedMsgDataLength and/or // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly diff --git a/contracts/src/v0.8/shared/ocr2/OCR2Base.sol b/contracts/src/v0.8/shared/ocr2/OCR2Base.sol index 0f0317602d3..baedac7710a 100644 --- a/contracts/src/v0.8/shared/ocr2/OCR2Base.sol +++ b/contracts/src/v0.8/shared/ocr2/OCR2Base.sol @@ -1,47 +1,45 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {ConfirmedOwner} from "../access/ConfirmedOwner.sol"; +import {OwnerIsCreator} from "../access/OwnerIsCreator.sol"; import {OCR2Abstract} from "./OCR2Abstract.sol"; -/** - * @notice Onchain verification of reports from the offchain reporting protocol - * @dev THIS CONTRACT HAS NOT GONE THROUGH ANY SECURITY REVIEW. DO NOT USE IN PROD. - * @dev For details on its operation, see the offchain reporting protocol design - * doc, which refers to this contract as simply the "contract". - * @dev This contract is meant to aid rapid development of new applications based on OCR2. - * However, for actual production contracts, it is expected that most of the logic of this contract - * will be folded directly into the application contract. Inheritance prevents us from doing lots - * of juicy storage layout optimizations, leading to a substantial increase in gas cost. - */ +/// @notice Onchain verification of reports from the offchain reporting protocol +/// @dev THIS CONTRACT HAS NOT GONE THROUGH ANY SECURITY REVIEW. DO NOT USE IN PROD. +/// @dev For details on its operation, see the offchain reporting protocol design +/// doc, which refers to this contract as simply the "contract". +/// @dev This contract is meant to aid rapid development of new applications based on OCR2. +/// However, for actual production contracts, it is expected that most of the logic of this contract +/// will be folded directly into the application contract. Inheritance prevents us from doing lots +/// of juicy storage layout optimizations, leading to a substantial increase in gas cost. // solhint-disable custom-errors -abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { +abstract contract OCR2Base is OwnerIsCreator, OCR2Abstract { error ReportInvalid(); bool internal immutable i_uniqueReports; - constructor(bool uniqueReports) ConfirmedOwner(msg.sender) { + constructor(bool uniqueReports) OwnerIsCreator() { i_uniqueReports = uniqueReports; } - // Storing these fields used on the hot path in a ConfigInfo variable reduces the - // retrieval of all of them to a single SLOAD. If any further fields are - // added, make sure that storage of the struct still takes at most 32 bytes. + /// @dev Storing these fields used on the hot path in a ConfigInfo variable reduces + /// the retrieval of all of them to a single SLOAD. If any further fields are + /// added, make sure that storage of the struct still takes at most 32 bytes. struct ConfigInfo { bytes32 latestConfigDigest; - uint8 f; // TODO: could be optimized by squeezing into one slot - uint8 n; + uint8 f; // ───╮ + uint8 n; // ───╯ } ConfigInfo internal s_configInfo; - // incremented each time a new config is posted. This count is incorporated - // into the config digest, to prevent replay attacks. + /// @dev Incremented each time a new config is posted. This count is incorporated + /// into the config digest, to prevent replay attacks. uint32 internal s_configCount; - uint32 internal s_latestConfigBlockNumber; // makes it easier for offchain systems - // to extract config from logs. + /// @dev Makes it easier for offchain systems to extract config from logs. + uint32 internal s_latestConfigBlockNumber; - // Used for s_oracles[a].role, where a is an address, to track the purpose - // of the address, or to indicate that the address is unset. + /// @dev Used for s_oracles[a].role, where a is an address, to track the purpose + /// of the address, or to indicate that the address is unset. enum Role { // No oracle role has been set for address a Unset, @@ -55,30 +53,26 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { } struct Oracle { - uint8 index; // Index of oracle in s_signers/s_transmitters - Role role; // Role of the address which mapped to this struct + uint8 index; // ───╮ Index of oracle in s_signers/s_transmitters + Role role; // ─────╯ Role of the address which mapped to this struct } mapping(address => Oracle) /* signer OR transmitter address */ internal s_oracles; - // s_signers contains the signing address of each oracle + /// @notice Contains the signing address of each oracle address[] internal s_signers; - // s_transmitters contains the transmission address of each oracle, - // i.e. the address the oracle actually sends transactions to the contract from + /// @notice Contains the transmission address of each oracle, + /// i.e. the address the oracle actually sends transactions to the contract from address[] internal s_transmitters; - /* - * Config logic - */ - - // Reverts transaction if config args are invalid + /// @dev Reverts transaction if config args are invalid modifier checkConfigValid( uint256 _numSigners, uint256 _numTransmitters, uint256 _f ) { - require(_numSigners <= maxNumOracles, "too many signers"); + require(_numSigners <= MAX_NUM_ORACLES, "too many signers"); require(_f > 0, "f must be positive"); require(_numSigners == _numTransmitters, "oracle addresses out of registration"); require(_numSigners > 3 * _f, "faulty-oracle f too high"); @@ -105,15 +99,13 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { return (true, bytes32(0), uint32(0)); } - /** - * @notice sets offchain reporting protocol configuration incl. participating oracles - * @param _signers addresses with which oracles sign the reports - * @param _transmitters addresses oracles use to transmit the reports - * @param _f number of faulty oracles the system can tolerate - * @param _onchainConfig encoded on-chain contract configuration - * @param _offchainConfigVersion version number for offchainEncoding schema - * @param _offchainConfig encoded off-chain oracle configuration - */ + /// @notice sets offchain reporting protocol configuration incl. participating oracles + /// @param _signers addresses with which oracles sign the reports + /// @param _transmitters addresses oracles use to transmit the reports + /// @param _f number of faulty oracles the system can tolerate + /// @param _onchainConfig encoded on-chain contract configuration + /// @param _offchainConfigVersion version number for offchainEncoding schema + /// @param _offchainConfig encoded off-chain oracle configuration function setConfig( address[] memory _signers, address[] memory _transmitters, @@ -187,12 +179,10 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { _afterSetConfig(args.f, args.onchainConfig); } - /** - * @notice information about current offchain reporting protocol configuration - * @return configCount ordinal number of current config, out of all configs applied to this contract so far - * @return blockNumber block at which this config was set - * @return configDigest domain-separation tag for current config (see configDigestFromConfigData) - */ + /// @notice information about current offchain reporting protocol configuration + /// @return configCount ordinal number of current config, out of all configs applied to this contract so far + /// @return blockNumber block at which this config was set + /// @return configDigest domain-separation tag for current config (see configDigestFromConfigData) function latestConfigDetails() external view @@ -202,10 +192,8 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { return (s_configCount, s_latestConfigBlockNumber, s_configInfo.latestConfigDigest); } - /** - * @return list of addresses permitted to transmit reports to this contract - * @dev The list will match the order used to specify the transmitter during setConfig - */ + /// @return list of addresses permitted to transmit reports to this contract + /// @dev The list will match the order used to specify the transmitter during setConfig function transmitters() external view returns (address[] memory) { return s_transmitters; } @@ -214,31 +202,27 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { function _afterSetConfig(uint8 _f, bytes memory _onchainConfig) internal virtual; - /** - * @dev hook to allow additional validation of the report by the extending contract - * @param configDigest separation tag for current config (see configDigestFromConfigData) - * @param epochAndRound 27 byte padding, 4-byte epoch and 1-byte round - * @param report serialized report - */ + /// @dev hook to allow additional validation of the report by the extending contract + /// @param configDigest separation tag for current config (see configDigestFromConfigData) + /// @param epochAndRound 27 byte padding, 4-byte epoch and 1-byte round + /// @param report serialized report function _validateReport( bytes32 configDigest, uint40 epochAndRound, bytes memory report ) internal virtual returns (bool); - /** - * @dev hook called after the report has been fully validated - * for the extending contract to handle additional logic, such as oracle payment - * @param initialGas the amount of gas before validation - * @param transmitter the address of the account that submitted the report - * @param signers the addresses of all signing accounts - * @param report serialized report - */ + /// @dev hook called after the report has been fully validated + /// for the extending contract to handle additional logic, such as oracle payment + /// @param initialGas the amount of gas before validation + /// @param transmitter the address of the account that submitted the report + /// @param signers the addresses of all signing accounts + /// @param report serialized report function _report( uint256 initialGas, address transmitter, uint8 signerCount, - address[maxNumOracles] memory signers, + address[MAX_NUM_ORACLES] memory signers, bytes calldata report ) internal virtual; @@ -274,13 +258,11 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { require(msg.data.length == expected, "calldata length mismatch"); } - /** - * @notice transmit is called to post a new report to the contract - * @param report serialized report, which the signatures are signing. - * @param rs ith element is the R components of the ith signature on report. Must have at most maxNumOracles entries - * @param ss ith element is the S components of the ith signature on report. Must have at most maxNumOracles entries - * @param rawVs ith element is the the V component of the ith signature - */ + /// @notice transmit is called to post a new report to the contract + /// @param report serialized report, which the signatures are signing. + /// @param rs ith element is the R components of the ith signature on report. Must have at most maxNumOracles entries + /// @param ss ith element is the S components of the ith signature on report. Must have at most maxNumOracles entries + /// @param rawVs ith element is the the V component of the ith signature function transmit( // NOTE: If these parameters are changed, expectedMsgDataLength and/or // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly @@ -328,7 +310,7 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { ); } - address[maxNumOracles] memory signed; + address[MAX_NUM_ORACLES] memory signed; uint8 signerCount = 0; { diff --git a/contracts/src/v0.8/shared/test/call/CallWithExactGas.t.sol b/contracts/src/v0.8/shared/test/call/CallWithExactGas.t.sol new file mode 100644 index 00000000000..e5c90b172cd --- /dev/null +++ b/contracts/src/v0.8/shared/test/call/CallWithExactGas.t.sol @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {CallWithExactGas} from "../../call/CallWithExactGas.sol"; +import {CallWithExactGasHelper} from "./CallWithExactGasHelper.sol"; +import {BaseTest} from "../BaseTest.t.sol"; +import {GenericReceiver} from "../testhelpers/GenericReceiver.sol"; + +contract CallWithExactGasSetup is BaseTest { + GenericReceiver internal s_receiver; + CallWithExactGasHelper internal s_caller; + uint256 internal constant DEFAULT_GAS_LIMIT = 20_000; + uint16 internal constant DEFAULT_GAS_FOR_CALL_EXACT_CHECK = 5000; + uint256 internal constant EXTCODESIZE_GAS_COST = 2600; + + function setUp() public virtual override { + BaseTest.setUp(); + + s_receiver = new GenericReceiver(false); + s_caller = new CallWithExactGasHelper(); + } +} + +contract CallWithExactGas__callWithExactGas is CallWithExactGasSetup { + function test_callWithExactGasSuccess(bytes memory payload, bytes4 funcSelector) public { + vm.pauseGasMetering(); + + bytes memory data = abi.encodeWithSelector(funcSelector, payload); + vm.assume( + funcSelector != GenericReceiver.setRevert.selector && + funcSelector != GenericReceiver.setErr.selector && + funcSelector != 0x5100fc21 // s_toRevert(), which is public and therefore has a function selector + ); + + vm.expectCall(address(s_receiver), data); + vm.resumeGasMetering(); + + bool success = s_caller.callWithExactGas( + data, + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + assertTrue(success); + } + + function test_CallWithExactGasSafeReturnDataExactGas() public { + // The calculated overhead for otherwise unaccounted for gas usage + uint256 overheadForCallWithExactGas = 364; + + bytes memory payload = abi.encodeWithSelector( + s_caller.callWithExactGas.selector, + "", + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + // Since only 63/64th of the gas gets passed, we compensate + uint256 allowedGas = (DEFAULT_GAS_LIMIT + (DEFAULT_GAS_LIMIT / 64)); + + allowedGas += EXTCODESIZE_GAS_COST + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + overheadForCallWithExactGas; + + // Due to EIP-150 we expect to lose 1/64, so we compensate for this + allowedGas = (allowedGas * 64) / 63; + + (bool success, bytes memory retData) = address(s_caller).call{gas: allowedGas}(payload); + + assertTrue(success); + assertEq(abi.encode(true), retData); + } + + function test_CallWithExactGasReceiverErrorSuccess() public { + bytes memory data = abi.encode("0x52656E73"); + + bytes memory errorData = new bytes(20); + for (uint256 i = 0; i < errorData.length; ++i) { + errorData[i] = 0x01; + } + s_receiver.setErr(errorData); + s_receiver.setRevert(true); + + vm.expectCall(address(s_receiver), data); + + bool success = s_caller.callWithExactGas( + data, + address(s_receiver), + DEFAULT_GAS_LIMIT * 10, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + assertFalse(success); + } + + function test_NoContractReverts() public { + address addressWithoutContract = address(1337); + + vm.expectRevert(CallWithExactGas.NoContract.selector); + + s_caller.callWithExactGas( + "", // empty payload as it will revert well before needing it + addressWithoutContract, + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + } + + function test_NoGasForCallExactCheckReverts() public { + bytes memory payload = abi.encodeWithSelector( + s_caller.callWithExactGas.selector, + "", // empty payload as it will revert well before needing it + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + (bool success, bytes memory retData) = address(s_caller).call{gas: DEFAULT_GAS_FOR_CALL_EXACT_CHECK - 1}(payload); + assertFalse(success); + assertEq(retData.length, CallWithExactGas.NoGasForCallExactCheck.selector.length); + assertEq(abi.encodeWithSelector(CallWithExactGas.NoGasForCallExactCheck.selector), retData); + } + + function test_NotEnoughGasForCallReverts() public { + bytes memory payload = abi.encodeWithSelector( + s_caller.callWithExactGas.selector, + "", // empty payload as it will revert well before needing it + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + // Supply enough gas for the final call, the DEFAULT_GAS_FOR_CALL_EXACT_CHECK, + // the extcodesize and account for EIP-150. This doesn't account for any other gas + // usage, and will therefore fail because the checks and memory stored/loads + // also cost gas. + uint256 allowedGas = (DEFAULT_GAS_LIMIT + (DEFAULT_GAS_LIMIT / 64)) + DEFAULT_GAS_FOR_CALL_EXACT_CHECK; + // extcodesize gas cost + allowedGas += EXTCODESIZE_GAS_COST; + // EIP-150 + allowedGas = (allowedGas * 64) / 63; + + // Expect this call to fail due to not having enough gas for the final call + (bool success, bytes memory retData) = address(s_caller).call{gas: allowedGas}(payload); + + assertFalse(success); + assertEq(retData.length, CallWithExactGas.NotEnoughGasForCall.selector.length); + assertEq(abi.encodeWithSelector(CallWithExactGas.NotEnoughGasForCall.selector), retData); + } +} + +contract CallWithExactGas__callWithExactGasSafeReturnData is CallWithExactGasSetup { + function testFuzz_CallWithExactGasSafeReturnDataSuccess(bytes memory payload, bytes4 funcSelector) public { + vm.pauseGasMetering(); + bytes memory data = abi.encodeWithSelector(funcSelector, payload); + vm.assume( + funcSelector != GenericReceiver.setRevert.selector && + funcSelector != GenericReceiver.setErr.selector && + funcSelector != 0x5100fc21 // s_toRevert(), which is public and therefore has a function selector + ); + uint16 maxRetBytes = 0; + + vm.expectCall(address(s_receiver), data); + vm.resumeGasMetering(); + + (bool success, bytes memory retData, uint256 gasUsed) = s_caller.callWithExactGasSafeReturnData( + data, + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK, + maxRetBytes + ); + + assertTrue(success); + assertEq(retData.length, 0); + assertGt(gasUsed, 500); + } + + function test_CallWithExactGasSafeReturnDataExactGas() public { + // The gas cost for `extcodesize` + uint256 extcodesizeGas = EXTCODESIZE_GAS_COST; + // The calculated overhead for retData initialization + uint256 overheadForRetDataInit = 114; + // The calculated overhead for otherwise unaccounted for gas usage + uint256 overheadForCallWithExactGas = 486; + + bytes memory payload = abi.encodeWithSelector( + s_caller.callWithExactGasSafeReturnData.selector, + "", + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK, + 0 + ); + + // Since only 63/64th of the gas gets passed, we compensate + uint256 allowedGas = (DEFAULT_GAS_LIMIT + (DEFAULT_GAS_LIMIT / 64)); + + allowedGas += + extcodesizeGas + + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + + overheadForRetDataInit + + overheadForCallWithExactGas; + + // Due to EIP-150 we expect to lose 1/64, so we compensate for this + allowedGas = (allowedGas * 64) / 63; + + vm.expectCall(address(s_receiver), ""); + (bool success, bytes memory retData) = address(s_caller).call{gas: allowedGas}(payload); + + assertTrue(success); + (bool innerSuccess, bytes memory innerRetData, uint256 gasUsed) = abi.decode(retData, (bool, bytes, uint256)); + + assertTrue(innerSuccess); + assertEq(innerRetData.length, 0); + assertGt(gasUsed, 500); + } + + function testFuzz_CallWithExactGasReceiverErrorSuccess(uint16 testRetBytes) public { + uint16 maxReturnBytes = 500; + // Bound with upper limit, otherwise the test runs out of gas. + testRetBytes = uint16(bound(testRetBytes, 0, maxReturnBytes * 10)); + + bytes memory data = abi.encode("0x52656E73"); + + bytes memory errorData = new bytes(testRetBytes); + for (uint256 i = 0; i < errorData.length; ++i) { + errorData[i] = 0x01; + } + s_receiver.setErr(errorData); + s_receiver.setRevert(true); + + vm.expectCall(address(s_receiver), data); + + (bool success, bytes memory retData, uint256 gasUsed) = s_caller.callWithExactGasSafeReturnData( + data, + address(s_receiver), + DEFAULT_GAS_LIMIT * 10, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK, + maxReturnBytes + ); + + assertFalse(success); + + bytes memory expectedReturnData = errorData; + + // If expected return data is longer than MAX_RET_BYTES, truncate it to MAX_RET_BYTES + if (expectedReturnData.length > maxReturnBytes) { + expectedReturnData = new bytes(maxReturnBytes); + for (uint256 i = 0; i < maxReturnBytes; ++i) { + expectedReturnData[i] = errorData[i]; + } + } + assertEq(expectedReturnData, retData); + assertGt(gasUsed, 500); + } + + function test_NoContractReverts() public { + address addressWithoutContract = address(1337); + + vm.expectRevert(CallWithExactGas.NoContract.selector); + + s_caller.callWithExactGasSafeReturnData( + "", // empty payload as it will revert well before needing it + addressWithoutContract, + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK, + 0 + ); + } + + function test_NoGasForCallExactCheckReverts() public { + bytes memory payload = abi.encodeWithSelector( + s_caller.callWithExactGasSafeReturnData.selector, + "", // empty payload as it will revert well before needing it + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK, + 0 + ); + + (bool success, bytes memory retData) = address(s_caller).call{gas: DEFAULT_GAS_FOR_CALL_EXACT_CHECK - 1}(payload); + assertFalse(success); + assertEq(retData.length, CallWithExactGas.NoGasForCallExactCheck.selector.length); + assertEq(abi.encodeWithSelector(CallWithExactGas.NoGasForCallExactCheck.selector), retData); + } + + function test_NotEnoughGasForCallReverts() public { + bytes memory payload = abi.encodeWithSelector( + s_caller.callWithExactGasSafeReturnData.selector, + "", // empty payload as it will revert well before needing it + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK, + 0 + ); + + // Supply enough gas for the final call, the DEFAULT_GAS_FOR_CALL_EXACT_CHECK, + // the extcodesize and account for EIP-150. This doesn't account for any other gas + // usage, and will therefore fail because the checks and memory stored/loads + // also cost gas. + uint256 allowedGas = (DEFAULT_GAS_LIMIT + (DEFAULT_GAS_LIMIT / 64)) + DEFAULT_GAS_FOR_CALL_EXACT_CHECK; + // extcodesize gas cost + allowedGas += EXTCODESIZE_GAS_COST; + // EIP-150 + allowedGas = (allowedGas * 64) / 63; + + // Expect this call to fail due to not having enough gas for the final call + (bool success, bytes memory retData) = address(s_caller).call{gas: allowedGas}(payload); + + assertFalse(success); + assertEq(retData.length, CallWithExactGas.NotEnoughGasForCall.selector.length); + assertEq(abi.encodeWithSelector(CallWithExactGas.NotEnoughGasForCall.selector), retData); + } +} + +contract CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract is CallWithExactGasSetup { + function test_CallWithExactGasEvenIfTargetIsNoContractSuccess(bytes memory payload, bytes4 funcSelector) public { + vm.pauseGasMetering(); + bytes memory data = abi.encodeWithSelector(funcSelector, payload); + vm.assume( + funcSelector != GenericReceiver.setRevert.selector && + funcSelector != GenericReceiver.setErr.selector && + funcSelector != 0x5100fc21 // s_toRevert(), which is public and therefore has a function selector + ); + vm.expectCall(address(s_receiver), data); + vm.resumeGasMetering(); + + (bool success, bool sufficientGas) = s_caller.callWithExactGasEvenIfTargetIsNoContract( + data, + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + assertTrue(success); + assertTrue(sufficientGas); + } + + function test_CallWithExactGasEvenIfTargetIsNoContractExactGasSuccess() public { + // The calculated overhead for otherwise unaccounted for gas usage + uint256 overheadForCallWithExactGas = 446; + + bytes memory data = abi.encode("0x52656E73"); + + bytes memory payload = abi.encodeWithSelector( + s_caller.callWithExactGasEvenIfTargetIsNoContract.selector, + data, + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + // Since only 63/64th of the gas gets passed, we compensate + uint256 allowedGas = (DEFAULT_GAS_LIMIT + (DEFAULT_GAS_LIMIT / 64)); + + allowedGas += DEFAULT_GAS_FOR_CALL_EXACT_CHECK + overheadForCallWithExactGas; + + // Due to EIP-150 we expect to lose 1/64, so we compensate for this + allowedGas = (allowedGas * 64) / 63; + + vm.expectCall(address(s_receiver), data); + (bool outerCallSuccess, bytes memory SuccessAndSufficientGas) = address(s_caller).call{gas: allowedGas}(payload); + + // The call succeeds + assertTrue(outerCallSuccess); + + (bool success, bool sufficientGas) = abi.decode(SuccessAndSufficientGas, (bool, bool)); + assertTrue(success); + assertTrue(sufficientGas); + } + + function test_CallWithExactGasEvenIfTargetIsNoContractReceiverErrorSuccess() public { + bytes memory data = abi.encode("0x52656E73"); + + bytes memory errorData = new bytes(20); + for (uint256 i = 0; i < errorData.length; ++i) { + errorData[i] = 0x01; + } + s_receiver.setErr(errorData); + s_receiver.setRevert(true); + + vm.expectCall(address(s_receiver), data); + + (bool success, bool sufficientGas) = s_caller.callWithExactGasEvenIfTargetIsNoContract( + data, + address(s_receiver), + DEFAULT_GAS_LIMIT * 10, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + // We don't care if it reverts, we only care if we have enough gas + assertFalse(success); + assertTrue(sufficientGas); + } + + function test_NoContractSuccess() public { + bytes memory data = abi.encode("0x52656E73"); + address addressWithoutContract = address(1337); + + (bool success, bool sufficientGas) = s_caller.callWithExactGasEvenIfTargetIsNoContract( + data, + addressWithoutContract, + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + assertTrue(success); + assertTrue(sufficientGas); + } + + function test_NoGasForCallExactCheckReturnFalseSuccess() public { + bytes memory payload = abi.encodeWithSelector( + s_caller.callWithExactGasEvenIfTargetIsNoContract.selector, + "", // empty payload as it will revert well before needing it + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + (bool outerCallSuccess, bytes memory SuccessAndSufficientGas) = address(s_caller).call{ + gas: DEFAULT_GAS_FOR_CALL_EXACT_CHECK - 1 + }(payload); + + // The call succeeds + assertTrue(outerCallSuccess); + + (bool success, bool sufficientGas) = abi.decode(SuccessAndSufficientGas, (bool, bool)); + assertFalse(success); + assertFalse(sufficientGas); + } + + function test_NotEnoughGasForCallReturnsFalseSuccess() public { + bytes memory payload = abi.encodeWithSelector( + s_caller.callWithExactGasEvenIfTargetIsNoContract.selector, + "", // empty payload as it will revert well before needing it + address(s_receiver), + DEFAULT_GAS_LIMIT, + DEFAULT_GAS_FOR_CALL_EXACT_CHECK + ); + + // Supply enough gas for the final call, the DEFAULT_GAS_FOR_CALL_EXACT_CHECK, + // and account for EIP-150. This doesn't account for any other gas usage, and + // will therefore fail because the checks and memory stored/loads also cost gas. + uint256 allowedGas = (DEFAULT_GAS_LIMIT + (DEFAULT_GAS_LIMIT / 64)) + DEFAULT_GAS_FOR_CALL_EXACT_CHECK; + // EIP-150 + allowedGas = (allowedGas * 64) / 63; + + // Expect this call to fail due to not having enough gas for the final call + (bool outerCallSuccess, bytes memory SuccessAndSufficientGas) = address(s_caller).call{gas: allowedGas}(payload); + + // The call succeeds + assertTrue(outerCallSuccess); + + (bool success, bool sufficientGas) = abi.decode(SuccessAndSufficientGas, (bool, bool)); + assertFalse(success); + assertFalse(sufficientGas); + } +} diff --git a/contracts/src/v0.8/shared/test/call/CallWithExactGasHelper.sol b/contracts/src/v0.8/shared/test/call/CallWithExactGasHelper.sol new file mode 100644 index 00000000000..932315639b9 --- /dev/null +++ b/contracts/src/v0.8/shared/test/call/CallWithExactGasHelper.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {CallWithExactGas} from "../../call/CallWithExactGas.sol"; + +contract CallWithExactGasHelper { + function callWithExactGas( + bytes memory payload, + address target, + uint256 gasLimit, + uint16 gasForCallExactCheck + ) public returns (bool success) { + return CallWithExactGas._callWithExactGas(payload, target, gasLimit, gasForCallExactCheck); + } + + function callWithExactGasSafeReturnData( + bytes memory payload, + address target, + uint256 gasLimit, + uint16 gasForCallExactCheck, + uint16 maxReturnBytes + ) public returns (bool success, bytes memory retData, uint256 gasUsed) { + return + CallWithExactGas._callWithExactGasSafeReturnData(payload, target, gasLimit, gasForCallExactCheck, maxReturnBytes); + } + + function callWithExactGasEvenIfTargetIsNoContract( + bytes memory payload, + address target, + uint256 gasLimit, + uint16 gasForCallExactCheck + ) public returns (bool success, bool sufficientGas) { + return CallWithExactGas._callWithExactGasEvenIfTargetIsNoContract(payload, target, gasLimit, gasForCallExactCheck); + } +} diff --git a/contracts/src/v0.8/shared/test/testhelpers/GenericReceiver.sol b/contracts/src/v0.8/shared/test/testhelpers/GenericReceiver.sol new file mode 100644 index 00000000000..2c058012dfd --- /dev/null +++ b/contracts/src/v0.8/shared/test/testhelpers/GenericReceiver.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract GenericReceiver { + bool public s_toRevert; + bytes private s_err; + + constructor(bool toRevert) { + s_toRevert = toRevert; + } + + function setRevert(bool toRevert) external { + s_toRevert = toRevert; + } + + function setErr(bytes memory err) external { + s_err = err; + } + + // solhint-disable-next-line payable-fallback + fallback() external { + if (s_toRevert) { + bytes memory reason = s_err; + assembly { + revert(add(32, reason), mload(reason)) + } + } + } +} diff --git a/contracts/src/v0.8/shared/test/token/ERC677/BurnMintERC677.t.sol b/contracts/src/v0.8/shared/test/token/ERC677/BurnMintERC677.t.sol index 6b13f390009..de9067a569a 100644 --- a/contracts/src/v0.8/shared/test/token/ERC677/BurnMintERC677.t.sol +++ b/contracts/src/v0.8/shared/test/token/ERC677/BurnMintERC677.t.sol @@ -7,8 +7,8 @@ import {IERC677} from "../../../token/ERC677/IERC677.sol"; import {BaseTest} from "../../BaseTest.t.sol"; import {BurnMintERC677} from "../../../token/ERC677/BurnMintERC677.sol"; -import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; -import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/introspection/IERC165.sol"; +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol"; contract BurnMintERC677Setup is BaseTest { event Transfer(address indexed from, address indexed to, uint256 value); diff --git a/contracts/src/v0.8/shared/test/token/ERC677/OpStackBurnMintERC677.t.sol b/contracts/src/v0.8/shared/test/token/ERC677/OpStackBurnMintERC677.t.sol index f22a92a4258..7987fefec48 100644 --- a/contracts/src/v0.8/shared/test/token/ERC677/OpStackBurnMintERC677.t.sol +++ b/contracts/src/v0.8/shared/test/token/ERC677/OpStackBurnMintERC677.t.sol @@ -9,7 +9,7 @@ import {BurnMintERC677} from "../../../token/ERC677/BurnMintERC677.sol"; import {BaseTest} from "../../BaseTest.t.sol"; import {OpStackBurnMintERC677} from "../../../token/ERC677/OpStackBurnMintERC677.sol"; -import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/introspection/IERC165.sol"; +import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol"; contract OpStackBurnMintERC677Setup is BaseTest { address internal s_l1Token = address(897352983527); diff --git a/contracts/src/v0.8/shared/token/ERC20/IBurnMintERC20.sol b/contracts/src/v0.8/shared/token/ERC20/IBurnMintERC20.sol index 2b2f3fd3787..b9b3b54bf73 100644 --- a/contracts/src/v0.8/shared/token/ERC20/IBurnMintERC20.sol +++ b/contracts/src/v0.8/shared/token/ERC20/IBurnMintERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; interface IBurnMintERC20 is IERC20 { /// @notice Mints new tokens for a given address. diff --git a/contracts/src/v0.8/shared/token/ERC20/IOptimismMintableERC20.sol b/contracts/src/v0.8/shared/token/ERC20/IOptimismMintableERC20.sol index 6362d9e78ac..4e9d3a2494c 100644 --- a/contracts/src/v0.8/shared/token/ERC20/IOptimismMintableERC20.sol +++ b/contracts/src/v0.8/shared/token/ERC20/IOptimismMintableERC20.sol @@ -2,7 +2,7 @@ // solhint-disable one-contract-per-file pragma solidity ^0.8.0; -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/introspection/IERC165.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol"; /// @title IOptimismMintableERC20Minimal /// @dev This interface is a subset of the Optimism ERC20 interface that is defined diff --git a/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.sol b/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.sol index 775d5fb3d94..556914da127 100644 --- a/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.sol +++ b/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.sol @@ -7,10 +7,10 @@ import {IERC677} from "./IERC677.sol"; import {ERC677} from "./ERC677.sol"; import {OwnerIsCreator} from "../../access/OwnerIsCreator.sol"; -import {ERC20Burnable} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/extensions/ERC20Burnable.sol"; -import {EnumerableSet} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/structs/EnumerableSet.sol"; -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/introspection/IERC165.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; +import {ERC20Burnable} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import {EnumerableSet} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol"; +import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; /// @notice A basic ERC677 compatible token contract with burn and minting roles. /// @dev The total supply can be limited during deployment. diff --git a/contracts/src/v0.8/shared/token/ERC677/ERC677.sol b/contracts/src/v0.8/shared/token/ERC677/ERC677.sol index c9a2996e8e9..aa75a1170c7 100644 --- a/contracts/src/v0.8/shared/token/ERC677/ERC677.sol +++ b/contracts/src/v0.8/shared/token/ERC677/ERC677.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.0; import {IERC677} from "./IERC677.sol"; -import {IERC677Receiver} from "./IERC677Receiver.sol"; +import {IERC677Receiver} from "../../interfaces/IERC677Receiver.sol"; -import {Address} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Address.sol"; -import {ERC20} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/ERC20.sol"; +import {Address} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Address.sol"; +import {ERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol"; contract ERC677 is IERC677, ERC20 { using Address for address; diff --git a/contracts/src/v0.8/shared/token/ERC677/IERC677Receiver.sol b/contracts/src/v0.8/shared/token/ERC677/IERC677Receiver.sol deleted file mode 100644 index 2e44d860a82..00000000000 --- a/contracts/src/v0.8/shared/token/ERC677/IERC677Receiver.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IERC677Receiver { - function onTokenTransfer(address sender, uint256 amount, bytes calldata data) external; -} diff --git a/contracts/src/v0.8/shared/token/ERC677/OpStackBurnMintERC677.sol b/contracts/src/v0.8/shared/token/ERC677/OpStackBurnMintERC677.sol index 714a4a11baf..95c64c9cd23 100644 --- a/contracts/src/v0.8/shared/token/ERC677/OpStackBurnMintERC677.sol +++ b/contracts/src/v0.8/shared/token/ERC677/OpStackBurnMintERC677.sol @@ -5,7 +5,7 @@ import {IOptimismMintableERC20Minimal, IOptimismMintableERC20} from "../ERC20/IO import {BurnMintERC677} from "./BurnMintERC677.sol"; -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/introspection/IERC165.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol"; /// @notice A basic ERC677 compatible token contract with burn and minting roles that supports /// the native L2 bridging requirements of the Optimism Stack. diff --git a/contracts/src/v0.8/tests/FeedConsumer.sol b/contracts/src/v0.8/tests/FeedConsumer.sol index 3c9462b0ac5..c9fc62357a6 100644 --- a/contracts/src/v0.8/tests/FeedConsumer.sol +++ b/contracts/src/v0.8/tests/FeedConsumer.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {AggregatorV2V3Interface} from "../interfaces/AggregatorV2V3Interface.sol"; +import {AggregatorV2V3Interface} from "../shared/interfaces/AggregatorV2V3Interface.sol"; contract FeedConsumer { AggregatorV2V3Interface public immutable AGGREGATOR; diff --git a/contracts/src/v0.8/tests/MockETHLINKAggregator.sol b/contracts/src/v0.8/tests/MockETHLINKAggregator.sol index 98dd775a117..d685aac7314 100644 --- a/contracts/src/v0.8/tests/MockETHLINKAggregator.sol +++ b/contracts/src/v0.8/tests/MockETHLINKAggregator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "../interfaces/AggregatorV3Interface.sol"; +import "../shared/interfaces/AggregatorV3Interface.sol"; contract MockETHLINKAggregator is AggregatorV3Interface { int256 public answer; diff --git a/contracts/src/v0.8/tests/MockV3Aggregator.sol b/contracts/src/v0.8/tests/MockV3Aggregator.sol index d261b2c4b14..9822d23e853 100644 --- a/contracts/src/v0.8/tests/MockV3Aggregator.sol +++ b/contracts/src/v0.8/tests/MockV3Aggregator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "../interfaces/AggregatorV2V3Interface.sol"; +import "../shared/interfaces/AggregatorV2V3Interface.sol"; /** * @title MockV3Aggregator diff --git a/contracts/src/v0.8/transmission/dev/ERC-4337/Paymaster.sol b/contracts/src/v0.8/transmission/dev/ERC-4337/Paymaster.sol index cd84bb6a0e0..970ddf6b7e6 100644 --- a/contracts/src/v0.8/transmission/dev/ERC-4337/Paymaster.sol +++ b/contracts/src/v0.8/transmission/dev/ERC-4337/Paymaster.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.15; import {IPaymaster} from "../../../vendor/entrypoint/interfaces/IPaymaster.sol"; import {SCALibrary} from "./SCALibrary.sol"; import {LinkTokenInterface} from "../../../shared/interfaces/LinkTokenInterface.sol"; -import {AggregatorV3Interface} from "../../../interfaces/AggregatorV3Interface.sol"; +import {AggregatorV3Interface} from "../../../shared/interfaces/AggregatorV3Interface.sol"; import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; import {UserOperation} from "../../../vendor/entrypoint/interfaces/UserOperation.sol"; import {_packValidationData} from "../../../vendor/entrypoint/core/Helpers.sol"; diff --git a/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/L2/GasPriceOracle.sol b/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/L2/GasPriceOracle.sol new file mode 100644 index 00000000000..aebc1f747a1 --- /dev/null +++ b/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/L2/GasPriceOracle.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { ISemver } from "../universal/ISemver.sol"; +import { Predeploys } from "../libraries/Predeploys.sol"; +import { L1Block } from "./L1Block.sol"; + +/// @custom:proxied +/// @custom:predeploy 0x420000000000000000000000000000000000000F +/// @title GasPriceOracle +/// @notice This contract maintains the variables responsible for computing the L1 portion of the +/// total fee charged on L2. Before Bedrock, this contract held variables in state that were +/// read during the state transition function to compute the L1 portion of the transaction +/// fee. After Bedrock, this contract now simply proxies the L1Block contract, which has +/// the values used to compute the L1 portion of the fee in its state. +/// +/// The contract exposes an API that is useful for knowing how large the L1 portion of the +/// transaction fee will be. The following events were deprecated with Bedrock: +/// - event OverheadUpdated(uint256 overhead); +/// - event ScalarUpdated(uint256 scalar); +/// - event DecimalsUpdated(uint256 decimals); +contract GasPriceOracle is ISemver { + /// @notice Number of decimals used in the scalar. + uint256 public constant DECIMALS = 6; + + /// @notice Semantic version. + /// @custom:semver 1.1.0 + string public constant version = "1.1.0"; + + /// @notice Computes the L1 portion of the fee based on the size of the rlp encoded input + /// transaction, the current L1 base fee, and the various dynamic parameters. + /// @param _data Unsigned fully RLP-encoded transaction to get the L1 fee for. + /// @return L1 fee that should be paid for the tx + function getL1Fee(bytes memory _data) external view returns (uint256) { + uint256 l1GasUsed = getL1GasUsed(_data); + uint256 l1Fee = l1GasUsed * l1BaseFee(); + uint256 divisor = 10 ** DECIMALS; + uint256 unscaled = l1Fee * scalar(); + uint256 scaled = unscaled / divisor; + return scaled; + } + + /// @notice Retrieves the current gas price (base fee). + /// @return Current L2 gas price (base fee). + function gasPrice() public view returns (uint256) { + return block.basefee; + } + + /// @notice Retrieves the current base fee. + /// @return Current L2 base fee. + function baseFee() public view returns (uint256) { + return block.basefee; + } + + /// @notice Retrieves the current fee overhead. + /// @return Current fee overhead. + function overhead() public view returns (uint256) { + return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).l1FeeOverhead(); + } + + /// @notice Retrieves the current fee scalar. + /// @return Current fee scalar. + function scalar() public view returns (uint256) { + return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).l1FeeScalar(); + } + + /// @notice Retrieves the latest known L1 base fee. + /// @return Latest known L1 base fee. + function l1BaseFee() public view returns (uint256) { + return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).basefee(); + } + + /// @custom:legacy + /// @notice Retrieves the number of decimals used in the scalar. + /// @return Number of decimals used in the scalar. + function decimals() public pure returns (uint256) { + return DECIMALS; + } + + /// @notice Computes the amount of L1 gas used for a transaction. Adds the overhead which + /// represents the per-transaction gas overhead of posting the transaction and state + /// roots to L1. Adds 68 bytes of padding to account for the fact that the input does + /// not have a signature. + /// @param _data Unsigned fully RLP-encoded transaction to get the L1 gas for. + /// @return Amount of L1 gas used to publish the transaction. + function getL1GasUsed(bytes memory _data) public view returns (uint256) { + uint256 total = 0; + uint256 length = _data.length; + for (uint256 i = 0; i < length; i++) { + if (_data[i] == 0) { + total += 4; + } else { + total += 16; + } + } + uint256 unsigned = total + overhead(); + return unsigned + (68 * 16); + } +} \ No newline at end of file diff --git a/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/L2/L1Block.sol b/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/L2/L1Block.sol new file mode 100644 index 00000000000..7722b53b30c --- /dev/null +++ b/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/L2/L1Block.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { ISemver } from "../universal/ISemver.sol"; + +/// @custom:proxied +/// @custom:predeploy 0x4200000000000000000000000000000000000015 +/// @title L1Block +/// @notice The L1Block predeploy gives users access to information about the last known L1 block. +/// Values within this contract are updated once per epoch (every L1 block) and can only be +/// set by the "depositor" account, a special system address. Depositor account transactions +/// are created by the protocol whenever we move to a new epoch. +contract L1Block is ISemver { + /// @notice Address of the special depositor account. + address public constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001; + + /// @notice The latest L1 block number known by the L2 system. + uint64 public number; + + /// @notice The latest L1 timestamp known by the L2 system. + uint64 public timestamp; + + /// @notice The latest L1 basefee. + uint256 public basefee; + + /// @notice The latest L1 blockhash. + bytes32 public hash; + + /// @notice The number of L2 blocks in the same epoch. + uint64 public sequenceNumber; + + /// @notice The versioned hash to authenticate the batcher by. + bytes32 public batcherHash; + + /// @notice The overhead value applied to the L1 portion of the transaction fee. + uint256 public l1FeeOverhead; + + /// @notice The scalar value applied to the L1 portion of the transaction fee. + uint256 public l1FeeScalar; + + /// @custom:semver 1.1.0 + string public constant version = "1.1.0"; + + /// @notice Updates the L1 block values. + /// @param _number L1 blocknumber. + /// @param _timestamp L1 timestamp. + /// @param _basefee L1 basefee. + /// @param _hash L1 blockhash. + /// @param _sequenceNumber Number of L2 blocks since epoch start. + /// @param _batcherHash Versioned hash to authenticate batcher by. + /// @param _l1FeeOverhead L1 fee overhead. + /// @param _l1FeeScalar L1 fee scalar. + function setL1BlockValues( + uint64 _number, + uint64 _timestamp, + uint256 _basefee, + bytes32 _hash, + uint64 _sequenceNumber, + bytes32 _batcherHash, + uint256 _l1FeeOverhead, + uint256 _l1FeeScalar + ) + external + { + require(msg.sender == DEPOSITOR_ACCOUNT, "L1Block: only the depositor account can set L1 block values"); + + number = _number; + timestamp = _timestamp; + basefee = _basefee; + hash = _hash; + sequenceNumber = _sequenceNumber; + batcherHash = _batcherHash; + l1FeeOverhead = _l1FeeOverhead; + l1FeeScalar = _l1FeeScalar; + } +} \ No newline at end of file diff --git a/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/libraries/Predeploys.sol b/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/libraries/Predeploys.sol new file mode 100644 index 00000000000..4a0d399ce71 --- /dev/null +++ b/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/libraries/Predeploys.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Predeploys +/// @notice Contains constant addresses for contracts that are pre-deployed to the L2 system. +library Predeploys { + /// @notice Address of the L2ToL1MessagePasser predeploy. + address internal constant L2_TO_L1_MESSAGE_PASSER = 0x4200000000000000000000000000000000000016; + + /// @notice Address of the L2CrossDomainMessenger predeploy. + address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007; + + /// @notice Address of the L2StandardBridge predeploy. + address internal constant L2_STANDARD_BRIDGE = 0x4200000000000000000000000000000000000010; + + /// @notice Address of the L2ERC721Bridge predeploy. + address internal constant L2_ERC721_BRIDGE = 0x4200000000000000000000000000000000000014; + + //// @notice Address of the SequencerFeeWallet predeploy. + address internal constant SEQUENCER_FEE_WALLET = 0x4200000000000000000000000000000000000011; + + /// @notice Address of the OptimismMintableERC20Factory predeploy. + address internal constant OPTIMISM_MINTABLE_ERC20_FACTORY = 0x4200000000000000000000000000000000000012; + + /// @notice Address of the OptimismMintableERC721Factory predeploy. + address internal constant OPTIMISM_MINTABLE_ERC721_FACTORY = 0x4200000000000000000000000000000000000017; + + /// @notice Address of the L1Block predeploy. + address internal constant L1_BLOCK_ATTRIBUTES = 0x4200000000000000000000000000000000000015; + + /// @notice Address of the GasPriceOracle predeploy. Includes fee information + /// and helpers for computing the L1 portion of the transaction fee. + address internal constant GAS_PRICE_ORACLE = 0x420000000000000000000000000000000000000F; + + /// @custom:legacy + /// @notice Address of the L1MessageSender predeploy. Deprecated. Use L2CrossDomainMessenger + /// or access tx.origin (or msg.sender) in a L1 to L2 transaction instead. + address internal constant L1_MESSAGE_SENDER = 0x4200000000000000000000000000000000000001; + + /// @custom:legacy + /// @notice Address of the DeployerWhitelist predeploy. No longer active. + address internal constant DEPLOYER_WHITELIST = 0x4200000000000000000000000000000000000002; + + /// @custom:legacy + /// @notice Address of the LegacyERC20ETH predeploy. Deprecated. Balances are migrated to the + /// state trie as of the Bedrock upgrade. Contract has been locked and write functions + /// can no longer be accessed. + address internal constant LEGACY_ERC20_ETH = 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000; + + /// @custom:legacy + /// @notice Address of the L1BlockNumber predeploy. Deprecated. Use the L1Block predeploy + /// instead, which exposes more information about the L1 state. + address internal constant L1_BLOCK_NUMBER = 0x4200000000000000000000000000000000000013; + + /// @custom:legacy + /// @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated + /// L2ToL1MessagePasser contract instead. + address internal constant LEGACY_MESSAGE_PASSER = 0x4200000000000000000000000000000000000000; + + /// @notice Address of the ProxyAdmin predeploy. + address internal constant PROXY_ADMIN = 0x4200000000000000000000000000000000000018; + + /// @notice Address of the BaseFeeVault predeploy. + address internal constant BASE_FEE_VAULT = 0x4200000000000000000000000000000000000019; + + /// @notice Address of the L1FeeVault predeploy. + address internal constant L1_FEE_VAULT = 0x420000000000000000000000000000000000001A; + + /// @notice Address of the GovernanceToken predeploy. + address internal constant GOVERNANCE_TOKEN = 0x4200000000000000000000000000000000000042; + + /// @notice Address of the SchemaRegistry predeploy. + address internal constant SCHEMA_REGISTRY = 0x4200000000000000000000000000000000000020; + + /// @notice Address of the EAS predeploy. + address internal constant EAS = 0x4200000000000000000000000000000000000021; +} \ No newline at end of file diff --git a/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/universal/ISemver.sol b/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/universal/ISemver.sol new file mode 100644 index 00000000000..ae9569a0505 --- /dev/null +++ b/contracts/src/v0.8/vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/universal/ISemver.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title ISemver +/// @notice ISemver is a simple contract for ensuring that contracts are +/// versioned using semantic versioning. +interface ISemver { + /// @notice Getter for the semantic version of the contract. This is not + /// meant to be used onchain but instead meant to be used by offchain + /// tooling. + /// @return Semver contract version as a string. + function version() external view returns (string memory); +} \ No newline at end of file diff --git a/contracts/src/v0.8/vendor/MockOVMCrossDomainMessenger.sol b/contracts/src/v0.8/vendor/MockOVMCrossDomainMessenger.sol index 1947379929f..ae8b6af1cc3 100644 --- a/contracts/src/v0.8/vendor/MockOVMCrossDomainMessenger.sol +++ b/contracts/src/v0.8/vendor/MockOVMCrossDomainMessenger.sol @@ -2,7 +2,7 @@ pragma solidity >=0.7.6 <0.9.0; -import "./openzeppelin-solidity/v4.8.0/contracts/utils/Address.sol"; +import "./openzeppelin-solidity/v4.8.3/contracts/utils/Address.sol"; /** * @title iOVM_CrossDomainMessenger diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC20.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC20.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/draft-IERC20Permit.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/draft-IERC20Permit.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/draft-IERC20Permit.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/draft-IERC20Permit.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/mocks/ERC20Mock.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/mocks/ERC20Mock.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/security/Pausable.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/security/Pausable.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/security/Pausable.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/security/Pausable.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/ERC20.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/ERC20.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/extensions/ERC20Burnable.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/ERC20Burnable.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/extensions/ERC20Burnable.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/ERC20Burnable.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/extensions/IERC20Metadata.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/IERC20Metadata.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/extensions/IERC20Metadata.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/IERC20Metadata.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/extensions/draft-ERC20Permit.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/draft-ERC20Permit.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/extensions/draft-ERC20Permit.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/draft-ERC20Permit.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/extensions/draft-IERC20Permit.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/draft-IERC20Permit.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/extensions/draft-IERC20Permit.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/draft-IERC20Permit.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Address.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Address.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Address.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Address.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Context.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Context.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Context.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Context.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Counters.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Counters.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Counters.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Counters.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/StorageSlot.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/StorageSlot.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/StorageSlot.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/StorageSlot.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Strings.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Strings.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/Strings.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Strings.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/cryptography/ECDSA.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/cryptography/ECDSA.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/cryptography/ECDSA.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/cryptography/ECDSA.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/cryptography/EIP712.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/cryptography/EIP712.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/cryptography/EIP712.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/cryptography/EIP712.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/introspection/IERC165.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/introspection/IERC165.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/Math.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/Math.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/Math.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/Math.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/SafeCast.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SafeCast.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/SafeCast.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SafeCast.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/SignedMath.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SignedMath.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/SignedMath.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SignedMath.sol diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableMap.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableMap.sol new file mode 100644 index 00000000000..7f4e9115b19 --- /dev/null +++ b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableMap.sol @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableMap.sol) +// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js. + +pragma solidity ^0.8.0; + +import "./EnumerableSet.sol"; + +/** + * @dev Library for managing an enumerable variant of Solidity's + * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] + * type. + * + * Maps have the following properties: + * + * - Entries are added, removed, and checked for existence in constant time + * (O(1)). + * - Entries are enumerated in O(n). No guarantees are made on the ordering. + * + * ``` + * contract Example { + * // Add the library methods + * using EnumerableMap for EnumerableMap.UintToAddressMap; + * + * // Declare a set state variable + * EnumerableMap.UintToAddressMap private myMap; + * } + * ``` + * + * The following map types are supported: + * + * - `uint256 -> address` (`UintToAddressMap`) since v3.0.0 + * - `address -> uint256` (`AddressToUintMap`) since v4.6.0 + * - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0 + * - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0 + * - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0 + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure + * unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an + * array of EnumerableMap. + * ==== + */ +library EnumerableMap { + using EnumerableSet for EnumerableSet.Bytes32Set; + + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Map type with + // bytes32 keys and values. + // The Map implementation uses private functions, and user-facing + // implementations (such as Uint256ToAddressMap) are just wrappers around + // the underlying Map. + // This means that we can only create new EnumerableMaps for types that fit + // in bytes32. + + struct Bytes32ToBytes32Map { + // Storage of keys + EnumerableSet.Bytes32Set _keys; + mapping(bytes32 => bytes32) _values; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set( + Bytes32ToBytes32Map storage map, + bytes32 key, + bytes32 value + ) internal returns (bool) { + map._values[key] = value; + return map._keys.add(key); + } + + /** + * @dev Removes a key-value pair from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) { + delete map._values[key]; + return map._keys.remove(key); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) { + return map._keys.contains(key); + } + + /** + * @dev Returns the number of key-value pairs in the map. O(1). + */ + function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) { + return map._keys.length(); + } + + /** + * @dev Returns the key-value pair stored at position `index` in the map. O(1). + * + * Note that there are no guarantees on the ordering of entries inside the + * array, and it may change when more entries are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) { + bytes32 key = map._keys.at(index); + return (key, map._values[key]); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) { + bytes32 value = map._values[key]; + if (value == bytes32(0)) { + return (contains(map, key), bytes32(0)); + } else { + return (true, value); + } + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) { + bytes32 value = map._values[key]; + require(value != 0 || contains(map, key), "EnumerableMap: nonexistent key"); + return value; + } + + /** + * @dev Same as {get}, with a custom error message when `key` is not in the map. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryGet}. + */ + function get( + Bytes32ToBytes32Map storage map, + bytes32 key, + string memory errorMessage + ) internal view returns (bytes32) { + bytes32 value = map._values[key]; + require(value != 0 || contains(map, key), errorMessage); + return value; + } + + // UintToUintMap + + struct UintToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set( + UintToUintMap storage map, + uint256 key, + uint256 value + ) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToUintMap storage map, uint256 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the set. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (uint256(key), uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(key))); + } + + /** + * @dev Same as {get}, with a custom error message when `key` is not in the map. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryGet}. + */ + function get( + UintToUintMap storage map, + uint256 key, + string memory errorMessage + ) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(key), errorMessage)); + } + + // UintToAddressMap + + struct UintToAddressMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set( + UintToAddressMap storage map, + uint256 key, + address value + ) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToAddressMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the set. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (uint256(key), address(uint160(uint256(value)))); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); + return (success, address(uint160(uint256(value)))); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { + return address(uint160(uint256(get(map._inner, bytes32(key))))); + } + + /** + * @dev Same as {get}, with a custom error message when `key` is not in the map. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryGet}. + */ + function get( + UintToAddressMap storage map, + uint256 key, + string memory errorMessage + ) internal view returns (address) { + return address(uint160(uint256(get(map._inner, bytes32(key), errorMessage)))); + } + + // AddressToUintMap + + struct AddressToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set( + AddressToUintMap storage map, + address key, + uint256 value + ) internal returns (bool) { + return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(AddressToUintMap storage map, address key) internal returns (bool) { + return remove(map._inner, bytes32(uint256(uint160(key)))); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(AddressToUintMap storage map, address key) internal view returns (bool) { + return contains(map._inner, bytes32(uint256(uint160(key)))); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(AddressToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the set. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (address(uint160(uint256(key))), uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(AddressToUintMap storage map, address key) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(uint256(uint160(key))))); + } + + /** + * @dev Same as {get}, with a custom error message when `key` is not in the map. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryGet}. + */ + function get( + AddressToUintMap storage map, + address key, + string memory errorMessage + ) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(uint256(uint160(key))), errorMessage)); + } + + // Bytes32ToUintMap + + struct Bytes32ToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set( + Bytes32ToUintMap storage map, + bytes32 key, + uint256 value + ) internal returns (bool) { + return set(map._inner, key, bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) { + return remove(map._inner, key); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) { + return contains(map._inner, key); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(Bytes32ToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the set. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (key, uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, key); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) { + return uint256(get(map._inner, key)); + } + + /** + * @dev Same as {get}, with a custom error message when `key` is not in the map. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryGet}. + */ + function get( + Bytes32ToUintMap storage map, + bytes32 key, + string memory errorMessage + ) internal view returns (uint256) { + return uint256(get(map._inner, key, errorMessage)); + } +} \ No newline at end of file diff --git a/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/structs/EnumerableSet.sol b/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol similarity index 100% rename from contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/utils/structs/EnumerableSet.sol rename to contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol diff --git a/contracts/src/v0.8/vrf/VRFCoordinatorV2.sol b/contracts/src/v0.8/vrf/VRFCoordinatorV2.sol index 5150d263a8b..5acd3e74358 100644 --- a/contracts/src/v0.8/vrf/VRFCoordinatorV2.sol +++ b/contracts/src/v0.8/vrf/VRFCoordinatorV2.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.4; import {LinkTokenInterface} from "../shared/interfaces/LinkTokenInterface.sol"; import {BlockhashStoreInterface} from "./interfaces/BlockhashStoreInterface.sol"; -import {AggregatorV3Interface} from "../interfaces/AggregatorV3Interface.sol"; +import {AggregatorV3Interface} from "../shared/interfaces/AggregatorV3Interface.sol"; import {VRFCoordinatorV2Interface} from "./interfaces/VRFCoordinatorV2Interface.sol"; import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; import {IERC677Receiver} from "../shared/interfaces/IERC677Receiver.sol"; diff --git a/contracts/src/v0.8/vrf/VRFV2Wrapper.sol b/contracts/src/v0.8/vrf/VRFV2Wrapper.sol index 805c8d76cb6..abe479cb20a 100644 --- a/contracts/src/v0.8/vrf/VRFV2Wrapper.sol +++ b/contracts/src/v0.8/vrf/VRFV2Wrapper.sol @@ -6,7 +6,7 @@ import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; import {VRFConsumerBaseV2} from "./VRFConsumerBaseV2.sol"; import {LinkTokenInterface} from "../shared/interfaces/LinkTokenInterface.sol"; -import {AggregatorV3Interface} from "../interfaces/AggregatorV3Interface.sol"; +import {AggregatorV3Interface} from "../shared/interfaces/AggregatorV3Interface.sol"; import {VRFCoordinatorV2Interface} from "./interfaces/VRFCoordinatorV2Interface.sol"; import {VRFV2WrapperInterface} from "./interfaces/VRFV2WrapperInterface.sol"; import {VRFV2WrapperConsumerBase} from "./VRFV2WrapperConsumerBase.sol"; diff --git a/contracts/src/v0.8/vrf/dev/SubscriptionAPI.sol b/contracts/src/v0.8/vrf/dev/SubscriptionAPI.sol index e4708bb1fcf..d7cc5b86c5a 100644 --- a/contracts/src/v0.8/vrf/dev/SubscriptionAPI.sol +++ b/contracts/src/v0.8/vrf/dev/SubscriptionAPI.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol"; import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; -import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol"; +import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; import {IERC677Receiver} from "../../shared/interfaces/IERC677Receiver.sol"; import {IVRFSubscriptionV2Plus} from "./interfaces/IVRFSubscriptionV2Plus.sol"; diff --git a/contracts/src/v0.8/vrf/dev/VRFV2PlusWrapper.sol b/contracts/src/v0.8/vrf/dev/VRFV2PlusWrapper.sol index 9c3b983e300..a724b70a3d8 100644 --- a/contracts/src/v0.8/vrf/dev/VRFV2PlusWrapper.sol +++ b/contracts/src/v0.8/vrf/dev/VRFV2PlusWrapper.sol @@ -6,7 +6,7 @@ import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface. import {IVRFV2PlusMigrate} from "./interfaces/IVRFV2PlusMigrate.sol"; import {VRFConsumerBaseV2Plus} from "./VRFConsumerBaseV2Plus.sol"; import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol"; -import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol"; +import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; import {VRFV2PlusClient} from "./libraries/VRFV2PlusClient.sol"; import {IVRFV2PlusWrapper} from "./interfaces/IVRFV2PlusWrapper.sol"; import {VRFV2PlusWrapperConsumerBase} from "./VRFV2PlusWrapperConsumerBase.sol"; diff --git a/contracts/src/v0.8/vrf/testhelpers/VRFCoordinatorV2TestHelper.sol b/contracts/src/v0.8/vrf/testhelpers/VRFCoordinatorV2TestHelper.sol index f9385329686..c5d1d90c126 100644 --- a/contracts/src/v0.8/vrf/testhelpers/VRFCoordinatorV2TestHelper.sol +++ b/contracts/src/v0.8/vrf/testhelpers/VRFCoordinatorV2TestHelper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol"; +import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; // Ideally this contract should inherit from VRFCoordinatorV2 and delegate calls to VRFCoordinatorV2 // However, due to exceeding contract size limit, the logic from VRFCoordinatorV2 is ported over to this contract diff --git a/contracts/test/v0.6/AggregatorFacade.test.ts b/contracts/test/v0.6/AggregatorFacade.test.ts deleted file mode 100644 index f85c24ae6cd..00000000000 --- a/contracts/test/v0.6/AggregatorFacade.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { ethers } from 'hardhat' -import { numToBytes32, publicAbi } from '../test-helpers/helpers' -import { assert } from 'chai' -import { Contract, ContractFactory, Signer } from 'ethers' -import { getUsers } from '../test-helpers/setup' -import { convertFufillParams, decodeRunRequest } from '../test-helpers/oracle' -import { bigNumEquals, evmRevert } from '../test-helpers/matchers' - -let defaultAccount: Signer - -let linkTokenFactory: ContractFactory -let aggregatorFactory: ContractFactory -let oracleFactory: ContractFactory -let aggregatorFacadeFactory: ContractFactory - -before(async () => { - const users = await getUsers() - - defaultAccount = users.roles.defaultAccount - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - defaultAccount, - ) - aggregatorFactory = await ethers.getContractFactory( - 'src/v0.4/Aggregator.sol:Aggregator', - defaultAccount, - ) - oracleFactory = await ethers.getContractFactory( - 'src/v0.6/Oracle.sol:Oracle', - defaultAccount, - ) - aggregatorFacadeFactory = await ethers.getContractFactory( - 'src/v0.6/AggregatorFacade.sol:AggregatorFacade', - defaultAccount, - ) -}) - -describe('AggregatorFacade', () => { - const jobId1 = - '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000001' - const previousResponse = numToBytes32(54321) - const response = numToBytes32(67890) - const decimals = 18 - const description = 'LINK / USD: Historic Aggregator Facade' - - let link: Contract - let aggregator: Contract - let oc1: Contract - let facade: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(defaultAccount).deploy() - oc1 = await oracleFactory.connect(defaultAccount).deploy(link.address) - aggregator = await aggregatorFactory - .connect(defaultAccount) - .deploy(link.address, 0, 1, [oc1.address], [jobId1]) - facade = await aggregatorFacadeFactory - .connect(defaultAccount) - .deploy(aggregator.address, decimals, description) - - let requestTx = await aggregator.requestRateUpdate() - let receipt = await requestTx.wait() - let request = decodeRunRequest(receipt.logs?.[3]) - await oc1.fulfillOracleRequest( - ...convertFufillParams(request, previousResponse), - ) - requestTx = await aggregator.requestRateUpdate() - receipt = await requestTx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - await oc1.fulfillOracleRequest(...convertFufillParams(request, response)) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(facade, [ - 'aggregator', - 'decimals', - 'description', - 'getAnswer', - 'getRoundData', - 'getTimestamp', - 'latestAnswer', - 'latestRound', - 'latestRoundData', - 'latestTimestamp', - 'version', - ]) - }) - - describe('#constructor', () => { - it('uses the decimals set in the constructor', async () => { - bigNumEquals(decimals, await facade.decimals()) - }) - - it('uses the description set in the constructor', async () => { - assert.equal(description, await facade.description()) - }) - - it('sets the version to 2', async () => { - bigNumEquals(2, await facade.version()) - }) - }) - - describe('#getAnswer/latestAnswer', () => { - it('pulls the rate from the aggregator', async () => { - bigNumEquals(response, await facade.latestAnswer()) - const latestRound = await facade.latestRound() - bigNumEquals(response, await facade.getAnswer(latestRound)) - }) - }) - - describe('#getTimestamp/latestTimestamp', () => { - it('pulls the timestamp from the aggregator', async () => { - const height = await aggregator.latestTimestamp() - assert.notEqual('0', height.toString()) - bigNumEquals(height, await facade.latestTimestamp()) - const latestRound = await facade.latestRound() - bigNumEquals( - await aggregator.latestTimestamp(), - await facade.getTimestamp(latestRound), - ) - }) - }) - - describe('#getRoundData', () => { - it('assembles the requested round data', async () => { - const previousId = (await facade.latestRound()).sub(1) - const round = await facade.getRoundData(previousId) - bigNumEquals(previousId, round.roundId) - bigNumEquals(previousResponse, round.answer) - bigNumEquals(await facade.getTimestamp(previousId), round.startedAt) - bigNumEquals(await facade.getTimestamp(previousId), round.updatedAt) - bigNumEquals(previousId, round.answeredInRound) - }) - - it('returns zero data for non-existing rounds', async () => { - const roundId = 13371337 - await evmRevert(facade.getRoundData(roundId), 'No data present') - }) - }) - - describe('#latestRoundData', () => { - it('assembles the requested round data', async () => { - const latestId = await facade.latestRound() - const round = await facade.latestRoundData() - bigNumEquals(latestId, round.roundId) - bigNumEquals(response, round.answer) - bigNumEquals(await facade.getTimestamp(latestId), round.startedAt) - bigNumEquals(await facade.getTimestamp(latestId), round.updatedAt) - bigNumEquals(latestId, round.answeredInRound) - }) - - describe('when there is no latest round', () => { - beforeEach(async () => { - aggregator = await aggregatorFactory - .connect(defaultAccount) - .deploy(link.address, 0, 1, [oc1.address], [jobId1]) - facade = await aggregatorFacadeFactory - .connect(defaultAccount) - .deploy(aggregator.address, decimals, description) - }) - - it('assembles the requested round data', async () => { - await evmRevert(facade.latestRoundData(), 'No data present') - }) - }) - }) -}) diff --git a/contracts/test/v0.6/BasicConsumer.test.ts b/contracts/test/v0.6/BasicConsumer.test.ts deleted file mode 100644 index ce0b7c643e2..00000000000 --- a/contracts/test/v0.6/BasicConsumer.test.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { ethers } from 'hardhat' -import { toWei, increaseTime5Minutes, toHex } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { BigNumber, constants, Contract, ContractFactory } from 'ethers' -import { Roles, getUsers } from '../test-helpers/setup' -import { bigNumEquals, evmRevert } from '../test-helpers/matchers' -import { - convertFufillParams, - decodeRunRequest, - encodeOracleRequest, - RunRequest, -} from '../test-helpers/oracle' -import cbor from 'cbor' -import { makeDebug } from '../test-helpers/debug' - -const d = makeDebug('BasicConsumer') -let basicConsumerFactory: ContractFactory -let oracleFactory: ContractFactory -let linkTokenFactory: ContractFactory - -let roles: Roles - -before(async () => { - roles = (await getUsers()).roles - basicConsumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/BasicConsumer.sol:BasicConsumer', - roles.defaultAccount, - ) - oracleFactory = await ethers.getContractFactory( - 'src/v0.6/Oracle.sol:Oracle', - roles.oracleNode, - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) -}) - -describe('BasicConsumer', () => { - const specId = '0x4c7b7ffb66b344fbaa64995af81e355a'.padEnd(66, '0') - const currency = 'USD' - const payment = toWei('1') - let link: Contract - let oc: Contract - let cc: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - oc = await oracleFactory.connect(roles.oracleNode).deploy(link.address) - cc = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, oc.address, specId) - }) - - it('has a predictable gas price [ @skip-coverage ]', async () => { - const rec = await ethers.provider.getTransactionReceipt( - cc.deployTransaction.hash ?? '', - ) - assert.isBelow(rec.gasUsed?.toNumber() ?? -1, 1750000) - }) - - describe('#requestEthereumPrice', () => { - describe('without LINK', () => { - it('reverts', async () => - await expect(cc.requestEthereumPrice(currency, payment)).to.be.reverted) - }) - - describe('with LINK', () => { - beforeEach(async () => { - await link.transfer(cc.address, toWei('1')) - }) - - it('triggers a log event in the Oracle contract', async () => { - const tx = await cc.requestEthereumPrice(currency, payment) - const receipt = await tx.wait() - - const log = receipt?.logs?.[3] - assert.equal(log?.address.toLowerCase(), oc.address.toLowerCase()) - - const request = decodeRunRequest(log) - const expected = { - path: ['USD'], - get: 'https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD,EUR,JPY', - } - - assert.equal(toHex(specId), request.specId) - bigNumEquals(toWei('1'), request.payment) - assert.equal(cc.address.toLowerCase(), request.requester.toLowerCase()) - assert.equal(1, request.dataVersion) - assert.deepEqual(expected, cbor.decodeFirstSync(request.data)) - }) - - it('has a reasonable gas cost [ @skip-coverage ]', async () => { - const tx = await cc.requestEthereumPrice(currency, payment) - const receipt = await tx.wait() - - assert.isBelow(receipt?.gasUsed?.toNumber() ?? -1, 140000) - }) - }) - }) - - describe('#fulfillOracleRequest', () => { - const response = ethers.utils.formatBytes32String('1,000,000.00') - let request: RunRequest - - beforeEach(async () => { - await link.transfer(cc.address, toWei('1')) - const tx = await cc.requestEthereumPrice(currency, payment) - const receipt = await tx.wait() - - request = decodeRunRequest(receipt?.logs?.[3]) - }) - - it('records the data given to it by the oracle', async () => { - await oc - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - const currentPrice = await cc.currentPrice() - assert.equal(currentPrice, response) - }) - - it('logs the data given to it by the oracle', async () => { - const tx = await oc - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - const receipt = await tx.wait() - - assert.equal(2, receipt?.logs?.length) - const log = receipt?.logs?.[1] - - assert.equal(log?.topics[2], response) - }) - - describe('when the consumer does not recognize the request ID', () => { - let otherRequest: RunRequest - - beforeEach(async () => { - // Create a request directly via the oracle, rather than through the - // chainlink client (consumer). The client should not respond to - // fulfillment of this request, even though the oracle will faithfully - // forward the fulfillment to it. - const args = encodeOracleRequest( - toHex(specId), - cc.address, - basicConsumerFactory.interface.getSighash('fulfill'), - 43, - constants.HashZero, - ) - const tx = await link.transferAndCall(oc.address, 0, args) - const receipt = await tx.wait() - - otherRequest = decodeRunRequest(receipt?.logs?.[2]) - }) - - it('does not accept the data provided', async () => { - d('otherRequest %s', otherRequest) - await oc - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(otherRequest, response)) - - const received = await cc.currentPrice() - - assert.equal(ethers.utils.parseBytes32String(received), '') - }) - }) - - describe('when called by anyone other than the oracle contract', () => { - it('does not accept the data provided', async () => { - await evmRevert( - cc.connect(roles.oracleNode).fulfill(request.requestId, response), - ) - - const received = await cc.currentPrice() - assert.equal(ethers.utils.parseBytes32String(received), '') - }) - }) - }) - - describe('#cancelRequest', () => { - const depositAmount = toWei('1') - let request: RunRequest - - beforeEach(async () => { - await link.transfer(cc.address, depositAmount) - const tx = await cc.requestEthereumPrice(currency, payment) - const receipt = await tx.wait() - - request = decodeRunRequest(receipt.logs?.[3]) - }) - - describe('before 5 minutes', () => { - it('cant cancel the request', () => - evmRevert( - cc - .connect(roles.consumer) - .cancelRequest( - oc.address, - request.requestId, - request.payment, - request.callbackFunc, - request.expiration, - ), - )) - }) - - describe('after 5 minutes', () => { - it('can cancel the request', async () => { - await increaseTime5Minutes(ethers.provider) - - await cc - .connect(roles.consumer) - .cancelRequest( - oc.address, - request.requestId, - request.payment, - request.callbackFunc, - request.expiration, - ) - }) - }) - }) - - describe('#withdrawLink', () => { - const depositAmount = toWei('1') - - beforeEach(async () => { - await link.transfer(cc.address, depositAmount) - const balance = await link.balanceOf(cc.address) - bigNumEquals(balance, depositAmount) - }) - - it('transfers LINK out of the contract', async () => { - await cc.connect(roles.consumer).withdrawLink() - const ccBalance = await link.balanceOf(cc.address) - const consumerBalance = BigNumber.from( - await link.balanceOf(await roles.consumer.getAddress()), - ) - bigNumEquals(ccBalance, 0) - bigNumEquals(consumerBalance, depositAmount) - }) - }) -}) diff --git a/contracts/test/v0.6/BlockhashStore.test.ts b/contracts/test/v0.6/BlockhashStore.test.ts deleted file mode 100644 index 453b2eca3b1..00000000000 --- a/contracts/test/v0.6/BlockhashStore.test.ts +++ /dev/null @@ -1,285 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' - -let personas: Personas -let blockhashStoreTestHelperFactory: ContractFactory - -type TestBlocks = { - num: number - rlpHeader: Uint8Array - hash: string -} - -const mainnetBlocks: TestBlocks[] = [ - { - num: 10000467, - rlpHeader: ethers.utils.arrayify( - '0xf90215a058ee3c05e880cb25a3db92b9f1479c5453690ca97f9bcbb18d21965d3213578ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ea674fdde714fd979de3edf0f56aa9716b898ec8a0a448355652812a7d518b5c979a15bba02cfe4576d8eb61e8b5731ecc37f2bec6a0049f25ed97f9ed9a9c8521ab39cd2c48438d1d18c84dcab5bf494c19595bd462a0b1169f28bdbe5dd61ebc20b7a459be9d7fa898f5a3ba5fed6d502d94b9a8101bb901001000008180000210000080010001080310e004800c3040000060484000010804088050044302a500240041040010012120840002400005092000808000640012081000880010008040200208000004050800400002244044006041040040010890040504020040008004222502000800220000021800006400802036500000000400014640d00020002110000001440000001509543802080004210004100de04744a2810000000032250080810000502210c04289480800000423080800004000a020220030203000020001000000042c00420090000008003308459020e010a01000200190900040e81000040040000020000a8044001000202010000600c087086c49cadb1b57839898538398909483984b9e845eb02fbf94505059452d65746865726d696e652d6575312d34a06d0287c21536fac432714bd3f3712ff1a7e409faf1b10edac9b9547da1d4f7b188930531280477460c', - ), - hash: '0x4a65bcdf3466a16740b74849cc10fc57d4acb24cce148665482812699a400464', - }, - { - num: 10000468, - rlpHeader: ethers.utils.arrayify( - '0xf9020da04a65bcdf3466a16740b74849cc10fc57d4acb24cce148665482812699a400464a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479404668ec2f57cc15c381b461b9fedab5d451c8f7fa0bcd4ddbb7125a5c06df96d862921dc0bba8664b3f759a233fe565a615c0ab3eaa0087ab379852c83e4b660de1668fc93333201ad0d233167ea6cef8bacaf5cba2aa0d81855037b2a6b56eba0c2ed129fb4102fb831b0baa187a0f6e0c155400f7855b9010080040040200000000010102081000000500040010408040800010110000000008000005808020000902021818000210000000000081100401000400014400001041008000020448800180128800008000200000420e01200000000000000011000001000020000208000b42200a0008000510200080200008c002018108010014030200000080000000002000010008000011008004003081000400080100803040080040300000002044080480000000000008080101000000050000000000840000002200040000a0080000442008006005502800000040008000890201002022402208002900020900000000080000100100201080000000003400000004887086d57541477ba839898548398968083989147845eb02fc28c73706964657230380b03ac53a076c676a0ab090b373b6242851a4beab7b8cdc9d3ebe211747a255b78c0278c42880ea13d40042dd1e6', - ), - hash: '0x00fd2589a272b85ffaf63223641571bf95891c936b7514ee4e87a593e52de7c9', - }, - { - num: 10000469, - rlpHeader: ethers.utils.arrayify( - '0xf90211a000fd2589a272b85ffaf63223641571bf95891c936b7514ee4e87a593e52de7c9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347945a0b54d5dc17e0aadc383d2db43b0a0d3e029c4ca01b28d3b4e4d3442a9e6caed9f80b6a639bce6f3a283d4e004e6bb44e483ceeeba067c00d9067bc023b8fab8e3afd1bc0f2470f08003bdf9f167fbfeede2422ac4ea09d8b344d9ab1b7288f8c5e644c53b1a5288da6d6ee0e388ec76f291def48da15b90100c462095870a26a0804132e208110329710d459054558159c103208d44820002016108136199200061063699d8400254a22828c11b5512e3303c98ec7747cc02d00161880c2f2c580e806bccc04805190265f096601342058020a8324c277735d8202220412f03303201252a3000038883a4bb0010e6b004408306232150a84d110100d0c4b9d228022812602c05c801d20500d4ed10010ce2400428a96950a98050c00e603292a806c4983b25814880000440a23821191121996410c5110c949616c2066a4a0488087d4c226c14208042c00d609b5cc44051400219d93626818728612a9b18690e03c902014a900e0018828011494b80d4708799b0d8a83cace87086e64fefefb48839898558398968083986664845eb02fc7906574682d70726f2d687a662d74303032a09f1918a362b55ebd072cc9548fb74f89301d41c2a1feb13c08a1c2c3cb0606d88810dfa530069367fb', - ), - hash: '0x325fde74e261fc483a16506bbc711b645b043ad24c7db0136845a1be262cf0c9', - }, - { - num: 10000470, - rlpHeader: ethers.utils.arrayify( - '0xf90215a0325fde74e261fc483a16506bbc711b645b043ad24c7db0136845a1be262cf0c9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ea674fdde714fd979de3edf0f56aa9716b898ec8a020647cfa35563093442a12d80bf2bacb83da1de8340366677f3822591a334ccea066ad7285f6c5b6407f62c6b65a83aeaaa71ad9a97c2bb15139140f2dbb60f7e0a0c0e633851d0b5ce661ecc054517425e82425fcc6170db9693e5b5a6dd5ef6d6bb90100c0c000c1520708182080c8e461891c2402800a80d44a00034259414012012a5006a1416331181504902044960808f1129018800311621e920886804693749b10542400142e984580ccba634881c4156962200ecfb004000005468db44842781c59923110262660802315006106388b028412c42c000820c508e66b7851fa68002008144cd7860cd884280802915163399c168d5a11b0649486084110149469a1e61c31134204b903206566885180bc0426c0c6c0a4d408e182242f08180d204c624a040248425041ac028010d088820402ba4bd38c2d1215829300543465603822110500811290490148049300040e000c280086a09e8100089818ce480a887e87086c4965bf3c8a839898568398705c839847d2845eb02fe994505059452d65746865726d696e652d6575312d35a09d8ae288d0eede524f3ef5e6cfcc5ba07f380bc695bb71578a7b91cfa517071b8859d0976006378e52', - ), - hash: '0x5cf096dfd1fc2d2947a96fdec5377ab7beaa0eb00c80728a3b96f0864cec506a', - }, -] - -const maticBlocks: TestBlocks[] = [ - { - num: 10000467, - rlpHeader: ethers.utils.arrayify( - '0xf9025da0212093b89337e6741aca0c6c1cbfc64b56155bdcc3623fa9bcbfa0498fa135aba01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0ac0ec242516093308f7a2cc6965f38835eb3db69cba93401daef672666a3aefea0d06985e9ae671d22fb4b3d73ef4e074a448f66b4769ad8d59c3b8aef1ede15e2a00076d4897a88e08c25ca12c558f622d03d532d8b674e8c6b9847000b98dbe480b90100040000000200000000000000000000000000000000000000100000000000400000000000000000000000002800000000100080000000000000000001000000400000000000000000000000080002008000000000000000000021000000000000000000000200000000001000000008000000000008000001800800100000000000010000000010100000800000000000001000000200100000000000000000002000000004000000000000000080010000000000000000200000000000000040000000420000000000010000000000000004040004000000001000001000200100100080000000000400000000100000100000000000000000000000021000000e839898538401312d008302e54b84600df884b861d78301091883626f7288676f312e31352e35856c696e7578000000000000000003eb49c29f5facd767206f64b8a5c9b325bced5c9156f489c6281c68eddc9e5f2ef1177c02a99d8ab6216dcf2879eefddfc27c75ffa9ef6a2185ce9983d1434901a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x6c3b869ca26fece236545f7914d8249651d729852dc1445f53a94d5a59cdc9da', - }, - { - num: 10000468, - rlpHeader: ethers.utils.arrayify( - '0xf9025da06c3b869ca26fece236545f7914d8249651d729852dc1445f53a94d5a59cdc9daa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0fa236c78bbe5939cc62985e32582c2158468a5b2b4dd02d514edb0bea95f0fd3a0e05ccfb09764e5cd6811ef2c2616d4a57f187be84235e2569c9b8d70489f1a44a0aea27aed2ad1d553e30501e6fe47fee0842c3b7ce5867e579b29975f02ec4282b90100008000100000000000000400080800000009000000010020000000000800000000000000080000000000000000000000000080000080000820400000000000000000200000000000000000080000008000200000200000000003009000020000000200000010000000001000000000000000000000000000800040100000000000000000000010000000100100000000000000000102004000000040000000002000000008000000000000000000000000000000200000000000000000000041000000020000080001010000000000000008000000110000001001800020000000100000000001400000040000000000000010010000000001000000001000000e839898548401312d00830494ed84600df886b861d78301091883626f7288676f312e31352e35856c696e75780000000000000000aa8ed86143b48b6aa7170d2083c3a7be31cbdfdc40f39badb8747f4c2198279a71c0d3eb5d25f3b7da5a48b887f61e22fe0baa692aa03807ad12f6fe25af087e00a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x258aa48bde013579fbfef2e222bcc222b1f57bf898a71c623f9024229c9f6111', - }, - { - num: 10000469, - rlpHeader: ethers.utils.arrayify( - '0xf9025aa0258aa48bde013579fbfef2e222bcc222b1f57bf898a71c623f9024229c9f6111a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0fa236c78bbe5939cc62985e32582c2158468a5b2b4dd02d514edb0bea95f0fd3a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e839898558401312d008084600df888b861d78301091883626f7288676f312e31352e35856c696e75780000000000000000bd8668cc5d89583a7cc26fb96650e61f045ffe5248ae80c667ba7648df41e3d552060998ac151f2d15bd1b98f0a2a50c4281729a4c0aae4758a3bad280207c2901a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x611779767f1deb5a17723ec71d1b397b18a0fc9a40d282810a33bd6a0a5f46f9', - }, - { - num: 10000470, - rlpHeader: ethers.utils.arrayify( - '0xf9025aa0611779767f1deb5a17723ec71d1b397b18a0fc9a40d282810a33bd6a0a5f46f9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0fa236c78bbe5939cc62985e32582c2158468a5b2b4dd02d514edb0bea95f0fd3a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e839898568401312d008084600df88ab861d78301091883626f7288676f312e31352e35856c696e75780000000000000000b617675c3b01e98319508130e1a583d57ce6b3a8a97fa2fbdaa33673cc6c609d6f7c361c833838f54b724d3a83cdd73e2398bb147970cd0b057865386cb08e1300a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x2edf2f5c5faa5046b2304f76c92096a25e7c4343a7b75c36b29e8e9755d93397', - }, -] - -// The following headers from Binance Smart Chain were retrieved using `go run -// binance.go`, where binance.go contains -// -// package main -// -// import ( -// "context" -// "fmt" -// "log" -// "math/big" -// "math/rand" -// "strings" -// -// "github.com/ethereum/go-ethereum/ethclient" -// "github.com/ethereum/go-ethereum/rlp" -// ) -// -// var tsBlockTemplate = ` -// { -// num: %d, -// rlpHeader: ethers.utils.arrayify( -// '0x%x', -// ), -// hash: '0x%x', -// }, -// ` -// -// func main() { -// client, err := ethclient.Dial("https://bsc-dataseed.binance.org/") -// if err != nil { -// log.Fatal(err) -// } -// -// header, err := client.HeaderByNumber(context.Background(), nil) -// if err != nil { -// log.Fatal(err) -// } -// topBlockNum := header.Number.Int64() -// numBlocks := int64(4) -// if topBlockNum < numBlocks { -// log.Fatalf("need at least %d consecutive blocks", numBlocks) -// } -// targetBlock := int64(rand.Intn(int(topBlockNum - numBlocks))) -// simulatedHeadBlock := targetBlock + numBlocks - 1 -// for blockNum := targetBlock; blockNum <= simulatedHeadBlock; blockNum++ { -// header, err := client.HeaderByNumber(context.Background(), big.NewInt(blockNum)) -// if err != nil { -// log.Fatal(err) -// } -// s, err := rlp.EncodeToBytes(header) -// if err != nil { -// log.Fatalf("could not encode header: got error %s from %v", err, header) -// } -// // fmt.Printf("header for block number %d: 0x%x\n", blockNum, s) -// fmt.Printf(strings.TrimLeft(tsBlockTemplate, "\n"), blockNum, s, header.Hash()) -// } -// } -const binanceBlocks: TestBlocks[] = [ - { - num: 1875651, - rlpHeader: ethers.utils.arrayify( - '0xf9025da029c26248bebbe0d0acb209d13ac9337c4b5c313696c031dd63b3cd16cbdc0c21a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794b8f7166496996a7da21cf1f1b04d9b3e26a3d077a03f962867b5e86191c3280bd52c4249587e08ddfa9851cea981fb7a5721c9157aa05924ae05d17347687ba81d093aee159ccc65cefc8314b0515ef921e553df05a2a089af99a7afa586e7d67062d051df4255304bb730f6d62fdd3bdb207f1513b23bb901000100000000000000000800000000000000000000000200000000000000800000000000000200100000000000000800000000000000000000000000000000000000000000000000800000140800000008201000001000000202000000001200000000002002020000000000000000080000000000000002000000001000000000000002000000008010000000000000000002040080008400280000c00000081000400000004000000010000000020000000000000000000000000000000000000001000210200000000000000000000800000000000000000000000000002010000004000000000001000000000000000000000800020000000000000000000002831c9ec38401c9c380830789c2845f9faab1b861d883010002846765746888676f312e31332e34856c696e7578000000000000003311ee6830f31dc9116d8a59178b539d91eb6811c1d533c4a59bf77262689c552218bb1eae9cb9d6bf6e1066bea78052c8767313ace71c919d02e70760bd255401a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0xe0a935b1e37420ac1d855215bdad4730a5ffe315eda287c6c18aa86c426ede74', - }, - { - num: 1875652, - rlpHeader: ethers.utils.arrayify( - '0xf9025da0e0a935b1e37420ac1d855215bdad4730a5ffe315eda287c6c18aa86c426ede74a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c2be4ec20253b8642161bc3f444f53679c1f3d47a0dbf2d40cf5533b65ac9b5be35cac75f7b106244ba14ee476a832c28d46a53452a04f83b8a51d3e17b6a02a0caa79acc7597996c5b8c68dba12c33095ae086089eea02fa2642645b2de17227a6c18c3fa491f54b3bdfe8ac8e04924a33a005a0e9e61b901000100000100000000000008000000000000000000040000000000000000800000000000000000000000000000000800000800000000000400000000000020000040100080000000000000000800000000209000001000000200000000801000400800002002030000000000000100080000002000000002004000011000000002000100040000000000100000000000000000040100009000300000000000000002004000004000000000000000020000002000000010000000200000800000000001000280000000000000008000000000000000800000000000020000002000041000000000000001200020001000080000002a40020040000000000000000002831c9ec48401c9c38083044b40845f9faab4b861d883010002846765746888676f312e31332e34856c696e757800000000000000cfc02687b2394922055792a8e67dad566f6690de06b229d752433b2067207b5f43b9f3c63f91cea5a79bbfc51d9132b933a706ab504038a92f37d57af2bb6c2e01a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x629e5abcae42940e00d7b38aa7b2ecccfbab582cb7a0b2c3658c2dad8e66549d', - }, - { - num: 1875653, - rlpHeader: ethers.utils.arrayify( - '0xf9025da0629e5abcae42940e00d7b38aa7b2ecccfbab582cb7a0b2c3658c2dad8e66549da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ce2fd7544e0b2cc94692d4a704debef7bcb61328a0718e7db53041585a814d658c32c88fd550c2c6d200826020008925e0a0f7967fa000fbf842e492a47cc9786885783259a08aed71055e78216006408932515fd960a0c7ffeb2189b8fcde43733cf1958cdb1c38c44052cfbb41125382240c232a98f8b901000000000000000000000000000000000000000002000000000004000000000000000000010000000000000000000000000000000000000200000000004020200000010000000800000000208800000000201000000000000000080000000000000000002002220000000000000000080000000000000000000000001000000000100000000000080010000000000000000000040000000000000000000000000002000000000008000000004000000000000000000000200000000000000000000000000202000000000000000000000000000000000008000000000000002080001000000000000001000000000000000000080100000000000000000000000002831c9ec58401c9c38083025019845f9faab7b861d883010002846765746888676f312e31332e34856c696e7578000000000000008c3c7a5c83e930fbd9d14f83c9b3931f032f0f678919c35b8b32ca6dae9948950bfa326fae134fa234fa7b84c06bdc3f7c6d6414c2a266df1339e563be8bd9cc00a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0xae8574651adabfd0ca55e2cee0e2e639ced73ec1cc0a35debeeceee6943442a9', - }, - { - num: 1875654, - rlpHeader: ethers.utils.arrayify( - '0xf9025da0ae8574651adabfd0ca55e2cee0e2e639ced73ec1cc0a35debeeceee6943442a9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794d6caa02bbebaebb5d7e581e4b66559e635f805ffa02df6a1173c63ec0a8acc46c818670f030aece1154b9f3bbc70f46a8427dd8dd6a0fa8835c499682d8c90759ff9ea1c291048755b967e48880a0fc21d19ec034a59a0b4e22607cb105c04156044b3f98c2cecae1553b45aa9b6044c37573791a27576b901000200000008000000000001000000000000000000000020000000000000020000000000000000000000000000000000000000000000000040000000000220000000000000000400000000001802000000201000000000000000000000000000000000002002020000000000000000080000000000000000000000001000000000000000000000100000000000000000000000040000000000000000010200200002000400000000400000000200000000000000080000000000000000000008000000000200000000000000000000000000000000000000000000000000002000001000000000000001000000000000000000000000000000000008080000000002831c9ec68401c9c3808301e575845f9faabab861d883010002846765746888676f312e31332e34856c696e757800000000000000399e73b0e963ec029e815623a414aa852508a28dd9799a1bf4e2380c8db687a46cc5b6cc20352ae21e35cfd28124a32fcd49ac8fac5b03901b3e03963e4fff5801a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x189990455c59a5dea78071df9a2008ede292ff0a062fc5c4c6ca35fbe476f834', - }, -] - -before(async () => { - personas = (await getUsers()).personas - blockhashStoreTestHelperFactory = await ethers.getContractFactory( - 'src/v0.6/tests/BlockhashStoreTestHelper.sol:BlockhashStoreTestHelper', - personas.Default, - ) -}) - -runBlockhashStoreTests(mainnetBlocks, 'Ethereum') -runBlockhashStoreTests(maticBlocks, 'Matic') -runBlockhashStoreTests(binanceBlocks, 'Binance Smart Chain') - -async function runBlockhashStoreTests( - blocks: TestBlocks[], - description: string, -) { - describe(`BlockhashStore (${description})`, () => { - let blockhashStoreTestHelper: Contract - - beforeEach(async () => { - blockhashStoreTestHelper = await blockhashStoreTestHelperFactory - .connect(personas.Default) - .deploy() - - const [lastBlock] = blocks.slice(-1) - await blockhashStoreTestHelper - .connect(personas.Default) - .godmodeSetHash(lastBlock.num, lastBlock.hash) - assert.strictEqual( - await blockhashStoreTestHelper.getBlockhash(lastBlock.num), - lastBlock.hash, - ) - }) - - it('getBlockhash reverts for unknown blockhashes', async () => { - await expect( - blockhashStoreTestHelper.getBlockhash(99999999), - ).to.be.revertedWith('blockhash not found in store') - }) - - it('storeVerifyHeader records valid blockhashes', async () => { - for (let i = blocks.length - 2; i >= 0; i--) { - assert.strictEqual( - ethers.utils.keccak256(blocks[i + 1].rlpHeader), - await blockhashStoreTestHelper.getBlockhash(blocks[i + 1].num), - ) - await blockhashStoreTestHelper - .connect(personas.Default) - .storeVerifyHeader(blocks[i].num, blocks[i + 1].rlpHeader) - assert.strictEqual( - await blockhashStoreTestHelper.getBlockhash(blocks[i].num), - blocks[i].hash, - ) - } - }) - - it('storeVerifyHeader rejects unknown headers', async () => { - const unknownBlock = blocks[0] - await expect( - blockhashStoreTestHelper - .connect(personas.Default) - .storeVerifyHeader(unknownBlock.num - 1, unknownBlock.rlpHeader), - ).to.be.revertedWith('header has unknown blockhash') - }) - - it('storeVerifyHeader rejects corrupted headers', async () => { - const [lastBlock] = blocks.slice(-1) - const modifiedHeader = new Uint8Array(lastBlock.rlpHeader) - modifiedHeader[137] += 1 - await expect( - blockhashStoreTestHelper - .connect(personas.Default) - .storeVerifyHeader(lastBlock.num - 1, modifiedHeader), - ).to.be.revertedWith('header has unknown blockhash') - }) - - it('store accepts recent block numbers', async () => { - await ethers.provider.send('evm_mine', []) - - const n = (await ethers.provider.getBlockNumber()) - 1 - await blockhashStoreTestHelper.connect(personas.Default).store(n) - - assert.equal( - await blockhashStoreTestHelper.getBlockhash(n), - (await ethers.provider.getBlock(n)).hash, - ) - }) - - it('store rejects future block numbers', async () => { - await expect( - blockhashStoreTestHelper.connect(personas.Default).store(99999999999), - ).to.be.revertedWith('blockhash(n) failed') - }) - - it('store rejects old block numbers', async () => { - for (let i = 0; i < 300; i++) { - await ethers.provider.send('evm_mine', []) - } - - await expect( - blockhashStoreTestHelper - .connect(personas.Default) - .store((await ethers.provider.getBlockNumber()) - 256), - ).to.be.revertedWith('blockhash(n) failed') - }) - - it('storeEarliest works', async () => { - for (let i = 0; i < 300; i++) { - await ethers.provider.send('evm_mine', []) - } - - await blockhashStoreTestHelper.connect(personas.Default).storeEarliest() - - const n = (await ethers.provider.getBlockNumber()) - 256 - assert.equal( - await blockhashStoreTestHelper.getBlockhash(n), - (await ethers.provider.getBlock(n)).hash, - ) - }) - }) -} diff --git a/contracts/test/v0.6/Chainlink.test.ts b/contracts/test/v0.6/Chainlink.test.ts deleted file mode 100644 index f3587dc30a7..00000000000 --- a/contracts/test/v0.6/Chainlink.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi, decodeDietCBOR, hexToBuf } from '../test-helpers/helpers' -import { assert } from 'chai' -import { Contract, ContractFactory, providers, Signer } from 'ethers' -import { Roles, getUsers } from '../test-helpers/setup' -import { makeDebug } from '../test-helpers/debug' - -const debug = makeDebug('ChainlinkTestHelper') -let concreteChainlinkFactory: ContractFactory - -let roles: Roles - -before(async () => { - roles = (await getUsers()).roles - concreteChainlinkFactory = await ethers.getContractFactory( - 'src/v0.6/tests/ChainlinkTestHelper.sol:ChainlinkTestHelper', - roles.defaultAccount, - ) -}) - -describe('ChainlinkTestHelper', () => { - let ccl: Contract - let defaultAccount: Signer - - beforeEach(async () => { - defaultAccount = roles.defaultAccount - ccl = await concreteChainlinkFactory.connect(defaultAccount).deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(ccl, [ - 'add', - 'addBytes', - 'addInt', - 'addStringArray', - 'addUint', - 'closeEvent', - 'setBuffer', - ]) - }) - - async function parseCCLEvent(tx: providers.TransactionResponse) { - const receipt = await tx.wait() - const data = receipt.logs?.[0].data - const d = debug.extend('parseCCLEvent') - d('data %s', data) - return ethers.utils.defaultAbiCoder.decode(['bytes'], data ?? '') - } - - describe('#close', () => { - it('handles empty payloads', async () => { - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, {}) - }) - }) - - describe('#setBuffer', () => { - it('emits the buffer', async () => { - await ccl.setBuffer('0xA161616162') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { a: 'b' }) - }) - }) - - describe('#add', () => { - it('stores and logs keys and values', async () => { - await ccl.add('first', 'word!!') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 'word!!' }) - }) - - it('handles two entries', async () => { - await ccl.add('first', 'uno') - await ccl.add('second', 'dos') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 'uno', - second: 'dos', - }) - }) - }) - - describe('#addBytes', () => { - it('stores and logs keys and values', async () => { - await ccl.addBytes('first', '0xaabbccddeeff') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - const expected = hexToBuf('0xaabbccddeeff') - assert.deepEqual(decoded, { first: expected }) - }) - - it('handles two entries', async () => { - await ccl.addBytes('first', '0x756E6F') - await ccl.addBytes('second', '0x646F73') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - const expectedFirst = hexToBuf('0x756E6F') - const expectedSecond = hexToBuf('0x646F73') - assert.deepEqual(decoded, { - first: expectedFirst, - second: expectedSecond, - }) - }) - - it('handles strings', async () => { - await ccl.addBytes('first', ethers.utils.toUtf8Bytes('apple')) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - const expected = ethers.utils.toUtf8Bytes('apple') - assert.deepEqual(decoded, { first: expected }) - }) - }) - - describe('#addInt', () => { - it('stores and logs keys and values', async () => { - await ccl.addInt('first', 1) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 1 }) - }) - - it('handles two entries', async () => { - await ccl.addInt('first', 1) - await ccl.addInt('second', 2) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 1, - second: 2, - }) - }) - }) - - describe('#addUint', () => { - it('stores and logs keys and values', async () => { - await ccl.addUint('first', 1) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 1 }) - }) - - it('handles two entries', async () => { - await ccl.addUint('first', 1) - await ccl.addUint('second', 2) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 1, - second: 2, - }) - }) - }) - - describe('#addStringArray', () => { - it('stores and logs keys and values', async () => { - await ccl.addStringArray('word', [ - ethers.utils.formatBytes32String('seinfeld'), - ethers.utils.formatBytes32String('"4"'), - ethers.utils.formatBytes32String('LIFE'), - ]) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { word: ['seinfeld', '"4"', 'LIFE'] }) - }) - }) -}) diff --git a/contracts/test/v0.6/ChainlinkClient.test.ts b/contracts/test/v0.6/ChainlinkClient.test.ts deleted file mode 100644 index bfd43d7f3f9..00000000000 --- a/contracts/test/v0.6/ChainlinkClient.test.ts +++ /dev/null @@ -1,376 +0,0 @@ -import { ethers } from 'hardhat' -import { assert } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { Roles, getUsers } from '../test-helpers/setup' -import { - convertFufillParams, - decodeCCRequest, - decodeRunRequest, - RunRequest, -} from '../test-helpers/oracle' -import { decodeDietCBOR } from '../test-helpers/helpers' -import { evmRevert } from '../test-helpers/matchers' - -let concreteChainlinkClientFactory: ContractFactory -let emptyOracleFactory: ContractFactory -let getterSetterFactory: ContractFactory -let oracleFactory: ContractFactory -let linkTokenFactory: ContractFactory - -let roles: Roles - -before(async () => { - roles = (await getUsers()).roles - - concreteChainlinkClientFactory = await ethers.getContractFactory( - 'src/v0.6/tests/ChainlinkClientTestHelper.sol:ChainlinkClientTestHelper', - roles.defaultAccount, - ) - emptyOracleFactory = await ethers.getContractFactory( - 'src/v0.6/tests/EmptyOracle.sol:EmptyOracle', - roles.defaultAccount, - ) - getterSetterFactory = await ethers.getContractFactory( - 'src/v0.5/tests/GetterSetter.sol:GetterSetter', - roles.defaultAccount, - ) - oracleFactory = await ethers.getContractFactory( - 'src/v0.4/Oracle.sol:Oracle', - roles.defaultAccount, - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) -}) - -describe('ChainlinkClientTestHelper', () => { - const specId = - '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000000' - let cc: Contract - let gs: Contract - let oc: Contract - let newoc: Contract - let link: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - oc = await oracleFactory.connect(roles.defaultAccount).deploy(link.address) - newoc = await oracleFactory - .connect(roles.defaultAccount) - .deploy(link.address) - gs = await getterSetterFactory.connect(roles.defaultAccount).deploy() - cc = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, oc.address) - }) - - describe('#newRequest', () => { - it('forwards the information to the oracle contract through the link token', async () => { - const tx = await cc.publicNewRequest( - specId, - gs.address, - ethers.utils.toUtf8Bytes('requestedBytes32(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - - assert.equal(1, receipt.logs?.length) - const [jId, cbAddr, cbFId, cborData] = receipt.logs - ? decodeCCRequest(receipt.logs[0]) - : [] - const params = decodeDietCBOR(cborData ?? '') - - assert.equal(specId, jId) - assert.equal(gs.address, cbAddr) - assert.equal('0xed53e511', cbFId) - assert.deepEqual({}, params) - }) - }) - - describe('#chainlinkRequest(Request)', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const { events, logs } = await tx.wait() - - assert.equal(4, events?.length) - - assert.equal(logs?.[0].address, cc.address) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - }) - - describe('#chainlinkRequestTo(Request)', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { events } = await tx.wait() - - assert.equal(4, events?.length) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - - it('emits an event on the target oracle contract', async () => { - const tx = await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { logs } = await tx.wait() - const event = logs && newoc.interface.parseLog(logs[3]) - - assert.equal(4, logs?.length) - assert.equal(event?.name, 'OracleRequest') - }) - - it('does not modify the stored oracle address', async () => { - await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const actualOracleAddress = await cc.publicOracleAddress() - assert.equal(oc.address, actualOracleAddress) - }) - }) - - describe('#cancelChainlinkRequest', () => { - let requestId: string - // a concrete chainlink attached to an empty oracle - let ecc: Contract - - beforeEach(async () => { - const emptyOracle = await emptyOracleFactory - .connect(roles.defaultAccount) - .deploy() - ecc = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, emptyOracle.address) - - const tx = await ecc.publicRequest( - specId, - ecc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { events } = await tx.wait() - requestId = (events?.[0]?.args as any).id - }) - - it('emits an event from the contract showing the run was cancelled', async () => { - const tx = await ecc.publicCancelRequest( - requestId, - 0, - ethers.utils.hexZeroPad('0x', 4), - 0, - ) - const { events } = await tx.wait() - - assert.equal(1, events?.length) - assert.equal(events?.[0].event, 'ChainlinkCancelled') - assert.equal(requestId, (events?.[0].args as any).id) - }) - - it('throws if given a bogus event ID', async () => { - await evmRevert( - ecc.publicCancelRequest( - ethers.utils.formatBytes32String('bogusId'), - 0, - ethers.utils.hexZeroPad('0x', 4), - 0, - ), - ) - }) - }) - - describe('#recordChainlinkFulfillment(modifier)', () => { - let request: RunRequest - - beforeEach(async () => { - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { logs } = await tx.wait() - - request = decodeRunRequest(logs?.[3]) - }) - - it('emits an event marking the request fulfilled', async () => { - const tx = await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - const { logs } = await tx.wait() - - const event = logs && cc.interface.parseLog(logs[0]) - - assert.equal(1, logs?.length) - assert.equal(event?.name, 'ChainlinkFulfilled') - assert.equal(request.requestId, event?.args.id) - }) - - it('should only allow one fulfillment per id', async () => { - await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - await evmRevert( - oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Must have a valid requestId', - ) - }) - - it('should only allow the oracle to fulfill the request', async () => { - await evmRevert( - oc - .connect(roles.stranger) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Not an authorized node to fulfill requests', - ) - }) - }) - - describe('#fulfillChainlinkRequest(function)', () => { - let request: RunRequest - - beforeEach(async () => { - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes( - 'publicFulfillChainlinkRequest(bytes32,bytes32)', - ), - 0, - ) - const { logs } = await tx.wait() - - request = decodeRunRequest(logs?.[3]) - }) - - it('emits an event marking the request fulfilled', async () => { - const tx = await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - const { logs } = await tx.wait() - const event = logs && cc.interface.parseLog(logs[0]) - - assert.equal(1, logs?.length) - assert.equal(event?.name, 'ChainlinkFulfilled') - assert.equal(request.requestId, event?.args?.id) - }) - - it('should only allow one fulfillment per id', async () => { - await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - await evmRevert( - oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Must have a valid requestId', - ) - }) - - it('should only allow the oracle to fulfill the request', async () => { - await evmRevert( - oc - .connect(roles.stranger) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Not an authorized node to fulfill requests', - ) - }) - }) - - describe('#chainlinkToken', () => { - it('returns the Link Token address', async () => { - const addr = await cc.publicChainlinkToken() - assert.equal(addr, link.address) - }) - }) - - describe('#addExternalRequest', () => { - let mock: Contract - let request: RunRequest - - beforeEach(async () => { - mock = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, oc.address) - - const tx = await cc.publicRequest( - specId, - mock.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const receipt = await tx.wait() - - request = decodeRunRequest(receipt.logs?.[3]) - await mock.publicAddExternalRequest(oc.address, request.requestId) - }) - - it('allows the external request to be fulfilled', async () => { - await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - }) - - it('does not allow the same requestId to be used', async () => { - await evmRevert( - cc.publicAddExternalRequest(newoc.address, request.requestId), - ) - }) - }) -}) diff --git a/contracts/test/v0.6/CheckedMath.test.ts b/contracts/test/v0.6/CheckedMath.test.ts deleted file mode 100644 index 14520d9d9b9..00000000000 --- a/contracts/test/v0.6/CheckedMath.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: MIT -// Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/c9630526e24ba53d9647787588a19ffaa3dd65e1/test/math/SignedSafeMath.test.js - -import { ethers } from 'hardhat' -import { assert } from 'chai' -import { BigNumber, constants, Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals } from '../test-helpers/matchers' - -let mathFactory: ContractFactory -let personas: Personas - -before(async () => { - personas = (await getUsers()).personas - mathFactory = await ethers.getContractFactory( - 'src/v0.6/tests/CheckedMathTestHelper.sol:CheckedMathTestHelper', - personas.Default, - ) -}) - -const int256Max = constants.MaxInt256 -const int256Min = constants.MinInt256 - -describe('CheckedMath', () => { - let math: Contract - - beforeEach(async () => { - math = await mathFactory.connect(personas.Default).deploy() - }) - - describe('#add', () => { - const a = BigNumber.from('1234') - const b = BigNumber.from('5678') - - it('is commutative', async () => { - const c1 = await math.add(a, b) - const c2 = await math.add(b, a) - - bigNumEquals(c1.result, c2.result) - assert.isTrue(c1.ok) - assert.isTrue(c2.ok) - }) - - it('is commutative with big numbers', async () => { - const c1 = await math.add(int256Max, int256Min) - const c2 = await math.add(int256Min, int256Max) - - bigNumEquals(c1.result, c2.result) - assert.isTrue(c1.ok) - assert.isTrue(c2.ok) - }) - - it('returns false when overflowing', async () => { - const c1 = await math.add(int256Max, 1) - const c2 = await math.add(1, int256Max) - - bigNumEquals(0, c1.result) - bigNumEquals(0, c2.result) - assert.isFalse(c1.ok) - assert.isFalse(c2.ok) - }) - - it('returns false when underflowing', async () => { - const c1 = await math.add(int256Min, -1) - const c2 = await math.add(-1, int256Min) - - bigNumEquals(0, c1.result) - bigNumEquals(0, c2.result) - assert.isFalse(c1.ok) - assert.isFalse(c2.ok) - }) - }) - - describe('#sub', () => { - const a = BigNumber.from('1234') - const b = BigNumber.from('5678') - - it('subtracts correctly if it does not overflow and the result is negative', async () => { - const c = await math.sub(a, b) - const expected = a.sub(b) - - bigNumEquals(expected, c.result) - assert.isTrue(c.ok) - }) - - it('subtracts correctly if it does not overflow and the result is positive', async () => { - const c = await math.sub(b, a) - const expected = b.sub(a) - - bigNumEquals(expected, c.result) - assert.isTrue(c.ok) - }) - - it('returns false on overflow', async () => { - const c = await math.sub(int256Max, -1) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - - it('returns false on underflow', async () => { - const c = await math.sub(int256Min, 1) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - }) - - describe('#mul', () => { - const a = BigNumber.from('5678') - const b = BigNumber.from('-1234') - - it('is commutative', async () => { - const c1 = await math.mul(a, b) - const c2 = await math.mul(b, a) - - bigNumEquals(c1.result, c2.result) - assert.isTrue(c1.ok) - assert.isTrue(c2.ok) - }) - - it('multiplies by 0 correctly', async () => { - const c = await math.mul(a, 0) - - bigNumEquals(0, c.result) - assert.isTrue(c.ok) - }) - - it('returns false on multiplication overflow', async () => { - const c = await math.mul(int256Max, 2) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - - it('returns false when the integer minimum is negated', async () => { - const c = await math.mul(int256Min, -1) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - }) - - describe('#div', () => { - const a = BigNumber.from('5678') - const b = BigNumber.from('-5678') - - it('divides correctly', async () => { - const c = await math.div(a, b) - - bigNumEquals(a.div(b), c.result) - assert.isTrue(c.ok) - }) - - it('divides a 0 numerator correctly', async () => { - const c = await math.div(0, a) - - bigNumEquals(0, c.result) - assert.isTrue(c.ok) - }) - - it('returns complete number result on non-even division', async () => { - const c = await math.div(7000, 5678) - - bigNumEquals(1, c.result) - assert.isTrue(c.ok) - }) - - it('reverts when 0 is the denominator', async () => { - const c = await math.div(a, 0) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - - it('reverts on underflow with a negative denominator', async () => { - const c = await math.div(int256Min, -1) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - }) -}) diff --git a/contracts/test/v0.6/DeviationFlaggingValidator.test.ts b/contracts/test/v0.6/DeviationFlaggingValidator.test.ts deleted file mode 100644 index f79a8c7aa47..00000000000 --- a/contracts/test/v0.6/DeviationFlaggingValidator.test.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { BigNumber, Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals } from '../test-helpers/matchers' - -let personas: Personas -let validatorFactory: ContractFactory -let flagsFactory: ContractFactory -let acFactory: ContractFactory - -before(async () => { - personas = (await getUsers()).personas - validatorFactory = await ethers.getContractFactory( - 'src/v0.6/DeviationFlaggingValidator.sol:DeviationFlaggingValidator', - personas.Carol, - ) - flagsFactory = await ethers.getContractFactory( - 'src/v0.6/Flags.sol:Flags', - personas.Carol, - ) - acFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - personas.Carol, - ) -}) - -describe('DeviationFlaggingValidator', () => { - let validator: Contract - let flags: Contract - let ac: Contract - const flaggingThreshold = 10000 // 10% - const previousRoundId = 2 - const previousValue = 1000000 - const currentRoundId = 3 - const currentValue = 1000000 - - beforeEach(async () => { - ac = await acFactory.connect(personas.Carol).deploy() - flags = await flagsFactory.connect(personas.Carol).deploy(ac.address) - validator = await validatorFactory - .connect(personas.Carol) - .deploy(flags.address, flaggingThreshold) - await ac.connect(personas.Carol).addAccess(validator.address) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(validator, [ - 'THRESHOLD_MULTIPLIER', - 'flaggingThreshold', - 'flags', - 'isValid', - 'setFlagsAddress', - 'setFlaggingThreshold', - 'validate', - // Owned methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('sets the arguments passed in', async () => { - assert.equal(flags.address, await validator.flags()) - bigNumEquals(flaggingThreshold, await validator.flaggingThreshold()) - }) - }) - - describe('#validate', () => { - describe('when the deviation is greater than the threshold', () => { - const currentValue = 1100010 - - it('does raises a flag for the calling address', async () => { - await expect( - validator - .connect(personas.Nelly) - .validate( - previousRoundId, - previousValue, - currentRoundId, - currentValue, - ), - ) - .to.emit(flags, 'FlagRaised') - .withArgs(await personas.Nelly.getAddress()) - }) - - it('uses less than the gas allotted by the aggregator', async () => { - const tx = await validator - .connect(personas.Nelly) - .validate( - previousRoundId, - previousValue, - currentRoundId, - currentValue, - ) - const receipt = await tx.wait() - assert(receipt) - if (receipt && receipt.gasUsed) { - assert.isAbove(receipt.gasUsed.toNumber(), 60000) - } - }) - }) - - describe('when the deviation is less than or equal to the threshold', () => { - const currentValue = 1100009 - - it('does raises a flag for the calling address', async () => { - await expect( - validator - .connect(personas.Nelly) - .validate( - previousRoundId, - previousValue, - currentRoundId, - currentValue, - ), - ).to.not.emit(flags, 'FlagRaised') - }) - - it('uses less than the gas allotted by the aggregator', async () => { - const tx = await validator - .connect(personas.Nelly) - .validate( - previousRoundId, - previousValue, - currentRoundId, - currentValue, - ) - const receipt = await tx.wait() - assert(receipt) - if (receipt && receipt.gasUsed) { - assert.isAbove(receipt.gasUsed.toNumber(), 24000) - } - }) - }) - - describe('when called with a previous value of zero', () => { - const previousValue = 0 - - it('does not raise any flags', async () => { - const tx = await validator - .connect(personas.Nelly) - .validate( - previousRoundId, - previousValue, - currentRoundId, - currentValue, - ) - const receipt = await tx.wait() - assert.equal(0, receipt.events?.length) - }) - }) - }) - - describe('#isValid', () => { - const previousValue = 1000000 - - describe('with a validation larger than the deviation', () => { - const currentValue = 1100010 - it('is not valid', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('with a validation smaller than the deviation', () => { - const currentValue = 1100009 - it('is valid', async () => { - assert.isTrue( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('with positive previous and negative current', () => { - const previousValue = 1000000 - const currentValue = -900000 - it('correctly detects the difference', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('with negative previous and positive current', () => { - const previousValue = -900000 - const currentValue = 1000000 - it('correctly detects the difference', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('when the difference overflows', () => { - const previousValue = BigNumber.from(2).pow(255).sub(1) - const currentValue = BigNumber.from(-1) - - it('does not revert and returns false', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('when the rounding overflows', () => { - const previousValue = BigNumber.from(2).pow(255).div(10000) - const currentValue = BigNumber.from(1) - - it('does not revert and returns false', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('when the division overflows', () => { - const previousValue = BigNumber.from(2).pow(255).sub(1) - const currentValue = BigNumber.from(-1) - - it('does not revert and returns false', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - }) - - describe('#setFlaggingThreshold', () => { - const newThreshold = 777 - - it('changes the flagging thresold', async () => { - assert.equal(flaggingThreshold, await validator.flaggingThreshold()) - - await validator.connect(personas.Carol).setFlaggingThreshold(newThreshold) - - assert.equal(newThreshold, await validator.flaggingThreshold()) - }) - - it('emits a log event only when actually changed', async () => { - await expect( - validator.connect(personas.Carol).setFlaggingThreshold(newThreshold), - ) - .to.emit(validator, 'FlaggingThresholdUpdated') - .withArgs(flaggingThreshold, newThreshold) - - await expect( - validator.connect(personas.Carol).setFlaggingThreshold(newThreshold), - ).to.not.emit(validator, 'FlaggingThresholdUpdated') - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - validator.connect(personas.Neil).setFlaggingThreshold(newThreshold), - ).to.be.revertedWith('Only callable by owner') - }) - }) - }) - - describe('#setFlagsAddress', () => { - const newFlagsAddress = '0x0123456789012345678901234567890123456789' - - it('changes the flags address', async () => { - assert.equal(flags.address, await validator.flags()) - - await validator.connect(personas.Carol).setFlagsAddress(newFlagsAddress) - - assert.equal(newFlagsAddress, await validator.flags()) - }) - - it('emits a log event only when actually changed', async () => { - await expect( - validator.connect(personas.Carol).setFlagsAddress(newFlagsAddress), - ) - .to.emit(validator, 'FlagsAddressUpdated') - .withArgs(flags.address, newFlagsAddress) - - await expect( - validator.connect(personas.Carol).setFlagsAddress(newFlagsAddress), - ).to.not.emit(validator, 'FlagsAddressUpdated') - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - validator.connect(personas.Neil).setFlagsAddress(newFlagsAddress), - ).to.be.revertedWith('Only callable by owner') - }) - }) - }) -}) diff --git a/contracts/test/v0.6/Flags.test.ts b/contracts/test/v0.6/Flags.test.ts deleted file mode 100644 index 8f589184299..00000000000 --- a/contracts/test/v0.6/Flags.test.ts +++ /dev/null @@ -1,405 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' - -let personas: Personas - -let controllerFactory: ContractFactory -let flagsFactory: ContractFactory -let consumerFactory: ContractFactory - -let controller: Contract -let flags: Contract -let consumer: Contract - -before(async () => { - personas = (await getUsers()).personas - controllerFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - personas.Nelly, - ) - consumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/FlagsTestHelper.sol:FlagsTestHelper', - personas.Nelly, - ) - flagsFactory = await ethers.getContractFactory( - 'src/v0.6/Flags.sol:Flags', - personas.Nelly, - ) -}) - -describe('Flags', () => { - beforeEach(async () => { - controller = await controllerFactory.deploy() - flags = await flagsFactory.deploy(controller.address) - await flags.disableAccessCheck() - consumer = await consumerFactory.deploy(flags.address) - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(flags, [ - 'getFlag', - 'getFlags', - 'lowerFlags', - 'raiseFlag', - 'raiseFlags', - 'raisingAccessController', - 'setRaisingAccessController', - // Ownable methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - // AccessControl methods: - 'addAccess', - 'disableAccessCheck', - 'enableAccessCheck', - 'removeAccess', - 'checkEnabled', - 'hasAccess', - ]) - }) - - describe('#raiseFlag', () => { - describe('when called by the owner', () => { - it('updates the warning flag', async () => { - assert.equal(false, await flags.getFlag(consumer.address)) - - await flags.connect(personas.Nelly).raiseFlag(consumer.address) - - assert.equal(true, await flags.getFlag(consumer.address)) - }) - - it('emits an event log', async () => { - await expect(flags.connect(personas.Nelly).raiseFlag(consumer.address)) - .to.emit(flags, 'FlagRaised') - .withArgs(consumer.address) - }) - - describe('if a flag has already been raised', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).raiseFlag(consumer.address) - }) - - it('emits an event log', async () => { - const tx = await flags - .connect(personas.Nelly) - .raiseFlag(consumer.address) - const receipt = await tx.wait() - assert.equal(0, receipt.events?.length) - }) - }) - }) - - describe('when called by an enabled setter', () => { - beforeEach(async () => { - await controller - .connect(personas.Nelly) - .addAccess(await personas.Neil.getAddress()) - }) - - it('sets the flags', async () => { - await flags.connect(personas.Neil).raiseFlag(consumer.address), - assert.equal(true, await flags.getFlag(consumer.address)) - }) - }) - - describe('when called by a non-enabled setter', () => { - it('reverts', async () => { - await expect( - flags.connect(personas.Neil).raiseFlag(consumer.address), - ).to.be.revertedWith('Not allowed to raise flags') - }) - }) - - describe('when called when there is no raisingAccessController', () => { - beforeEach(async () => { - await expect( - flags - .connect(personas.Nelly) - .setRaisingAccessController( - '0x0000000000000000000000000000000000000000', - ), - ).to.emit(flags, 'RaisingAccessControllerUpdated') - assert.equal( - '0x0000000000000000000000000000000000000000', - await flags.raisingAccessController(), - ) - }) - - it('succeeds for the owner', async () => { - await flags.connect(personas.Nelly).raiseFlag(consumer.address) - assert.equal(true, await flags.getFlag(consumer.address)) - }) - - it('reverts for non-owner', async () => { - await expect(flags.connect(personas.Neil).raiseFlag(consumer.address)) - .to.be.reverted - }) - }) - }) - - describe('#raiseFlags', () => { - describe('when called by the owner', () => { - it('updates the warning flag', async () => { - assert.equal(false, await flags.getFlag(consumer.address)) - - await flags.connect(personas.Nelly).raiseFlags([consumer.address]) - - assert.equal(true, await flags.getFlag(consumer.address)) - }) - - it('emits an event log', async () => { - await expect( - flags.connect(personas.Nelly).raiseFlags([consumer.address]), - ) - .to.emit(flags, 'FlagRaised') - .withArgs(consumer.address) - }) - - describe('if a flag has already been raised', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).raiseFlags([consumer.address]) - }) - - it('emits an event log', async () => { - const tx = await flags - .connect(personas.Nelly) - .raiseFlags([consumer.address]) - const receipt = await tx.wait() - assert.equal(0, receipt.events?.length) - }) - }) - }) - - describe('when called by an enabled setter', () => { - beforeEach(async () => { - await controller - .connect(personas.Nelly) - .addAccess(await personas.Neil.getAddress()) - }) - - it('sets the flags', async () => { - await flags.connect(personas.Neil).raiseFlags([consumer.address]), - assert.equal(true, await flags.getFlag(consumer.address)) - }) - }) - - describe('when called by a non-enabled setter', () => { - it('reverts', async () => { - await expect( - flags.connect(personas.Neil).raiseFlags([consumer.address]), - ).to.be.revertedWith('Not allowed to raise flags') - }) - }) - - describe('when called when there is no raisingAccessController', () => { - beforeEach(async () => { - await expect( - flags - .connect(personas.Nelly) - .setRaisingAccessController( - '0x0000000000000000000000000000000000000000', - ), - ).to.emit(flags, 'RaisingAccessControllerUpdated') - - assert.equal( - '0x0000000000000000000000000000000000000000', - await flags.raisingAccessController(), - ) - }) - - it('succeeds for the owner', async () => { - await flags.connect(personas.Nelly).raiseFlags([consumer.address]) - assert.equal(true, await flags.getFlag(consumer.address)) - }) - - it('reverts for non-owners', async () => { - await expect( - flags.connect(personas.Neil).raiseFlags([consumer.address]), - ).to.be.reverted - }) - }) - }) - - describe('#lowerFlags', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).raiseFlags([consumer.address]) - }) - - describe('when called by the owner', () => { - it('updates the warning flag', async () => { - assert.equal(true, await flags.getFlag(consumer.address)) - - await flags.connect(personas.Nelly).lowerFlags([consumer.address]) - - assert.equal(false, await flags.getFlag(consumer.address)) - }) - - it('emits an event log', async () => { - await expect( - flags.connect(personas.Nelly).lowerFlags([consumer.address]), - ) - .to.emit(flags, 'FlagLowered') - .withArgs(consumer.address) - }) - - describe('if a flag has already been raised', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).lowerFlags([consumer.address]) - }) - - it('emits an event log', async () => { - const tx = await flags - .connect(personas.Nelly) - .lowerFlags([consumer.address]) - const receipt = await tx.wait() - assert.equal(0, receipt.events?.length) - }) - }) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - flags.connect(personas.Neil).lowerFlags([consumer.address]), - ).to.be.revertedWith('Only callable by owner') - }) - }) - }) - - describe('#getFlag', () => { - describe('if the access control is turned on', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).enableAccessCheck() - }) - - it('reverts', async () => { - await expect(consumer.getFlag(consumer.address)).to.be.revertedWith( - 'No access', - ) - }) - - describe('if access is granted to the address', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).addAccess(consumer.address) - }) - - it('does not revert', async () => { - await consumer.getFlag(consumer.address) - }) - }) - }) - - describe('if the access control is turned off', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).disableAccessCheck() - }) - - it('does not revert', async () => { - await consumer.getFlag(consumer.address) - }) - - describe('if access is granted to the address', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).addAccess(consumer.address) - }) - - it('does not revert', async () => { - await consumer.getFlag(consumer.address) - }) - }) - }) - }) - - describe('#getFlags', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).disableAccessCheck() - await flags - .connect(personas.Nelly) - .raiseFlags([ - await personas.Neil.getAddress(), - await personas.Norbert.getAddress(), - ]) - }) - - it('respects the access controls of #getFlag', async () => { - await flags.connect(personas.Nelly).enableAccessCheck() - - await expect(consumer.getFlag(consumer.address)).to.be.revertedWith( - 'No access', - ) - - await flags.connect(personas.Nelly).addAccess(consumer.address) - - await consumer.getFlag(consumer.address) - }) - - it('returns the flags in the order they are requested', async () => { - const response = await consumer.getFlags([ - await personas.Nelly.getAddress(), - await personas.Neil.getAddress(), - await personas.Ned.getAddress(), - await personas.Norbert.getAddress(), - ]) - - assert.deepEqual([false, true, false, true], response) - }) - }) - - describe('#setRaisingAccessController', () => { - let controller2: Contract - - beforeEach(async () => { - controller2 = await controllerFactory.connect(personas.Nelly).deploy() - await controller2.connect(personas.Nelly).enableAccessCheck() - }) - - it('updates access control rules', async () => { - const neilAddress = await personas.Neil.getAddress() - await controller.connect(personas.Nelly).addAccess(neilAddress) - await flags.connect(personas.Neil).raiseFlags([consumer.address]) // doesn't raise - - await flags - .connect(personas.Nelly) - .setRaisingAccessController(controller2.address) - - await expect( - flags.connect(personas.Neil).raiseFlags([consumer.address]), - ).to.be.revertedWith('Not allowed to raise flags') - }) - - it('emits a log announcing the change', async () => { - await expect( - flags - .connect(personas.Nelly) - .setRaisingAccessController(controller2.address), - ) - .to.emit(flags, 'RaisingAccessControllerUpdated') - .withArgs(controller.address, controller2.address) - }) - - it('does not emit a log when there is no change', async () => { - await flags - .connect(personas.Nelly) - .setRaisingAccessController(controller2.address) - - await expect( - flags - .connect(personas.Nelly) - .setRaisingAccessController(controller2.address), - ).to.not.emit(flags, 'RaisingAccessControllerUpdated') - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - flags - .connect(personas.Neil) - .setRaisingAccessController(controller2.address), - ).to.be.revertedWith('Only callable by owner') - }) - }) - }) -}) diff --git a/contracts/test/v0.6/FluxAggregator.test.ts b/contracts/test/v0.6/FluxAggregator.test.ts deleted file mode 100644 index 5a268ceebe9..00000000000 --- a/contracts/test/v0.6/FluxAggregator.test.ts +++ /dev/null @@ -1,3252 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { - Signer, - Contract, - ContractFactory, - BigNumber, - BigNumberish, - ContractTransaction, - constants, -} from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals, evmRevert } from '../test-helpers/matchers' -import { - publicAbi, - toWei, - increaseTimeBy, - mineBlock, - evmWordToAddress, -} from '../test-helpers/helpers' -import { randomBytes } from '@ethersproject/random' -import { fail } from 'assert' - -let personas: Personas -let linkTokenFactory: ContractFactory -let fluxAggregatorFactory: ContractFactory -let validatorMockFactory: ContractFactory -let testHelperFactory: ContractFactory -let validatorFactory: ContractFactory -let flagsFactory: ContractFactory -let acFactory: ContractFactory -let gasGuzzlerFactory: ContractFactory -let emptyAddress: string - -before(async () => { - personas = (await getUsers()).personas - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - ) - fluxAggregatorFactory = await ethers.getContractFactory( - 'src/v0.6/FluxAggregator.sol:FluxAggregator', - ) - validatorMockFactory = await ethers.getContractFactory( - 'src/v0.6/tests/AggregatorValidatorMock.sol:AggregatorValidatorMock', - ) - testHelperFactory = await ethers.getContractFactory( - 'src/v0.6/tests/FluxAggregatorTestHelper.sol:FluxAggregatorTestHelper', - ) - validatorFactory = await ethers.getContractFactory( - 'src/v0.6/DeviationFlaggingValidator.sol:DeviationFlaggingValidator', - ) - flagsFactory = await ethers.getContractFactory('src/v0.6/Flags.sol:Flags') - acFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - ) - gasGuzzlerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/GasGuzzler.sol:GasGuzzler', - ) - emptyAddress = constants.AddressZero -}) - -describe('FluxAggregator', () => { - const paymentAmount = toWei('3') - const deposit = toWei('100') - const answer = 100 - const minAns = 1 - const maxAns = 1 - const rrDelay = 0 - const timeout = 1800 - const decimals = 18 - const description = 'LINK/USD' - const reserveRounds = 2 - const minSubmissionValue = BigNumber.from('1') - const maxSubmissionValue = BigNumber.from('100000000000000000000') - - let aggregator: Contract - let link: Contract - let testHelper: Contract - let validator: Contract - let gasGuzzler: Contract - let nextRound: number - let oracles: Signer[] - - async function updateFutureRounds( - aggregator: Contract, - overrides: { - minAnswers?: BigNumberish - maxAnswers?: BigNumberish - payment?: BigNumberish - restartDelay?: BigNumberish - timeout?: BigNumberish - } = {}, - ) { - overrides = overrides || {} - const round = { - payment: overrides.payment || paymentAmount, - minAnswers: overrides.minAnswers || minAns, - maxAnswers: overrides.maxAnswers || maxAns, - restartDelay: overrides.restartDelay || rrDelay, - timeout: overrides.timeout || timeout, - } - - return aggregator.updateFutureRounds( - round.payment, - round.minAnswers, - round.maxAnswers, - round.restartDelay, - round.timeout, - ) - } - - async function addOracles( - aggregator: Contract, - oraclesAndAdmin: Signer[], - minAnswers: number, - maxAnswers: number, - restartDelay: number, - ): Promise { - return aggregator.connect(personas.Carol).changeOracles( - [], - oraclesAndAdmin.map(async (oracle) => await oracle.getAddress()), - oraclesAndAdmin.map(async (admin) => await admin.getAddress()), - minAnswers, - maxAnswers, - restartDelay, - ) - } - - async function advanceRound( - aggregator: Contract, - submitters: Signer[], - currentSubmission: number = answer, - ): Promise { - for (const submitter of submitters) { - await aggregator.connect(submitter).submit(nextRound, currentSubmission) - } - nextRound++ - return nextRound - } - - const ShouldBeSet = 'expects it to be different' - const ShouldNotBeSet = 'expects it to equal' - let startingState: any - - async function checkOracleRoundState( - state: any, - want: { - eligibleToSubmit: boolean - roundId: BigNumberish - latestSubmission: BigNumberish - startedAt: string - timeout: BigNumberish - availableFunds: BigNumberish - oracleCount: BigNumberish - paymentAmount: BigNumberish - }, - ) { - assert.equal( - want.eligibleToSubmit, - state._eligibleToSubmit, - 'round state: unexecpted eligibility', - ) - bigNumEquals( - want.roundId, - state._roundId, - 'round state: unexpected Round ID', - ) - bigNumEquals( - want.latestSubmission, - state._latestSubmission, - 'round state: unexpected latest submission', - ) - if (want.startedAt === ShouldBeSet) { - assert.isAbove( - state._startedAt.toNumber(), - startingState._startedAt.toNumber(), - 'round state: expected the started at to be the same as previous', - ) - } else { - bigNumEquals( - 0, - state._startedAt, - 'round state: expected the started at not to be updated', - ) - } - bigNumEquals( - want.timeout, - state._timeout.toNumber(), - 'round state: unexepcted timeout', - ) - bigNumEquals( - want.availableFunds, - state._availableFunds, - 'round state: unexepected funds', - ) - bigNumEquals( - want.oracleCount, - state._oracleCount, - 'round state: unexpected oracle count', - ) - bigNumEquals( - want.paymentAmount, - state._paymentAmount, - 'round state: unexpected paymentamount', - ) - } - - beforeEach(async () => { - link = await linkTokenFactory.connect(personas.Default).deploy() - aggregator = await fluxAggregatorFactory - .connect(personas.Carol) - .deploy( - link.address, - paymentAmount, - timeout, - emptyAddress, - minSubmissionValue, - maxSubmissionValue, - decimals, - ethers.utils.formatBytes32String(description), - ) - await link.transfer(aggregator.address, deposit) - await aggregator.updateAvailableFunds() - bigNumEquals(deposit, await link.balanceOf(aggregator.address)) - nextRound = 1 - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(aggregator, [ - 'acceptAdmin', - 'allocatedFunds', - 'availableFunds', - 'changeOracles', - 'decimals', - 'description', - 'getAdmin', - 'getAnswer', - 'getOracles', - 'getRoundData', - 'getTimestamp', - 'latestAnswer', - 'latestRound', - 'latestRoundData', - 'latestTimestamp', - 'linkToken', - 'maxSubmissionCount', - 'maxSubmissionValue', - 'minSubmissionCount', - 'minSubmissionValue', - 'onTokenTransfer', - 'oracleCount', - 'oracleRoundState', - 'paymentAmount', - 'requestNewRound', - 'restartDelay', - 'setRequesterPermissions', - 'setValidator', - 'submit', - 'timeout', - 'transferAdmin', - 'updateAvailableFunds', - 'updateFutureRounds', - 'withdrawFunds', - 'withdrawPayment', - 'withdrawablePayment', - 'validator', - 'version', - // Owned methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('sets the paymentAmount', async () => { - bigNumEquals( - BigNumber.from(paymentAmount), - await aggregator.paymentAmount(), - ) - }) - - it('sets the timeout', async () => { - bigNumEquals(BigNumber.from(timeout), await aggregator.timeout()) - }) - - it('sets the decimals', async () => { - bigNumEquals(BigNumber.from(decimals), await aggregator.decimals()) - }) - - it('sets the description', async () => { - assert.equal( - ethers.utils.formatBytes32String(description), - await aggregator.description(), - ) - }) - - it('sets the version to 3', async () => { - bigNumEquals(3, await aggregator.version()) - }) - - it('sets the validator', async () => { - assert.equal(emptyAddress, await aggregator.validator()) - }) - }) - - describe('#submit', () => { - let minMax - - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned, personas.Nelly] - minMax = oracles.length - await addOracles(aggregator, oracles, minMax, minMax, rrDelay) - }) - - it('updates the allocated and available funds counters', async () => { - bigNumEquals(0, await aggregator.allocatedFunds()) - - const tx = await aggregator - .connect(personas.Neil) - .submit(nextRound, answer) - const receipt = await tx.wait() - - bigNumEquals(paymentAmount, await aggregator.allocatedFunds()) - const expectedAvailable = deposit.sub(paymentAmount) - bigNumEquals(expectedAvailable, await aggregator.availableFunds()) - const logged = BigNumber.from( - receipt.logs?.[2].topics[1] ?? BigNumber.from(-1), - ) - bigNumEquals(expectedAvailable, logged) - }) - - it('emits a log event announcing submission details', async () => { - await expect(aggregator.connect(personas.Nelly).submit(nextRound, answer)) - .to.emit(aggregator, 'SubmissionReceived') - .withArgs(answer, nextRound, await personas.Nelly.getAddress()) - }) - - describe('when the minimum oracles have not reported', () => { - it('pays the oracles that have reported', async () => { - bigNumEquals( - 0, - await aggregator - .connect(personas.Neil) - .withdrawablePayment(await personas.Neil.getAddress()), - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - bigNumEquals( - paymentAmount, - await aggregator - .connect(personas.Neil) - .withdrawablePayment(await personas.Neil.getAddress()), - ) - bigNumEquals( - 0, - await aggregator - .connect(personas.Ned) - .withdrawablePayment(await personas.Ned.getAddress()), - ) - bigNumEquals( - 0, - await aggregator - .connect(personas.Nelly) - .withdrawablePayment(await personas.Nelly.getAddress()), - ) - }) - - it('does not update the answer', async () => { - bigNumEquals(ethers.constants.Zero, await aggregator.latestAnswer()) - - // Not updated because of changes by the owner setting minSubmissionCount to 3 - await aggregator.connect(personas.Ned).submit(nextRound, answer) - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - bigNumEquals(ethers.constants.Zero, await aggregator.latestAnswer()) - }) - }) - - describe('when an oracle prematurely bumps the round', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { minAnswers: 2, maxAnswers: 3 }) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Neil).submit(nextRound + 1, answer), - 'previous round not supersedable', - ) - }) - }) - - describe('when the minimum number of oracles have reported', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { minAnswers: 2, maxAnswers: 3 }) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('updates the answer with the median', async () => { - bigNumEquals(0, await aggregator.latestAnswer()) - - await aggregator.connect(personas.Ned).submit(nextRound, 99) - bigNumEquals(99, await aggregator.latestAnswer()) // ((100+99) / 2).to_i - - await aggregator.connect(personas.Nelly).submit(nextRound, 101) - - bigNumEquals(100, await aggregator.latestAnswer()) - }) - - it('updates the updated timestamp', async () => { - const originalTimestamp = await aggregator.latestTimestamp() - assert.isAbove(originalTimestamp.toNumber(), 0) - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - const currentTimestamp = await aggregator.latestTimestamp() - assert.isAbove( - currentTimestamp.toNumber(), - originalTimestamp.toNumber(), - ) - }) - - it('announces the new answer with a log event', async () => { - const tx = await aggregator - .connect(personas.Nelly) - .submit(nextRound, answer) - const receipt = await tx.wait() - - const newAnswer = BigNumber.from( - receipt.logs?.[0].topics[1] ?? ethers.constants.Zero, - ) - - assert.equal(answer, newAnswer.toNumber()) - }) - - it('does not set the timedout flag', async () => { - evmRevert(aggregator.getRoundData(nextRound), 'No data present') - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - const round = await aggregator.getRoundData(nextRound) - assert.equal(nextRound, round.answeredInRound.toNumber()) - }) - - it('updates the round details', async () => { - evmRevert(aggregator.latestRoundData(), 'No data present') - - increaseTimeBy(15, ethers.provider) - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - const roundAfter = await aggregator.getRoundData(nextRound) - bigNumEquals(nextRound, roundAfter.roundId) - bigNumEquals(answer, roundAfter.answer) - assert.isFalse(roundAfter.startedAt.isZero()) - bigNumEquals( - await aggregator.getTimestamp(nextRound), - roundAfter.updatedAt, - ) - bigNumEquals(nextRound, roundAfter.answeredInRound) - - assert.isBelow( - roundAfter.startedAt.toNumber(), - roundAfter.updatedAt.toNumber(), - ) - - const roundAfterLatest = await aggregator.latestRoundData() - bigNumEquals(roundAfter.roundId, roundAfterLatest.roundId) - bigNumEquals(roundAfter.answer, roundAfterLatest.answer) - bigNumEquals(roundAfter.startedAt, roundAfterLatest.startedAt) - bigNumEquals(roundAfter.updatedAt, roundAfterLatest.updatedAt) - bigNumEquals( - roundAfter.answeredInRound, - roundAfterLatest.answeredInRound, - ) - }) - }) - - describe('when an oracle submits for a round twice', () => { - it('reverts', async () => { - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - await evmRevert( - aggregator.connect(personas.Neil).submit(nextRound, answer), - 'cannot report on previous rounds', - ) - }) - }) - - describe('when updated after the max answers submitted', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Ned).submit(nextRound, answer), - 'round not accepting submissions', - ) - }) - }) - - describe('when a new highest round number is passed in', () => { - it('increments the answer round', async () => { - const startingState = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - bigNumEquals(1, startingState._roundId) - - await advanceRound(aggregator, oracles) - - const updatedState = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - bigNumEquals(2, updatedState._roundId) - }) - - it('sets the startedAt time for the reporting round', async () => { - evmRevert(aggregator.getRoundData(nextRound), 'No data present') - - const tx = await aggregator - .connect(oracles[0]) - .submit(nextRound, answer) - await aggregator.connect(oracles[1]).submit(nextRound, answer) - await aggregator.connect(oracles[2]).submit(nextRound, answer) - const receipt = await tx.wait() - const block = await ethers.provider.getBlock(receipt.blockHash ?? '') - - const round = await aggregator.getRoundData(nextRound) - bigNumEquals(BigNumber.from(block.timestamp), round.startedAt) - }) - - it('announces a new round by emitting a log', async () => { - const tx = await aggregator - .connect(personas.Neil) - .submit(nextRound, answer) - const receipt = await tx.wait() - - const topics = receipt.logs?.[0].topics ?? [] - const roundNumber = BigNumber.from(topics[1]) - const startedBy = evmWordToAddress(topics[2]) - - bigNumEquals(nextRound, roundNumber.toNumber()) - bigNumEquals(startedBy, await personas.Neil.getAddress()) - }) - }) - - describe('when a round is passed in higher than expected', () => { - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Neil).submit(nextRound + 1, answer), - 'invalid round to report', - ) - }) - }) - - describe('when called by a non-oracle', () => { - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Carol).submit(nextRound, answer), - 'not enabled oracle', - ) - }) - }) - - describe('when there are not sufficient available funds', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Carol) - .withdrawFunds( - await personas.Carol.getAddress(), - deposit.sub(paymentAmount.mul(oracles.length).mul(reserveRounds)), - ) - - // drain remaining funds - await advanceRound(aggregator, oracles) - await advanceRound(aggregator, oracles) - }) - - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Neil).submit(nextRound, answer), - 'SafeMath: subtraction overflow', - ) - }) - }) - - describe('when a new round opens before the previous rounds closes', () => { - beforeEach(async () => { - oracles = [personas.Nancy, personas.Norbert] - await addOracles(aggregator, oracles, 3, 4, rrDelay) - await advanceRound(aggregator, [ - personas.Nelly, - personas.Neil, - personas.Nancy, - ]) - - // start the next round - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - }) - - it('still allows the previous round to be answered', async () => { - await aggregator.connect(personas.Ned).submit(nextRound - 1, answer) - }) - - describe('once the current round is answered', () => { - beforeEach(async () => { - oracles = [personas.Neil, personas.Nancy] - for (let i = 0; i < oracles.length; i++) { - await aggregator.connect(oracles[i]).submit(nextRound, answer) - } - }) - - it('does not allow reports for the previous round', async () => { - await evmRevert( - aggregator.connect(personas.Ned).submit(nextRound - 1, answer), - 'invalid round to report', - ) - }) - }) - - describe('when the previous round has finished', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Norbert) - .submit(nextRound - 1, answer) - }) - - it('does not allow reports for the previous round', async () => { - await evmRevert( - aggregator.connect(personas.Ned).submit(nextRound - 1, answer), - 'round not accepting submissions', - ) - }) - }) - }) - - describe('when price is updated mid-round', () => { - const newAmount = toWei('50') - - it('pays the same amount to all oracles per round', async () => { - await link.transfer( - aggregator.address, - newAmount.mul(oracles.length).mul(reserveRounds), - ) - await aggregator.updateAvailableFunds() - - bigNumEquals( - 0, - await aggregator - .connect(personas.Neil) - .withdrawablePayment(await personas.Neil.getAddress()), - ) - bigNumEquals( - 0, - await aggregator - .connect(personas.Nelly) - .withdrawablePayment(await personas.Nelly.getAddress()), - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - await updateFutureRounds(aggregator, { payment: newAmount }) - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - bigNumEquals( - paymentAmount, - await aggregator - .connect(personas.Neil) - .withdrawablePayment(await personas.Neil.getAddress()), - ) - bigNumEquals( - paymentAmount, - await aggregator - .connect(personas.Nelly) - .withdrawablePayment(await personas.Nelly.getAddress()), - ) - }) - }) - - describe('when delay is on', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { - minAnswers: oracles.length, - maxAnswers: oracles.length, - restartDelay: 1, - }) - }) - - it("does not revert on the oracle's first round", async () => { - // Since lastUpdatedRound defaults to zero and that's the only - // indication that an oracle hasn't responded, this test guards against - // the situation where we don't check that and no one can start a round. - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('does revert before the delay', async () => { - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - nextRound++ - - await evmRevert( - aggregator.connect(personas.Neil).submit(nextRound, answer), - 'previous round not supersedable', - ) - }) - }) - - describe('when an oracle starts a round before the restart delay is over', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator.connect(personas.Carol), { - minAnswers: 1, - maxAnswers: 1, - }) - - oracles = [personas.Neil, personas.Ned, personas.Nelly] - for (let i = 0; i < oracles.length; i++) { - await aggregator.connect(oracles[i]).submit(nextRound, answer) - nextRound++ - } - - const newDelay = 2 - // Since Ned and Nelly have answered recently, and we set the delay - // to 2, only Nelly can answer as she is the only oracle that hasn't - // started the last two rounds. - await updateFutureRounds(aggregator, { - maxAnswers: oracles.length, - restartDelay: newDelay, - }) - }) - - describe('when called by an oracle who has not answered recently', () => { - it('does not revert', async () => { - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - }) - - describe('when called by an oracle who answered recently', () => { - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Ned).submit(nextRound, answer), - 'round not accepting submissions', - ) - - await evmRevert( - aggregator.connect(personas.Nelly).submit(nextRound, answer), - 'round not accepting submissions', - ) - }) - }) - }) - - describe('when the price is not updated for a round', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { - minAnswers: oracles.length, - maxAnswers: oracles.length, - restartDelay: 1, - }) - - for (const oracle of oracles) { - await aggregator.connect(oracle).submit(nextRound, answer) - } - nextRound++ - - await aggregator.connect(personas.Ned).submit(nextRound, answer) - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - await increaseTimeBy(timeout + 1, ethers.provider) - nextRound++ - }) - - it('allows a new round to be started', async () => { - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - }) - - it('sets the info for the previous round', async () => { - const previousRound = nextRound - 1 - let updated = await aggregator.getTimestamp(previousRound) - let ans = await aggregator.getAnswer(previousRound) - assert.equal(0, updated.toNumber()) - assert.equal(0, ans.toNumber()) - - const tx = await aggregator - .connect(personas.Nelly) - .submit(nextRound, answer) - const receipt = await tx.wait() - - const block = await ethers.provider.getBlock(receipt.blockHash ?? '') - - updated = await aggregator.getTimestamp(previousRound) - ans = await aggregator.getAnswer(previousRound) - bigNumEquals(BigNumber.from(block.timestamp), updated) - assert.equal(answer, ans.toNumber()) - - const round = await aggregator.getRoundData(previousRound) - bigNumEquals(previousRound, round.roundId) - bigNumEquals(ans, round.answer) - bigNumEquals(updated, round.updatedAt) - bigNumEquals(previousRound - 1, round.answeredInRound) - }) - - it('sets the previous round as timed out', async () => { - const previousRound = nextRound - 1 - evmRevert(aggregator.getRoundData(previousRound), 'No data present') - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - const round = await aggregator.getRoundData(previousRound) - assert.notEqual(round.roundId, round.answeredInRound) - bigNumEquals(previousRound - 1, round.answeredInRound) - }) - - it('still respects the delay restriction', async () => { - // expected to revert because the sender started the last round - await evmRevert( - aggregator.connect(personas.Ned).submit(nextRound, answer), - ) - }) - - it('uses the timeout set at the beginning of the round', async () => { - await updateFutureRounds(aggregator, { - timeout: timeout + 100000, - }) - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - }) - }) - - describe('submitting values near the edges of allowed values', () => { - it('rejects values below the submission value range', async () => { - await evmRevert( - aggregator - .connect(personas.Neil) - .submit(nextRound, minSubmissionValue.sub(1)), - 'value below minSubmissionValue', - ) - }) - - it('accepts submissions equal to the min submission value', async () => { - await aggregator - .connect(personas.Neil) - .submit(nextRound, minSubmissionValue) - }) - - it('accepts submissions equal to the max submission value', async () => { - await aggregator - .connect(personas.Neil) - .submit(nextRound, maxSubmissionValue) - }) - - it('rejects submissions equal to the max submission value', async () => { - await evmRevert( - aggregator - .connect(personas.Neil) - .submit(nextRound, maxSubmissionValue.add(1)), - 'value above maxSubmissionValue', - ) - }) - }) - - describe('when a validator is set', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { minAnswers: 1, maxAnswers: 1 }) - oracles = [personas.Nelly] - - validator = await validatorMockFactory.connect(personas.Carol).deploy() - await aggregator.connect(personas.Carol).setValidator(validator.address) - }) - - it('calls out to the validator', async () => { - await expect( - aggregator.connect(personas.Nelly).submit(nextRound, answer), - ) - .to.emit(validator, 'Validated') - .withArgs(0, 0, nextRound, answer) - }) - }) - - describe('when the answer validator eats all gas', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { minAnswers: 1, maxAnswers: 1 }) - oracles = [personas.Nelly] - - gasGuzzler = await gasGuzzlerFactory.connect(personas.Carol).deploy() - await aggregator - .connect(personas.Carol) - .setValidator(gasGuzzler.address) - assert.equal(gasGuzzler.address, await aggregator.validator()) - }) - - it('still updates', async () => { - bigNumEquals(0, await aggregator.latestAnswer()) - - await aggregator - .connect(personas.Nelly) - .submit(nextRound, answer, { gasLimit: 500000 }) - - bigNumEquals(answer, await aggregator.latestAnswer()) - }) - }) - }) - - describe('#getAnswer', () => { - const answers = [1, 10, 101, 1010, 10101, 101010, 1010101] - - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - - for (const answer of answers) { - await aggregator.connect(personas.Neil).submit(nextRound++, answer) - } - }) - - it('retrieves the answer recorded for past rounds', async () => { - for (let i = nextRound; i < nextRound; i++) { - const answer = await aggregator.getAnswer(i) - bigNumEquals(BigNumber.from(answers[i - 1]), answer) - } - }) - - it("returns 0 for answers greater than uint32's max", async () => { - const overflowedId = BigNumber.from(2).pow(32).add(1) - const answer = await aggregator.getAnswer(overflowedId) - bigNumEquals(0, answer) - }) - }) - - describe('#getTimestamp', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - - for (let i = 0; i < 10; i++) { - await aggregator.connect(personas.Neil).submit(nextRound++, i + 1) - } - }) - - it('retrieves the answer recorded for past rounds', async () => { - let lastTimestamp = ethers.constants.Zero - - for (let i = 1; i < nextRound; i++) { - const currentTimestamp = await aggregator.getTimestamp(i) - assert.isAtLeast(currentTimestamp.toNumber(), lastTimestamp.toNumber()) - lastTimestamp = currentTimestamp - } - }) - - it("returns 0 for answers greater than uint32's max", async () => { - const overflowedId = BigNumber.from(2).pow(32).add(1) - const answer = await aggregator.getTimestamp(overflowedId) - bigNumEquals(0, answer) - }) - }) - - describe('#changeOracles', () => { - describe('adding oracles', () => { - it('increases the oracle count', async () => { - const pastCount = await aggregator.oracleCount() - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - const currentCount = await aggregator.oracleCount() - - bigNumEquals(currentCount, pastCount + 1) - }) - - it('adds the address in getOracles', async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - assert.deepEqual( - [await personas.Neil.getAddress()], - await aggregator.getOracles(), - ) - }) - - it('updates the round details', async () => { - await addOracles( - aggregator, - [personas.Neil, personas.Ned, personas.Nelly], - 1, - 3, - 2, - ) - bigNumEquals(1, await aggregator.minSubmissionCount()) - bigNumEquals(3, await aggregator.maxSubmissionCount()) - bigNumEquals(2, await aggregator.restartDelay()) - }) - - it('emits a log', async () => { - const tx = await aggregator - .connect(personas.Carol) - .changeOracles( - [], - [await personas.Ned.getAddress()], - [await personas.Neil.getAddress()], - 1, - 1, - 0, - ) - expect(tx) - .to.emit(aggregator, 'OraclePermissionsUpdated') - .withArgs(await personas.Ned.getAddress(), true) - - expect(tx) - .to.emit(aggregator, 'OracleAdminUpdated') - .withArgs( - await personas.Ned.getAddress(), - await personas.Neil.getAddress(), - ) - }) - - describe('when the oracle has already been added', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - }) - - it('reverts', async () => { - await evmRevert( - addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay), - 'oracle already enabled', - ) - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Neil) - .changeOracles( - [], - [await personas.Neil.getAddress()], - [await personas.Neil.getAddress()], - minAns, - maxAns, - rrDelay, - ), - 'Only callable by owner', - ) - }) - }) - - describe('when an oracle gets added mid-round', () => { - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned] - await addOracles( - aggregator, - oracles, - oracles.length, - oracles.length, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - await addOracles( - aggregator, - [personas.Nelly], - oracles.length + 1, - oracles.length + 1, - rrDelay, - ) - }) - - it('does not allow the oracle to update the round', async () => { - await evmRevert( - aggregator.connect(personas.Nelly).submit(nextRound, answer), - 'not yet enabled oracle', - ) - }) - - it('does allow the oracle to update future rounds', async () => { - // complete round - await aggregator.connect(personas.Ned).submit(nextRound, answer) - - // now can participate in new rounds - await aggregator.connect(personas.Nelly).submit(nextRound + 1, answer) - }) - }) - - describe('when an oracle is added after removed for a round', () => { - it('allows the oracle to update', async () => { - oracles = [personas.Neil, personas.Nelly] - await addOracles( - aggregator, - oracles, - oracles.length, - oracles.length, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - nextRound++ - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 1, - 1, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound++ - - await addOracles(aggregator, [personas.Nelly], 1, 1, rrDelay) - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - }) - }) - - describe('when an oracle is added and immediately removed mid-round', () => { - it('allows the oracle to update', async () => { - await addOracles( - aggregator, - oracles, - oracles.length, - oracles.length, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - nextRound++ - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 1, - 1, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound++ - - await addOracles(aggregator, [personas.Nelly], 1, 1, rrDelay) - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - }) - }) - - describe('when an oracle is re-added with a different admin address', () => { - it('reverts', async () => { - await addOracles( - aggregator, - oracles, - oracles.length, - oracles.length, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 1, - 1, - rrDelay, - ) - - await evmRevert( - aggregator - .connect(personas.Carol) - .changeOracles( - [], - [await personas.Nelly.getAddress()], - [await personas.Carol.getAddress()], - 1, - 1, - rrDelay, - ), - 'owner cannot overwrite admin', - ) - }) - }) - - const limit = 77 - describe(`when adding more than ${limit} oracles`, () => { - let oracles: Signer[] - - beforeEach(async () => { - oracles = [] - for (let i = 0; i < limit; i++) { - const account = await new ethers.Wallet( - randomBytes(32), - ethers.provider, - ) - await personas.Default.sendTransaction({ - to: account.address, - value: toWei('0.1'), - }) - - oracles.push(account) - } - - await link.transfer( - aggregator.address, - paymentAmount.mul(limit).mul(reserveRounds), - ) - await aggregator.updateAvailableFunds() - - let addresses = oracles.slice(0, 50).map(async (o) => o.getAddress()) - await aggregator - .connect(personas.Carol) - .changeOracles([], addresses, addresses, 1, 50, rrDelay) - // add in two transactions to avoid gas limit issues - addresses = oracles.slice(50, 100).map(async (o) => o.getAddress()) - await aggregator - .connect(personas.Carol) - .changeOracles([], addresses, addresses, 1, oracles.length, rrDelay) - }) - - it('not use too much gas [ @skip-coverage ]', async () => { - let tx: any - assert.deepEqual( - // test adveserial quickselect algo - [2, 4, 6, 8, 10, 12, 14, 16, 1, 9, 5, 11, 3, 13, 7, 15], - adverserialQuickselectList(16), - ) - const inputs = adverserialQuickselectList(limit) - for (let i = 0; i < limit; i++) { - tx = await aggregator - .connect(oracles[i]) - .submit(nextRound, inputs[i]) - } - assert.isTrue(!!tx) - if (tx) { - const receipt = await tx.wait() - assert.isBelow(receipt.gasUsed.toNumber(), 600_000) - } - }) - - function adverserialQuickselectList(len: number): number[] { - const xs: number[] = [] - const pi: number[] = [] - for (let i = 0; i < len; i++) { - pi[i] = i - xs[i] = 0 - } - - for (let l = len; l > 0; l--) { - const pivot = Math.floor((l - 1) / 2) - xs[pi[pivot]] = l - const temp = pi[l - 1] - pi[l - 1] = pi[pivot] - pi[pivot] = temp - } - return xs - } - - it('reverts when another oracle is added', async () => { - await evmRevert( - aggregator - .connect(personas.Carol) - .changeOracles( - [], - [await personas.Neil.getAddress()], - [await personas.Neil.getAddress()], - limit + 1, - limit + 1, - rrDelay, - ), - 'max oracles allowed', - ) - }) - }) - - it('reverts when minSubmissions is set to 0', async () => { - await evmRevert( - addOracles(aggregator, [personas.Neil], 0, 0, 0), - 'min must be greater than 0', - ) - }) - }) - - describe('removing oracles', () => { - beforeEach(async () => { - oracles = [personas.Neil, personas.Nelly] - await addOracles( - aggregator, - oracles, - oracles.length, - oracles.length, - rrDelay, - ) - }) - - it('decreases the oracle count', async () => { - const pastCount = await aggregator.oracleCount() - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - const currentCount = await aggregator.oracleCount() - - expect(currentCount).to.equal(pastCount - 1) - }) - - it('updates the round details', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles([await personas.Neil.getAddress()], [], [], 1, 1, 0) - - bigNumEquals(1, await aggregator.minSubmissionCount()) - bigNumEquals(1, await aggregator.maxSubmissionCount()) - bigNumEquals(ethers.constants.Zero, await aggregator.restartDelay()) - }) - - it('emits a log', async () => { - await expect( - aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ), - ) - .to.emit(aggregator, 'OraclePermissionsUpdated') - .withArgs(await personas.Neil.getAddress(), false) - }) - - it('removes the address in getOracles', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - assert.deepEqual( - [await personas.Nelly.getAddress()], - await aggregator.getOracles(), - ) - }) - - describe('when the oracle is not currently added', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - }) - - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ), - 'oracle not enabled', - ) - }) - }) - - describe('when removing the last oracle', () => { - it('does not revert', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - - await aggregator - .connect(personas.Carol) - .changeOracles([await personas.Nelly.getAddress()], [], [], 0, 0, 0) - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Ned) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - 0, - 0, - rrDelay, - ), - 'Only callable by owner', - ) - }) - }) - - describe('when an oracle gets removed', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 1, - 1, - rrDelay, - ) - }) - - it('is allowed to report on one more round', async () => { - // next round - await advanceRound(aggregator, [personas.Nelly]) - // finish round - await advanceRound(aggregator, [personas.Neil]) - - // cannot participate in future rounds - await evmRevert( - aggregator.connect(personas.Nelly).submit(nextRound, answer), - 'no longer allowed oracle', - ) - }) - }) - - describe('when an oracle gets removed mid-round', () => { - beforeEach(async () => { - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 1, - 1, - rrDelay, - ) - }) - - it('is allowed to finish that round and one more round', async () => { - await advanceRound(aggregator, [personas.Nelly]) // finish round - - await advanceRound(aggregator, [personas.Nelly]) // next round - - // cannot participate in future rounds - await evmRevert( - aggregator.connect(personas.Nelly).submit(nextRound, answer), - 'no longer allowed oracle', - ) - }) - }) - - it('reverts when minSubmissions is set to 0', async () => { - await evmRevert( - aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 0, - 0, - 0, - ), - 'min must be greater than 0', - ) - }) - }) - - describe('adding and removing oracles at once', () => { - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned] - await addOracles(aggregator, oracles, 1, 1, rrDelay) - }) - - it('can swap out oracles', async () => { - assert.include( - await aggregator.getOracles(), - await personas.Ned.getAddress(), - ) - assert.notInclude( - await aggregator.getOracles(), - await personas.Nelly.getAddress(), - ) - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Ned.getAddress()], - [await personas.Nelly.getAddress()], - [await personas.Nelly.getAddress()], - 1, - 1, - rrDelay, - ) - - assert.notInclude( - await aggregator.getOracles(), - await personas.Ned.getAddress(), - ) - assert.include( - await aggregator.getOracles(), - await personas.Nelly.getAddress(), - ) - }) - - it('is possible to remove and add the same address', async () => { - assert.include( - await aggregator.getOracles(), - await personas.Ned.getAddress(), - ) - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Ned.getAddress()], - [await personas.Ned.getAddress()], - [await personas.Ned.getAddress()], - 1, - 1, - rrDelay, - ) - - assert.include( - await aggregator.getOracles(), - await personas.Ned.getAddress(), - ) - }) - }) - }) - - describe('#getOracles', () => { - describe('after adding oracles', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - - assert.deepEqual( - [await personas.Neil.getAddress()], - await aggregator.getOracles(), - ) - }) - - it('returns the addresses of added oracles', async () => { - await addOracles(aggregator, [personas.Ned], minAns, maxAns, rrDelay) - - assert.deepEqual( - [await personas.Neil.getAddress(), await personas.Ned.getAddress()], - await aggregator.getOracles(), - ) - - await addOracles(aggregator, [personas.Nelly], minAns, maxAns, rrDelay) - assert.deepEqual( - [ - await personas.Neil.getAddress(), - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ], - await aggregator.getOracles(), - ) - }) - }) - - describe('after removing oracles', () => { - beforeEach(async () => { - await addOracles( - aggregator, - [personas.Neil, personas.Ned, personas.Nelly], - minAns, - maxAns, - rrDelay, - ) - - assert.deepEqual( - [ - await personas.Neil.getAddress(), - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ], - await aggregator.getOracles(), - ) - }) - - it('reorders when removing from the beginning', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - assert.deepEqual( - [await personas.Nelly.getAddress(), await personas.Ned.getAddress()], - await aggregator.getOracles(), - ) - }) - - it('reorders when removing from the middle', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Ned.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - assert.deepEqual( - [await personas.Neil.getAddress(), await personas.Nelly.getAddress()], - await aggregator.getOracles(), - ) - }) - - it('pops the last node off at the end', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - assert.deepEqual( - [await personas.Neil.getAddress(), await personas.Ned.getAddress()], - await aggregator.getOracles(), - ) - }) - }) - }) - - describe('#withdrawFunds', () => { - it('succeeds', async () => { - await aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), deposit) - - bigNumEquals(0, await aggregator.availableFunds()) - bigNumEquals( - deposit, - await link.balanceOf(await personas.Carol.getAddress()), - ) - }) - - it('does not let withdrawals happen multiple times', async () => { - await aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), deposit) - - await evmRevert( - aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), deposit), - 'insufficient reserve funds', - ) - }) - - describe('with a number higher than the available LINK balance', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('fails', async () => { - await evmRevert( - aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), deposit), - 'insufficient reserve funds', - ) - - bigNumEquals( - deposit.sub(paymentAmount), - await aggregator.availableFunds(), - ) - }) - }) - - describe('with oracles still present', () => { - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned, personas.Nelly] - await addOracles(aggregator, oracles, 1, 1, rrDelay) - - bigNumEquals(deposit, await aggregator.availableFunds()) - }) - - it('does not allow withdrawal with less than 2x rounds of payments', async () => { - const oracleReserve = paymentAmount - .mul(oracles.length) - .mul(reserveRounds) - const allowed = deposit.sub(oracleReserve) - - //one more than the allowed amount cannot be withdrawn - await evmRevert( - aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), allowed.add(1)), - 'insufficient reserve funds', - ) - - // the allowed amount can be withdrawn - await aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), allowed) - }) - }) - - describe('when called by a non-owner', () => { - it('fails', async () => { - await evmRevert( - aggregator - .connect(personas.Eddy) - .withdrawFunds(await personas.Carol.getAddress(), deposit), - 'Only callable by owner', - ) - - bigNumEquals(deposit, await aggregator.availableFunds()) - }) - }) - }) - - describe('#updateFutureRounds', () => { - let minSubmissionCount, maxSubmissionCount - const newPaymentAmount = toWei('2') - const newMin = 1 - const newMax = 3 - const newDelay = 2 - - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned, personas.Nelly] - minSubmissionCount = oracles.length - maxSubmissionCount = oracles.length - await addOracles( - aggregator, - oracles, - minSubmissionCount, - maxSubmissionCount, - rrDelay, - ) - - bigNumEquals(paymentAmount, await aggregator.paymentAmount()) - assert.equal(minSubmissionCount, await aggregator.minSubmissionCount()) - assert.equal(maxSubmissionCount, await aggregator.maxSubmissionCount()) - }) - - it('updates the min and max answer counts', async () => { - await updateFutureRounds(aggregator, { - payment: newPaymentAmount, - minAnswers: newMin, - maxAnswers: newMax, - restartDelay: newDelay, - }) - - bigNumEquals(newPaymentAmount, await aggregator.paymentAmount()) - bigNumEquals( - BigNumber.from(newMin), - await aggregator.minSubmissionCount(), - ) - bigNumEquals( - BigNumber.from(newMax), - await aggregator.maxSubmissionCount(), - ) - bigNumEquals(BigNumber.from(newDelay), await aggregator.restartDelay()) - }) - - it('emits a log announcing the new round details', async () => { - await expect( - updateFutureRounds(aggregator, { - payment: newPaymentAmount, - minAnswers: newMin, - maxAnswers: newMax, - restartDelay: newDelay, - timeout: timeout + 1, - }), - ) - .to.emit(aggregator, 'RoundDetailsUpdated') - .withArgs(newPaymentAmount, newMin, newMax, newDelay, timeout + 1) - }) - - describe('when it is set to higher than the number or oracles', () => { - it('reverts', async () => { - await evmRevert( - updateFutureRounds(aggregator, { - maxAnswers: 4, - }), - 'max cannot exceed total', - ) - }) - }) - - describe('when it sets the min higher than the max', () => { - it('reverts', async () => { - await evmRevert( - updateFutureRounds(aggregator, { - minAnswers: 3, - maxAnswers: 2, - }), - 'max must equal/exceed min', - ) - }) - }) - - describe('when delay equal or greater the oracle count', () => { - it('reverts', async () => { - await evmRevert( - updateFutureRounds(aggregator, { - restartDelay: 3, - }), - 'delay cannot exceed total', - ) - }) - }) - - describe('when the payment amount does not cover reserve rounds', () => { - beforeEach(async () => {}) - - it('reverts', async () => { - const most = deposit.div(oracles.length * reserveRounds) - - // Relaxed check for the revert message due to a bug in ethers where any error message - // that starts with insufficient funds will be incorrectly returned as 'insufficient funds for intrinsic transaction cost' - await updateFutureRounds(aggregator, { - payment: most.add(1), - }).then( - () => { - // onFulfillment callback - fail('expected to revert but did not') - }, - (error: any) => { - // onRejected callback - const message = - error instanceof Object && 'message' in error - ? error.message - : JSON.stringify(error) - assert.isTrue(message.includes('insufficient funds')) - }, - ) - - await updateFutureRounds(aggregator, { - payment: most, - }) - }) - }) - - describe('min oracles is set to 0', () => { - it('reverts', async () => { - await evmRevert( - aggregator.updateFutureRounds(paymentAmount, 0, 0, rrDelay, timeout), - 'min must be greater than 0', - ) - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => { - await evmRevert( - updateFutureRounds(aggregator.connect(personas.Ned)), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#updateAvailableFunds', () => { - it('checks the LINK token to see if any additional funds are available', async () => { - const originalBalance = await aggregator.availableFunds() - - await aggregator.updateAvailableFunds() - - bigNumEquals(originalBalance, await aggregator.availableFunds()) - - await link.transfer(aggregator.address, deposit) - await aggregator.updateAvailableFunds() - - const newBalance = await aggregator.availableFunds() - bigNumEquals(originalBalance.add(deposit), newBalance) - }) - - it('removes allocated funds from the available balance', async () => { - const originalBalance = await aggregator.availableFunds() - - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - await link.transfer(aggregator.address, deposit) - await aggregator.updateAvailableFunds() - - const expected = originalBalance.add(deposit).sub(paymentAmount) - const newBalance = await aggregator.availableFunds() - bigNumEquals(expected, newBalance) - }) - - it('emits a log', async () => { - await link.transfer(aggregator.address, deposit) - - const tx = await aggregator.updateAvailableFunds() - const receipt = await tx.wait() - - const reportedBalance = BigNumber.from(receipt.logs?.[0].topics[1] ?? -1) - bigNumEquals(await aggregator.availableFunds(), reportedBalance) - }) - - describe('when the available funds have not changed', () => { - it('does not emit a log', async () => { - const tx = await aggregator.updateAvailableFunds() - const receipt = await tx.wait() - - assert.equal(0, receipt.logs?.length) - }) - }) - }) - - describe('#withdrawPayment', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('transfers LINK to the recipient', async () => { - const originalBalance = await link.balanceOf(aggregator.address) - bigNumEquals(0, await link.balanceOf(await personas.Neil.getAddress())) - - await aggregator - .connect(personas.Neil) - .withdrawPayment( - await personas.Neil.getAddress(), - await personas.Neil.getAddress(), - paymentAmount, - ) - - bigNumEquals( - originalBalance.sub(paymentAmount), - await link.balanceOf(aggregator.address), - ) - bigNumEquals( - paymentAmount, - await link.balanceOf(await personas.Neil.getAddress()), - ) - }) - - it('decrements the allocated funds counter', async () => { - const originalAllocation = await aggregator.allocatedFunds() - - await aggregator - .connect(personas.Neil) - .withdrawPayment( - await personas.Neil.getAddress(), - await personas.Neil.getAddress(), - paymentAmount, - ) - - bigNumEquals( - originalAllocation.sub(paymentAmount), - await aggregator.allocatedFunds(), - ) - }) - - describe('when the caller withdraws more than they have', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Neil) - .withdrawPayment( - await personas.Neil.getAddress(), - await personas.Neil.getAddress(), - paymentAmount.add(BigNumber.from(1)), - ), - 'insufficient withdrawable funds', - ) - }) - }) - - describe('when the caller is not the admin', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Nelly) - .withdrawPayment( - await personas.Neil.getAddress(), - await personas.Nelly.getAddress(), - BigNumber.from(1), - ), - 'only callable by admin', - ) - }) - }) - }) - - describe('#transferAdmin', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [], - [await personas.Ned.getAddress()], - [await personas.Neil.getAddress()], - minAns, - maxAns, - rrDelay, - ) - }) - - describe('when the admin tries to transfer the admin', () => { - it('works', async () => { - await expect( - aggregator - .connect(personas.Neil) - .transferAdmin( - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ), - ) - .to.emit(aggregator, 'OracleAdminUpdateRequested') - .withArgs( - await personas.Ned.getAddress(), - await personas.Neil.getAddress(), - await personas.Nelly.getAddress(), - ) - assert.equal( - await personas.Neil.getAddress(), - await aggregator.getAdmin(await personas.Ned.getAddress()), - ) - }) - }) - - describe('when the non-admin owner tries to update the admin', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Carol) - .transferAdmin( - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ), - 'only callable by admin', - ) - }) - }) - - describe('when the non-admin oracle tries to update the admin', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Ned) - .transferAdmin( - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ), - 'only callable by admin', - ) - }) - }) - }) - - describe('#acceptAdmin', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [], - [await personas.Ned.getAddress()], - [await personas.Neil.getAddress()], - minAns, - maxAns, - rrDelay, - ) - const tx = await aggregator - .connect(personas.Neil) - .transferAdmin( - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ) - await tx.wait() - }) - - describe('when the new admin tries to accept', () => { - it('works', async () => { - await expect( - aggregator - .connect(personas.Nelly) - .acceptAdmin(await personas.Ned.getAddress()), - ) - .to.emit(aggregator, 'OracleAdminUpdated') - .withArgs( - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ) - assert.equal( - await personas.Nelly.getAddress(), - await aggregator.getAdmin(await personas.Ned.getAddress()), - ) - }) - }) - - describe('when someone other than the new admin tries to accept', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Ned) - .acceptAdmin(await personas.Ned.getAddress()), - 'only callable by pending admin', - ) - await evmRevert( - aggregator - .connect(personas.Neil) - .acceptAdmin(await personas.Ned.getAddress()), - 'only callable by pending admin', - ) - }) - }) - }) - - describe('#onTokenTransfer', () => { - it('updates the available balance', async () => { - const originalBalance = await aggregator.availableFunds() - - await aggregator.updateAvailableFunds() - - bigNumEquals(originalBalance, await aggregator.availableFunds()) - - await link.transferAndCall(aggregator.address, deposit, '0x') - - const newBalance = await aggregator.availableFunds() - bigNumEquals(originalBalance.add(deposit), newBalance) - }) - - it('reverts given calldata', async () => { - await evmRevert( - // error message is not bubbled up by link token - link.transferAndCall(aggregator.address, deposit, '0x12345678'), - ) - }) - }) - - describe('#requestNewRound', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], 1, 1, 0) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound = nextRound + 1 - - await aggregator.setRequesterPermissions( - await personas.Carol.getAddress(), - true, - 0, - ) - }) - - it('announces a new round via log event', async () => { - await expect(aggregator.requestNewRound()).to.emit(aggregator, 'NewRound') - }) - - it('returns the new round ID', async () => { - testHelper = await testHelperFactory.connect(personas.Carol).deploy() - await aggregator.setRequesterPermissions(testHelper.address, true, 0) - let roundId = await testHelper.requestedRoundId() - assert.equal(roundId.toNumber(), 0) - - await testHelper.requestNewRound(aggregator.address) - - // return value captured by test helper - roundId = await testHelper.requestedRoundId() - assert.isAbove(roundId.toNumber(), 0) - }) - - describe('when there is a round in progress', () => { - beforeEach(async () => { - await aggregator.requestNewRound() - }) - - it('reverts', async () => { - await evmRevert( - aggregator.requestNewRound(), - 'prev round must be supersedable', - ) - }) - - describe('when that round has timed out', () => { - beforeEach(async () => { - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - }) - - it('starts a new round', async () => { - await expect(aggregator.requestNewRound()).to.emit( - aggregator, - 'NewRound', - ) - }) - }) - }) - - describe('when there is a restart delay set', () => { - beforeEach(async () => { - await aggregator.setRequesterPermissions( - await personas.Eddy.getAddress(), - true, - 1, - ) - }) - - it('reverts if a round is started before the delay', async () => { - await aggregator.connect(personas.Eddy).requestNewRound() - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound = nextRound + 1 - - // Eddy can't start because of the delay - await evmRevert( - aggregator.connect(personas.Eddy).requestNewRound(), - 'must delay requests', - ) - // Carol starts a new round instead - await aggregator.connect(personas.Carol).requestNewRound() - - // round completes - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound = nextRound + 1 - - // now Eddy can start again - await aggregator.connect(personas.Eddy).requestNewRound() - }) - }) - - describe('when all oracles have been removed and then re-added', () => { - it('does not get stuck', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles([await personas.Neil.getAddress()], [], [], 0, 0, 0) - - // advance a few rounds - for (let i = 0; i < 7; i++) { - await aggregator.requestNewRound() - nextRound = nextRound + 1 - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - } - - await addOracles(aggregator, [personas.Neil], 1, 1, 0) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - }) - }) - - describe('#setRequesterPermissions', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], 1, 1, 0) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound = nextRound + 1 - }) - - describe('when called by the owner', () => { - it('allows the specified address to start new rounds', async () => { - await aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - true, - 0, - ) - - await aggregator.connect(personas.Neil).requestNewRound() - }) - - it('emits a log announcing the update', async () => { - await expect( - aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - true, - 0, - ), - ) - .to.emit(aggregator, 'RequesterPermissionsSet') - .withArgs(await personas.Neil.getAddress(), true, 0) - }) - - describe('when the address is already authorized', () => { - beforeEach(async () => { - await aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - true, - 0, - ) - }) - - it('does not emit a log for already authorized accounts', async () => { - const tx = await aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - true, - 0, - ) - const receipt = await tx.wait() - assert.equal(0, receipt?.logs?.length) - }) - }) - - describe('when permission is removed by the owner', () => { - beforeEach(async () => { - await aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - true, - 0, - ) - }) - - it('does not allow the specified address to start new rounds', async () => { - await aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - false, - 0, - ) - - await evmRevert( - aggregator.connect(personas.Neil).requestNewRound(), - 'not authorized requester', - ) - }) - - it('emits a log announcing the update', async () => { - await expect( - aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - false, - 0, - ), - ) - .to.emit(aggregator, 'RequesterPermissionsSet') - .withArgs(await personas.Neil.getAddress(), false, 0) - }) - - it('does not emit a log for accounts without authorization', async () => { - const tx = await aggregator.setRequesterPermissions( - await personas.Ned.getAddress(), - false, - 0, - ) - const receipt = await tx.wait() - assert.equal(0, receipt?.logs?.length) - }) - }) - }) - - describe('when called by a stranger', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Neil) - .setRequesterPermissions(await personas.Neil.getAddress(), true, 0), - 'Only callable by owner', - ) - - await evmRevert( - aggregator.connect(personas.Neil).requestNewRound(), - 'not authorized requester', - ) - }) - }) - }) - - describe('#oracleRoundState', () => { - describe('when round ID 0 is passed in', () => { - const previousSubmission = 42 - let baseFunds: any - let minAnswers: number - let maxAnswers: number - let submitters: Signer[] - - beforeEach(async () => { - oracles = [ - personas.Neil, - personas.Ned, - personas.Nelly, - personas.Nancy, - personas.Norbert, - ] - minAnswers = 3 - maxAnswers = 4 - - await addOracles(aggregator, oracles, minAnswers, maxAnswers, rrDelay) - submitters = [ - personas.Nelly, - personas.Ned, - personas.Neil, - personas.Nancy, - ] - await advanceRound(aggregator, submitters, previousSubmission) - baseFunds = BigNumber.from(deposit).sub( - paymentAmount.mul(submitters.length), - ) - startingState = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - }) - - it('returns all of the important round information', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 2, - latestSubmission: previousSubmission, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds, - oracleCount: oracles.length, - paymentAmount, - }) - }) - - it('reverts if called by a contract', async () => { - testHelper = await testHelperFactory.connect(personas.Carol).deploy() - await evmRevert( - testHelper.readOracleRoundState( - aggregator.address, - await personas.Neil.getAddress(), - ), - 'off-chain reading only', - ) - }) - - describe('when the restart delay is not enforced', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { - minAnswers, - maxAnswers, - restartDelay: 0, - }) - }) - - describe('< min submissions and oracle not included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [personas.Neil]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 2, - latestSubmission: previousSubmission, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('< min submissions and oracle included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [personas.Nelly]) - }) - - it('is not eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 2, - latestSubmission: answer, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount), - oracleCount: oracles.length, - paymentAmount, - }) - }) - - describe('and timed out', () => { - beforeEach(async () => { - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('>= min submissions and oracle not included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [ - personas.Neil, - personas.Nancy, - personas.Ned, - ]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 2, - latestSubmission: previousSubmission, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('>= min submissions and oracle included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [ - personas.Neil, - personas.Nelly, - personas.Ned, - ]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - - describe('and timed out', () => { - beforeEach(async () => { - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('max submissions and oracle not included', () => { - beforeEach(async () => { - submitters = [ - personas.Neil, - personas.Ned, - personas.Nancy, - personas.Norbert, - ] - assert.equal( - submitters.length, - maxAnswers, - 'precondition, please update submitters if maxAnswers changes', - ) - await advanceRound(aggregator, submitters) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 3, - latestSubmission: previousSubmission, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(4)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('max submissions and oracle included', () => { - beforeEach(async () => { - submitters = [ - personas.Neil, - personas.Ned, - personas.Nelly, - personas.Nancy, - ] - assert.equal( - submitters.length, - maxAnswers, - 'precondition, please update submitters if maxAnswers changes', - ) - await advanceRound(aggregator, submitters) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(4)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('when the restart delay is enforced', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { - minAnswers, - maxAnswers, - restartDelay: maxAnswers - 1, - }) - }) - - describe('< min submissions and oracle not included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [personas.Neil, personas.Ned]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 2, - latestSubmission: previousSubmission, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount.mul(2)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('< min submissions and oracle included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [personas.Neil, personas.Nelly]) - }) - - it('is not eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 2, - latestSubmission: answer, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount.mul(2)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - - describe('and timed out', () => { - beforeEach(async () => { - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(2)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('>= min submissions and oracle not included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [ - personas.Neil, - personas.Ned, - personas.Nancy, - ]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 2, - latestSubmission: previousSubmission, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('>= min submissions and oracle included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [ - personas.Neil, - personas.Ned, - personas.Nelly, - ]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - - describe('and timed out', () => { - beforeEach(async () => { - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, // restart delay enforced - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('max submissions and oracle not included', () => { - beforeEach(async () => { - submitters = [ - personas.Neil, - personas.Ned, - personas.Nancy, - personas.Norbert, - ] - assert.equal( - submitters.length, - maxAnswers, - 'precondition, please update submitters if maxAnswers changes', - ) - await advanceRound(aggregator, submitters, answer) - }) - - it('is not eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 3, - latestSubmission: previousSubmission, - startedAt: ShouldNotBeSet, - timeout: 0, // details have been deleted - availableFunds: baseFunds.sub(paymentAmount.mul(4)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('max submissions and oracle included', () => { - beforeEach(async () => { - submitters = [ - personas.Neil, - personas.Ned, - personas.Nelly, - personas.Nancy, - ] - assert.equal( - submitters.length, - maxAnswers, - 'precondition, please update submitters if maxAnswers changes', - ) - await advanceRound(aggregator, submitters, answer) - }) - - it('is not eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(4)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - }) - - describe('when non-zero round ID 0 is passed in', () => { - const answers = [0, 42, 47, 52, 57] - let currentFunds: any - - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned, personas.Nelly] - - await addOracles(aggregator, oracles, 2, 3, rrDelay) - startingState = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - await advanceRound(aggregator, oracles, answers[1]) - await advanceRound( - aggregator, - [personas.Neil, personas.Ned], - answers[2], - ) - await advanceRound(aggregator, oracles, answers[3]) - await advanceRound(aggregator, [personas.Neil], answers[4]) - const submissionsSoFar = 9 - currentFunds = BigNumber.from(deposit).sub( - paymentAmount.mul(submissionsSoFar), - ) - }) - - it('returns info about previous rounds', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 1, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 1, - latestSubmission: answers[3], - startedAt: ShouldBeSet, - timeout: 0, - availableFunds: currentFunds, - oracleCount: oracles.length, - paymentAmount: 0, - }) - }) - - it('returns info about previous rounds that were not submitted to', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 2, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 2, - latestSubmission: answers[3], - startedAt: ShouldBeSet, - timeout, - availableFunds: currentFunds, - oracleCount: oracles.length, - paymentAmount, - }) - }) - - describe('for the current round', () => { - describe('which has not been submitted to', () => { - it("returns info about the current round that hasn't been submitted to", async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 4, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 4, - latestSubmission: answers[3], - startedAt: ShouldBeSet, - timeout, - availableFunds: currentFunds, - oracleCount: oracles.length, - paymentAmount, - }) - }) - - it('returns info about the subsequent round', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 5, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 5, - latestSubmission: answers[3], - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: currentFunds, - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('which has been submitted to', () => { - beforeEach(async () => { - await aggregator.connect(personas.Nelly).submit(4, answers[4]) - }) - - it("returns info about the current round that hasn't been submitted to", async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 4, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 4, - latestSubmission: answers[4], - startedAt: ShouldBeSet, - timeout, - availableFunds: currentFunds.sub(paymentAmount), - oracleCount: oracles.length, - paymentAmount, - }) - }) - - it('returns info about the subsequent round', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 5, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 5, - latestSubmission: answers[4], - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: currentFunds.sub(paymentAmount), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - it('returns speculative info about future rounds', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 6, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 6, - latestSubmission: answers[3], - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: currentFunds, - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('#getRoundData', () => { - let latestRoundId: any - beforeEach(async () => { - oracles = [personas.Nelly] - const minMax = oracles.length - await addOracles(aggregator, oracles, minMax, minMax, rrDelay) - await advanceRound(aggregator, oracles, answer) - latestRoundId = await aggregator.latestRound() - }) - - it('returns the relevant round information', async () => { - const round = await aggregator.getRoundData(latestRoundId) - bigNumEquals(latestRoundId, round.roundId) - bigNumEquals(answer, round.answer) - const nowSeconds = new Date().valueOf() / 1000 - assert.isAbove(round.updatedAt.toNumber(), nowSeconds - 120) - bigNumEquals(round.updatedAt, round.startedAt) - bigNumEquals(latestRoundId, round.answeredInRound) - }) - - it('reverts if a round is not present', async () => { - await evmRevert( - aggregator.getRoundData(latestRoundId.add(1)), - 'No data present', - ) - }) - - it('reverts if a round ID is too big', async () => { - const overflowedId = BigNumber.from(2).pow(32).add(1) - - await evmRevert(aggregator.getRoundData(overflowedId), 'No data present') - }) - }) - - describe('#latestRoundData', () => { - beforeEach(async () => { - oracles = [personas.Nelly] - const minMax = oracles.length - await addOracles(aggregator, oracles, minMax, minMax, rrDelay) - }) - - describe('when an answer has already been received', () => { - beforeEach(async () => { - await advanceRound(aggregator, oracles, answer) - }) - - it('returns the relevant round info without reverting', async () => { - const round = await aggregator.latestRoundData() - const latestRoundId = await aggregator.latestRound() - - bigNumEquals(latestRoundId, round.roundId) - bigNumEquals(answer, round.answer) - const nowSeconds = new Date().valueOf() / 1000 - assert.isAbove(round.updatedAt.toNumber(), nowSeconds - 120) - bigNumEquals(round.updatedAt, round.startedAt) - bigNumEquals(latestRoundId, round.answeredInRound) - }) - }) - - it('reverts if a round is not present', async () => { - await evmRevert(aggregator.latestRoundData(), 'No data present') - }) - }) - - describe('#latestAnswer', () => { - beforeEach(async () => { - oracles = [personas.Nelly] - const minMax = oracles.length - await addOracles(aggregator, oracles, minMax, minMax, rrDelay) - }) - - describe('when an answer has already been received', () => { - beforeEach(async () => { - await advanceRound(aggregator, oracles, answer) - }) - - it('returns the latest answer without reverting', async () => { - bigNumEquals(answer, await aggregator.latestAnswer()) - }) - }) - - it('returns zero', async () => { - bigNumEquals(0, await aggregator.latestAnswer()) - }) - }) - - describe('#setValidator', () => { - beforeEach(async () => { - validator = await validatorMockFactory.connect(personas.Carol).deploy() - assert.equal(emptyAddress, await aggregator.validator()) - }) - - it('emits a log event showing the validator was changed', async () => { - await expect( - aggregator.connect(personas.Carol).setValidator(validator.address), - ) - .to.emit(aggregator, 'ValidatorUpdated') - .withArgs(emptyAddress, validator.address) - assert.equal(validator.address, await aggregator.validator()) - - await expect( - aggregator.connect(personas.Carol).setValidator(validator.address), - ).to.not.emit(aggregator, 'ValidatorUpdated') - assert.equal(validator.address, await aggregator.validator()) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Neil).setValidator(validator.address), - 'Only callable by owner', - ) - }) - }) - }) - - describe('integrating with historic deviation checker', () => { - let validator: Contract - let flags: Contract - let ac: Contract - const flaggingThreshold = 1000 // 1% - - beforeEach(async () => { - ac = await acFactory.connect(personas.Carol).deploy() - flags = await flagsFactory.connect(personas.Carol).deploy(ac.address) - validator = await validatorFactory - .connect(personas.Carol) - .deploy(flags.address, flaggingThreshold) - await ac.connect(personas.Carol).addAccess(validator.address) - - await aggregator.connect(personas.Carol).setValidator(validator.address) - - oracles = [personas.Nelly] - const minMax = oracles.length - await addOracles(aggregator, oracles, minMax, minMax, rrDelay) - }) - - it('raises a flag on with high enough deviation', async () => { - await aggregator.connect(personas.Nelly).submit(nextRound, 100) - nextRound++ - - await expect(aggregator.connect(personas.Nelly).submit(nextRound, 102)) - .to.emit(flags, 'FlagRaised') - .withArgs(aggregator.address) - }) - - it('does not raise a flag with low enough deviation', async () => { - await aggregator.connect(personas.Nelly).submit(nextRound, 100) - nextRound++ - - await expect( - aggregator.connect(personas.Nelly).submit(nextRound, 101), - ).to.not.emit(flags, 'FlagRaised') - }) - }) -}) diff --git a/contracts/test/v0.6/Median.test.ts b/contracts/test/v0.6/Median.test.ts deleted file mode 100644 index 8aea4722b6d..00000000000 --- a/contracts/test/v0.6/Median.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { ethers } from 'hardhat' -import { assert } from 'chai' -import { Signer, Contract, ContractFactory, BigNumber } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals } from '../test-helpers/matchers' - -let defaultAccount: Signer -let medianTestHelperFactory: ContractFactory -before(async () => { - const personas: Personas = (await getUsers()).personas - defaultAccount = personas.Default - medianTestHelperFactory = await ethers.getContractFactory( - 'src/v0.6/tests/MedianTestHelper.sol:MedianTestHelper', - defaultAccount, - ) -}) - -describe('Median', () => { - let median: Contract - - beforeEach(async () => { - median = await medianTestHelperFactory.connect(defaultAccount).deploy() - }) - - describe('testing various lists', () => { - const tests = [ - { - name: 'ordered ascending', - responses: [0, 1, 2, 3, 4, 5, 6, 7], - want: 3, - }, - { - name: 'ordered descending', - responses: [7, 6, 5, 4, 3, 2, 1, 0], - want: 3, - }, - { - name: 'unordered length 1', - responses: [20], - want: 20, - }, - { - name: 'unordered length 2', - responses: [20, 0], - want: 10, - }, - { - name: 'unordered length 3', - responses: [20, 0, 16], - want: 16, - }, - { - name: 'unordered length 4', - responses: [20, 0, 15, 16], - want: 15, - }, - { - name: 'unordered length 7', - responses: [1001, 1, 101, 10, 11, 0, 111], - want: 11, - }, - { - name: 'unordered length 9', - responses: [8, 8, 4, 5, 5, 7, 9, 5, 9], - want: 7, - }, - { - name: 'unordered long', - responses: [33, 44, 89, 101, 67, 7, 23, 55, 88, 324, 0, 88], - want: 61, // 67 + 55 / 2 - }, - { - name: 'unordered longer', - responses: [ - 333121, 323453, 337654, 345363, 345363, 333456, 335477, 333323, - 332352, 354648, 983260, 333856, 335468, 376987, 333253, 388867, - 337879, 333324, 338678, - ], - want: 335477, - }, - { - name: 'overflowing numbers', - responses: [ - BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819967', - ), - BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819967', - ), - ], - want: BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819967', - ), - }, - { - name: 'overflowing numbers', - responses: [ - BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819967', - ), - BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819966', - ), - ], - want: BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819966', - ), - }, - { - name: 'really long', - responses: [ - 56, 2, 31, 33, 55, 38, 35, 12, 41, 47, 21, 22, 40, 39, 10, 32, 49, 3, - 54, 45, 53, 14, 20, 59, 1, 30, 24, 6, 5, 37, 58, 51, 46, 17, 29, 7, - 27, 9, 43, 8, 34, 42, 28, 23, 57, 0, 11, 48, 52, 50, 15, 16, 26, 25, - 4, 36, 19, 44, 18, 13, - ], - want: 29, - }, - ] - - for (const test of tests) { - it(test.name, async () => { - bigNumEquals(test.want, await median.publicGet(test.responses)) - }) - } - }) - - // long running (minutes) exhaustive test. - // skipped because very slow, but useful for thorough validation - xit('permutations', async () => { - const permutations = (list: number[]) => { - const result: number[][] = [] - const used: number[] = [] - - const permute = (unused: number[]) => { - if (unused.length == 0) { - result.push([...used]) - return - } - - for (let i = 0; i < unused.length; i++) { - const elem = unused.splice(i, 1)[0] - used.push(elem) - permute(unused) - unused.splice(i, 0, elem) - used.pop() - } - } - - permute(list) - return result - } - - { - const list = [0, 2, 5, 7, 8, 10] - for (const permuted of permutations(list)) { - for (let i = 0; i < list.length; i++) { - for (let j = 0; j < list.length; j++) { - if (i < j) { - const foo = await median.publicQuickselectTwo(permuted, i, j) - bigNumEquals(list[i], foo[0]) - bigNumEquals(list[j], foo[1]) - } - } - } - } - } - - { - const list = [0, 1, 1, 1, 2] - for (const permuted of permutations(list)) { - for (let i = 0; i < list.length; i++) { - for (let j = 0; j < list.length; j++) { - if (i < j) { - const foo = await median.publicQuickselectTwo(permuted, i, j) - bigNumEquals(list[i], foo[0]) - bigNumEquals(list[j], foo[1]) - } - } - } - } - } - }) - - // Checks the validity of the sorting network in `shortList` - describe('validate sorting network', () => { - const net = [ - [0, 1], - [2, 3], - [4, 5], - [0, 2], - [1, 3], - [4, 6], - [1, 2], - [5, 6], - [0, 4], - [1, 5], - [2, 6], - [1, 4], - [3, 6], - [2, 4], - [3, 5], - [3, 4], - ] - - // See: https://en.wikipedia.org/wiki/Sorting_network#Zero-one_principle - xit('zero-one principle', async () => { - const sortWithNet = (list: number[]) => { - for (const [i, j] of net) { - if (list[i] > list[j]) { - ;[list[i], list[j]] = [list[j], list[i]] - } - } - } - - for (let n = 0; n < (1 << 7) - 1; n++) { - const list = [ - (n >> 6) & 1, - (n >> 5) & 1, - (n >> 4) & 1, - (n >> 3) & 1, - (n >> 2) & 1, - (n >> 1) & 1, - (n >> 0) & 1, - ] - const sum = list.reduce((a, b) => a + b, 0) - sortWithNet(list) - const sortedSum = list.reduce((a, b) => a + b, 0) - assert.equal(sortedSum, sum, 'Number of zeros and ones changed') - list.reduce((switched, i) => { - assert.isTrue(!switched || i != 0, 'error at n=' + n.toString()) - return i != 0 - }, false) - } - }) - }) -}) diff --git a/contracts/test/v0.6/Owned.test.ts b/contracts/test/v0.6/Owned.test.ts deleted file mode 100644 index f522b9c44c9..00000000000 --- a/contracts/test/v0.6/Owned.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Signer, Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' - -let personas: Personas - -let owner: Signer -let nonOwner: Signer -let newOwner: Signer - -let ownedFactory: ContractFactory -let owned: Contract - -before(async () => { - personas = (await getUsers()).personas - owner = personas.Carol - nonOwner = personas.Neil - newOwner = personas.Ned - ownedFactory = await ethers.getContractFactory( - 'src/v0.6/Owned.sol:Owned', - owner, - ) -}) - -describe('Owned', () => { - beforeEach(async () => { - owned = await ownedFactory.connect(owner).deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(owned, ['acceptOwnership', 'owner', 'transferOwnership']) - }) - - describe('#constructor', () => { - it('assigns ownership to the deployer', async () => { - const [actual, expected] = await Promise.all([ - owner.getAddress(), - owned.owner(), - ]) - - assert.equal(actual, expected) - }) - }) - - describe('#transferOwnership', () => { - describe('when called by an owner', () => { - it('emits a log', async () => { - await expect( - owned.connect(owner).transferOwnership(await newOwner.getAddress()), - ) - .to.emit(owned, 'OwnershipTransferRequested') - .withArgs(await owner.getAddress(), await newOwner.getAddress()) - }) - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => - await expect( - owned.connect(nonOwner).transferOwnership(await newOwner.getAddress()), - ).to.be.reverted) - }) - - describe('#acceptOwnership', () => { - describe('after #transferOwnership has been called', () => { - beforeEach(async () => { - await owned - .connect(owner) - .transferOwnership(await newOwner.getAddress()) - }) - - it('allows the recipient to call it', async () => { - await expect(owned.connect(newOwner).acceptOwnership()) - .to.emit(owned, 'OwnershipTransferred') - .withArgs(await owner.getAddress(), await newOwner.getAddress()) - }) - - it('does not allow a non-recipient to call it', async () => - await expect(owned.connect(nonOwner).acceptOwnership()).to.be.reverted) - }) - }) -}) diff --git a/contracts/test/v0.6/SignedSafeMath.test.ts b/contracts/test/v0.6/SignedSafeMath.test.ts deleted file mode 100644 index e942f64d6b5..00000000000 --- a/contracts/test/v0.6/SignedSafeMath.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { ethers } from 'hardhat' -import { expect } from 'chai' -import { Signer, Contract, ContractFactory, BigNumber } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals } from '../test-helpers/matchers' - -let defaultAccount: Signer -let concreteSignedSafeMathFactory: ContractFactory - -before(async () => { - const personas: Personas = (await getUsers()).personas - defaultAccount = personas.Default - concreteSignedSafeMathFactory = await ethers.getContractFactory( - 'src/v0.6/tests/ConcreteSignedSafeMath.sol:ConcreteSignedSafeMath', - defaultAccount, - ) -}) - -describe('SignedSafeMath', () => { - // a version of the adder contract where we make all ABI exposed functions constant - // TODO: submit upstream PR to support constant contract type generation - let adder: Contract - let response: BigNumber - - const INT256_MAX = BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819967', - ) - const INT256_MIN = BigNumber.from( - '-57896044618658097711785492504343953926634992332820282019728792003956564819968', - ) - - beforeEach(async () => { - adder = await concreteSignedSafeMathFactory.connect(defaultAccount).deploy() - }) - - describe('#add', () => { - describe('given a positive and a positive', () => { - it('works', async () => { - response = await adder.testAdd(1, 2) - bigNumEquals(3, response) - }) - - it('works with zero', async () => { - response = await adder.testAdd(INT256_MAX, 0) - bigNumEquals(INT256_MAX, response) - }) - - describe('when both are large enough to overflow', () => { - it('throws', async () => { - await expect(adder.testAdd(INT256_MAX, 1)).to.be.revertedWith( - 'SignedSafeMath: addition overflow', - ) - }) - }) - }) - - describe('given a negative and a negative', () => { - it('works', async () => { - response = await adder.testAdd(-1, -2) - bigNumEquals(-3, response) - }) - - it('works with zero', async () => { - response = await adder.testAdd(INT256_MIN, 0) - bigNumEquals(INT256_MIN, response) - }) - - describe('when both are large enough to overflow', () => { - it('throws', async () => { - await expect(adder.testAdd(INT256_MIN, -1)).to.be.revertedWith( - 'SignedSafeMath: addition overflow', - ) - }) - }) - }) - - describe('given a positive and a negative', () => { - it('works', async () => { - response = await adder.testAdd(1, -2) - bigNumEquals(-1, response) - }) - }) - - describe('given a negative and a positive', () => { - it('works', async () => { - response = await adder.testAdd(-1, 2) - bigNumEquals(1, response) - }) - }) - }) - - describe('#avg', () => { - describe('given a positive and a positive', () => { - it('works', async () => { - response = await adder.testAvg(2, 4) - bigNumEquals(3, response) - }) - - it('works with zero', async () => { - response = await adder.testAvg(0, 4) - bigNumEquals(2, response) - response = await adder.testAvg(4, 0) - bigNumEquals(2, response) - }) - - it('works with large numbers', async () => { - response = await adder.testAvg(INT256_MAX, INT256_MAX) - bigNumEquals(INT256_MAX, response) - }) - - it('rounds towards zero', async () => { - response = await adder.testAvg(1, 2) - bigNumEquals(1, response) - }) - }) - - describe('given a negative and a negative', () => { - it('works', async () => { - response = await adder.testAvg(-2, -4) - bigNumEquals(-3, response) - }) - - it('works with zero', async () => { - response = await adder.testAvg(0, -4) - bigNumEquals(-2, response) - response = await adder.testAvg(-4, 0) - bigNumEquals(-2, response) - }) - - it('works with large numbers', async () => { - response = await adder.testAvg(INT256_MIN, INT256_MIN) - bigNumEquals(INT256_MIN, response) - }) - - it('rounds towards zero', async () => { - response = await adder.testAvg(-1, -2) - bigNumEquals(-1, response) - }) - }) - - describe('given a positive and a negative', () => { - it('works', async () => { - response = await adder.testAvg(2, -4) - bigNumEquals(-1, response) - response = await adder.testAvg(4, -2) - bigNumEquals(1, response) - }) - - it('works with large numbers', async () => { - response = await adder.testAvg(INT256_MAX, -2) - bigNumEquals(INT256_MAX.sub(2).div(2), response) - response = await adder.testAvg(INT256_MAX, INT256_MIN) - bigNumEquals(0, response) - }) - - it('rounds towards zero', async () => { - response = await adder.testAvg(1, -4) - bigNumEquals(-1, response) - response = await adder.testAvg(4, -1) - bigNumEquals(1, response) - }) - }) - - describe('given a negative and a positive', () => { - it('works', async () => { - response = await adder.testAvg(-2, 4) - bigNumEquals(1, response) - response = await adder.testAvg(-4, 2) - bigNumEquals(-1, response) - }) - - it('works with large numbers', async () => { - response = await adder.testAvg(INT256_MIN, 2) - bigNumEquals(INT256_MIN.add(2).div(2), response) - response = await adder.testAvg(INT256_MIN, INT256_MAX) - bigNumEquals(0, response) - }) - - it('rounds towards zero', async () => { - response = await adder.testAvg(-1, 4) - bigNumEquals(1, response) - response = await adder.testAvg(-4, 1) - bigNumEquals(-1, response) - }) - }) - }) -}) diff --git a/contracts/test/v0.6/SimpleReadAccessController.test.ts b/contracts/test/v0.6/SimpleReadAccessController.test.ts deleted file mode 100644 index 7b76bc38cad..00000000000 --- a/contracts/test/v0.6/SimpleReadAccessController.test.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Contract, ContractFactory, Transaction } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' - -let personas: Personas - -let controllerFactory: ContractFactory -let controller: Contract - -before(async () => { - personas = (await getUsers()).personas - controllerFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleReadAccessController.sol:SimpleReadAccessController', - personas.Carol, - ) -}) - -describe('SimpleReadAccessController', () => { - beforeEach(async () => { - controller = await controllerFactory.connect(personas.Carol).deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(controller, [ - 'hasAccess', - 'addAccess', - 'disableAccessCheck', - 'enableAccessCheck', - 'removeAccess', - 'checkEnabled', - // Owned - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('defaults checkEnabled to true', async () => { - assert(await controller.checkEnabled()) - }) - }) - - describe('#hasAccess', () => { - it('allows unauthorized calls originating from the same account', async () => { - assert.isTrue( - await controller - .connect(personas.Eddy) - .hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('blocks unauthorized calls originating from different accounts', async () => { - assert.isFalse( - await controller - .connect(personas.Carol) - .hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - assert.isFalse( - await controller - .connect(personas.Eddy) - .hasAccess(await personas.Carol.getAddress(), '0x00'), - ) - }) - }) - - describe('#addAccess', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller - .connect(personas.Eddy) - .addAccess(await personas.Eddy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - assert.isFalse( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - tx = await controller.addAccess(await personas.Eddy.getAddress()) - }) - - it('adds the address to the controller', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - await expect(tx) - .to.emit(controller, 'AddedAccess') - .withArgs(await personas.Eddy.getAddress()) - }) - - describe('when called twice', () => { - it('does not emit a log', async () => { - const tx2 = await controller.addAccess( - await personas.Eddy.getAddress(), - ) - const receipt = await tx2.wait() - assert.equal(receipt.events?.length, 0) - }) - }) - }) - }) - - describe('#removeAccess', () => { - beforeEach(async () => { - await controller.addAccess(await personas.Eddy.getAddress()) - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller - .connect(personas.Eddy) - .removeAccess(await personas.Eddy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - tx = await controller.removeAccess(await personas.Eddy.getAddress()) - }) - - it('removes the address from the controller', async () => { - assert.isFalse( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - await expect(tx) - .to.emit(controller, 'RemovedAccess') - .withArgs(await personas.Eddy.getAddress()) - }) - - describe('when called twice', () => { - it('does not emit a log', async () => { - const tx2 = await controller.removeAccess( - await personas.Eddy.getAddress(), - ) - const receipt = await tx2.wait() - assert.equal(receipt.events?.length, 0) - }) - }) - }) - }) - - describe('#disableAccessCheck', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller.connect(personas.Eddy).disableAccessCheck(), - ).to.be.revertedWith('Only callable by owner') - assert.isTrue(await controller.checkEnabled()) - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - await controller.addAccess(await personas.Eddy.getAddress()) - tx = await controller.disableAccessCheck() - }) - - it('sets checkEnabled to false', async () => { - assert.isFalse(await controller.checkEnabled()) - }) - - it('allows users with access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('allows users without access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Ned.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - await expect(tx).to.emit(controller, 'CheckAccessDisabled') - }) - - describe('when called twice', () => { - it('does not emit a log', async () => { - const tx2 = await controller.disableAccessCheck() - const receipt = await tx2.wait() - assert.equal(receipt.events?.length, 0) - }) - }) - }) - }) - - describe('#enableAccessCheck', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller.connect(personas.Eddy).enableAccessCheck(), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - await controller.disableAccessCheck() - await controller.addAccess(await personas.Eddy.getAddress()) - tx = await controller.enableAccessCheck() - }) - - it('allows users with access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('does not allow users without access', async () => { - assert.isFalse( - await controller.hasAccess(await personas.Ned.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - expect(tx).to.emit(controller, 'CheckAccessEnabled') - }) - - describe('when called twice', () => { - it('does not emit a log', async () => { - const tx2 = await controller.enableAccessCheck() - const receipt = await tx2.wait() - assert.equal(receipt.events?.length, 0) - }) - }) - }) - }) -}) diff --git a/contracts/test/v0.6/SimpleWriteAccessController.test.ts b/contracts/test/v0.6/SimpleWriteAccessController.test.ts deleted file mode 100644 index ae6c1691f91..00000000000 --- a/contracts/test/v0.6/SimpleWriteAccessController.test.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Contract, ContractFactory, Transaction } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' - -let personas: Personas - -let controllerFactory: ContractFactory -let controller: Contract - -before(async () => { - personas = (await getUsers()).personas - controllerFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - personas.Carol, - ) -}) - -describe('SimpleWriteAccessController', () => { - beforeEach(async () => { - controller = await controllerFactory.connect(personas.Carol).deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(controller, [ - 'hasAccess', - 'addAccess', - 'disableAccessCheck', - 'enableAccessCheck', - 'removeAccess', - 'checkEnabled', - // Owned - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('defaults checkEnabled to true', async () => { - assert(await controller.checkEnabled()) - }) - }) - - describe('#hasAccess', () => { - it('allows unauthorized calls originating from the same account', async () => { - assert.isFalse( - await controller - .connect(personas.Eddy) - .hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('blocks unauthorized calls originating from different accounts', async () => { - assert.isFalse( - await controller - .connect(personas.Carol) - .hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - assert.isFalse( - await controller - .connect(personas.Eddy) - .hasAccess(await personas.Carol.getAddress(), '0x00'), - ) - }) - }) - - describe('#addAccess', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller - .connect(personas.Eddy) - .addAccess(await personas.Eddy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - assert.isFalse( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - tx = await controller.addAccess(await personas.Eddy.getAddress()) - }) - - it('adds the address to the controller', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - expect(tx) - .to.emit(controller, 'AddedAccess') - .withArgs(await personas.Eddy.getAddress()) - }) - }) - }) - - describe('#removeAccess', () => { - beforeEach(async () => { - await controller.addAccess(await personas.Eddy.getAddress()) - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller - .connect(personas.Eddy) - .removeAccess(await personas.Eddy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - tx = await controller.removeAccess(await personas.Eddy.getAddress()) - }) - - it('removes the address from the controller', async () => { - assert.isFalse( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - expect(tx) - .to.emit(controller, 'RemovedAccess') - .withArgs(await personas.Eddy.getAddress()) - }) - }) - }) - - describe('#disableAccessCheck', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller.connect(personas.Eddy).disableAccessCheck(), - ).to.be.revertedWith('Only callable by owner') - assert.isTrue(await controller.checkEnabled()) - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - await controller.addAccess(await personas.Eddy.getAddress()) - tx = await controller.disableAccessCheck() - }) - - it('sets checkEnabled to false', async () => { - assert.isFalse(await controller.checkEnabled()) - }) - - it('allows users with access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('allows users without access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Ned.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - await expect(tx).to.emit(controller, 'CheckAccessDisabled') - }) - }) - }) - - describe('#enableAccessCheck', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller.connect(personas.Eddy).enableAccessCheck(), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - await controller.disableAccessCheck() - await controller.addAccess(await personas.Eddy.getAddress()) - tx = await controller.enableAccessCheck() - }) - - it('allows users with access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('does not allow users without access', async () => { - assert.isFalse( - await controller.hasAccess(await personas.Ned.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - await expect(tx).to.emit(controller, 'CheckAccessEnabled') - }) - }) - }) -}) diff --git a/contracts/test/v0.6/VRFD20.test.ts b/contracts/test/v0.6/VRFD20.test.ts deleted file mode 100644 index 77141be6230..00000000000 --- a/contracts/test/v0.6/VRFD20.test.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { - BigNumber, - constants, - Contract, - ContractFactory, - ContractTransaction, -} from 'ethers' -import { getUsers, Personas, Roles } from '../test-helpers/setup' -import { - evmWordToAddress, - getLog, - publicAbi, - toBytes32String, - toWei, - numToBytes32, - getLogs, -} from '../test-helpers/helpers' - -let roles: Roles -let personas: Personas -let linkTokenFactory: ContractFactory -let vrfCoordinatorMockFactory: ContractFactory -let vrfD20Factory: ContractFactory - -before(async () => { - const users = await getUsers() - - roles = users.roles - personas = users.personas - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) - vrfCoordinatorMockFactory = await ethers.getContractFactory( - 'src/v0.6/tests/VRFCoordinatorMock.sol:VRFCoordinatorMock', - roles.defaultAccount, - ) - vrfD20Factory = await ethers.getContractFactory( - 'src/v0.6/examples/VRFD20.sol:VRFD20', - roles.defaultAccount, - ) -}) - -describe('VRFD20', () => { - const deposit = toWei('1') - const fee = toWei('0.1') - const keyHash = toBytes32String('keyHash') - - let link: Contract - let vrfCoordinator: Contract - let vrfD20: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - vrfCoordinator = await vrfCoordinatorMockFactory - .connect(roles.defaultAccount) - .deploy(link.address) - vrfD20 = await vrfD20Factory - .connect(roles.defaultAccount) - .deploy(vrfCoordinator.address, link.address, keyHash, fee) - await link.transfer(vrfD20.address, deposit) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(vrfD20, [ - // Owned - 'acceptOwnership', - 'owner', - 'transferOwnership', - //VRFConsumerBase - 'rawFulfillRandomness', - // VRFD20 - 'rollDice', - 'house', - 'withdrawLINK', - 'keyHash', - 'fee', - 'setKeyHash', - 'setFee', - ]) - }) - - describe('#withdrawLINK', () => { - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20 - .connect(roles.stranger) - .withdrawLINK(await roles.stranger.getAddress(), deposit), - ).to.be.revertedWith('Only callable by owner') - }) - - it('reverts when not enough LINK in the contract', async () => { - const withdrawAmount = deposit.mul(2) - await expect( - vrfD20 - .connect(roles.defaultAccount) - .withdrawLINK( - await roles.defaultAccount.getAddress(), - withdrawAmount, - ), - ).to.be.reverted - }) - }) - - describe('success', () => { - it('withdraws LINK', async () => { - const startingAmount = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - const expectedAmount = BigNumber.from(startingAmount).add(deposit) - await vrfD20 - .connect(roles.defaultAccount) - .withdrawLINK(await roles.defaultAccount.getAddress(), deposit) - const actualAmount = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - assert.equal(actualAmount.toString(), expectedAmount.toString()) - }) - }) - }) - - describe('#setKeyHash', () => { - const newHash = toBytes32String('newhash') - - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20.connect(roles.stranger).setKeyHash(newHash), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('success', () => { - it('sets the key hash', async () => { - await vrfD20.setKeyHash(newHash) - const actualHash = await vrfD20.keyHash() - assert.equal(actualHash, newHash) - }) - }) - }) - - describe('#setFee', () => { - const newFee = 1234 - - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20.connect(roles.stranger).setFee(newFee), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('success', () => { - it('sets the fee', async () => { - await vrfD20.setFee(newFee) - const actualFee = await vrfD20.fee() - assert.equal(actualFee.toString(), newFee.toString()) - }) - }) - }) - - describe('#house', () => { - describe('failure', () => { - it('reverts when dice not rolled', async () => { - await expect( - vrfD20.house(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Dice not rolled') - }) - - it('reverts when dice roll is in progress', async () => { - await vrfD20.rollDice(await personas.Nancy.getAddress()) - await expect( - vrfD20.house(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Roll in progress') - }) - }) - - describe('success', () => { - it('returns the correct house', async () => { - const randomness = 98765 - const expectedHouse = 'Martell' - const tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - const log = await getLog(tx, 3) - const eventRequestId = log?.topics?.[1] - await vrfCoordinator.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - const response = await vrfD20.house(await personas.Nancy.getAddress()) - assert.equal(response.toString(), expectedHouse) - }) - }) - }) - - describe('#rollDice', () => { - describe('success', () => { - let tx: ContractTransaction - beforeEach(async () => { - tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - }) - - it('emits a RandomnessRequest event from the VRFCoordinator', async () => { - const log = await getLog(tx, 2) - const topics = log?.topics - assert.equal(evmWordToAddress(topics?.[1]), vrfD20.address) - assert.equal(topics?.[2], keyHash) - assert.equal(topics?.[3], constants.HashZero) - }) - }) - - describe('failure', () => { - it('reverts when LINK balance is zero', async () => { - const vrfD202 = await vrfD20Factory - .connect(roles.defaultAccount) - .deploy(vrfCoordinator.address, link.address, keyHash, fee) - await expect( - vrfD202.rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Not enough LINK to pay fee') - }) - - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20 - .connect(roles.stranger) - .rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - - it('reverts when the roller rolls more than once', async () => { - await vrfD20.rollDice(await personas.Nancy.getAddress()) - await expect( - vrfD20.rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Already rolled') - }) - }) - }) - - describe('#fulfillRandomness', () => { - const randomness = 98765 - const expectedModResult = (randomness % 20) + 1 - const expectedHouse = 'Martell' - let eventRequestId: string - beforeEach(async () => { - const tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - const log = await getLog(tx, 3) - eventRequestId = log?.topics?.[1] - }) - - describe('success', () => { - let tx: ContractTransaction - beforeEach(async () => { - tx = await vrfCoordinator.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - }) - - it('emits a DiceLanded event', async () => { - const log = await getLog(tx, 0) - assert.equal(log?.topics[1], eventRequestId) - assert.equal(log?.topics[2], numToBytes32(expectedModResult)) - }) - - it('sets the correct dice roll result', async () => { - const response = await vrfD20.house(await personas.Nancy.getAddress()) - assert.equal(response.toString(), expectedHouse) - }) - - it('allows someone else to roll', async () => { - const secondRandomness = 55555 - tx = await vrfD20.rollDice(await personas.Ned.getAddress()) - const log = await getLog(tx, 3) - eventRequestId = log?.topics?.[1] - tx = await vrfCoordinator.callBackWithRandomness( - eventRequestId, - secondRandomness, - vrfD20.address, - ) - }) - }) - - describe('failure', () => { - it('does not fulfill when fulfilled by the wrong VRFcoordinator', async () => { - const vrfCoordinator2 = await vrfCoordinatorMockFactory - .connect(roles.defaultAccount) - .deploy(link.address) - - const tx = await vrfCoordinator2.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/AggregatorProxy.test.ts b/contracts/test/v0.7/AggregatorProxy.test.ts deleted file mode 100644 index 6e8ee41983d..00000000000 --- a/contracts/test/v0.7/AggregatorProxy.test.ts +++ /dev/null @@ -1,743 +0,0 @@ -import { ethers } from 'hardhat' -import { - increaseTimeBy, - numToBytes32, - publicAbi, - toWei, -} from '../test-helpers/helpers' -import { assert } from 'chai' -import { BigNumber, constants, Contract, ContractFactory, Signer } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals, evmRevert } from '../test-helpers/matchers' - -let personas: Personas -let defaultAccount: Signer - -let linkTokenFactory: ContractFactory -let aggregatorFactory: ContractFactory -let historicAggregatorFactory: ContractFactory -let aggregatorFacadeFactory: ContractFactory -let aggregatorProxyFactory: ContractFactory -let fluxAggregatorFactory: ContractFactory -let reverterFactory: ContractFactory - -before(async () => { - const users = await getUsers() - - personas = users.personas - defaultAccount = users.roles.defaultAccount - - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - defaultAccount, - ) - aggregatorFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', - defaultAccount, - ) - historicAggregatorFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockV2Aggregator.sol:MockV2Aggregator', - defaultAccount, - ) - aggregatorFacadeFactory = await ethers.getContractFactory( - 'src/v0.6/AggregatorFacade.sol:AggregatorFacade', - defaultAccount, - ) - historicAggregatorFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockV2Aggregator.sol:MockV2Aggregator', - defaultAccount, - ) - aggregatorFacadeFactory = await ethers.getContractFactory( - 'src/v0.6/AggregatorFacade.sol:AggregatorFacade', - defaultAccount, - ) - aggregatorProxyFactory = await ethers.getContractFactory( - 'src/v0.7/dev/AggregatorProxy.sol:AggregatorProxy', - defaultAccount, - ) - fluxAggregatorFactory = await ethers.getContractFactory( - 'src/v0.6/FluxAggregator.sol:FluxAggregator', - defaultAccount, - ) - reverterFactory = await ethers.getContractFactory( - 'src/v0.6/tests/Reverter.sol:Reverter', - defaultAccount, - ) -}) - -describe('AggregatorProxy', () => { - const deposit = toWei('100') - const response = numToBytes32(54321) - const response2 = numToBytes32(67890) - const decimals = 18 - const phaseBase = BigNumber.from(2).pow(64) - - let link: Contract - let aggregator: Contract - let aggregator2: Contract - let historicAggregator: Contract - let proxy: Contract - let flux: Contract - let reverter: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(defaultAccount).deploy() - aggregator = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response) - await link.transfer(aggregator.address, deposit) - proxy = await aggregatorProxyFactory - .connect(defaultAccount) - .deploy(aggregator.address) - const emptyAddress = constants.AddressZero - flux = await fluxAggregatorFactory - .connect(personas.Carol) - .deploy(link.address, 0, 0, emptyAddress, 0, 0, 18, 'TEST / LINK') - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(proxy, [ - 'aggregator', - 'confirmAggregator', - 'decimals', - 'description', - 'getAnswer', - 'getRoundData', - 'getTimestamp', - 'latestAnswer', - 'latestRound', - 'latestRoundData', - 'latestTimestamp', - 'phaseAggregators', - 'phaseId', - 'proposeAggregator', - 'proposedAggregator', - 'proposedGetRoundData', - 'proposedLatestRoundData', - 'version', - // Ownable methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('constructor', () => { - it('sets the proxy phase and aggregator', async () => { - bigNumEquals(1, await proxy.phaseId()) - assert.equal(aggregator.address, await proxy.phaseAggregators(1)) - }) - }) - - describe('#latestRound', () => { - it('pulls the rate from the aggregator', async () => { - bigNumEquals(phaseBase.add(1), await proxy.latestRound()) - }) - }) - - describe('#latestAnswer', () => { - it('pulls the rate from the aggregator', async () => { - bigNumEquals(response, await proxy.latestAnswer()) - const latestRound = await proxy.latestRound() - bigNumEquals(response, await proxy.getAnswer(latestRound)) - }) - - describe('after being updated to another contract', () => { - beforeEach(async () => { - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - await link.transfer(aggregator2.address, deposit) - bigNumEquals(response2, await aggregator2.latestAnswer()) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - }) - - it('pulls the rate from the new aggregator', async () => { - bigNumEquals(response2, await proxy.latestAnswer()) - const latestRound = await proxy.latestRound() - bigNumEquals(response2, await proxy.getAnswer(latestRound)) - }) - }) - - describe('when the relevant info is not available', () => { - beforeEach(async () => { - await proxy.proposeAggregator(flux.address) - await proxy.confirmAggregator(flux.address) - }) - - it('does not revert when called with a non existent ID', async () => { - const actual = await proxy.latestAnswer() - bigNumEquals(0, actual) - }) - }) - }) - - describe('#getAnswer', () => { - describe('when the relevant round is not available', () => { - beforeEach(async () => { - await proxy.proposeAggregator(flux.address) - await proxy.confirmAggregator(flux.address) - }) - - it('does not revert when called with a non existent ID', async () => { - const proxyId = phaseBase.mul(await proxy.phaseId()).add(1) - const actual = await proxy.getAnswer(proxyId) - bigNumEquals(0, actual) - }) - }) - - describe('when the answer reverts in a non-predicted way', () => { - it('reverts', async () => { - reverter = await reverterFactory.connect(defaultAccount).deploy() - await proxy.proposeAggregator(reverter.address) - await proxy.confirmAggregator(reverter.address) - assert.equal(reverter.address, await proxy.aggregator()) - - const proxyId = phaseBase.mul(await proxy.phaseId()) - - await evmRevert(proxy.getAnswer(proxyId), 'Raised by Reverter.sol') - }) - }) - - describe('after being updated to another contract', () => { - let preUpdateRoundId: BigNumber - let preUpdateAnswer: BigNumber - - beforeEach(async () => { - preUpdateRoundId = await proxy.latestRound() - preUpdateAnswer = await proxy.latestAnswer() - - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - await link.transfer(aggregator2.address, deposit) - bigNumEquals(response2, await aggregator2.latestAnswer()) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - }) - - it('reports answers for previous phases', async () => { - const actualAnswer = await proxy.getAnswer(preUpdateRoundId) - bigNumEquals(preUpdateAnswer, actualAnswer) - }) - }) - - describe('when the relevant info is not available', () => { - it('returns 0', async () => { - const actual = await proxy.getAnswer(phaseBase.mul(777)) - bigNumEquals(0, actual) - }) - }) - - describe('when the round ID is too large', () => { - const overflowRoundId = BigNumber.from(2) - .pow(255) - .add(phaseBase) // get the original phase - .add(1) // get the original round - it('returns 0', async () => { - const actual = await proxy.getTimestamp(overflowRoundId) - bigNumEquals(0, actual) - }) - }) - }) - - describe('#getTimestamp', () => { - describe('when the relevant round is not available', () => { - beforeEach(async () => { - await proxy.proposeAggregator(flux.address) - await proxy.confirmAggregator(flux.address) - }) - - it('does not revert when called with a non existent ID', async () => { - const proxyId = phaseBase.mul(await proxy.phaseId()).add(1) - const actual = await proxy.getTimestamp(proxyId) - bigNumEquals(0, actual) - }) - }) - - describe('when the relevant info is not available', () => { - it('returns 0', async () => { - const actual = await proxy.getTimestamp(phaseBase.mul(777)) - bigNumEquals(0, actual) - }) - }) - - describe('when the round ID is too large', () => { - const overflowRoundId = BigNumber.from(2) - .pow(255) - .add(phaseBase) // get the original phase - .add(1) // get the original round - - it('returns 0', async () => { - const actual = await proxy.getTimestamp(overflowRoundId) - bigNumEquals(0, actual) - }) - }) - }) - - describe('#latestTimestamp', () => { - beforeEach(async () => { - const height = await aggregator.latestTimestamp() - assert.notEqual('0', height.toString()) - }) - - it('pulls the timestamp from the aggregator', async () => { - bigNumEquals( - await aggregator.latestTimestamp(), - await proxy.latestTimestamp(), - ) - const latestRound = await proxy.latestRound() - bigNumEquals( - await aggregator.latestTimestamp(), - await proxy.getTimestamp(latestRound), - ) - }) - - describe('after being updated to another contract', () => { - beforeEach(async () => { - await increaseTimeBy(30, ethers.provider) - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - - const height2 = await aggregator2.latestTimestamp() - assert.notEqual('0', height2.toString()) - - const height1 = await aggregator.latestTimestamp() - assert.notEqual( - height1.toString(), - height2.toString(), - 'Height1 and Height2 should not be equal', - ) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - }) - - it('pulls the timestamp from the new aggregator', async () => { - bigNumEquals( - await aggregator2.latestTimestamp(), - await proxy.latestTimestamp(), - ) - const latestRound = await proxy.latestRound() - bigNumEquals( - await aggregator2.latestTimestamp(), - await proxy.getTimestamp(latestRound), - ) - }) - }) - }) - - describe('#getRoundData', () => { - describe('when pointed at a Historic Aggregator', () => { - beforeEach(async () => { - historicAggregator = await historicAggregatorFactory - .connect(defaultAccount) - .deploy(response2) - await proxy.proposeAggregator(historicAggregator.address) - await proxy.confirmAggregator(historicAggregator.address) - }) - - it('reverts', async () => { - const latestRoundId = await historicAggregator.latestRound() - await evmRevert(proxy.getRoundData(latestRoundId)) - }) - - describe('when pointed at an Aggregator Facade', () => { - beforeEach(async () => { - const facade = await aggregatorFacadeFactory - .connect(defaultAccount) - .deploy(aggregator.address, 18, 'LINK/USD: Aggregator Facade') - await proxy.proposeAggregator(facade.address) - await proxy.confirmAggregator(facade.address) - }) - - it('works for a valid roundId', async () => { - const aggId = await aggregator.latestRound() - const phaseId = phaseBase.mul(await proxy.phaseId()) - const proxyId = phaseId.add(aggId) - - const round = await proxy.getRoundData(proxyId) - bigNumEquals(proxyId, round.id) - bigNumEquals(response, round.answer) - const nowSeconds = new Date().valueOf() / 1000 - assert.isAbove(round.updatedAt.toNumber(), nowSeconds - 120) - bigNumEquals(round.updatedAt, round.startedAt) - bigNumEquals(proxyId, round.answeredInRound) - }) - }) - }) - - describe('when pointed at a FluxAggregator', () => { - beforeEach(async () => { - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - }) - - it('works for a valid round ID', async () => { - const aggId = phaseBase.sub(2) - await aggregator2 - .connect(personas.Carol) - .updateRoundData(aggId, response2, 77, 42) - - const phaseId = phaseBase.mul(await proxy.phaseId()) - const proxyId = phaseId.add(aggId) - - const round = await proxy.getRoundData(proxyId) - bigNumEquals(proxyId, round.id) - bigNumEquals(response2, round.answer) - bigNumEquals(42, round.startedAt) - bigNumEquals(77, round.updatedAt) - bigNumEquals(proxyId, round.answeredInRound) - }) - }) - - it('reads round ID of a previous phase', async () => { - const oldphaseId = phaseBase.mul(await proxy.phaseId()) - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - - const aggId = await aggregator.latestRound() - const proxyId = oldphaseId.add(aggId) - - const round = await proxy.getRoundData(proxyId) - bigNumEquals(proxyId, round.id) - bigNumEquals(response, round.answer) - - const nowSeconds = new Date().valueOf() / 1000 - assert.isAbove(round.startedAt.toNumber(), nowSeconds - 120) - bigNumEquals(round.startedAt, round.updatedAt) - bigNumEquals(proxyId, round.answeredInRound) - }) - }) - - describe('#latestRoundData', () => { - describe('when pointed at a Historic Aggregator', () => { - beforeEach(async () => { - historicAggregator = await historicAggregatorFactory - .connect(defaultAccount) - .deploy(response2) - await proxy.proposeAggregator(historicAggregator.address) - await proxy.confirmAggregator(historicAggregator.address) - }) - - it('reverts', async () => { - await evmRevert(proxy.latestRoundData()) - }) - - describe('when pointed at an Aggregator Facade', () => { - beforeEach(async () => { - const facade = await aggregatorFacadeFactory - .connect(defaultAccount) - .deploy( - historicAggregator.address, - 17, - 'DOGE/ZWL: Aggregator Facade', - ) - await proxy.proposeAggregator(facade.address) - await proxy.confirmAggregator(facade.address) - }) - - it('does not revert', async () => { - const aggId = await historicAggregator.latestRound() - const phaseId = phaseBase.mul(await proxy.phaseId()) - const proxyId = phaseId.add(aggId) - - const round = await proxy.latestRoundData() - bigNumEquals(proxyId, round.id) - bigNumEquals(response2, round.answer) - const nowSeconds = new Date().valueOf() / 1000 - assert.isAbove(round.updatedAt.toNumber(), nowSeconds - 120) - bigNumEquals(round.updatedAt, round.startedAt) - bigNumEquals(proxyId, round.answeredInRound) - }) - - it('uses the decimals set in the constructor', async () => { - bigNumEquals(17, await proxy.decimals()) - }) - - it('uses the description set in the constructor', async () => { - assert.equal('DOGE/ZWL: Aggregator Facade', await proxy.description()) - }) - - it('sets the version to 2', async () => { - bigNumEquals(2, await proxy.version()) - }) - }) - }) - - describe('when pointed at a FluxAggregator', () => { - beforeEach(async () => { - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - }) - - it('does not revert', async () => { - const aggId = phaseBase.sub(2) - await aggregator2 - .connect(personas.Carol) - .updateRoundData(aggId, response2, 77, 42) - - const phaseId = phaseBase.mul(await proxy.phaseId()) - const proxyId = phaseId.add(aggId) - - const round = await proxy.latestRoundData() - bigNumEquals(proxyId, round.id) - bigNumEquals(response2, round.answer) - bigNumEquals(42, round.startedAt) - bigNumEquals(77, round.updatedAt) - bigNumEquals(proxyId, round.answeredInRound) - }) - - it('uses the decimals of the aggregator', async () => { - bigNumEquals(18, await proxy.decimals()) - }) - - it('uses the description of the aggregator', async () => { - assert.equal( - 'v0.6/tests/MockV3Aggregator.sol', - await proxy.description(), - ) - }) - - it('uses the version of the aggregator', async () => { - bigNumEquals(0, await proxy.version()) - }) - }) - }) - - describe('#proposeAggregator', () => { - beforeEach(async () => { - await proxy.transferOwnership(await personas.Carol.getAddress()) - await proxy.connect(personas.Carol).acceptOwnership() - - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, 1) - - assert.equal(aggregator.address, await proxy.aggregator()) - }) - - describe('when called by the owner', () => { - it('sets the address of the proposed aggregator', async () => { - await proxy - .connect(personas.Carol) - .proposeAggregator(aggregator2.address) - - assert.equal(aggregator2.address, await proxy.proposedAggregator()) - }) - - it('emits an AggregatorProposed event', async () => { - const tx = await proxy - .connect(personas.Carol) - .proposeAggregator(aggregator2.address) - const receipt = await tx.wait() - const eventLog = receipt?.events - - assert.equal(eventLog?.length, 1) - assert.equal(eventLog?.[0].event, 'AggregatorProposed') - assert.equal(eventLog?.[0].args?.[0], aggregator.address) - assert.equal(eventLog?.[0].args?.[1], aggregator2.address) - }) - }) - - describe('when called by a non-owner', () => { - it('does not update', async () => { - await evmRevert( - proxy.connect(personas.Neil).proposeAggregator(aggregator2.address), - 'Only callable by owner', - ) - - assert.equal(aggregator.address, await proxy.aggregator()) - }) - }) - }) - - describe('#confirmAggregator', () => { - beforeEach(async () => { - await proxy.transferOwnership(await personas.Carol.getAddress()) - await proxy.connect(personas.Carol).acceptOwnership() - - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, 1) - - assert.equal(aggregator.address, await proxy.aggregator()) - }) - - describe('when called by the owner', () => { - beforeEach(async () => { - await proxy - .connect(personas.Carol) - .proposeAggregator(aggregator2.address) - }) - - it('sets the address of the new aggregator', async () => { - await proxy - .connect(personas.Carol) - .confirmAggregator(aggregator2.address) - - assert.equal(aggregator2.address, await proxy.aggregator()) - }) - - it('increases the phase', async () => { - bigNumEquals(1, await proxy.phaseId()) - - await proxy - .connect(personas.Carol) - .confirmAggregator(aggregator2.address) - - bigNumEquals(2, await proxy.phaseId()) - }) - - it('increases the round ID', async () => { - bigNumEquals(phaseBase.add(1), await proxy.latestRound()) - - await proxy - .connect(personas.Carol) - .confirmAggregator(aggregator2.address) - - bigNumEquals(phaseBase.mul(2).add(1), await proxy.latestRound()) - }) - - it('sets the proxy phase and aggregator', async () => { - assert.equal( - '0x0000000000000000000000000000000000000000', - await proxy.phaseAggregators(2), - ) - - await proxy - .connect(personas.Carol) - .confirmAggregator(aggregator2.address) - - assert.equal(aggregator2.address, await proxy.phaseAggregators(2)) - }) - - it('emits an AggregatorConfirmed event', async () => { - const tx = await proxy - .connect(personas.Carol) - .confirmAggregator(aggregator2.address) - const receipt = await tx.wait() - const eventLog = receipt?.events - - assert.equal(eventLog?.length, 1) - assert.equal(eventLog?.[0].event, 'AggregatorConfirmed') - assert.equal(eventLog?.[0].args?.[0], aggregator.address) - assert.equal(eventLog?.[0].args?.[1], aggregator2.address) - }) - }) - - describe('when called by a non-owner', () => { - beforeEach(async () => { - await proxy - .connect(personas.Carol) - .proposeAggregator(aggregator2.address) - }) - - it('does not update', async () => { - await evmRevert( - proxy.connect(personas.Neil).confirmAggregator(aggregator2.address), - 'Only callable by owner', - ) - - assert.equal(aggregator.address, await proxy.aggregator()) - }) - }) - }) - - describe('#proposedGetRoundData', () => { - beforeEach(async () => { - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - }) - - describe('when an aggregator has been proposed', () => { - beforeEach(async () => { - await proxy - .connect(defaultAccount) - .proposeAggregator(aggregator2.address) - assert.equal(await proxy.proposedAggregator(), aggregator2.address) - }) - - it('returns the data for the proposed aggregator', async () => { - const roundId = await aggregator2.latestRound() - const round = await proxy.proposedGetRoundData(roundId) - bigNumEquals(roundId, round.id) - bigNumEquals(response2, round.answer) - }) - - describe('after the aggregator has been confirmed', () => { - beforeEach(async () => { - await proxy - .connect(defaultAccount) - .confirmAggregator(aggregator2.address) - assert.equal(await proxy.aggregator(), aggregator2.address) - }) - - it('reverts', async () => { - const roundId = await aggregator2.latestRound() - await evmRevert( - proxy.proposedGetRoundData(roundId), - 'No proposed aggregator present', - ) - }) - }) - }) - }) - - describe('#proposedLatestRoundData', () => { - beforeEach(async () => { - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - }) - - describe('when an aggregator has been proposed', () => { - beforeEach(async () => { - await proxy - .connect(defaultAccount) - .proposeAggregator(aggregator2.address) - assert.equal(await proxy.proposedAggregator(), aggregator2.address) - }) - - it('returns the data for the proposed aggregator', async () => { - const roundId = await aggregator2.latestRound() - const round = await proxy.proposedLatestRoundData() - bigNumEquals(roundId, round.id) - bigNumEquals(response2, round.answer) - }) - - describe('after the aggregator has been confirmed', () => { - beforeEach(async () => { - await proxy - .connect(defaultAccount) - .confirmAggregator(aggregator2.address) - assert.equal(await proxy.aggregator(), aggregator2.address) - }) - - it('reverts', async () => { - await evmRevert( - proxy.proposedLatestRoundData(), - 'No proposed aggregator present', - ) - }) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/AuthorizedForwarder.test.ts b/contracts/test/v0.7/AuthorizedForwarder.test.ts deleted file mode 100644 index e1fa2f1f708..00000000000 --- a/contracts/test/v0.7/AuthorizedForwarder.test.ts +++ /dev/null @@ -1,444 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Contract, ContractFactory, ContractReceipt } from 'ethers' -import { getUsers, Roles } from '../test-helpers/setup' -import { evmRevert } from '../test-helpers/matchers' - -let getterSetterFactory: ContractFactory -let forwarderFactory: ContractFactory -let brokenFactory: ContractFactory -let linkTokenFactory: ContractFactory - -let roles: Roles -const zeroAddress = ethers.constants.AddressZero - -before(async () => { - const users = await getUsers() - - roles = users.roles - getterSetterFactory = await ethers.getContractFactory( - 'src/v0.4/tests/GetterSetter.sol:GetterSetter', - roles.defaultAccount, - ) - brokenFactory = await ethers.getContractFactory( - 'src/v0.8/tests/Broken.sol:Broken', - roles.defaultAccount, - ) - forwarderFactory = await ethers.getContractFactory( - 'src/v0.7/AuthorizedForwarder.sol:AuthorizedForwarder', - roles.defaultAccount, - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) -}) - -describe('AuthorizedForwarder', () => { - let link: Contract - let forwarder: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - forwarder = await forwarderFactory - .connect(roles.defaultAccount) - .deploy( - link.address, - await roles.defaultAccount.getAddress(), - zeroAddress, - '0x', - ) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(forwarder, [ - 'forward', - 'getAuthorizedSenders', - 'getChainlinkToken', - 'isAuthorizedSender', - 'ownerForward', - 'setAuthorizedSenders', - 'transferOwnershipWithMessage', - 'typeAndVersion', - // ConfirmedOwner - 'transferOwnership', - 'acceptOwnership', - 'owner', - ]) - }) - - describe('#typeAndVersion', () => { - it('describes the authorized forwarder', async () => { - assert.equal( - await forwarder.typeAndVersion(), - 'AuthorizedForwarder 1.0.0', - ) - }) - }) - - describe('deployment', () => { - it('sets the correct link token', async () => { - assert.equal(await forwarder.getChainlinkToken(), link.address) - }) - - it('reverts on zeroAddress value for link token', async () => { - await evmRevert( - forwarderFactory.connect(roles.defaultAccount).deploy( - zeroAddress, // Link Address - await roles.defaultAccount.getAddress(), - zeroAddress, - '0x', - ), - ) - }) - - it('sets no authorized senders', async () => { - const senders = await forwarder.getAuthorizedSenders() - assert.equal(senders.length, 0) - }) - }) - - describe('#setAuthorizedSenders', () => { - let newSenders: string[] - let receipt: ContractReceipt - describe('when called by the owner', () => { - describe('set authorized senders containing duplicate/s', () => { - beforeEach(async () => { - newSenders = [ - await roles.oracleNode1.getAddress(), - await roles.oracleNode1.getAddress(), - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - }) - it('reverts with a must not have duplicate senders message', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders), - 'Must not have duplicate senders', - ) - }) - }) - - describe('setting 3 authorized senders', () => { - beforeEach(async () => { - newSenders = [ - await roles.oracleNode1.getAddress(), - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - const tx = await forwarder - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders) - receipt = await tx.wait() - }) - - it('adds the authorized nodes', async () => { - const authorizedSenders = await forwarder.getAuthorizedSenders() - assert.equal(newSenders.length, authorizedSenders.length) - for (let i = 0; i < authorizedSenders.length; i++) { - assert.equal(authorizedSenders[i], newSenders[i]) - } - }) - - it('emits an event', async () => { - assert.equal(receipt.events?.length, 1) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'AuthorizedSendersChanged') - const encodedSenders = ethers.utils.defaultAbiCoder.encode( - ['address[]', 'address'], - [newSenders, await roles.defaultAccount.getAddress()], - ) - assert.equal(responseEvent?.data, encodedSenders) - }) - - it('replaces the authorized nodes', async () => { - const newSenders = await forwarder - .connect(roles.defaultAccount) - .getAuthorizedSenders() - assert.notIncludeOrderedMembers(newSenders, [ - await roles.oracleNode.getAddress(), - ]) - }) - - after(async () => { - await forwarder - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.oracleNode.getAddress()]) - }) - }) - - describe('setting 0 authorized senders', () => { - beforeEach(async () => { - newSenders = [] - }) - - it('reverts with a minimum senders message', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders), - 'Must have at least 1 sender', - ) - }) - }) - }) - - describe('when called by a non-owner', () => { - it('cannot add an authorized node', async () => { - await evmRevert( - forwarder - .connect(roles.stranger) - .setAuthorizedSenders([await roles.stranger.getAddress()]), - 'Cannot set authorized senders', - ) - }) - }) - }) - - describe('#forward', () => { - let bytes: string - let payload: string - let mock: Contract - - beforeEach(async () => { - mock = await getterSetterFactory.connect(roles.defaultAccount).deploy() - bytes = ethers.utils.hexlify(ethers.utils.randomBytes(100)) - payload = getterSetterFactory.interface.encodeFunctionData( - getterSetterFactory.interface.getFunction('setBytes'), - [bytes], - ) - }) - - describe('when called by an unauthorized node', () => { - it('reverts', async () => { - await evmRevert( - forwarder.connect(roles.stranger).forward(mock.address, payload), - ) - }) - }) - - describe('when called by an authorized node', () => { - beforeEach(async () => { - await forwarder - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.defaultAccount.getAddress()]) - }) - - describe('when destination call reverts', () => { - let brokenMock: Contract - let brokenPayload: string - let brokenMsgPayload: string - - beforeEach(async () => { - brokenMock = await brokenFactory - .connect(roles.defaultAccount) - .deploy() - brokenMsgPayload = brokenFactory.interface.encodeFunctionData( - brokenFactory.interface.getFunction('revertWithMessage'), - ['Failure message'], - ) - - brokenPayload = brokenFactory.interface.encodeFunctionData( - brokenFactory.interface.getFunction('revertSilently'), - [], - ) - }) - - describe('when reverts with message', () => { - it('return revert message', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .forward(brokenMock.address, brokenMsgPayload), - "reverted with reason string 'Failure message'", - ) - }) - }) - - describe('when reverts without message', () => { - it('return silent failure message', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .forward(brokenMock.address, brokenPayload), - 'Forwarded call reverted without reason', - ) - }) - }) - }) - - describe('when sending to a non-contract address', () => { - it('reverts', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .forward(zeroAddress, payload), - 'Must forward to a contract', - ) - }) - }) - - describe('when attempting to forward to the link token', () => { - it('reverts', async () => { - const sighash = linkTokenFactory.interface.getSighash('name') // any Link Token function - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .forward(link.address, sighash), - ) - }) - }) - - describe('when forwarding to any other address', () => { - it('forwards the data', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .forward(mock.address, payload) - await tx.wait() - assert.equal(await mock.getBytes(), bytes) - }) - - it('perceives the message is sent by the AuthorizedForwarder', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .forward(mock.address, payload) - await expect(tx) - .to.emit(mock, 'SetBytes') - .withArgs(forwarder.address, bytes) - }) - }) - }) - }) - - describe('#transferOwnershipWithMessage', () => { - const message = '0x42' - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - forwarder - .connect(roles.stranger) - .transferOwnershipWithMessage( - await roles.stranger.getAddress(), - message, - ), - 'Only callable by owner', - ) - }) - }) - - describe('when called by the owner', () => { - it('calls the normal ownership transfer proposal', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .transferOwnershipWithMessage( - await roles.stranger.getAddress(), - message, - ) - const receipt = await tx.wait() - - assert.equal(receipt?.events?.[0]?.event, 'OwnershipTransferRequested') - assert.equal(receipt?.events?.[0]?.address, forwarder.address) - assert.equal( - receipt?.events?.[0]?.args?.[0], - await roles.defaultAccount.getAddress(), - ) - assert.equal( - receipt?.events?.[0]?.args?.[1], - await roles.stranger.getAddress(), - ) - }) - - it('calls the normal ownership transfer proposal', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .transferOwnershipWithMessage( - await roles.stranger.getAddress(), - message, - ) - const receipt = await tx.wait() - - assert.equal( - receipt?.events?.[1]?.event, - 'OwnershipTransferRequestedWithMessage', - ) - assert.equal(receipt?.events?.[1]?.address, forwarder.address) - assert.equal( - receipt?.events?.[1]?.args?.[0], - await roles.defaultAccount.getAddress(), - ) - assert.equal( - receipt?.events?.[1]?.args?.[1], - await roles.stranger.getAddress(), - ) - assert.equal(receipt?.events?.[1]?.args?.[2], message) - }) - }) - }) - - describe('#ownerForward', () => { - let bytes: string - let payload: string - let mock: Contract - - beforeEach(async () => { - mock = await getterSetterFactory.connect(roles.defaultAccount).deploy() - bytes = ethers.utils.hexlify(ethers.utils.randomBytes(100)) - payload = getterSetterFactory.interface.encodeFunctionData( - getterSetterFactory.interface.getFunction('setBytes'), - [bytes], - ) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - forwarder.connect(roles.stranger).ownerForward(mock.address, payload), - ) - }) - }) - - describe('when called by owner', () => { - describe('when attempting to forward to the link token', () => { - it('does not revert', async () => { - const sighash = linkTokenFactory.interface.getSighash('name') // any Link Token function - - await forwarder - .connect(roles.defaultAccount) - .ownerForward(link.address, sighash) - }) - }) - - describe('when forwarding to any other address', () => { - it('forwards the data', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .ownerForward(mock.address, payload) - await tx.wait() - assert.equal(await mock.getBytes(), bytes) - }) - - it('reverts when sending to a non-contract address', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .ownerForward(zeroAddress, payload), - 'Must forward to a contract', - ) - }) - - it('perceives the message is sent by the Operator', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .ownerForward(mock.address, payload) - await expect(tx) - .to.emit(mock, 'SetBytes') - .withArgs(forwarder.address, bytes) - }) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/Chainlink.test.ts b/contracts/test/v0.7/Chainlink.test.ts deleted file mode 100644 index 7792895934c..00000000000 --- a/contracts/test/v0.7/Chainlink.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi, decodeDietCBOR, hexToBuf } from '../test-helpers/helpers' -import { assert } from 'chai' -import { Contract, ContractFactory, providers, Signer } from 'ethers' -import { Roles, getUsers } from '../test-helpers/setup' -import { makeDebug } from '../test-helpers/debug' - -const debug = makeDebug('ChainlinkTestHelper') -let concreteChainlinkFactory: ContractFactory - -let roles: Roles - -before(async () => { - roles = (await getUsers()).roles - concreteChainlinkFactory = await ethers.getContractFactory( - 'src/v0.7/tests/ChainlinkTestHelper.sol:ChainlinkTestHelper', - roles.defaultAccount, - ) -}) - -describe('ChainlinkTestHelper', () => { - let ccl: Contract - let defaultAccount: Signer - - beforeEach(async () => { - defaultAccount = roles.defaultAccount - ccl = await concreteChainlinkFactory.connect(defaultAccount).deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(ccl, [ - 'add', - 'addBytes', - 'addInt', - 'addStringArray', - 'addUint', - 'closeEvent', - 'setBuffer', - ]) - }) - - async function parseCCLEvent(tx: providers.TransactionResponse) { - const receipt = await tx.wait() - const data = receipt.logs?.[0].data - const d = debug.extend('parseCCLEvent') - d('data %s', data) - return ethers.utils.defaultAbiCoder.decode(['bytes'], data ?? '') - } - - describe('#close', () => { - it('handles empty payloads', async () => { - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, {}) - }) - }) - - describe('#setBuffer', () => { - it('emits the buffer', async () => { - await ccl.setBuffer('0xA161616162') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { a: 'b' }) - }) - }) - - describe('#add', () => { - it('stores and logs keys and values', async () => { - await ccl.add('first', 'word!!') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 'word!!' }) - }) - - it('handles two entries', async () => { - await ccl.add('first', 'uno') - await ccl.add('second', 'dos') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 'uno', - second: 'dos', - }) - }) - }) - - describe('#addBytes', () => { - it('stores and logs keys and values', async () => { - await ccl.addBytes('first', '0xaabbccddeeff') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - const expected = hexToBuf('0xaabbccddeeff') - assert.deepEqual(decoded, { first: expected }) - }) - - it('handles two entries', async () => { - await ccl.addBytes('first', '0x756E6F') - await ccl.addBytes('second', '0x646F73') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - const expectedFirst = hexToBuf('0x756E6F') - const expectedSecond = hexToBuf('0x646F73') - assert.deepEqual(decoded, { - first: expectedFirst, - second: expectedSecond, - }) - }) - - it('handles strings', async () => { - await ccl.addBytes('first', ethers.utils.toUtf8Bytes('apple')) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - const expected = ethers.utils.toUtf8Bytes('apple') - assert.deepEqual(decoded, { first: expected }) - }) - }) - - describe('#addInt', () => { - it('stores and logs keys and values', async () => { - await ccl.addInt('first', 1) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 1 }) - }) - - it('handles two entries', async () => { - await ccl.addInt('first', 1) - await ccl.addInt('second', 2) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 1, - second: 2, - }) - }) - }) - - describe('#addUint', () => { - it('stores and logs keys and values', async () => { - await ccl.addUint('first', 1) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 1 }) - }) - - it('handles two entries', async () => { - await ccl.addUint('first', 1) - await ccl.addUint('second', 2) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 1, - second: 2, - }) - }) - }) - - describe('#addStringArray', () => { - it('stores and logs keys and values', async () => { - await ccl.addStringArray('word', [ - ethers.utils.formatBytes32String('seinfeld'), - ethers.utils.formatBytes32String('"4"'), - ethers.utils.formatBytes32String('LIFE'), - ]) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { word: ['seinfeld', '"4"', 'LIFE'] }) - }) - }) -}) diff --git a/contracts/test/v0.7/ChainlinkClient.test.ts b/contracts/test/v0.7/ChainlinkClient.test.ts deleted file mode 100644 index 198d382af79..00000000000 --- a/contracts/test/v0.7/ChainlinkClient.test.ts +++ /dev/null @@ -1,454 +0,0 @@ -import { ethers } from 'hardhat' -import { assert } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { Roles, getUsers } from '../test-helpers/setup' -import { - convertFufillParams, - decodeCCRequest, - decodeRunRequest, - RunRequest, -} from '../test-helpers/oracle' -import { decodeDietCBOR } from '../test-helpers/helpers' -import { evmRevert } from '../test-helpers/matchers' - -let concreteChainlinkClientFactory: ContractFactory -let emptyOracleFactory: ContractFactory -let getterSetterFactory: ContractFactory -let operatorFactory: ContractFactory -let linkTokenFactory: ContractFactory - -let roles: Roles - -before(async () => { - roles = (await getUsers()).roles - - concreteChainlinkClientFactory = await ethers.getContractFactory( - 'src/v0.7/tests/ChainlinkClientTestHelper.sol:ChainlinkClientTestHelper', - roles.defaultAccount, - ) - emptyOracleFactory = await ethers.getContractFactory( - 'src/v0.6/tests/EmptyOracle.sol:EmptyOracle', - roles.defaultAccount, - ) - getterSetterFactory = await ethers.getContractFactory( - 'src/v0.5/tests/GetterSetter.sol:GetterSetter', - roles.defaultAccount, - ) - operatorFactory = await ethers.getContractFactory( - 'src/v0.7/Operator.sol:Operator', - roles.defaultAccount, - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) -}) - -describe('ChainlinkClientTestHelper', () => { - const specId = - '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000000' - let cc: Contract - let gs: Contract - let oc: Contract - let newoc: Contract - let link: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - oc = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - newoc = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - gs = await getterSetterFactory.connect(roles.defaultAccount).deploy() - cc = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, oc.address) - }) - - describe('#newRequest', () => { - it('forwards the information to the oracle contract through the link token', async () => { - const tx = await cc.publicNewRequest( - specId, - gs.address, - ethers.utils.toUtf8Bytes('requestedBytes32(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - - assert.equal(1, receipt.logs?.length) - const [jId, cbAddr, cbFId, cborData] = receipt.logs - ? decodeCCRequest(receipt.logs[0]) - : [] - const params = decodeDietCBOR(cborData ?? '') - - assert.equal(specId, jId) - assert.equal(gs.address, cbAddr) - assert.equal('0xed53e511', cbFId) - assert.deepEqual({}, params) - }) - }) - - describe('#chainlinkRequest(Request)', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const { events, logs } = await tx.wait() - - assert.equal(4, events?.length) - - assert.equal(logs?.[0].address, cc.address) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - }) - - describe('#chainlinkRequestTo(Request)', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { events } = await tx.wait() - - assert.equal(4, events?.length) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - - it('emits an event on the target oracle contract', async () => { - const tx = await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { logs } = await tx.wait() - const event = logs && newoc.interface.parseLog(logs[3]) - - assert.equal(4, logs?.length) - assert.equal(event?.name, 'OracleRequest') - }) - - it('does not modify the stored oracle address', async () => { - await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const actualOracleAddress = await cc.publicOracleAddress() - assert.equal(oc.address, actualOracleAddress) - }) - }) - - describe('#requestOracleData', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequestOracleData( - specId, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const { events, logs } = await tx.wait() - - assert.equal(4, events?.length) - - assert.equal(logs?.[0].address, cc.address) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - }) - - describe('#requestOracleDataFrom', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequestOracleDataFrom( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { events } = await tx.wait() - - assert.equal(4, events?.length) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - - it('emits an event on the target oracle contract', async () => { - const tx = await cc.publicRequestOracleDataFrom( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { logs } = await tx.wait() - const event = logs && newoc.interface.parseLog(logs[3]) - - assert.equal(4, logs?.length) - assert.equal(event?.name, 'OracleRequest') - }) - - it('does not modify the stored oracle address', async () => { - await cc.publicRequestOracleDataFrom( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const actualOracleAddress = await cc.publicOracleAddress() - assert.equal(oc.address, actualOracleAddress) - }) - }) - - describe('#cancelChainlinkRequest', () => { - let requestId: string - // a concrete chainlink attached to an empty oracle - let ecc: Contract - - beforeEach(async () => { - const emptyOracle = await emptyOracleFactory - .connect(roles.defaultAccount) - .deploy() - ecc = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, emptyOracle.address) - - const tx = await ecc.publicRequest( - specId, - ecc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { events } = await tx.wait() - requestId = (events?.[0]?.args as any).id - }) - - it('emits an event from the contract showing the run was cancelled', async () => { - const tx = await ecc.publicCancelRequest( - requestId, - 0, - ethers.utils.hexZeroPad('0x', 4), - 0, - ) - const { events } = await tx.wait() - - assert.equal(1, events?.length) - assert.equal(events?.[0].event, 'ChainlinkCancelled') - assert.equal(requestId, (events?.[0].args as any).id) - }) - - it('throws if given a bogus event ID', async () => { - await evmRevert( - ecc.publicCancelRequest( - ethers.utils.formatBytes32String('bogusId'), - 0, - ethers.utils.hexZeroPad('0x', 4), - 0, - ), - ) - }) - }) - - describe('#recordChainlinkFulfillment(modifier)', () => { - let request: RunRequest - - beforeEach(async () => { - await oc.setAuthorizedSenders([await roles.defaultAccount.getAddress()]) - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { logs } = await tx.wait() - - request = decodeRunRequest(logs?.[3]) - }) - - it('emits an event marking the request fulfilled', async () => { - const tx = await oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - const { logs } = await tx.wait() - - const event = logs && cc.interface.parseLog(logs[1]) - - assert.equal(2, logs?.length) - assert.equal(event?.name, 'ChainlinkFulfilled') - assert.equal(request.requestId, event?.args.id) - }) - - it('should only allow one fulfillment per id', async () => { - await oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - await evmRevert( - oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Must have a valid requestId', - ) - }) - - it('should only allow the oracle to fulfill the request', async () => { - await evmRevert( - oc - .connect(roles.stranger) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Not authorized sender', - ) - }) - }) - - describe('#fulfillChainlinkRequest(function)', () => { - let request: RunRequest - - beforeEach(async () => { - await oc.setAuthorizedSenders([await roles.defaultAccount.getAddress()]) - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes( - 'publicFulfillChainlinkRequest(bytes32,bytes32)', - ), - 0, - ) - const { logs } = await tx.wait() - - request = decodeRunRequest(logs?.[3]) - }) - - it('emits an event marking the request fulfilled', async () => { - const tx = await oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - const { logs } = await tx.wait() - const event = logs && cc.interface.parseLog(logs[1]) - - assert.equal(2, logs?.length) - assert.equal(event?.name, 'ChainlinkFulfilled') - assert.equal(request.requestId, event?.args?.id) - }) - - it('should only allow one fulfillment per id', async () => { - await oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - await evmRevert( - oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Must have a valid requestId', - ) - }) - - it('should only allow the oracle to fulfill the request', async () => { - await evmRevert( - oc - .connect(roles.stranger) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Not authorized sender', - ) - }) - }) - - describe('#chainlinkToken', () => { - it('returns the Link Token address', async () => { - const addr = await cc.publicChainlinkToken() - assert.equal(addr, link.address) - }) - }) - - describe('#addExternalRequest', () => { - let mock: Contract - let request: RunRequest - - beforeEach(async () => { - mock = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, oc.address) - - const tx = await cc.publicRequest( - specId, - mock.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const receipt = await tx.wait() - - request = decodeRunRequest(receipt.logs?.[3]) - await mock.publicAddExternalRequest(oc.address, request.requestId) - }) - - it('allows the external request to be fulfilled', async () => { - await oc.setAuthorizedSenders([await roles.defaultAccount.getAddress()]) - await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - }) - - it('does not allow the same requestId to be used', async () => { - await evmRevert( - cc.publicAddExternalRequest(newoc.address, request.requestId), - ) - }) - }) -}) diff --git a/contracts/test/v0.7/CompoundPriceFlaggingValidator.test.ts b/contracts/test/v0.7/CompoundPriceFlaggingValidator.test.ts deleted file mode 100644 index 315f7bd9e6b..00000000000 --- a/contracts/test/v0.7/CompoundPriceFlaggingValidator.test.ts +++ /dev/null @@ -1,471 +0,0 @@ -import { ethers } from 'hardhat' -import { evmWordToAddress, getLogs, publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { - BigNumber, - Contract, - ContractFactory, - ContractTransaction, -} from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { evmRevert } from '../test-helpers/matchers' - -let personas: Personas -let validatorFactory: ContractFactory -let acFactory: ContractFactory -let flagsFactory: ContractFactory -let aggregatorFactory: ContractFactory -let compoundOracleFactory: ContractFactory - -before(async () => { - personas = (await getUsers()).personas - - validatorFactory = await ethers.getContractFactory( - 'src/v0.7/dev/CompoundPriceFlaggingValidator.sol:CompoundPriceFlaggingValidator', - personas.Carol, - ) - acFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - personas.Carol, - ) - flagsFactory = await ethers.getContractFactory( - 'src/v0.6/Flags.sol:Flags', - personas.Carol, - ) - aggregatorFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', - personas.Carol, - ) - compoundOracleFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockCompoundOracle.sol:MockCompoundOracle', - personas.Carol, - ) -}) - -describe('CompoundPriceFlaggingVlidator', () => { - let validator: Contract - let aggregator: Contract - let compoundOracle: Contract - let flags: Contract - let ac: Contract - - const aggregatorDecimals = 18 - // 1000 - const initialAggregatorPrice = BigNumber.from('1000000000000000000000') - - const compoundSymbol = 'ETH' - const compoundDecimals = 6 - // 1100 (10% deviation from aggregator price) - const initialCompoundPrice = BigNumber.from('1100000000') - - // (50,000,000 / 1,000,000,000) = 0.05 = 5% deviation threshold - const initialDeviationNumerator = 50_000_000 - - beforeEach(async () => { - ac = await acFactory.connect(personas.Carol).deploy() - flags = await flagsFactory.connect(personas.Carol).deploy(ac.address) - aggregator = await aggregatorFactory - .connect(personas.Carol) - .deploy(aggregatorDecimals, initialAggregatorPrice) - compoundOracle = await compoundOracleFactory - .connect(personas.Carol) - .deploy() - await compoundOracle.setPrice( - compoundSymbol, - initialCompoundPrice, - compoundDecimals, - ) - validator = await validatorFactory - .connect(personas.Carol) - .deploy(flags.address, compoundOracle.address) - await validator - .connect(personas.Carol) - .setFeedDetails( - aggregator.address, - compoundSymbol, - compoundDecimals, - initialDeviationNumerator, - ) - await ac.connect(personas.Carol).addAccess(validator.address) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(validator, [ - 'update', - 'check', - 'setFeedDetails', - 'setFlagsAddress', - 'setCompoundOpenOracleAddress', - 'getFeedDetails', - 'flags', - 'compoundOpenOracle', - // Upkeep methods: - 'checkUpkeep', - 'performUpkeep', - // Owned methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('sets the owner', async () => { - assert.equal(await validator.owner(), await personas.Carol.getAddress()) - }) - - it('sets the arguments passed in', async () => { - assert.equal(await validator.flags(), flags.address) - assert.equal(await validator.compoundOpenOracle(), compoundOracle.address) - }) - }) - - describe('#setOpenOracleAddress', () => { - let newCompoundOracle: Contract - let tx: ContractTransaction - - beforeEach(async () => { - newCompoundOracle = await compoundOracleFactory - .connect(personas.Carol) - .deploy() - tx = await validator - .connect(personas.Carol) - .setCompoundOpenOracleAddress(newCompoundOracle.address) - }) - - it('changes the compound oracke address', async () => { - assert.equal( - await validator.compoundOpenOracle(), - newCompoundOracle.address, - ) - }) - - it('emits a log event', async () => { - await expect(tx) - .to.emit(validator, 'CompoundOpenOracleAddressUpdated') - .withArgs(compoundOracle.address, newCompoundOracle.address) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - validator - .connect(personas.Neil) - .setCompoundOpenOracleAddress(newCompoundOracle.address), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#setFlagsAddress', () => { - let newFlagsContract: Contract - let tx: ContractTransaction - - beforeEach(async () => { - newFlagsContract = await flagsFactory - .connect(personas.Carol) - .deploy(ac.address) - tx = await validator - .connect(personas.Carol) - .setFlagsAddress(newFlagsContract.address) - }) - - it('changes the flags address', async () => { - assert.equal(await validator.flags(), newFlagsContract.address) - }) - - it('emits a log event', async () => { - await expect(tx) - .to.emit(validator, 'FlagsAddressUpdated') - .withArgs(flags.address, newFlagsContract.address) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - validator - .connect(personas.Neil) - .setFlagsAddress(newFlagsContract.address), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#setFeedDetails', () => { - let mockAggregator: Contract - let tx: ContractTransaction - const symbol = 'BTC' - const decimals = 8 - const deviationNumerator = 50_000_000 // 5% - - beforeEach(async () => { - await compoundOracle.connect(personas.Carol).setPrice('BTC', 1500000, 2) - mockAggregator = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, 4000000000000) - tx = await validator - .connect(personas.Carol) - .setFeedDetails( - mockAggregator.address, - symbol, - decimals, - deviationNumerator, - ) - }) - - it('sets the correct state', async () => { - const response = await validator - .connect(personas.Carol) - .getFeedDetails(mockAggregator.address) - - assert.equal(response[0], symbol) - assert.equal(response[1], decimals) - assert.equal(response[2].toString(), deviationNumerator.toString()) - }) - - it('uses the existing symbol if one already exists', async () => { - const newSymbol = 'LINK' - - await compoundOracle - .connect(personas.Carol) - .setPrice(newSymbol, 1500000, 2) - - tx = await validator - .connect(personas.Carol) - .setFeedDetails( - mockAggregator.address, - newSymbol, - decimals, - deviationNumerator, - ) - - // Check the event - await expect(tx) - .to.emit(validator, 'FeedDetailsSet') - .withArgs(mockAggregator.address, symbol, decimals, deviationNumerator) - - // Check the state - const response = await validator - .connect(personas.Carol) - .getFeedDetails(mockAggregator.address) - assert.equal(response[0], symbol) - }) - - it('emits an event', async () => { - await expect(tx) - .to.emit(validator, 'FeedDetailsSet') - .withArgs(mockAggregator.address, symbol, decimals, deviationNumerator) - }) - - it('fails when given a 0 numerator', async () => { - await evmRevert( - validator - .connect(personas.Carol) - .setFeedDetails(mockAggregator.address, symbol, decimals, 0), - 'Invalid threshold numerator', - ) - }) - - it('fails when given a numerator above 1 billion', async () => { - await evmRevert( - validator - .connect(personas.Carol) - .setFeedDetails( - mockAggregator.address, - symbol, - decimals, - 1_200_000_000, - ), - 'Invalid threshold numerator', - ) - }) - - it('fails when the compound price is invalid', async () => { - await evmRevert( - validator - .connect(personas.Carol) - .setFeedDetails( - mockAggregator.address, - 'TEST', - decimals, - deviationNumerator, - ), - 'Invalid Compound price', - ) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - validator - .connect(personas.Neil) - .setFeedDetails( - mockAggregator.address, - symbol, - decimals, - deviationNumerator, - ), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#check', () => { - describe('with a single aggregator', () => { - describe('with a deviated price exceding threshold', () => { - it('returns the deviated aggregator', async () => { - const aggregators = [aggregator.address] - const response = await validator.check(aggregators) - assert.equal(response.length, 1) - assert.equal(response[0], aggregator.address) - }) - }) - - describe('with a price within the threshold', () => { - const newCompoundPrice = BigNumber.from('1000000000') - beforeEach(async () => { - await compoundOracle.setPrice( - 'ETH', - newCompoundPrice, - compoundDecimals, - ) - }) - - it('returns an empty array', async () => { - const aggregators = [aggregator.address] - const response = await validator.check(aggregators) - assert.equal(response.length, 0) - }) - }) - }) - }) - - describe('#update', () => { - describe('with a single aggregator', () => { - describe('with a deviated price exceding threshold', () => { - it('raises a flag on the flags contract', async () => { - const aggregators = [aggregator.address] - const tx = await validator.connect(personas.Carol).update(aggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 1) - assert.equal(evmWordToAddress(logs[0].topics[1]), aggregator.address) - }) - }) - - describe('with a price within the threshold', () => { - const newCompoundPrice = BigNumber.from('1000000000') - beforeEach(async () => { - await compoundOracle.setPrice( - 'ETH', - newCompoundPrice, - compoundDecimals, - ) - }) - - it('does nothing', async () => { - const aggregators = [aggregator.address] - const tx = await validator.connect(personas.Carol).update(aggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - }) - }) - - describe('#checkUpkeep', () => { - describe('with a single aggregator', () => { - describe('with a deviated price exceding threshold', () => { - it('returns the deviated aggregator', async () => { - const aggregators = [aggregator.address] - const encodedAggregators = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const response = await validator - .connect(personas.Carol) - .checkUpkeep(encodedAggregators) - - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - assert.equal(decodedResponse?.[0]?.[0], aggregators[0]) - }) - }) - - describe('with a price within the threshold', () => { - const newCompoundPrice = BigNumber.from('1000000000') - beforeEach(async () => { - await compoundOracle.setPrice( - 'ETH', - newCompoundPrice, - compoundDecimals, - ) - }) - - it('returns an empty array', async () => { - const aggregators = [aggregator.address] - const encodedAggregators = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const response = await validator - .connect(personas.Carol) - .checkUpkeep(encodedAggregators) - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - assert.equal(decodedResponse?.[0]?.length, 0) - }) - }) - }) - }) - - describe('#performUpkeep', () => { - describe('with a single aggregator', () => { - describe('with a deviated price exceding threshold', () => { - it('raises a flag on the flags contract', async () => { - const aggregators = [aggregator.address] - const encodedAggregators = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const tx = await validator - .connect(personas.Carol) - .performUpkeep(encodedAggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 1) - assert.equal(evmWordToAddress(logs[0].topics[1]), aggregator.address) - }) - }) - - describe('with a price within the threshold', () => { - const newCompoundPrice = BigNumber.from('1000000000') - beforeEach(async () => { - await compoundOracle.setPrice( - 'ETH', - newCompoundPrice, - compoundDecimals, - ) - }) - - it('does nothing', async () => { - const aggregators = [aggregator.address] - const encodedAggregators = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const tx = await validator - .connect(personas.Carol) - .performUpkeep(encodedAggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/ConfirmedOwner.test.ts b/contracts/test/v0.7/ConfirmedOwner.test.ts deleted file mode 100644 index 3502cd15bc2..00000000000 --- a/contracts/test/v0.7/ConfirmedOwner.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Contract, ContractFactory, Signer } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { evmRevert } from '../test-helpers/matchers' - -let confirmedOwnerTestHelperFactory: ContractFactory -let confirmedOwnerFactory: ContractFactory - -let personas: Personas -let owner: Signer -let nonOwner: Signer -let newOwner: Signer - -before(async () => { - const users = await getUsers() - personas = users.personas - owner = personas.Carol - nonOwner = personas.Neil - newOwner = personas.Ned - - confirmedOwnerTestHelperFactory = await ethers.getContractFactory( - 'src/v0.7/tests/ConfirmedOwnerTestHelper.sol:ConfirmedOwnerTestHelper', - owner, - ) - confirmedOwnerFactory = await ethers.getContractFactory( - 'src/v0.7/ConfirmedOwner.sol:ConfirmedOwner', - owner, - ) -}) - -describe('ConfirmedOwner', () => { - let confirmedOwner: Contract - - beforeEach(async () => { - confirmedOwner = await confirmedOwnerTestHelperFactory - .connect(owner) - .deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(confirmedOwner, [ - 'acceptOwnership', - 'owner', - 'transferOwnership', - // test helper public methods - 'modifierOnlyOwner', - ]) - }) - - describe('#constructor', () => { - it('assigns ownership to the deployer', async () => { - const [actual, expected] = await Promise.all([ - owner.getAddress(), - confirmedOwner.owner(), - ]) - - assert.equal(actual, expected) - }) - - it('reverts if assigned to the zero address', async () => { - await evmRevert( - confirmedOwnerFactory - .connect(owner) - .deploy(ethers.constants.AddressZero), - 'Cannot set owner to zero', - ) - }) - }) - - describe('#onlyOwner modifier', () => { - describe('when called by an owner', () => { - it('successfully calls the method', async () => { - const tx = await confirmedOwner.connect(owner).modifierOnlyOwner() - await expect(tx).to.emit(confirmedOwner, 'Here') - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => - await evmRevert(confirmedOwner.connect(nonOwner).modifierOnlyOwner())) - }) - }) - - describe('#transferOwnership', () => { - describe('when called by an owner', () => { - it('emits a log', async () => { - const tx = await confirmedOwner - .connect(owner) - .transferOwnership(await newOwner.getAddress()) - await expect(tx) - .to.emit(confirmedOwner, 'OwnershipTransferRequested') - .withArgs(await owner.getAddress(), await newOwner.getAddress()) - }) - - it('does not allow ownership transfer to self', async () => { - await evmRevert( - confirmedOwner - .connect(owner) - .transferOwnership(await owner.getAddress()), - 'Cannot transfer to self', - ) - }) - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => - await evmRevert( - confirmedOwner - .connect(nonOwner) - .transferOwnership(await newOwner.getAddress()), - )) - }) - - describe('#acceptOwnership', () => { - describe('after #transferOwnership has been called', () => { - beforeEach(async () => { - await confirmedOwner - .connect(owner) - .transferOwnership(await newOwner.getAddress()) - }) - - it('allows the recipient to call it', async () => { - const tx = await confirmedOwner.connect(newOwner).acceptOwnership() - await expect(tx) - .to.emit(confirmedOwner, 'OwnershipTransferred') - .withArgs(await owner.getAddress(), await newOwner.getAddress()) - }) - - it('does not allow a non-recipient to call it', async () => - await evmRevert(confirmedOwner.connect(nonOwner).acceptOwnership())) - }) - }) -}) diff --git a/contracts/test/v0.7/KeeperRegistry1_1.test.ts b/contracts/test/v0.7/KeeperRegistry1_1.test.ts deleted file mode 100644 index 4e3a8c91b35..00000000000 --- a/contracts/test/v0.7/KeeperRegistry1_1.test.ts +++ /dev/null @@ -1,1725 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { evmRevert } from '../test-helpers/matchers' -import { getUsers, Personas } from '../test-helpers/setup' -import { BigNumber, BigNumberish, Signer } from 'ethers' -import { LinkToken__factory as LinkTokenFactory } from '../../typechain/factories/LinkToken__factory' -import { KeeperRegistry1_1__factory as KeeperRegistryFactory } from '../../typechain/factories/KeeperRegistry1_1__factory' -import { MockV3Aggregator__factory as MockV3AggregatorFactory } from '../../typechain/factories/MockV3Aggregator__factory' -import { UpkeepMock__factory as UpkeepMockFactory } from '../../typechain/factories/UpkeepMock__factory' -import { UpkeepReverter__factory as UpkeepReverterFactory } from '../../typechain/factories/UpkeepReverter__factory' -import { KeeperRegistry1_1 as KeeperRegistry } from '../../typechain/KeeperRegistry1_1' -import { MockV3Aggregator } from '../../typechain/MockV3Aggregator' -import { LinkToken } from '../../typechain/LinkToken' -import { UpkeepMock } from '../../typechain/UpkeepMock' -import { toWei } from '../test-helpers/helpers' - -async function getUpkeepID(tx: any) { - const receipt = await tx.wait() - return receipt.events[0].args.id -} - -// ----------------------------------------------------------------------------------------------- -// DEV: these *should* match the perform/check gas overhead values in the contract and on the node -const PERFORM_GAS_OVERHEAD = BigNumber.from(90000) -const CHECK_GAS_OVERHEAD = BigNumber.from(170000) -// ----------------------------------------------------------------------------------------------- - -// Smart contract factories -let linkTokenFactory: LinkTokenFactory -let mockV3AggregatorFactory: MockV3AggregatorFactory -let keeperRegistryFactory: KeeperRegistryFactory -let upkeepMockFactory: UpkeepMockFactory -let upkeepReverterFactory: UpkeepReverterFactory - -let personas: Personas - -before(async () => { - personas = (await getUsers()).personas - - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - ) - // need full path because there are two contracts with name MockV3Aggregator - mockV3AggregatorFactory = (await ethers.getContractFactory( - 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', - )) as unknown as MockV3AggregatorFactory - // @ts-ignore bug in autogen file - keeperRegistryFactory = await ethers.getContractFactory('KeeperRegistry1_1') - upkeepMockFactory = await ethers.getContractFactory('UpkeepMock') - upkeepReverterFactory = await ethers.getContractFactory('UpkeepReverter') -}) - -describe('KeeperRegistry1_1', () => { - const linkEth = BigNumber.from(300000000) - const gasWei = BigNumber.from(100) - const linkDivisibility = BigNumber.from('1000000000000000000') - const executeGas = BigNumber.from('100000') - const paymentPremiumBase = BigNumber.from('1000000000') - const paymentPremiumPPB = BigNumber.from('250000000') - const flatFeeMicroLink = BigNumber.from(0) - const blockCountPerTurn = BigNumber.from(3) - const emptyBytes = '0x00' - const zeroAddress = ethers.constants.AddressZero - const extraGas = BigNumber.from('250000') - const registryGasOverhead = BigNumber.from('80000') - const stalenessSeconds = BigNumber.from(43820) - const gasCeilingMultiplier = BigNumber.from(1) - const maxCheckGas = BigNumber.from(20000000) - const fallbackGasPrice = BigNumber.from(200) - const fallbackLinkPrice = BigNumber.from(200000000) - - let owner: Signer - let keeper1: Signer - let keeper2: Signer - let keeper3: Signer - let nonkeeper: Signer - let admin: Signer - let payee1: Signer - let payee2: Signer - let payee3: Signer - - let linkToken: LinkToken - let linkEthFeed: MockV3Aggregator - let gasPriceFeed: MockV3Aggregator - let registry: KeeperRegistry - let mock: UpkeepMock - - let id: BigNumber - let keepers: string[] - let payees: string[] - - beforeEach(async () => { - owner = personas.Default - keeper1 = personas.Carol - keeper2 = personas.Eddy - keeper3 = personas.Nancy - nonkeeper = personas.Ned - admin = personas.Neil - payee1 = personas.Nelly - payee2 = personas.Norbert - payee3 = personas.Nick - - keepers = [ - await keeper1.getAddress(), - await keeper2.getAddress(), - await keeper3.getAddress(), - ] - payees = [ - await payee1.getAddress(), - await payee2.getAddress(), - await payee3.getAddress(), - ] - - linkToken = await linkTokenFactory.connect(owner).deploy() - gasPriceFeed = await mockV3AggregatorFactory - .connect(owner) - .deploy(0, gasWei) - linkEthFeed = await mockV3AggregatorFactory - .connect(owner) - .deploy(9, linkEth) - registry = await keeperRegistryFactory - .connect(owner) - .deploy( - linkToken.address, - linkEthFeed.address, - gasPriceFeed.address, - paymentPremiumPPB, - flatFeeMicroLink, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - gasCeilingMultiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - - mock = await upkeepMockFactory.deploy() - await linkToken - .connect(owner) - .transfer(await keeper1.getAddress(), toWei('1000')) - await linkToken - .connect(owner) - .transfer(await keeper2.getAddress(), toWei('1000')) - await linkToken - .connect(owner) - .transfer(await keeper3.getAddress(), toWei('1000')) - - await registry.connect(owner).setKeepers(keepers, payees) - const tx = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - id = await getUpkeepID(tx) - }) - - const linkForGas = ( - upkeepGasSpent: BigNumberish, - premiumPPB?: BigNumberish, - flatFee?: BigNumberish, - ) => { - premiumPPB = premiumPPB === undefined ? paymentPremiumPPB : premiumPPB - flatFee = flatFee === undefined ? flatFeeMicroLink : flatFee - const gasSpent = registryGasOverhead.add(BigNumber.from(upkeepGasSpent)) - const base = gasWei.mul(gasSpent).mul(linkDivisibility).div(linkEth) - const premium = base.mul(premiumPPB).div(paymentPremiumBase) - const flatFeeJules = BigNumber.from(flatFee).mul('1000000000000') - return base.add(premium).add(flatFeeJules) - } - - describe('#setKeepers', () => { - const IGNORE_ADDRESS = '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF' - it('reverts when not called by the owner', async () => { - await evmRevert( - registry.connect(keeper1).setKeepers([], []), - 'Only callable by owner', - ) - }) - - it('reverts when adding the same keeper twice', async () => { - await evmRevert( - registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress(), await keeper1.getAddress()], - [await payee1.getAddress(), await payee1.getAddress()], - ), - 'cannot add keeper twice', - ) - }) - - it('reverts with different numbers of keepers/payees', async () => { - await evmRevert( - registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress(), await keeper2.getAddress()], - [await payee1.getAddress()], - ), - 'address lists not the same length', - ) - await evmRevert( - registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress()], - [await payee1.getAddress(), await payee2.getAddress()], - ), - 'address lists not the same length', - ) - }) - - it('reverts if the payee is the zero address', async () => { - await evmRevert( - registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress(), await keeper2.getAddress()], - [ - await payee1.getAddress(), - '0x0000000000000000000000000000000000000000', - ], - ), - 'cannot set payee to the zero address', - ) - }) - - it('emits events for every keeper added and removed', async () => { - const oldKeepers = [ - await keeper1.getAddress(), - await keeper2.getAddress(), - ] - const oldPayees = [await payee1.getAddress(), await payee2.getAddress()] - await registry.connect(owner).setKeepers(oldKeepers, oldPayees) - assert.deepEqual(oldKeepers, await registry.getKeeperList()) - - // remove keepers - const newKeepers = [ - await keeper2.getAddress(), - await keeper3.getAddress(), - ] - const newPayees = [await payee2.getAddress(), await payee3.getAddress()] - const tx = await registry.connect(owner).setKeepers(newKeepers, newPayees) - assert.deepEqual(newKeepers, await registry.getKeeperList()) - - await expect(tx) - .to.emit(registry, 'KeepersUpdated') - .withArgs(newKeepers, newPayees) - }) - - it('updates the keeper to inactive when removed', async () => { - await registry.connect(owner).setKeepers(keepers, payees) - await registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress(), await keeper3.getAddress()], - [await payee1.getAddress(), await payee3.getAddress()], - ) - const added = await registry.getKeeperInfo(await keeper1.getAddress()) - assert.isTrue(added.active) - const removed = await registry.getKeeperInfo(await keeper2.getAddress()) - assert.isFalse(removed.active) - }) - - it('does not change the payee if IGNORE_ADDRESS is used as payee', async () => { - const oldKeepers = [ - await keeper1.getAddress(), - await keeper2.getAddress(), - ] - const oldPayees = [await payee1.getAddress(), await payee2.getAddress()] - await registry.connect(owner).setKeepers(oldKeepers, oldPayees) - assert.deepEqual(oldKeepers, await registry.getKeeperList()) - - const newKeepers = [ - await keeper2.getAddress(), - await keeper3.getAddress(), - ] - const newPayees = [IGNORE_ADDRESS, await payee3.getAddress()] - const tx = await registry.connect(owner).setKeepers(newKeepers, newPayees) - assert.deepEqual(newKeepers, await registry.getKeeperList()) - - const ignored = await registry.getKeeperInfo(await keeper2.getAddress()) - assert.equal(await payee2.getAddress(), ignored.payee) - assert.equal(true, ignored.active) - - await expect(tx) - .to.emit(registry, 'KeepersUpdated') - .withArgs(newKeepers, newPayees) - }) - - it('reverts if the owner changes the payee', async () => { - await registry.connect(owner).setKeepers(keepers, payees) - await evmRevert( - registry - .connect(owner) - .setKeepers(keepers, [ - await payee1.getAddress(), - await payee2.getAddress(), - await owner.getAddress(), - ]), - 'cannot change payee', - ) - }) - }) - - describe('#registerUpkeep', () => { - it('reverts if the target is not a contract', async () => { - await evmRevert( - registry - .connect(owner) - .registerUpkeep( - zeroAddress, - executeGas, - await admin.getAddress(), - emptyBytes, - ), - 'target is not a contract', - ) - }) - - it('reverts if called by a non-owner', async () => { - await evmRevert( - registry - .connect(keeper1) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ), - 'Only callable by owner or registrar', - ) - }) - - it('reverts if execute gas is too low', async () => { - await evmRevert( - registry - .connect(owner) - .registerUpkeep( - mock.address, - 2299, - await admin.getAddress(), - emptyBytes, - ), - 'min gas is 2300', - ) - }) - - it('reverts if execute gas is too high', async () => { - await evmRevert( - registry - .connect(owner) - .registerUpkeep( - mock.address, - 5000001, - await admin.getAddress(), - emptyBytes, - ), - 'max gas is 5000000', - ) - }) - - it('creates a record of the registration', async () => { - const tx = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - id = await getUpkeepID(tx) - await expect(tx) - .to.emit(registry, 'UpkeepRegistered') - .withArgs(id, executeGas, await admin.getAddress()) - const registration = await registry.getUpkeep(id) - assert.equal(mock.address, registration.target) - assert.equal(0, registration.balance.toNumber()) - assert.equal(emptyBytes, registration.checkData) - assert(registration.maxValidBlocknumber.eq('0xffffffffffffffff')) - }) - }) - - describe('#addFunds', () => { - const amount = toWei('1') - - beforeEach(async () => { - await linkToken.connect(keeper1).approve(registry.address, toWei('100')) - }) - - it('reverts if the registration does not exist', async () => { - await evmRevert( - registry.connect(keeper1).addFunds(id.add(1), amount), - 'upkeep must be active', - ) - }) - - it('adds to the balance of the registration', async () => { - await registry.connect(keeper1).addFunds(id, amount) - const registration = await registry.getUpkeep(id) - assert.isTrue(amount.eq(registration.balance)) - }) - - it('emits a log', async () => { - const tx = await registry.connect(keeper1).addFunds(id, amount) - await expect(tx) - .to.emit(registry, 'FundsAdded') - .withArgs(id, await keeper1.getAddress(), amount) - }) - - it('reverts if the upkeep is canceled', async () => { - await registry.connect(admin).cancelUpkeep(id) - await evmRevert( - registry.connect(keeper1).addFunds(id, amount), - 'upkeep must be active', - ) - }) - }) - - describe('#checkUpkeep', () => { - it('reverts if the upkeep is not funded', async () => { - await mock.setCanPerform(true) - await mock.setCanCheck(true) - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()), - 'insufficient funds', - ) - }) - - context('when the registration is funded', () => { - beforeEach(async () => { - await linkToken.connect(keeper1).approve(registry.address, toWei('100')) - await registry.connect(keeper1).addFunds(id, toWei('100')) - }) - - it('reverts if executed', async () => { - await mock.setCanPerform(true) - await mock.setCanCheck(true) - await evmRevert( - registry.checkUpkeep(id, await keeper1.getAddress()), - 'only for simulated backend', - ) - }) - - it('reverts if the specified keeper is not valid', async () => { - await mock.setCanPerform(true) - await mock.setCanCheck(true) - await evmRevert( - registry.checkUpkeep(id, await owner.getAddress()), - 'only for simulated backend', - ) - }) - - context('and upkeep is not needed', () => { - beforeEach(async () => { - await mock.setCanCheck(false) - }) - - it('reverts', async () => { - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()), - 'upkeep not needed', - ) - }) - }) - - context('and the upkeep check fails', () => { - beforeEach(async () => { - const reverter = await upkeepReverterFactory.deploy() - const tx = await registry - .connect(owner) - .registerUpkeep( - reverter.address, - 2500000, - await admin.getAddress(), - emptyBytes, - ) - id = await getUpkeepID(tx) - await linkToken - .connect(keeper1) - .approve(registry.address, toWei('100')) - await registry.connect(keeper1).addFunds(id, toWei('100')) - }) - - it('reverts', async () => { - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()), - 'call to check target failed', - ) - }) - }) - - context('and upkeep check simulations succeeds', () => { - beforeEach(async () => { - await mock.setCanCheck(true) - await mock.setCanPerform(true) - }) - - context('and the registry is paused', () => { - beforeEach(async () => { - await registry.connect(owner).pause() - }) - - it('reverts', async () => { - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()), - 'Pausable: paused', - ) - - await registry.connect(owner).unpause() - - await registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()) - }) - }) - - it('returns true with pricing info if the target can execute', async () => { - const newGasMultiplier = BigNumber.from(10) - await registry - .connect(owner) - .setConfig( - paymentPremiumPPB, - flatFeeMicroLink, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - newGasMultiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - const response = await registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()) - assert.isTrue(response.gasLimit.eq(executeGas)) - assert.isTrue(response.linkEth.eq(linkEth)) - assert.isTrue( - response.adjustedGasWei.eq(gasWei.mul(newGasMultiplier)), - ) - assert.isTrue( - response.maxLinkPayment.eq( - linkForGas(executeGas.toNumber()).mul(newGasMultiplier), - ), - ) - }) - - it('has a large enough gas overhead to cover upkeeps that use all their gas [ @skip-coverage ]', async () => { - await mock.setCheckGasToBurn(maxCheckGas) - await mock.setPerformGasToBurn(executeGas) - const gas = maxCheckGas - .add(executeGas) - .add(PERFORM_GAS_OVERHEAD) - .add(CHECK_GAS_OVERHEAD) - await registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress(), { - gasLimit: gas, - }) - }) - }) - }) - }) - - describe('#performUpkeep', () => { - let _lastKeeper = keeper1 - async function getPerformPaymentAmount() { - _lastKeeper = _lastKeeper === keeper1 ? keeper2 : keeper1 - const before = ( - await registry.getKeeperInfo(await _lastKeeper.getAddress()) - ).balance - await registry.connect(_lastKeeper).performUpkeep(id, '0x') - const after = ( - await registry.getKeeperInfo(await _lastKeeper.getAddress()) - ).balance - const difference = after.sub(before) - return difference - } - - it('reverts if the registration is not funded', async () => { - await evmRevert( - registry.connect(keeper2).performUpkeep(id, '0x'), - 'insufficient funds', - ) - }) - - context('when the registration is funded', () => { - beforeEach(async () => { - await linkToken.connect(owner).approve(registry.address, toWei('100')) - await registry.connect(owner).addFunds(id, toWei('100')) - }) - - it('does not revert if the target cannot execute', async () => { - const mockResponse = await mock - .connect(zeroAddress) - .callStatic.checkUpkeep('0x') - assert.isFalse(mockResponse.callable) - - await registry.connect(keeper3).performUpkeep(id, '0x') - }) - - it('returns false if the target cannot execute', async () => { - const mockResponse = await mock - .connect(zeroAddress) - .callStatic.checkUpkeep('0x') - assert.isFalse(mockResponse.callable) - - assert.isFalse( - await registry.connect(keeper1).callStatic.performUpkeep(id, '0x'), - ) - }) - - it('returns true if called', async () => { - await mock.setCanPerform(true) - - const response = await registry - .connect(keeper1) - .callStatic.performUpkeep(id, '0x') - assert.isTrue(response) - }) - - it('reverts if not enough gas supplied', async () => { - await mock.setCanPerform(true) - - await evmRevert( - registry - .connect(keeper1) - .performUpkeep(id, '0x', { gasLimit: BigNumber.from('120000') }), - ) - }) - - it('executes the data passed to the registry', async () => { - await mock.setCanPerform(true) - - const performData = '0xc0ffeec0ffee' - const tx = await registry - .connect(keeper1) - .performUpkeep(id, performData, { gasLimit: extraGas }) - const receipt = await tx.wait() - const eventLog = receipt?.events - - assert.equal(eventLog?.length, 2) - assert.equal(eventLog?.[1].event, 'UpkeepPerformed') - assert.equal(eventLog?.[1].args?.[0].toNumber(), id.toNumber()) - assert.equal(eventLog?.[1].args?.[1], true) - assert.equal(eventLog?.[1].args?.[2], await keeper1.getAddress()) - assert.isNotEmpty(eventLog?.[1].args?.[3]) - assert.equal(eventLog?.[1].args?.[4], performData) - }) - - it('updates payment balances', async () => { - const keeperBefore = await registry.getKeeperInfo( - await keeper1.getAddress(), - ) - const registrationBefore = await registry.getUpkeep(id) - const keeperLinkBefore = await linkToken.balanceOf( - await keeper1.getAddress(), - ) - const registryLinkBefore = await linkToken.balanceOf(registry.address) - - // Do the thing - await registry.connect(keeper1).performUpkeep(id, '0x') - - const keeperAfter = await registry.getKeeperInfo( - await keeper1.getAddress(), - ) - const registrationAfter = await registry.getUpkeep(id) - const keeperLinkAfter = await linkToken.balanceOf( - await keeper1.getAddress(), - ) - const registryLinkAfter = await linkToken.balanceOf(registry.address) - - assert.isTrue(keeperAfter.balance.gt(keeperBefore.balance)) - assert.isTrue(registrationBefore.balance.gt(registrationAfter.balance)) - assert.isTrue(keeperLinkAfter.eq(keeperLinkBefore)) - assert.isTrue(registryLinkBefore.eq(registryLinkAfter)) - }) - - it('only pays for gas used [ @skip-coverage ]', async () => { - const before = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - const tx = await registry.connect(keeper1).performUpkeep(id, '0x') - const receipt = await tx.wait() - const after = (await registry.getKeeperInfo(await keeper1.getAddress())) - .balance - - const max = linkForGas(executeGas.toNumber()) - const totalTx = linkForGas(receipt.gasUsed.toNumber()) - const difference = after.sub(before) - assert.isTrue(max.gt(totalTx)) - assert.isTrue(totalTx.gt(difference)) - assert.isTrue(linkForGas(5700).lt(difference)) // exact number is flaky - assert.isTrue(linkForGas(6000).gt(difference)) // instead test a range - }) - - it('only pays at a rate up to the gas ceiling [ @skip-coverage ]', async () => { - const multiplier = BigNumber.from(10) - const gasPrice = BigNumber.from('1000000000') // 10M x the gas feed's rate - await registry - .connect(owner) - .setConfig( - paymentPremiumPPB, - flatFeeMicroLink, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - multiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - - const before = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - const tx = await registry - .connect(keeper1) - .performUpkeep(id, '0x', { gasPrice }) - const receipt = await tx.wait() - const after = (await registry.getKeeperInfo(await keeper1.getAddress())) - .balance - - const max = linkForGas(executeGas).mul(multiplier) - const totalTx = linkForGas(receipt.gasUsed).mul(multiplier) - const difference = after.sub(before) - assert.isTrue(max.gt(totalTx)) - assert.isTrue(totalTx.gt(difference)) - assert.isTrue(linkForGas(5700).mul(multiplier).lt(difference)) - assert.isTrue(linkForGas(6000).mul(multiplier).gt(difference)) - }) - - it('only pays as much as the node spent [ @skip-coverage ]', async () => { - const multiplier = BigNumber.from(10) - const gasPrice = BigNumber.from(200) // 2X the gas feed's rate - const effectiveMultiplier = BigNumber.from(2) - await registry - .connect(owner) - .setConfig( - paymentPremiumPPB, - flatFeeMicroLink, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - multiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - - const before = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - const tx = await registry - .connect(keeper1) - .performUpkeep(id, '0x', { gasPrice }) - const receipt = await tx.wait() - const after = (await registry.getKeeperInfo(await keeper1.getAddress())) - .balance - - const max = linkForGas(executeGas.toNumber()).mul(effectiveMultiplier) - const totalTx = linkForGas(receipt.gasUsed).mul(effectiveMultiplier) - const difference = after.sub(before) - assert.isTrue(max.gt(totalTx)) - assert.isTrue(totalTx.gt(difference)) - assert.isTrue(linkForGas(5700).mul(effectiveMultiplier).lt(difference)) - assert.isTrue(linkForGas(6000).mul(effectiveMultiplier).gt(difference)) - }) - - it('pays the caller even if the target function fails', async () => { - const tx = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - const id = await getUpkeepID(tx) - await linkToken.connect(owner).approve(registry.address, toWei('100')) - await registry.connect(owner).addFunds(id, toWei('100')) - const keeperBalanceBefore = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - - // Do the thing - await registry.connect(keeper1).performUpkeep(id, '0x') - - const keeperBalanceAfter = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - assert.isTrue(keeperBalanceAfter.gt(keeperBalanceBefore)) - }) - - it('reverts if called by a non-keeper', async () => { - await evmRevert( - registry.connect(nonkeeper).performUpkeep(id, '0x'), - 'only active keepers', - ) - }) - - it('reverts if the upkeep has been canceled', async () => { - await mock.setCanPerform(true) - - await registry.connect(owner).cancelUpkeep(id) - - await evmRevert( - registry.connect(keeper1).performUpkeep(id, '0x'), - 'invalid upkeep id', - ) - }) - - it('uses the fallback gas price if the feed price is stale [ @skip-coverage ]', async () => { - const normalAmount = await getPerformPaymentAmount() - const roundId = 99 - const answer = 100 - const updatedAt = 946684800 // New Years 2000 🥳 - const startedAt = 946684799 - await gasPriceFeed - .connect(owner) - .updateRoundData(roundId, answer, updatedAt, startedAt) - const amountWithStaleFeed = await getPerformPaymentAmount() - assert.isTrue(normalAmount.lt(amountWithStaleFeed)) - }) - - it('uses the fallback gas price if the feed price is non-sensical [ @skip-coverage ]', async () => { - const normalAmount = await getPerformPaymentAmount() - const roundId = 99 - const updatedAt = Math.floor(Date.now() / 1000) - const startedAt = 946684799 - await gasPriceFeed - .connect(owner) - .updateRoundData(roundId, -100, updatedAt, startedAt) - const amountWithNegativeFeed = await getPerformPaymentAmount() - await gasPriceFeed - .connect(owner) - .updateRoundData(roundId, 0, updatedAt, startedAt) - const amountWithZeroFeed = await getPerformPaymentAmount() - assert.isTrue(normalAmount.lt(amountWithNegativeFeed)) - assert.isTrue(normalAmount.lt(amountWithZeroFeed)) - }) - - it('uses the fallback if the link price feed is stale', async () => { - const normalAmount = await getPerformPaymentAmount() - const roundId = 99 - const answer = 100 - const updatedAt = 946684800 // New Years 2000 🥳 - const startedAt = 946684799 - await linkEthFeed - .connect(owner) - .updateRoundData(roundId, answer, updatedAt, startedAt) - const amountWithStaleFeed = await getPerformPaymentAmount() - assert.isTrue(normalAmount.lt(amountWithStaleFeed)) - }) - - it('uses the fallback link price if the feed price is non-sensical', async () => { - const normalAmount = await getPerformPaymentAmount() - const roundId = 99 - const updatedAt = Math.floor(Date.now() / 1000) - const startedAt = 946684799 - await linkEthFeed - .connect(owner) - .updateRoundData(roundId, -100, updatedAt, startedAt) - const amountWithNegativeFeed = await getPerformPaymentAmount() - await linkEthFeed - .connect(owner) - .updateRoundData(roundId, 0, updatedAt, startedAt) - const amountWithZeroFeed = await getPerformPaymentAmount() - assert.isTrue(normalAmount.lt(amountWithNegativeFeed)) - assert.isTrue(normalAmount.lt(amountWithZeroFeed)) - }) - - it('reverts if the same caller calls twice in a row', async () => { - await registry.connect(keeper1).performUpkeep(id, '0x') - await evmRevert( - registry.connect(keeper1).performUpkeep(id, '0x'), - 'keepers must take turns', - ) - await registry.connect(keeper2).performUpkeep(id, '0x') - await evmRevert( - registry.connect(keeper2).performUpkeep(id, '0x'), - 'keepers must take turns', - ) - await registry.connect(keeper1).performUpkeep(id, '0x') - }) - - it('has a large enough gas overhead to cover upkeeps that use all their gas [ @skip-coverage ]', async () => { - await mock.setPerformGasToBurn(executeGas) - await mock.setCanPerform(true) - const gas = executeGas.add(PERFORM_GAS_OVERHEAD) - const performData = '0xc0ffeec0ffee' - const tx = await registry - .connect(keeper1) - .performUpkeep(id, performData, { gasLimit: gas }) - const receipt = await tx.wait() - const eventLog = receipt?.events - - assert.equal(eventLog?.length, 2) - assert.equal(eventLog?.[1].event, 'UpkeepPerformed') - assert.equal(eventLog?.[1].args?.[0].toNumber(), id.toNumber()) - assert.equal(eventLog?.[1].args?.[1], true) - assert.equal(eventLog?.[1].args?.[2], await keeper1.getAddress()) - assert.isNotEmpty(eventLog?.[1].args?.[3]) - assert.equal(eventLog?.[1].args?.[4], performData) - }) - }) - }) - - describe('#withdrawFunds', () => { - beforeEach(async () => { - await linkToken.connect(keeper1).approve(registry.address, toWei('100')) - await registry.connect(keeper1).addFunds(id, toWei('1')) - }) - - it('reverts if called by anyone but the admin', async () => { - await evmRevert( - registry - .connect(owner) - .withdrawFunds(id.add(1).toNumber(), await payee1.getAddress()), - 'only callable by admin', - ) - }) - - it('reverts if called on an uncanceled upkeep', async () => { - await evmRevert( - registry.connect(admin).withdrawFunds(id, await payee1.getAddress()), - 'upkeep must be canceled', - ) - }) - - it('reverts if called with the 0 address', async () => { - await evmRevert( - registry.connect(admin).withdrawFunds(id, zeroAddress), - 'cannot send to zero address', - ) - }) - - describe('after the registration is cancelled', () => { - beforeEach(async () => { - await registry.connect(owner).cancelUpkeep(id) - }) - - it('moves the funds out and updates the balance', async () => { - const payee1Before = await linkToken.balanceOf( - await payee1.getAddress(), - ) - const registryBefore = await linkToken.balanceOf(registry.address) - - let registration = await registry.getUpkeep(id) - assert.isTrue(toWei('1').eq(registration.balance)) - - await registry - .connect(admin) - .withdrawFunds(id, await payee1.getAddress()) - - const payee1After = await linkToken.balanceOf(await payee1.getAddress()) - const registryAfter = await linkToken.balanceOf(registry.address) - - assert.isTrue(payee1Before.add(toWei('1')).eq(payee1After)) - assert.isTrue(registryBefore.sub(toWei('1')).eq(registryAfter)) - - registration = await registry.getUpkeep(id) - assert.equal(0, registration.balance.toNumber()) - }) - }) - }) - - describe('#cancelUpkeep', () => { - it('reverts if the ID is not valid', async () => { - await evmRevert( - registry.connect(owner).cancelUpkeep(id.add(1).toNumber()), - 'too late to cancel upkeep', - ) - }) - - it('reverts if called by a non-owner/non-admin', async () => { - await evmRevert( - registry.connect(keeper1).cancelUpkeep(id), - 'only owner or admin', - ) - }) - - describe('when called by the owner', async () => { - it('sets the registration to invalid immediately', async () => { - const tx = await registry.connect(owner).cancelUpkeep(id) - const receipt = await tx.wait() - const registration = await registry.getUpkeep(id) - assert.equal( - registration.maxValidBlocknumber.toNumber(), - receipt.blockNumber, - ) - }) - - it('emits an event', async () => { - const tx = await registry.connect(owner).cancelUpkeep(id) - const receipt = await tx.wait() - await expect(tx) - .to.emit(registry, 'UpkeepCanceled') - .withArgs(id, BigNumber.from(receipt.blockNumber)) - }) - - it('updates the canceled registrations list', async () => { - let canceled = await registry.callStatic.getCanceledUpkeepList() - assert.deepEqual([], canceled) - - await registry.connect(owner).cancelUpkeep(id) - - canceled = await registry.callStatic.getCanceledUpkeepList() - assert.deepEqual([id], canceled) - }) - - it('immediately prevents upkeep', async () => { - await registry.connect(owner).cancelUpkeep(id) - - await evmRevert( - registry.connect(keeper2).performUpkeep(id, '0x'), - 'invalid upkeep id', - ) - }) - - it('does not revert if reverts if called multiple times', async () => { - await registry.connect(owner).cancelUpkeep(id) - await evmRevert( - registry.connect(owner).cancelUpkeep(id), - 'too late to cancel upkeep', - ) - }) - - describe('when called by the owner when the admin has just canceled', () => { - let oldExpiration: BigNumber - - beforeEach(async () => { - await registry.connect(admin).cancelUpkeep(id) - const registration = await registry.getUpkeep(id) - oldExpiration = registration.maxValidBlocknumber - }) - - it('allows the owner to cancel it more quickly', async () => { - await registry.connect(owner).cancelUpkeep(id) - - const registration = await registry.getUpkeep(id) - const newExpiration = registration.maxValidBlocknumber - assert.isTrue(newExpiration.lt(oldExpiration)) - }) - }) - }) - - describe('when called by the admin', async () => { - const delay = 50 - - it('sets the registration to invalid in 50 blocks', async () => { - const tx = await registry.connect(admin).cancelUpkeep(id) - const receipt = await tx.wait() - const registration = await registry.getUpkeep(id) - assert.equal( - registration.maxValidBlocknumber.toNumber(), - receipt.blockNumber + 50, - ) - }) - - it('emits an event', async () => { - const tx = await registry.connect(admin).cancelUpkeep(id) - const receipt = await tx.wait() - await expect(tx) - .to.emit(registry, 'UpkeepCanceled') - .withArgs(id, BigNumber.from(receipt.blockNumber + delay)) - }) - - it('updates the canceled registrations list', async () => { - let canceled = await registry.callStatic.getCanceledUpkeepList() - assert.deepEqual([], canceled) - - await registry.connect(admin).cancelUpkeep(id) - - canceled = await registry.callStatic.getCanceledUpkeepList() - assert.deepEqual([id], canceled) - }) - - it('immediately prevents upkeep', async () => { - await linkToken.connect(owner).approve(registry.address, toWei('100')) - await registry.connect(owner).addFunds(id, toWei('100')) - await registry.connect(admin).cancelUpkeep(id) - await registry.connect(keeper2).performUpkeep(id, '0x') // still works - - for (let i = 0; i < delay; i++) { - await ethers.provider.send('evm_mine', []) - } - - await evmRevert( - registry.connect(keeper2).performUpkeep(id, '0x'), - 'invalid upkeep id', - ) - }) - - it('reverts if called again by the admin', async () => { - await registry.connect(admin).cancelUpkeep(id) - - await evmRevert( - registry.connect(admin).cancelUpkeep(id), - 'too late to cancel upkeep', - ) - }) - - it('does not revert or double add the cancellation record if called by the owner immediately after', async () => { - await registry.connect(admin).cancelUpkeep(id) - - await registry.connect(owner).cancelUpkeep(id) - - const canceled = await registry.callStatic.getCanceledUpkeepList() - assert.deepEqual([id], canceled) - }) - - it('reverts if called by the owner after the timeout', async () => { - await registry.connect(admin).cancelUpkeep(id) - - for (let i = 0; i < delay; i++) { - await ethers.provider.send('evm_mine', []) - } - - await evmRevert( - registry.connect(owner).cancelUpkeep(id), - 'too late to cancel upkeep', - ) - }) - }) - }) - - describe('#withdrawPayment', () => { - beforeEach(async () => { - await linkToken.connect(owner).approve(registry.address, toWei('100')) - await registry.connect(owner).addFunds(id, toWei('100')) - await registry.connect(keeper1).performUpkeep(id, '0x') - }) - - it('reverts if called by anyone but the payee', async () => { - await evmRevert( - registry - .connect(payee2) - .withdrawPayment( - await keeper1.getAddress(), - await nonkeeper.getAddress(), - ), - 'only callable by payee', - ) - }) - - it('reverts if called with the 0 address', async () => { - await evmRevert( - registry - .connect(payee2) - .withdrawPayment(await keeper1.getAddress(), zeroAddress), - 'cannot send to zero address', - ) - }) - - it('updates the balances', async () => { - const to = await nonkeeper.getAddress() - const keeperBefore = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - const registrationBefore = (await registry.getUpkeep(id)).balance - const toLinkBefore = await linkToken.balanceOf(to) - const registryLinkBefore = await linkToken.balanceOf(registry.address) - - //// Do the thing - await registry - .connect(payee1) - .withdrawPayment(await keeper1.getAddress(), to) - - const keeperAfter = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - const registrationAfter = (await registry.getUpkeep(id)).balance - const toLinkAfter = await linkToken.balanceOf(to) - const registryLinkAfter = await linkToken.balanceOf(registry.address) - - assert.isTrue(keeperAfter.eq(BigNumber.from(0))) - assert.isTrue(registrationBefore.eq(registrationAfter)) - assert.isTrue(toLinkBefore.add(keeperBefore).eq(toLinkAfter)) - assert.isTrue(registryLinkBefore.sub(keeperBefore).eq(registryLinkAfter)) - }) - - it('emits a log announcing the withdrawal', async () => { - const balance = (await registry.getKeeperInfo(await keeper1.getAddress())) - .balance - const tx = await registry - .connect(payee1) - .withdrawPayment( - await keeper1.getAddress(), - await nonkeeper.getAddress(), - ) - await expect(tx) - .to.emit(registry, 'PaymentWithdrawn') - .withArgs( - await keeper1.getAddress(), - balance, - await nonkeeper.getAddress(), - await payee1.getAddress(), - ) - }) - }) - - describe('#transferPayeeship', () => { - it('reverts when called by anyone but the current payee', async () => { - await evmRevert( - registry - .connect(payee2) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ), - 'only callable by payee', - ) - }) - - it('reverts when transferring to self', async () => { - await evmRevert( - registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee1.getAddress(), - ), - 'cannot transfer to self', - ) - }) - - it('does not change the payee', async () => { - await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - - const info = await registry.getKeeperInfo(await keeper1.getAddress()) - assert.equal(await payee1.getAddress(), info.payee) - }) - - it('emits an event announcing the new payee', async () => { - const tx = await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - await expect(tx) - .to.emit(registry, 'PayeeshipTransferRequested') - .withArgs( - await keeper1.getAddress(), - await payee1.getAddress(), - await payee2.getAddress(), - ) - }) - - it('does not emit an event when called with the same proposal', async () => { - await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - - const tx = await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - const receipt = await tx.wait() - assert.equal(0, receipt.logs.length) - }) - }) - - describe('#acceptPayeeship', () => { - beforeEach(async () => { - await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - }) - - it('reverts when called by anyone but the proposed payee', async () => { - await evmRevert( - registry.connect(payee1).acceptPayeeship(await keeper1.getAddress()), - 'only callable by proposed payee', - ) - }) - - it('emits an event announcing the new payee', async () => { - const tx = await registry - .connect(payee2) - .acceptPayeeship(await keeper1.getAddress()) - await expect(tx) - .to.emit(registry, 'PayeeshipTransferred') - .withArgs( - await keeper1.getAddress(), - await payee1.getAddress(), - await payee2.getAddress(), - ) - }) - - it('does change the payee', async () => { - await registry.connect(payee2).acceptPayeeship(await keeper1.getAddress()) - - const info = await registry.getKeeperInfo(await keeper1.getAddress()) - assert.equal(await payee2.getAddress(), info.payee) - }) - }) - - describe('#setConfig', () => { - const payment = BigNumber.from(1) - const flatFee = BigNumber.from(2) - const checks = BigNumber.from(3) - const staleness = BigNumber.from(4) - const ceiling = BigNumber.from(5) - const maxGas = BigNumber.from(6) - const fbGasEth = BigNumber.from(7) - const fbLinkEth = BigNumber.from(8) - - it('reverts when called by anyone but the proposed owner', async () => { - await evmRevert( - registry - .connect(payee1) - .setConfig( - payment, - flatFee, - checks, - maxGas, - staleness, - gasCeilingMultiplier, - fbGasEth, - fbLinkEth, - ), - 'Only callable by owner', - ) - }) - - it('updates the config', async () => { - const old = await registry.getConfig() - const oldFlatFee = await registry.getFlatFee() - assert.isTrue(paymentPremiumPPB.eq(old.paymentPremiumPPB)) - assert.isTrue(flatFeeMicroLink.eq(oldFlatFee)) - assert.isTrue(blockCountPerTurn.eq(old.blockCountPerTurn)) - assert.isTrue(stalenessSeconds.eq(old.stalenessSeconds)) - assert.isTrue(gasCeilingMultiplier.eq(old.gasCeilingMultiplier)) - - await registry - .connect(owner) - .setConfig( - payment, - flatFee, - checks, - maxGas, - staleness, - ceiling, - fbGasEth, - fbLinkEth, - ) - - const updated = await registry.getConfig() - const newFlatFee = await registry.getFlatFee() - assert.equal(updated.paymentPremiumPPB, payment.toNumber()) - assert.equal(newFlatFee, flatFee.toNumber()) - assert.equal(updated.blockCountPerTurn, checks.toNumber()) - assert.equal(updated.stalenessSeconds, staleness.toNumber()) - assert.equal(updated.gasCeilingMultiplier, ceiling.toNumber()) - assert.equal(updated.checkGasLimit, maxGas.toNumber()) - assert.equal(updated.fallbackGasPrice.toNumber(), fbGasEth.toNumber()) - assert.equal(updated.fallbackLinkPrice.toNumber(), fbLinkEth.toNumber()) - }) - - it('emits an event', async () => { - const tx = await registry - .connect(owner) - .setConfig( - payment, - flatFee, - checks, - maxGas, - staleness, - ceiling, - fbGasEth, - fbLinkEth, - ) - await expect(tx) - .to.emit(registry, 'ConfigSet') - .withArgs( - payment, - checks, - maxGas, - staleness, - ceiling, - fbGasEth, - fbLinkEth, - ) - }) - }) - - describe('#onTokenTransfer', () => { - const amount = toWei('1') - - it('reverts if not called by the LINK token', async () => { - const data = ethers.utils.defaultAbiCoder.encode( - ['uint256'], - [id.toNumber().toString()], - ) - - await evmRevert( - registry - .connect(keeper1) - .onTokenTransfer(await keeper1.getAddress(), amount, data), - 'only callable through LINK', - ) - }) - - it('reverts if not called with more or less than 32 bytes', async () => { - const longData = ethers.utils.defaultAbiCoder.encode( - ['uint256', 'uint256'], - ['33', '34'], - ) - const shortData = '0x12345678' - - await evmRevert( - linkToken - .connect(owner) - .transferAndCall(registry.address, amount, longData), - ) - await evmRevert( - linkToken - .connect(owner) - .transferAndCall(registry.address, amount, shortData), - ) - }) - - it('reverts if the upkeep is canceled', async () => { - await registry.connect(admin).cancelUpkeep(id) - await evmRevert( - registry.connect(keeper1).addFunds(id, amount), - 'upkeep must be active', - ) - }) - - it('updates the funds of the job id passed', async () => { - const data = ethers.utils.defaultAbiCoder.encode( - ['uint256'], - [id.toNumber().toString()], - ) - - const before = (await registry.getUpkeep(id)).balance - await linkToken - .connect(owner) - .transferAndCall(registry.address, amount, data) - const after = (await registry.getUpkeep(id)).balance - - assert.isTrue(before.add(amount).eq(after)) - }) - }) - - describe('#recoverFunds', () => { - const sent = toWei('7') - - beforeEach(async () => { - await linkToken.connect(keeper1).approve(registry.address, toWei('100')) - - // add funds to upkeep 1 and perform and withdraw some payment - const tx = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - const id1 = await getUpkeepID(tx) - await registry.connect(keeper1).addFunds(id1, toWei('5')) - await registry.connect(keeper1).performUpkeep(id1, '0x') - await registry.connect(keeper2).performUpkeep(id1, '0x') - await registry.connect(keeper3).performUpkeep(id1, '0x') - await registry - .connect(payee1) - .withdrawPayment( - await keeper1.getAddress(), - await nonkeeper.getAddress(), - ) - - // transfer funds directly to the registry - await linkToken.connect(keeper1).transfer(registry.address, sent) - - // add funds to upkeep 2 and perform and withdraw some payment - const tx2 = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - const id2 = await getUpkeepID(tx2) - await registry.connect(keeper1).addFunds(id2, toWei('5')) - await registry.connect(keeper1).performUpkeep(id2, '0x') - await registry.connect(keeper2).performUpkeep(id2, '0x') - await registry.connect(keeper3).performUpkeep(id2, '0x') - await registry - .connect(payee2) - .withdrawPayment( - await keeper2.getAddress(), - await nonkeeper.getAddress(), - ) - - // transfer funds using onTokenTransfer - const data = ethers.utils.defaultAbiCoder.encode( - ['uint256'], - [id2.toNumber().toString()], - ) - await linkToken - .connect(owner) - .transferAndCall(registry.address, toWei('1'), data) - - // remove a keeper - await registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress(), await keeper2.getAddress()], - [await payee1.getAddress(), await payee2.getAddress()], - ) - - // withdraw some funds - await registry.connect(owner).cancelUpkeep(id1) - await registry.connect(admin).withdrawFunds(id1, await admin.getAddress()) - }) - - it('reverts if not called by owner', async () => { - await evmRevert( - registry.connect(keeper1).recoverFunds(), - 'Only callable by owner', - ) - }) - - it('allows any funds that have been accidentally transfered to be moved', async () => { - const balanceBefore = await linkToken.balanceOf(registry.address) - - await linkToken.balanceOf(registry.address) - - await registry.connect(owner).recoverFunds() - const balanceAfter = await linkToken.balanceOf(registry.address) - assert.isTrue(balanceBefore.eq(balanceAfter.add(sent))) - }) - }) - - describe('#pause', () => { - it('reverts if called by a non-owner', async () => { - await evmRevert( - registry.connect(keeper1).pause(), - 'Only callable by owner', - ) - }) - - it('marks the contract as paused', async () => { - assert.isFalse(await registry.paused()) - - await registry.connect(owner).pause() - - assert.isTrue(await registry.paused()) - }) - }) - - describe('#unpause', () => { - beforeEach(async () => { - await registry.connect(owner).pause() - }) - - it('reverts if called by a non-owner', async () => { - await evmRevert( - registry.connect(keeper1).unpause(), - 'Only callable by owner', - ) - }) - - it('marks the contract as not paused', async () => { - assert.isTrue(await registry.paused()) - - await registry.connect(owner).unpause() - - assert.isFalse(await registry.paused()) - }) - }) - - describe('#getMaxPaymentForGas', () => { - const gasAmounts = [100000, 10000000] - const premiums = [0, 250000000] - const flatFees = [0, 1000000] - it('calculates the max fee approptiately', async () => { - for (let idx = 0; idx < gasAmounts.length; idx++) { - const gas = gasAmounts[idx] - for (let jdx = 0; jdx < premiums.length; jdx++) { - const premium = premiums[jdx] - for (let kdx = 0; kdx < flatFees.length; kdx++) { - const flatFee = flatFees[kdx] - await registry - .connect(owner) - .setConfig( - premium, - flatFee, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - gasCeilingMultiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - const price = await registry.getMaxPaymentForGas(gas) - expect(price).to.equal(linkForGas(gas, premium, flatFee)) - } - } - } - }) - }) - - describe('#checkUpkeep / #performUpkeep', () => { - const performData = '0xc0ffeec0ffee' - const multiplier = BigNumber.from(10) - const flatFee = BigNumber.from('100000') //0.1 LINK - const callGasPrice = 1 - - it('uses the same minimum balance calculation [ @skip-coverage ]', async () => { - await registry - .connect(owner) - .setConfig( - paymentPremiumPPB, - flatFee, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - multiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - await linkToken.connect(owner).approve(registry.address, toWei('100')) - - const tx1 = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - const upkeepID1 = await getUpkeepID(tx1) - const tx2 = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - const upkeepID2 = await getUpkeepID(tx2) - await mock.setCanCheck(true) - await mock.setCanPerform(true) - // upkeep 1 is underfunded, 2 is funded - const minBalance1 = (await registry.getMaxPaymentForGas(executeGas)).sub( - 1, - ) - const minBalance2 = await registry.getMaxPaymentForGas(executeGas) - await registry.connect(owner).addFunds(upkeepID1, minBalance1) - await registry.connect(owner).addFunds(upkeepID2, minBalance2) - // upkeep 1 check should revert, 2 should succeed - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(upkeepID1, await keeper1.getAddress(), { - gasPrice: callGasPrice, - }), - ) - await registry - .connect(zeroAddress) - .callStatic.checkUpkeep(upkeepID2, await keeper1.getAddress(), { - gasPrice: callGasPrice, - }) - // upkeep 1 perform should revert, 2 should succeed - await evmRevert( - registry - .connect(keeper1) - .performUpkeep(upkeepID1, performData, { gasLimit: extraGas }), - 'insufficient funds', - ) - await registry - .connect(keeper1) - .performUpkeep(upkeepID2, performData, { gasLimit: extraGas }) - }) - }) - - describe('#getMinBalanceForUpkeep / #checkUpkeep', () => { - it('calculates the minimum balance appropriately', async () => { - const oneWei = BigNumber.from('1') - await linkToken.connect(keeper1).approve(registry.address, toWei('100')) - await mock.setCanCheck(true) - await mock.setCanPerform(true) - const minBalance = await registry.getMinBalanceForUpkeep(id) - const tooLow = minBalance.sub(oneWei) - await registry.connect(keeper1).addFunds(id, tooLow) - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()), - 'insufficient funds', - ) - await registry.connect(keeper1).addFunds(id, oneWei) - await registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()) - }) - }) -}) diff --git a/contracts/test/v0.7/Operator.test.ts b/contracts/test/v0.7/Operator.test.ts deleted file mode 100644 index 4af846576b3..00000000000 --- a/contracts/test/v0.7/Operator.test.ts +++ /dev/null @@ -1,3819 +0,0 @@ -import { ethers } from 'hardhat' -import { - publicAbi, - toBytes32String, - toWei, - stringToBytes, - increaseTime5Minutes, - getLog, -} from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { - BigNumber, - constants, - Contract, - ContractFactory, - ContractReceipt, - ContractTransaction, - Signer, -} from 'ethers' -import { getUsers, Roles } from '../test-helpers/setup' -import { bigNumEquals, evmRevert } from '../test-helpers/matchers' -import type { providers } from 'ethers' -import { - convertCancelParams, - convertCancelByRequesterParams, - convertFufillParams, - convertFulfill2Params, - decodeRunRequest, - encodeOracleRequest, - encodeRequestOracleData, - RunRequest, -} from '../test-helpers/oracle' - -let v7ConsumerFactory: ContractFactory -let basicConsumerFactory: ContractFactory -let multiWordConsumerFactory: ContractFactory -let gasGuzzlingConsumerFactory: ContractFactory -let getterSetterFactory: ContractFactory -let maliciousRequesterFactory: ContractFactory -let maliciousConsumerFactory: ContractFactory -let maliciousMultiWordConsumerFactory: ContractFactory -let operatorFactory: ContractFactory -let forwarderFactory: ContractFactory -let linkTokenFactory: ContractFactory -const zeroAddress = ethers.constants.AddressZero - -let roles: Roles - -before(async () => { - const users = await getUsers() - - roles = users.roles - v7ConsumerFactory = await ethers.getContractFactory( - 'src/v0.7/tests/Consumer.sol:Consumer', - ) - basicConsumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/BasicConsumer.sol:BasicConsumer', - ) - multiWordConsumerFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MultiWordConsumer.sol:MultiWordConsumer', - ) - gasGuzzlingConsumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/GasGuzzlingConsumer.sol:GasGuzzlingConsumer', - ) - getterSetterFactory = await ethers.getContractFactory( - 'src/v0.4/tests/GetterSetter.sol:GetterSetter', - ) - maliciousRequesterFactory = await ethers.getContractFactory( - 'src/v0.4/tests/MaliciousRequester.sol:MaliciousRequester', - ) - maliciousConsumerFactory = await ethers.getContractFactory( - 'src/v0.4/tests/MaliciousConsumer.sol:MaliciousConsumer', - ) - maliciousMultiWordConsumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/MaliciousMultiWordConsumer.sol:MaliciousMultiWordConsumer', - ) - operatorFactory = await ethers.getContractFactory( - 'src/v0.7/Operator.sol:Operator', - ) - forwarderFactory = await ethers.getContractFactory( - 'src/v0.7/AuthorizedForwarder.sol:AuthorizedForwarder', - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - ) -}) - -describe('Operator', () => { - let fHash: string - let specId: string - let to: string - let link: Contract - let operator: Contract - let forwarder1: Contract - let forwarder2: Contract - let owner: Signer - - beforeEach(async () => { - fHash = getterSetterFactory.interface.getSighash('requestedBytes32') - specId = - '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000000' - to = '0x80e29acb842498fe6591f020bd82766dce619d43' - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - owner = roles.defaultAccount - operator = await operatorFactory - .connect(owner) - .deploy(link.address, await owner.getAddress()) - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.oracleNode.getAddress()]) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(operator, [ - 'acceptAuthorizedReceivers', - 'acceptOwnableContracts', - 'cancelOracleRequest', - 'cancelOracleRequestByRequester', - 'distributeFunds', - 'fulfillOracleRequest', - 'fulfillOracleRequest2', - 'getAuthorizedSenders', - 'getChainlinkToken', - 'getExpiryTime', - 'isAuthorizedSender', - 'onTokenTransfer', - 'operatorRequest', - 'oracleRequest', - 'ownerForward', - 'ownerTransferAndCall', - 'setAuthorizedSenders', - 'setAuthorizedSendersOn', - 'transferOwnableContracts', - 'typeAndVersion', - 'withdraw', - 'withdrawable', - // Ownable methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#typeAndVersion', () => { - it('describes the operator', async () => { - assert.equal(await operator.typeAndVersion(), 'Operator 1.0.0') - }) - }) - - describe('#transferOwnableContracts', () => { - beforeEach(async () => { - forwarder1 = await forwarderFactory - .connect(owner) - .deploy(link.address, operator.address, zeroAddress, '0x') - forwarder2 = await forwarderFactory - .connect(owner) - .deploy(link.address, operator.address, zeroAddress, '0x') - }) - - describe('being called by the owner', () => { - it('cannot transfer to self', async () => { - await evmRevert( - operator - .connect(owner) - .transferOwnableContracts([forwarder1.address], operator.address), - 'Cannot transfer to self', - ) - }) - - it('emits an ownership transfer request event', async () => { - const tx = await operator - .connect(owner) - .transferOwnableContracts( - [forwarder1.address, forwarder2.address], - await roles.oracleNode1.getAddress(), - ) - const receipt = await tx.wait() - assert.equal(receipt?.events?.length, 2) - const log1 = receipt?.events?.[0] - assert.equal(log1?.event, 'OwnershipTransferRequested') - assert.equal(log1?.address, forwarder1.address) - assert.equal(log1?.args?.[0], operator.address) - assert.equal(log1?.args?.[1], await roles.oracleNode1.getAddress()) - const log2 = receipt?.events?.[1] - assert.equal(log2?.event, 'OwnershipTransferRequested') - assert.equal(log2?.address, forwarder2.address) - assert.equal(log2?.args?.[0], operator.address) - assert.equal(log2?.args?.[1], await roles.oracleNode1.getAddress()) - }) - }) - - describe('being called by a non-owner', () => { - it('reverts with message', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .transferOwnableContracts( - [forwarder1.address], - await roles.oracleNode2.getAddress(), - ), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#acceptOwnableContracts', () => { - describe('being called by the owner', () => { - let operator2: Contract - let receipt: ContractReceipt - - beforeEach(async () => { - operator2 = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - forwarder1 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, zeroAddress, '0x') - forwarder2 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, zeroAddress, '0x') - await operator - .connect(roles.defaultAccount) - .transferOwnableContracts( - [forwarder1.address, forwarder2.address], - operator2.address, - ) - const tx = await operator2 - .connect(roles.defaultAccount) - .acceptOwnableContracts([forwarder1.address, forwarder2.address]) - receipt = await tx.wait() - }) - - it('sets the new owner on the forwarder', async () => { - assert.equal(await forwarder1.owner(), operator2.address) - }) - - it('emits ownership transferred events', async () => { - assert.equal(receipt?.events?.[0]?.event, 'OwnableContractAccepted') - assert.equal(receipt?.events?.[0]?.args?.[0], forwarder1.address) - - assert.equal(receipt?.events?.[1]?.event, 'OwnershipTransferred') - assert.equal(receipt?.events?.[1]?.address, forwarder1.address) - assert.equal(receipt?.events?.[1]?.args?.[0], operator.address) - assert.equal(receipt?.events?.[1]?.args?.[1], operator2.address) - - assert.equal(receipt?.events?.[2]?.event, 'OwnableContractAccepted') - assert.equal(receipt?.events?.[2]?.args?.[0], forwarder2.address) - - assert.equal(receipt?.events?.[3]?.event, 'OwnershipTransferred') - assert.equal(receipt?.events?.[3]?.address, forwarder2.address) - assert.equal(receipt?.events?.[3]?.args?.[0], operator.address) - assert.equal(receipt?.events?.[3]?.args?.[1], operator2.address) - }) - }) - - describe('being called by a non-owner authorized sender', () => { - it('does not revert', async () => { - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.oracleNode1.getAddress()]) - - await operator.connect(roles.oracleNode1).acceptOwnableContracts([]) - }) - }) - - describe('being called by a non owner', () => { - it('reverts with message', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .acceptOwnableContracts([await roles.oracleNode2.getAddress()]), - 'Cannot set authorized senders', - ) - }) - }) - }) - - describe('#distributeFunds', () => { - describe('when called with empty arrays', () => { - it('reverts with invalid array message', async () => { - await evmRevert( - operator.connect(roles.defaultAccount).distributeFunds([], []), - 'Invalid array length(s)', - ) - }) - }) - - describe('when called with unequal array lengths', () => { - it('reverts with invalid array message', async () => { - const receivers = [ - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - const amounts = [1, 2, 3] - await evmRevert( - operator - .connect(roles.defaultAccount) - .distributeFunds(receivers, amounts), - 'Invalid array length(s)', - ) - }) - }) - - describe('when called with not enough ETH', () => { - it('reverts with subtraction overflow message', async () => { - const amountToSend = toWei('2') - const ethSent = toWei('1') - await evmRevert( - operator - .connect(roles.defaultAccount) - .distributeFunds( - [await roles.oracleNode2.getAddress()], - [amountToSend], - { - value: ethSent, - }, - ), - 'SafeMath: subtraction overflow', - ) - }) - }) - - describe('when called with too much ETH', () => { - it('reverts with too much ETH message', async () => { - const amountToSend = toWei('2') - const ethSent = toWei('3') - await evmRevert( - operator - .connect(roles.defaultAccount) - .distributeFunds( - [await roles.oracleNode2.getAddress()], - [amountToSend], - { - value: ethSent, - }, - ), - 'Too much ETH sent', - ) - }) - }) - - describe('when called with correct values', () => { - it('updates the balances', async () => { - const node2BalanceBefore = await roles.oracleNode2.getBalance() - const node3BalanceBefore = await roles.oracleNode3.getBalance() - const receivers = [ - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - const sendNode2 = toWei('2') - const sendNode3 = toWei('3') - const totalAmount = toWei('5') - const amounts = [sendNode2, sendNode3] - - await operator - .connect(roles.defaultAccount) - .distributeFunds(receivers, amounts, { value: totalAmount }) - - const node2BalanceAfter = await roles.oracleNode2.getBalance() - const node3BalanceAfter = await roles.oracleNode3.getBalance() - - assert.equal( - node2BalanceAfter.sub(node2BalanceBefore).toString(), - sendNode2.toString(), - ) - - assert.equal( - node3BalanceAfter.sub(node3BalanceBefore).toString(), - sendNode3.toString(), - ) - }) - }) - }) - - describe('#setAuthorizedSenders', () => { - let newSenders: string[] - let receipt: ContractReceipt - describe('when called by the owner', () => { - describe('setting 3 authorized senders', () => { - beforeEach(async () => { - newSenders = [ - await roles.oracleNode1.getAddress(), - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - const tx = await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders) - receipt = await tx.wait() - }) - - it('adds the authorized nodes', async () => { - const authorizedSenders = await operator.getAuthorizedSenders() - assert.equal(newSenders.length, authorizedSenders.length) - for (let i = 0; i < authorizedSenders.length; i++) { - assert.equal(authorizedSenders[i], newSenders[i]) - } - }) - - it('emits an event on the Operator', async () => { - assert.equal(receipt.events?.length, 1) - - const encodedSenders1 = ethers.utils.defaultAbiCoder.encode( - ['address[]', 'address'], - [newSenders, await roles.defaultAccount.getAddress()], - ) - - const responseEvent1 = receipt.events?.[0] - assert.equal(responseEvent1?.event, 'AuthorizedSendersChanged') - assert.equal(responseEvent1?.data, encodedSenders1) - }) - - it('replaces the authorized nodes', async () => { - const originalAuthorization = await operator - .connect(roles.defaultAccount) - .isAuthorizedSender(await roles.oracleNode.getAddress()) - assert.isFalse(originalAuthorization) - }) - - after(async () => { - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.oracleNode.getAddress()]) - }) - }) - - describe('setting 0 authorized senders', () => { - beforeEach(async () => { - newSenders = [] - }) - - it('reverts with a minimum senders message', async () => { - await evmRevert( - operator - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders), - 'Must have at least 1 sender', - ) - }) - }) - }) - - describe('when called by an authorized sender', () => { - beforeEach(async () => { - newSenders = [await roles.oracleNode1.getAddress()] - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders) - }) - - it('succeeds', async () => { - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.stranger.getAddress()]) - }) - }) - - describe('when called by a non-owner', () => { - it('cannot add an authorized node', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .setAuthorizedSenders([await roles.stranger.getAddress()]), - 'Cannot set authorized senders', - ) - }) - }) - }) - - describe('#setAuthorizedSendersOn', () => { - let newSenders: string[] - - beforeEach(async () => { - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.oracleNode1.getAddress()]) - newSenders = [ - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - - forwarder1 = await forwarderFactory - .connect(owner) - .deploy(link.address, operator.address, zeroAddress, '0x') - forwarder2 = await forwarderFactory - .connect(owner) - .deploy(link.address, operator.address, zeroAddress, '0x') - }) - - describe('when called by a non-authorized sender', () => { - it('reverts', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .setAuthorizedSendersOn(newSenders, [forwarder1.address]), - 'Cannot set authorized senders', - ) - }) - }) - - describe('when called by an owner', () => { - it('does not revert', async () => { - await operator - .connect(roles.defaultAccount) - .setAuthorizedSendersOn( - [forwarder1.address, forwarder2.address], - newSenders, - ) - }) - }) - - describe('when called by an authorized sender', () => { - it('does not revert', async () => { - await operator - .connect(roles.oracleNode1) - .setAuthorizedSendersOn( - [forwarder1.address, forwarder2.address], - newSenders, - ) - }) - - it('does revert with 0 senders', async () => { - await operator - .connect(roles.oracleNode1) - .setAuthorizedSendersOn( - [forwarder1.address, forwarder2.address], - newSenders, - ) - }) - - it('emits a log announcing the change and who made it', async () => { - const targets = [forwarder1.address, forwarder2.address] - const tx = await operator - .connect(roles.oracleNode1) - .setAuthorizedSendersOn(targets, newSenders) - - const receipt = await tx.wait() - const encodedArgs = ethers.utils.defaultAbiCoder.encode( - ['address[]', 'address[]', 'address'], - [targets, newSenders, await roles.oracleNode1.getAddress()], - ) - - const event1 = receipt.events?.[0] - assert.equal(event1?.event, 'TargetsUpdatedAuthorizedSenders') - assert.equal(event1?.address, operator.address) - assert.equal(event1?.data, encodedArgs) - }) - - it('updates the sender list on each of the targets', async () => { - const tx = await operator - .connect(roles.oracleNode1) - .setAuthorizedSendersOn( - [forwarder1.address, forwarder2.address], - newSenders, - ) - - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 3, receipt.toString()) - const encodedSenders = ethers.utils.defaultAbiCoder.encode( - ['address[]', 'address'], - [newSenders, operator.address], - ) - - const event1 = receipt.events?.[1] - assert.equal(event1?.event, 'AuthorizedSendersChanged') - assert.equal(event1?.address, forwarder1.address) - assert.equal(event1?.data, encodedSenders) - - const event2 = receipt.events?.[2] - assert.equal(event2?.event, 'AuthorizedSendersChanged') - assert.equal(event2?.address, forwarder2.address) - assert.equal(event2?.data, encodedSenders) - }) - }) - }) - - describe('#acceptAuthorizedReceivers', () => { - let newSenders: string[] - - describe('being called by the owner', () => { - let operator2: Contract - let receipt: ContractReceipt - - beforeEach(async () => { - operator2 = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - forwarder1 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, zeroAddress, '0x') - forwarder2 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, zeroAddress, '0x') - await operator - .connect(roles.defaultAccount) - .transferOwnableContracts( - [forwarder1.address, forwarder2.address], - operator2.address, - ) - newSenders = [ - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - - const tx = await operator2 - .connect(roles.defaultAccount) - .acceptAuthorizedReceivers( - [forwarder1.address, forwarder2.address], - newSenders, - ) - receipt = await tx.wait() - }) - - it('sets the new owner on the forwarder', async () => { - assert.equal(await forwarder1.owner(), operator2.address) - }) - - it('emits ownership transferred events', async () => { - assert.equal(receipt?.events?.[0]?.event, 'OwnableContractAccepted') - assert.equal(receipt?.events?.[0]?.args?.[0], forwarder1.address) - - assert.equal(receipt?.events?.[1]?.event, 'OwnershipTransferred') - assert.equal(receipt?.events?.[1]?.address, forwarder1.address) - assert.equal(receipt?.events?.[1]?.args?.[0], operator.address) - assert.equal(receipt?.events?.[1]?.args?.[1], operator2.address) - - assert.equal(receipt?.events?.[2]?.event, 'OwnableContractAccepted') - assert.equal(receipt?.events?.[2]?.args?.[0], forwarder2.address) - - assert.equal(receipt?.events?.[3]?.event, 'OwnershipTransferred') - assert.equal(receipt?.events?.[3]?.address, forwarder2.address) - assert.equal(receipt?.events?.[3]?.args?.[0], operator.address) - assert.equal(receipt?.events?.[3]?.args?.[1], operator2.address) - - assert.equal( - receipt?.events?.[4]?.event, - 'TargetsUpdatedAuthorizedSenders', - ) - - const encodedSenders = ethers.utils.defaultAbiCoder.encode( - ['address[]', 'address'], - [newSenders, operator2.address], - ) - assert.equal(receipt?.events?.[5]?.event, 'AuthorizedSendersChanged') - assert.equal(receipt?.events?.[5]?.address, forwarder1.address) - assert.equal(receipt?.events?.[5]?.data, encodedSenders) - - assert.equal(receipt?.events?.[6]?.event, 'AuthorizedSendersChanged') - assert.equal(receipt?.events?.[6]?.address, forwarder2.address) - assert.equal(receipt?.events?.[6]?.data, encodedSenders) - }) - }) - - describe('being called by a non owner', () => { - it('reverts with message', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .acceptAuthorizedReceivers( - [forwarder1.address, forwarder2.address], - newSenders, - ), - 'Cannot set authorized senders', - ) - }) - }) - }) - - describe('#onTokenTransfer', () => { - describe('when called from any address but the LINK token', () => { - it('triggers the intended method', async () => { - const callData = encodeOracleRequest( - specId, - to, - fHash, - 0, - constants.HashZero, - ) - - await evmRevert( - operator.onTokenTransfer( - await roles.defaultAccount.getAddress(), - 0, - callData, - ), - ) - }) - }) - - describe('when called from the LINK token', () => { - it('triggers the intended method', async () => { - const callData = encodeOracleRequest( - specId, - to, - fHash, - 0, - constants.HashZero, - ) - - const tx = await link.transferAndCall(operator.address, 0, callData, { - value: 0, - }) - const receipt = await tx.wait() - - assert.equal(3, receipt.logs?.length) - }) - - describe('with no data', () => { - it('reverts', async () => { - await evmRevert( - link.transferAndCall(operator.address, 0, '0x', { - value: 0, - }), - ) - }) - }) - }) - - describe('malicious requester', () => { - let mock: Contract - let requester: Contract - const paymentAmount = toWei('1') - - beforeEach(async () => { - mock = await maliciousRequesterFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(mock.address, paymentAmount) - }) - - it('cannot withdraw from oracle', async () => { - const operatorOriginalBalance = await link.balanceOf(operator.address) - const mockOriginalBalance = await link.balanceOf(mock.address) - - await evmRevert(mock.maliciousWithdraw()) - - const operatorNewBalance = await link.balanceOf(operator.address) - const mockNewBalance = await link.balanceOf(mock.address) - - bigNumEquals(operatorOriginalBalance, operatorNewBalance) - bigNumEquals(mockNewBalance, mockOriginalBalance) - }) - - describe('if the requester tries to create a requestId for another contract', () => { - it('the requesters ID will not match with the oracle contract', async () => { - const tx = await mock.maliciousTargetConsumer(to) - const receipt = await tx.wait() - - const mockRequestId = receipt.logs?.[0].data - const requestId = (receipt.events?.[0].args as any).requestId - assert.notEqual(mockRequestId, requestId) - }) - - it('the target requester can still create valid requests', async () => { - requester = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - await link.transfer(requester.address, paymentAmount) - await mock.maliciousTargetConsumer(requester.address) - await requester.requestEthereumPrice('USD', paymentAmount) - }) - }) - }) - - it('does not allow recursive calls of onTokenTransfer', async () => { - const requestPayload = encodeOracleRequest( - specId, - to, - fHash, - 0, - constants.HashZero, - ) - - const ottSelector = - operatorFactory.interface.getSighash('onTokenTransfer') - const header = - '000000000000000000000000c5fdf4076b8f3a5357c5e395ab970b5b54098fef' + // to - '0000000000000000000000000000000000000000000000000000000000000539' + // amount - '0000000000000000000000000000000000000000000000000000000000000060' + // offset - '0000000000000000000000000000000000000000000000000000000000000136' // length - - const maliciousPayload = ottSelector + header + requestPayload.slice(2) - - await evmRevert( - link.transferAndCall(operator.address, 0, maliciousPayload, { - value: 0, - }), - ) - }) - }) - - describe('#oracleRequest', () => { - describe('when called through the LINK token', () => { - const paid = 100 - let log: providers.Log | undefined - let receipt: providers.TransactionReceipt - - beforeEach(async () => { - const args = encodeOracleRequest( - specId, - to, - fHash, - 1, - constants.HashZero, - ) - const tx = await link.transferAndCall(operator.address, paid, args) - receipt = await tx.wait() - assert.equal(3, receipt?.logs?.length) - - log = receipt.logs && receipt.logs[2] - }) - - it('logs an event', async () => { - assert.equal(operator.address, log?.address) - - assert.equal(log?.topics?.[1], specId) - - const req = decodeRunRequest(receipt?.logs?.[2]) - assert.equal(await roles.defaultAccount.getAddress(), req.requester) - bigNumEquals(paid, req.payment) - }) - - it('uses the expected event signature', async () => { - // If updating this test, be sure to update models.RunLogTopic. - const eventSignature = - '0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65' - assert.equal(eventSignature, log?.topics?.[0]) - }) - - it('does not allow the same requestId to be used twice', async () => { - const args2 = encodeOracleRequest( - specId, - to, - fHash, - 1, - constants.HashZero, - ) - await evmRevert(link.transferAndCall(operator.address, paid, args2)) - }) - - describe('when called with a payload less than 2 EVM words + function selector', () => { - it('throws an error', async () => { - const funcSelector = - operatorFactory.interface.getSighash('oracleRequest') - const maliciousData = - funcSelector + - '0000000000000000000000000000000000000000000000000000000000000000000' - await evmRevert( - link.transferAndCall(operator.address, paid, maliciousData), - ) - }) - }) - - describe('when called with a payload between 3 and 9 EVM words', () => { - it('throws an error', async () => { - const funcSelector = - operatorFactory.interface.getSighash('oracleRequest') - const maliciousData = - funcSelector + - '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001' - await evmRevert( - link.transferAndCall(operator.address, paid, maliciousData), - ) - }) - }) - }) - - describe('when dataVersion is higher than 255', () => { - it('throws an error', async () => { - const paid = 100 - const args = encodeOracleRequest( - specId, - to, - fHash, - 1, - constants.HashZero, - 256, - ) - await evmRevert(link.transferAndCall(operator.address, paid, args)) - }) - }) - - describe('when not called through the LINK token', () => { - it('reverts', async () => { - await evmRevert( - operator - .connect(roles.oracleNode) - .oracleRequest( - '0x0000000000000000000000000000000000000000', - 0, - specId, - to, - fHash, - 1, - 1, - '0x', - ), - ) - }) - }) - }) - - describe('#operatorRequest', () => { - describe('when called through the LINK token', () => { - const paid = 100 - let log: providers.Log | undefined - let receipt: providers.TransactionReceipt - - beforeEach(async () => { - const args = encodeRequestOracleData( - specId, - fHash, - 1, - constants.HashZero, - ) - const tx = await link.transferAndCall(operator.address, paid, args) - receipt = await tx.wait() - assert.equal(3, receipt?.logs?.length) - - log = receipt.logs && receipt.logs[2] - }) - - it('logs an event', async () => { - assert.equal(operator.address, log?.address) - - assert.equal(log?.topics?.[1], specId) - - const req = decodeRunRequest(receipt?.logs?.[2]) - assert.equal(await roles.defaultAccount.getAddress(), req.requester) - bigNumEquals(paid, req.payment) - }) - - it('uses the expected event signature', async () => { - // If updating this test, be sure to update models.RunLogTopic. - const eventSignature = - '0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65' - assert.equal(eventSignature, log?.topics?.[0]) - }) - - it('does not allow the same requestId to be used twice', async () => { - const args2 = encodeRequestOracleData( - specId, - fHash, - 1, - constants.HashZero, - ) - await evmRevert(link.transferAndCall(operator.address, paid, args2)) - }) - - describe('when called with a payload less than 2 EVM words + function selector', () => { - it('throws an error', async () => { - const funcSelector = - operatorFactory.interface.getSighash('oracleRequest') - const maliciousData = - funcSelector + - '0000000000000000000000000000000000000000000000000000000000000000000' - await evmRevert( - link.transferAndCall(operator.address, paid, maliciousData), - ) - }) - }) - - describe('when called with a payload between 3 and 9 EVM words', () => { - it('throws an error', async () => { - const funcSelector = - operatorFactory.interface.getSighash('oracleRequest') - const maliciousData = - funcSelector + - '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001' - await evmRevert( - link.transferAndCall(operator.address, paid, maliciousData), - ) - }) - }) - }) - - describe('when dataVersion is higher than 255', () => { - it('throws an error', async () => { - const paid = 100 - const args = encodeRequestOracleData( - specId, - fHash, - 1, - constants.HashZero, - 256, - ) - await evmRevert(link.transferAndCall(operator.address, paid, args)) - }) - }) - - describe('when not called through the LINK token', () => { - it('reverts', async () => { - await evmRevert( - operator - .connect(roles.oracleNode) - .oracleRequest( - '0x0000000000000000000000000000000000000000', - 0, - specId, - to, - fHash, - 1, - 1, - '0x', - ), - ) - }) - }) - }) - - describe('#fulfillOracleRequest', () => { - const response = 'Hi Mom!' - let maliciousRequester: Contract - let basicConsumer: Contract - let maliciousConsumer: Contract - let gasGuzzlingConsumer: Contract - let request: ReturnType - - describe('gas guzzling consumer [ @skip-coverage ]', () => { - beforeEach(async () => { - gasGuzzlingConsumer = await gasGuzzlingConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(gasGuzzlingConsumer.address, paymentAmount) - const tx = - await gasGuzzlingConsumer.gassyRequestEthereumPrice(paymentAmount) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('emits an OracleResponse event', async () => { - const fulfillParams = convertFufillParams(request, response) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 1) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - }) - - describe('cooperative consumer', () => { - beforeEach(async () => { - basicConsumer = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(basicConsumer.address, paymentAmount) - const currency = 'USD' - const tx = await basicConsumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - describe('when called by an unauthorized node', () => { - beforeEach(async () => { - assert.equal( - false, - await operator.isAuthorizedSender( - await roles.stranger.getAddress(), - ), - ) - }) - - it('raises an error', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .fulfillOracleRequest(...convertFufillParams(request, response)), - ) - }) - }) - - describe('when fulfilled with the wrong function', () => { - let v7Consumer - beforeEach(async () => { - v7Consumer = await v7ConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(v7Consumer.address, paymentAmount) - const currency = 'USD' - const tx = await v7Consumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('raises an error', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .fulfillOracleRequest(...convertFufillParams(request, response)), - ) - }) - }) - - describe('when called by an authorized node', () => { - it('raises an error if the request ID does not exist', async () => { - request.requestId = ethers.utils.formatBytes32String('DOESNOTEXIST') - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)), - ) - }) - - it('sets the value on the requested contract', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - const currentValue = await basicConsumer.currentPrice() - assert.equal(response, ethers.utils.parseBytes32String(currentValue)) - }) - - it('emits an OracleResponse event', async () => { - const fulfillParams = convertFufillParams(request, response) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 3) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - - it('does not allow a request to be fulfilled twice', async () => { - const response2 = response + ' && Hello World!!' - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response2)), - ) - - const currentValue = await basicConsumer.currentPrice() - assert.equal(response, ethers.utils.parseBytes32String(currentValue)) - }) - }) - - describe('when the oracle does not provide enough gas', () => { - // if updating this defaultGasLimit, be sure it matches with the - // defaultGasLimit specified in store/tx_manager.go - const defaultGasLimit = 500000 - - beforeEach(async () => { - bigNumEquals(0, await operator.withdrawable()) - }) - - it('does not allow the oracle to withdraw the payment', async () => { - await evmRevert( - operator.connect(roles.oracleNode).fulfillOracleRequest( - ...convertFufillParams(request, response, { - gasLimit: 70000, - }), - ), - ) - - bigNumEquals(0, await operator.withdrawable()) - }) - - it(`${defaultGasLimit} is enough to pass the gas requirement`, async () => { - await operator.connect(roles.oracleNode).fulfillOracleRequest( - ...convertFufillParams(request, response, { - gasLimit: defaultGasLimit, - }), - ) - - bigNumEquals(request.payment, await operator.withdrawable()) - }) - }) - }) - - describe('with a malicious requester', () => { - beforeEach(async () => { - const paymentAmount = toWei('1') - maliciousRequester = await maliciousRequesterFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousRequester.address, paymentAmount) - }) - - it('cannot cancel before the expiration', async () => { - await evmRevert( - maliciousRequester.maliciousRequestCancel( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ), - ) - }) - - it('cannot call functions on the LINK token through callbacks', async () => { - await evmRevert( - maliciousRequester.request( - specId, - link.address, - ethers.utils.toUtf8Bytes('transfer(address,uint256)'), - ), - ) - }) - - describe('requester lies about amount of LINK sent', () => { - it('the oracle uses the amount of LINK actually paid', async () => { - const tx = await maliciousRequester.maliciousPrice(specId) - const receipt = await tx.wait() - const req = decodeRunRequest(receipt.logs?.[3]) - - assert(toWei('1').eq(req.payment)) - }) - }) - }) - - describe('with a malicious consumer', () => { - const paymentAmount = toWei('1') - - beforeEach(async () => { - maliciousConsumer = await maliciousConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousConsumer.address, paymentAmount) - }) - - describe('fails during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('assertFail(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response2)), - ) - }) - }) - - describe('calls selfdestruct', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - await maliciousConsumer.remove() - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - }) - - describe('request is canceled during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('cancelRequestOnFulfill(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - bigNumEquals(0, await link.balanceOf(maliciousConsumer.address)) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - const mockBalance = await link.balanceOf(maliciousConsumer.address) - bigNumEquals(mockBalance, 0) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response2)), - ) - }) - }) - - describe('tries to steal funds from node', () => { - it('is not successful with call', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthCall(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with send', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthSend(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with transfer', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthTransfer(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - }) - - describe('when calling an owned contract', () => { - beforeEach(async () => { - forwarder1 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, link.address, operator.address, '0x') - }) - - it('does not allow the contract to callback to owned contracts', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('whatever(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - let request = decodeRunRequest(receipt.logs?.[3]) - let responseParams = convertFufillParams(request, response) - // set the params to be the owned address - responseParams[2] = forwarder1.address - - //accept ownership - await operator - .connect(roles.defaultAccount) - .acceptOwnableContracts([forwarder1.address]) - - // do the thing - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...responseParams), - 'Cannot call owned contract', - ) - - await operator - .connect(roles.defaultAccount) - .transferOwnableContracts([forwarder1.address], link.address) - //reverts for a different reason after transferring ownership - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...responseParams), - 'Params do not match request ID', - ) - }) - }) - }) - }) - - describe('#fulfillOracleRequest2', () => { - describe('single word fulfils', () => { - const response = 'Hi mom!' - const responseTypes = ['bytes32'] - const responseValues = [toBytes32String(response)] - let maliciousRequester: Contract - let basicConsumer: Contract - let maliciousConsumer: Contract - let gasGuzzlingConsumer: Contract - let request: ReturnType - let request2: ReturnType - - describe('gas guzzling consumer [ @skip-coverage ]', () => { - beforeEach(async () => { - gasGuzzlingConsumer = await gasGuzzlingConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(gasGuzzlingConsumer.address, paymentAmount) - const tx = - await gasGuzzlingConsumer.gassyRequestEthereumPrice(paymentAmount) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 1) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - }) - - describe('cooperative consumer', () => { - beforeEach(async () => { - basicConsumer = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(basicConsumer.address, paymentAmount) - const currency = 'USD' - const tx = await basicConsumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - describe('when called by an unauthorized node', () => { - beforeEach(async () => { - assert.equal( - false, - await operator.isAuthorizedSender( - await roles.stranger.getAddress(), - ), - ) - }) - - it('raises an error', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - }) - - describe('when called by an authorized node', () => { - it('raises an error if the request ID does not exist', async () => { - request.requestId = ethers.utils.formatBytes32String('DOESNOTEXIST') - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - - it('sets the value on the requested contract', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const currentValue = await basicConsumer.currentPrice() - assert.equal( - response, - ethers.utils.parseBytes32String(currentValue), - ) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 3) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - - it('does not allow a request to be fulfilled twice', async () => { - const response2 = response + ' && Hello World!!' - const response2Values = [toBytes32String(response2)] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - - const currentValue = await basicConsumer.currentPrice() - assert.equal( - response, - ethers.utils.parseBytes32String(currentValue), - ) - }) - }) - - describe('when the oracle does not provide enough gas', () => { - // if updating this defaultGasLimit, be sure it matches with the - // defaultGasLimit specified in store/tx_manager.go - const defaultGasLimit = 500000 - - beforeEach(async () => { - bigNumEquals(0, await operator.withdrawable()) - }) - - it('does not allow the oracle to withdraw the payment', async () => { - await evmRevert( - operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - { - gasLimit: 70000, - }, - ), - ), - ) - - bigNumEquals(0, await operator.withdrawable()) - }) - - it(`${defaultGasLimit} is enough to pass the gas requirement`, async () => { - await operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params(request, responseTypes, responseValues, { - gasLimit: defaultGasLimit, - }), - ) - - bigNumEquals(request.payment, await operator.withdrawable()) - }) - }) - }) - - describe('with a malicious oracle', () => { - beforeEach(async () => { - // Setup Request 1 - basicConsumer = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(basicConsumer.address, paymentAmount) - const currency = 'USD' - const tx = await basicConsumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - // Setup Request 2 - await link.transfer(basicConsumer.address, paymentAmount) - const tx2 = await basicConsumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt2 = await tx2.wait() - request2 = decodeRunRequest(receipt2.logs?.[3]) - }) - - it('cannot spoof requestId in response data by moving calldata offset', async () => { - // Malicious Oracle Fulfill 2 - const functionSelector = '0x6ae0bc76' // fulfillOracleRequest2 - const dataOffset = - '0000000000000000000000000000000000000000000000000000000000000100' // Moved to 0x0124 - const fillerBytes = - '0000000000000000000000000000000000000000000000000000000000000000' - const expectedCalldataStart = request.requestId.slice(2) // 0xe4, this is checked against requestId in validateMultiWordResponseId - const dataSize = - '0000000000000000000000000000000000000000000000000000000000000040' // Two 32 byte blocks - const maliciousCalldataId = request2.requestId.slice(2) // 0x0124, set to a different requestId - const calldataData = - '1122334455667788991122334455667788991122334455667788991122334455' // some garbage value as response value - - const data = - functionSelector + - /** Input Params - slice off 0x prefix and pad with 0's */ - request.requestId.slice(2) + - request.payment.slice(2).padStart(64, '0') + - request.callbackAddr.slice(2).padStart(64, '0') + - request.callbackFunc.slice(2).padEnd(64, '0') + - request.expiration.slice(2).padStart(64, '0') + - // calldata "data" - dataOffset + - fillerBytes + - expectedCalldataStart + - dataSize + - maliciousCalldataId + - calldataData - - await evmRevert( - operator.connect(roles.oracleNode).signer.sendTransaction({ - to: operator.address, - data, - }), - ) - }) - }) - - describe('with a malicious requester', () => { - beforeEach(async () => { - const paymentAmount = toWei('1') - maliciousRequester = await maliciousRequesterFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousRequester.address, paymentAmount) - }) - - it('cannot cancel before the expiration', async () => { - await evmRevert( - maliciousRequester.maliciousRequestCancel( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ), - ) - }) - - it('cannot call functions on the LINK token through callbacks', async () => { - await evmRevert( - maliciousRequester.request( - specId, - link.address, - ethers.utils.toUtf8Bytes('transfer(address,uint256)'), - ), - ) - }) - - describe('requester lies about amount of LINK sent', () => { - it('the oracle uses the amount of LINK actually paid', async () => { - const tx = await maliciousRequester.maliciousPrice(specId) - const receipt = await tx.wait() - const req = decodeRunRequest(receipt.logs?.[3]) - - assert(toWei('1').eq(req.payment)) - }) - }) - }) - - describe('with a malicious consumer', () => { - const paymentAmount = toWei('1') - - beforeEach(async () => { - maliciousConsumer = await maliciousConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousConsumer.address, paymentAmount) - }) - - describe('fails during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('assertFail(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - const response2Values = [toBytes32String(response2)] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - }) - }) - - describe('calls selfdestruct', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - await maliciousConsumer.remove() - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - }) - - describe('request is canceled during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes( - 'cancelRequestOnFulfill(bytes32,bytes32)', - ), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - bigNumEquals(0, await link.balanceOf(maliciousConsumer.address)) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const mockBalance = await link.balanceOf(maliciousConsumer.address) - bigNumEquals(mockBalance, 0) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - const response2Values = [toBytes32String(response2)] - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - }) - }) - - describe('tries to steal funds from node', () => { - it('is not successful with call', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthCall(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with send', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthSend(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with transfer', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthTransfer(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - }) - - describe('when calling an owned contract', () => { - beforeEach(async () => { - forwarder1 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, link.address, operator.address, '0x') - }) - - it('does not allow the contract to callback to owned contracts', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('whatever(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - let request = decodeRunRequest(receipt.logs?.[3]) - let responseParams = convertFufillParams(request, response) - // set the params to be the owned address - responseParams[2] = forwarder1.address - - //accept ownership - await operator - .connect(roles.defaultAccount) - .acceptOwnableContracts([forwarder1.address]) - - // do the thing - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...responseParams), - 'Cannot call owned contract', - ) - - await operator - .connect(roles.defaultAccount) - .transferOwnableContracts([forwarder1.address], link.address) - //reverts for a different reason after transferring ownership - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...responseParams), - 'Params do not match request ID', - ) - }) - }) - }) - }) - - describe('multi word fulfils', () => { - describe('one bytes parameter', () => { - const response = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\ - Fusce euismod malesuada ligula, eget semper metus ultrices sit amet.' - const responseTypes = ['bytes'] - const responseValues = [stringToBytes(response)] - let maliciousRequester: Contract - let multiConsumer: Contract - let maliciousConsumer: Contract - let gasGuzzlingConsumer: Contract - let request: ReturnType - - describe('gas guzzling consumer [ @skip-coverage ]', () => { - beforeEach(async () => { - gasGuzzlingConsumer = await gasGuzzlingConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(gasGuzzlingConsumer.address, paymentAmount) - const tx = - await gasGuzzlingConsumer.gassyMultiWordRequest(paymentAmount) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 1) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - }) - - describe('cooperative consumer', () => { - beforeEach(async () => { - multiConsumer = await multiWordConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(multiConsumer.address, paymentAmount) - const currency = 'USD' - const tx = await multiConsumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it("matches the consumer's request ID", async () => { - const nonce = await multiConsumer.publicGetNextRequestCount() - const tx = await multiConsumer.requestEthereumPrice('USD', 0) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - const packed = ethers.utils.solidityPack( - ['address', 'uint256'], - [multiConsumer.address, nonce], - ) - const expected = ethers.utils.keccak256(packed) - assert.equal(expected, request.requestId) - }) - - describe('when called by an unauthorized node', () => { - beforeEach(async () => { - assert.equal( - false, - await operator.isAuthorizedSender( - await roles.stranger.getAddress(), - ), - ) - }) - - it('raises an error', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - }) - - describe('when called by an authorized node', () => { - it('raises an error if the request ID does not exist', async () => { - request.requestId = - ethers.utils.formatBytes32String('DOESNOTEXIST') - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - - it('sets the value on the requested contract', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const currentValue = await multiConsumer.currentPrice() - assert.equal(response, ethers.utils.toUtf8String(currentValue)) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 3) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - - it('does not allow a request to be fulfilled twice', async () => { - const response2 = response + ' && Hello World!!' - const response2Values = [stringToBytes(response2)] - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - - const currentValue = await multiConsumer.currentPrice() - assert.equal(response, ethers.utils.toUtf8String(currentValue)) - }) - }) - - describe('when the oracle does not provide enough gas', () => { - // if updating this defaultGasLimit, be sure it matches with the - // defaultGasLimit specified in store/tx_manager.go - const defaultGasLimit = 500000 - - beforeEach(async () => { - bigNumEquals(0, await operator.withdrawable()) - }) - - it('does not allow the oracle to withdraw the payment', async () => { - await evmRevert( - operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - { - gasLimit: 70000, - }, - ), - ), - ) - - bigNumEquals(0, await operator.withdrawable()) - }) - - it(`${defaultGasLimit} is enough to pass the gas requirement`, async () => { - await operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - { - gasLimit: defaultGasLimit, - }, - ), - ) - - bigNumEquals(request.payment, await operator.withdrawable()) - }) - }) - }) - - describe('with a malicious requester', () => { - beforeEach(async () => { - const paymentAmount = toWei('1') - maliciousRequester = await maliciousRequesterFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousRequester.address, paymentAmount) - }) - - it('cannot cancel before the expiration', async () => { - await evmRevert( - maliciousRequester.maliciousRequestCancel( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ), - ) - }) - - it('cannot call functions on the LINK token through callbacks', async () => { - await evmRevert( - maliciousRequester.request( - specId, - link.address, - ethers.utils.toUtf8Bytes('transfer(address,uint256)'), - ), - ) - }) - - describe('requester lies about amount of LINK sent', () => { - it('the oracle uses the amount of LINK actually paid', async () => { - const tx = await maliciousRequester.maliciousPrice(specId) - const receipt = await tx.wait() - const req = decodeRunRequest(receipt.logs?.[3]) - - assert(toWei('1').eq(req.payment)) - }) - }) - }) - - describe('with a malicious consumer', () => { - const paymentAmount = toWei('1') - - beforeEach(async () => { - maliciousConsumer = await maliciousMultiWordConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousConsumer.address, paymentAmount) - }) - - describe('fails during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('assertFail(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - const response2Values = [stringToBytes(response2)] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - }) - }) - - describe('calls selfdestruct', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - await maliciousConsumer.remove() - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - }) - - describe('request is canceled during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes( - 'cancelRequestOnFulfill(bytes32,bytes32)', - ), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - bigNumEquals(0, await link.balanceOf(maliciousConsumer.address)) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const mockBalance = await link.balanceOf( - maliciousConsumer.address, - ) - bigNumEquals(mockBalance, 0) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - const response2Values = [stringToBytes(response2)] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - }) - }) - - describe('tries to steal funds from node', () => { - it('is not successful with call', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthCall(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with send', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthSend(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with transfer', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthTransfer(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - }) - }) - }) - - describe('multiple bytes32 parameters', () => { - const response1 = '100' - const response2 = '7777777' - const response3 = 'forty two' - const responseTypes = ['bytes32', 'bytes32', 'bytes32'] - const responseValues = [ - toBytes32String(response1), - toBytes32String(response2), - toBytes32String(response3), - ] - let maliciousRequester: Contract - let multiConsumer: Contract - let maliciousConsumer: Contract - let gasGuzzlingConsumer: Contract - let request: ReturnType - - describe('gas guzzling consumer [ @skip-coverage ]', () => { - beforeEach(async () => { - gasGuzzlingConsumer = await gasGuzzlingConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(gasGuzzlingConsumer.address, paymentAmount) - const tx = - await gasGuzzlingConsumer.gassyMultiWordRequest(paymentAmount) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 1) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - }) - - describe('cooperative consumer', () => { - beforeEach(async () => { - multiConsumer = await multiWordConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(multiConsumer.address, paymentAmount) - const currency = 'USD' - const tx = await multiConsumer.requestMultipleParameters( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - describe('when called by an unauthorized node', () => { - beforeEach(async () => { - assert.equal( - false, - await operator.isAuthorizedSender( - await roles.stranger.getAddress(), - ), - ) - }) - - it('raises an error', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - }) - - describe('when called by an authorized node', () => { - it('raises an error if the request ID does not exist', async () => { - request.requestId = - ethers.utils.formatBytes32String('DOESNOTEXIST') - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - - it('sets the value on the requested contract', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const firstValue = await multiConsumer.usd() - const secondValue = await multiConsumer.eur() - const thirdValue = await multiConsumer.jpy() - assert.equal( - response1, - ethers.utils.parseBytes32String(firstValue), - ) - assert.equal( - response2, - ethers.utils.parseBytes32String(secondValue), - ) - assert.equal( - response3, - ethers.utils.parseBytes32String(thirdValue), - ) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 3) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - - it('does not allow a request to be fulfilled twice', async () => { - const response4 = response3 + ' && Hello World!!' - const repeatedResponseValues = [ - toBytes32String(response1), - toBytes32String(response2), - toBytes32String(response4), - ] - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - repeatedResponseValues, - ), - ), - ) - - const firstValue = await multiConsumer.usd() - const secondValue = await multiConsumer.eur() - const thirdValue = await multiConsumer.jpy() - assert.equal( - response1, - ethers.utils.parseBytes32String(firstValue), - ) - assert.equal( - response2, - ethers.utils.parseBytes32String(secondValue), - ) - assert.equal( - response3, - ethers.utils.parseBytes32String(thirdValue), - ) - }) - }) - - describe('when the oracle does not provide enough gas', () => { - // if updating this defaultGasLimit, be sure it matches with the - // defaultGasLimit specified in store/tx_manager.go - const defaultGasLimit = 500000 - - beforeEach(async () => { - bigNumEquals(0, await operator.withdrawable()) - }) - - it('does not allow the oracle to withdraw the payment', async () => { - await evmRevert( - operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - { - gasLimit: 70000, - }, - ), - ), - ) - - bigNumEquals(0, await operator.withdrawable()) - }) - - it(`${defaultGasLimit} is enough to pass the gas requirement`, async () => { - await operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - { - gasLimit: defaultGasLimit, - }, - ), - ) - - bigNumEquals(request.payment, await operator.withdrawable()) - }) - }) - }) - - describe('with a malicious requester', () => { - beforeEach(async () => { - const paymentAmount = toWei('1') - maliciousRequester = await maliciousRequesterFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousRequester.address, paymentAmount) - }) - - it('cannot cancel before the expiration', async () => { - await evmRevert( - maliciousRequester.maliciousRequestCancel( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ), - ) - }) - - it('cannot call functions on the LINK token through callbacks', async () => { - await evmRevert( - maliciousRequester.request( - specId, - link.address, - ethers.utils.toUtf8Bytes('transfer(address,uint256)'), - ), - ) - }) - - describe('requester lies about amount of LINK sent', () => { - it('the oracle uses the amount of LINK actually paid', async () => { - const tx = await maliciousRequester.maliciousPrice(specId) - const receipt = await tx.wait() - const req = decodeRunRequest(receipt.logs?.[3]) - - assert(toWei('1').eq(req.payment)) - }) - }) - }) - - describe('with a malicious consumer', () => { - const paymentAmount = toWei('1') - - beforeEach(async () => { - maliciousConsumer = await maliciousMultiWordConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousConsumer.address, paymentAmount) - }) - - describe('fails during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('assertFail(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response4 = 'hack the planet 102' - const repeatedResponseValues = [ - toBytes32String(response1), - toBytes32String(response2), - toBytes32String(response4), - ] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - repeatedResponseValues, - ), - ), - ) - }) - }) - - describe('calls selfdestruct', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - await maliciousConsumer.remove() - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - }) - - describe('request is canceled during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes( - 'cancelRequestOnFulfill(bytes32,bytes32)', - ), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - bigNumEquals(0, await link.balanceOf(maliciousConsumer.address)) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const mockBalance = await link.balanceOf( - maliciousConsumer.address, - ) - bigNumEquals(mockBalance, 0) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response4 = 'hack the planet 102' - const repeatedResponseValues = [ - toBytes32String(response1), - toBytes32String(response2), - toBytes32String(response4), - ] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - repeatedResponseValues, - ), - ), - ) - }) - }) - - describe('tries to steal funds from node', () => { - it('is not successful with call', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthCall(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with send', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthSend(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with transfer', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthTransfer(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - }) - }) - }) - }) - - describe('when the response data is too short', () => { - const response = 'Hi mom!' - const responseTypes = ['bytes32'] - const responseValues = [toBytes32String(response)] - - it('reverts', async () => { - let basicConsumer = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(basicConsumer.address, paymentAmount) - const tx = await basicConsumer.requestEthereumPrice( - 'USD', - paymentAmount, - ) - const receipt = await tx.wait() - let request = decodeRunRequest(receipt.logs?.[3]) - - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - fulfillParams[5] = '0x' // overwrite the data to be of lenght 0 - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams), - 'Response must be > 32 bytes', - ) - }) - }) - }) - - describe('#withdraw', () => { - describe('without reserving funds via oracleRequest', () => { - it('does nothing', async () => { - let balance = await link.balanceOf(await roles.oracleNode.getAddress()) - assert.equal(0, balance.toNumber()) - await evmRevert( - operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), toWei('1')), - ) - balance = await link.balanceOf(await roles.oracleNode.getAddress()) - assert.equal(0, balance.toNumber()) - }) - - describe('recovering funds that were mistakenly sent', () => { - const paid = 1 - beforeEach(async () => { - await link.transfer(operator.address, paid) - }) - - it('withdraws funds', async () => { - const operatorBalanceBefore = await link.balanceOf(operator.address) - const accountBalanceBefore = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.defaultAccount.getAddress(), paid) - - const operatorBalanceAfter = await link.balanceOf(operator.address) - const accountBalanceAfter = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - const accountDifference = - accountBalanceAfter.sub(accountBalanceBefore) - const operatorDifference = - operatorBalanceBefore.sub(operatorBalanceAfter) - - bigNumEquals(operatorDifference, paid) - bigNumEquals(accountDifference, paid) - }) - }) - }) - - describe('reserving funds via oracleRequest', () => { - const payment = 15 - let request: ReturnType - - beforeEach(async () => { - const requester = await roles.defaultAccount.getAddress() - const args = encodeOracleRequest( - specId, - requester, - fHash, - 0, - constants.HashZero, - ) - const tx = await link.transferAndCall(operator.address, payment, args) - const receipt = await tx.wait() - assert.equal(3, receipt.logs?.length) - request = decodeRunRequest(receipt.logs?.[2]) - }) - - describe('but not freeing funds w fulfillOracleRequest', () => { - it('does not transfer funds', async () => { - await evmRevert( - operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), payment), - ) - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - assert.equal(0, balance.toNumber()) - }) - - describe('recovering funds that were mistakenly sent', () => { - const paid = 1 - beforeEach(async () => { - await link.transfer(operator.address, paid) - }) - - it('withdraws funds', async () => { - const operatorBalanceBefore = await link.balanceOf(operator.address) - const accountBalanceBefore = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.defaultAccount.getAddress(), paid) - - const operatorBalanceAfter = await link.balanceOf(operator.address) - const accountBalanceAfter = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - const accountDifference = - accountBalanceAfter.sub(accountBalanceBefore) - const operatorDifference = - operatorBalanceBefore.sub(operatorBalanceAfter) - - bigNumEquals(operatorDifference, paid) - bigNumEquals(accountDifference, paid) - }) - }) - }) - - describe('and freeing funds', () => { - beforeEach(async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest( - ...convertFufillParams(request, 'Hello World!'), - ) - }) - - it('does not allow input greater than the balance', async () => { - const originalOracleBalance = await link.balanceOf(operator.address) - const originalStrangerBalance = await link.balanceOf( - await roles.stranger.getAddress(), - ) - const withdrawalAmount = payment + 1 - - assert.isAbove(withdrawalAmount, originalOracleBalance.toNumber()) - await evmRevert( - operator - .connect(roles.defaultAccount) - .withdraw(await roles.stranger.getAddress(), withdrawalAmount), - ) - - const newOracleBalance = await link.balanceOf(operator.address) - const newStrangerBalance = await link.balanceOf( - await roles.stranger.getAddress(), - ) - - assert.equal( - originalOracleBalance.toNumber(), - newOracleBalance.toNumber(), - ) - assert.equal( - originalStrangerBalance.toNumber(), - newStrangerBalance.toNumber(), - ) - }) - - it('allows transfer of partial balance by owner to specified address', async () => { - const partialAmount = 6 - const difference = payment - partialAmount - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.stranger.getAddress(), partialAmount) - const strangerBalance = await link.balanceOf( - await roles.stranger.getAddress(), - ) - const oracleBalance = await link.balanceOf(operator.address) - assert.equal(partialAmount, strangerBalance.toNumber()) - assert.equal(difference, oracleBalance.toNumber()) - }) - - it('allows transfer of entire balance by owner to specified address', async () => { - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.stranger.getAddress(), payment) - const balance = await link.balanceOf( - await roles.stranger.getAddress(), - ) - assert.equal(payment, balance.toNumber()) - }) - - it('does not allow a transfer of funds by non-owner', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .withdraw(await roles.stranger.getAddress(), payment), - ) - const balance = await link.balanceOf( - await roles.stranger.getAddress(), - ) - assert.isTrue(ethers.constants.Zero.eq(balance)) - }) - - describe('recovering funds that were mistakenly sent', () => { - const paid = 1 - beforeEach(async () => { - await link.transfer(operator.address, paid) - }) - - it('withdraws funds', async () => { - const operatorBalanceBefore = await link.balanceOf(operator.address) - const accountBalanceBefore = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.defaultAccount.getAddress(), paid) - - const operatorBalanceAfter = await link.balanceOf(operator.address) - const accountBalanceAfter = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - const accountDifference = - accountBalanceAfter.sub(accountBalanceBefore) - const operatorDifference = - operatorBalanceBefore.sub(operatorBalanceAfter) - - bigNumEquals(operatorDifference, paid) - bigNumEquals(accountDifference, paid) - }) - }) - }) - }) - }) - - describe('#withdrawable', () => { - let request: ReturnType - const amount = toWei('1') - - beforeEach(async () => { - const requester = await roles.defaultAccount.getAddress() - const args = encodeOracleRequest( - specId, - requester, - fHash, - 0, - constants.HashZero, - ) - const tx = await link.transferAndCall(operator.address, amount, args) - const receipt = await tx.wait() - assert.equal(3, receipt.logs?.length) - request = decodeRunRequest(receipt.logs?.[2]) - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, 'Hello World!')) - }) - - it('returns the correct value', async () => { - const withdrawAmount = await operator.withdrawable() - bigNumEquals(withdrawAmount, request.payment) - }) - - describe('funds that were mistakenly sent', () => { - const paid = 1 - beforeEach(async () => { - await link.transfer(operator.address, paid) - }) - - it('returns the correct value', async () => { - const withdrawAmount = await operator.withdrawable() - - const expectedAmount = amount.add(paid) - bigNumEquals(withdrawAmount, expectedAmount) - }) - }) - }) - - describe('#ownerTransferAndCall', () => { - let operator2: Contract - let args: string - let to: string - const startingBalance = 1000 - const payment = 20 - - beforeEach(async () => { - operator2 = await operatorFactory - .connect(roles.oracleNode2) - .deploy(link.address, await roles.oracleNode2.getAddress()) - to = operator2.address - args = encodeOracleRequest( - specId, - operator.address, - operatorFactory.interface.getSighash('fulfillOracleRequest'), - 1, - constants.HashZero, - ) - }) - - describe('when called by a non-owner', () => { - it('reverts with owner error message', async () => { - await link.transfer(operator.address, startingBalance) - await evmRevert( - operator - .connect(roles.stranger) - .ownerTransferAndCall(to, payment, args), - 'Only callable by owner', - ) - }) - }) - - describe('when called by the owner', () => { - beforeEach(async () => { - await link.transfer(operator.address, startingBalance) - }) - - describe('without sufficient funds in contract', () => { - it('reverts with funds message', async () => { - const tooMuch = startingBalance * 2 - await evmRevert( - operator - .connect(roles.defaultAccount) - .ownerTransferAndCall(to, tooMuch, args), - 'Amount requested is greater than withdrawable balance', - ) - }) - }) - - describe('with sufficient funds', () => { - let tx: ContractTransaction - let receipt: ContractReceipt - let requesterBalanceBefore: BigNumber - let requesterBalanceAfter: BigNumber - let receiverBalanceBefore: BigNumber - let receiverBalanceAfter: BigNumber - - before(async () => { - requesterBalanceBefore = await link.balanceOf(operator.address) - receiverBalanceBefore = await link.balanceOf(operator2.address) - tx = await operator - .connect(roles.defaultAccount) - .ownerTransferAndCall(to, payment, args) - receipt = await tx.wait() - requesterBalanceAfter = await link.balanceOf(operator.address) - receiverBalanceAfter = await link.balanceOf(operator2.address) - }) - - it('emits an event', async () => { - assert.equal(3, receipt.logs?.length) - const transferLog = await getLog(tx, 1) - const parsedLog = link.interface.parseLog({ - data: transferLog.data, - topics: transferLog.topics, - }) - await expect(parsedLog.name).to.equal('Transfer') - }) - - it('transfers the tokens', async () => { - bigNumEquals( - requesterBalanceBefore.sub(requesterBalanceAfter), - payment, - ) - bigNumEquals(receiverBalanceAfter.sub(receiverBalanceBefore), payment) - }) - }) - }) - }) - - describe('#cancelOracleRequestByRequester', () => { - const nonce = 17 - - describe('with no pending requests', () => { - it('fails', async () => { - const fakeRequest: RunRequest = { - requestId: ethers.utils.formatBytes32String('1337'), - payment: '0', - callbackFunc: - getterSetterFactory.interface.getSighash('requestedBytes32'), - expiration: '999999999999', - - callbackAddr: '', - data: Buffer.from(''), - dataVersion: 0, - specId: '', - requester: '', - topic: '', - } - await increaseTime5Minutes(ethers.provider) - - await evmRevert( - operator - .connect(roles.stranger) - .cancelOracleRequestByRequester( - ...convertCancelByRequesterParams(fakeRequest, nonce), - ), - ) - }) - }) - - describe('with a pending request', () => { - const startingBalance = 100 - let request: ReturnType - let receipt: providers.TransactionReceipt - - beforeEach(async () => { - const requestAmount = 20 - - await link.transfer(await roles.consumer.getAddress(), startingBalance) - - const args = encodeOracleRequest( - specId, - await roles.consumer.getAddress(), - fHash, - nonce, - constants.HashZero, - ) - const tx = await link - .connect(roles.consumer) - .transferAndCall(operator.address, requestAmount, args) - receipt = await tx.wait() - - assert.equal(3, receipt.logs?.length) - request = decodeRunRequest(receipt.logs?.[2]) - - // pre conditions - const oracleBalance = await link.balanceOf(operator.address) - bigNumEquals(request.payment, oracleBalance) - - const consumerAmount = await link.balanceOf( - await roles.consumer.getAddress(), - ) - assert.equal( - startingBalance - Number(request.payment), - consumerAmount.toNumber(), - ) - }) - - describe('from a stranger', () => { - it('fails', async () => { - await evmRevert( - operator - .connect(roles.consumer) - .cancelOracleRequestByRequester( - ...convertCancelByRequesterParams(request, nonce), - ), - ) - }) - }) - - describe('from the requester', () => { - it('refunds the correct amount', async () => { - await increaseTime5Minutes(ethers.provider) - await operator - .connect(roles.consumer) - .cancelOracleRequestByRequester( - ...convertCancelByRequesterParams(request, nonce), - ) - const balance = await link.balanceOf( - await roles.consumer.getAddress(), - ) - - assert.equal(startingBalance, balance.toNumber()) // 100 - }) - - it('triggers a cancellation event', async () => { - await increaseTime5Minutes(ethers.provider) - const tx = await operator - .connect(roles.consumer) - .cancelOracleRequestByRequester( - ...convertCancelByRequesterParams(request, nonce), - ) - const receipt = await tx.wait() - - assert.equal(receipt.logs?.length, 2) - assert.equal(request.requestId, receipt.logs?.[0].topics[1]) - }) - - it('fails when called twice', async () => { - await increaseTime5Minutes(ethers.provider) - await operator - .connect(roles.consumer) - .cancelOracleRequestByRequester( - ...convertCancelByRequesterParams(request, nonce), - ) - - await evmRevert( - operator - .connect(roles.consumer) - .cancelOracleRequestByRequester(...convertCancelParams(request)), - ) - }) - }) - }) - }) - - describe('#cancelOracleRequest', () => { - describe('with no pending requests', () => { - it('fails', async () => { - const fakeRequest: RunRequest = { - requestId: ethers.utils.formatBytes32String('1337'), - payment: '0', - callbackFunc: - getterSetterFactory.interface.getSighash('requestedBytes32'), - expiration: '999999999999', - - callbackAddr: '', - data: Buffer.from(''), - dataVersion: 0, - specId: '', - requester: '', - topic: '', - } - await increaseTime5Minutes(ethers.provider) - - await evmRevert( - operator - .connect(roles.stranger) - .cancelOracleRequest(...convertCancelParams(fakeRequest)), - ) - }) - }) - - describe('with a pending request', () => { - const startingBalance = 100 - let request: ReturnType - let receipt: providers.TransactionReceipt - - beforeEach(async () => { - const requestAmount = 20 - - await link.transfer(await roles.consumer.getAddress(), startingBalance) - - const args = encodeOracleRequest( - specId, - await roles.consumer.getAddress(), - fHash, - 1, - constants.HashZero, - ) - const tx = await link - .connect(roles.consumer) - .transferAndCall(operator.address, requestAmount, args) - receipt = await tx.wait() - - assert.equal(3, receipt.logs?.length) - request = decodeRunRequest(receipt.logs?.[2]) - }) - - it('has correct initial balances', async () => { - const oracleBalance = await link.balanceOf(operator.address) - bigNumEquals(request.payment, oracleBalance) - - const consumerAmount = await link.balanceOf( - await roles.consumer.getAddress(), - ) - assert.equal( - startingBalance - Number(request.payment), - consumerAmount.toNumber(), - ) - }) - - describe('from a stranger', () => { - it('fails', async () => { - await evmRevert( - operator - .connect(roles.consumer) - .cancelOracleRequest(...convertCancelParams(request)), - ) - }) - }) - - describe('from the requester', () => { - it('refunds the correct amount', async () => { - await increaseTime5Minutes(ethers.provider) - await operator - .connect(roles.consumer) - .cancelOracleRequest(...convertCancelParams(request)) - const balance = await link.balanceOf( - await roles.consumer.getAddress(), - ) - - assert.equal(startingBalance, balance.toNumber()) // 100 - }) - - it('triggers a cancellation event', async () => { - await increaseTime5Minutes(ethers.provider) - const tx = await operator - .connect(roles.consumer) - .cancelOracleRequest(...convertCancelParams(request)) - const receipt = await tx.wait() - - assert.equal(receipt.logs?.length, 2) - assert.equal(request.requestId, receipt.logs?.[0].topics[1]) - }) - - it('fails when called twice', async () => { - await increaseTime5Minutes(ethers.provider) - await operator - .connect(roles.consumer) - .cancelOracleRequest(...convertCancelParams(request)) - - await evmRevert( - operator - .connect(roles.consumer) - .cancelOracleRequest(...convertCancelParams(request)), - ) - }) - }) - }) - }) - - describe('#ownerForward', () => { - let bytes: string - let payload: string - let mock: Contract - - beforeEach(async () => { - bytes = ethers.utils.hexlify(ethers.utils.randomBytes(100)) - payload = getterSetterFactory.interface.encodeFunctionData( - getterSetterFactory.interface.getFunction('setBytes'), - [bytes], - ) - mock = await getterSetterFactory.connect(roles.defaultAccount).deploy() - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - operator.connect(roles.stranger).ownerForward(mock.address, payload), - ) - }) - }) - - describe('when called by owner', () => { - describe('when attempting to forward to the link token', () => { - it('reverts', async () => { - const sighash = linkTokenFactory.interface.getSighash('name') - await evmRevert( - operator - .connect(roles.defaultAccount) - .ownerForward(link.address, sighash), - 'Cannot call to LINK', - ) - }) - }) - - describe('when forwarding to any other address', () => { - it('forwards the data', async () => { - const tx = await operator - .connect(roles.defaultAccount) - .ownerForward(mock.address, payload) - await tx.wait() - assert.equal(await mock.getBytes(), bytes) - }) - - it('reverts when sending to a non-contract address', async () => { - await evmRevert( - operator - .connect(roles.defaultAccount) - .ownerForward(zeroAddress, payload), - 'Must forward to a contract', - ) - }) - - it('perceives the message is sent by the Operator', async () => { - const tx = await operator - .connect(roles.defaultAccount) - .ownerForward(mock.address, payload) - const receipt = await tx.wait() - const log: any = receipt.logs?.[0] - const logData = mock.interface.decodeEventLog( - mock.interface.getEvent('SetBytes'), - log.data, - log.topics, - ) - assert.equal(ethers.utils.getAddress(logData.from), operator.address) - }) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/OperatorFactory.test.ts b/contracts/test/v0.7/OperatorFactory.test.ts deleted file mode 100644 index d2a24600e23..00000000000 --- a/contracts/test/v0.7/OperatorFactory.test.ts +++ /dev/null @@ -1,293 +0,0 @@ -import { ethers } from 'hardhat' -import { evmWordToAddress, publicAbi } from '../test-helpers/helpers' -import { assert } from 'chai' -import { Contract, ContractFactory, ContractReceipt } from 'ethers' -import { getUsers, Roles } from '../test-helpers/setup' - -let linkTokenFactory: ContractFactory -let operatorGeneratorFactory: ContractFactory -let operatorFactory: ContractFactory -let forwarderFactory: ContractFactory - -let roles: Roles - -before(async () => { - const users = await getUsers() - - roles = users.roles - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) - operatorGeneratorFactory = await ethers.getContractFactory( - 'src/v0.7/OperatorFactory.sol:OperatorFactory', - roles.defaultAccount, - ) - operatorFactory = await ethers.getContractFactory( - 'src/v0.7/Operator.sol:Operator', - roles.defaultAccount, - ) - forwarderFactory = await ethers.getContractFactory( - 'src/v0.7/AuthorizedForwarder.sol:AuthorizedForwarder', - roles.defaultAccount, - ) -}) - -describe('OperatorFactory', () => { - let link: Contract - let operatorGenerator: Contract - let operator: Contract - let forwarder: Contract - let receipt: ContractReceipt - let emittedOperator: string - let emittedForwarder: string - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - operatorGenerator = await operatorGeneratorFactory - .connect(roles.defaultAccount) - .deploy(link.address) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(operatorGenerator, [ - 'created', - 'deployNewOperator', - 'deployNewOperatorAndForwarder', - 'deployNewForwarder', - 'deployNewForwarderAndTransferOwnership', - 'getChainlinkToken', - 'typeAndVersion', - ]) - }) - - describe('#typeAndVersion', () => { - it('describes the authorized forwarder', async () => { - assert.equal( - await operatorGenerator.typeAndVersion(), - 'OperatorFactory 1.0.0', - ) - }) - }) - - describe('#deployNewOperator', () => { - beforeEach(async () => { - const tx = await operatorGenerator - .connect(roles.oracleNode) - .deployNewOperator() - - receipt = await tx.wait() - emittedOperator = evmWordToAddress(receipt.logs?.[0].topics?.[1]) - }) - - it('emits an event', async () => { - assert.equal(receipt?.events?.[0]?.event, 'OperatorCreated') - assert.equal(emittedOperator, receipt.events?.[0].args?.[0]) - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[1], - ) - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[2], - ) - }) - - it('sets the correct owner', async () => { - operator = await operatorFactory - .connect(roles.defaultAccount) - .attach(emittedOperator) - const ownerString = await operator.owner() - assert.equal(ownerString, await roles.oracleNode.getAddress()) - }) - - it('records that it deployed that address', async () => { - assert.isTrue(await operatorGenerator.created(emittedOperator)) - }) - }) - - describe('#deployNewOperatorAndForwarder', () => { - beforeEach(async () => { - const tx = await operatorGenerator - .connect(roles.oracleNode) - .deployNewOperatorAndForwarder() - - receipt = await tx.wait() - emittedOperator = evmWordToAddress(receipt.logs?.[0].topics?.[1]) - emittedForwarder = evmWordToAddress(receipt.logs?.[3].topics?.[1]) - }) - - it('emits an event recording that the operator was deployed', async () => { - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[1], - ) - assert.equal(receipt?.events?.[0]?.event, 'OperatorCreated') - assert.equal(receipt?.events?.[0]?.args?.[0], emittedOperator) - assert.equal( - receipt?.events?.[0]?.args?.[1], - await roles.oracleNode.getAddress(), - ) - assert.equal( - receipt?.events?.[0]?.args?.[2], - await roles.oracleNode.getAddress(), - ) - }) - - it('proposes the transfer of the forwarder to the operator', async () => { - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[1], - ) - assert.equal( - receipt?.events?.[1]?.topics?.[0], - '0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278', //OwnershipTransferRequested(address,address) - ) - assert.equal( - evmWordToAddress(receipt?.events?.[1]?.topics?.[1]), - operatorGenerator.address, - ) - assert.equal( - evmWordToAddress(receipt?.events?.[1]?.topics?.[2]), - emittedOperator, - ) - - assert.equal( - receipt?.events?.[2]?.topics?.[0], - '0x4e1e878dc28d5f040db5969163ff1acd75c44c3f655da2dde9c70bbd8e56dc7e', //OwnershipTransferRequestedWithMessage(address,address,bytes) - ) - assert.equal( - evmWordToAddress(receipt?.events?.[2]?.topics?.[1]), - operatorGenerator.address, - ) - assert.equal( - evmWordToAddress(receipt?.events?.[2]?.topics?.[2]), - emittedOperator, - ) - }) - - it('emits an event recording that the forwarder was deployed', async () => { - assert.equal(receipt?.events?.[3]?.event, 'AuthorizedForwarderCreated') - assert.equal(receipt?.events?.[3]?.args?.[0], emittedForwarder) - assert.equal(receipt?.events?.[3]?.args?.[1], operatorGenerator.address) - assert.equal( - receipt?.events?.[3]?.args?.[2], - await roles.oracleNode.getAddress(), - ) - }) - - it('sets the correct owner on the operator', async () => { - operator = await operatorFactory - .connect(roles.defaultAccount) - .attach(receipt?.events?.[0]?.args?.[0]) - assert.equal(await roles.oracleNode.getAddress(), await operator.owner()) - }) - - it('sets the operator as the owner of the forwarder', async () => { - forwarder = await forwarderFactory - .connect(roles.defaultAccount) - .attach(emittedForwarder) - assert.equal(operatorGenerator.address, await forwarder.owner()) - }) - - it('records that it deployed that address', async () => { - assert.isTrue(await operatorGenerator.created(emittedOperator)) - assert.isTrue(await operatorGenerator.created(emittedForwarder)) - }) - }) - - describe('#deployNewForwarder', () => { - beforeEach(async () => { - const tx = await operatorGenerator - .connect(roles.oracleNode) - .deployNewForwarder() - - receipt = await tx.wait() - emittedForwarder = receipt.events?.[0].args?.[0] - }) - - it('emits an event', async () => { - assert.equal(receipt?.events?.[0]?.event, 'AuthorizedForwarderCreated') - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[1], - ) // owner - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[2], - ) // sender - }) - - it('sets the caller as the owner', async () => { - forwarder = await forwarderFactory - .connect(roles.defaultAccount) - .attach(emittedForwarder) - const ownerString = await forwarder.owner() - assert.equal(ownerString, await roles.oracleNode.getAddress()) - }) - - it('records that it deployed that address', async () => { - assert.isTrue(await operatorGenerator.created(emittedForwarder)) - }) - }) - - describe('#deployNewForwarderAndTransferOwnership', () => { - const message = '0x42' - - beforeEach(async () => { - const tx = await operatorGenerator - .connect(roles.oracleNode) - .deployNewForwarderAndTransferOwnership( - await roles.stranger.getAddress(), - message, - ) - receipt = await tx.wait() - - emittedForwarder = evmWordToAddress(receipt.logs?.[2].topics?.[1]) - }) - - it('emits an event', async () => { - assert.equal(receipt?.events?.[2]?.event, 'AuthorizedForwarderCreated') - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[2].args?.[1], - ) // owner - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[2].args?.[2], - ) // sender - }) - - it('sets the caller as the owner', async () => { - forwarder = await forwarderFactory - .connect(roles.defaultAccount) - .attach(emittedForwarder) - const ownerString = await forwarder.owner() - assert.equal(ownerString, await roles.oracleNode.getAddress()) - }) - - it('proposes a transfer to the recipient', async () => { - const emittedOwner = evmWordToAddress(receipt.logs?.[0].topics?.[1]) - assert.equal(emittedOwner, await roles.oracleNode.getAddress()) - const emittedRecipient = evmWordToAddress(receipt.logs?.[0].topics?.[2]) - assert.equal(emittedRecipient, await roles.stranger.getAddress()) - }) - - it('proposes a transfer to the recipient with the specified message', async () => { - const emittedOwner = evmWordToAddress(receipt.logs?.[1].topics?.[1]) - assert.equal(emittedOwner, await roles.oracleNode.getAddress()) - const emittedRecipient = evmWordToAddress(receipt.logs?.[1].topics?.[2]) - assert.equal(emittedRecipient, await roles.stranger.getAddress()) - - const encodedMessage = ethers.utils.defaultAbiCoder.encode( - ['bytes'], - [message], - ) - assert.equal(receipt?.logs?.[1]?.data, encodedMessage) - }) - - it('records that it deployed that address', async () => { - assert.isTrue(await operatorGenerator.created(emittedForwarder)) - }) - }) -}) diff --git a/contracts/test/v0.7/StalenessFlaggingValidator.test.ts b/contracts/test/v0.7/StalenessFlaggingValidator.test.ts deleted file mode 100644 index 8a5c4b67632..00000000000 --- a/contracts/test/v0.7/StalenessFlaggingValidator.test.ts +++ /dev/null @@ -1,632 +0,0 @@ -import { ethers } from 'hardhat' -import { - evmWordToAddress, - getLog, - getLogs, - numToBytes32, - publicAbi, -} from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { BigNumber, Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { evmRevert } from '../test-helpers/matchers' - -let personas: Personas -let validatorFactory: ContractFactory -let flagsFactory: ContractFactory -let acFactory: ContractFactory -let aggregatorFactory: ContractFactory - -before(async () => { - personas = (await getUsers()).personas - - validatorFactory = await ethers.getContractFactory( - 'src/v0.7/dev/StalenessFlaggingValidator.sol:StalenessFlaggingValidator', - personas.Carol, - ) - flagsFactory = await ethers.getContractFactory( - 'src/v0.6/Flags.sol:Flags', - personas.Carol, - ) - acFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - personas.Carol, - ) - aggregatorFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', - personas.Carol, - ) -}) - -describe('StalenessFlaggingValidator', () => { - let validator: Contract - let flags: Contract - let ac: Contract - - const flaggingThreshold1 = 10000 - const flaggingThreshold2 = 20000 - - beforeEach(async () => { - ac = await acFactory.connect(personas.Carol).deploy() - flags = await flagsFactory.connect(personas.Carol).deploy(ac.address) - validator = await validatorFactory - .connect(personas.Carol) - .deploy(flags.address) - - await ac.connect(personas.Carol).addAccess(validator.address) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(validator, [ - 'update', - 'check', - 'setThresholds', - 'setFlagsAddress', - 'threshold', - 'flags', - // Upkeep methods: - 'checkUpkeep', - 'performUpkeep', - // Owned methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('sets the arguments passed in', async () => { - assert.equal(await validator.flags(), flags.address) - }) - - it('sets the owner', async () => { - assert.equal(await validator.owner(), await personas.Carol.getAddress()) - }) - }) - - describe('#setFlagsAddress', () => { - const newFlagsAddress = '0x0123456789012345678901234567890123456789' - - it('changes the flags address', async () => { - assert.equal(flags.address, await validator.flags()) - - await validator.connect(personas.Carol).setFlagsAddress(newFlagsAddress) - - assert.equal(newFlagsAddress, await validator.flags()) - }) - - it('emits a log event only when actually changed', async () => { - const tx = await validator - .connect(personas.Carol) - .setFlagsAddress(newFlagsAddress) - await expect(tx) - .to.emit(validator, 'FlagsAddressUpdated') - .withArgs(flags.address, newFlagsAddress) - - const sameChangeTx = await validator - .connect(personas.Carol) - .setFlagsAddress(newFlagsAddress) - - await expect(sameChangeTx).to.not.emit(validator, 'FlagsAddressUpdated') - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - validator.connect(personas.Neil).setFlagsAddress(newFlagsAddress), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#setThresholds', () => { - let agg1: Contract - let agg2: Contract - let aggregators: Array - let thresholds: Array - - beforeEach(async () => { - const decimals = 8 - const initialAnswer = 10000000000 - agg1 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - agg2 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - }) - - describe('failure', () => { - beforeEach(() => { - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1] - }) - - it('reverts when called by a non-owner', async () => { - await evmRevert( - validator - .connect(personas.Neil) - .setThresholds(aggregators, thresholds), - 'Only callable by owner', - ) - }) - - it('reverts when passed uneven arrays', async () => { - await evmRevert( - validator - .connect(personas.Carol) - .setThresholds(aggregators, thresholds), - 'Different sized arrays', - ) - }) - }) - - describe('success', () => { - let tx: any - - beforeEach(() => { - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1, flaggingThreshold2] - }) - - describe('when called with 2 new thresholds', () => { - beforeEach(async () => { - tx = await validator - .connect(personas.Carol) - .setThresholds(aggregators, thresholds) - }) - - it('sets the thresholds', async () => { - const first = await validator.threshold(agg1.address) - const second = await validator.threshold(agg2.address) - assert.equal(first.toString(), flaggingThreshold1.toString()) - assert.equal(second.toString(), flaggingThreshold2.toString()) - }) - - it('emits events', async () => { - const firstEvent = await getLog(tx, 0) - assert.equal(evmWordToAddress(firstEvent.topics[1]), agg1.address) - assert.equal(firstEvent.topics[3], numToBytes32(flaggingThreshold1)) - const secondEvent = await getLog(tx, 1) - assert.equal(evmWordToAddress(secondEvent.topics[1]), agg2.address) - assert.equal(secondEvent.topics[3], numToBytes32(flaggingThreshold2)) - }) - }) - - describe('when called with 2, but 1 has not changed', () => { - it('emits only 1 event', async () => { - tx = await validator - .connect(personas.Carol) - .setThresholds(aggregators, thresholds) - - const newThreshold = flaggingThreshold2 + 1 - tx = await validator - .connect(personas.Carol) - .setThresholds(aggregators, [flaggingThreshold1, newThreshold]) - const logs = await getLogs(tx) - assert.equal(logs.length, 1) - const log = logs[0] - assert.equal(evmWordToAddress(log.topics[1]), agg2.address) - assert.equal(log.topics[2], numToBytes32(flaggingThreshold2)) - assert.equal(log.topics[3], numToBytes32(newThreshold)) - }) - }) - }) - }) - - describe('#check', () => { - let agg1: Contract - let agg2: Contract - let aggregators: Array - let thresholds: Array - const decimals = 8 - const initialAnswer = 10000000000 - beforeEach(async () => { - agg1 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - agg2 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1, flaggingThreshold2] - await validator.setThresholds(aggregators, thresholds) - }) - - describe('when neither are stale', () => { - it('returns an empty array', async () => { - const response = await validator.check(aggregators) - assert.equal(response.length, 0) - }) - }) - - describe('when threshold is not set in the validator', () => { - it('returns an empty array', async () => { - const agg3 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - const response = await validator.check([agg3.address]) - assert.equal(response.length, 0) - }) - }) - - describe('when one of the aggregators is stale', () => { - it('returns an array with one stale aggregator', async () => { - const currentTimestamp = await agg1.latestTimestamp() - const staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - const response = await validator.check(aggregators) - - assert.equal(response.length, 1) - assert.equal(response[0], agg1.address) - }) - }) - - describe('When both aggregators are stale', () => { - it('returns an array with both aggregators', async () => { - let currentTimestamp = await agg1.latestTimestamp() - let staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - currentTimestamp = await agg2.latestTimestamp() - staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold2 + 1), - ) - await agg2.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const response = await validator.check(aggregators) - - assert.equal(response.length, 2) - assert.equal(response[0], agg1.address) - assert.equal(response[1], agg2.address) - }) - }) - }) - - describe('#update', () => { - let agg1: Contract - let agg2: Contract - let aggregators: Array - let thresholds: Array - const decimals = 8 - const initialAnswer = 10000000000 - beforeEach(async () => { - agg1 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - agg2 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1, flaggingThreshold2] - await validator.setThresholds(aggregators, thresholds) - }) - - describe('when neither are stale', () => { - it('does not raise a flag', async () => { - const tx = await validator.update(aggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - - describe('when threshold is not set in the validator', () => { - it('does not raise a flag', async () => { - const agg3 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - const tx = await validator.update([agg3.address]) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - - describe('when one is stale', () => { - it('raises a flag for that aggregator', async () => { - const currentTimestamp = await agg1.latestTimestamp() - const staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const tx = await validator.update(aggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 1) - assert.equal(evmWordToAddress(logs[0].topics[1]), agg1.address) - }) - }) - - describe('when both are stale', () => { - it('raises 2 flags, one for each aggregator', async () => { - let currentTimestamp = await agg1.latestTimestamp() - let staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - currentTimestamp = await agg2.latestTimestamp() - staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold2 + 1), - ) - await agg2.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const tx = await validator.update(aggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 2) - assert.equal(evmWordToAddress(logs[0].topics[1]), agg1.address) - assert.equal(evmWordToAddress(logs[1].topics[1]), agg2.address) - }) - }) - }) - - describe('#checkUpkeep', () => { - let agg1: Contract - let agg2: Contract - let aggregators: Array - let thresholds: Array - const decimals = 8 - const initialAnswer = 10000000000 - beforeEach(async () => { - agg1 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - agg2 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1, flaggingThreshold2] - await validator.setThresholds(aggregators, thresholds) - }) - - describe('when neither are stale', () => { - it('returns false and an empty array', async () => { - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const response = await validator.checkUpkeep(bytesData) - - assert.equal(response[0], false) - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - assert.equal(decodedResponse[0].length, 0) - }) - }) - - describe('when threshold is not set in the validator', () => { - it('returns flase and an empty array', async () => { - const agg3 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [[agg3.address]], - ) - const response = await validator.checkUpkeep(bytesData) - - assert.equal(response[0], false) - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - assert.equal(decodedResponse[0].length, 0) - }) - }) - - describe('when one of the aggregators is stale', () => { - it('returns true with an array with one stale aggregator', async () => { - const currentTimestamp = await agg1.latestTimestamp() - const staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const response = await validator.checkUpkeep(bytesData) - - assert.equal(response[0], true) - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - const decodedArray = decodedResponse[0] - assert.equal(decodedArray.length, 1) - assert.equal(decodedArray[0], agg1.address) - }) - }) - - describe('When both aggregators are stale', () => { - it('returns true with an array with both aggregators', async () => { - let currentTimestamp = await agg1.latestTimestamp() - let staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - currentTimestamp = await agg2.latestTimestamp() - staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold2 + 1), - ) - await agg2.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const response = await validator.checkUpkeep(bytesData) - - assert.equal(response[0], true) - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - const decodedArray = decodedResponse[0] - assert.equal(decodedArray.length, 2) - assert.equal(decodedArray[0], agg1.address) - assert.equal(decodedArray[1], agg2.address) - }) - }) - }) - - describe('#performUpkeep', () => { - let agg1: Contract - let agg2: Contract - let aggregators: Array - let thresholds: Array - const decimals = 8 - const initialAnswer = 10000000000 - beforeEach(async () => { - agg1 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - agg2 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1, flaggingThreshold2] - await validator.setThresholds(aggregators, thresholds) - }) - - describe('when neither are stale', () => { - it('does not raise a flag', async () => { - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const tx = await validator.performUpkeep(bytesData) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - - describe('when threshold is not set in the validator', () => { - it('does not raise a flag', async () => { - const agg3 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [[agg3.address]], - ) - const tx = await validator.performUpkeep(bytesData) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - - describe('when one is stale', () => { - it('raises a flag for that aggregator', async () => { - const currentTimestamp = await agg1.latestTimestamp() - const staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const tx = await validator.performUpkeep(bytesData) - const logs = await getLogs(tx) - assert.equal(logs.length, 1) - assert.equal(evmWordToAddress(logs[0].topics[1]), agg1.address) - }) - }) - - describe('when both are stale', () => { - it('raises 2 flags, one for each aggregator', async () => { - let currentTimestamp = await agg1.latestTimestamp() - let staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - currentTimestamp = await agg2.latestTimestamp() - staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold2 + 1), - ) - await agg2.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const tx = await validator.performUpkeep(bytesData) - const logs = await getLogs(tx) - assert.equal(logs.length, 2) - assert.equal(evmWordToAddress(logs[0].topics[1]), agg1.address) - assert.equal(evmWordToAddress(logs[1].topics[1]), agg2.address) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/UpkeepRegistrationRequests.test.ts b/contracts/test/v0.7/UpkeepRegistrationRequests.test.ts deleted file mode 100644 index 5ec9306c668..00000000000 --- a/contracts/test/v0.7/UpkeepRegistrationRequests.test.ts +++ /dev/null @@ -1,603 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { evmRevert } from '../test-helpers/matchers' -import { getUsers, Personas } from '../test-helpers/setup' -import { BigNumber, Signer } from 'ethers' -import { LinkToken__factory as LinkTokenFactory } from '../../typechain/factories/LinkToken__factory' -import { KeeperRegistry1_1__factory as KeeperRegistryFactory } from '../../typechain/factories/KeeperRegistry1_1__factory' -import { MockV3Aggregator__factory as MockV3AggregatorFactory } from '../../typechain/factories/MockV3Aggregator__factory' -import { UpkeepRegistrationRequests__factory as UpkeepRegistrationRequestsFactory } from '../../typechain/factories/UpkeepRegistrationRequests__factory' -import { UpkeepMock__factory as UpkeepMockFactory } from '../../typechain/factories/UpkeepMock__factory' -import { KeeperRegistry1_1 as KeeperRegistry } from '../../typechain/KeeperRegistry1_1' -import { UpkeepRegistrationRequests } from '../../typechain/UpkeepRegistrationRequests' -import { MockV3Aggregator } from '../../typechain/MockV3Aggregator' -import { LinkToken } from '../../typechain/LinkToken' -import { UpkeepMock } from '../../typechain/UpkeepMock' - -let linkTokenFactory: LinkTokenFactory -let mockV3AggregatorFactory: MockV3AggregatorFactory -let keeperRegistryFactory: KeeperRegistryFactory -let upkeepRegistrationRequestsFactory: UpkeepRegistrationRequestsFactory -let upkeepMockFactory: UpkeepMockFactory - -let personas: Personas - -before(async () => { - personas = (await getUsers()).personas - - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - ) - mockV3AggregatorFactory = (await ethers.getContractFactory( - 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', - )) as unknown as MockV3AggregatorFactory - // @ts-ignore bug in autogen file - keeperRegistryFactory = await ethers.getContractFactory('KeeperRegistry1_1') - upkeepRegistrationRequestsFactory = await ethers.getContractFactory( - 'UpkeepRegistrationRequests', - ) - upkeepMockFactory = await ethers.getContractFactory('UpkeepMock') -}) - -const errorMsgs = { - onlyOwner: 'revert Only callable by owner', - onlyAdmin: 'only admin / owner can cancel', - hashPayload: 'hash and payload do not match', - requestNotFound: 'request not found', -} - -describe('UpkeepRegistrationRequests', () => { - const upkeepName = 'SampleUpkeep' - - const linkEth = BigNumber.from(300000000) - const gasWei = BigNumber.from(100) - const executeGas = BigNumber.from(100000) - const source = BigNumber.from(100) - const paymentPremiumPPB = BigNumber.from(250000000) - const flatFeeMicroLink = BigNumber.from(0) - - const window_big = BigNumber.from(1000) - const window_small = BigNumber.from(2) - const threshold_big = BigNumber.from(1000) - const threshold_small = BigNumber.from(5) - - const blockCountPerTurn = BigNumber.from(3) - const emptyBytes = '0x00' - const stalenessSeconds = BigNumber.from(43820) - const gasCeilingMultiplier = BigNumber.from(1) - const maxCheckGas = BigNumber.from(20000000) - const fallbackGasPrice = BigNumber.from(200) - const fallbackLinkPrice = BigNumber.from(200000000) - const minLINKJuels = BigNumber.from('1000000000000000000') - const amount = BigNumber.from('5000000000000000000') - const amount1 = BigNumber.from('6000000000000000000') - - let owner: Signer - let admin: Signer - let someAddress: Signer - let registrarOwner: Signer - let stranger: Signer - - let linkToken: LinkToken - let linkEthFeed: MockV3Aggregator - let gasPriceFeed: MockV3Aggregator - let registry: KeeperRegistry - let mock: UpkeepMock - let registrar: UpkeepRegistrationRequests - - beforeEach(async () => { - owner = personas.Default - admin = personas.Neil - someAddress = personas.Ned - registrarOwner = personas.Nelly - stranger = personas.Nancy - - linkToken = await linkTokenFactory.connect(owner).deploy() - gasPriceFeed = await mockV3AggregatorFactory - .connect(owner) - .deploy(0, gasWei) - linkEthFeed = await mockV3AggregatorFactory - .connect(owner) - .deploy(9, linkEth) - registry = await keeperRegistryFactory - .connect(owner) - .deploy( - linkToken.address, - linkEthFeed.address, - gasPriceFeed.address, - paymentPremiumPPB, - flatFeeMicroLink, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - gasCeilingMultiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - - mock = await upkeepMockFactory.deploy() - - registrar = await upkeepRegistrationRequestsFactory - .connect(registrarOwner) - .deploy(linkToken.address, minLINKJuels) - - 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( - registrar - .connect(someAddress) - .register( - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ), - 'Must use LINK token', - ) - }) - - it('reverts if the amount passed in data mismatches actual amount sent', async () => { - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - true, - window_small, - threshold_big, - registry.address, - minLINKJuels, - ) - - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount1, - source, - ], - ) - - await evmRevert( - linkToken.transferAndCall(registrar.address, amount, abiEncodedBytes), - 'Amount mismatch', - ) - }) - - it('reverts if the admin address is 0x0000...', async () => { - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - '0x0000000000000000000000000000000000000000', - emptyBytes, - amount, - source, - ], - ) - - await evmRevert( - linkToken.transferAndCall(registrar.address, amount, abiEncodedBytes), - 'Unable to create request', - ) - }) - - it('Auto Approve ON - registers an upkeep on KeeperRegistry instantly and emits both RegistrationRequested and RegistrationApproved events', async () => { - //get current upkeep count - const upkeepCount = await registry.getUpkeepCount() - - //set auto approve ON with high threshold limits - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - true, - window_small, - threshold_big, - registry.address, - minLINKJuels, - ) - - //register with auto approve ON - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ], - ) - const tx = await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - - //confirm if a new upkeep has been registered and the details are the same as the one just registered - const newupkeep = await registry.getUpkeep(upkeepCount) - assert.equal(newupkeep.target, mock.address) - assert.equal(newupkeep.admin, await admin.getAddress()) - assert.equal(newupkeep.checkData, emptyBytes) - assert.equal(newupkeep.balance.toString(), amount.toString()) - assert.equal(newupkeep.executeGas, executeGas.toNumber()) - - await expect(tx).to.emit(registrar, 'RegistrationRequested') - await expect(tx).to.emit(registrar, 'RegistrationApproved') - }) - - it('Auto Approve OFF - does not registers an upkeep on KeeperRegistry, emits only RegistrationRequested event', async () => { - //get upkeep count before attempting registration - const beforeCount = await registry.getUpkeepCount() - - //set auto approve OFF, threshold limits dont matter in this case - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - false, - window_small, - threshold_big, - registry.address, - minLINKJuels, - ) - - //register with auto approve OFF - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ], - ) - const tx = await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - const receipt = await tx.wait() - - //get upkeep count after attempting registration - const afterCount = await registry.getUpkeepCount() - //confirm that a new upkeep has NOT been registered and upkeep count is still the same - assert.deepEqual(beforeCount, afterCount) - - //confirm that only RegistrationRequested event is emitted and RegistrationApproved event is not - await expect(tx).to.emit(registrar, 'RegistrationRequested') - await expect(tx).not.to.emit(registrar, 'RegistrationApproved') - - const hash = receipt.logs[2].topics[1] - const pendingRequest = await registrar.getPendingRequest(hash) - assert.equal(await admin.getAddress(), pendingRequest[0]) - assert.ok(amount.eq(pendingRequest[1])) - }) - - it('Auto Approve ON - Throttle max approvals - does not registers an upkeep on KeeperRegistry beyond the throttle limit, emits only RegistrationRequested event after throttle starts', async () => { - //get upkeep count before attempting registration - const beforeCount = await registry.getUpkeepCount() - - //set auto approve on, with low threshold limits - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - true, - window_big, - threshold_small, - registry.address, - minLINKJuels, - ) - - let abiEncodedBytes = registrar.interface.encodeFunctionData('register', [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ]) - - //register within threshold, new upkeep should be registered - await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - const intermediateCount = await registry.getUpkeepCount() - //make sure 1 upkeep was registered - assert.equal(beforeCount.toNumber() + 1, intermediateCount.toNumber()) - - //try registering more than threshold(say 2x), new upkeeps should not be registered after the threshold amount is reached - for (let step = 0; step < threshold_small.toNumber() * 2; step++) { - abiEncodedBytes = registrar.interface.encodeFunctionData('register', [ - upkeepName, - emptyBytes, - mock.address, - executeGas.toNumber() + step, // make unique hash - await admin.getAddress(), - emptyBytes, - amount, - source, - ]) - - await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - } - const afterCount = await registry.getUpkeepCount() - //count of newly registered upkeeps should be equal to the threshold set for auto approval - const newRegistrationsCount = - afterCount.toNumber() - beforeCount.toNumber() - assert( - newRegistrationsCount == threshold_small.toNumber(), - 'Registrations beyond threshold', - ) - }) - }) - - describe('#approve', () => { - let hash: string - - beforeEach(async () => { - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - false, - window_small, - threshold_big, - registry.address, - minLINKJuels, - ) - - //register with auto approve OFF - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ], - ) - - const tx = await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - const receipt = await tx.wait() - hash = receipt.logs[2].topics[1] - }) - - it('reverts if not called by the owner', async () => { - const tx = registrar - .connect(stranger) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - await evmRevert(tx, 'Only callable by owner') - }) - - it('reverts if the hash does not exist', async () => { - const tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - '0x000000000000000000000000322813fd9a801c5507c9de605d63cea4f2ce6c44', - ) - await evmRevert(tx, errorMsgs.requestNotFound) - }) - - it('reverts if any member of the payload changes', async () => { - let tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - ethers.Wallet.createRandom().address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - await evmRevert(tx, errorMsgs.hashPayload) - tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - 10000, - await admin.getAddress(), - emptyBytes, - hash, - ) - await evmRevert(tx, errorMsgs.hashPayload) - tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - ethers.Wallet.createRandom().address, - emptyBytes, - hash, - ) - await evmRevert(tx, errorMsgs.hashPayload) - tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - '0x1234', - hash, - ) - await evmRevert(tx, errorMsgs.hashPayload) - }) - - it('approves an existing registration request', async () => { - const tx = await registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - await expect(tx).to.emit(registrar, 'RegistrationApproved') - }) - - it('deletes the request afterwards / reverts if the request DNE', async () => { - await registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - const tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - await evmRevert(tx, errorMsgs.requestNotFound) - }) - }) - - describe('#cancel', () => { - let hash: string - - beforeEach(async () => { - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - false, - window_small, - threshold_big, - registry.address, - minLINKJuels, - ) - - //register with auto approve OFF - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ], - ) - const tx = await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - const receipt = await tx.wait() - hash = receipt.logs[2].topics[1] - // submit duplicate request (increase balance) - await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - }) - - it('reverts if not called by the admin / owner', async () => { - const tx = registrar.connect(stranger).cancel(hash) - await evmRevert(tx, errorMsgs.onlyAdmin) - }) - - it('reverts if the hash does not exist', async () => { - const tx = registrar - .connect(registrarOwner) - .cancel( - '0x000000000000000000000000322813fd9a801c5507c9de605d63cea4f2ce6c44', - ) - await evmRevert(tx, 'request not found') - }) - - it('refunds the total request balance to the admin address', async () => { - const before = await linkToken.balanceOf(await admin.getAddress()) - const tx = await registrar.connect(admin).cancel(hash) - const after = await linkToken.balanceOf(await admin.getAddress()) - assert.isTrue(after.sub(before).eq(amount.mul(BigNumber.from(2)))) - await expect(tx).to.emit(registrar, 'RegistrationRejected') - }) - - it('deletes the request hash', async () => { - await registrar.connect(registrarOwner).cancel(hash) - let tx = registrar.connect(registrarOwner).cancel(hash) - await evmRevert(tx, errorMsgs.requestNotFound) - tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - await evmRevert(tx, errorMsgs.requestNotFound) - }) - }) -}) diff --git a/contracts/test/v0.7/VRFD20.test.ts b/contracts/test/v0.7/VRFD20.test.ts deleted file mode 100644 index f1e0e9ab0a8..00000000000 --- a/contracts/test/v0.7/VRFD20.test.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { - BigNumber, - constants, - Contract, - ContractFactory, - ContractTransaction, -} from 'ethers' -import { getUsers, Personas, Roles } from '../test-helpers/setup' -import { - evmWordToAddress, - getLog, - publicAbi, - toBytes32String, - toWei, - numToBytes32, - getLogs, -} from '../test-helpers/helpers' - -let roles: Roles -let personas: Personas -let linkTokenFactory: ContractFactory -let vrfCoordinatorMockFactory: ContractFactory -let vrfD20Factory: ContractFactory - -before(async () => { - const users = await getUsers() - - roles = users.roles - personas = users.personas - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) - vrfCoordinatorMockFactory = await ethers.getContractFactory( - 'src/v0.7/tests/VRFCoordinatorMock.sol:VRFCoordinatorMock', - roles.defaultAccount, - ) - vrfD20Factory = await ethers.getContractFactory( - 'src/v0.6/examples/VRFD20.sol:VRFD20', - roles.defaultAccount, - ) -}) - -describe('VRFD20', () => { - const deposit = toWei('1') - const fee = toWei('0.1') - const keyHash = toBytes32String('keyHash') - - let link: Contract - let vrfCoordinator: Contract - let vrfD20: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - vrfCoordinator = await vrfCoordinatorMockFactory - .connect(roles.defaultAccount) - .deploy(link.address) - vrfD20 = await vrfD20Factory - .connect(roles.defaultAccount) - .deploy(vrfCoordinator.address, link.address, keyHash, fee) - await link.transfer(vrfD20.address, deposit) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(vrfD20, [ - // Owned - 'acceptOwnership', - 'owner', - 'transferOwnership', - //VRFConsumerBase - 'rawFulfillRandomness', - // VRFD20 - 'rollDice', - 'house', - 'withdrawLINK', - 'keyHash', - 'fee', - 'setKeyHash', - 'setFee', - ]) - }) - - describe('#withdrawLINK', () => { - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20 - .connect(roles.stranger) - .withdrawLINK(await roles.stranger.getAddress(), deposit), - ).to.be.revertedWith('Only callable by owner') - }) - - it('reverts when not enough LINK in the contract', async () => { - const withdrawAmount = deposit.mul(2) - await expect( - vrfD20 - .connect(roles.defaultAccount) - .withdrawLINK( - await roles.defaultAccount.getAddress(), - withdrawAmount, - ), - ).to.be.reverted - }) - }) - - describe('success', () => { - it('withdraws LINK', async () => { - const startingAmount = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - const expectedAmount = BigNumber.from(startingAmount).add(deposit) - await vrfD20 - .connect(roles.defaultAccount) - .withdrawLINK(await roles.defaultAccount.getAddress(), deposit) - const actualAmount = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - assert.equal(actualAmount.toString(), expectedAmount.toString()) - }) - }) - }) - - describe('#setKeyHash', () => { - const newHash = toBytes32String('newhash') - - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20.connect(roles.stranger).setKeyHash(newHash), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('success', () => { - it('sets the key hash', async () => { - await vrfD20.setKeyHash(newHash) - const actualHash = await vrfD20.keyHash() - assert.equal(actualHash, newHash) - }) - }) - }) - - describe('#setFee', () => { - const newFee = 1234 - - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20.connect(roles.stranger).setFee(newFee), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('success', () => { - it('sets the fee', async () => { - await vrfD20.setFee(newFee) - const actualFee = await vrfD20.fee() - assert.equal(actualFee.toString(), newFee.toString()) - }) - }) - }) - - describe('#house', () => { - describe('failure', () => { - it('reverts when dice not rolled', async () => { - await expect( - vrfD20.house(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Dice not rolled') - }) - - it('reverts when dice roll is in progress', async () => { - await vrfD20.rollDice(await personas.Nancy.getAddress()) - await expect( - vrfD20.house(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Roll in progress') - }) - }) - - describe('success', () => { - it('returns the correct house', async () => { - const randomness = 98765 - const expectedHouse = 'Martell' - const tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - const log = await getLog(tx, 3) - const eventRequestId = log?.topics?.[1] - await vrfCoordinator.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - const response = await vrfD20.house(await personas.Nancy.getAddress()) - assert.equal(response.toString(), expectedHouse) - }) - }) - }) - - describe('#rollDice', () => { - describe('success', () => { - let tx: ContractTransaction - beforeEach(async () => { - tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - }) - - it('emits a RandomnessRequest event from the VRFCoordinator', async () => { - const log = await getLog(tx, 2) - const topics = log?.topics - assert.equal(evmWordToAddress(topics?.[1]), vrfD20.address) - assert.equal(topics?.[2], keyHash) - assert.equal(topics?.[3], constants.HashZero) - }) - }) - - describe('failure', () => { - it('reverts when LINK balance is zero', async () => { - const vrfD202 = await vrfD20Factory - .connect(roles.defaultAccount) - .deploy(vrfCoordinator.address, link.address, keyHash, fee) - await expect( - vrfD202.rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Not enough LINK to pay fee') - }) - - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20 - .connect(roles.stranger) - .rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - - it('reverts when the roller rolls more than once', async () => { - await vrfD20.rollDice(await personas.Nancy.getAddress()) - await expect( - vrfD20.rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Already rolled') - }) - }) - }) - - describe('#fulfillRandomness', () => { - const randomness = 98765 - const expectedModResult = (randomness % 20) + 1 - const expectedHouse = 'Martell' - let eventRequestId: string - beforeEach(async () => { - const tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - const log = await getLog(tx, 3) - eventRequestId = log?.topics?.[1] - }) - - describe('success', () => { - let tx: ContractTransaction - beforeEach(async () => { - tx = await vrfCoordinator.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - }) - - it('emits a DiceLanded event', async () => { - const log = await getLog(tx, 0) - assert.equal(log?.topics[1], eventRequestId) - assert.equal(log?.topics[2], numToBytes32(expectedModResult)) - }) - - it('sets the correct dice roll result', async () => { - const response = await vrfD20.house(await personas.Nancy.getAddress()) - assert.equal(response.toString(), expectedHouse) - }) - - it('allows someone else to roll', async () => { - const secondRandomness = 55555 - tx = await vrfD20.rollDice(await personas.Ned.getAddress()) - const log = await getLog(tx, 3) - eventRequestId = log?.topics?.[1] - tx = await vrfCoordinator.callBackWithRandomness( - eventRequestId, - secondRandomness, - vrfD20.address, - ) - }) - }) - - describe('failure', () => { - it('does not fulfill when fulfilled by the wrong VRFcoordinator', async () => { - const vrfCoordinator2 = await vrfCoordinatorMockFactory - .connect(roles.defaultAccount) - .deploy(link.address) - - const tx = await vrfCoordinator2.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/gasUsage.test.ts b/contracts/test/v0.7/gasUsage.test.ts deleted file mode 100644 index 97146622d06..00000000000 --- a/contracts/test/v0.7/gasUsage.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { ethers } from 'hardhat' -import { toBytes32String, toWei } from '../test-helpers/helpers' -import { Contract, ContractFactory } from 'ethers' -import { getUsers, Roles } from '../test-helpers/setup' -import { - convertFufillParams, - convertFulfill2Params, - decodeRunRequest, -} from '../test-helpers/oracle' -import { gasDiffLessThan } from '../test-helpers/matchers' - -let operatorFactory: ContractFactory -let oracleFactory: ContractFactory -let basicConsumerFactory: ContractFactory -let linkTokenFactory: ContractFactory - -let roles: Roles - -before(async () => { - const users = await getUsers() - - roles = users.roles - operatorFactory = await ethers.getContractFactory( - 'src/v0.7/Operator.sol:Operator', - roles.defaultAccount, - ) - oracleFactory = await ethers.getContractFactory( - 'src/v0.6/Oracle.sol:Oracle', - roles.defaultAccount, - ) - basicConsumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/BasicConsumer.sol:BasicConsumer', - roles.defaultAccount, - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) -}) - -describe('Operator Gas Tests [ @skip-coverage ]', () => { - const specId = - '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000000' - let link: Contract - let oracle1: Contract - let operator1: Contract - let operator2: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - - operator1 = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - await operator1.setAuthorizedSenders([await roles.oracleNode.getAddress()]) - - operator2 = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - await operator2.setAuthorizedSenders([await roles.oracleNode.getAddress()]) - - oracle1 = await oracleFactory - .connect(roles.defaultAccount) - .deploy(link.address) - await oracle1.setFulfillmentPermission( - await roles.oracleNode.getAddress(), - true, - ) - }) - - // Test Oracle.fulfillOracleRequest vs Operator.fulfillOracleRequest - describe('v0.6/Oracle vs v0.7/Operator #fulfillOracleRequest', () => { - const response = 'Hi Mom!' - let basicConsumer1: Contract - let basicConsumer2: Contract - - let request1: ReturnType - let request2: ReturnType - - beforeEach(async () => { - basicConsumer1 = await basicConsumerFactory - .connect(roles.consumer) - .deploy(link.address, oracle1.address, specId) - basicConsumer2 = await basicConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator1.address, specId) - - const paymentAmount = toWei('1') - const currency = 'USD' - - await link.transfer(basicConsumer1.address, paymentAmount) - const tx1 = await basicConsumer1.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt1 = await tx1.wait() - request1 = decodeRunRequest(receipt1.logs?.[3]) - - await link.transfer(basicConsumer2.address, paymentAmount) - const tx2 = await basicConsumer2.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt2 = await tx2.wait() - request2 = decodeRunRequest(receipt2.logs?.[3]) - }) - - it('uses acceptable gas', async () => { - const tx1 = await oracle1 - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request1, response)) - const tx2 = await operator1 - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request2, response)) - const receipt1 = await tx1.wait() - const receipt2 = await tx2.wait() - // 38014 vs 40260 - gasDiffLessThan(3900, receipt1, receipt2) - }) - }) - - // Test Operator1.fulfillOracleRequest vs Operator2.fulfillOracleRequest2 - // with single word response - describe('Operator #fulfillOracleRequest vs #fulfillOracleRequest2', () => { - const response = 'Hi Mom!' - let basicConsumer1: Contract - let basicConsumer2: Contract - - let request1: ReturnType - let request2: ReturnType - - beforeEach(async () => { - basicConsumer1 = await basicConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator1.address, specId) - basicConsumer2 = await basicConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator2.address, specId) - - const paymentAmount = toWei('1') - const currency = 'USD' - - await link.transfer(basicConsumer1.address, paymentAmount) - const tx1 = await basicConsumer1.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt1 = await tx1.wait() - request1 = decodeRunRequest(receipt1.logs?.[3]) - - await link.transfer(basicConsumer2.address, paymentAmount) - const tx2 = await basicConsumer2.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt2 = await tx2.wait() - request2 = decodeRunRequest(receipt2.logs?.[3]) - }) - - it('uses acceptable gas', async () => { - const tx1 = await operator1 - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request1, response)) - - const responseTypes = ['bytes32'] - const responseValues = [toBytes32String(response)] - const tx2 = await operator2 - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params(request2, responseTypes, responseValues), - ) - - const receipt1 = await tx1.wait() - const receipt2 = await tx2.wait() - gasDiffLessThan(1240, receipt1, receipt2) - }) - }) -}) diff --git a/contracts/test/v0.8/automation/LinkAvailableBalanceMonitor.test.ts b/contracts/test/v0.8/automation/LinkAvailableBalanceMonitor.test.ts index 4efd98039f5..76a3dcfff1b 100644 --- a/contracts/test/v0.8/automation/LinkAvailableBalanceMonitor.test.ts +++ b/contracts/test/v0.8/automation/LinkAvailableBalanceMonitor.test.ts @@ -34,8 +34,8 @@ const PAUSED_ERR = 'Pausable: paused' const zeroLINK = ethers.utils.parseEther('0') const oneLINK = ethers.utils.parseEther('1') const twoLINK = ethers.utils.parseEther('2') +const fourLINK = ethers.utils.parseEther('4') const fiveLINK = ethers.utils.parseEther('5') -const sixLINK = ethers.utils.parseEther('6') const tenLINK = ethers.utils.parseEther('10') const oneHundredLINK = ethers.utils.parseEther('100') @@ -60,6 +60,7 @@ let directTarget2: MockContract let watchListAddresses: string[] let watchListMinBalances: BigNumber[] +let watchListTopUpAmounts: BigNumber[] async function assertContractLinkBalances( balance1: BigNumber, @@ -121,6 +122,7 @@ const setup = async () => { directTarget2.address, ] watchListMinBalances = [oneLINK, oneLINK, oneLINK, twoLINK, twoLINK] + watchListTopUpAmounts = [twoLINK, twoLINK, twoLINK, twoLINK, twoLINK] await proxy1.mock.aggregator.returns(aggregator1.address) await proxy2.mock.aggregator.returns(aggregator2.address) @@ -142,8 +144,20 @@ const setup = async () => { owner, ) - lt = await ltFactory.deploy() - labm = await labmFactory.deploy(lt.address, twoLINK) + // New parameters needed by the constructor + const maxPerform = 5 + const maxCheck = 20 + const minWaitPeriodSeconds = 0 + const upkeepInterval = 10 + + lt = (await ltFactory.deploy()) as LinkToken + labm = await labmFactory.deploy( + lt.address, + minWaitPeriodSeconds, + maxPerform, + maxCheck, + upkeepInterval, + ) await labm.deployed() for (let i = 1; i <= 4; i++) { @@ -153,7 +167,11 @@ const setup = async () => { const setTx = await labm .connect(owner) - .setWatchList(watchListAddresses, watchListMinBalances) + .setWatchList( + watchListAddresses, + watchListMinBalances, + watchListTopUpAmounts, + ) await setTx.wait() } @@ -171,19 +189,27 @@ describe('LinkAvailableBalanceMonitor', () => { describe('setTopUpAmount()', () => { it('configures the top-up amount', async () => { - await labm.connect(owner).setTopUpAmount(100) - assert.equal((await labm.getTopUpAmount()).toNumber(), 100) + await labm + .connect(owner) + .setTopUpAmount(directTarget1.address, BigNumber.from(100)) + const report = await labm.getAccountInfo(directTarget1.address) + assert.equal(report.topUpAmount.toString(), '100') }) it('configuresis only callable by the owner', async () => { - await expect(labm.connect(stranger).setTopUpAmount(100)).to.be.reverted + await expect( + labm.connect(stranger).setTopUpAmount(directTarget1.address, 100), + ).to.be.reverted }) }) describe('setMinBalance()', () => { it('configures the min balance', async () => { - await labm.connect(owner).setMinBalance(proxy1.address, 100) - assert.equal((await labm.getMinBalance(proxy1.address)).toNumber(), 100) + await labm + .connect(owner) + .setMinBalance(proxy1.address, BigNumber.from(100)) + const report = await labm.getAccountInfo(proxy1.address) + assert.equal(report.minBalance.toString(), '100') }) it('reverts if address is not in the watchlist', async () => { @@ -263,66 +289,29 @@ describe('LinkAvailableBalanceMonitor', () => { beforeEach(async () => { // reset watchlist to empty before running these tests - await labm.connect(owner).setWatchList([], []) - let watchList = await labm.getWatchList() - assert.deepEqual(watchList, [[], []]) + await labm.connect(owner).setWatchList([], [], []) + const watchList = await labm.getWatchList() + assert.deepEqual(watchList, []) }) it('Should allow owner to adjust the watchlist', async () => { // add first watchlist let tx = await labm .connect(owner) - .setWatchList([watchAddress1], [oneLINK]) + .setWatchList([watchAddress1], [oneLINK], [oneLINK]) let watchList = await labm.getWatchList() - assert.deepEqual(watchList[0], [watchAddress1]) - assert.deepEqual( - watchList[1].map((x) => x.toString()), - [oneLINK].map((x) => x.toString()), - ) + assert.deepEqual(watchList[0], watchAddress1) // add more to watchlist tx = await labm .connect(owner) .setWatchList( [watchAddress1, watchAddress2, watchAddress3], [oneLINK, oneLINK, oneLINK], + [oneLINK, oneLINK, oneLINK], ) await tx.wait() watchList = await labm.getWatchList() - assert.deepEqual(watchList[0], [ - watchAddress1, - watchAddress2, - watchAddress3, - ]) - assert.deepEqual( - watchList[1].map((x) => x.toString()), - [oneLINK, oneLINK, oneLINK].map((x) => x.toString()), - ) - // remove some from watchlist - tx = await labm - .connect(owner) - .removeFromWatchlist([watchAddress3, watchAddress1]) - await tx.wait() - watchList = await labm.getWatchList() - assert.deepEqual(watchList[0], [watchAddress2]) - assert.deepEqual( - watchList[1].map((x) => x.toString()), - [oneLINK].map((x) => x.toString()), - ) - // add some to watchlist - tx = await labm - .connect(owner) - .addToWatchList([watchAddress1, watchAddress3], [twoLINK, twoLINK]) - await tx.wait() - watchList = await labm.getWatchList() - assert.deepEqual(watchList[0], [ - watchAddress2, - watchAddress1, - watchAddress3, - ]) - assert.deepEqual( - watchList[1].map((x) => x.toString()), - [oneLINK, twoLINK, twoLINK].map((x) => x.toString()), - ) + assert.deepEqual(watchList, [watchAddress1, watchAddress2, watchAddress3]) }) it('Should not allow different length arrays in the watchlist', async () => { @@ -332,6 +321,7 @@ describe('LinkAvailableBalanceMonitor', () => { .setWatchList( [watchAddress1, watchAddress2, watchAddress1], [oneLINK, oneLINK], + [oneLINK, oneLINK], ) await expect(tx).to.be.revertedWith(errMsg) }) @@ -343,12 +333,6 @@ describe('LinkAvailableBalanceMonitor', () => { .setWatchList( [watchAddress1, watchAddress2, watchAddress1], [oneLINK, oneLINK, oneLINK], - ) - await expect(tx).to.be.revertedWith(errMsg) - tx = labm - .connect(owner) - .addToWatchList( - [watchAddress1, watchAddress2, watchAddress1], [oneLINK, oneLINK, oneLINK], ) await expect(tx).to.be.revertedWith(errMsg) @@ -357,14 +341,8 @@ describe('LinkAvailableBalanceMonitor', () => { it('Should not allow strangers to set the watchlist', async () => { const setTxStranger = labm .connect(stranger) - .setWatchList([watchAddress1], [oneLINK]) + .setWatchList([watchAddress1], [oneLINK], [oneLINK]) await expect(setTxStranger).to.be.revertedWith(OWNABLE_ERR) - const addTxStranger = labm - .connect(stranger) - .addToWatchList([watchAddress1], [oneLINK]) - await expect(addTxStranger).to.be.revertedWith(OWNABLE_ERR) - const removeTxStranger = labm.connect(stranger).removeFromWatchlist([]) - await expect(removeTxStranger).to.be.revertedWith(OWNABLE_ERR) }) it('Should revert if any of the addresses are empty', async () => { @@ -373,12 +351,6 @@ describe('LinkAvailableBalanceMonitor', () => { .setWatchList( [watchAddress1, ethers.constants.AddressZero], [oneLINK, oneLINK], - ) - await expect(tx).to.be.revertedWith(INVALID_WATCHLIST_ERR) - tx = labm - .connect(owner) - .addToWatchList( - [watchAddress1, ethers.constants.AddressZero], [oneLINK, oneLINK], ) await expect(tx).to.be.revertedWith(INVALID_WATCHLIST_ERR) @@ -387,69 +359,68 @@ describe('LinkAvailableBalanceMonitor', () => { describe('checkUpkeep() / sampleUnderfundedAddresses() [ @skip-coverage ]', () => { it('Should return list of address that are underfunded', async () => { - const fundTx = await lt.connect(owner).transfer( - labm.address, - tenLINK, // needs 10 total - ) + const fundTx = await lt + .connect(owner) + .transfer(labm.address, oneHundredLINK) await fundTx.wait() + + await labm.setWatchList( + watchListAddresses, + watchListMinBalances, + watchListTopUpAmounts, + ) + const [should, payload] = await labm.checkUpkeep('0x') assert.isTrue(should) let [addresses] = ethers.utils.defaultAbiCoder.decode( ['address[]'], payload, ) + expect(addresses).to.deep.equalInAnyOrder(watchListAddresses) - // checkUpkeep payload should match sampleUnderfundedAddresses() addresses = await labm.sampleUnderfundedAddresses() expect(addresses).to.deep.equalInAnyOrder(watchListAddresses) }) - it('Should return some results even if contract cannot fund all eligible targets', async () => { + it('Should omit aggregators that have sufficient funding', async () => { const fundTx = await lt.connect(owner).transfer( labm.address, - fiveLINK, // needs 2Link per contract, so can fund 2 max + oneHundredLINK, // enough for anything that needs funding ) await fundTx.wait() - const [should, payload] = await labm.checkUpkeep('0x') - assert.isTrue(should) - let [addresses] = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - payload, + + await labm.setWatchList( + [aggregator2.address, directTarget1.address, directTarget2.address], + [oneLINK, twoLINK, twoLINK], + [oneLINK, oneLINK, oneLINK], ) - assert.equal(addresses.length, 2) - assert.notEqual(addresses[0], addresses[1]) - assert(watchListAddresses.includes(addresses[0])) - assert(watchListAddresses.includes(addresses[1])) - // underfunded sample should still match list - addresses = await labm.sampleUnderfundedAddresses() - expect(addresses).to.deep.equalInAnyOrder(watchListAddresses) - }) - it('Should omit aggregators that have sufficient funding', async () => { + // all of them are underfunded, return 3 + await aggregator2.mock.linkAvailableForPayment.returns(zeroLINK) + await directTarget1.mock.linkAvailableForPayment.returns(zeroLINK) + await directTarget2.mock.linkAvailableForPayment.returns(zeroLINK) + let addresses = await labm.sampleUnderfundedAddresses() - expect(addresses).to.deep.equalInAnyOrder(watchListAddresses) - await aggregator2.mock.linkAvailableForPayment.returns(oneLINK) // aggregator2 is enough funded - await directTarget1.mock.linkAvailableForPayment.returns(oneLINK) // directTarget1 is NOT enough funded - await directTarget2.mock.linkAvailableForPayment.returns(twoLINK) // directTarget2 is enough funded - addresses = await labm.sampleUnderfundedAddresses() expect(addresses).to.deep.equalInAnyOrder([ - proxy1.address, - proxy3.address, + aggregator2.address, directTarget1.address, + directTarget2.address, ]) - await aggregator1.mock.linkAvailableForPayment.returns(tenLINK) + await aggregator2.mock.linkAvailableForPayment.returns(oneLINK) // aggregator2 is enough funded + await directTarget1.mock.linkAvailableForPayment.returns(oneLINK) // directTarget1 is NOT enough funded + await directTarget2.mock.linkAvailableForPayment.returns(oneLINK) // directTarget2 is NOT funded addresses = await labm.sampleUnderfundedAddresses() expect(addresses).to.deep.equalInAnyOrder([ - proxy3.address, directTarget1.address, + directTarget2.address, ]) - await aggregator3.mock.linkAvailableForPayment.returns(tenLINK) + await directTarget1.mock.linkAvailableForPayment.returns(tenLINK) addresses = await labm.sampleUnderfundedAddresses() - expect(addresses).to.deep.equalInAnyOrder([directTarget1.address]) + expect(addresses).to.deep.equalInAnyOrder([directTarget2.address]) - await directTarget1.mock.linkAvailableForPayment.returns(tenLINK) + await directTarget2.mock.linkAvailableForPayment.returns(tenLINK) addresses = await labm.sampleUnderfundedAddresses() expect(addresses).to.deep.equalInAnyOrder([]) }) @@ -468,13 +439,15 @@ describe('LinkAvailableBalanceMonitor', () => { let MAX_CHECK: number let proxyAddresses: string[] let minBalances: BigNumber[] + let topUpAmount: BigNumber[] let aggregators: MockContract[] beforeEach(async () => { - MAX_PERFORM = (await labm.MAX_PERFORM()).toNumber() - MAX_CHECK = (await labm.MAX_CHECK()).toNumber() + MAX_PERFORM = await labm.getMaxPerform() + MAX_CHECK = await labm.getMaxCheck() proxyAddresses = [] minBalances = [] + topUpAmount = [] aggregators = [] const numAggregators = MAX_CHECK + 50 for (let idx = 0; idx < numAggregators; idx++) { @@ -490,18 +463,18 @@ describe('LinkAvailableBalanceMonitor', () => { await aggregator.mock.linkAvailableForPayment.returns(0) proxyAddresses.push(proxy.address) minBalances.push(oneLINK) + topUpAmount.push(oneLINK) aggregators.push(aggregator) } - await labm.setWatchList(proxyAddresses, minBalances) - expect(await labm.getWatchList()).to.deep.equalInAnyOrder([ - proxyAddresses, - minBalances, - ]) + await labm.setWatchList(proxyAddresses, minBalances, topUpAmount) + let watchlist = await labm.getWatchList() + expect(watchlist).to.deep.equalInAnyOrder(proxyAddresses) + assert.equal(watchlist.length, minBalances.length) }) it('Should not include more than MAX_PERFORM addresses', async () => { const addresses = await labm.sampleUnderfundedAddresses() - assert.equal(addresses.length, MAX_PERFORM) + expect(addresses.length).to.be.lessThanOrEqual(MAX_PERFORM) }) it('Should sample from the list of addresses pseudorandomly', async () => { @@ -544,7 +517,11 @@ describe('LinkAvailableBalanceMonitor', () => { ) await labm .connect(owner) - .setWatchList(watchListAddresses, watchListMinBalances) + .setWatchList( + watchListAddresses, + watchListMinBalances, + watchListTopUpAmounts, + ) }) it('Should revert when paused', async () => { @@ -554,32 +531,38 @@ describe('LinkAvailableBalanceMonitor', () => { }) it('Should fund the appropriate addresses', async () => { - await lt.connect(owner).transfer(labm.address, tenLINK) - await assertContractLinkBalances( - zeroLINK, - zeroLINK, - zeroLINK, - zeroLINK, - zeroLINK, - ) + await aggregator1.mock.linkAvailableForPayment.returns(zeroLINK) + await aggregator2.mock.linkAvailableForPayment.returns(zeroLINK) + await aggregator3.mock.linkAvailableForPayment.returns(zeroLINK) + await directTarget1.mock.linkAvailableForPayment.returns(zeroLINK) + await directTarget2.mock.linkAvailableForPayment.returns(zeroLINK) + + const fundTx = await lt.connect(owner).transfer(labm.address, tenLINK) + await fundTx.wait() + + h.assertLinkTokenBalance(lt, aggregator1.address, zeroLINK) + h.assertLinkTokenBalance(lt, aggregator2.address, zeroLINK) + h.assertLinkTokenBalance(lt, aggregator3.address, zeroLINK) + h.assertLinkTokenBalance(lt, directTarget1.address, zeroLINK) + h.assertLinkTokenBalance(lt, directTarget2.address, zeroLINK) + const performTx = await labm .connect(keeperRegistry) - .performUpkeep(validPayload, { gasLimit: 2_500_000 }) + .performUpkeep(validPayload, { gasLimit: 1_500_000 }) await performTx.wait() - await assertContractLinkBalances( - twoLINK, - twoLINK, - twoLINK, - twoLINK, - twoLINK, - ) + + h.assertLinkTokenBalance(lt, aggregator1.address, twoLINK) + h.assertLinkTokenBalance(lt, aggregator2.address, twoLINK) + h.assertLinkTokenBalance(lt, aggregator3.address, twoLINK) + h.assertLinkTokenBalance(lt, directTarget1.address, twoLINK) + h.assertLinkTokenBalance(lt, directTarget2.address, twoLINK) }) it('Can handle MAX_PERFORM proxies within gas limit', async () => { - // add MAX_PERFORM number of proxies - const MAX_PERFORM = (await labm.MAX_PERFORM()).toNumber() + const MAX_PERFORM = await labm.getMaxPerform() const proxyAddresses = [] const minBalances = [] + const topUpAmount = [] for (let idx = 0; idx < MAX_PERFORM; idx++) { const proxy = await deployMockContract( owner, @@ -593,20 +576,29 @@ describe('LinkAvailableBalanceMonitor', () => { await aggregator.mock.linkAvailableForPayment.returns(0) proxyAddresses.push(proxy.address) minBalances.push(oneLINK) + topUpAmount.push(oneLINK) } - await labm.setWatchList(proxyAddresses, minBalances) - expect(await labm.getWatchList()).to.deep.equalInAnyOrder([ - proxyAddresses, - minBalances, - ]) + await labm.setWatchList(proxyAddresses, minBalances, topUpAmount) + let watchlist = await labm.getWatchList() + expect(watchlist).to.deep.equalInAnyOrder(proxyAddresses) + assert.equal(watchlist.length, minBalances.length) + // add funds - const fundsNeeded = (await labm.getTopUpAmount()).mul(MAX_PERFORM) + const wl = await labm.getWatchList() + let fundsNeeded = BigNumber.from(0) + for (let idx = 0; idx < wl.length; idx++) { + const targetInfo = await labm.getAccountInfo(wl[idx]) + const targetTopUpAmount = targetInfo.topUpAmount + fundsNeeded.add(targetTopUpAmount) + } await lt.connect(owner).transfer(labm.address, fundsNeeded) + // encode payload const payload = ethers.utils.defaultAbiCoder.encode( ['address[]'], [proxyAddresses], ) + // do the thing await labm .connect(keeperRegistry) @@ -615,6 +607,11 @@ describe('LinkAvailableBalanceMonitor', () => { }) describe('topUp()', () => { + it('Should revert topUp address(0)', async () => { + const tx = await labm.connect(owner).topUp([ethers.constants.AddressZero]) + await expect(tx).to.emit(labm, 'TopUpBlocked') + }) + context('when not paused', () => { it('Should be callable by anyone', async () => { const users = [owner, keeperRegistry, stranger] @@ -651,13 +648,13 @@ describe('LinkAvailableBalanceMonitor', () => { it('Should fund the appropriate addresses', async () => { const tx = await labm.connect(keeperRegistry).topUp(watchListAddresses) - await assertContractLinkBalances( - twoLINK, - twoLINK, - twoLINK, - twoLINK, - twoLINK, - ) + + await aggregator1.mock.linkAvailableForPayment.returns(twoLINK) + await aggregator2.mock.linkAvailableForPayment.returns(twoLINK) + await aggregator3.mock.linkAvailableForPayment.returns(twoLINK) + await directTarget1.mock.linkAvailableForPayment.returns(twoLINK) + await directTarget2.mock.linkAvailableForPayment.returns(twoLINK) + await expect(tx) .to.emit(labm, 'TopUpSucceeded') .withArgs(proxy1.address) @@ -679,13 +676,12 @@ describe('LinkAvailableBalanceMonitor', () => { await labm .connect(keeperRegistry) .topUp([proxy1.address, directTarget1.address]) - await assertContractLinkBalances( - twoLINK, - zeroLINK, - zeroLINK, - twoLINK, - zeroLINK, - ) + + await aggregator1.mock.linkAvailableForPayment.returns(twoLINK) + await aggregator2.mock.linkAvailableForPayment.returns(zeroLINK) + await aggregator3.mock.linkAvailableForPayment.returns(zeroLINK) + await directTarget1.mock.linkAvailableForPayment.returns(twoLINK) + await directTarget2.mock.linkAvailableForPayment.returns(zeroLINK) }) it('Should skip un-approved addresses', async () => { @@ -694,6 +690,7 @@ describe('LinkAvailableBalanceMonitor', () => { .setWatchList( [proxy1.address, directTarget1.address], [oneLINK, oneLINK], + [oneLINK, oneLINK], ) const tx = await labm .connect(keeperRegistry) @@ -704,13 +701,13 @@ describe('LinkAvailableBalanceMonitor', () => { directTarget1.address, directTarget2.address, ]) - await assertContractLinkBalances( - twoLINK, - zeroLINK, - zeroLINK, - twoLINK, - zeroLINK, - ) + + h.assertLinkTokenBalance(lt, aggregator1.address, twoLINK) + h.assertLinkTokenBalance(lt, aggregator2.address, zeroLINK) + h.assertLinkTokenBalance(lt, aggregator3.address, zeroLINK) + h.assertLinkTokenBalance(lt, directTarget1.address, twoLINK) + h.assertLinkTokenBalance(lt, directTarget2.address, zeroLINK) + await expect(tx) .to.emit(labm, 'TopUpSucceeded') .withArgs(proxy1.address) @@ -727,7 +724,11 @@ describe('LinkAvailableBalanceMonitor', () => { it('Should skip an address if the proxy is invalid and it is not a direct target', async () => { await labm .connect(owner) - .setWatchList([proxy1.address, proxy4.address], [oneLINK, oneLINK]) + .setWatchList( + [proxy1.address, proxy4.address], + [oneLINK, oneLINK], + [oneLINK, oneLINK], + ) const tx = await labm .connect(keeperRegistry) .topUp([proxy1.address, proxy4.address]) @@ -741,7 +742,11 @@ describe('LinkAvailableBalanceMonitor', () => { await proxy4.mock.aggregator.returns(aggregator4.address) await labm .connect(owner) - .setWatchList([proxy1.address, proxy4.address], [oneLINK, oneLINK]) + .setWatchList( + [proxy1.address, proxy4.address], + [oneLINK, oneLINK], + [oneLINK, oneLINK], + ) const tx = await labm .connect(keeperRegistry) .topUp([proxy1.address, proxy4.address]) @@ -756,7 +761,11 @@ describe('LinkAvailableBalanceMonitor', () => { await aggregator4.mock.linkAvailableForPayment.returns(tenLINK) await labm .connect(owner) - .setWatchList([proxy1.address, proxy4.address], [oneLINK, oneLINK]) + .setWatchList( + [proxy1.address, proxy4.address], + [oneLINK, oneLINK], + [oneLINK, oneLINK], + ) const tx = await labm .connect(keeperRegistry) .topUp([proxy1.address, proxy4.address]) @@ -773,6 +782,7 @@ describe('LinkAvailableBalanceMonitor', () => { .setWatchList( [proxy1.address, directTarget1.address], [oneLINK, oneLINK], + [oneLINK, oneLINK], ) const tx = await labm .connect(keeperRegistry) @@ -787,25 +797,26 @@ describe('LinkAvailableBalanceMonitor', () => { }) context('when partially funded', () => { - it('Should fund as many addresses as possible', async () => { + it('Should fund as many addresses as possible T', async () => { await lt.connect(owner).transfer( labm.address, - fiveLINK, // only enough LINK to fund 2 addresses + fourLINK, // only enough LINK to fund 2 addresses ) + + await aggregator1.mock.linkAvailableForPayment.returns(twoLINK) + await aggregator2.mock.linkAvailableForPayment.returns(twoLINK) + await aggregator3.mock.linkAvailableForPayment.returns(zeroLINK) + await directTarget1.mock.linkAvailableForPayment.returns(zeroLINK) + await directTarget2.mock.linkAvailableForPayment.returns(zeroLINK) + + h.assertLinkTokenBalance(lt, aggregator1.address, twoLINK) + h.assertLinkTokenBalance(lt, aggregator2.address, twoLINK) + h.assertLinkTokenBalance(lt, aggregator3.address, zeroLINK) + h.assertLinkTokenBalance(lt, directTarget1.address, zeroLINK) + h.assertLinkTokenBalance(lt, directTarget2.address, zeroLINK) + const tx = await labm.connect(keeperRegistry).topUp(watchListAddresses) - await assertContractLinkBalances( - twoLINK, - twoLINK, - zeroLINK, - zeroLINK, - zeroLINK, - ) - await expect(tx) - .to.emit(labm, 'TopUpSucceeded') - .withArgs(proxy1.address) - await expect(tx) - .to.emit(labm, 'TopUpSucceeded') - .withArgs(proxy2.address) + await expect(tx).to.emit(labm, 'TopUpSucceeded') }) }) }) diff --git a/contracts/test/v0.8/automation/UpkeepBalanceMonitor.test.ts b/contracts/test/v0.8/automation/UpkeepBalanceMonitor.test.ts new file mode 100644 index 00000000000..259a9c3b9f8 --- /dev/null +++ b/contracts/test/v0.8/automation/UpkeepBalanceMonitor.test.ts @@ -0,0 +1,399 @@ +import { ethers } from 'hardhat' +import { expect } from 'chai' +import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { randomAddress } from '../../test-helpers/helpers' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { IKeeperRegistryMaster__factory as RegistryFactory } from '../../../typechain/factories/IKeeperRegistryMaster__factory' +import { IAutomationForwarder__factory as ForwarderFactory } from '../../../typechain/factories/IAutomationForwarder__factory' +import { UpkeepBalanceMonitor } from '../../../typechain/UpkeepBalanceMonitor' +import { LinkToken } from '../../../typechain/LinkToken' +import { BigNumber } from 'ethers' +import { + deployMockContract, + MockContract, +} from '@ethereum-waffle/mock-contract' + +let owner: SignerWithAddress +let stranger: SignerWithAddress +let registry: MockContract +let registry2: MockContract +let forwarder: MockContract +let linkToken: LinkToken +let upkeepBalanceMonitor: UpkeepBalanceMonitor + +const setup = async () => { + const accounts = await ethers.getSigners() + owner = accounts[0] + stranger = accounts[1] + + const ltFactory = await ethers.getContractFactory( + 'src/v0.4/LinkToken.sol:LinkToken', + owner, + ) + linkToken = (await ltFactory.deploy()) as LinkToken + const bmFactory = await ethers.getContractFactory( + 'UpkeepBalanceMonitor', + owner, + ) + upkeepBalanceMonitor = await bmFactory.deploy(linkToken.address, { + maxBatchSize: 10, + minPercentage: 120, + targetPercentage: 300, + maxTopUpAmount: ethers.utils.parseEther('100'), + }) + registry = await deployMockContract(owner, RegistryFactory.abi) + registry2 = await deployMockContract(owner, RegistryFactory.abi) + forwarder = await deployMockContract(owner, ForwarderFactory.abi) + await forwarder.mock.getRegistry.returns(registry.address) + await upkeepBalanceMonitor.setForwarder(forwarder.address) + await linkToken + .connect(owner) + .transfer(upkeepBalanceMonitor.address, ethers.utils.parseEther('10000')) + await upkeepBalanceMonitor + .connect(owner) + .setWatchList(registry.address, [0, 1, 2, 3, 4, 5, 6, 7, 8]) + await upkeepBalanceMonitor + .connect(owner) + .setWatchList(registry2.address, [9, 10, 11]) + for (let i = 0; i < 9; i++) { + await registry.mock.getMinBalance.withArgs(i).returns(100) + await registry.mock.getBalance.withArgs(i).returns(121) // all upkeeps are sufficiently funded + } + for (let i = 9; i < 12; i++) { + await registry2.mock.getMinBalance.withArgs(i).returns(100) + await registry2.mock.getBalance.withArgs(i).returns(121) // all upkeeps are sufficiently funded + } +} + +describe('UpkeepBalanceMonitor', () => { + beforeEach(async () => { + await loadFixture(setup) + }) + + describe('constructor()', () => { + it('should set the initial values correctly', async () => { + const config = await upkeepBalanceMonitor.getConfig() + expect(config.maxBatchSize).to.equal(10) + expect(config.minPercentage).to.equal(120) + expect(config.targetPercentage).to.equal(300) + expect(config.maxTopUpAmount).to.equal(ethers.utils.parseEther('100')) + }) + }) + + describe('setConfig()', () => { + const newConfig = { + maxBatchSize: 100, + minPercentage: 150, + targetPercentage: 500, + maxTopUpAmount: 1, + } + + it('should set config correctly', async () => { + await upkeepBalanceMonitor.connect(owner).setConfig(newConfig) + const config = await upkeepBalanceMonitor.getConfig() + expect(config.maxBatchSize).to.equal(newConfig.maxBatchSize) + expect(config.minPercentage).to.equal(newConfig.minPercentage) + expect(config.targetPercentage).to.equal(newConfig.targetPercentage) + expect(config.maxTopUpAmount).to.equal(newConfig.maxTopUpAmount) + }) + + it('cannot be called by a non-owner', async () => { + await expect( + upkeepBalanceMonitor.connect(stranger).setConfig(newConfig), + ).to.be.revertedWith('Only callable by owner') + }) + + it('should emit an event', async () => { + await expect( + upkeepBalanceMonitor.connect(owner).setConfig(newConfig), + ).to.emit(upkeepBalanceMonitor, 'ConfigSet') + }) + }) + + describe('setForwarder()', () => { + const newForwarder = randomAddress() + + it('should set the forwarder correctly', async () => { + await upkeepBalanceMonitor.connect(owner).setForwarder(newForwarder) + const forwarderAddress = await upkeepBalanceMonitor.getForwarder() + expect(forwarderAddress).to.equal(newForwarder) + }) + + it('cannot be called by a non-owner', async () => { + await expect( + upkeepBalanceMonitor.connect(stranger).setForwarder(randomAddress()), + ).to.be.revertedWith('Only callable by owner') + }) + + it('should emit an event', async () => { + await expect( + upkeepBalanceMonitor.connect(owner).setForwarder(newForwarder), + ) + .to.emit(upkeepBalanceMonitor, 'ForwarderSet') + .withArgs(newForwarder) + }) + }) + + describe('setWatchList()', () => { + const newWatchList = [ + BigNumber.from(1), + BigNumber.from(2), + BigNumber.from(10), + ] + + it('should add addresses to the watchlist', async () => { + await upkeepBalanceMonitor + .connect(owner) + .setWatchList(registry.address, newWatchList) + const [_, upkeepIDs] = await upkeepBalanceMonitor.getWatchList() + expect(upkeepIDs[0]).to.deep.equal(newWatchList) + }) + + it('cannot be called by a non-owner', async () => { + await expect( + upkeepBalanceMonitor + .connect(stranger) + .setWatchList(registry.address, [1, 2, 3]), + ).to.be.revertedWith('Only callable by owner') + }) + + it('should emit an event', async () => { + await expect( + upkeepBalanceMonitor + .connect(owner) + .setWatchList(registry.address, newWatchList), + ) + .to.emit(upkeepBalanceMonitor, 'WatchListSet') + .withArgs(registry.address) + }) + }) + + describe('withdraw()', () => { + const payee = randomAddress() + const withdrawAmount = 100 + + it('should withdraw funds to a payee', async () => { + const initialBalance = await linkToken.balanceOf( + upkeepBalanceMonitor.address, + ) + await upkeepBalanceMonitor.connect(owner).withdraw(withdrawAmount, payee) + const finalBalance = await linkToken.balanceOf( + upkeepBalanceMonitor.address, + ) + const payeeBalance = await linkToken.balanceOf(payee) + expect(finalBalance).to.equal(initialBalance.sub(withdrawAmount)) + expect(payeeBalance).to.equal(withdrawAmount) + }) + + it('cannot be called by a non-owner', async () => { + await expect( + upkeepBalanceMonitor.connect(stranger).withdraw(withdrawAmount, payee), + ).to.be.revertedWith('Only callable by owner') + }) + + it('should emit an event', async () => { + await expect( + upkeepBalanceMonitor.connect(owner).withdraw(withdrawAmount, payee), + ) + .to.emit(upkeepBalanceMonitor, 'FundsWithdrawn') + .withArgs(100, payee) + }) + }) + + describe('pause() and unpause()', () => { + it('should pause and unpause the contract', async () => { + await upkeepBalanceMonitor.connect(owner).pause() + expect(await upkeepBalanceMonitor.paused()).to.be.true + await upkeepBalanceMonitor.connect(owner).unpause() + expect(await upkeepBalanceMonitor.paused()).to.be.false + }) + + it('cannot be called by a non-owner', async () => { + await expect( + upkeepBalanceMonitor.connect(stranger).pause(), + ).to.be.revertedWith('Only callable by owner') + await upkeepBalanceMonitor.connect(owner).pause() + await expect( + upkeepBalanceMonitor.connect(stranger).unpause(), + ).to.be.revertedWith('Only callable by owner') + }) + }) + + describe('checkUpkeep() / getUnderfundedUpkeeps()', () => { + it('should find the underfunded upkeeps', async () => { + let [upkeepIDs, registries, topUpAmounts] = + await upkeepBalanceMonitor.getUnderfundedUpkeeps() + expect(upkeepIDs.length).to.equal(0) + expect(registries.length).to.equal(0) + expect(topUpAmounts.length).to.equal(0) + let [upkeepNeeded, performData] = + await upkeepBalanceMonitor.checkUpkeep('0x') + expect(upkeepNeeded).to.be.false + expect(performData).to.equal('0x') + // update the balance for some upkeeps + await registry.mock.getBalance.withArgs(2).returns(120) + await registry.mock.getBalance.withArgs(4).returns(15) + await registry.mock.getBalance.withArgs(5).returns(0) + ;[upkeepIDs, registries, topUpAmounts] = + await upkeepBalanceMonitor.getUnderfundedUpkeeps() + expect(upkeepIDs.map((v) => v.toNumber())).to.deep.equal([2, 4, 5]) + expect(registries).to.deep.equal([ + registry.address, + registry.address, + registry.address, + ]) + expect(topUpAmounts.map((v) => v.toNumber())).to.deep.equal([ + 180, 285, 300, + ]) + ;[upkeepNeeded, performData] = + await upkeepBalanceMonitor.checkUpkeep('0x') + expect(upkeepNeeded).to.be.true + expect(performData).to.equal( + ethers.utils.defaultAbiCoder.encode( + ['uint256[]', 'address[]', 'uint256[]'], + [ + [2, 4, 5], + [registry.address, registry.address, registry.address], + [180, 285, 300], + ], + ), + ) + // update all to need funding + for (let i = 0; i < 9; i++) { + await registry.mock.getBalance.withArgs(i).returns(0) + } + for (let i = 9; i < 12; i++) { + await registry2.mock.getBalance.withArgs(i).returns(0) + } + // only the max batch size are included in the list + ;[upkeepIDs, registries, topUpAmounts] = + await upkeepBalanceMonitor.getUnderfundedUpkeeps() + expect(upkeepIDs.length).to.equal(10) + expect(topUpAmounts.length).to.equal(10) + expect(upkeepIDs.map((v) => v.toNumber())).to.deep.equal([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + ]) + expect(registries).to.deep.equal([ + ...Array(9).fill(registry.address), + registry2.address, + ]) + expect(topUpAmounts.map((v) => v.toNumber())).to.deep.equal([ + ...Array(10).fill(300), + ]) + // update the balance for some upkeeps + await registry.mock.getBalance.withArgs(0).returns(300) + await registry.mock.getBalance.withArgs(5).returns(300) + ;[upkeepIDs, registries, topUpAmounts] = + await upkeepBalanceMonitor.getUnderfundedUpkeeps() + expect(upkeepIDs.length).to.equal(10) + expect(topUpAmounts.length).to.equal(10) + expect(upkeepIDs.map((v) => v.toNumber())).to.deep.equal([ + 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, + ]) + expect(registries).to.deep.equal([ + ...Array(7).fill(registry.address), + ...Array(3).fill(registry2.address), + ]) + expect(topUpAmounts.map((v) => v.toNumber())).to.deep.equal([ + ...Array(10).fill(300), + ]) + }) + }) + + describe('topUp()', () => { + beforeEach(async () => { + await registry.mock.onTokenTransfer + .withArgs( + upkeepBalanceMonitor.address, + 100, + ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), + ) + .returns() + await registry.mock.onTokenTransfer + .withArgs( + upkeepBalanceMonitor.address, + 50, + ethers.utils.defaultAbiCoder.encode(['uint256'], [7]), + ) + .returns() + }) + + it('cannot be called by a non-owner', async () => { + await expect( + upkeepBalanceMonitor.connect(stranger).topUp([], [], []), + ).to.be.revertedWith('OnlyForwarderOrOwner()') + }) + + it('should revert if the contract is paused', async () => { + await upkeepBalanceMonitor.connect(owner).pause() + await expect( + upkeepBalanceMonitor.connect(owner).topUp([], [], []), + ).to.be.revertedWith('Pausable: paused') + }) + + it('tops up the upkeeps by the amounts provided', async () => { + const initialBalance = await linkToken.balanceOf(registry.address) + const tx = await upkeepBalanceMonitor + .connect(owner) + .topUp([1, 7], [registry.address, registry.address], [100, 50]) + const finalBalance = await linkToken.balanceOf(registry.address) + expect(finalBalance).to.equal(initialBalance.add(150)) + await expect(tx) + .to.emit(upkeepBalanceMonitor, 'TopUpSucceeded') + .withArgs(1, 100) + await expect(tx) + .to.emit(upkeepBalanceMonitor, 'TopUpSucceeded') + .withArgs(7, 50) + }) + + it('does not abort if one top-up fails', async () => { + const initialBalance = await linkToken.balanceOf(registry.address) + const tx = await upkeepBalanceMonitor + .connect(owner) + .topUp( + [1, 7, 100], + [registry.address, registry.address, registry.address], + [100, 50, 100], + ) + const finalBalance = await linkToken.balanceOf(registry.address) + expect(finalBalance).to.equal(initialBalance.add(150)) + await expect(tx) + .to.emit(upkeepBalanceMonitor, 'TopUpSucceeded') + .withArgs(1, 100) + await expect(tx) + .to.emit(upkeepBalanceMonitor, 'TopUpSucceeded') + .withArgs(7, 50) + await expect(tx) + .to.emit(upkeepBalanceMonitor, 'TopUpFailed') + .withArgs(100) + }) + }) + + describe('checkUpkeep() / performUpkeep()', () => { + it('works round-trip', async () => { + await registry.mock.getBalance.withArgs(1).returns(100) // needs 200 + await registry.mock.getBalance.withArgs(7).returns(0) // needs 300 + await registry.mock.onTokenTransfer + .withArgs( + upkeepBalanceMonitor.address, + 200, + ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), + ) + .returns() + await registry.mock.onTokenTransfer + .withArgs( + upkeepBalanceMonitor.address, + 300, + ethers.utils.defaultAbiCoder.encode(['uint256'], [7]), + ) + .returns() + const [upkeepNeeded, performData] = + await upkeepBalanceMonitor.checkUpkeep('0x') + expect(upkeepNeeded).to.be.true + const initialBalance = await linkToken.balanceOf(registry.address) + await upkeepBalanceMonitor.connect(owner).performUpkeep(performData) + const finalBalance = await linkToken.balanceOf(registry.address) + expect(finalBalance).to.equal(initialBalance.add(500)) + }) + }) +}) diff --git a/contracts/test/v0.8/dev/ArbitrumValidator.test.ts b/contracts/test/v0.8/dev/ArbitrumValidator.test.ts index 2f95a6f6fb0..232eea95839 100644 --- a/contracts/test/v0.8/dev/ArbitrumValidator.test.ts +++ b/contracts/test/v0.8/dev/ArbitrumValidator.test.ts @@ -12,7 +12,7 @@ import { abi as arbitrumSequencerStatusRecorderAbi } from '../../../artifacts/sr // @ts-ignore import { abi as arbitrumInboxAbi } from '../../../artifacts/src/v0.8/vendor/arb-bridge-eth/v0.8.0-custom/contracts/bridge/interfaces/IInbox.sol/IInbox.json' // @ts-ignore -import { abi as aggregatorAbi } from '../../../artifacts/src/v0.8/interfaces/AggregatorV2V3Interface.sol/AggregatorV2V3Interface.json' +import { abi as aggregatorAbi } from '../../../artifacts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol/AggregatorV2V3Interface.json' const truncateBigNumToAddress = (num: BigNumberish) => { // Pad, then slice off '0x' prefix diff --git a/contracts/test/v0.8/dev/OptimismValidator.test.ts b/contracts/test/v0.8/dev/OptimismValidator.test.ts index 120b1057d14..ee69211f56d 100644 --- a/contracts/test/v0.8/dev/OptimismValidator.test.ts +++ b/contracts/test/v0.8/dev/OptimismValidator.test.ts @@ -8,7 +8,7 @@ import { abi as optimismSequencerStatusRecorderAbi } from '../../../artifacts/sr // @ts-ignore import { abi as optimismL1CrossDomainMessengerAbi } from '@eth-optimism/contracts/artifacts/contracts/L1/messaging/L1CrossDomainMessenger.sol' // @ts-ignore -import { abi as aggregatorAbi } from '../../../artifacts/src/v0.8/interfaces/AggregatorV2V3Interface.sol/AggregatorV2V3Interface.json' +import { abi as aggregatorAbi } from '../../../artifacts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol/AggregatorV2V3Interface.json' describe('OptimismValidator', () => { const GAS_LIMIT = BigNumber.from(1_900_000) diff --git a/core/assets/currencies.go b/core/assets/currencies.go deleted file mode 100644 index 1201f0be35c..00000000000 --- a/core/assets/currencies.go +++ /dev/null @@ -1,301 +0,0 @@ -package assets - -import ( - "database/sql/driver" - "fmt" - "math/big" - "strings" - - "github.com/smartcontractkit/chainlink/v2/core/utils" - - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - "github.com/shopspring/decimal" -) - -var ErrNoQuotesForCurrency = errors.New("cannot unmarshal json.Number into currency") - -// getDenominator returns 10**precision. -func getDenominator(precision int) *big.Int { - x := big.NewInt(10) - return new(big.Int).Exp(x, big.NewInt(int64(precision)), nil) -} - -func format(i *big.Int, precision int) string { - r := big.NewRat(1, 1).SetFrac(i, getDenominator(precision)) - return fmt.Sprintf("%v", r.FloatString(precision)) -} - -// Link contains a field to represent the smallest units of LINK -type Link big.Int - -// NewLinkFromJuels returns a new struct to represent LINK from it's smallest unit -func NewLinkFromJuels(w int64) *Link { - return (*Link)(big.NewInt(w)) -} - -// String returns Link formatted as a string. -func (l *Link) String() string { - if l == nil { - return "0" - } - return fmt.Sprintf("%v", (*big.Int)(l)) -} - -// Link returns Link formatted as a string, in LINK units -func (l *Link) Link() string { - if l == nil { - return "0" - } - return format((*big.Int)(l), 18) -} - -// SetInt64 delegates to *big.Int.SetInt64 -func (l *Link) SetInt64(w int64) *Link { - return (*Link)((*big.Int)(l).SetInt64(w)) -} - -// ToInt returns the Link value as a *big.Int. -func (l *Link) ToInt() *big.Int { - return (*big.Int)(l) -} - -// ToHash returns a 32 byte representation of this value -func (l *Link) ToHash() common.Hash { - return common.BigToHash((*big.Int)(l)) -} - -// Set delegates to *big.Int.Set -func (l *Link) Set(x *Link) *Link { - il := (*big.Int)(l) - ix := (*big.Int)(x) - - w := il.Set(ix) - return (*Link)(w) -} - -// SetString delegates to *big.Int.SetString -func (l *Link) SetString(s string, base int) (*Link, bool) { - w, ok := (*big.Int)(l).SetString(s, base) - return (*Link)(w), ok -} - -// Cmp defers to big.Int Cmp -func (l *Link) Cmp(y *Link) int { - return (*big.Int)(l).Cmp((*big.Int)(y)) -} - -// Add defers to big.Int Add -func (l *Link) Add(x, y *Link) *Link { - il := (*big.Int)(l) - ix := (*big.Int)(x) - iy := (*big.Int)(y) - - return (*Link)(il.Add(ix, iy)) -} - -// Text defers to big.Int Text -func (l *Link) Text(base int) string { - return (*big.Int)(l).Text(base) -} - -var linkFmtThreshold = (*Link)(new(big.Int).Exp(big.NewInt(10), big.NewInt(12), nil)) - -// MarshalText implements the encoding.TextMarshaler interface. -func (l *Link) MarshalText() ([]byte, error) { - if l.Cmp(linkFmtThreshold) >= 0 { - return []byte(fmt.Sprintf("%s link", decimal.NewFromBigInt(l.ToInt(), -18))), nil - } - return (*big.Int)(l).MarshalText() -} - -// MarshalJSON implements the json.Marshaler interface. -func (l Link) MarshalJSON() ([]byte, error) { - value, err := l.MarshalText() - if err != nil { - return nil, err - } - return []byte(fmt.Sprintf(`"%s"`, value)), nil -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -func (l *Link) UnmarshalJSON(data []byte) error { - if utils.IsQuoted(data) { - return l.UnmarshalText(utils.RemoveQuotes(data)) - } - return ErrNoQuotesForCurrency -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -func (l *Link) UnmarshalText(text []byte) error { - s := string(text) - if strings.HasSuffix(s, "link") { - s = strings.TrimSuffix(s, "link") - s = strings.TrimSuffix(s, " ") - d, err := decimal.NewFromString(s) - if err != nil { - return errors.Wrapf(err, "assets: cannot unmarshal %q into a *assets.Link", text) - } - d = d.Mul(decimal.New(1, 18)) - if !d.IsInteger() { - err := errors.New("maximum precision is juels") - return errors.Wrapf(err, "assets: cannot unmarshal %q into a *assets.Link", text) - } - l.Set((*Link)(d.Rat().Num())) - return nil - } - if strings.HasSuffix(s, "juels") { - s = strings.TrimSuffix(s, "juels") - s = strings.TrimSuffix(s, " ") - } - if _, ok := l.SetString(s, 10); !ok { - return errors.Errorf("assets: cannot unmarshal %q into a *assets.Link", text) - } - return nil -} - -// IsZero returns true when the value is 0 and false otherwise -func (l *Link) IsZero() bool { - zero := big.NewInt(0) - return (*big.Int)(l).Cmp(zero) == 0 -} - -// Symbol returns LINK -func (*Link) Symbol() string { - return "LINK" -} - -// Value returns the Link value for serialization to database. -func (l Link) Value() (driver.Value, error) { - b := (big.Int)(l) - return b.String(), nil -} - -// Scan reads the database value and returns an instance. -func (l *Link) Scan(value interface{}) error { - switch v := value.(type) { - case string: - decoded, ok := l.SetString(v, 10) - if !ok { - return fmt.Errorf("unable to set string %v of %T to base 10 big.Int for Link", value, value) - } - *l = *decoded - case []uint8: - // The SQL library returns numeric() types as []uint8 of the string representation - decoded, ok := l.SetString(string(v), 10) - if !ok { - return fmt.Errorf("unable to set string %v of %T to base 10 big.Int for Link", value, value) - } - *l = *decoded - case int64: - return fmt.Errorf("unable to convert %v of %T to Link, is the sql type set to varchar?", value, value) - default: - return fmt.Errorf("unable to convert %v of %T to Link", value, value) - } - - return nil -} - -// Eth contains a field to represent the smallest units of ETH -type Eth big.Int - -// NewEth returns a new struct to represent ETH from it's smallest unit (wei) -func NewEth(w int64) *Eth { - return (*Eth)(big.NewInt(w)) -} - -// NewEthValue returns a new struct to represent ETH from it's smallest unit (wei) -func NewEthValue(w int64) Eth { - eth := NewEth(w) - return *eth -} - -// NewEthValueS returns a new struct to represent ETH from a string value of Eth (not wei) -// the underlying value is still wei -func NewEthValueS(s string) (Eth, error) { - e, err := decimal.NewFromString(s) - if err != nil { - return Eth{}, err - } - w := e.Mul(decimal.RequireFromString("10").Pow(decimal.RequireFromString("18"))) - return *(*Eth)(w.BigInt()), nil -} - -// Cmp delegates to *big.Int.Cmp -func (e *Eth) Cmp(y *Eth) int { - return e.ToInt().Cmp(y.ToInt()) -} - -func (e *Eth) String() string { - if e == nil { - return "" - } - return format(e.ToInt(), 18) -} - -// SetInt64 delegates to *big.Int.SetInt64 -func (e *Eth) SetInt64(w int64) *Eth { - return (*Eth)(e.ToInt().SetInt64(w)) -} - -// SetString delegates to *big.Int.SetString -func (e *Eth) SetString(s string, base int) (*Eth, bool) { - w, ok := e.ToInt().SetString(s, base) - return (*Eth)(w), ok -} - -// MarshalJSON implements the json.Marshaler interface. -func (e Eth) MarshalJSON() ([]byte, error) { - value, err := e.MarshalText() - if err != nil { - return nil, err - } - return []byte(fmt.Sprintf(`"%s"`, value)), nil -} - -// MarshalText implements the encoding.TextMarshaler interface. -func (e *Eth) MarshalText() ([]byte, error) { - return e.ToInt().MarshalText() -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -func (e *Eth) UnmarshalJSON(data []byte) error { - if utils.IsQuoted(data) { - return e.UnmarshalText(utils.RemoveQuotes(data)) - } - return ErrNoQuotesForCurrency -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -func (e *Eth) UnmarshalText(text []byte) error { - if _, ok := e.SetString(string(text), 10); !ok { - return fmt.Errorf("assets: cannot unmarshal %q into a *assets.Eth", text) - } - return nil -} - -// IsZero returns true when the value is 0 and false otherwise -func (e *Eth) IsZero() bool { - zero := big.NewInt(0) - return e.ToInt().Cmp(zero) == 0 -} - -// Symbol returns ETH -func (*Eth) Symbol() string { - return "ETH" -} - -// ToInt returns the Eth value as a *big.Int. -func (e *Eth) ToInt() *big.Int { - return (*big.Int)(e) -} - -// Scan reads the database value and returns an instance. -func (e *Eth) Scan(value interface{}) error { - return (*utils.Big)(e).Scan(value) -} - -// Value returns the Eth value for serialization to database. -func (e Eth) Value() (driver.Value, error) { - return (utils.Big)(e).Value() -} diff --git a/core/assets/currencies_test.go b/core/assets/currencies_test.go deleted file mode 100644 index 5f2a00223f5..00000000000 --- a/core/assets/currencies_test.go +++ /dev/null @@ -1,357 +0,0 @@ -package assets_test - -import ( - "encoding/json" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink/v2/core/assets" -) - -func TestAssets_NewLinkAndString(t *testing.T) { - t.Parallel() - - link := assets.NewLinkFromJuels(0) - - assert.Equal(t, "0", link.String()) - - link.SetInt64(1) - assert.Equal(t, "1", link.String()) - - link.SetString("900000000000000000", 10) - assert.Equal(t, "900000000000000000", link.String()) - - link.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639935", 10) - assert.Equal(t, "115792089237316195423570985008687907853269984665640564039457584007913129639935", link.String()) - - var nilLink *assets.Link - assert.Equal(t, "0", nilLink.String()) -} - -func TestAssets_NewLinkAndLink(t *testing.T) { - t.Parallel() - - link := assets.NewLinkFromJuels(0) - - assert.Equal(t, "0.000000000000000000", link.Link()) - - link.SetInt64(1) - assert.Equal(t, "0.000000000000000001", link.Link()) - - link.SetString("900000000000000000", 10) - assert.Equal(t, "0.900000000000000000", link.Link()) - - link.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639935", 10) - assert.Equal(t, "115792089237316195423570985008687907853269984665640564039457.584007913129639935", link.Link()) - - link.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639936", 10) - assert.Equal(t, "115792089237316195423570985008687907853269984665640564039457.584007913129639936", link.Link()) - - var nilLink *assets.Link - assert.Equal(t, "0", nilLink.Link()) -} - -func TestAssets_Link_MarshalJson(t *testing.T) { - t.Parallel() - - link := assets.NewLinkFromJuels(1) - - b, err := json.Marshal(link) - assert.NoError(t, err) - assert.Equal(t, []byte(`"1"`), b) -} - -func TestAssets_Link_UnmarshalJsonOk(t *testing.T) { - t.Parallel() - - link := assets.Link{} - - err := json.Unmarshal([]byte(`"1"`), &link) - assert.NoError(t, err) - assert.Equal(t, "0.000000000000000001", link.Link()) -} - -func TestAssets_Link_UnmarshalJsonError(t *testing.T) { - t.Parallel() - - link := assets.Link{} - - err := json.Unmarshal([]byte(`"x"`), &link) - assert.EqualError(t, err, "assets: cannot unmarshal \"x\" into a *assets.Link") - - err = json.Unmarshal([]byte(`1`), &link) - assert.Equal(t, assets.ErrNoQuotesForCurrency, err) -} - -func TestAssets_NewEthAndString(t *testing.T) { - t.Parallel() - - eth := assets.NewEth(0) - - assert.Equal(t, "0.000000000000000000", eth.String()) - - eth.SetInt64(1) - assert.Equal(t, "0.000000000000000001", eth.String()) - - eth.SetString("900000000000000000", 10) - assert.Equal(t, "0.900000000000000000", eth.String()) - - eth.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639935", 10) - assert.Equal(t, "115792089237316195423570985008687907853269984665640564039457.584007913129639935", eth.String()) - - eth.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639936", 10) - assert.Equal(t, "115792089237316195423570985008687907853269984665640564039457.584007913129639936", eth.String()) -} - -func TestAssets_Eth_IsZero(t *testing.T) { - t.Parallel() - - zeroEth := assets.NewEth(0) - assert.True(t, zeroEth.IsZero()) - - oneLink := assets.NewEth(1) - assert.False(t, oneLink.IsZero()) -} - -func TestAssets_Eth_MarshalJson(t *testing.T) { - t.Parallel() - - eth := assets.NewEth(1) - - b, err := json.Marshal(eth) - assert.NoError(t, err) - assert.Equal(t, []byte(`"1"`), b) -} - -func TestAssets_Eth_UnmarshalJsonOk(t *testing.T) { - t.Parallel() - - eth := assets.Eth{} - - err := json.Unmarshal([]byte(`"1"`), ð) - assert.NoError(t, err) - assert.Equal(t, "0.000000000000000001", eth.String()) -} - -func TestAssets_Eth_UnmarshalJsonError(t *testing.T) { - t.Parallel() - - eth := assets.Eth{} - - err := json.Unmarshal([]byte(`"x"`), ð) - assert.EqualError(t, err, "assets: cannot unmarshal \"x\" into a *assets.Eth") - - err = json.Unmarshal([]byte(`1`), ð) - assert.Equal(t, assets.ErrNoQuotesForCurrency, err) -} - -func TestAssets_LinkToInt(t *testing.T) { - t.Parallel() - - link := assets.NewLinkFromJuels(0) - assert.Equal(t, big.NewInt(0), link.ToInt()) - - link = assets.NewLinkFromJuels(123) - assert.Equal(t, big.NewInt(123), link.ToInt()) -} - -func TestAssets_LinkToHash(t *testing.T) { - t.Parallel() - - link := assets.NewLinkFromJuels(123) - expected := common.BigToHash((*big.Int)(link)) - assert.Equal(t, expected, link.ToHash()) -} - -func TestAssets_LinkSetLink(t *testing.T) { - t.Parallel() - - link1 := assets.NewLinkFromJuels(123) - link2 := assets.NewLinkFromJuels(321) - link3 := link1.Set(link2) - assert.Equal(t, link3, link2) -} - -func TestAssets_LinkCmpLink(t *testing.T) { - t.Parallel() - - link1 := assets.NewLinkFromJuels(123) - link2 := assets.NewLinkFromJuels(321) - assert.NotZero(t, link1.Cmp(link2)) - - link3 := assets.NewLinkFromJuels(321) - assert.Zero(t, link3.Cmp(link2)) -} - -func TestAssets_LinkAddLink(t *testing.T) { - t.Parallel() - - link1 := assets.NewLinkFromJuels(123) - link2 := assets.NewLinkFromJuels(321) - sum := assets.NewLinkFromJuels(123 + 321) - assert.Equal(t, sum, link1.Add(link1, link2)) -} - -func TestAssets_LinkText(t *testing.T) { - t.Parallel() - - link := assets.NewLinkFromJuels(123) - assert.Equal(t, "123", link.Text(10)) - assert.Equal(t, "7b", link.Text(16)) -} - -func TestAssets_LinkIsZero(t *testing.T) { - t.Parallel() - - link := assets.NewLinkFromJuels(123) - assert.False(t, link.IsZero()) - - link = assets.NewLinkFromJuels(0) - assert.True(t, link.IsZero()) -} - -func TestAssets_LinkSymbol(t *testing.T) { - t.Parallel() - - link := assets.NewLinkFromJuels(123) - assert.Equal(t, "LINK", link.Symbol()) -} - -func TestAssets_LinkScanValue(t *testing.T) { - t.Parallel() - - link := assets.NewLinkFromJuels(123) - v, err := link.Value() - assert.NoError(t, err) - - link2 := assets.NewLinkFromJuels(0) - err = link2.Scan(v) - assert.NoError(t, err) - assert.Equal(t, link2, link) - - err = link2.Scan("123") - assert.NoError(t, err) - assert.Equal(t, link2, link) - - err = link2.Scan([]uint8{'1', '2', '3'}) - assert.NoError(t, err) - assert.Equal(t, link2, link) - - assert.ErrorContains(t, link2.Scan([]uint8{'x'}), "unable to set string") - assert.ErrorContains(t, link2.Scan("123.56"), "unable to set string") - assert.ErrorContains(t, link2.Scan(1.5), "unable to convert") - assert.ErrorContains(t, link2.Scan(int64(123)), "unable to convert") -} - -func TestAssets_NewEth(t *testing.T) { - t.Parallel() - - ethRef := assets.NewEth(123) - ethVal := assets.NewEthValue(123) - ethStr, err := assets.NewEthValueS(ethRef.String()) - assert.NoError(t, err) - assert.Equal(t, *ethRef, ethVal) - assert.Equal(t, *ethRef, ethStr) -} - -func TestAssets_EthSymbol(t *testing.T) { - t.Parallel() - - eth := assets.NewEth(123) - assert.Equal(t, "ETH", eth.Symbol()) -} - -func TestAssets_EthScanValue(t *testing.T) { - t.Parallel() - - eth := assets.NewEth(123) - v, err := eth.Value() - assert.NoError(t, err) - - eth2 := assets.NewEth(0) - err = eth2.Scan(v) - assert.NoError(t, err) - - assert.Equal(t, eth, eth2) -} - -func TestAssets_EthCmpEth(t *testing.T) { - t.Parallel() - - eth1 := assets.NewEth(123) - eth2 := assets.NewEth(321) - assert.NotZero(t, eth1.Cmp(eth2)) - - eth3 := assets.NewEth(321) - assert.Zero(t, eth3.Cmp(eth2)) -} - -func TestLink(t *testing.T) { - for _, tt := range []struct { - input string - exp string - }{ - {"0", "0"}, - {"1", "1"}, - {"1 juels", "1"}, - {"100000000000", "100000000000"}, - {"0.0000001 link", "100000000000"}, - {"1000000000000", "0.000001 link"}, - {"100000000000000", "0.0001 link"}, - {"0.0001 link", "0.0001 link"}, - {"10000000000000000", "0.01 link"}, - {"0.01 link", "0.01 link"}, - {"100000000000000000", "0.1 link"}, - {"0.1 link", "0.1 link"}, - {"1.0 link", "1 link"}, - {"1000000000000000000", "1 link"}, - {"1000000000000000000 juels", "1 link"}, - {"1100000000000000000", "1.1 link"}, - {"1.1link", "1.1 link"}, - {"1.1 link", "1.1 link"}, - } { - t.Run(tt.input, func(t *testing.T) { - var l assets.Link - err := l.UnmarshalText([]byte(tt.input)) - require.NoError(t, err) - b, err := l.MarshalText() - require.NoError(t, err) - assert.Equal(t, tt.exp, string(b)) - }) - } -} - -func FuzzLink(f *testing.F) { - f.Add("1") - f.Add("1 link") - f.Add("1.1link") - f.Add("2.3") - f.Add("2.3 link") - f.Add("00005 link") - f.Add("0.0005link") - f.Add("1100000000000000000000000000000") - f.Add("1100000000000000000000000000000 juels") - f.Fuzz(func(t *testing.T, v string) { - if len(v) > 1_000 { - t.Skip() - } - var l assets.Link - err := l.UnmarshalText([]byte(v)) - if err != nil { - t.Skip() - } - - b, err := l.MarshalText() - require.NoErrorf(t, err, "failed to marshal %v after unmarshaling from %q", l, v) - - var l2 assets.Link - err = l2.UnmarshalText(b) - require.NoErrorf(t, err, "failed to unmarshal %s after marshaling from %v", string(b), l) - require.Equal(t, l, l2, "unequal values after marshal/unmarshal") - }) -} diff --git a/core/bridges/bridge_type.go b/core/bridges/bridge_type.go index 9b3a8570ca2..b2e86df4b9f 100644 --- a/core/bridges/bridge_type.go +++ b/core/bridges/bridge_type.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/bridges/bridge_type_test.go b/core/bridges/bridge_type_test.go index 8c4a7cbace0..6a719d5b539 100644 --- a/core/bridges/bridge_type_test.go +++ b/core/bridges/bridge_type_test.go @@ -11,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/store/models" diff --git a/core/bridges/orm.go b/core/bridges/orm.go index 96801f4484c..cfad1da836e 100644 --- a/core/bridges/orm.go +++ b/core/bridges/orm.go @@ -6,8 +6,8 @@ import ( "sync" "time" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/bridges/orm_test.go b/core/bridges/orm_test.go index db043393350..0b485764c8b 100644 --- a/core/bridges/orm_test.go +++ b/core/bridges/orm_test.go @@ -5,15 +5,16 @@ import ( "time" "github.com/google/uuid" - "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" diff --git a/core/cbor/cbor.go b/core/cbor/cbor.go index 754e5729345..cc3f74e423e 100644 --- a/core/cbor/cbor.go +++ b/core/cbor/cbor.go @@ -17,7 +17,7 @@ func ParseDietCBOR(b []byte) (map[string]interface{}, error) { b = autoAddMapDelimiters(b) var m map[interface{}]interface{} - if err := cbor.Unmarshal(b, &m); err != nil { + if _, err := cbor.UnmarshalFirst(b, &m); err != nil { return nil, err } @@ -38,7 +38,8 @@ func ParseDietCBOR(b []byte) (map[string]interface{}, error) { // "top-level map" requirement of "diet" CBOR. func ParseDietCBORToStruct(b []byte, v interface{}) error { b = autoAddMapDelimiters(b) - return cbor.Unmarshal(b, v) + _, err := cbor.UnmarshalFirst(b, v) + return err } // ParseStandardCBOR parses CBOR in "standards compliant" mode. @@ -49,7 +50,7 @@ func ParseStandardCBOR(b []byte) (a interface{}, err error) { if len(b) == 0 { return nil, nil } - if err = cbor.Unmarshal(b, &a); err != nil { + if _, err = cbor.UnmarshalFirst(b, &a); err != nil { return nil, err } return diff --git a/core/chainlink.Dockerfile b/core/chainlink.Dockerfile index be1fdb9053b..22e65c0aa7a 100644 --- a/core/chainlink.Dockerfile +++ b/core/chainlink.Dockerfile @@ -17,6 +17,22 @@ COPY . . # Build the golang binary RUN make install-chainlink +# Link LOOP Plugin source dirs with simple names +RUN go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-feeds | xargs -I % ln -s % /chainlink-feeds +RUN go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-solana | xargs -I % ln -s % /chainlink-solana + +# Build image: Plugins +FROM golang:1.21-bullseye as buildplugins +RUN go version + +WORKDIR /chainlink-feeds +COPY --from=buildgo /chainlink-feeds . +RUN go install ./cmd/chainlink-feeds + +WORKDIR /chainlink-solana +COPY --from=buildgo /chainlink-solana . +RUN go install ./pkg/solana/cmd/chainlink-solana + # Final image: ubuntu with chainlink binary FROM ubuntu:20.04 @@ -32,6 +48,10 @@ RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ COPY --from=buildgo /go/bin/chainlink /usr/local/bin/ +# Install (but don't enable) LOOP Plugins +COPY --from=buildplugins /go/bin/chainlink-feeds /usr/local/bin/ +COPY --from=buildplugins /go/bin/chainlink-solana /usr/local/bin/ + # Dependency of CosmWasm/wasmd COPY --from=buildgo /go/pkg/mod/github.com/\!cosm\!wasm/wasmvm@v*/internal/api/libwasmvm.*.so /usr/lib/ RUN chmod 755 /usr/lib/libwasmvm.*.so diff --git a/core/chainlink.devspace.Dockerfile b/core/chainlink.devspace.Dockerfile index 9ec061ae40d..88d3cec16ad 100644 --- a/core/chainlink.devspace.Dockerfile +++ b/core/chainlink.devspace.Dockerfile @@ -20,7 +20,7 @@ RUN make install-chainlink # Final image: ubuntu with chainlink binary FROM golang:1.21-bullseye -ARG CHAINLINK_USER=root +ARG CHAINLINK_USER=chainlink ENV DEBIAN_FRONTEND noninteractive RUN apt-get update && apt-get install -y ca-certificates gnupg lsb-release curl diff --git a/core/chains/chain_kv.go b/core/chains/chain_kv.go index 5094f2885e5..4365a859bb2 100644 --- a/core/chains/chain_kv.go +++ b/core/chains/chain_kv.go @@ -6,7 +6,7 @@ import ( "golang.org/x/exp/maps" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" ) type ChainsKV[T types.ChainService] struct { diff --git a/core/chains/chain_kv_test.go b/core/chains/chain_kv_test.go index a30de3090b8..205ee693d6f 100644 --- a/core/chains/chain_kv_test.go +++ b/core/chains/chain_kv_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/chains" ) diff --git a/core/chains/cosmos/chain.go b/core/chains/cosmos/chain.go deleted file mode 100644 index 43ba5a4e796..00000000000 --- a/core/chains/cosmos/chain.go +++ /dev/null @@ -1,250 +0,0 @@ -package cosmos - -import ( - "context" - "crypto/rand" - "fmt" - "math/big" - "time" - - "github.com/pkg/errors" - "go.uber.org/multierr" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/smartcontractkit/sqlx" - - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/adapters" - cosmosclient "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/client" - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/db" - relaychains "github.com/smartcontractkit/chainlink-relay/pkg/chains" - "github.com/smartcontractkit/chainlink-relay/pkg/logger" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - "github.com/smartcontractkit/chainlink-relay/pkg/services" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" - - "github.com/smartcontractkit/chainlink/v2/core/chains" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos/cosmostxm" - "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/utils" -) - -// DefaultRequestTimeout is the default Cosmos client timeout. -// Note that while the cosmos node is processing a heavy block, -// requests can be delayed significantly (https://github.com/tendermint/tendermint/issues/6899), -// however there's nothing we can do but wait until the block is processed. -// So we set a fairly high timeout here. -// TODO(BCI-979): Remove this, or make this configurable with the updated client. -const DefaultRequestTimeout = 30 * time.Second - -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") -) - -// Chain is a wrap for easy use in other places in the core node -type Chain = adapters.Chain - -// ChainOpts holds options for configuring a Chain. -type ChainOpts struct { - QueryConfig pg.QConfig - Logger logger.Logger - DB *sqlx.DB - KeyStore loop.Keystore - EventBroadcaster pg.EventBroadcaster -} - -func (o *ChainOpts) Validate() (err error) { - required := func(s string) error { - return fmt.Errorf("%s is required", s) - } - if o.QueryConfig == nil { - err = multierr.Append(err, required("Config")) - } - if o.Logger == nil { - err = multierr.Append(err, required("Logger'")) - } - if o.DB == nil { - err = multierr.Append(err, required("DB")) - } - if o.KeyStore == nil { - err = multierr.Append(err, required("KeyStore")) - } - if o.EventBroadcaster == nil { - err = multierr.Append(err, required("EventBroadcaster")) - } - return -} - -func NewChain(cfg *CosmosConfig, opts ChainOpts) (adapters.Chain, error) { - if !cfg.IsEnabled() { - return nil, fmt.Errorf("cannot create new chain with ID %s, the chain is disabled", *cfg.ChainID) - } - c, err := newChain(*cfg.ChainID, cfg, opts.DB, opts.KeyStore, opts.QueryConfig, opts.EventBroadcaster, opts.Logger) - if err != nil { - return nil, err - } - return c, nil -} - -var _ adapters.Chain = (*chain)(nil) - -type chain struct { - utils.StartStopOnce - id string - cfg *CosmosConfig - txm *cosmostxm.Txm - lggr logger.Logger -} - -func newChain(id string, cfg *CosmosConfig, db *sqlx.DB, ks loop.Keystore, logCfg pg.QConfig, eb pg.EventBroadcaster, lggr logger.Logger) (*chain, error) { - lggr = logger.With(lggr, "cosmosChainID", id) - var ch = chain{ - id: id, - cfg: cfg, - lggr: logger.Named(lggr, "Chain"), - } - tc := func() (cosmosclient.ReaderWriter, error) { - return ch.getClient("") - } - gpe := cosmosclient.NewMustGasPriceEstimator([]cosmosclient.GasPricesEstimator{ - cosmosclient.NewClosureGasPriceEstimator(func() (map[string]sdk.DecCoin, error) { - return map[string]sdk.DecCoin{ - cfg.GasToken(): sdk.NewDecCoinFromDec(cfg.GasToken(), cfg.FallbackGasPrice()), - }, nil - }), - }, lggr) - ch.txm = cosmostxm.NewTxm(db, tc, *gpe, ch.id, cfg, ks, lggr, logCfg, eb) - - return &ch, nil -} - -func (c *chain) Name() string { - return c.lggr.Name() -} - -func (c *chain) ID() string { - return c.id -} - -func (c *chain) ChainID() string { - return c.id -} - -func (c *chain) Config() coscfg.Config { - return c.cfg -} - -func (c *chain) TxManager() adapters.TxManager { - return c.txm -} - -func (c *chain) Reader(name string) (cosmosclient.Reader, error) { - return c.getClient(name) -} - -// getClient returns a client, optionally requiring a specific node by name. -func (c *chain) getClient(name string) (cosmosclient.ReaderWriter, error) { - var node db.Node - if name == "" { // Any node - nodes, err := c.cfg.ListNodes() - if err != nil { - return nil, fmt.Errorf("failed to list nodes: %w", err) - } - if len(nodes) == 0 { - return nil, errors.New("no nodes available") - } - nodeIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(nodes)))) - if err != nil { - return nil, fmt.Errorf("could not generate a random node index: %w", err) - } - node = nodes[nodeIndex.Int64()] - } else { // Named node - var err error - node, err = c.cfg.GetNode(name) - if err != nil { - return nil, fmt.Errorf("failed to get node named %s: %w", name, err) - } - if node.CosmosChainID != c.id { - return nil, fmt.Errorf("failed to create client for chain %s with node %s: wrong chain id %s", c.id, name, node.CosmosChainID) - } - } - client, err := cosmosclient.NewClient(c.id, node.TendermintURL, DefaultRequestTimeout, logger.Named(c.lggr, "Client."+name)) - if err != nil { - return nil, fmt.Errorf("failed to create client: %w", err) - } - c.lggr.Debugw("Created client", "name", node.Name, "tendermint-url", node.TendermintURL) - return client, nil -} - -// Start starts cosmos chain. -func (c *chain) Start(ctx context.Context) error { - return c.StartOnce("Chain", func() error { - c.lggr.Debug("Starting") - return c.txm.Start(ctx) - }) -} - -func (c *chain) Close() error { - return c.StopOnce("Chain", func() error { - c.lggr.Debug("Stopping") - return c.txm.Close() - }) -} - -func (c *chain) Ready() error { - return multierr.Combine( - c.StartStopOnce.Ready(), - c.txm.Ready(), - ) -} - -func (c *chain) HealthReport() map[string]error { - m := map[string]error{c.Name(): c.Healthy()} - services.CopyHealth(m, c.txm.HealthReport()) - return m -} - -// ChainService interface -func (c *chain) GetChainStatus(ctx context.Context) (relaytypes.ChainStatus, error) { - toml, err := c.cfg.TOMLString() - if err != nil { - return relaytypes.ChainStatus{}, err - } - return relaytypes.ChainStatus{ - ID: c.id, - Enabled: *c.cfg.Enabled, - Config: toml, - }, nil -} -func (c *chain) ListNodeStatuses(ctx context.Context, pageSize int32, pageToken string) (stats []relaytypes.NodeStatus, nextPageToken string, total int, err error) { - return relaychains.ListNodeStatuses(int(pageSize), pageToken, c.listNodeStatuses) -} - -func (c *chain) Transact(ctx context.Context, from, to string, amount *big.Int, balanceCheck bool) error { - return chains.ErrLOOPPUnsupported -} - -// TODO BCF-2602 statuses are static for non-evm chain and should be dynamic -func (c *chain) listNodeStatuses(start, end int) ([]relaytypes.NodeStatus, int, error) { - stats := make([]relaytypes.NodeStatus, 0) - total := len(c.cfg.Nodes) - if start >= total { - return stats, total, relaychains.ErrOutOfRange - } - if end > total { - end = total - } - nodes := c.cfg.Nodes[start:end] - for _, node := range nodes { - stat, err := nodeStatus(node, c.ChainID()) - if err != nil { - return stats, total, err - } - stats = append(stats, stat) - } - return stats, total, nil -} diff --git a/core/chains/cosmos/config.go b/core/chains/cosmos/config.go deleted file mode 100644 index 8b4c8c13f32..00000000000 --- a/core/chains/cosmos/config.go +++ /dev/null @@ -1,272 +0,0 @@ -package cosmos - -import ( - "fmt" - "net/url" - "slices" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/pelletier/go-toml/v2" - "github.com/shopspring/decimal" - "go.uber.org/multierr" - - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/db" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" - - "github.com/smartcontractkit/chainlink/v2/core/chains" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/utils/config" -) - -type CosmosConfigs []*CosmosConfig - -func (cs CosmosConfigs) validateKeys() (err error) { - // Unique chain IDs - chainIDs := config.UniqueStrings{} - for i, c := range cs { - if chainIDs.IsDupe(c.ChainID) { - err = multierr.Append(err, config.NewErrDuplicate(fmt.Sprintf("%d.ChainID", i), *c.ChainID)) - } - } - - // Unique node names - names := config.UniqueStrings{} - for i, c := range cs { - for j, n := range c.Nodes { - if names.IsDupe(n.Name) { - err = multierr.Append(err, config.NewErrDuplicate(fmt.Sprintf("%d.Nodes.%d.Name", i, j), *n.Name)) - } - } - } - - // Unique TendermintURLs - urls := config.UniqueStrings{} - for i, c := range cs { - for j, n := range c.Nodes { - u := (*url.URL)(n.TendermintURL) - if urls.IsDupeFmt(u) { - err = multierr.Append(err, config.NewErrDuplicate(fmt.Sprintf("%d.Nodes.%d.TendermintURL", i, j), u.String())) - } - } - } - return - -} - -func (cs CosmosConfigs) ValidateConfig() (err error) { - return cs.validateKeys() -} - -func (cs *CosmosConfigs) SetFrom(fs *CosmosConfigs) (err error) { - if err1 := fs.validateKeys(); err1 != nil { - return err1 - } - for _, f := range *fs { - if f.ChainID == nil { - *cs = append(*cs, f) - } else if i := slices.IndexFunc(*cs, func(c *CosmosConfig) bool { - return c.ChainID != nil && *c.ChainID == *f.ChainID - }); i == -1 { - *cs = append(*cs, f) - } else { - (*cs)[i].SetFrom(f) - } - } - return -} - -func nodeStatus(n *coscfg.Node, id relay.ChainID) (relaytypes.NodeStatus, error) { - var s relaytypes.NodeStatus - s.ChainID = id - s.Name = *n.Name - b, err := toml.Marshal(n) - if err != nil { - return relaytypes.NodeStatus{}, err - } - s.Config = string(b) - return s, nil -} - -type CosmosNodes []*coscfg.Node - -func (ns *CosmosNodes) SetFrom(fs *CosmosNodes) { - for _, f := range *fs { - if f.Name == nil { - *ns = append(*ns, f) - } else if i := slices.IndexFunc(*ns, func(n *coscfg.Node) bool { - return n.Name != nil && *n.Name == *f.Name - }); i == -1 { - *ns = append(*ns, f) - } else { - setFromNode((*ns)[i], f) - } - } -} - -func setFromNode(n, f *coscfg.Node) { - if f.Name != nil { - n.Name = f.Name - } - if f.TendermintURL != nil { - n.TendermintURL = f.TendermintURL - } -} - -func legacyNode(n *coscfg.Node, id string) db.Node { - return db.Node{ - Name: *n.Name, - CosmosChainID: id, - TendermintURL: (*url.URL)(n.TendermintURL).String(), - } -} - -type CosmosConfig struct { - ChainID *string - // Do not access directly. Use [IsEnabled] - Enabled *bool - coscfg.Chain - Nodes CosmosNodes -} - -func (c *CosmosConfig) IsEnabled() bool { - return c.Enabled == nil || *c.Enabled -} - -func (c *CosmosConfig) SetFrom(f *CosmosConfig) { - if f.ChainID != nil { - c.ChainID = f.ChainID - } - if f.Enabled != nil { - c.Enabled = f.Enabled - } - setFromChain(&c.Chain, &f.Chain) - c.Nodes.SetFrom(&f.Nodes) -} - -func setFromChain(c, f *coscfg.Chain) { - if f.Bech32Prefix != nil { - c.Bech32Prefix = f.Bech32Prefix - } - if f.BlockRate != nil { - c.BlockRate = f.BlockRate - } - if f.BlocksUntilTxTimeout != nil { - c.BlocksUntilTxTimeout = f.BlocksUntilTxTimeout - } - if f.ConfirmPollPeriod != nil { - c.ConfirmPollPeriod = f.ConfirmPollPeriod - } - if f.FallbackGasPrice != nil { - c.FallbackGasPrice = f.FallbackGasPrice - } - if f.GasToken != nil { - c.GasToken = f.GasToken - } - if f.GasLimitMultiplier != nil { - c.GasLimitMultiplier = f.GasLimitMultiplier - } - if f.MaxMsgsPerBatch != nil { - c.MaxMsgsPerBatch = f.MaxMsgsPerBatch - } - if f.OCR2CachePollPeriod != nil { - c.OCR2CachePollPeriod = f.OCR2CachePollPeriod - } - if f.OCR2CacheTTL != nil { - c.OCR2CacheTTL = f.OCR2CacheTTL - } - if f.TxMsgTimeout != nil { - c.TxMsgTimeout = f.TxMsgTimeout - } -} - -func (c *CosmosConfig) ValidateConfig() (err error) { - if c.ChainID == nil { - err = multierr.Append(err, config.ErrMissing{Name: "ChainID", Msg: "required for all chains"}) - } else if *c.ChainID == "" { - err = multierr.Append(err, config.ErrEmpty{Name: "ChainID", Msg: "required for all chains"}) - } - - if len(c.Nodes) == 0 { - err = multierr.Append(err, config.ErrMissing{Name: "Nodes", Msg: "must have at least one node"}) - } - - return -} - -func (c *CosmosConfig) TOMLString() (string, error) { - b, err := toml.Marshal(c) - if err != nil { - return "", err - } - return string(b), nil -} - -var _ coscfg.Config = &CosmosConfig{} - -func (c *CosmosConfig) Bech32Prefix() string { - return *c.Chain.Bech32Prefix -} - -func (c *CosmosConfig) BlockRate() time.Duration { - return c.Chain.BlockRate.Duration() -} - -func (c *CosmosConfig) BlocksUntilTxTimeout() int64 { - return *c.Chain.BlocksUntilTxTimeout -} - -func (c *CosmosConfig) ConfirmPollPeriod() time.Duration { - return c.Chain.ConfirmPollPeriod.Duration() -} - -func (c *CosmosConfig) FallbackGasPrice() sdk.Dec { - return sdkDecFromDecimal(c.Chain.FallbackGasPrice) -} - -func (c *CosmosConfig) GasToken() string { - return *c.Chain.GasToken -} - -func (c *CosmosConfig) GasLimitMultiplier() float64 { - return c.Chain.GasLimitMultiplier.InexactFloat64() -} - -func (c *CosmosConfig) MaxMsgsPerBatch() int64 { - return *c.Chain.MaxMsgsPerBatch -} - -func (c *CosmosConfig) OCR2CachePollPeriod() time.Duration { - return c.Chain.OCR2CachePollPeriod.Duration() -} - -func (c *CosmosConfig) OCR2CacheTTL() time.Duration { - return c.Chain.OCR2CacheTTL.Duration() -} - -func (c *CosmosConfig) TxMsgTimeout() time.Duration { - return c.Chain.TxMsgTimeout.Duration() -} - -func sdkDecFromDecimal(d *decimal.Decimal) sdk.Dec { - i := d.Shift(sdk.Precision) - return sdk.NewDecFromBigIntWithPrec(i.BigInt(), sdk.Precision) -} - -func (c *CosmosConfig) GetNode(name string) (db.Node, error) { - for _, n := range c.Nodes { - if *n.Name == name { - return legacyNode(n, *c.ChainID), nil - } - } - return db.Node{}, fmt.Errorf("%w: node %q", chains.ErrNotFound, name) -} - -func (c *CosmosConfig) ListNodes() ([]db.Node, error) { - var allNodes []db.Node - for _, n := range c.Nodes { - allNodes = append(allNodes, legacyNode(n, *c.ChainID)) - } - return allNodes, nil -} diff --git a/core/chains/cosmos/config_test.go b/core/chains/cosmos/config_test.go deleted file mode 100644 index 54f91a13620..00000000000 --- a/core/chains/cosmos/config_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package cosmos - -import ( - "reflect" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/shopspring/decimal" - "github.com/stretchr/testify/assert" - - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/db" - "github.com/smartcontractkit/chainlink-relay/pkg/utils" -) - -func Test_sdkDecFromDecimal(t *testing.T) { - tests := []string{ - "0.0", - "0.1", - "1.0", - "0.000000000000000001", - } - for _, tt := range tests { - t.Run(tt, func(t *testing.T) { - val := decimal.RequireFromString(tt) - exp := sdk.MustNewDecFromStr(tt) - assert.Equal(t, exp, sdkDecFromDecimal(&val)) - }) - } -} - -func TestCosmosConfig_GetNode(t *testing.T) { - type fields struct { - ChainID *string - Nodes CosmosNodes - } - type args struct { - name string - } - tests := []struct { - name string - fields fields - args args - want db.Node - wantErr bool - }{ - { - name: "not found", - args: args{ - name: "not a node", - }, - fields: fields{Nodes: CosmosNodes{}}, - want: db.Node{}, - wantErr: true, - }, - { - name: "success", - args: args{ - name: "node", - }, - fields: fields{ - ChainID: ptr("chainID"), - Nodes: []*coscfg.Node{ - &coscfg.Node{ - Name: ptr("node"), - TendermintURL: &utils.URL{}, - }, - }}, - want: db.Node{ - CosmosChainID: "chainID", - Name: "node", - TendermintURL: "", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := &CosmosConfig{ - Nodes: tt.fields.Nodes, - ChainID: tt.fields.ChainID, - } - got, err := c.GetNode(tt.args.name) - if (err != nil) != tt.wantErr { - t.Errorf("CosmosConfig.GetNode() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("CosmosConfig.GetNode() = %v, want %v", got, tt.want) - } - }) - } -} - -func ptr[T any](t T) *T { - return &t -} diff --git a/core/chains/cosmos/cosmostxm/helpers_test.go b/core/chains/cosmos/cosmostxm/helpers_test.go deleted file mode 100644 index ad93189082e..00000000000 --- a/core/chains/cosmos/cosmostxm/helpers_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package cosmostxm - -import ( - "context" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "golang.org/x/exp/maps" - - cosmosclient "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/client" -) - -func (txm *Txm) ORM() *ORM { - return txm.orm -} - -func (txm *Txm) ConfirmTx(ctx context.Context, tc cosmosclient.Reader, txHash string, broadcasted []int64, maxPolls int, pollPeriod time.Duration) error { - return txm.confirmTx(ctx, tc, txHash, broadcasted, maxPolls, pollPeriod) -} - -func (txm *Txm) ConfirmAnyUnconfirmed(ctx context.Context) { - txm.confirmAnyUnconfirmed(ctx) -} - -func (txm *Txm) MarshalMsg(msg sdk.Msg) (string, []byte, error) { - return txm.marshalMsg(msg) -} - -func (txm *Txm) SendMsgBatch(ctx context.Context) { - txm.sendMsgBatch(ctx) -} - -func (ka *KeystoreAdapter) Accounts(ctx context.Context) ([]string, error) { - ka.mutex.Lock() - defer ka.mutex.Unlock() - err := ka.updateMappingLocked() - if err != nil { - return nil, err - } - addresses := maps.Keys(ka.addressToPubKey) - - return addresses, nil -} diff --git a/core/chains/cosmos/cosmostxm/key_wrapper.go b/core/chains/cosmos/cosmostxm/key_wrapper.go deleted file mode 100644 index 1d2d686c8c0..00000000000 --- a/core/chains/cosmos/cosmostxm/key_wrapper.go +++ /dev/null @@ -1,62 +0,0 @@ -package cosmostxm - -import ( - "bytes" - "context" - - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" -) - -// KeyWrapper uses a KeystoreAdapter to implement the cosmos-sdk PrivKey interface for a specific key. -type KeyWrapper struct { - adapter *KeystoreAdapter - account string -} - -var _ cryptotypes.PrivKey = &KeyWrapper{} - -func NewKeyWrapper(adapter *KeystoreAdapter, account string) *KeyWrapper { - return &KeyWrapper{ - adapter: adapter, - account: account, - } -} - -func (a *KeyWrapper) Bytes() []byte { - // don't expose the private key. - return nil -} - -func (a *KeyWrapper) Sign(msg []byte) ([]byte, error) { - return a.adapter.Sign(context.Background(), a.account, msg) -} - -func (a *KeyWrapper) PubKey() cryptotypes.PubKey { - pubKey, err := a.adapter.PubKey(a.account) - if err != nil { - // return an empty pubkey if it's not found. - return &secp256k1.PubKey{Key: []byte{}} - } - return pubKey -} - -func (a *KeyWrapper) Equals(other cryptotypes.LedgerPrivKey) bool { - return bytes.Equal(a.PubKey().Bytes(), other.PubKey().Bytes()) -} - -func (a *KeyWrapper) Type() string { - return "secp256k1" -} - -func (a *KeyWrapper) Reset() { - // no-op -} - -func (a *KeyWrapper) String() string { - return "" -} - -func (a *KeyWrapper) ProtoMessage() { - // no-op -} diff --git a/core/chains/cosmos/cosmostxm/keystore_adapter.go b/core/chains/cosmos/cosmostxm/keystore_adapter.go deleted file mode 100644 index c8556015c6e..00000000000 --- a/core/chains/cosmos/cosmostxm/keystore_adapter.go +++ /dev/null @@ -1,129 +0,0 @@ -package cosmostxm - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "sync" - - "github.com/cometbft/cometbft/crypto" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/types/bech32" - "github.com/pkg/errors" - "golang.org/x/crypto/ripemd160" //nolint: staticcheck - - "github.com/smartcontractkit/chainlink-relay/pkg/loop" -) - -type accountInfo struct { - Account string - PubKey *secp256k1.PubKey -} - -// An adapter for a Cosmos loop.Keystore to translate public keys into bech32-prefixed account addresses. -type KeystoreAdapter struct { - keystore loop.Keystore - accountPrefix string - mutex sync.RWMutex - addressToPubKey map[string]*accountInfo -} - -func NewKeystoreAdapter(keystore loop.Keystore, accountPrefix string) *KeystoreAdapter { - return &KeystoreAdapter{ - keystore: keystore, - accountPrefix: accountPrefix, - addressToPubKey: make(map[string]*accountInfo), - } -} - -func (ka *KeystoreAdapter) updateMappingLocked() error { - accounts, err := ka.keystore.Accounts(context.Background()) - if err != nil { - return err - } - - // similar to cosmos-sdk, cache and re-use calculated bech32 addresses to prevent duplicated work. - // ref: https://github.com/cosmos/cosmos-sdk/blob/3b509c187e1643757f5ef8a0b5ae3decca0c7719/types/address.go#L705 - - type cacheEntry struct { - bech32Addr string - accountInfo *accountInfo - } - accountCache := make(map[string]cacheEntry, len(ka.addressToPubKey)) - for bech32Addr, accountInfo := range ka.addressToPubKey { - accountCache[accountInfo.Account] = cacheEntry{bech32Addr: bech32Addr, accountInfo: accountInfo} - } - - addressToPubKey := make(map[string]*accountInfo, len(accounts)) - for _, account := range accounts { - if prevEntry, ok := accountCache[account]; ok { - addressToPubKey[prevEntry.bech32Addr] = prevEntry.accountInfo - continue - } - pubKeyBytes, err := hex.DecodeString(account) - if err != nil { - return err - } - - if len(pubKeyBytes) != secp256k1.PubKeySize { - return errors.New("length of pubkey is incorrect") - } - - sha := sha256.Sum256(pubKeyBytes) - hasherRIPEMD160 := ripemd160.New() - _, _ = hasherRIPEMD160.Write(sha[:]) - address := crypto.Address(hasherRIPEMD160.Sum(nil)) - - bech32Addr, err := bech32.ConvertAndEncode(ka.accountPrefix, address) - if err != nil { - return err - } - - addressToPubKey[bech32Addr] = &accountInfo{ - Account: account, - PubKey: &secp256k1.PubKey{Key: pubKeyBytes}, - } - } - - ka.addressToPubKey = addressToPubKey - return nil -} - -func (ka *KeystoreAdapter) lookup(id string) (*accountInfo, error) { - ka.mutex.RLock() - ai, ok := ka.addressToPubKey[id] - ka.mutex.RUnlock() - if !ok { - // try updating the mapping once, incase there was an update on the keystore. - ka.mutex.Lock() - err := ka.updateMappingLocked() - if err != nil { - ka.mutex.Unlock() - return nil, err - } - ai, ok = ka.addressToPubKey[id] - ka.mutex.Unlock() - if !ok { - return nil, errors.New("No such id") - } - } - return ai, nil -} - -func (ka *KeystoreAdapter) Sign(ctx context.Context, id string, hash []byte) ([]byte, error) { - accountInfo, err := ka.lookup(id) - if err != nil { - return nil, err - } - return ka.keystore.Sign(ctx, accountInfo.Account, hash) -} - -// Returns the cosmos PubKey associated with the prefixed address. -func (ka *KeystoreAdapter) PubKey(address string) (cryptotypes.PubKey, error) { - accountInfo, err := ka.lookup(address) - if err != nil { - return nil, err - } - return accountInfo.PubKey, nil -} diff --git a/core/chains/cosmos/cosmostxm/main_test.go b/core/chains/cosmos/cosmostxm/main_test.go deleted file mode 100644 index bc340afa430..00000000000 --- a/core/chains/cosmos/cosmostxm/main_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package cosmostxm - -import ( - "os" - "testing" - - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/params" -) - -func TestMain(m *testing.M) { - params.InitCosmosSdk( - /* bech32Prefix= */ "wasm", - /* token= */ "cosm", - ) - code := m.Run() - os.Exit(code) -} diff --git a/core/chains/cosmos/cosmostxm/orm.go b/core/chains/cosmos/cosmostxm/orm.go deleted file mode 100644 index cc9b179cce5..00000000000 --- a/core/chains/cosmos/cosmostxm/orm.go +++ /dev/null @@ -1,104 +0,0 @@ -package cosmostxm - -import ( - "database/sql" - - "github.com/pkg/errors" - - "github.com/smartcontractkit/sqlx" - - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/adapters" - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/db" - - "github.com/smartcontractkit/chainlink-relay/pkg/logger" - - "github.com/smartcontractkit/chainlink/v2/core/services/pg" -) - -// ORM manages the data model for cosmos tx management. -type ORM struct { - chainID string - q pg.Q -} - -// NewORM creates an ORM scoped to chainID. -func NewORM(chainID string, db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig) *ORM { - namedLogger := logger.Named(lggr, "Configs") - q := pg.NewQ(db, namedLogger, cfg) - return &ORM{ - chainID: chainID, - q: q, - } -} - -// InsertMsg inserts a cosmos msg, assumed to be a serialized cosmos ExecuteContractMsg. -func (o *ORM) InsertMsg(contractID, typeURL string, msg []byte, qopts ...pg.QOpt) (int64, error) { - var tm adapters.Msg - q := o.q.WithOpts(qopts...) - err := q.Get(&tm, `INSERT INTO cosmos_msgs (contract_id, type, raw, state, cosmos_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 -} - -// UpdateMsgsContract updates messages for the given contract. -func (o *ORM) UpdateMsgsContract(contractID string, from, to db.State, qopts ...pg.QOpt) error { - q := o.q.WithOpts(qopts...) - _, err := q.Exec(`UPDATE cosmos_msgs SET state = $1, updated_at = NOW() - WHERE cosmos_chain_id = $2 AND contract_id = $3 AND state = $4`, to, o.chainID, contractID, from) - if err != nil { - return err - } - return nil -} - -// GetMsgsState returns the oldest messages with a given state up to limit. -func (o *ORM) GetMsgsState(state db.State, limit int64, qopts ...pg.QOpt) (adapters.Msgs, error) { - if limit < 1 { - return adapters.Msgs{}, errors.New("limit must be greater than 0") - } - q := o.q.WithOpts(qopts...) - var msgs adapters.Msgs - if err := q.Select(&msgs, `SELECT * FROM cosmos_msgs WHERE state = $1 AND cosmos_chain_id = $2 ORDER BY id ASC LIMIT $3`, state, o.chainID, limit); err != nil { - return nil, err - } - return msgs, nil -} - -// GetMsgs returns any messages matching ids. -func (o *ORM) GetMsgs(ids ...int64) (adapters.Msgs, error) { - var msgs adapters.Msgs - if err := o.q.Select(&msgs, `SELECT * FROM cosmos_msgs WHERE id = ANY($1)`, ids); err != nil { - return nil, err - } - return msgs, nil -} - -// UpdateMsgs updates msgs with the given ids. -// Note state transitions are validated at the db level. -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") - } - q := o.q.WithOpts(qopts...) - var res sql.Result - var err error - if state == db.Broadcasted { - res, err = q.Exec(`UPDATE cosmos_msgs SET state = $1, updated_at = NOW(), tx_hash = $2 WHERE id = ANY($3)`, state, *txHash, ids) - } else { - res, err = q.Exec(`UPDATE cosmos_msgs SET state = $1, updated_at = NOW() WHERE id = ANY($2)`, state, ids) - } - if err != nil { - return err - } - count, err := res.RowsAffected() - if err != nil { - return err - } - if int(count) != len(ids) { - return errors.Errorf("expected %d records updated, got %d", len(ids), count) - } - return nil -} diff --git a/core/chains/cosmos/cosmostxm/orm_test.go b/core/chains/cosmos/cosmostxm/orm_test.go deleted file mode 100644 index c7418749360..00000000000 --- a/core/chains/cosmos/cosmostxm/orm_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package cosmostxm_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - cosmosdb "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/db" - - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos/cosmostxm" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" -) - -func TestORM(t *testing.T) { - db := pgtest.NewSqlxDB(t) - lggr := logger.TestLogger(t) - logCfg := pgtest.NewQConfig(true) - chainID := cosmostest.RandomChainID() - o := cosmostxm.NewORM(chainID, db, lggr, logCfg) - - // Create - mid, err := o.InsertMsg("0x123", "", []byte("hello")) - require.NoError(t, err) - assert.NotEqual(t, 0, int(mid)) - - // Read - unstarted, err := o.GetMsgsState(cosmosdb.Unstarted, 5) - 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) - t.Log(unstarted[0].UpdatedAt, unstarted[0].CreatedAt) - - // Limit - unstarted, err = o.GetMsgsState(cosmosdb.Unstarted, 0) - assert.Error(t, err) - assert.Empty(t, unstarted) - unstarted, err = o.GetMsgsState(cosmosdb.Unstarted, -1) - assert.Error(t, err) - assert.Empty(t, unstarted) - mid2, err := o.InsertMsg("0xabc", "", []byte("test")) - require.NoError(t, err) - assert.NotEqual(t, 0, int(mid2)) - unstarted, err = o.GetMsgsState(cosmosdb.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.GetMsgsState(cosmosdb.Unstarted, 2) - require.NoError(t, err) - require.Equal(t, 2, len(unstarted)) - assert.Equal(t, "test", string(unstarted[1].Raw)) - assert.Equal(t, chainID, unstarted[1].ChainID) - - // Update - txHash := "123" - err = o.UpdateMsgs([]int64{mid}, cosmosdb.Started, &txHash) - require.NoError(t, err) - err = o.UpdateMsgs([]int64{mid}, cosmosdb.Broadcasted, &txHash) - require.NoError(t, err) - broadcasted, err := o.GetMsgsState(cosmosdb.Broadcasted, 5) - require.NoError(t, err) - require.Equal(t, 1, len(broadcasted)) - assert.Equal(t, broadcasted[0].Raw, unstarted[0].Raw) - require.NotNil(t, broadcasted[0].TxHash) - assert.Equal(t, *broadcasted[0].TxHash, txHash) - assert.Equal(t, chainID, broadcasted[0].ChainID) - - err = o.UpdateMsgs([]int64{mid}, cosmosdb.Confirmed, nil) - require.NoError(t, err) - confirmed, err := o.GetMsgsState(cosmosdb.Confirmed, 5) - require.NoError(t, err) - require.Equal(t, 1, len(confirmed)) -} diff --git a/core/chains/cosmos/cosmostxm/txm.go b/core/chains/cosmos/cosmostxm/txm.go deleted file mode 100644 index e9fb2f6aca3..00000000000 --- a/core/chains/cosmos/cosmostxm/txm.go +++ /dev/null @@ -1,541 +0,0 @@ -package cosmostxm - -import ( - "cmp" - "context" - "encoding/hex" - "fmt" - "slices" - "strings" - "time" - - "github.com/gogo/protobuf/proto" - "github.com/pkg/errors" - - "github.com/smartcontractkit/sqlx" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - "github.com/cometbft/cometbft/crypto/tmhash" - sdk "github.com/cosmos/cosmos-sdk/types" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/x/bank/types" - - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos" - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/adapters" - cosmosclient "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/client" - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/db" - - "github.com/smartcontractkit/chainlink-relay/pkg/logger" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - - "github.com/smartcontractkit/chainlink/v2/core/services" - "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/utils" -) - -var ( - _ services.ServiceCtx = (*Txm)(nil) - _ adapters.TxManager = (*Txm)(nil) -) - -// Txm manages transactions for the cosmos blockchain. -type Txm struct { - starter utils.StartStopOnce - eb pg.EventBroadcaster - sub pg.Subscription - orm *ORM - lggr logger.Logger - tc func() (cosmosclient.ReaderWriter, error) - keystoreAdapter *KeystoreAdapter - stop, done chan struct{} - cfg coscfg.Config - gpe cosmosclient.ComposedGasPriceEstimator -} - -// NewTxm creates a txm. Uses simulation so should only be used to send txes to trusted contracts i.e. OCR. -func NewTxm(db *sqlx.DB, tc func() (cosmosclient.ReaderWriter, error), gpe cosmosclient.ComposedGasPriceEstimator, chainID string, cfg coscfg.Config, ks loop.Keystore, lggr logger.Logger, logCfg pg.QConfig, eb pg.EventBroadcaster) *Txm { - lggr = logger.Named(lggr, "Txm") - keystoreAdapter := NewKeystoreAdapter(ks, cfg.Bech32Prefix()) - return &Txm{ - starter: utils.StartStopOnce{}, - eb: eb, - orm: NewORM(chainID, db, lggr, logCfg), - lggr: lggr, - tc: tc, - keystoreAdapter: keystoreAdapter, - stop: make(chan struct{}), - done: make(chan struct{}), - cfg: cfg, - gpe: gpe, - } -} - -// Start subscribes to pg notifications about cosmos msg inserts and processes them. -func (txm *Txm) Start(context.Context) error { - return txm.starter.StartOnce("cosmostxm", func() error { - sub, err := txm.eb.Subscribe(pg.ChannelInsertOnCosmosMsg, "") - if err != nil { - return err - } - txm.sub = sub - go txm.run() - return nil - }) -} - -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.GetMsgsState(db.Broadcasted, txm.cfg.MaxMsgsPerBatch()) - if err != nil { - // Should never happen but if so, theoretically can retry with a reboot - logger.Criticalw(txm.lggr, "unable to look for broadcasted but unconfirmed txes", "err", err) - return - } - if len(broadcasted) == 0 { - return - } - tc, err := txm.tc() - if err != nil { - logger.Criticalw(txm.lggr, "unable to get client for handling broadcasted but unconfirmed txes", "count", len(broadcasted), "err", err) - return - } - msgsByTxHash := make(map[string]adapters.Msgs) - for _, msg := range broadcasted { - msgsByTxHash[*msg.TxHash] = append(msgsByTxHash[*msg.TxHash], msg) - } - for txHash, msgs := range msgsByTxHash { - maxPolls, pollPeriod := txm.confirmPollConfig() - err := txm.confirmTx(ctx, tc, txHash, msgs.GetIDs(), maxPolls, pollPeriod) - if err != nil { - txm.lggr.Errorw("unable to confirm broadcasted but unconfirmed txes", "err", err, "txhash", txHash) - if ctx.Err() != nil { - return - } - } - } - } -} - -func (txm *Txm) run() { - defer close(txm.done) - ctx, cancel := utils.StopChan(txm.stop).NewCtx() - defer cancel() - txm.confirmAnyUnconfirmed(ctx) - // Jitter in case we have multiple cosmos chains each with their own client. - tick := time.After(utils.WithJitter(txm.cfg.BlockRate())) - for { - select { - case <-txm.sub.Events(): - txm.sendMsgBatch(ctx) - case <-tick: - txm.sendMsgBatch(ctx) - tick = time.After(utils.WithJitter(txm.cfg.BlockRate())) - case <-txm.stop: - return - } - } -} - -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) -} - -type msgValidator struct { - cutoff time.Time - expired, valid adapters.Msgs -} - -func (e *msgValidator) add(msg adapters.Msg) { - if msg.CreatedAt.Before(e.cutoff) { - e.expired = append(e.expired, msg) - } else { - e.valid = append(e.valid, msg) - } -} - -func (e *msgValidator) sortValid() { - slices.SortFunc(e.valid, func(a, b adapters.Msg) int { - ac, bc := a.CreatedAt, b.CreatedAt - if ac.Equal(bc) { - return cmp.Compare(a.ID, b.ID) - } - if ac.After(bc) { - return 1 - } - return -1 // ac.Before(bc) - }) -} - -func (txm *Txm) sendMsgBatch(ctx context.Context) { - msgs := msgValidator{cutoff: time.Now().Add(-txm.cfg.TxMsgTimeout())} - err := txm.orm.q.Transaction(func(tx pg.Queryer) error { - // There may be leftover Started messages after a crash or failed send attempt. - started, err := txm.orm.GetMsgsState(db.Started, txm.cfg.MaxMsgsPerBatch(), pg.WithQueryer(tx)) - if err != nil { - txm.lggr.Errorw("unable to read unstarted msgs", "err", err) - return err - } - if limit := txm.cfg.MaxMsgsPerBatch() - int64(len(started)); limit > 0 { - // Use the remaining batch budget for Unstarted - unstarted, err := txm.orm.GetMsgsState(db.Unstarted, limit, pg.WithQueryer(tx)) //nolint - if err != nil { - txm.lggr.Errorw("unable to read unstarted msgs", "err", err) - return err - } - for _, msg := range unstarted { - msgs.add(msg) - } - // Update valid, Unstarted messages to Started - err = txm.orm.UpdateMsgs(msgs.valid.GetIDs(), db.Started, nil, pg.WithQueryer(tx)) - if err != nil { - // Assume transient db error retry - txm.lggr.Errorw("unable to mark unstarted txes as started", "err", err) - return err - } - } - for _, msg := range started { - msgs.add(msg) - } - // Update expired messages (Unstarted or Started) to Errored - err = txm.orm.UpdateMsgs(msgs.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 { - return - } - if len(msgs.valid) == 0 { - return - } - msgs.sortValid() - txm.lggr.Debugw("building a batch", "not expired", msgs.valid, "marked expired", msgs.expired) - var msgsByFrom = make(map[string]adapters.Msgs) - for _, m := range msgs.valid { - msg, sender, err2 := unmarshalMsg(m.Type, m.Raw) - if err2 != nil { - // Should be impossible given the check in Enqueue - logger.Criticalw(txm.lggr, "Failed to unmarshal msg, skipping", "err", err2, "msg", m) - continue - } - m.DecodedMsg = msg - _, err2 = sdk.AccAddressFromBech32(sender) - if err2 != nil { - // Should never happen, we parse sender on Enqueue - logger.Criticalw(txm.lggr, "Unable to parse sender", "err", err2, "sender", sender) - continue - } - msgsByFrom[sender] = append(msgsByFrom[sender], m) - } - - txm.lggr.Debugw("msgsByFrom", "msgsByFrom", msgsByFrom) - gasPrice, err := txm.GasPrice() - if err != nil { - // Should be impossible - logger.Criticalw(txm.lggr, "Failed to get gas price", "err", err) - return - } - for s, msgs := range msgsByFrom { - sender, _ := sdk.AccAddressFromBech32(s) // Already checked validity above - err := txm.sendMsgBatchFromAddress(ctx, gasPrice, sender, msgs) - if err != nil { - txm.lggr.Errorw("Could not send message batch", "err", err, "from", sender.String()) - continue - } - if ctx.Err() != nil { - return - } - } - -} - -func (txm *Txm) sendMsgBatchFromAddress(ctx context.Context, gasPrice sdk.DecCoin, sender sdk.AccAddress, msgs adapters.Msgs) error { - tc, err := txm.tc() - if err != nil { - logger.Criticalw(txm.lggr, "unable to get client", "err", err) - return err - } - an, sn, err := tc.Account(sender) - if err != nil { - txm.lggr.Warnw("unable to read account", "err", err, "from", sender.String()) - // If we can't read the account, assume transient api issues and leave msgs unstarted - // to retry on next poll. - return err - } - - txm.lggr.Debugw("simulating batch", "from", sender, "msgs", msgs, "seqnum", sn) - simResults, err := tc.BatchSimulateUnsigned(msgs.GetSimMsgs(), sn) - if err != nil { - txm.lggr.Warnw("unable to simulate", "err", err, "from", sender.String()) - // If we can't simulate assume transient api issue and retry on next poll. - // Note one rare scenario in which this can happen: the cosmos node misbehaves - // in that it confirms a txhash is present but still gives an old seq num. - // This is benign as the next retry will succeeds. - return err - } - txm.lggr.Debugw("simulation results", "from", sender, "succeeded", simResults.Succeeded, "failed", simResults.Failed) - 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. - return err - } - - // Continue if there are no successful txes - if len(simResults.Succeeded) == 0 { - txm.lggr.Warnw("all sim msgs errored, not sending tx", "from", sender.String()) - return errors.New("all sim msgs errored") - } - // Get the gas limit for the successful batch - s, err := tc.SimulateUnsigned(simResults.Succeeded.GetMsgs(), sn) - if err != nil { - // In the OCR context this should only happen upon stale report - txm.lggr.Warnw("unexpected failure after successful simulation", "err", err) - return err - } - gasLimit := s.GasInfo.GasUsed - - lb, err := tc.LatestBlock() - if err != nil { - txm.lggr.Warnw("unable to get latest block", "err", err, "from", sender.String()) - // Assume transient api issue and retry. - return err - } - header, timeout := lb.SdkBlock.Header.Height, txm.cfg.BlocksUntilTxTimeout() - if header < 0 { - return fmt.Errorf("invalid negative header height: %d", header) - } else if timeout < 0 { - return fmt.Errorf("invalid negative blocks until tx timeout: %d", timeout) - } - timeoutHeight := uint64(header) + uint64(timeout) - signedTx, err := tc.CreateAndSign(simResults.Succeeded.GetMsgs(), an, sn, gasLimit, txm.cfg.GasLimitMultiplier(), - gasPrice, NewKeyWrapper(txm.keystoreAdapter, sender.String()), timeoutHeight) - if err != nil { - txm.lggr.Errorw("unable to sign tx", "err", err, "from", sender.String()) - return err - } - - // We need to ensure that we either broadcast successfully and mark the tx as - // broadcasted OR we do not broadcast successfully and we do not mark it as broadcasted. - // We do this by first marking it broadcasted then rolling back if the broadcast api call fails. - // There is still a small chance of network failure or node/db crash after broadcasting but before committing the tx, - // in which case the msgs would be picked up again and re-broadcast, ensuring at-least once delivery. - 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.UpdateMsgs(simResults.Succeeded.GetSimMsgsIDs(), db.Broadcasted, &txHash, pg.WithQueryer(tx)) - if err != nil { - return err - } - - txm.lggr.Infow("broadcasting tx", "from", sender, "msgs", simResults.Succeeded, "gasLimit", gasLimit, "gasPrice", gasPrice.String(), "timeoutHeight", timeoutHeight, "hash", txHash) - resp, err = tc.Broadcast(signedTx, txtypes.BroadcastMode_BROADCAST_MODE_SYNC) - if err != nil { - // Rollback marking as broadcasted - // Note can happen if the node's mempool is full, where we expect errCode 20. - return err - } - if resp.TxResponse == nil { - // Rollback marking as broadcasted - return errors.New("unexpected nil tx response") - } - if resp.TxResponse.TxHash != txHash { - // Should never happen - logger.Criticalw(txm.lggr, "txhash mismatch", "got", resp.TxResponse.TxHash, "want", txHash) - } - return nil - }) - if err != nil { - txm.lggr.Errorw("error broadcasting tx", "err", err, "from", sender.String()) - // Was unable to broadcast, retry on next poll - return err - } - - maxPolls, pollPeriod := txm.confirmPollConfig() - if err := txm.confirmTx(ctx, tc, resp.TxResponse.TxHash, simResults.Succeeded.GetSimMsgsIDs(), maxPolls, pollPeriod); err != nil { - txm.lggr.Errorw("error confirming tx", "err", err, "hash", resp.TxResponse.TxHash) - return err - } - - return nil -} - -func (txm *Txm) confirmPollConfig() (maxPolls int, pollPeriod time.Duration) { - blocks := txm.cfg.BlocksUntilTxTimeout() - blockPeriod := txm.cfg.BlockRate() - pollPeriod = txm.cfg.ConfirmPollPeriod() - if pollPeriod == 0 { - // don't divide by zero - maxPolls = 1 - } else { - maxPolls = int((time.Duration(blocks) * blockPeriod) / pollPeriod) - } - return -} - -func (txm *Txm) confirmTx(ctx context.Context, tc cosmosclient.Reader, txHash string, broadcasted []int64, maxPolls int, pollPeriod time.Duration) error { - // We either mark these broadcasted txes as confirmed or errored. - // Confirmed: we see the txhash onchain. There are no reorgs in cosmos chains. - // Errored: we do not see the txhash onchain after waiting for N blocks worth - // of time (plus a small buffer to account for block time variance) where N - // is TimeoutHeight - HeightAtBroadcast. In other words, if we wait for that long - // and the tx is not confirmed, we know it has timed out. - for tries := 0; tries < maxPolls; tries++ { - // Jitter in-case we're confirming multiple txes in parallel for different keys - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(utils.WithJitter(pollPeriod)): - } - // Confirm that this tx is onchain, ensuring the sequence number has incremented - // so we can build a new batch - tx, err := tc.Tx(txHash) - if err != nil { - if strings.Contains(err.Error(), "not found") { - txm.lggr.Infow("txhash not found yet, still confirming", "hash", txHash) - } else { - txm.lggr.Errorw("error looking for hash of tx", "err", err, "hash", txHash) - } - continue - } - // Sanity check - if tx.TxResponse == nil || tx.TxResponse.TxHash != txHash { - txm.lggr.Errorw("error looking for hash of tx, unexpected response", "tx", tx, "hash", txHash) - continue - } - - txm.lggr.Infow("successfully sent batch", "hash", txHash, "msgs", broadcasted) - // If confirmed mark these as completed. - err = txm.orm.UpdateMsgs(broadcasted, db.Confirmed, nil) - if err != nil { - return err - } - return nil - } - 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.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 - } - return nil -} - -// Enqueue enqueue a msg destined for the cosmos chain. -func (txm *Txm) Enqueue(contractID string, msg sdk.Msg) (int64, error) { - typeURL, raw, err := txm.marshalMsg(msg) - if err != nil { - 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. - - var id int64 - err = txm.orm.q.Transaction(func(tx pg.Queryer) (err error) { - // cancel any unstarted msgs (normally just one) - err = txm.orm.UpdateMsgsContract(contractID, db.Unstarted, db.Errored, pg.WithQueryer(tx)) - if err != nil { - return err - } - id, err = txm.orm.InsertMsg(contractID, typeURL, raw, pg.WithQueryer(tx)) - return err - }) - return id, err -} - -func (txm *Txm) marshalMsg(msg sdk.Msg) (string, []byte, 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 "", nil, 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 "", nil, err - } - - default: - return "", nil, &cosmos.ErrMsgUnsupported{Msg: msg} - } - typeURL := sdk.MsgTypeURL(msg) - raw, err := proto.Marshal(msg) - if err != nil { - txm.lggr.Errorw("failed to marshal msg, skipping", "err", err, "msg", msg) - return "", nil, err - } - return typeURL, raw, nil -} - -// GetMsgs returns any messages matching ids. -func (txm *Txm) GetMsgs(ids ...int64) (adapters.Msgs, error) { - return txm.orm.GetMsgs(ids...) -} - -// GasPrice returns the gas price from the estimator in the configured fee token. -func (txm *Txm) GasPrice() (sdk.DecCoin, error) { - prices := txm.gpe.GasPrices() - gasPrice, ok := prices[txm.cfg.GasToken()] - if !ok { - return sdk.DecCoin{}, errors.New("unexpected empty gas price") - } - return gasPrice, nil -} - -// Close close service -func (txm *Txm) Close() error { - txm.sub.Close() - close(txm.stop) - <-txm.done - return nil -} - -func (txm *Txm) Name() string { return "cosmostxm" } - -// Healthy service is healthy -func (txm *Txm) Healthy() error { - return nil -} - -// Ready service is ready -func (txm *Txm) Ready() error { - return nil -} - -func (txm *Txm) HealthReport() map[string]error { return map[string]error{txm.Name(): txm.Healthy()} } diff --git a/core/chains/cosmos/cosmostxm/txm_internal_test.go b/core/chains/cosmos/cosmostxm/txm_internal_test.go deleted file mode 100644 index 66a8c98b637..00000000000 --- a/core/chains/cosmos/cosmostxm/txm_internal_test.go +++ /dev/null @@ -1,428 +0,0 @@ -package cosmostxm_test - -import ( - "fmt" - "testing" - "time" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - tmservicetypes "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" - cosmostypes "github.com/cosmos/cosmos-sdk/types" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" - - cosmosclient "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/client" - tcmocks "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/client/mocks" - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - cosmosdb "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/db" - relayutils "github.com/smartcontractkit/chainlink-relay/pkg/utils" - - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos/cosmostxm" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore" - "github.com/smartcontractkit/chainlink/v2/core/utils" -) - -func generateExecuteMsg(t *testing.T, msg []byte, from, to cosmostypes.AccAddress) cosmostypes.Msg { - return &wasmtypes.MsgExecuteContract{ - Sender: from.String(), - Contract: to.String(), - Msg: msg, - Funds: cosmostypes.Coins{}, - } -} - -func newReaderWriterMock(t *testing.T) *tcmocks.ReaderWriter { - tc := new(tcmocks.ReaderWriter) - tc.Test(t) - t.Cleanup(func() { tc.AssertExpectations(t) }) - return tc -} - -func TestTxm(t *testing.T) { - db := pgtest.NewSqlxDB(t) - lggr := testutils.LoggerAssertMaxLevel(t, zapcore.ErrorLevel) - ks := keystore.NewInMemory(db, utils.FastScryptParams, lggr, pgtest.NewQConfig(true)) - require.NoError(t, ks.Unlock("blah")) - - for i := 0; i < 4; i++ { - _, err := ks.Cosmos().Create() - require.NoError(t, err) - } - - loopKs := &keystore.CosmosLoopKeystore{Cosmos: ks.Cosmos()} - adapter := cosmostxm.NewKeystoreAdapter(loopKs, "wasm") - accounts, err := adapter.Accounts(testutils.Context(t)) - require.NoError(t, err) - require.Equal(t, len(accounts), 4) - - sender1, err := cosmostypes.AccAddressFromBech32(accounts[0]) - require.NoError(t, err) - sender2, err := cosmostypes.AccAddressFromBech32(accounts[1]) - require.NoError(t, err) - contract, err := cosmostypes.AccAddressFromBech32(accounts[2]) - require.NoError(t, err) - contract2, err := cosmostypes.AccAddressFromBech32(accounts[3]) - require.NoError(t, err) - - logCfg := pgtest.NewQConfig(true) - chainID := cosmostest.RandomChainID() - two := int64(2) - gasToken := "ucosm" - cfg := &cosmos.CosmosConfig{Chain: coscfg.Chain{ - MaxMsgsPerBatch: &two, - GasToken: &gasToken, - }} - cfg.SetDefaults() - gpe := cosmosclient.NewMustGasPriceEstimator([]cosmosclient.GasPricesEstimator{ - cosmosclient.NewFixedGasPriceEstimator(map[string]cosmostypes.DecCoin{ - cfg.GasToken(): cosmostypes.NewDecCoinFromDec(cfg.GasToken(), cosmostypes.MustNewDecFromStr("0.01")), - }, - lggr.(logger.SugaredLogger), - ), - }, lggr) - - t.Run("single msg", func(t *testing.T) { - tc := newReaderWriterMock(t) - tcFn := func() (cosmosclient.ReaderWriter, error) { return tc, nil } - loopKs := &keystore.CosmosLoopKeystore{Cosmos: ks.Cosmos()} - txm := cosmostxm.NewTxm(db, tcFn, *gpe, chainID, cfg, loopKs, lggr, logCfg, nil) - - // Enqueue a single msg, then send it in a batch - id1, err := txm.Enqueue(contract.String(), generateExecuteMsg(t, []byte(`1`), sender1, contract)) - require.NoError(t, err) - tc.On("Account", mock.Anything).Return(uint64(0), uint64(0), nil) - tc.On("BatchSimulateUnsigned", mock.Anything, mock.Anything).Return(&cosmosclient.BatchSimResults{ - Failed: nil, - Succeeded: cosmosclient.SimMsgs{{ID: id1, Msg: &wasmtypes.MsgExecuteContract{ - Sender: sender1.String(), - Msg: []byte(`1`), - }}}, - }, nil) - tc.On("SimulateUnsigned", mock.Anything, mock.Anything).Return(&txtypes.SimulateResponse{GasInfo: &cosmostypes.GasInfo{ - GasUsed: 1_000_000, - }}, nil) - tc.On("LatestBlock").Return(&tmservicetypes.GetLatestBlockResponse{SdkBlock: &tmservicetypes.Block{ - Header: tmservicetypes.Header{Height: 1}, - }}, nil) - tc.On("CreateAndSign", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]byte{0x01}, nil) - - txResp := &cosmostypes.TxResponse{TxHash: "4BF5122F344554C53BDE2EBB8CD2B7E3D1600AD631C385A5D7CCE23C7785459A"} - tc.On("Broadcast", mock.Anything, mock.Anything).Return(&txtypes.BroadcastTxResponse{TxResponse: txResp}, nil) - tc.On("Tx", mock.Anything).Return(&txtypes.GetTxResponse{Tx: &txtypes.Tx{}, TxResponse: txResp}, nil) - txm.SendMsgBatch(testutils.Context(t)) - - // Should be in completed state - completed, err := txm.ORM().GetMsgs(id1) - require.NoError(t, err) - require.Equal(t, 1, len(completed)) - assert.Equal(t, completed[0].State, cosmosdb.Confirmed) - }) - - t.Run("two msgs different accounts", func(t *testing.T) { - tc := newReaderWriterMock(t) - tcFn := func() (cosmosclient.ReaderWriter, error) { return tc, nil } - loopKs := &keystore.CosmosLoopKeystore{Cosmos: ks.Cosmos()} - txm := cosmostxm.NewTxm(db, tcFn, *gpe, chainID, cfg, loopKs, lggr, pgtest.NewQConfig(true), nil) - - id1, err := txm.Enqueue(contract.String(), generateExecuteMsg(t, []byte(`0`), sender1, contract)) - require.NoError(t, err) - id2, err := txm.Enqueue(contract.String(), generateExecuteMsg(t, []byte(`1`), sender2, contract)) - require.NoError(t, err) - - tc.On("Account", mock.Anything).Return(uint64(0), uint64(0), nil).Once() - // Note this must be arg dependent, we don't know which order - // the procesing will happen in (map iteration by from address). - tc.On("BatchSimulateUnsigned", cosmosclient.SimMsgs{ - { - ID: id2, - Msg: &wasmtypes.MsgExecuteContract{ - Sender: sender2.String(), - Msg: []byte(`1`), - Contract: contract.String(), - }, - }, - }, mock.Anything).Return(&cosmosclient.BatchSimResults{ - Failed: nil, - Succeeded: cosmosclient.SimMsgs{ - { - ID: id2, - Msg: &wasmtypes.MsgExecuteContract{ - Sender: sender2.String(), - Msg: []byte(`1`), - Contract: contract.String(), - }, - }, - }, - }, nil).Once() - tc.On("SimulateUnsigned", mock.Anything, mock.Anything).Return(&txtypes.SimulateResponse{GasInfo: &cosmostypes.GasInfo{ - GasUsed: 1_000_000, - }}, nil).Once() - tc.On("LatestBlock").Return(&tmservicetypes.GetLatestBlockResponse{SdkBlock: &tmservicetypes.Block{ - Header: tmservicetypes.Header{Height: 1}, - }}, nil).Once() - tc.On("CreateAndSign", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]byte{0x01}, nil).Once() - txResp := &cosmostypes.TxResponse{TxHash: "4BF5122F344554C53BDE2EBB8CD2B7E3D1600AD631C385A5D7CCE23C7785459A"} - tc.On("Broadcast", mock.Anything, mock.Anything).Return(&txtypes.BroadcastTxResponse{TxResponse: txResp}, nil).Once() - tc.On("Tx", mock.Anything).Return(&txtypes.GetTxResponse{Tx: &txtypes.Tx{}, TxResponse: txResp}, nil).Once() - txm.SendMsgBatch(testutils.Context(t)) - - // Should be in completed state - completed, err := txm.ORM().GetMsgs(id1, id2) - require.NoError(t, err) - require.Equal(t, 2, len(completed)) - assert.Equal(t, cosmosdb.Errored, completed[0].State) // cancelled - assert.Equal(t, cosmosdb.Confirmed, completed[1].State) - }) - - t.Run("two msgs different contracts", func(t *testing.T) { - tc := newReaderWriterMock(t) - tcFn := func() (cosmosclient.ReaderWriter, error) { return tc, nil } - loopKs := &keystore.CosmosLoopKeystore{Cosmos: ks.Cosmos()} - txm := cosmostxm.NewTxm(db, tcFn, *gpe, chainID, cfg, loopKs, lggr, pgtest.NewQConfig(true), nil) - - id1, err := txm.Enqueue(contract.String(), generateExecuteMsg(t, []byte(`0`), sender1, contract)) - require.NoError(t, err) - id2, err := txm.Enqueue(contract2.String(), generateExecuteMsg(t, []byte(`1`), sender2, contract2)) - require.NoError(t, err) - ids := []int64{id1, id2} - senders := []string{sender1.String(), sender2.String()} - contracts := []string{contract.String(), contract2.String()} - for i := 0; i < 2; i++ { - tc.On("Account", mock.Anything).Return(uint64(0), uint64(0), nil).Once() - // Note this must be arg dependent, we don't know which order - // the procesing will happen in (map iteration by from address). - tc.On("BatchSimulateUnsigned", cosmosclient.SimMsgs{ - { - ID: ids[i], - Msg: &wasmtypes.MsgExecuteContract{ - Sender: senders[i], - Msg: []byte(fmt.Sprintf(`%d`, i)), - Contract: contracts[i], - }, - }, - }, mock.Anything).Return(&cosmosclient.BatchSimResults{ - Failed: nil, - Succeeded: cosmosclient.SimMsgs{ - { - ID: ids[i], - Msg: &wasmtypes.MsgExecuteContract{ - Sender: senders[i], - Msg: []byte(fmt.Sprintf(`%d`, i)), - Contract: contracts[i], - }, - }, - }, - }, nil).Once() - tc.On("SimulateUnsigned", mock.Anything, mock.Anything).Return(&txtypes.SimulateResponse{GasInfo: &cosmostypes.GasInfo{ - GasUsed: 1_000_000, - }}, nil).Once() - tc.On("LatestBlock").Return(&tmservicetypes.GetLatestBlockResponse{SdkBlock: &tmservicetypes.Block{ - Header: tmservicetypes.Header{Height: 1}, - }}, nil).Once() - tc.On("CreateAndSign", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]byte{0x01}, nil).Once() - } - txResp := &cosmostypes.TxResponse{TxHash: "4BF5122F344554C53BDE2EBB8CD2B7E3D1600AD631C385A5D7CCE23C7785459A"} - tc.On("Broadcast", mock.Anything, mock.Anything).Return(&txtypes.BroadcastTxResponse{TxResponse: txResp}, nil).Twice() - tc.On("Tx", mock.Anything).Return(&txtypes.GetTxResponse{Tx: &txtypes.Tx{}, TxResponse: txResp}, nil).Twice() - txm.SendMsgBatch(testutils.Context(t)) - - // Should be in completed state - completed, err := txm.ORM().GetMsgs(id1, id2) - require.NoError(t, err) - require.Equal(t, 2, len(completed)) - assert.Equal(t, cosmosdb.Confirmed, completed[0].State) - assert.Equal(t, cosmosdb.Confirmed, completed[1].State) - }) - - t.Run("failed to confirm", func(t *testing.T) { - tc := newReaderWriterMock(t) - tc.On("Tx", mock.Anything).Return(&txtypes.GetTxResponse{ - Tx: &txtypes.Tx{}, - TxResponse: &cosmostypes.TxResponse{TxHash: "0x123"}, - }, errors.New("not found")).Twice() - tcFn := func() (cosmosclient.ReaderWriter, error) { return tc, nil } - loopKs := &keystore.CosmosLoopKeystore{Cosmos: ks.Cosmos()} - txm := cosmostxm.NewTxm(db, tcFn, *gpe, chainID, cfg, loopKs, lggr, pgtest.NewQConfig(true), nil) - i, err := txm.ORM().InsertMsg("blah", "", []byte{0x01}) - require.NoError(t, err) - txh := "0x123" - require.NoError(t, txm.ORM().UpdateMsgs([]int64{i}, cosmosdb.Started, &txh)) - require.NoError(t, txm.ORM().UpdateMsgs([]int64{i}, cosmosdb.Broadcasted, &txh)) - err = txm.ConfirmTx(testutils.Context(t), tc, txh, []int64{i}, 2, 1*time.Millisecond) - require.NoError(t, err) - m, err := txm.ORM().GetMsgs(i) - require.NoError(t, err) - require.Equal(t, 1, len(m)) - assert.Equal(t, cosmosdb.Errored, m[0].State) - }) - - t.Run("confirm any unconfirmed", func(t *testing.T) { - require.Equal(t, int64(2), cfg.MaxMsgsPerBatch()) - txHash1 := "0x1234" - txHash2 := "0x1235" - txHash3 := "0xabcd" - tc := newReaderWriterMock(t) - tc.On("Tx", txHash1).Return(&txtypes.GetTxResponse{ - TxResponse: &cosmostypes.TxResponse{TxHash: txHash1}, - }, nil).Once() - tc.On("Tx", txHash2).Return(&txtypes.GetTxResponse{ - TxResponse: &cosmostypes.TxResponse{TxHash: txHash2}, - }, nil).Once() - tc.On("Tx", txHash3).Return(&txtypes.GetTxResponse{ - TxResponse: &cosmostypes.TxResponse{TxHash: txHash3}, - }, nil).Once() - tcFn := func() (cosmosclient.ReaderWriter, error) { return tc, nil } - loopKs := &keystore.CosmosLoopKeystore{Cosmos: ks.Cosmos()} - txm := cosmostxm.NewTxm(db, tcFn, *gpe, chainID, cfg, loopKs, lggr, pgtest.NewQConfig(true), nil) - - // Insert and broadcast 3 msgs with different txhashes. - id1, err := txm.ORM().InsertMsg("blah", "", []byte{0x01}) - require.NoError(t, err) - id2, err := txm.ORM().InsertMsg("blah", "", []byte{0x02}) - require.NoError(t, err) - id3, err := txm.ORM().InsertMsg("blah", "", []byte{0x03}) - require.NoError(t, err) - err = txm.ORM().UpdateMsgs([]int64{id1}, cosmosdb.Started, &txHash1) - require.NoError(t, err) - err = txm.ORM().UpdateMsgs([]int64{id2}, cosmosdb.Started, &txHash2) - require.NoError(t, err) - err = txm.ORM().UpdateMsgs([]int64{id3}, cosmosdb.Started, &txHash3) - require.NoError(t, err) - err = txm.ORM().UpdateMsgs([]int64{id1}, cosmosdb.Broadcasted, &txHash1) - require.NoError(t, err) - err = txm.ORM().UpdateMsgs([]int64{id2}, cosmosdb.Broadcasted, &txHash2) - require.NoError(t, err) - err = txm.ORM().UpdateMsgs([]int64{id3}, cosmosdb.Broadcasted, &txHash3) - require.NoError(t, err) - - // Confirm them as in a restart while confirming scenario - txm.ConfirmAnyUnconfirmed(testutils.Context(t)) - msgs, err := txm.ORM().GetMsgs(id1, id2, id3) - require.NoError(t, err) - require.Equal(t, 3, len(msgs)) - assert.Equal(t, cosmosdb.Confirmed, msgs[0].State) - assert.Equal(t, cosmosdb.Confirmed, msgs[1].State) - assert.Equal(t, cosmosdb.Confirmed, msgs[2].State) - }) - - t.Run("expired msgs", func(t *testing.T) { - tc := new(tcmocks.ReaderWriter) - timeout, err := relayutils.NewDuration(1 * time.Millisecond) - require.NoError(t, err) - tcFn := func() (cosmosclient.ReaderWriter, error) { return tc, nil } - two := int64(2) - cfgShortExpiry := &cosmos.CosmosConfig{Chain: coscfg.Chain{ - MaxMsgsPerBatch: &two, - TxMsgTimeout: &timeout, - }} - cfgShortExpiry.SetDefaults() - loopKs := &keystore.CosmosLoopKeystore{Cosmos: ks.Cosmos()} - txm := cosmostxm.NewTxm(db, tcFn, *gpe, chainID, cfgShortExpiry, loopKs, lggr, pgtest.NewQConfig(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(testutils.Context(t)) - // Should be marked errored - m, err := txm.ORM().GetMsgs(id1) - require.NoError(t, err) - assert.Equal(t, cosmosdb.Errored, m[0].State) - - // Send a batch which is all expired - id2, err := txm.ORM().InsertMsg("blah", "", []byte{0x03}) - require.NoError(t, err) - id3, err := txm.ORM().InsertMsg("blah", "", []byte{0x03}) - require.NoError(t, err) - time.Sleep(1 * time.Millisecond) - txm.SendMsgBatch(testutils.Context(t)) - require.NoError(t, err) - ms, err := txm.ORM().GetMsgs(id2, id3) - require.NoError(t, err) - assert.Equal(t, cosmosdb.Errored, ms[0].State) - assert.Equal(t, cosmosdb.Errored, ms[1].State) - }) - - t.Run("started msgs", func(t *testing.T) { - tc := new(tcmocks.ReaderWriter) - tc.On("Account", mock.Anything).Return(uint64(0), uint64(0), nil) - tc.On("SimulateUnsigned", mock.Anything, mock.Anything).Return(&txtypes.SimulateResponse{GasInfo: &cosmostypes.GasInfo{ - GasUsed: 1_000_000, - }}, nil) - tc.On("LatestBlock").Return(&tmservicetypes.GetLatestBlockResponse{SdkBlock: &tmservicetypes.Block{ - Header: tmservicetypes.Header{Height: 1}, - }}, nil) - tc.On("CreateAndSign", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]byte{0x01}, nil) - txResp := &cosmostypes.TxResponse{TxHash: "4BF5122F344554C53BDE2EBB8CD2B7E3D1600AD631C385A5D7CCE23C7785459A"} - tc.On("Broadcast", mock.Anything, mock.Anything).Return(&txtypes.BroadcastTxResponse{TxResponse: txResp}, nil) - tc.On("Tx", mock.Anything).Return(&txtypes.GetTxResponse{Tx: &txtypes.Tx{}, TxResponse: txResp}, nil) - tcFn := func() (cosmosclient.ReaderWriter, error) { return tc, nil } - two := int64(2) - cfgMaxMsgs := &cosmos.CosmosConfig{Chain: coscfg.Chain{ - MaxMsgsPerBatch: &two, - }} - cfgMaxMsgs.SetDefaults() - loopKs := &keystore.CosmosLoopKeystore{Cosmos: ks.Cosmos()} - txm := cosmostxm.NewTxm(db, tcFn, *gpe, chainID, cfgMaxMsgs, loopKs, lggr, pgtest.NewQConfig(true), nil) - - // Leftover started is processed - msg1 := generateExecuteMsg(t, []byte{0x03}, sender1, contract) - id1 := mustInsertMsg(t, txm, contract.String(), msg1) - require.NoError(t, txm.ORM().UpdateMsgs([]int64{id1}, cosmosdb.Started, nil)) - msgs := cosmosclient.SimMsgs{{ID: id1, Msg: &wasmtypes.MsgExecuteContract{ - Sender: sender1.String(), - Msg: []byte{0x03}, - Contract: contract.String(), - }}} - tc.On("BatchSimulateUnsigned", msgs, mock.Anything). - Return(&cosmosclient.BatchSimResults{Failed: nil, Succeeded: msgs}, nil).Once() - time.Sleep(1 * time.Millisecond) - txm.SendMsgBatch(testutils.Context(t)) - m, err := txm.ORM().GetMsgs(id1) - require.NoError(t, err) - assert.Equal(t, cosmosdb.Confirmed, m[0].State) - - // Leftover started is not cancelled - msg2 := generateExecuteMsg(t, []byte{0x04}, sender1, contract) - msg3 := generateExecuteMsg(t, []byte{0x05}, sender1, contract) - id2 := mustInsertMsg(t, txm, contract.String(), msg2) - require.NoError(t, txm.ORM().UpdateMsgs([]int64{id2}, cosmosdb.Started, nil)) - time.Sleep(time.Millisecond) // ensure != CreatedAt - id3 := mustInsertMsg(t, txm, contract.String(), msg3) - msgs = cosmosclient.SimMsgs{{ID: id2, Msg: &wasmtypes.MsgExecuteContract{ - Sender: sender1.String(), - Msg: []byte{0x04}, - Contract: contract.String(), - }}, {ID: id3, Msg: &wasmtypes.MsgExecuteContract{ - Sender: sender1.String(), - Msg: []byte{0x05}, - Contract: contract.String(), - }}} - tc.On("BatchSimulateUnsigned", msgs, mock.Anything). - Return(&cosmosclient.BatchSimResults{Failed: nil, Succeeded: msgs}, nil).Once() - time.Sleep(1 * time.Millisecond) - txm.SendMsgBatch(testutils.Context(t)) - require.NoError(t, err) - ms, err := txm.ORM().GetMsgs(id2, id3) - require.NoError(t, err) - assert.Equal(t, cosmosdb.Confirmed, ms[0].State) - assert.Equal(t, cosmosdb.Confirmed, ms[1].State) - }) -} - -func mustInsertMsg(t *testing.T, txm *cosmostxm.Txm, contractID string, msg cosmostypes.Msg) int64 { - typeURL, raw, err := txm.MarshalMsg(msg) - require.NoError(t, err) - id, err := txm.ORM().InsertMsg(contractID, typeURL, raw) - require.NoError(t, err) - return id -} diff --git a/core/chains/cosmos/cosmostxm/txm_test.go b/core/chains/cosmos/cosmostxm/txm_test.go deleted file mode 100644 index a7a8d0280c6..00000000000 --- a/core/chains/cosmos/cosmostxm/txm_test.go +++ /dev/null @@ -1,152 +0,0 @@ -//go:build integration - -package cosmostxm_test - -import ( - "fmt" - "testing" - "time" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - "github.com/cometbft/cometbft/abci/types" - sdk "github.com/cosmos/cosmos-sdk/types" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/google/uuid" - "github.com/onsi/gomega" - "github.com/stretchr/testify/require" - - cosmosclient "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/client" - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos/cosmostxm" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore" - "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/utils" - - . "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/db" -) - -func TestTxm_Integration(t *testing.T) { - chainID := cosmostest.RandomChainID() - cosmosChain := coscfg.Chain{} - cosmosChain.SetDefaults() - fallbackGasPrice := sdk.NewDecCoinFromDec(*cosmosChain.GasToken, sdk.MustNewDecFromStr("0.01")) - chainConfig := cosmos.CosmosConfig{ChainID: &chainID, Enabled: ptr(true), Chain: cosmosChain} - cfg, db := heavyweight.FullTestDBNoFixturesV2(t, "cosmos_txm", func(c *chainlink.Config, s *chainlink.Secrets) { - c.Cosmos = cosmos.CosmosConfigs{&chainConfig} - }) - lggr := logger.TestLogger(t) - logCfg := pgtest.NewQConfig(true) - gpe := cosmosclient.NewMustGasPriceEstimator([]cosmosclient.GasPricesEstimator{ - cosmosclient.NewFixedGasPriceEstimator(map[string]sdk.DecCoin{ - *cosmosChain.GasToken: fallbackGasPrice, - }, - lggr.(logger.SugaredLogger), - ), - }, lggr) - orm := cosmostxm.NewORM(chainID, db, lggr, logCfg) - eb := pg.NewEventBroadcaster(cfg.Database().URL(), 0, 0, lggr, uuid.New()) - require.NoError(t, eb.Start(testutils.Context(t))) - t.Cleanup(func() { require.NoError(t, eb.Close()) }) - ks := keystore.NewInMemory(db, utils.FastScryptParams, lggr, pgtest.NewQConfig(true)) - zeConfig := sdk.GetConfig() - fmt.Println(zeConfig) - accounts, testdir, tendermintURL := cosmosclient.SetupLocalCosmosNode(t, chainID, *cosmosChain.GasToken) - tc, err := cosmosclient.NewClient(chainID, tendermintURL, cosmos.DefaultRequestTimeout, lggr) - require.NoError(t, err) - - loopKs := &keystore.CosmosLoopKeystore{Cosmos: ks.Cosmos()} - keystoreAdapter := cosmostxm.NewKeystoreAdapter(loopKs, *cosmosChain.Bech32Prefix) - - // First create a transmitter key and fund it with 1k native tokens - require.NoError(t, ks.Unlock("blah")) - err = ks.Cosmos().EnsureKey() - require.NoError(t, err) - ksAccounts, err := keystoreAdapter.Accounts(testutils.Context(t)) - require.NoError(t, err) - transmitterAddress := ksAccounts[0] - transmitterID, err := sdk.AccAddressFromBech32(transmitterAddress) - require.NoError(t, err) - an, sn, err := tc.Account(accounts[0].Address) - require.NoError(t, err) - resp, err := tc.SignAndBroadcast([]sdk.Msg{banktypes.NewMsgSend(accounts[0].Address, transmitterID, sdk.NewCoins(sdk.NewInt64Coin(*cosmosChain.GasToken, 100000)))}, - an, sn, gpe.GasPrices()[*cosmosChain.GasToken], accounts[0].PrivateKey, txtypes.BroadcastMode_BROADCAST_MODE_SYNC) - tx, success := cosmosclient.AwaitTxCommitted(t, tc, resp.TxResponse.TxHash) - require.True(t, success) - require.Equal(t, types.CodeTypeOK, tx.TxResponse.Code) - require.NoError(t, err) - - // TODO: find a way to pull this test artifact from - // the chainlink-cosmos repo instead of copying it to cores testdata - contractID := cosmosclient.DeployTestContract(t, tendermintURL, chainID, *cosmosChain.GasToken, accounts[0], cosmosclient.Account{ - Name: "transmitter", - PrivateKey: cosmostxm.NewKeyWrapper(keystoreAdapter, transmitterAddress), - Address: transmitterID, - }, tc, testdir, "../../../testdata/cosmos/my_first_contract.wasm") - - tcFn := func() (cosmosclient.ReaderWriter, error) { return tc, nil } - // Start txm - txm := cosmostxm.NewTxm(db, tcFn, *gpe, chainID, &chainConfig, loopKs, lggr, pgtest.NewQConfig(true), eb) - require.NoError(t, txm.Start(testutils.Context(t))) - - // Change the contract state - setMsg := &wasmtypes.MsgExecuteContract{ - Sender: transmitterID.String(), - Contract: contractID.String(), - Msg: []byte(`{"reset":{"count":5}}`), - Funds: sdk.Coins{}, - } - _, err = txm.Enqueue(contractID.String(), setMsg) - require.NoError(t, err) - - // Observe the counter gets set eventually - gomega.NewWithT(t).Eventually(func() bool { - d, err := tc.ContractState(contractID, []byte(`{"get_count":{}}`)) - require.NoError(t, err) - t.Log("contract value", string(d)) - return string(d) == `{"count":5}` - }, 20*time.Second, time.Second).Should(gomega.BeTrue()) - // Ensure messages are completed - gomega.NewWithT(t).Eventually(func() bool { - 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.MsgExecuteContract{ - Sender: transmitterID.String(), - Contract: contractID.String(), - Msg: []byte(`{"blah":{"blah":5}}`), - Funds: sdk.Coins{}, - } - _, 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(), setMsg) - require.NoError(t, err) - - // Ensure messages are completed - gomega.NewWithT(t).Eventually(func() bool { - succeeded, err := orm.GetMsgsState(Confirmed, 5) - require.NoError(t, err) - 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) - }, 20*time.Second, time.Second).Should(gomega.BeTrue()) - - // Observe the messages have been marked as completed - require.NoError(t, txm.Close()) -} - -func ptr[T any](t T) *T { return &t } diff --git a/core/chains/cosmos/relayer_adapter.go b/core/chains/cosmos/relayer_adapter.go deleted file mode 100644 index ace441c2bb5..00000000000 --- a/core/chains/cosmos/relayer_adapter.go +++ /dev/null @@ -1,50 +0,0 @@ -package cosmos - -import ( - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/adapters" - - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - - pkgcosmos "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos" - "github.com/smartcontractkit/chainlink/v2/core/chains" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" -) - -// LegacyChainContainer is container interface for Cosmos chains -type LegacyChainContainer interface { - Get(id string) (adapters.Chain, error) - Len() int - List(ids ...string) ([]adapters.Chain, error) - Slice() []adapters.Chain -} - -type LegacyChains = chains.ChainsKV[adapters.Chain] - -var _ LegacyChainContainer = &LegacyChains{} - -func NewLegacyChains(m map[string]adapters.Chain) *LegacyChains { - return chains.NewChainsKV[adapters.Chain](m) -} - -type LoopRelayerChainer interface { - loop.Relayer - Chain() adapters.Chain -} - -type LoopRelayerChain struct { - loop.Relayer - chain adapters.Chain -} - -func NewLoopRelayerChain(r *pkgcosmos.Relayer, s adapters.Chain) *LoopRelayerChain { - ra := relay.NewServerAdapter(r, s) - return &LoopRelayerChain{ - Relayer: ra, - chain: s, - } -} -func (r *LoopRelayerChain) Chain() adapters.Chain { - return r.chain -} - -var _ LoopRelayerChainer = &LoopRelayerChain{} diff --git a/core/chains/cosmos/types/types.go b/core/chains/cosmos/types/types.go deleted file mode 100644 index 69b086a9706..00000000000 --- a/core/chains/cosmos/types/types.go +++ /dev/null @@ -1,8 +0,0 @@ -package types - -// NewNode defines a new node to create. -type NewNode struct { - Name string `json:"name"` - CosmosChainID string `json:"cosmosChainId"` - TendermintURL string `json:"tendermintURL" db:"tendermint_url"` -} diff --git a/core/chains/evm/assets/assets.go b/core/chains/evm/assets/assets.go new file mode 100644 index 00000000000..377e92a855a --- /dev/null +++ b/core/chains/evm/assets/assets.go @@ -0,0 +1,117 @@ +package assets + +import ( + "database/sql/driver" + "fmt" + "math/big" + + "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/utils/bytes" + "github.com/smartcontractkit/chainlink/v2/core/utils" + + "github.com/shopspring/decimal" +) + +// Eth contains a field to represent the smallest units of ETH +type Eth big.Int + +// NewEth returns a new struct to represent ETH from it's smallest unit (wei) +func NewEth(w int64) *Eth { + return (*Eth)(big.NewInt(w)) +} + +// NewEthValue returns a new struct to represent ETH from it's smallest unit (wei) +func NewEthValue(w int64) Eth { + eth := NewEth(w) + return *eth +} + +// NewEthValueS returns a new struct to represent ETH from a string value of Eth (not wei) +// the underlying value is still wei +func NewEthValueS(s string) (Eth, error) { + e, err := decimal.NewFromString(s) + if err != nil { + return Eth{}, err + } + w := e.Mul(decimal.RequireFromString("10").Pow(decimal.RequireFromString("18"))) + return *(*Eth)(w.BigInt()), nil +} + +// Cmp delegates to *big.Int.Cmp +func (e *Eth) Cmp(y *Eth) int { + return e.ToInt().Cmp(y.ToInt()) +} + +func (e *Eth) String() string { + if e == nil { + return "" + } + return assets.Format(e.ToInt(), 18) +} + +// SetInt64 delegates to *big.Int.SetInt64 +func (e *Eth) SetInt64(w int64) *Eth { + return (*Eth)(e.ToInt().SetInt64(w)) +} + +// SetString delegates to *big.Int.SetString +func (e *Eth) SetString(s string, base int) (*Eth, bool) { + w, ok := e.ToInt().SetString(s, base) + return (*Eth)(w), ok +} + +// MarshalJSON implements the json.Marshaler interface. +func (e Eth) MarshalJSON() ([]byte, error) { + value, err := e.MarshalText() + if err != nil { + return nil, err + } + return []byte(fmt.Sprintf(`"%s"`, value)), nil +} + +// MarshalText implements the encoding.TextMarshaler interface. +func (e *Eth) MarshalText() ([]byte, error) { + return e.ToInt().MarshalText() +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (e *Eth) UnmarshalJSON(data []byte) error { + if bytes.HasQuotes(data) { + return e.UnmarshalText(bytes.TrimQuotes(data)) + } + return assets.ErrNoQuotesForCurrency +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +func (e *Eth) UnmarshalText(text []byte) error { + if _, ok := e.SetString(string(text), 10); !ok { + return fmt.Errorf("assets: cannot unmarshal %q into a *assets.Eth", text) + } + return nil +} + +// IsZero returns true when the value is 0 and false otherwise +func (e *Eth) IsZero() bool { + zero := big.NewInt(0) + return e.ToInt().Cmp(zero) == 0 +} + +// Symbol returns ETH +func (*Eth) Symbol() string { + return "ETH" +} + +// ToInt returns the Eth value as a *big.Int. +func (e *Eth) ToInt() *big.Int { + return (*big.Int)(e) +} + +// Scan reads the database value and returns an instance. +func (e *Eth) Scan(value interface{}) error { + return (*utils.Big)(e).Scan(value) +} + +// Value returns the Eth value for serialization to database. +func (e Eth) Value() (driver.Value, error) { + return (utils.Big)(e).Value() +} diff --git a/core/chains/evm/assets/assets_test.go b/core/chains/evm/assets/assets_test.go new file mode 100644 index 00000000000..09eb7b6888f --- /dev/null +++ b/core/chains/evm/assets/assets_test.go @@ -0,0 +1,116 @@ +package assets_test + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" +) + +func TestAssets_NewEthAndString(t *testing.T) { + t.Parallel() + + eth := assets.NewEth(0) + + assert.Equal(t, "0.000000000000000000", eth.String()) + + eth.SetInt64(1) + assert.Equal(t, "0.000000000000000001", eth.String()) + + eth.SetString("900000000000000000", 10) + assert.Equal(t, "0.900000000000000000", eth.String()) + + eth.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639935", 10) + assert.Equal(t, "115792089237316195423570985008687907853269984665640564039457.584007913129639935", eth.String()) + + eth.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639936", 10) + assert.Equal(t, "115792089237316195423570985008687907853269984665640564039457.584007913129639936", eth.String()) +} + +func TestAssets_Eth_IsZero(t *testing.T) { + t.Parallel() + + zeroEth := assets.NewEth(0) + assert.True(t, zeroEth.IsZero()) + + oneLink := assets.NewEth(1) + assert.False(t, oneLink.IsZero()) +} + +func TestAssets_Eth_MarshalJson(t *testing.T) { + t.Parallel() + + eth := assets.NewEth(1) + + b, err := json.Marshal(eth) + assert.NoError(t, err) + assert.Equal(t, []byte(`"1"`), b) +} + +func TestAssets_Eth_UnmarshalJsonOk(t *testing.T) { + t.Parallel() + + eth := assets.Eth{} + + err := json.Unmarshal([]byte(`"1"`), ð) + assert.NoError(t, err) + assert.Equal(t, "0.000000000000000001", eth.String()) +} + +func TestAssets_Eth_UnmarshalJsonError(t *testing.T) { + t.Parallel() + + eth := assets.Eth{} + + err := json.Unmarshal([]byte(`"x"`), ð) + assert.EqualError(t, err, "assets: cannot unmarshal \"x\" into a *assets.Eth") + + err = json.Unmarshal([]byte(`1`), ð) + assert.Equal(t, commonassets.ErrNoQuotesForCurrency, err) +} + +func TestAssets_NewEth(t *testing.T) { + t.Parallel() + + ethRef := assets.NewEth(123) + ethVal := assets.NewEthValue(123) + ethStr, err := assets.NewEthValueS(ethRef.String()) + assert.NoError(t, err) + assert.Equal(t, *ethRef, ethVal) + assert.Equal(t, *ethRef, ethStr) +} + +func TestAssets_EthSymbol(t *testing.T) { + t.Parallel() + + eth := assets.NewEth(123) + assert.Equal(t, "ETH", eth.Symbol()) +} + +func TestAssets_EthScanValue(t *testing.T) { + t.Parallel() + + eth := assets.NewEth(123) + v, err := eth.Value() + assert.NoError(t, err) + + eth2 := assets.NewEth(0) + err = eth2.Scan(v) + assert.NoError(t, err) + + assert.Equal(t, eth, eth2) +} + +func TestAssets_EthCmpEth(t *testing.T) { + t.Parallel() + + eth1 := assets.NewEth(123) + eth2 := assets.NewEth(321) + assert.NotZero(t, eth1.Cmp(eth2)) + + eth3 := assets.NewEth(321) + assert.Zero(t, eth3.Cmp(eth2)) +} diff --git a/core/assets/units.go b/core/chains/evm/assets/units.go similarity index 100% rename from core/assets/units.go rename to core/chains/evm/assets/units.go diff --git a/core/assets/units_test.go b/core/chains/evm/assets/units_test.go similarity index 93% rename from core/assets/units_test.go rename to core/chains/evm/assets/units_test.go index 7b23072033d..37eb1393257 100644 --- a/core/assets/units_test.go +++ b/core/chains/evm/assets/units_test.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/assert" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" ) func TestAssets_Units(t *testing.T) { diff --git a/core/assets/wei.go b/core/chains/evm/assets/wei.go similarity index 98% rename from core/assets/wei.go rename to core/chains/evm/assets/wei.go index 7d1240d384c..be0143b3a86 100644 --- a/core/assets/wei.go +++ b/core/chains/evm/assets/wei.go @@ -10,8 +10,8 @@ import ( "github.com/shopspring/decimal" "golang.org/x/exp/constraints" + bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" "github.com/smartcontractkit/chainlink/v2/core/utils" - bigmath "github.com/smartcontractkit/chainlink/v2/core/utils/big_math" ) const ( diff --git a/core/assets/wei_test.go b/core/chains/evm/assets/wei_test.go similarity index 100% rename from core/assets/wei_test.go rename to core/chains/evm/assets/wei_test.go diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go new file mode 100644 index 00000000000..3efc5645e22 --- /dev/null +++ b/core/chains/evm/client/chain_client.go @@ -0,0 +1,275 @@ +package client + +import ( + "context" + "math/big" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" +) + +var _ Client = (*chainClient)(nil) + +// TODO-1663: rename this to client, once the client.go file is deprecated. +type chainClient struct { + multiNode commonclient.MultiNode[ + *big.Int, + evmtypes.Nonce, + common.Address, + common.Hash, + *types.Transaction, + common.Hash, + types.Log, + ethereum.FilterQuery, + *evmtypes.Receipt, + *assets.Wei, + *evmtypes.Head, + RPCCLient, + ] + logger logger.Logger +} + +func NewChainClient( + logger logger.Logger, + selectionMode string, + leaseDuration time.Duration, + noNewHeadsThreshold time.Duration, + nodes []commonclient.Node[*big.Int, *evmtypes.Head, RPCCLient], + sendonlys []commonclient.SendOnlyNode[*big.Int, RPCCLient], + chainID *big.Int, + chainType config.ChainType, +) Client { + multiNode := commonclient.NewMultiNode[ + *big.Int, + evmtypes.Nonce, + common.Address, + common.Hash, + *types.Transaction, + common.Hash, + types.Log, + ethereum.FilterQuery, + *evmtypes.Receipt, + *assets.Wei, + *evmtypes.Head, + RPCCLient, + ]( + logger, + selectionMode, + leaseDuration, + noNewHeadsThreshold, + nodes, + sendonlys, + chainID, + chainType, + "EVM", + ClassifySendOnlyError, + ) + return &chainClient{ + multiNode: multiNode, + logger: logger, + } +} + +func (c *chainClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { + return c.multiNode.BalanceAt(ctx, account, blockNumber) +} + +func (c *chainClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { + batch := make([]any, len(b)) + for i, arg := range b { + batch[i] = any(arg) + } + return c.multiNode.BatchCallContext(ctx, batch) +} + +func (c *chainClient) BatchCallContextAll(ctx context.Context, b []rpc.BatchElem) error { + batch := make([]any, len(b)) + for i, arg := range b { + batch[i] = any(arg) + } + return c.multiNode.BatchCallContextAll(ctx, batch) +} + +// TODO-1663: return custom Block type instead of geth's once client.go is deprecated. +func (c *chainClient) BlockByHash(ctx context.Context, hash common.Hash) (b *types.Block, err error) { + rpc, err := c.multiNode.SelectNodeRPC() + if err != nil { + return b, err + } + return rpc.BlockByHashGeth(ctx, hash) +} + +// TODO-1663: return custom Block type instead of geth's once client.go is deprecated. +func (c *chainClient) BlockByNumber(ctx context.Context, number *big.Int) (b *types.Block, err error) { + rpc, err := c.multiNode.SelectNodeRPC() + if err != nil { + return b, err + } + return rpc.BlockByNumberGeth(ctx, number) +} + +func (c *chainClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { + return c.multiNode.CallContext(ctx, result, method, args...) +} + +func (c *chainClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + return c.multiNode.CallContract(ctx, msg, blockNumber) +} + +// TODO-1663: change this to actual ChainID() call once client.go is deprecated. +func (c *chainClient) ChainID() (*big.Int, error) { + //return c.multiNode.ChainID(ctx), nil + return c.multiNode.ConfiguredChainID(), nil +} + +func (c *chainClient) Close() { + c.multiNode.Close() +} + +func (c *chainClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { + return c.multiNode.CodeAt(ctx, account, blockNumber) +} + +func (c *chainClient) ConfiguredChainID() *big.Int { + return c.multiNode.ConfiguredChainID() +} + +func (c *chainClient) Dial(ctx context.Context) error { + return c.multiNode.Dial(ctx) +} + +func (c *chainClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + return c.multiNode.EstimateGas(ctx, call) +} +func (c *chainClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + return c.multiNode.FilterEvents(ctx, q) +} + +func (c *chainClient) HeaderByHash(ctx context.Context, h common.Hash) (head *types.Header, err error) { + rpc, err := c.multiNode.SelectNodeRPC() + if err != nil { + return head, err + } + return rpc.HeaderByHash(ctx, h) +} + +func (c *chainClient) HeaderByNumber(ctx context.Context, n *big.Int) (head *types.Header, err error) { + rpc, err := c.multiNode.SelectNodeRPC() + if err != nil { + return head, err + } + return rpc.HeaderByNumber(ctx, n) +} + +func (c *chainClient) HeadByHash(ctx context.Context, h common.Hash) (*evmtypes.Head, error) { + return c.multiNode.BlockByHash(ctx, h) +} + +func (c *chainClient) HeadByNumber(ctx context.Context, n *big.Int) (*evmtypes.Head, error) { + return c.multiNode.BlockByNumber(ctx, n) +} + +func (c *chainClient) IsL2() bool { + return c.multiNode.IsL2() +} + +func (c *chainClient) LINKBalance(ctx context.Context, address common.Address, linkAddress common.Address) (*commonassets.Link, error) { + return c.multiNode.LINKBalance(ctx, address, linkAddress) +} + +func (c *chainClient) LatestBlockHeight(ctx context.Context) (*big.Int, error) { + return c.multiNode.LatestBlockHeight(ctx) +} + +func (c *chainClient) NodeStates() map[string]string { + return c.multiNode.NodeStates() +} + +func (c *chainClient) PendingCodeAt(ctx context.Context, account common.Address) (b []byte, err error) { + rpc, err := c.multiNode.SelectNodeRPC() + if err != nil { + return b, err + } + return rpc.PendingCodeAt(ctx, account) +} + +// TODO-1663: change this to evmtypes.Nonce(int64) once client.go is deprecated. +func (c *chainClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + n, err := c.multiNode.PendingSequenceAt(ctx, account) + return uint64(n), err +} + +func (c *chainClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { + return c.multiNode.SendTransaction(ctx, tx) +} + +func (c *chainClient) SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, fromAddress common.Address) (commonclient.SendTxReturnCode, error) { + err := c.SendTransaction(ctx, tx) + return ClassifySendError(err, c.logger, tx, fromAddress, c.IsL2()) +} + +func (c *chainClient) SequenceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (evmtypes.Nonce, error) { + return c.multiNode.SequenceAt(ctx, account, blockNumber) +} + +func (c *chainClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (s ethereum.Subscription, err error) { + rpc, err := c.multiNode.SelectNodeRPC() + if err != nil { + return s, err + } + return rpc.SubscribeFilterLogs(ctx, q, ch) +} + +func (c *chainClient) SubscribeNewHead(ctx context.Context, ch chan<- *evmtypes.Head) (ethereum.Subscription, error) { + csf := newChainIDSubForwarder(c.ConfiguredChainID(), ch) + err := csf.start(c.multiNode.Subscribe(ctx, csf.srcCh, "newHeads")) + if err != nil { + return nil, err + } + return csf, nil +} + +func (c *chainClient) SuggestGasPrice(ctx context.Context) (p *big.Int, err error) { + rpc, err := c.multiNode.SelectNodeRPC() + if err != nil { + return p, err + } + return rpc.SuggestGasPrice(ctx) +} + +func (c *chainClient) SuggestGasTipCap(ctx context.Context) (t *big.Int, err error) { + rpc, err := c.multiNode.SelectNodeRPC() + if err != nil { + return t, err + } + return rpc.SuggestGasTipCap(ctx) +} + +func (c *chainClient) TokenBalance(ctx context.Context, address common.Address, contractAddress common.Address) (*big.Int, error) { + return c.multiNode.TokenBalance(ctx, address, contractAddress) +} + +func (c *chainClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, error) { + return c.multiNode.TransactionByHash(ctx, txHash) +} + +// TODO-1663: return custom Receipt type instead of geth's once client.go is deprecated. +func (c *chainClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (r *types.Receipt, err error) { + rpc, err := c.multiNode.SelectNodeRPC() + if err != nil { + return r, err + } + //return rpc.TransactionReceipt(ctx, txHash) + return rpc.TransactionReceiptGeth(ctx, txHash) +} diff --git a/core/chains/evm/client/client.go b/core/chains/evm/client/client.go index 3a3b8b23a92..688cc3c9bea 100644 --- a/core/chains/evm/client/client.go +++ b/core/chains/evm/client/client.go @@ -2,16 +2,18 @@ package client import ( "context" + "fmt" "math/big" "strings" "time" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" + "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + "github.com/smartcontractkit/chainlink/v2/common/config" htrktypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/config" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/ethereum/go-ethereum" @@ -61,7 +63,7 @@ type Client interface { HeadByHash(ctx context.Context, n common.Hash) (*evmtypes.Head, error) SubscribeNewHead(ctx context.Context, ch chan<- *evmtypes.Head) (ethereum.Subscription, error) - SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, fromAddress common.Address) (clienttypes.SendTxReturnCode, error) + SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, fromAddress common.Address) (commonclient.SendTxReturnCode, error) // Wrapped Geth client methods // blockNumber can be specified as `nil` to imply latest block @@ -108,6 +110,8 @@ var _ htrktypes.Client[*evmtypes.Head, ethereum.Subscription, *big.Int, common.H // NewClientWithNodes instantiates a client from a list of nodes // Currently only supports one primary +// +// Deprecated: use [NewChainClient] func NewClientWithNodes(logger logger.Logger, selectionMode string, leaseDuration time.Duration, noNewHeadsThreshold time.Duration, primaryNodes []Node, sendOnlyNodes []SendOnlyNode, chainID *big.Int, chainType config.ChainType) (*client, error) { pool := NewPool(logger, selectionMode, leaseDuration, noNewHeadsThreshold, primaryNodes, sendOnlyNodes, chainID, chainType) return &client{ @@ -163,7 +167,9 @@ func (client *client) TokenBalance(ctx context.Context, address common.Address, if err != nil { return numLinkBigInt, err } - numLinkBigInt.SetString(result, 0) + if _, ok := numLinkBigInt.SetString(result, 0); !ok { + return nil, fmt.Errorf("failed to parse int: %s", result) + } return numLinkBigInt, nil } @@ -211,9 +217,9 @@ func (client *client) HeaderByHash(ctx context.Context, h common.Hash) (*types.H return client.pool.HeaderByHash(ctx, h) } -func (client *client) SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, fromAddress common.Address) (clienttypes.SendTxReturnCode, error) { +func (client *client) SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, fromAddress common.Address) (commonclient.SendTxReturnCode, error) { err := client.SendTransaction(ctx, tx) - return NewSendErrorReturnCode(err, client.logger, tx, fromAddress, client.pool.ChainType().IsL2()) + return ClassifySendError(err, client.logger, tx, fromAddress, client.pool.ChainType().IsL2()) } // SendTransaction also uses the sendonly HTTP RPC URLs if set diff --git a/core/chains/evm/client/client_test.go b/core/chains/evm/client/client_test.go index 88bc37411c6..631b5722dec 100644 --- a/core/chains/evm/client/client_test.go +++ b/core/chains/evm/client/client_test.go @@ -22,27 +22,55 @@ import ( "github.com/stretchr/testify/require" "github.com/tidwall/gjson" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/utils" ) -func mustNewClient(t *testing.T, wsURL string, sendonlys ...url.URL) evmclient.Client { +func mustNewClient(t *testing.T, wsURL string, sendonlys ...url.URL) client.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.TestNodePoolConfig{ - NodeSelectionMode: evmclient.NodeSelectionMode_RoundRobin, +func mustNewClientWithChainID(t *testing.T, wsURL string, chainID *big.Int, sendonlys ...url.URL) client.Client { + cfg := client.TestNodePoolConfig{ + NodeSelectionMode: client.NodeSelectionMode_RoundRobin, } - c, err := evmclient.NewClientWithTestNode(t, cfg, time.Second*0, wsURL, nil, sendonlys, 42, chainID) + c, err := client.NewClientWithTestNode(t, cfg, time.Second*0, wsURL, nil, sendonlys, 42, chainID) require.NoError(t, err) return c } +func mustNewChainClient(t *testing.T, wsURL string, sendonlys ...url.URL) client.Client { + return mustNewChainClientWithChainID(t, wsURL, testutils.FixtureChainID, sendonlys...) +} + +func mustNewChainClientWithChainID(t *testing.T, wsURL string, chainID *big.Int, sendonlys ...url.URL) client.Client { + cfg := client.TestNodePoolConfig{ + NodeSelectionMode: client.NodeSelectionMode_RoundRobin, + } + c, err := client.NewChainClientWithTestNode(t, cfg, time.Second*0, cfg.NodeLeaseDuration, wsURL, nil, sendonlys, 42, chainID) + require.NoError(t, err) + return c +} + +func mustNewClients(t *testing.T, wsURL string, sendonlys ...url.URL) []client.Client { + var clients []client.Client + clients = append(clients, mustNewClient(t, wsURL, sendonlys...)) + clients = append(clients, mustNewChainClient(t, wsURL, sendonlys...)) + return clients +} + +func mustNewClientsWithChainID(t *testing.T, wsURL string, chainID *big.Int, sendonlys ...url.URL) []client.Client { + var clients []client.Client + clients = append(clients, mustNewClientWithChainID(t, wsURL, chainID, sendonlys...)) + clients = append(clients, mustNewChainClientWithChainID(t, wsURL, chainID, sendonlys...)) + return clients +} + func TestEthClient_TransactionReceipt(t *testing.T) { t.Parallel() @@ -61,7 +89,7 @@ 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) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -76,22 +104,24 @@ func TestEthClient_TransactionReceipt(t *testing.T) { resp.Result = string(result) } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - hash := common.HexToHash(txHash) - receipt, err := ethClient.TransactionReceipt(testutils.Context(t), hash) - require.NoError(t, err) - assert.Equal(t, hash, receipt.TxHash) - assert.Equal(t, big.NewInt(11), receipt.BlockNumber) + hash := common.HexToHash(txHash) + receipt, err := ethClient.TransactionReceipt(testutils.Context(t), hash) + require.NoError(t, err) + assert.Equal(t, hash, receipt.TxHash) + assert.Equal(t, big.NewInt(11), receipt.BlockNumber) + } }) 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) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -106,15 +136,17 @@ func TestEthClient_TransactionReceipt(t *testing.T) { resp.Result = string(result) } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - hash := common.HexToHash(txHash) - _, err = ethClient.TransactionReceipt(testutils.Context(t), hash) - require.Equal(t, ethereum.NotFound, errors.Cause(err)) + hash := common.HexToHash(txHash) + _, err = ethClient.TransactionReceipt(testutils.Context(t), hash) + require.Equal(t, ethereum.NotFound, errors.Cause(err)) + } }) } @@ -123,7 +155,7 @@ func TestEthClient_PendingNonceAt(t *testing.T) { address := testutils.NewAddress() - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -142,17 +174,19 @@ func TestEthClient_PendingNonceAt(t *testing.T) { resp.Result = `"0x100"` } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - result, err := ethClient.PendingNonceAt(testutils.Context(t), address) - require.NoError(t, err) + result, err := ethClient.PendingNonceAt(testutils.Context(t), address) + require.NoError(t, err) - var expected uint64 = 256 - require.Equal(t, result, expected) + var expected uint64 = 256 + require.Equal(t, result, expected) + } } func TestEthClient_BalanceAt(t *testing.T) { @@ -172,7 +206,7 @@ func TestEthClient_BalanceAt(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -187,15 +221,17 @@ func TestEthClient_BalanceAt(t *testing.T) { resp.Result = `"` + hexutil.EncodeBig(test.balance) + `"` } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - result, err := ethClient.BalanceAt(testutils.Context(t), address, nil) - require.NoError(t, err) - assert.Equal(t, test.balance, result) + result, err := ethClient.BalanceAt(testutils.Context(t), address, nil) + require.NoError(t, err) + assert.Equal(t, test.balance, result) + } }) } } @@ -203,7 +239,7 @@ func TestEthClient_BalanceAt(t *testing.T) { func TestEthClient_LatestBlockHeight(t *testing.T) { t.Parallel() - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -218,15 +254,17 @@ func TestEthClient_LatestBlockHeight(t *testing.T) { } resp.Result = `"0x100"` return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - result, err := ethClient.LatestBlockHeight(testutils.Context(t)) - require.NoError(t, err) - require.Equal(t, big.NewInt(256), result) + result, err := ethClient.LatestBlockHeight(testutils.Context(t)) + require.NoError(t, err) + require.Equal(t, big.NewInt(256), result) + } } func TestEthClient_GetERC20Balance(t *testing.T) { @@ -248,10 +286,10 @@ func TestEthClient_GetERC20Balance(t *testing.T) { t.Run(test.name, func(t *testing.T) { contractAddress := testutils.NewAddress() userAddress := testutils.NewAddress() - functionSelector := evmtypes.HexToFunctionSelector(evmclient.BALANCE_OF_ADDRESS_FUNCTION_SELECTOR) // balanceOf(address) + functionSelector := evmtypes.HexToFunctionSelector(client.BALANCE_OF_ADDRESS_FUNCTION_SELECTOR) // balanceOf(address) txData := utils.ConcatBytes(functionSelector.Bytes(), common.LeftPadBytes(userAddress.Bytes(), utils.EVMWordByteLen)) - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -274,16 +312,17 @@ func TestEthClient_GetERC20Balance(t *testing.T) { resp.Result = `"` + hexutil.EncodeBig(test.balance) + `"` } return + }).WSURL().String() - }) - - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - result, err := ethClient.TokenBalance(ctx, userAddress, contractAddress) - require.NoError(t, err) - assert.Equal(t, test.balance, result) + result, err := ethClient.TokenBalance(ctx, userAddress, contractAddress) + require.NoError(t, err) + assert.Equal(t, test.balance, result) + } }) } } @@ -330,7 +369,7 @@ func TestEthClient_HeaderByNumber(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -352,22 +391,24 @@ func TestEthClient_HeaderByNumber(t *testing.T) { resp.Result = test.rpcResp } return - }) - - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + }).WSURL().String() - ctx, cancel := context.WithTimeout(testutils.Context(t), 5*time.Second) - defer cancel() - result, err := ethClient.HeadByNumber(ctx, expectedBlockNum) - if test.error != nil { - require.Error(t, err, test.error) - } else { + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) require.NoError(t, err) - require.Equal(t, expectedBlockHash, result.Hash.Hex()) - require.Equal(t, test.expectedResponseBlock, result.Number) - require.Zero(t, cltest.FixtureChainID.Cmp(result.EVMChainID.ToInt())) + + ctx, cancel := context.WithTimeout(testutils.Context(t), 5*time.Second) + result, err := ethClient.HeadByNumber(ctx, expectedBlockNum) + if test.error != nil { + require.Error(t, err, test.error) + } else { + require.NoError(t, err) + require.Equal(t, expectedBlockHash, result.Hash.Hex()) + require.Equal(t, test.expectedResponseBlock, result.Number) + require.Zero(t, cltest.FixtureChainID.Cmp(result.EVMChainID.ToInt())) + } + cancel() } }) } @@ -378,7 +419,7 @@ 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}) - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -393,14 +434,16 @@ func TestEthClient_SendTransaction_NoSecondaryURL(t *testing.T) { } resp.Result = `"` + tx.Hash().Hex() + `"` return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - err = ethClient.SendTransaction(testutils.Context(t), tx) - assert.NoError(t, err) + err = ethClient.SendTransaction(testutils.Context(t), tx) + assert.NoError(t, err) + } } func TestEthClient_SendTransaction_WithSecondaryURLs(t *testing.T) { @@ -408,7 +451,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) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -421,7 +464,7 @@ func TestEthClient_SendTransaction_WithSecondaryURLs(t *testing.T) { resp.Result = `"` + tx.Hash().Hex() + `"` } return - }) + }).WSURL().String() rpcSrv := rpc.NewServer() t.Cleanup(rpcSrv.Stop) @@ -432,16 +475,19 @@ func TestEthClient_SendTransaction_WithSecondaryURLs(t *testing.T) { t.Cleanup(ts.Close) sendonlyURL := *cltest.MustParseURL(t, ts.URL) - ethClient := mustNewClient(t, wsURL, sendonlyURL, sendonlyURL) - err = ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) - err = ethClient.SendTransaction(testutils.Context(t), tx) - require.NoError(t, err) + clients := mustNewClients(t, wsURL, sendonlyURL, sendonlyURL) + for _, ethClient := range clients { + err = ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) + + err = ethClient.SendTransaction(testutils.Context(t), tx) + require.NoError(t, err) + } // Unfortunately it's a bit tricky to test this, since there is no // synchronization. We have to rely on timing instead. - require.Eventually(t, func() bool { return service.sentCount.Load() == int32(2) }, testutils.WaitTimeout(t), 500*time.Millisecond) + require.Eventually(t, func() bool { return service.sentCount.Load() == int32(len(clients)*2) }, testutils.WaitTimeout(t), 500*time.Millisecond) } func TestEthClient_SendTransactionReturnCode(t *testing.T) { @@ -451,7 +497,7 @@ func TestEthClient_SendTransactionReturnCode(t *testing.T) { tx := types.NewTransaction(uint64(42), testutils.NewAddress(), big.NewInt(142), 242, big.NewInt(342), []byte{1, 2, 3}) t.Run("returns Fatal error type when error message is fatal", func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -465,19 +511,21 @@ func TestEthClient_SendTransactionReturnCode(t *testing.T) { resp.Error.Message = "invalid sender" } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) - assert.Error(t, err) - assert.Equal(t, errType, clienttypes.Fatal) + errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) + assert.Error(t, err) + assert.Equal(t, errType, commonclient.Fatal) + } }) t.Run("returns TransactionAlreadyKnown error type when error message is nonce too low", func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -491,19 +539,21 @@ func TestEthClient_SendTransactionReturnCode(t *testing.T) { resp.Error.Message = "nonce too low" } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) - assert.Error(t, err) - assert.Equal(t, errType, clienttypes.TransactionAlreadyKnown) + errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) + assert.Error(t, err) + assert.Equal(t, errType, commonclient.TransactionAlreadyKnown) + } }) t.Run("returns Successful error type when there is no error message", func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -516,19 +566,21 @@ func TestEthClient_SendTransactionReturnCode(t *testing.T) { resp.Result = `"` + tx.Hash().Hex() + `"` } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) - assert.NoError(t, err) - assert.Equal(t, errType, clienttypes.Successful) + errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) + assert.NoError(t, err) + assert.Equal(t, errType, commonclient.Successful) + } }) t.Run("returns Underpriced error type when transaction is terminally underpriced", func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -542,19 +594,21 @@ func TestEthClient_SendTransactionReturnCode(t *testing.T) { resp.Error.Message = "transaction underpriced" } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) - assert.Error(t, err) - assert.Equal(t, errType, clienttypes.Underpriced) + errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) + assert.Error(t, err) + assert.Equal(t, errType, commonclient.Underpriced) + } }) t.Run("returns Unsupported error type when error message is queue full", func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -568,19 +622,21 @@ func TestEthClient_SendTransactionReturnCode(t *testing.T) { resp.Error.Message = "queue full" } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) - assert.Error(t, err) - assert.Equal(t, errType, clienttypes.Unsupported) + errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) + assert.Error(t, err) + assert.Equal(t, errType, commonclient.Unsupported) + } }) t.Run("returns Retryable error type when there is a transaction gap", func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -594,19 +650,21 @@ func TestEthClient_SendTransactionReturnCode(t *testing.T) { resp.Error.Message = "NonceGap" } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) - assert.Error(t, err) - assert.Equal(t, errType, clienttypes.Retryable) + errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) + assert.Error(t, err) + assert.Equal(t, errType, commonclient.Retryable) + } }) t.Run("returns InsufficientFunds error type when the sender address doesn't have enough funds", func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -620,19 +678,21 @@ func TestEthClient_SendTransactionReturnCode(t *testing.T) { resp.Error.Message = "insufficient funds for transfer" } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) - assert.Error(t, err) - assert.Equal(t, errType, clienttypes.InsufficientFunds) + errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) + assert.Error(t, err) + assert.Equal(t, errType, commonclient.InsufficientFunds) + } }) t.Run("returns ExceedsFeeCap error type when gas price is too high for the node", func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -646,19 +706,21 @@ func TestEthClient_SendTransactionReturnCode(t *testing.T) { resp.Error.Message = "Transaction fee cap exceeded" } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) - assert.Error(t, err) - assert.Equal(t, errType, clienttypes.ExceedsMaxFee) + errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) + assert.Error(t, err) + assert.Equal(t, errType, commonclient.ExceedsMaxFee) + } }) t.Run("returns Unknown error type when the error can't be categorized", func(t *testing.T) { - wsURL := cltest.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -672,15 +734,17 @@ func TestEthClient_SendTransactionReturnCode(t *testing.T) { resp.Error.Message = "some random error" } return - }) + }).WSURL().String() - ethClient := mustNewClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClients(t, wsURL) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) - assert.Error(t, err) - assert.Equal(t, errType, clienttypes.Unknown) + errType, err := ethClient.SendTransactionReturnCode(testutils.Context(t), tx, fromAddress) + assert.Error(t, err) + assert.Equal(t, errType, commonclient.Unknown) + } }) } @@ -705,7 +769,7 @@ func TestEthClient_SubscribeNewHead(t *testing.T) { defer cancel() chainId := big.NewInt(123456) - wsURL := cltest.NewWSServer(t, chainId, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + wsURL := testutils.NewWSServer(t, chainId, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { if method == "eth_unsubscribe" { resp.Result = "true" return @@ -716,26 +780,134 @@ func TestEthClient_SubscribeNewHead(t *testing.T) { resp.Notify = headResult } return - }) + }).WSURL().String() - ethClient := mustNewClientWithChainID(t, wsURL, chainId) - err := ethClient.Dial(testutils.Context(t)) - require.NoError(t, err) + clients := mustNewClientsWithChainID(t, wsURL, chainId) + for _, ethClient := range clients { + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) - headCh := make(chan *evmtypes.Head) - sub, err := ethClient.SubscribeNewHead(ctx, headCh) - require.NoError(t, err) - defer sub.Unsubscribe() - - select { - case err := <-sub.Err(): - t.Fatal(err) - case <-ctx.Done(): - t.Fatal(ctx.Err()) - case h := <-headCh: - require.NotNil(t, h.EVMChainID) - require.Zero(t, chainId.Cmp(h.EVMChainID.ToInt())) + headCh := make(chan *evmtypes.Head) + sub, err := ethClient.SubscribeNewHead(ctx, headCh) + require.NoError(t, err) + + select { + case err := <-sub.Err(): + t.Fatal(err) + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case h := <-headCh: + require.NotNil(t, h.EVMChainID) + require.Zero(t, chainId.Cmp(h.EVMChainID.ToInt())) + } + sub.Unsubscribe() } } -const headResult = evmclient.HeadResult +func TestEthClient_ErroringClient(t *testing.T) { + t.Parallel() + ctx := testutils.Context(t) + + // Empty node means there are no active nodes to select from, causing client to always return error. + erroringClient := client.NewChainClientWithEmptyNode(t, commonclient.NodeSelectionModeRoundRobin, time.Second*0, time.Second*0, testutils.FixtureChainID) + + _, err := erroringClient.BalanceAt(ctx, common.Address{}, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + err = erroringClient.BatchCallContext(ctx, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + err = erroringClient.BatchCallContextAll(ctx, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.BlockByHash(ctx, common.Hash{}) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.BlockByNumber(ctx, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + err = erroringClient.CallContext(ctx, nil, "") + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.CallContract(ctx, ethereum.CallMsg{}, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + // TODO-1663: test actual ChainID() call once client.go is deprecated. + id, err := erroringClient.ChainID() + require.Equal(t, id, testutils.FixtureChainID) + //require.Equal(t, err, commonclient.ErroringNodeError) + require.Equal(t, err, nil) + + _, err = erroringClient.CodeAt(ctx, common.Address{}, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + id = erroringClient.ConfiguredChainID() + require.Equal(t, id, testutils.FixtureChainID) + + err = erroringClient.Dial(ctx) + require.ErrorContains(t, err, "no available nodes for chain") + + _, err = erroringClient.EstimateGas(ctx, ethereum.CallMsg{}) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.FilterLogs(ctx, ethereum.FilterQuery{}) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.HeaderByHash(ctx, common.Hash{}) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.HeaderByNumber(ctx, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.HeadByHash(ctx, common.Hash{}) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.HeadByNumber(ctx, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.LINKBalance(ctx, common.Address{}, common.Address{}) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.LatestBlockHeight(ctx) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.PendingCodeAt(ctx, common.Address{}) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.PendingNonceAt(ctx, common.Address{}) + require.Equal(t, err, commonclient.ErroringNodeError) + + err = erroringClient.SendTransaction(ctx, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + code, err := erroringClient.SendTransactionReturnCode(ctx, nil, common.Address{}) + require.Equal(t, code, commonclient.Unknown) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.SequenceAt(ctx, common.Address{}, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.SubscribeNewHead(ctx, nil) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.SuggestGasPrice(ctx) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.SuggestGasTipCap(ctx) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.TokenBalance(ctx, common.Address{}, common.Address{}) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.TransactionByHash(ctx, common.Hash{}) + require.Equal(t, err, commonclient.ErroringNodeError) + + _, err = erroringClient.TransactionReceipt(ctx, common.Hash{}) + require.Equal(t, err, commonclient.ErroringNodeError) + +} + +const headResult = client.HeadResult diff --git a/core/chains/evm/client/erroring_node.go b/core/chains/evm/client/erroring_node.go index 21c4d269ea4..c33891728a7 100644 --- a/core/chains/evm/client/erroring_node.go +++ b/core/chains/evm/client/erroring_node.go @@ -5,7 +5,6 @@ import ( "math/big" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -132,7 +131,7 @@ func (e *erroringNode) State() NodeState { return NodeStateUnreachable } -func (e *erroringNode) StateAndLatest() (NodeState, int64, *utils.Big) { +func (e *erroringNode) StateAndLatest() (NodeState, int64, *big.Int) { return NodeStateUnreachable, -1, nil } diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index 7b89e7b92d1..143a5f8806f 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -10,9 +10,9 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/label" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) // fatal means this transaction can never be accepted even with a different nonce or higher gas price @@ -207,7 +207,23 @@ var harmony = ClientErrors{ Fatal: harmonyFatal, } -var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo} +var zkSync = ClientErrors{ + NonceTooLow: regexp.MustCompile(`(?:: |^)nonce too low\..+actual: \d*$`), + NonceTooHigh: regexp.MustCompile(`(?:: |^)nonce too high\..+actual: \d*$`), + TerminallyUnderpriced: regexp.MustCompile(`(?:: |^)max fee per gas less than block base fee$`), + InsufficientEth: regexp.MustCompile(`(?:: |^)(?:insufficient balance for transfer$|insufficient funds for gas + value)`), + TxFeeExceedsCap: regexp.MustCompile(`(?:: |^)max priority fee per gas higher than max fee per gas$`), + // intrinsic gas too low - gas limit less than 14700 + // Not enough gas for transaction validation - gas limit less than L2 fee + // Failed to pay the fee to the operator - gas limit less than L2+L1 fee + // Error function_selector = 0x, data = 0x - contract call with gas limit of 0 + // can't start a transaction from a non-account - trying to send from an invalid address, e.g. estimating a contract -> contract tx + // max fee per gas higher than 2^64-1 - uint64 overflow + // oversized data - data too large + Fatal: regexp.MustCompile(`(?:: |^)(?:exceeds block gas limit|intrinsic gas too low|Not enough gas for transaction validation|Failed to pay the fee to the operator|Error function_selector = 0x, data = 0x|invalid sender. can't start a transaction from a non-account|max(?: priority)? fee per (?:gas|pubdata byte) higher than 2\^64-1|oversized data. max: \d+; actual: \d+)$`), +} + +var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync} func (s *SendError) is(errorType int) bool { if s == nil || s.err == nil { @@ -397,20 +413,20 @@ func ExtractRPCError(baseErr error) (*JsonError, error) { return &jErr, nil } -func NewSendErrorReturnCode(err error, lggr logger.Logger, tx *types.Transaction, fromAddress common.Address, isL2 bool) (clienttypes.SendTxReturnCode, error) { +func ClassifySendError(err error, lggr logger.Logger, tx *types.Transaction, fromAddress common.Address, isL2 bool) (commonclient.SendTxReturnCode, error) { sendError := NewSendError(err) if sendError == nil { - return clienttypes.Successful, err + return commonclient.Successful, err } if sendError.Fatal() { - lggr.Criticalw("Fatal error sending transaction", "err", sendError, "etx", tx) + logger.Criticalw(lggr, "Fatal error sending transaction", "err", sendError, "etx", tx) // Attempt is thrown away in this case; we don't need it since it never got accepted by a node - return clienttypes.Fatal, err + return commonclient.Fatal, err } if sendError.IsNonceTooLowError() || sendError.IsTransactionAlreadyMined() { // Nonce too low indicated that a transaction at this nonce was confirmed already. // Mark it as TransactionAlreadyKnown. - return clienttypes.TransactionAlreadyKnown, err + return commonclient.TransactionAlreadyKnown, err } if sendError.IsReplacementUnderpriced() { lggr.Errorw(fmt.Sprintf("Replacement transaction underpriced for eth_tx %x. "+ @@ -419,49 +435,61 @@ func NewSendErrorReturnCode(err error, lggr logger.Logger, tx *types.Transaction tx.Hash(), err), "gasPrice", tx.GasPrice, "gasTipCap", tx.GasTipCap, "gasFeeCap", tx.GasFeeCap) // Assume success and hand off to the next cycle. - return clienttypes.Successful, err + return commonclient.Successful, err } if sendError.IsTransactionAlreadyInMempool() { lggr.Debugw("Transaction already in mempool", "txHash", tx.Hash, "nodeErr", sendError.Error()) - return clienttypes.Successful, err + return commonclient.Successful, err } if sendError.IsTemporarilyUnderpriced() { lggr.Infow("Transaction temporarily underpriced", "err", sendError.Error()) - return clienttypes.Successful, err + return commonclient.Successful, err } if sendError.IsTerminallyUnderpriced() { - return clienttypes.Underpriced, err + return commonclient.Underpriced, err } if sendError.L2FeeTooLow() || sendError.IsL2FeeTooHigh() || sendError.IsL2Full() { if isL2 { - return clienttypes.FeeOutOfValidRange, err + return commonclient.FeeOutOfValidRange, err } - return clienttypes.Unsupported, errors.Wrap(sendError, "this error type only handled for L2s") + return commonclient.Unsupported, errors.Wrap(sendError, "this error type only handled for L2s") } if sendError.IsNonceTooHighError() { // This error occurs when the tx nonce is greater than current_nonce + tx_count_in_mempool, // instead of keeping the tx in mempool. This can happen if previous transactions haven't // reached the client yet. The correct thing to do is to mark it as retryable. lggr.Warnw("Transaction has a nonce gap.", "err", err) - return clienttypes.Retryable, err + return commonclient.Retryable, err } if sendError.IsInsufficientEth() { - lggr.Criticalw(fmt.Sprintf("Tx %x with type 0x%d was rejected due to insufficient eth: %s\n"+ + logger.Criticalw(lggr, fmt.Sprintf("Tx %x with type 0x%d was rejected due to insufficient eth: %s\n"+ "ACTION REQUIRED: Chainlink wallet with address 0x%x is OUT OF FUNDS", tx.Hash(), tx.Type(), sendError.Error(), fromAddress, ), "err", sendError) - return clienttypes.InsufficientFunds, err + return commonclient.InsufficientFunds, err } if sendError.IsTimeout() { - return clienttypes.Retryable, errors.Wrapf(sendError, "timeout while sending transaction %s", tx.Hash().Hex()) + return commonclient.Retryable, errors.Wrapf(sendError, "timeout while sending transaction %s", tx.Hash().Hex()) } if sendError.IsTxFeeExceedsCap() { - lggr.Criticalw(fmt.Sprintf("Sending transaction failed: %s", label.RPCTxFeeCapConfiguredIncorrectlyWarning), + logger.Criticalw(lggr, fmt.Sprintf("Sending transaction failed: %s", label.RPCTxFeeCapConfiguredIncorrectlyWarning), "etx", tx, "err", sendError, "id", "RPCTxFeeCapExceeded", ) - return clienttypes.ExceedsMaxFee, err + return commonclient.ExceedsMaxFee, err + } + return commonclient.Unknown, err +} + +// ClassifySendOnlyError handles SendOnly nodes error codes. In that case, we don't assume there is another transaction that will be correctly +// priced. +func ClassifySendOnlyError(err error) commonclient.SendTxReturnCode { + sendError := NewSendError(err) + if sendError == nil || sendError.IsNonceTooLowError() || sendError.IsTransactionAlreadyMined() || sendError.IsTransactionAlreadyInMempool() { + // Nonce too low or transaction known errors are expected since + // the primary SendTransaction may well have succeeded already + return commonclient.Successful } - return clienttypes.Unknown, err + return commonclient.Fatal } diff --git a/core/chains/evm/client/errors_test.go b/core/chains/evm/client/errors_test.go index a5a3cc15eb6..ad8079824ab 100644 --- a/core/chains/evm/client/errors_test.go +++ b/core/chains/evm/client/errors_test.go @@ -40,6 +40,7 @@ func Test_Eth_Errors(t *testing.T) { {"call failed: nonce too low: address 0x0499BEA33347cb62D79A9C0b1EDA01d8d329894c current nonce (5833) > tx nonce (5511)", true, "Avalanche"}, {"call failed: OldNonce", true, "Nethermind"}, {"call failed: OldNonce, Current nonce: 22, nonce of rejected tx: 17", true, "Nethermind"}, + {"nonce too low. allowed nonce range: 427 - 447, actual: 426", true, "zkSync"}, } for _, test := range tests { @@ -60,6 +61,7 @@ func Test_Eth_Errors(t *testing.T) { {"nonce too high: address 0x336394A3219e71D9d9bd18201d34E95C1Bb7122C, tx: 8089 state: 8090", true, "Arbitrum"}, {"nonce too high", true, "Geth"}, {"nonce too high", true, "Erigon"}, + {"nonce too high. allowed nonce range: 427 - 477, actual: 527", true, "zkSync"}, } for _, test := range tests { @@ -152,6 +154,7 @@ func Test_Eth_Errors(t *testing.T) { {"FeeTooLowToCompete", true, "Nethermind"}, {"transaction underpriced", true, "Klaytn"}, {"intrinsic gas too low", true, "Klaytn"}, + {"max fee per gas less than block base fee", true, "zkSync"}, } for _, test := range tests { @@ -194,6 +197,8 @@ func Test_Eth_Errors(t *testing.T) { {"call failed: InsufficientFunds, Account balance: 4740799397601480913, cumulative cost: 22019342038993800000", true, "Nethermind"}, {"insufficient funds", true, "Klaytn"}, {"insufficient funds for gas * price + value + gatewayFee", true, "celo"}, + {"insufficient balance for transfer", true, "zkSync"}, + {"insufficient funds for gas + value. balance: 42719769622667482000, fee: 48098250000000, value: 42719769622667482000", true, "celo"}, } for _, test := range tests { err = evmclient.NewSendErrorS(test.message) @@ -213,6 +218,7 @@ func Test_Eth_Errors(t *testing.T) { {"invalid gas fee cap", true, "Klaytn"}, {"max fee per gas higher than max priority fee per gas", true, "Klaytn"}, {"tx fee (1.10 of currency celo) exceeds the configured cap (1.00 celo)", true, "celo"}, + {"max priority fee per gas higher than max fee per gas", true, "zkSync"}, } for _, test := range tests { err = evmclient.NewSendErrorS(test.message) @@ -329,6 +335,16 @@ func Test_Eth_Errors_Fatal(t *testing.T) { {"`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"}, + + {"intrinsic gas too low", true, "zkSync"}, + {"failed to validate the transaction. reason: Validation revert: Account validation error: Not enough gas for transaction validation", true, "zkSync"}, + {"failed to validate the transaction. reason: Validation revert: Failed to pay for the transaction: Failed to pay the fee to the operator", true, "zkSync"}, + {"failed to validate the transaction. reason: Validation revert: Account validation error: Error function_selector = 0x, data = 0x", true, "zkSync"}, + {"invalid sender. can't start a transaction from a non-account", true, "zkSync"}, + {"Failed to serialize transaction: max fee per gas higher than 2^64-1", true, "zkSync"}, + {"Failed to serialize transaction: max fee per pubdata byte higher than 2^64-1", true, "zkSync"}, + {"Failed to serialize transaction: max priority fee per gas higher than 2^64-1", true, "zkSync"}, + {"Failed to serialize transaction: oversized data. max: 1000000; actual: 1000000", true, "zkSync"}, } for _, test := range tests { diff --git a/core/chains/evm/client/helpers_test.go b/core/chains/evm/client/helpers_test.go index 342a9143432..27b335534da 100644 --- a/core/chains/evm/client/helpers_test.go +++ b/core/chains/evm/client/helpers_test.go @@ -9,9 +9,11 @@ import ( "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + commonconfig "github.com/smartcontractkit/chainlink/v2/common/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/utils" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) type TestNodePoolConfig struct { @@ -40,9 +42,9 @@ func NewClientWithTestNode(t *testing.T, nodePoolCfg config.NodePool, noNewHeads return nil, errors.Errorf("ethereum url scheme must be websocket: %s", parsed.String()) } - lggr := logger.TestLogger(t) + lggr := logger.Test(t) n := NewNode(nodePoolCfg, noNewHeadsThreshold, lggr, *parsed, rpcHTTPURL, "eth-primary-0", id, chainID, 1) - n.(*node).setLatestReceived(0, utils.NewBigI(0)) + n.(*node).setLatestReceived(0, big.NewInt(0)) primaries := []Node{n} var sendonlys []SendOnlyNode @@ -64,6 +66,67 @@ func Wrap(err error, s string) error { return wrap(err, s) } +func NewChainClientWithTestNode( + t *testing.T, + nodeCfg commonclient.NodeConfig, + noNewHeadsThreshold time.Duration, + leaseDuration time.Duration, + 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 + } + + if parsed.Scheme != "ws" && parsed.Scheme != "wss" { + return nil, errors.Errorf("ethereum url scheme must be websocket: %s", parsed.String()) + } + + lggr := logger.Test(t) + rpc := NewRPCClient(lggr, *parsed, rpcHTTPURL, "eth-primary-rpc-0", id, chainID, commonclient.Primary) + + n := commonclient.NewNode[*big.Int, *evmtypes.Head, RPCCLient]( + nodeCfg, noNewHeadsThreshold, lggr, *parsed, rpcHTTPURL, "eth-primary-node-0", id, chainID, 1, rpc, "EVM") + primaries := []commonclient.Node[*big.Int, *evmtypes.Head, RPCCLient]{n} + + var sendonlys []commonclient.SendOnlyNode[*big.Int, RPCCLient] + for i, u := range sendonlyRPCURLs { + if u.Scheme != "http" && u.Scheme != "https" { + return nil, errors.Errorf("sendonly ethereum rpc url scheme must be http(s): %s", u.String()) + } + var empty url.URL + rpc := NewRPCClient(lggr, empty, &sendonlyRPCURLs[i], fmt.Sprintf("eth-sendonly-rpc-%d", i), id, chainID, commonclient.Secondary) + s := commonclient.NewSendOnlyNode[*big.Int, RPCCLient]( + lggr, u, fmt.Sprintf("eth-sendonly-%d", i), chainID, rpc) + sendonlys = append(sendonlys, s) + } + + var chainType commonconfig.ChainType + c := NewChainClient(lggr, nodeCfg.SelectionMode(), leaseDuration, noNewHeadsThreshold, primaries, sendonlys, chainID, chainType) + t.Cleanup(c.Close) + return c, nil +} + +func NewChainClientWithEmptyNode( + t *testing.T, + selectionMode string, + leaseDuration time.Duration, + noNewHeadsThreshold time.Duration, + chainID *big.Int, +) Client { + + lggr := logger.Test(t) + + var chainType commonconfig.ChainType + c := NewChainClient(lggr, selectionMode, leaseDuration, noNewHeadsThreshold, nil, nil, chainID, chainType) + t.Cleanup(c.Close) + return c +} + type TestableSendOnlyNode interface { SendOnlyNode SetEthClient(newBatchSender BatchSender, newSender TxSender) diff --git a/core/chains/evm/client/mocks/client.go b/core/chains/evm/client/mocks/client.go index fdcb15d6a63..22498370a2a 100644 --- a/core/chains/evm/client/mocks/client.go +++ b/core/chains/evm/client/mocks/client.go @@ -5,12 +5,12 @@ package mocks import ( big "math/big" - assets "github.com/smartcontractkit/chainlink/v2/core/assets" - - chainsclient "github.com/smartcontractkit/chainlink/v2/common/chains/client" + assets "github.com/smartcontractkit/chainlink-common/pkg/assets" common "github.com/ethereum/go-ethereum/common" + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + context "context" ethereum "github.com/ethereum/go-ethereum" @@ -566,18 +566,18 @@ func (_m *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er } // SendTransactionReturnCode provides a mock function with given fields: ctx, tx, fromAddress -func (_m *Client) SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, fromAddress common.Address) (chainsclient.SendTxReturnCode, error) { +func (_m *Client) SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, fromAddress common.Address) (commonclient.SendTxReturnCode, error) { ret := _m.Called(ctx, tx, fromAddress) - var r0 chainsclient.SendTxReturnCode + var r0 commonclient.SendTxReturnCode var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction, common.Address) (chainsclient.SendTxReturnCode, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction, common.Address) (commonclient.SendTxReturnCode, error)); ok { return rf(ctx, tx, fromAddress) } - if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction, common.Address) chainsclient.SendTxReturnCode); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction, common.Address) commonclient.SendTxReturnCode); ok { r0 = rf(ctx, tx, fromAddress) } else { - r0 = ret.Get(0).(chainsclient.SendTxReturnCode) + r0 = ret.Get(0).(commonclient.SendTxReturnCode) } if rf, ok := ret.Get(1).(func(context.Context, *types.Transaction, common.Address) error); ok { diff --git a/core/chains/evm/client/node.go b/core/chains/evm/client/node.go index 4f7132a6cc4..a2c8b807ba2 100644 --- a/core/chains/evm/client/node.go +++ b/core/chains/evm/client/node.go @@ -19,10 +19,11 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) var ( @@ -82,6 +83,8 @@ var ( //go:generate mockery --quiet --name Node --output ../mocks/ --case=underscore // Node represents a client that connects to an ethereum-compatible RPC node +// +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.Node] type Node interface { Start(ctx context.Context) error Close() error @@ -89,7 +92,7 @@ type Node interface { // State returns NodeState State() NodeState // StateAndLatest returns NodeState with the latest received block number & total difficulty. - StateAndLatest() (state NodeState, blockNum int64, totalDifficulty *utils.Big) + StateAndLatest() (state NodeState, blockNum int64, totalDifficulty *big.Int) // Name is a unique identifier for this node. Name() string ChainID() *big.Int @@ -132,7 +135,7 @@ type rawclient struct { // Node represents one ethereum node. // It must have a ws url and may have a http url type node struct { - utils.StartStopOnce + services.StateMachine lfcLog logger.Logger rpcLog logger.Logger name string @@ -149,7 +152,7 @@ type node struct { state NodeState // Each node is tracking the last received head number and total difficulty stateLatestBlockNumber int64 - stateLatestTotalDifficulty *utils.Big + stateLatestTotalDifficulty *big.Int // Need to track subscriptions because closing the RPC does not (always?) // close the underlying subscription @@ -173,10 +176,12 @@ type node struct { // 1. see how many live nodes there are in total, 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. // 2. compare against the highest head (by number or difficulty) to ensure we don't fall behind too far. - nLiveNodes func() (count int, blockNumber int64, totalDifficulty *utils.Big) + nLiveNodes func() (count int, blockNumber int64, totalDifficulty *big.Int) } // NewNode returns a new *node as Node +// +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.NewNode] func NewNode(nodeCfg config.NodePool, noNewHeadsThreshold time.Duration, lggr logger.Logger, wsuri url.URL, httpuri *url.URL, name string, id int32, chainID *big.Int, nodeOrder int32) Node { n := new(node) n.name = name @@ -191,7 +196,8 @@ func NewNode(nodeCfg config.NodePool, noNewHeadsThreshold time.Duration, lggr lo } n.chStopInFlight = make(chan struct{}) n.nodeCtx, n.cancelNodeCtx = context.WithCancel(context.Background()) - lggr = lggr.Named("Node").With( + lggr = logger.Named(lggr, "Node") + lggr = logger.With(lggr, "nodeTier", "primary", "nodeName", name, "node", n.String(), @@ -199,8 +205,8 @@ func NewNode(nodeCfg config.NodePool, noNewHeadsThreshold time.Duration, lggr lo "nodeOrder", n.order, "mode", n.getNodeMode(), ) - n.lfcLog = lggr.Named("Lifecycle") - n.rpcLog = lggr.Named("RPC") + n.lfcLog = logger.Named(lggr, "Lifecycle") + n.rpcLog = logger.Named(lggr, "RPC") n.stateLatestBlockNumber = -1 return n @@ -258,9 +264,9 @@ func (n *node) dial(callerCtx context.Context) error { defer cancel() promEVMPoolRPCNodeDials.WithLabelValues(n.chainID.String(), n.name).Inc() - lggr := n.lfcLog.With("wsuri", n.ws.uri.Redacted()) + lggr := logger.With(n.lfcLog, "wsuri", n.ws.uri.Redacted()) if n.http != nil { - lggr = lggr.With("httpuri", n.http.uri.Redacted()) + lggr = logger.With(lggr, "httpuri", n.http.uri.Redacted()) } lggr.Debugw("RPC dial: evmclient.Client#dial") @@ -447,7 +453,7 @@ func (n *node) CallContext(ctx context.Context, result interface{}, method strin return err } defer cancel() - lggr := n.newRqLggr().With( + lggr := logger.With(n.newRqLggr(), "method", method, "args", args, ) @@ -472,9 +478,9 @@ func (n *node) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { return err } defer cancel() - lggr := n.newRqLggr().With("nBatchElems", len(b), "batchElems", b) + lggr := logger.With(n.newRqLggr(), "nBatchElems", len(b), "batchElems", b) - lggr.Trace("RPC call: evmclient.Client#BatchCallContext") + logger.Trace(lggr, "RPC call: evmclient.Client#BatchCallContext") start := time.Now() if http != nil { err = n.wrapHTTP(http.rpc.BatchCallContext(ctx, b)) @@ -494,7 +500,7 @@ func (n *node) EthSubscribe(ctx context.Context, channel chan<- *evmtypes.Head, return nil, err } defer cancel() - lggr := n.newRqLggr().With("args", args) + lggr := logger.With(n.newRqLggr(), "args", args) lggr.Debug("RPC call: evmclient.Client#EthSubscribe") start := time.Now() @@ -517,7 +523,7 @@ func (n *node) TransactionReceipt(ctx context.Context, txHash common.Hash) (rece return nil, err } defer cancel() - lggr := n.newRqLggr().With("txHash", txHash) + lggr := logger.With(n.newRqLggr(), "txHash", txHash) lggr.Debug("RPC call: evmclient.Client#TransactionReceipt") @@ -544,7 +550,7 @@ func (n *node) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *t return nil, err } defer cancel() - lggr := n.newRqLggr().With("txHash", txHash) + lggr := logger.With(n.newRqLggr(), "txHash", txHash) lggr.Debug("RPC call: evmclient.Client#TransactionByHash") @@ -571,7 +577,7 @@ func (n *node) HeaderByNumber(ctx context.Context, number *big.Int) (header *typ return nil, err } defer cancel() - lggr := n.newRqLggr().With("number", number) + lggr := logger.With(n.newRqLggr(), "number", number) lggr.Debug("RPC call: evmclient.Client#HeaderByNumber") start := time.Now() @@ -595,7 +601,7 @@ func (n *node) HeaderByHash(ctx context.Context, hash common.Hash) (header *type return nil, err } defer cancel() - lggr := n.newRqLggr().With("hash", hash) + lggr := logger.With(n.newRqLggr(), "hash", hash) lggr.Debug("RPC call: evmclient.Client#HeaderByHash") start := time.Now() @@ -621,7 +627,7 @@ func (n *node) SendTransaction(ctx context.Context, tx *types.Transaction) error return err } defer cancel() - lggr := n.newRqLggr().With("tx", tx) + lggr := logger.With(n.newRqLggr(), "tx", tx) lggr.Debug("RPC call: evmclient.Client#SendTransaction") start := time.Now() @@ -644,7 +650,7 @@ func (n *node) PendingNonceAt(ctx context.Context, account common.Address) (nonc return 0, err } defer cancel() - lggr := n.newRqLggr().With("account", account) + lggr := logger.With(n.newRqLggr(), "account", account) lggr.Debug("RPC call: evmclient.Client#PendingNonceAt") start := time.Now() @@ -673,7 +679,7 @@ func (n *node) NonceAt(ctx context.Context, account common.Address, blockNumber return 0, err } defer cancel() - lggr := n.newRqLggr().With("account", account, "blockNumber", blockNumber) + lggr := logger.With(n.newRqLggr(), "account", account, "blockNumber", blockNumber) lggr.Debug("RPC call: evmclient.Client#NonceAt") start := time.Now() @@ -699,7 +705,7 @@ func (n *node) PendingCodeAt(ctx context.Context, account common.Address) (code return nil, err } defer cancel() - lggr := n.newRqLggr().With("account", account) + lggr := logger.With(n.newRqLggr(), "account", account) lggr.Debug("RPC call: evmclient.Client#PendingCodeAt") start := time.Now() @@ -725,7 +731,7 @@ func (n *node) CodeAt(ctx context.Context, account common.Address, blockNumber * return nil, err } defer cancel() - lggr := n.newRqLggr().With("account", account, "blockNumber", blockNumber) + lggr := logger.With(n.newRqLggr(), "account", account, "blockNumber", blockNumber) lggr.Debug("RPC call: evmclient.Client#CodeAt") start := time.Now() @@ -751,7 +757,7 @@ func (n *node) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint return 0, err } defer cancel() - lggr := n.newRqLggr().With("call", call) + lggr := logger.With(n.newRqLggr(), "call", call) lggr.Debug("RPC call: evmclient.Client#EstimateGas") start := time.Now() @@ -803,7 +809,7 @@ func (n *node) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumb return nil, err } defer cancel() - lggr := n.newRqLggr().With("callMsg", msg, "blockNumber", blockNumber) + lggr := logger.With(n.newRqLggr(), "callMsg", msg, "blockNumber", blockNumber) lggr.Debug("RPC call: evmclient.Client#CallContract") start := time.Now() @@ -830,7 +836,7 @@ func (n *node) BlockByNumber(ctx context.Context, number *big.Int) (b *types.Blo return nil, err } defer cancel() - lggr := n.newRqLggr().With("number", number) + lggr := logger.With(n.newRqLggr(), "number", number) lggr.Debug("RPC call: evmclient.Client#BlockByNumber") start := time.Now() @@ -856,7 +862,7 @@ func (n *node) BlockByHash(ctx context.Context, hash common.Hash) (b *types.Bloc return nil, err } defer cancel() - lggr := n.newRqLggr().With("hash", hash) + lggr := logger.With(n.newRqLggr(), "hash", hash) lggr.Debug("RPC call: evmclient.Client#BlockByHash") start := time.Now() @@ -908,7 +914,7 @@ func (n *node) BalanceAt(ctx context.Context, account common.Address, blockNumbe return nil, err } defer cancel() - lggr := n.newRqLggr().With("account", account.Hex(), "blockNumber", blockNumber) + lggr := logger.With(n.newRqLggr(), "account", account.Hex(), "blockNumber", blockNumber) lggr.Debug("RPC call: evmclient.Client#BalanceAt") start := time.Now() @@ -934,7 +940,7 @@ func (n *node) FilterLogs(ctx context.Context, q ethereum.FilterQuery) (l []type return nil, err } defer cancel() - lggr := n.newRqLggr().With("q", q) + lggr := logger.With(n.newRqLggr(), "q", q) lggr.Debug("RPC call: evmclient.Client#FilterLogs") start := time.Now() @@ -960,7 +966,7 @@ func (n *node) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, return nil, err } defer cancel() - lggr := n.newRqLggr().With("q", q) + lggr := logger.With(n.newRqLggr(), "q", q) lggr.Debug("RPC call: evmclient.Client#SubscribeFilterLogs") start := time.Now() @@ -1006,7 +1012,7 @@ func (n *node) ChainID() (chainID *big.Int) { return n.chainID } // newRqLggr generates a new logger with a unique request ID func (n *node) newRqLggr() logger.Logger { - return n.rpcLog.With( + return logger.With(n.rpcLog, "requestID", uuid.New(), ) } @@ -1019,11 +1025,11 @@ func (n *node) logResult( callName string, results ...interface{}, ) { - lggr = lggr.With("duration", callDuration, "rpcDomain", rpcDomain, "callName", callName) + lggr = logger.With(lggr, "duration", callDuration, "rpcDomain", rpcDomain, "callName", callName) promEVMPoolRPCNodeCalls.WithLabelValues(n.chainID.String(), n.name).Inc() if err == nil { promEVMPoolRPCNodeCallsSuccess.WithLabelValues(n.chainID.String(), n.name).Inc() - lggr.Tracew( + logger.Tracew(lggr, fmt.Sprintf("evmclient.Client#%s RPC call success", callName), results..., ) @@ -1056,7 +1062,7 @@ func (n *node) wrapHTTP(err error) error { if err != nil { n.rpcLog.Debugw("Call failed", "err", err) } else { - n.rpcLog.Trace("Call succeeded") + logger.Trace(n.rpcLog, "Call succeeded") } return err } @@ -1100,7 +1106,7 @@ func (n *node) makeQueryCtx(ctx context.Context) (context.Context, context.Cance // 1. Passed in ctx cancels // 2. Passed in channel is closed // 3. Default timeout is reached (queryTimeout) -func makeQueryCtx(ctx context.Context, ch utils.StopChan) (context.Context, context.CancelFunc) { +func makeQueryCtx(ctx context.Context, ch services.StopChan) (context.Context, context.CancelFunc) { var chCancel, timeoutCancel context.CancelFunc ctx, chCancel = ch.Ctx(ctx) ctx, timeoutCancel = context.WithTimeout(ctx, queryTimeout) diff --git a/core/chains/evm/client/node_fsm.go b/core/chains/evm/client/node_fsm.go index 2d74512eac9..c92af3b4120 100644 --- a/core/chains/evm/client/node_fsm.go +++ b/core/chains/evm/client/node_fsm.go @@ -2,11 +2,10 @@ package client import ( "fmt" + "math/big" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - - "github.com/smartcontractkit/chainlink/v2/core/utils" ) var ( @@ -38,6 +37,8 @@ var ( // NodeState represents the current state of the node // Node is a FSM (finite state machine) +// +// Deprecated: to be removed. It is now internal in common/client type NodeState int func (n NodeState) String() string { @@ -110,7 +111,7 @@ func (n *node) State() NodeState { return n.state } -func (n *node) StateAndLatest() (NodeState, int64, *utils.Big) { +func (n *node) StateAndLatest() (NodeState, int64, *big.Int) { n.stateMu.RLock() defer n.stateMu.RUnlock() return n.state, n.stateLatestBlockNumber, n.stateLatestTotalDifficulty @@ -182,7 +183,7 @@ func (n *node) transitionToInSync(fn func()) { // declareOutOfSync puts a node into OutOfSync state, disconnecting all current // clients and making it unavailable for use until back in-sync. -func (n *node) declareOutOfSync(isOutOfSync func(num int64, td *utils.Big) bool) { +func (n *node) declareOutOfSync(isOutOfSync func(num int64, td *big.Int) bool) { n.transitionToOutOfSync(func() { n.lfcLog.Errorw("RPC Node is out of sync", "nodeState", n.state) n.wg.Add(1) diff --git a/core/chains/evm/client/node_fsm_test.go b/core/chains/evm/client/node_fsm_test.go index ce63a62a8bd..321bbc7a309 100644 --- a/core/chains/evm/client/node_fsm_test.go +++ b/core/chains/evm/client/node_fsm_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type fnMock struct{ calls int } @@ -43,7 +43,7 @@ func TestUnit_Node_StateTransitions(t *testing.T) { t.Parallel() s := testutils.NewWSServer(t, testutils.FixtureChainID, nil) - iN := NewNode(TestNodePoolConfig{}, time.Second*0, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, nil, 1) + iN := NewNode(TestNodePoolConfig{}, time.Second*0, logger.Test(t), *s.WSURL(), nil, "test node", 42, nil, 1) n := iN.(*node) assert.Equal(t, NodeStateUndialed, n.State()) diff --git a/core/chains/evm/client/node_lifecycle.go b/core/chains/evm/client/node_lifecycle.go index 609d1522ea5..f838325a646 100644 --- a/core/chains/evm/client/node_lifecycle.go +++ b/core/chains/evm/client/node_lifecycle.go @@ -4,12 +4,16 @@ import ( "context" "fmt" "math" + "math/big" "time" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -49,7 +53,7 @@ func zombieNodeCheckInterval(noNewHeadsThreshold time.Duration) time.Duration { return utils.WithJitter(interval) } -func (n *node) setLatestReceived(blockNumber int64, totalDifficulty *utils.Big) { +func (n *node) setLatestReceived(blockNumber int64, totalDifficulty *big.Int) { n.stateMu.Lock() defer n.stateMu.Unlock() n.stateLatestBlockNumber = blockNumber @@ -87,8 +91,9 @@ func (n *node) aliveLoop() { pollFailureThreshold := n.nodePoolCfg.PollFailureThreshold() pollInterval := n.nodePoolCfg.PollInterval() - lggr := n.lfcLog.Named("Alive").With("noNewHeadsTimeoutThreshold", noNewHeadsTimeoutThreshold, "pollInterval", pollInterval, "pollFailureThreshold", pollFailureThreshold) - lggr.Tracew("Alive loop starting", "nodeState", n.State()) + lggr := logger.Named(n.lfcLog, "Alive") + lggr = logger.With(lggr, "noNewHeadsTimeoutThreshold", noNewHeadsTimeoutThreshold, "pollInterval", pollInterval, "pollFailureThreshold", pollFailureThreshold) + logger.Tracew(lggr, "Alive loop starting", "nodeState", n.State()) headsC := make(chan *evmtypes.Head) sub, err := n.EthSubscribe(n.nodeCtx, headsC, "newHeads") @@ -137,7 +142,7 @@ func (n *node) aliveLoop() { case <-pollCh: var version string promEVMPoolRPCNodePolls.WithLabelValues(n.chainID.String(), n.name).Inc() - lggr.Tracew("Polling for version", "nodeState", n.State(), "pollFailures", pollFailures) + logger.Tracew(lggr, "Polling for version", "nodeState", n.State(), "pollFailures", pollFailures) ctx, cancel := context.WithTimeout(n.nodeCtx, pollInterval) ctx, cancel2 := n.makeQueryCtx(ctx) err := n.CallContext(ctx, &version, "web3_clientVersion") @@ -159,7 +164,7 @@ func (n *node) aliveLoop() { lggr.Errorw(fmt.Sprintf("RPC endpoint failed to respond to %d consecutive polls", pollFailures), "pollFailures", pollFailures, "nodeState", n.State()) if n.nLiveNodes != nil { if l, _, _ := n.nLiveNodes(); l < 2 { - lggr.Criticalf("RPC endpoint failed to respond to polls; %s %s", msgCannotDisable, msgDegradedState) + logger.Criticalf(lggr, "RPC endpoint failed to respond to polls; %s %s", msgCannotDisable, msgDegradedState) continue } } @@ -171,7 +176,7 @@ func (n *node) aliveLoop() { // note: there must be another live node for us to be out of sync lggr.Errorw("RPC endpoint has fallen behind", "blockNumber", num, "totalDifficulty", td, "nodeState", n.State()) if liveNodes < 2 { - lggr.Criticalf("RPC endpoint has fallen behind; %s %s", msgCannotDisable, msgDegradedState) + logger.Criticalf(lggr, "RPC endpoint has fallen behind; %s %s", msgCannotDisable, msgDegradedState) continue } n.declareOutOfSync(n.isOutOfSync) @@ -184,13 +189,13 @@ func (n *node) aliveLoop() { return } promEVMPoolRPCNodeNumSeenBlocks.WithLabelValues(n.chainID.String(), n.name).Inc() - lggr.Tracew("Got head", "head", bh) + logger.Tracew(lggr, "Got head", "head", bh) if bh.Number > highestReceivedBlockNumber { promEVMPoolRPCNodeHighestSeenBlock.WithLabelValues(n.chainID.String(), n.name).Set(float64(bh.Number)) - lggr.Tracew("Got higher block number, resetting timer", "latestReceivedBlockNumber", highestReceivedBlockNumber, "blockNumber", bh.Number, "nodeState", n.State()) + logger.Tracew(lggr, "Got higher block number, resetting timer", "latestReceivedBlockNumber", highestReceivedBlockNumber, "blockNumber", bh.Number, "nodeState", n.State()) highestReceivedBlockNumber = bh.Number } else { - lggr.Tracew("Ignoring previously seen block number", "latestReceivedBlockNumber", highestReceivedBlockNumber, "blockNumber", bh.Number, "nodeState", n.State()) + logger.Tracew(lggr, "Ignoring previously seen block number", "latestReceivedBlockNumber", highestReceivedBlockNumber, "blockNumber", bh.Number, "nodeState", n.State()) } if outOfSyncT != nil { outOfSyncT.Reset(noNewHeadsTimeoutThreshold) @@ -206,20 +211,20 @@ func (n *node) aliveLoop() { lggr.Errorw(fmt.Sprintf("RPC endpoint detected out of sync; no new heads received for %s (last head received was %v)", noNewHeadsTimeoutThreshold, highestReceivedBlockNumber), "nodeState", n.State(), "latestReceivedBlockNumber", highestReceivedBlockNumber, "noNewHeadsTimeoutThreshold", noNewHeadsTimeoutThreshold) if n.nLiveNodes != nil { if l, _, _ := n.nLiveNodes(); l < 2 { - lggr.Criticalf("RPC endpoint detected out of sync; %s %s", msgCannotDisable, msgDegradedState) + logger.Criticalf(lggr, "RPC endpoint detected out of sync; %s %s", msgCannotDisable, msgDegradedState) // 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.noNewHeadsThreshold)) continue } } - n.declareOutOfSync(func(num int64, td *utils.Big) bool { return num < highestReceivedBlockNumber }) + n.declareOutOfSync(func(num int64, td *big.Int) bool { return num < highestReceivedBlockNumber }) return } } } -func (n *node) isOutOfSync(num int64, td *utils.Big) (outOfSync bool) { +func (n *node) isOutOfSync(num int64, td *big.Int) (outOfSync bool) { outOfSync, _ = n.syncStatus(num, td) return } @@ -227,7 +232,7 @@ func (n *node) isOutOfSync(num int64, td *utils.Big) (outOfSync bool) { // syncStatus returns outOfSync true if num or td is more than SyncThresold behind the best node. // Always returns outOfSync false for SyncThreshold 0. // liveNodes is only included when outOfSync is true. -func (n *node) syncStatus(num int64, td *utils.Big) (outOfSync bool, liveNodes int) { +func (n *node) syncStatus(num int64, td *big.Int) (outOfSync bool, liveNodes int) { if n.nLiveNodes == nil { return // skip for tests } @@ -242,8 +247,8 @@ func (n *node) syncStatus(num int64, td *utils.Big) (outOfSync bool, liveNodes i case NodeSelectionMode_HighestHead, NodeSelectionMode_RoundRobin, NodeSelectionMode_PriorityLevel: return num < highest-int64(threshold), ln case NodeSelectionMode_TotalDifficulty: - bigThreshold := utils.NewBigI(int64(threshold)) - return td.Cmp(greatest.Sub(bigThreshold)) < 0, ln + bigThreshold := big.NewInt(int64(threshold)) + return td.Cmp(bigmath.Sub(greatest, bigThreshold)) < 0, ln default: panic("unrecognized NodeSelectionMode: " + mode) } @@ -255,7 +260,7 @@ const ( ) // outOfSyncLoop takes an OutOfSync node and waits until isOutOfSync returns false to go back to live status -func (n *node) outOfSyncLoop(isOutOfSync func(num int64, td *utils.Big) bool) { +func (n *node) outOfSyncLoop(isOutOfSync func(num int64, td *big.Int) bool) { defer n.wg.Done() { @@ -272,7 +277,7 @@ func (n *node) outOfSyncLoop(isOutOfSync func(num int64, td *utils.Big) bool) { outOfSyncAt := time.Now() - lggr := n.lfcLog.Named("OutOfSync") + lggr := logger.Named(n.lfcLog, "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 @@ -289,7 +294,7 @@ func (n *node) outOfSyncLoop(isOutOfSync func(num int64, td *utils.Big) bool) { return } - lggr.Tracew("Successfully subscribed to heads feed on out-of-sync RPC node", "nodeState", n.State()) + logger.Tracew(lggr, "Successfully subscribed to heads feed on out-of-sync RPC node", "nodeState", n.State()) ch := make(chan *evmtypes.Head) subCtx, cancel := n.makeQueryCtx(n.nodeCtx) @@ -324,7 +329,7 @@ func (n *node) outOfSyncLoop(isOutOfSync func(num int64, td *utils.Big) bool) { case <-time.After(zombieNodeCheckInterval(n.noNewHeadsThreshold)): if n.nLiveNodes != nil { if l, _, _ := n.nLiveNodes(); l < 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") + logger.Critical(lggr, "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 } @@ -354,7 +359,7 @@ func (n *node) unreachableLoop() { unreachableAt := time.Now() - lggr := n.lfcLog.Named("Unreachable") + lggr := logger.Named(n.lfcLog, "Unreachable") lggr.Debugw("Trying to revive unreachable RPC node", "nodeState", n.State()) dialRetryBackoff := utils.NewRedialBackoff() @@ -364,7 +369,7 @@ func (n *node) unreachableLoop() { case <-n.nodeCtx.Done(): return case <-time.After(dialRetryBackoff.Duration()): - lggr.Tracew("Trying to re-dial RPC node", "nodeState", n.State()) + logger.Tracew(lggr, "Trying to re-dial RPC node", "nodeState", n.State()) err := n.dial(n.nodeCtx) if err != nil { @@ -410,7 +415,7 @@ func (n *node) invalidChainIDLoop() { invalidAt := time.Now() - lggr := n.lfcLog.Named("InvalidChainID") + lggr := logger.Named(n.lfcLog, "InvalidChainID") lggr.Debugw(fmt.Sprintf("Periodically re-checking RPC node %s with invalid chain ID", n.String()), "nodeState", n.State()) chainIDRecheckBackoff := utils.NewRedialBackoff() diff --git a/core/chains/evm/client/node_lifecycle_test.go b/core/chains/evm/client/node_lifecycle_test.go index 9fdd9bb64e1..f097c2bc078 100644 --- a/core/chains/evm/client/node_lifecycle_test.go +++ b/core/chains/evm/client/node_lifecycle_test.go @@ -12,11 +12,10 @@ import ( "github.com/tidwall/gjson" "go.uber.org/zap" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) func standardHandler(method string, _ gjson.Result) (resp testutils.JSONRPCResponse) { @@ -34,7 +33,7 @@ func newTestNode(t *testing.T, cfg config.NodePool, noNewHeadsThresholds time.Du func newTestNodeWithCallback(t *testing.T, cfg config.NodePool, noNewHeadsThreshold time.Duration, callback testutils.JSONRPCHandler) *node { s := testutils.NewWSServer(t, testutils.FixtureChainID, callback) - iN := NewNode(cfg, noNewHeadsThreshold, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) + iN := NewNode(cfg, noNewHeadsThreshold, logger.Test(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) n := iN.(*node) return n } @@ -161,7 +160,7 @@ func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { } return }) - n.nLiveNodes = func() (int, int64, *utils.Big) { return 1, 0, nil } + n.nLiveNodes = func() (int, int64, *big.Int) { return 1, 0, nil } dial(t, n) defer func() { assert.NoError(t, n.Close()) }() @@ -222,7 +221,7 @@ func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { return }) - iN := NewNode(cfg, testutils.WaitTimeout(t), logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) + iN := NewNode(cfg, testutils.WaitTimeout(t), logger.Test(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) n := iN.(*node) dial(t, n) @@ -268,7 +267,7 @@ func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { return }) - iN := NewNode(cfg, 1*time.Second, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) + iN := NewNode(cfg, 1*time.Second, logger.Test(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) n := iN.(*node) dial(t, n) @@ -288,7 +287,7 @@ func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { }) 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) + lggr, observedLogs := logger.TestObserved(t, zap.ErrorLevel) pollDisabledCfg := TestNodePoolConfig{} s := testutils.NewWSServer(t, testutils.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { @@ -308,7 +307,7 @@ func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { iN := NewNode(pollDisabledCfg, testutils.TestInterval, lggr, *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) n := iN.(*node) - n.nLiveNodes = func() (int, int64, *utils.Big) { return 1, 0, nil } + n.nLiveNodes = func() (int, int64, *big.Int) { return 1, 0, nil } dial(t, n) defer func() { assert.NoError(t, n.Close()) }() @@ -351,9 +350,9 @@ func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { return }) - iN := NewNode(cfg, 0*time.Second, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) + iN := NewNode(cfg, 0*time.Second, logger.Test(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) n := iN.(*node) - n.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *utils.Big) { + n.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { return 2, highestHead.Load(), nil } @@ -413,9 +412,9 @@ func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { return }) - iN := NewNode(cfg, 0*time.Second, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) + iN := NewNode(cfg, 0*time.Second, logger.Test(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) n := iN.(*node) - n.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *utils.Big) { + n.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { return 2, highestHead.Load(), nil } @@ -439,7 +438,7 @@ func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { }) t.Run("when behind more than SyncThreshold but we are the last live node, forcibly stays alive", func(t *testing.T) { - lggr, observedLogs := logger.TestLoggerObserved(t, zap.ErrorLevel) + lggr, observedLogs := logger.TestObserved(t, zap.ErrorLevel) cfg := TestNodePoolConfig{NodeSyncThreshold: 5, NodePollFailureThreshold: 2, NodePollInterval: 100 * time.Millisecond, NodeSelectionMode: NodeSelectionMode_HighestHead} chSubbed := make(chan struct{}, 1) var highestHead atomic.Int64 @@ -474,7 +473,7 @@ func TestUnit_NodeLifecycle_aliveLoop(t *testing.T) { iN := NewNode(cfg, 0*time.Second, lggr, *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) n := iN.(*node) - n.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *utils.Big) { + n.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { return 1, highestHead.Load(), nil } @@ -521,7 +520,7 @@ func TestUnit_NodeLifecycle_outOfSyncLoop(t *testing.T) { n.wg.Add(1) go func() { defer close(ch) - n.aliveLoop() + n.outOfSyncLoop(func(num int64, td *big.Int) bool { return false }) }() assert.NoError(t, n.Close()) testutils.WaitWithTimeout(t, ch, "expected outOfSyncLoop to exit") @@ -536,7 +535,7 @@ func TestUnit_NodeLifecycle_outOfSyncLoop(t *testing.T) { n.wg.Add(1) - n.outOfSyncLoop(func(num int64, td *utils.Big) bool { return num == 0 }) + n.outOfSyncLoop(func(num int64, td *big.Int) bool { return num == 0 }) assert.Equal(t, NodeStateUnreachable, n.State()) }) @@ -557,7 +556,7 @@ func TestUnit_NodeLifecycle_outOfSyncLoop(t *testing.T) { return }) - iN := NewNode(cfg, time.Duration(time.Second), logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) + iN := NewNode(cfg, time.Duration(time.Second), logger.Test(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) n := iN.(*node) dial(t, n) @@ -565,7 +564,7 @@ func TestUnit_NodeLifecycle_outOfSyncLoop(t *testing.T) { defer func() { assert.NoError(t, n.Close()) }() n.wg.Add(1) - go n.outOfSyncLoop(func(num int64, td *utils.Big) bool { return num == 0 }) + go n.outOfSyncLoop(func(num int64, td *big.Int) bool { return num == 0 }) testutils.WaitWithTimeout(t, chSubbed, "timed out waiting for initial subscription") @@ -584,7 +583,7 @@ func TestUnit_NodeLifecycle_outOfSyncLoop(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) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) cfg := TestNodePoolConfig{} chSubbed := make(chan struct{}, 1) s := testutils.NewWSServer(t, testutils.FixtureChainID, @@ -612,7 +611,7 @@ func TestUnit_NodeLifecycle_outOfSyncLoop(t *testing.T) { defer func() { assert.NoError(t, n.Close()) }() n.wg.Add(1) - go n.outOfSyncLoop(func(num int64, td *utils.Big) bool { return num < 43 }) + go n.outOfSyncLoop(func(num int64, td *big.Int) bool { return num < 43 }) testutils.WaitWithTimeout(t, chSubbed, "timed out waiting for initial subscription") @@ -638,7 +637,7 @@ func TestUnit_NodeLifecycle_outOfSyncLoop(t *testing.T) { }) t.Run("transitions to alive if back in-sync", func(t *testing.T) { - lggr, observedLogs := logger.TestLoggerObserved(t, zap.DebugLevel) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) cfg := TestNodePoolConfig{NodeSyncThreshold: 5, NodeSelectionMode: NodeSelectionMode_HighestHead} chSubbed := make(chan struct{}, 1) const stall = 42 @@ -661,7 +660,7 @@ func TestUnit_NodeLifecycle_outOfSyncLoop(t *testing.T) { iN := NewNode(cfg, time.Second*0, lggr, *s.WSURL(), nil, "test node", 0, testutils.FixtureChainID, 1) n := iN.(*node) - n.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *utils.Big) { + n.nLiveNodes = func() (count int, blockNumber int64, totalDifficulty *big.Int) { return 2, stall + int64(cfg.SyncThreshold()), nil } @@ -715,16 +714,16 @@ func TestUnit_NodeLifecycle_outOfSyncLoop(t *testing.T) { return }) - iN := NewNode(cfg, testutils.TestInterval, logger.TestLogger(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) + iN := NewNode(cfg, testutils.TestInterval, logger.Test(t), *s.WSURL(), nil, "test node", 42, testutils.FixtureChainID, 1) n := iN.(*node) - n.nLiveNodes = func() (int, int64, *utils.Big) { return 0, 0, nil } + n.nLiveNodes = func() (int, int64, *big.Int) { return 0, 0, nil } dial(t, n) n.setState(NodeStateOutOfSync) defer func() { assert.NoError(t, n.Close()) }() n.wg.Add(1) - go n.outOfSyncLoop(func(num int64, td *utils.Big) bool { return num == 0 }) + go n.outOfSyncLoop(func(num int64, td *big.Int) bool { return num == 0 }) testutils.WaitWithTimeout(t, chSubbed, "timed out waiting for initial subscription") @@ -771,7 +770,7 @@ func TestUnit_NodeLifecycle_unreachableLoop(t *testing.T) { t.Run("on successful redial but failed verify, transitions to invalid chain ID", func(t *testing.T) { cfg := TestNodePoolConfig{} s := testutils.NewWSServer(t, testutils.FixtureChainID, standardHandler) - lggr, observedLogs := logger.TestLoggerObserved(t, zap.ErrorLevel) + lggr, observedLogs := logger.TestObserved(t, zap.ErrorLevel) iN := NewNode(cfg, time.Second*0, lggr, *s.WSURL(), nil, "test node", 0, big.NewInt(42), 1) n := iN.(*node) defer func() { assert.NoError(t, n.Close()) }() @@ -790,7 +789,7 @@ func TestUnit_NodeLifecycle_unreachableLoop(t *testing.T) { t.Run("on failed redial, keeps trying to redial", func(t *testing.T) { cfg := TestNodePoolConfig{} - lggr, observedLogs := logger.TestLoggerObserved(t, zap.DebugLevel) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) iN := NewNode(cfg, time.Second*0, lggr, *testutils.MustParseURL(t, "ws://test.invalid"), nil, "test node", 0, big.NewInt(42), 1) n := iN.(*node) defer func() { assert.NoError(t, n.Close()) }() @@ -842,7 +841,7 @@ func TestUnit_NodeLifecycle_invalidChainIDLoop(t *testing.T) { t.Run("on failed verify, keeps checking", func(t *testing.T) { cfg := TestNodePoolConfig{} s := testutils.NewWSServer(t, testutils.FixtureChainID, standardHandler) - lggr, observedLogs := logger.TestLoggerObserved(t, zap.ErrorLevel) + lggr, observedLogs := logger.TestObserved(t, zap.ErrorLevel) iN := NewNode(cfg, time.Second*0, lggr, *s.WSURL(), nil, "test node", 0, big.NewInt(42), 1) n := iN.(*node) defer func() { assert.NoError(t, n.Close()) }() diff --git a/core/chains/evm/client/node_selector_highest_head.go b/core/chains/evm/client/node_selector_highest_head.go index 9e88674fac5..2ed41486cff 100644 --- a/core/chains/evm/client/node_selector_highest_head.go +++ b/core/chains/evm/client/node_selector_highest_head.go @@ -6,6 +6,7 @@ import ( type highestHeadNodeSelector []Node +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.NewHighestHeadNodeSelector] func NewHighestHeadNodeSelector(nodes []Node) NodeSelector { return highestHeadNodeSelector(nodes) } diff --git a/core/chains/evm/client/node_selector_priority_level.go b/core/chains/evm/client/node_selector_priority_level.go index 7a5516dcd42..fba6d403327 100644 --- a/core/chains/evm/client/node_selector_priority_level.go +++ b/core/chains/evm/client/node_selector_priority_level.go @@ -16,6 +16,7 @@ type nodeWithPriority struct { priority int32 } +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.NewPriorityLevelNodeSelector] func NewPriorityLevelNodeSelector(nodes []Node) NodeSelector { return &priorityLevelNodeSelector{ nodes: nodes, diff --git a/core/chains/evm/client/node_selector_round_robin.go b/core/chains/evm/client/node_selector_round_robin.go index 9b2fa7508a7..3bd19f0ede4 100644 --- a/core/chains/evm/client/node_selector_round_robin.go +++ b/core/chains/evm/client/node_selector_round_robin.go @@ -7,6 +7,7 @@ type roundRobinSelector struct { roundRobinCount atomic.Uint32 } +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.NewRoundRobinSelector] func NewRoundRobinSelector(nodes []Node) NodeSelector { return &roundRobinSelector{ nodes: nodes, diff --git a/core/chains/evm/client/node_selector_total_difficulty.go b/core/chains/evm/client/node_selector_total_difficulty.go index a368422e49f..27d888947d9 100644 --- a/core/chains/evm/client/node_selector_total_difficulty.go +++ b/core/chains/evm/client/node_selector_total_difficulty.go @@ -1,18 +1,17 @@ package client -import ( - "github.com/smartcontractkit/chainlink/v2/core/utils" -) +import "math/big" type totalDifficultyNodeSelector []Node +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.NewTotalDifficultyNodeSelector] func NewTotalDifficultyNodeSelector(nodes []Node) NodeSelector { return totalDifficultyNodeSelector(nodes) } func (s totalDifficultyNodeSelector) Select() Node { // NodeNoNewHeadsThreshold may not be enabled, in this case all nodes have td == nil - var highestTD *utils.Big + var highestTD *big.Int var nodes []Node var aliveNodes []Node diff --git a/core/chains/evm/client/node_selector_total_difficulty_test.go b/core/chains/evm/client/node_selector_total_difficulty_test.go index d42a7461dee..486a421477e 100644 --- a/core/chains/evm/client/node_selector_total_difficulty_test.go +++ b/core/chains/evm/client/node_selector_total_difficulty_test.go @@ -1,11 +1,11 @@ package client_test import ( + "math/big" "testing" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/mocks" - "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/stretchr/testify/assert" ) @@ -27,10 +27,10 @@ func TestTotalDifficultyNodeSelector(t *testing.T) { node.On("StateAndLatest").Return(evmclient.NodeStateOutOfSync, int64(-1), nil) } else if i == 1 { // second node is alive - node.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), utils.NewBigI(7)) + node.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), big.NewInt(7)) } else { // third node is alive and best - node.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(2), utils.NewBigI(8)) + node.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(2), big.NewInt(8)) } node.On("Order").Maybe().Return(int32(1)) nodes = append(nodes, node) @@ -42,7 +42,7 @@ func TestTotalDifficultyNodeSelector(t *testing.T) { t.Run("stick to the same node", func(t *testing.T) { node := evmmocks.NewNode(t) // fourth node is alive (same as 3rd) - node.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(2), utils.NewBigI(8)) + node.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(2), big.NewInt(8)) node.On("Order").Maybe().Return(int32(1)) nodes = append(nodes, node) @@ -53,7 +53,7 @@ func TestTotalDifficultyNodeSelector(t *testing.T) { t.Run("another best node", func(t *testing.T) { node := evmmocks.NewNode(t) // fifth node is alive (better than 3rd and 4th) - node.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(3), utils.NewBigI(11)) + node.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(3), big.NewInt(11)) node.On("Order").Maybe().Return(int32(1)) nodes = append(nodes, node) @@ -87,7 +87,7 @@ func TestTotalDifficultyNodeSelector_None(t *testing.T) { node.On("StateAndLatest").Return(evmclient.NodeStateOutOfSync, int64(-1), nil) } else { // others are unreachable - node.On("StateAndLatest").Return(evmclient.NodeStateUnreachable, int64(1), utils.NewBigI(7)) + node.On("StateAndLatest").Return(evmclient.NodeStateUnreachable, int64(1), big.NewInt(7)) } nodes = append(nodes, node) } @@ -104,7 +104,7 @@ func TestTotalDifficultyNodeSelectorWithOrder(t *testing.T) { t.Run("same td and order", func(t *testing.T) { for i := 0; i < 3; i++ { node := evmmocks.NewNode(t) - node.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), utils.NewBigI(10)) + node.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), big.NewInt(10)) node.On("Order").Return(int32(2)) nodes = append(nodes, node) } @@ -115,15 +115,15 @@ func TestTotalDifficultyNodeSelectorWithOrder(t *testing.T) { t.Run("same td but different order", func(t *testing.T) { node1 := evmmocks.NewNode(t) - node1.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(3), utils.NewBigI(10)) + node1.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(3), big.NewInt(10)) node1.On("Order").Return(int32(3)) node2 := evmmocks.NewNode(t) - node2.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(3), utils.NewBigI(10)) + node2.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(3), big.NewInt(10)) node2.On("Order").Return(int32(1)) node3 := evmmocks.NewNode(t) - node3.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(3), utils.NewBigI(10)) + node3.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(3), big.NewInt(10)) node3.On("Order").Return(int32(2)) nodes := []evmclient.Node{node1, node2, node3} @@ -134,15 +134,15 @@ func TestTotalDifficultyNodeSelectorWithOrder(t *testing.T) { t.Run("different td but same order", func(t *testing.T) { node1 := evmmocks.NewNode(t) - node1.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), utils.NewBigI(10)) + node1.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), big.NewInt(10)) node1.On("Order").Maybe().Return(int32(3)) node2 := evmmocks.NewNode(t) - node2.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), utils.NewBigI(11)) + node2.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), big.NewInt(11)) node2.On("Order").Maybe().Return(int32(3)) node3 := evmmocks.NewNode(t) - node3.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), utils.NewBigI(12)) + node3.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), big.NewInt(12)) node3.On("Order").Return(int32(3)) nodes := []evmclient.Node{node1, node2, node3} @@ -153,19 +153,19 @@ func TestTotalDifficultyNodeSelectorWithOrder(t *testing.T) { t.Run("different head and different order", func(t *testing.T) { node1 := evmmocks.NewNode(t) - node1.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), utils.NewBigI(100)) + node1.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), big.NewInt(100)) node1.On("Order").Maybe().Return(int32(4)) node2 := evmmocks.NewNode(t) - node2.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), utils.NewBigI(110)) + node2.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), big.NewInt(110)) node2.On("Order").Maybe().Return(int32(5)) node3 := evmmocks.NewNode(t) - node3.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), utils.NewBigI(110)) + node3.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), big.NewInt(110)) node3.On("Order").Maybe().Return(int32(1)) node4 := evmmocks.NewNode(t) - node4.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), utils.NewBigI(105)) + node4.On("StateAndLatest").Return(evmclient.NodeStateAlive, int64(1), big.NewInt(105)) node4.On("Order").Maybe().Return(int32(2)) nodes := []evmclient.Node{node1, node2, node3, node4} diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go index 8e271aea1e7..e3bb1defd0d 100644 --- a/core/chains/evm/client/null_client.go +++ b/core/chains/evm/client/null_client.go @@ -9,10 +9,10 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) // NullClient satisfies the Client but has no side effects @@ -22,7 +22,7 @@ type NullClient struct { } func NewNullClient(cid *big.Int, lggr logger.Logger) *NullClient { - return &NullClient{cid: cid, lggr: lggr.Named("NullClient")} + return &NullClient{cid: cid, lggr: logger.Named(lggr, "NullClient")} } // NullClientChainID the ChainID that nullclient will return @@ -72,7 +72,7 @@ type nullSubscription struct { } func newNullSubscription(lggr logger.Logger) *nullSubscription { - return &nullSubscription{lggr: lggr.Named("NullSubscription")} + return &nullSubscription{lggr: logger.Named(lggr, "NullSubscription")} } func (ns *nullSubscription) Unsubscribe() { @@ -121,9 +121,9 @@ func (nc *NullClient) HeaderByHash(ctx context.Context, h common.Hash) (*types.H return nil, nil } -func (nc *NullClient) SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, sender common.Address) (clienttypes.SendTxReturnCode, error) { +func (nc *NullClient) SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, sender common.Address) (commonclient.SendTxReturnCode, error) { nc.lggr.Debug("SendTransactionReturnCode") - return clienttypes.Successful, nil + return commonclient.Successful, nil } func (nc *NullClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { diff --git a/core/chains/evm/client/null_client_test.go b/core/chains/evm/client/null_client_test.go index 6b0f6e3d1b9..8f4ebd91c97 100644 --- a/core/chains/evm/client/null_client_test.go +++ b/core/chains/evm/client/null_client_test.go @@ -11,17 +11,17 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestNullClient(t *testing.T) { t.Parallel() t.Run("chain id", func(t *testing.T) { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) cid := big.NewInt(123) nc := client.NewNullClient(cid, lggr) require.Equal(t, cid, nc.ConfiguredChainID()) @@ -31,7 +31,7 @@ func TestNullClient(t *testing.T) { }) t.Run("CL client methods", func(t *testing.T) { - lggr, logs := logger.TestLoggerObserved(t, zapcore.DebugLevel) + lggr, logs := logger.TestObserved(t, zapcore.DebugLevel) nc := client.NewNullClient(nil, lggr) ctx := testutils.Context(t) @@ -77,7 +77,7 @@ func TestNullClient(t *testing.T) { }) t.Run("Geth client methods", func(t *testing.T) { - lggr, logs := logger.TestLoggerObserved(t, zapcore.DebugLevel) + lggr, logs := logger.TestObserved(t, zapcore.DebugLevel) nc := client.NewNullClient(nil, lggr) ctx := testutils.Context(t) diff --git a/core/chains/evm/client/pool.go b/core/chains/evm/client/pool.go index 7e4667623de..6c36cc3e987 100644 --- a/core/chains/evm/client/pool.go +++ b/core/chains/evm/client/pool.go @@ -15,11 +15,11 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/common/config" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -39,6 +39,8 @@ const ( ) // NodeSelector represents a strategy to select the next node from the pool. +// +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.NodeSelector] type NodeSelector interface { // Select returns a Node, or nil if none can be selected. // Implementation must be thread-safe. @@ -48,6 +50,8 @@ type NodeSelector interface { } // PoolConfig represents settings for the Pool +// +// Deprecated: to be removed type PoolConfig interface { NodeSelectionMode() string NodeNoNewHeadsThreshold() time.Duration @@ -56,8 +60,10 @@ type PoolConfig interface { // Pool represents an abstraction over one or more primary nodes // It is responsible for liveness checking and balancing queries across live nodes +// +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.MultiNode] type Pool struct { - utils.StartStopOnce + services.StateMachine nodes []Node sendonlys []SendOnlyNode chainID *big.Int @@ -72,11 +78,14 @@ type Pool struct { activeMu sync.RWMutex activeNode Node - chStop utils.StopChan + chStop services.StopChan wg sync.WaitGroup } -func NewPool(logger logger.Logger, selectionMode string, leaseDuration time.Duration, noNewHeadsTreshold time.Duration, nodes []Node, sendonlys []SendOnlyNode, chainID *big.Int, chainType config.ChainType) *Pool { +// NewPool - creates new instance of [Pool] +// +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.NewMultiNode] +func NewPool(lggr logger.Logger, selectionMode string, leaseDuration time.Duration, noNewHeadsTreshold time.Duration, nodes []Node, sendonlys []SendOnlyNode, chainID *big.Int, chainType config.ChainType) *Pool { if chainID == nil { panic("chainID is required") } @@ -96,7 +105,8 @@ func NewPool(logger logger.Logger, selectionMode string, leaseDuration time.Dura } }() - lggr := logger.Named("Pool").With("evmChainID", chainID.String()) + lggr = logger.Named(lggr, "Pool") + lggr = logger.With(lggr, "evmChainID", chainID.String()) p := &Pool{ nodes: nodes, @@ -168,8 +178,8 @@ func (p *Pool) Dial(ctx context.Context) error { // nLiveNodes returns the number of currently alive nodes, as well as the highest block number and greatest total difficulty. // totalDifficulty will be 0 if all nodes return nil. -func (p *Pool) nLiveNodes() (nLiveNodes int, blockNumber int64, totalDifficulty *utils.Big) { - totalDifficulty = utils.NewBigI(0) +func (p *Pool) nLiveNodes() (nLiveNodes int, blockNumber int64, totalDifficulty *big.Int) { + totalDifficulty = big.NewInt(0) for _, n := range p.nodes { if s, num, td := n.StateAndLatest(); s == NodeStateAlive { nLiveNodes++ @@ -262,10 +272,10 @@ func (p *Pool) report() { } live := total - dead - p.logger.Tracew(fmt.Sprintf("Pool state: %d/%d nodes are alive", live, total), "nodeStates", nodeStates) + logger.Tracew(p.logger, fmt.Sprintf("Pool state: %d/%d nodes are alive", live, total), "nodeStates", nodeStates) if total == dead { rerr := fmt.Errorf("no EVM primary nodes available: 0/%d nodes are alive", total) - p.logger.Criticalw(rerr.Error(), "nodeStates", nodeStates) + logger.Criticalw(p.logger, rerr.Error(), "nodeStates", nodeStates) p.SvcErrBuffer.Append(rerr) } 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) @@ -310,7 +320,7 @@ func (p *Pool) selectNode() (node Node) { p.activeNode = p.nodeSelector.Select() if p.activeNode == nil { - p.logger.Criticalw("No live RPC nodes available", "NodeSelectionMode", p.nodeSelector.Name()) + logger.Criticalw(p.logger, "No live RPC nodes available", "NodeSelectionMode", p.nodeSelector.Name()) errmsg := fmt.Errorf("no live nodes available for chain %s", p.chainID.String()) p.SvcErrBuffer.Append(errmsg) return &erroringNode{errMsg: errmsg.Error()} @@ -357,7 +367,7 @@ func (p *Pool) BatchCallContextAll(ctx context.Context, b []rpc.BatchElem) error if err != nil { p.logger.Debugw("Secondary node BatchCallContext failed", "err", err) } else { - p.logger.Trace("Secondary node BatchCallContext success") + logger.Trace(p.logger, "Secondary node BatchCallContext success") } }(n) } diff --git a/core/chains/evm/client/pool_test.go b/core/chains/evm/client/pool_test.go index 15a6484756d..462aeed43ee 100644 --- a/core/chains/evm/client/pool_test.go +++ b/core/chains/evm/client/pool_test.go @@ -18,11 +18,11 @@ import ( "github.com/tidwall/gjson" "go.uber.org/zap" + "github.com/smartcontractkit/chainlink-common/pkg/logger" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type poolConfig struct { @@ -164,7 +164,7 @@ func TestPool_Dial(t *testing.T) { for i, n := range test.sendNodes { sendNodes[i] = n.newSendOnlyNode(t, test.sendNodeChainID) } - p := evmclient.NewPool(logger.TestLogger(t), defaultConfig.NodeSelectionMode(), defaultConfig.LeaseDuration(), time.Second*0, nodes, sendNodes, test.poolChainID, "") + p := evmclient.NewPool(logger.Test(t), defaultConfig.NodeSelectionMode(), defaultConfig.LeaseDuration(), time.Second*0, nodes, sendNodes, test.poolChainID, "") err := p.Dial(ctx) if err == nil { t.Cleanup(func() { assert.NoError(t, p.Close()) }) @@ -188,7 +188,7 @@ type chainIDResp struct { func (r *chainIDResp) newSendOnlyNode(t *testing.T, nodeChainID int64) evmclient.SendOnlyNode { httpURL := r.newHTTPServer(t) - return evmclient.NewSendOnlyNode(logger.TestLogger(t), *httpURL, t.Name(), big.NewInt(nodeChainID)) + return evmclient.NewSendOnlyNode(logger.Test(t), *httpURL, t.Name(), big.NewInt(nodeChainID)) } func (r *chainIDResp) newHTTPServer(t *testing.T) *url.URL { @@ -211,7 +211,7 @@ type chainIDResps struct { } 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) (resp testutils.JSONRPCResponse) { + ws := testutils.NewWSServer(t, big.NewInt(r.ws.chainID), func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { switch method { case "eth_subscribe": resp.Result = `"0x00"` @@ -223,7 +223,7 @@ func (r *chainIDResps) newNode(t *testing.T, nodeChainID int64) evmclient.Node { } t.Errorf("Unexpected method call: %s(%s)", method, params) return - }) + }).WSURL().String() wsURL, err := url.Parse(ws) require.NoError(t, err) @@ -234,7 +234,7 @@ func (r *chainIDResps) newNode(t *testing.T, nodeChainID int64) evmclient.Node { } defer func() { r.id++ }() - return evmclient.NewNode(evmclient.TestNodePoolConfig{}, time.Second*0, logger.TestLogger(t), *wsURL, httpURL, t.Name(), r.id, big.NewInt(nodeChainID), 0) + return evmclient.NewNode(evmclient.TestNodePoolConfig{}, time.Second*0, logger.Test(t), *wsURL, httpURL, t.Name(), r.id, big.NewInt(nodeChainID), 0) } type chainIDService struct { @@ -256,7 +256,7 @@ func TestUnit_Pool_RunLoop(t *testing.T) { n3 := evmmocks.NewNode(t) nodes := []evmclient.Node{n1, n2, n3} - lggr, observedLogs := logger.TestLoggerObserved(t, zap.ErrorLevel) + lggr, observedLogs := logger.TestObserved(t, zap.ErrorLevel) p := evmclient.NewPool(lggr, defaultConfig.NodeSelectionMode(), defaultConfig.LeaseDuration(), time.Second*0, nodes, []evmclient.SendOnlyNode{}, &cltest.FixtureChainID, "") n1.On("String").Maybe().Return("n1") @@ -331,7 +331,7 @@ func TestUnit_Pool_BatchCallContextAll(t *testing.T) { sendonlys = append(sendonlys, s) } - p := evmclient.NewPool(logger.TestLogger(t), defaultConfig.NodeSelectionMode(), defaultConfig.LeaseDuration(), time.Second*0, nodes, sendonlys, &cltest.FixtureChainID, "") + p := evmclient.NewPool(logger.Test(t), defaultConfig.NodeSelectionMode(), defaultConfig.LeaseDuration(), time.Second*0, nodes, sendonlys, &cltest.FixtureChainID, "") assert.True(t, p.ChainType().IsValid()) assert.False(t, p.ChainType().IsL2()) @@ -378,7 +378,7 @@ func TestUnit_Pool_LeaseDuration(t *testing.T) { n2.On("Order").Return(int32(2)) n2.On("ChainID").Return(testutils.FixtureChainID).Once() - lggr, observedLogs := logger.TestLoggerObserved(t, zap.InfoLevel) + lggr, observedLogs := logger.TestObserved(t, zap.InfoLevel) p := evmclient.NewPool(lggr, "PriorityLevel", time.Second*2, time.Second*0, nodes, []evmclient.SendOnlyNode{}, &cltest.FixtureChainID, "") require.NoError(t, p.Dial(testutils.Context(t))) t.Cleanup(func() { assert.NoError(t, p.Close()) }) diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go new file mode 100644 index 00000000000..01851c4ae90 --- /dev/null +++ b/core/chains/evm/client/rpc_client.go @@ -0,0 +1,1050 @@ +package client + +import ( + "context" + "fmt" + "math/big" + "net/url" + "strconv" + "sync" + "time" + + "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/google/uuid" + "github.com/pkg/errors" + + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + commontypes "github.com/smartcontractkit/chainlink/v2/common/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +// RPCCLient includes all the necessary generalized RPC methods along with any additional chain-specific methods. +type RPCCLient interface { + commonclient.RPC[ + *big.Int, + evmtypes.Nonce, + common.Address, + common.Hash, + *types.Transaction, + common.Hash, + types.Log, + ethereum.FilterQuery, + *evmtypes.Receipt, + *assets.Wei, + *evmtypes.Head, + ] + BlockByHashGeth(ctx context.Context, hash common.Hash) (b *types.Block, err error) + BlockByNumberGeth(ctx context.Context, number *big.Int) (b *types.Block, err error) + HeaderByHash(ctx context.Context, h common.Hash) (head *types.Header, err error) + HeaderByNumber(ctx context.Context, n *big.Int) (head *types.Header, err error) + PendingCodeAt(ctx context.Context, account common.Address) (b []byte, err error) + SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (s ethereum.Subscription, err error) + SuggestGasPrice(ctx context.Context) (p *big.Int, err error) + SuggestGasTipCap(ctx context.Context) (t *big.Int, err error) + TransactionReceiptGeth(ctx context.Context, txHash common.Hash) (r *types.Receipt, err error) +} + +type rpcClient struct { + rpcLog logger.Logger + name string + id int32 + chainID *big.Int + tier commonclient.NodeTier + + ws rawclient + http *rawclient + + stateMu sync.RWMutex // protects state* fields + + // Need to track subscriptions because closing the RPC does not (always?) + // close the underlying subscription + subs []ethereum.Subscription + + // Need to track the aliveLoop subscription, so we do not cancel it when checking lease on the MultiNode + aliveLoopSub ethereum.Subscription + + // chStopInFlight can be closed to immediately cancel all in-flight requests on + // this rpcClient. Closing and replacing should be serialized through + // stateMu since it can happen on state transitions as well as rpcClient Close. + chStopInFlight chan struct{} +} + +// NewRPCCLient returns a new *rpcClient as commonclient.RPC +func NewRPCClient( + lggr logger.Logger, + wsuri url.URL, + httpuri *url.URL, + name string, + id int32, + chainID *big.Int, + tier commonclient.NodeTier, +) RPCCLient { + r := new(rpcClient) + r.name = name + r.id = id + r.chainID = chainID + r.tier = tier + r.ws.uri = wsuri + if httpuri != nil { + r.http = &rawclient{uri: *httpuri} + } + r.chStopInFlight = make(chan struct{}) + lggr = logger.Named(lggr, "Client") + lggr = logger.With(lggr, + "clientTier", tier.String(), + "clientName", name, + "client", r.String(), + "evmChainID", chainID, + ) + r.rpcLog = logger.Named(lggr, "RPC") + + return r +} + +// Not thread-safe, pure dial. +func (r *rpcClient) Dial(callerCtx context.Context) error { + ctx, cancel := r.makeQueryCtx(callerCtx) + defer cancel() + + promEVMPoolRPCNodeDials.WithLabelValues(r.chainID.String(), r.name).Inc() + lggr := logger.With(r.rpcLog, "wsuri", r.ws.uri.Redacted()) + if r.http != nil { + lggr = logger.With(lggr, "httpuri", r.http.uri.Redacted()) + } + lggr.Debugw("RPC dial: evmclient.Client#dial") + + wsrpc, err := rpc.DialWebsocket(ctx, r.ws.uri.String(), "") + if err != nil { + promEVMPoolRPCNodeDialsFailed.WithLabelValues(r.chainID.String(), r.name).Inc() + return errors.Wrapf(err, "error while dialing websocket: %v", r.ws.uri.Redacted()) + } + + r.ws.rpc = wsrpc + r.ws.geth = ethclient.NewClient(wsrpc) + + if r.http != nil { + if err := r.DialHTTP(); err != nil { + return err + } + } + + promEVMPoolRPCNodeDialsSuccess.WithLabelValues(r.chainID.String(), r.name).Inc() + + return nil +} + +// Not thread-safe, pure dial. +// DialHTTP doesn't actually make any external HTTP calls +// It can only return error if the URL is malformed. +func (r *rpcClient) DialHTTP() error { + promEVMPoolRPCNodeDials.WithLabelValues(r.chainID.String(), r.name).Inc() + lggr := logger.With(r.rpcLog, "httpuri", r.ws.uri.Redacted()) + lggr.Debugw("RPC dial: evmclient.Client#dial") + + var httprpc *rpc.Client + httprpc, err := rpc.DialHTTP(r.http.uri.String()) + if err != nil { + promEVMPoolRPCNodeDialsFailed.WithLabelValues(r.chainID.String(), r.name).Inc() + return errors.Wrapf(err, "error while dialing HTTP: %v", r.http.uri.Redacted()) + } + + r.http.rpc = httprpc + r.http.geth = ethclient.NewClient(httprpc) + + promEVMPoolRPCNodeDialsSuccess.WithLabelValues(r.chainID.String(), r.name).Inc() + + return nil +} + +func (r *rpcClient) Close() { + defer func() { + if r.ws.rpc != nil { + r.ws.rpc.Close() + } + }() + + r.stateMu.Lock() + defer r.stateMu.Unlock() + r.cancelInflightRequests() +} + +// cancelInflightRequests closes and replaces the chStopInFlight +// WARNING: NOT THREAD-SAFE +// This must be called from within the r.stateMu lock +func (r *rpcClient) cancelInflightRequests() { + close(r.chStopInFlight) + r.chStopInFlight = make(chan struct{}) +} + +func (r *rpcClient) String() string { + s := fmt.Sprintf("(%s)%s:%s", r.tier.String(), r.name, r.ws.uri.Redacted()) + if r.http != nil { + s = s + fmt.Sprintf(":%s", r.http.uri.Redacted()) + } + return s +} + +func (r *rpcClient) logResult( + lggr logger.Logger, + err error, + callDuration time.Duration, + rpcDomain, + callName string, + results ...interface{}, +) { + lggr = logger.With(lggr, "duration", callDuration, "rpcDomain", rpcDomain, "callName", callName) + promEVMPoolRPCNodeCalls.WithLabelValues(r.chainID.String(), r.name).Inc() + if err == nil { + promEVMPoolRPCNodeCallsSuccess.WithLabelValues(r.chainID.String(), r.name).Inc() + logger.Tracew(lggr, + fmt.Sprintf("evmclient.Client#%s RPC call success", callName), + results..., + ) + } else { + promEVMPoolRPCNodeCallsFailed.WithLabelValues(r.chainID.String(), r.name).Inc() + lggr.Debugw( + fmt.Sprintf("evmclient.Client#%s RPC call failure", callName), + append(results, "err", err)..., + ) + } + promEVMPoolRPCCallTiming. + WithLabelValues( + r.chainID.String(), // chain id + r.name, // rpcClient name + rpcDomain, // rpc domain + "false", // is send only + strconv.FormatBool(err == nil), // is successful + callName, // rpc call name + ). + Observe(float64(callDuration)) +} + +func (r *rpcClient) getRPCDomain() string { + if r.http != nil { + return r.http.uri.Host + } + return r.ws.uri.Host +} + +// registerSub adds the sub to the rpcClient list +func (r *rpcClient) registerSub(sub ethereum.Subscription) { + r.stateMu.Lock() + defer r.stateMu.Unlock() + r.subs = append(r.subs, sub) +} + +// disconnectAll disconnects all clients connected to the rpcClient +// WARNING: NOT THREAD-SAFE +// This must be called from within the r.stateMu lock +func (r *rpcClient) DisconnectAll() { + if r.ws.rpc != nil { + r.ws.rpc.Close() + } + r.cancelInflightRequests() + r.unsubscribeAll() +} + +// unsubscribeAll unsubscribes all subscriptions +// WARNING: NOT THREAD-SAFE +// This must be called from within the r.stateMu lock +func (r *rpcClient) unsubscribeAll() { + for _, sub := range r.subs { + sub.Unsubscribe() + } + r.subs = nil +} +func (r *rpcClient) SetAliveLoopSub(sub commontypes.Subscription) { + r.stateMu.Lock() + defer r.stateMu.Unlock() + + r.aliveLoopSub = sub +} + +// SubscribersCount returns the number of client subscribed to the node +func (r *rpcClient) SubscribersCount() int32 { + r.stateMu.RLock() + defer r.stateMu.RUnlock() + return int32(len(r.subs)) +} + +// UnsubscribeAllExceptAliveLoop disconnects all subscriptions to the node except the alive loop subscription +// while holding the n.stateMu lock +func (r *rpcClient) UnsubscribeAllExceptAliveLoop() { + r.stateMu.Lock() + defer r.stateMu.Unlock() + + for _, s := range r.subs { + if s != r.aliveLoopSub { + s.Unsubscribe() + } + } +} + +// RPC wrappers + +// CallContext implementation +func (r *rpcClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), + "method", method, + "args", args, + ) + + lggr.Debug("RPC call: evmclient.Client#CallContext") + start := time.Now() + if http != nil { + err = r.wrapHTTP(http.rpc.CallContext(ctx, result, method, args...)) + } else { + err = r.wrapWS(ws.rpc.CallContext(ctx, result, method, args...)) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "CallContext") + + return err +} + +func (r *rpcClient) BatchCallContext(ctx context.Context, b []any) error { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return err + } + batch := make([]rpc.BatchElem, len(b)) + for i, arg := range b { + batch[i] = arg.(rpc.BatchElem) + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "nBatchElems", len(b), "batchElems", b) + + logger.Trace(lggr, "RPC call: evmclient.Client#BatchCallContext") + start := time.Now() + if http != nil { + err = r.wrapHTTP(http.rpc.BatchCallContext(ctx, batch)) + } else { + err = r.wrapWS(ws.rpc.BatchCallContext(ctx, batch)) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "BatchCallContext") + + return err +} + +func (r *rpcClient) Subscribe(ctx context.Context, channel chan<- *evmtypes.Head, args ...interface{}) (commontypes.Subscription, error) { + ctx, cancel, ws, _, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "args", args) + + lggr.Debug("RPC call: evmclient.Client#EthSubscribe") + start := time.Now() + sub, err := ws.rpc.EthSubscribe(ctx, channel, args...) + if err == nil { + r.registerSub(sub) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "EthSubscribe") + + return sub, err +} + +// GethClient wrappers + +func (r *rpcClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (receipt *evmtypes.Receipt, err error) { + err = r.CallContext(ctx, &receipt, "eth_getTransactionReceipt", txHash, false) + if err != nil { + return nil, err + } + if receipt == nil { + err = ethereum.NotFound + return + } + return +} + +func (r *rpcClient) TransactionReceiptGeth(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "txHash", txHash) + + lggr.Debug("RPC call: evmclient.Client#TransactionReceipt") + + start := time.Now() + if http != nil { + receipt, err = http.geth.TransactionReceipt(ctx, txHash) + err = r.wrapHTTP(err) + } else { + receipt, err = ws.geth.TransactionReceipt(ctx, txHash) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "TransactionReceipt", + "receipt", receipt, + ) + + return +} +func (r *rpcClient) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "txHash", txHash) + + lggr.Debug("RPC call: evmclient.Client#TransactionByHash") + + start := time.Now() + if http != nil { + tx, _, err = http.geth.TransactionByHash(ctx, txHash) + err = r.wrapHTTP(err) + } else { + tx, _, err = ws.geth.TransactionByHash(ctx, txHash) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "TransactionByHash", + "receipt", tx, + ) + + return +} + +func (r *rpcClient) HeaderByNumber(ctx context.Context, number *big.Int) (header *types.Header, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "number", number) + + lggr.Debug("RPC call: evmclient.Client#HeaderByNumber") + start := time.Now() + if http != nil { + header, err = http.geth.HeaderByNumber(ctx, number) + err = r.wrapHTTP(err) + } else { + header, err = ws.geth.HeaderByNumber(ctx, number) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "HeaderByNumber", "header", header) + + return +} + +func (r *rpcClient) HeaderByHash(ctx context.Context, hash common.Hash) (header *types.Header, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "hash", hash) + + lggr.Debug("RPC call: evmclient.Client#HeaderByHash") + start := time.Now() + if http != nil { + header, err = http.geth.HeaderByHash(ctx, hash) + err = r.wrapHTTP(err) + } else { + header, err = ws.geth.HeaderByHash(ctx, hash) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "HeaderByHash", + "header", header, + ) + + return +} + +func (r *rpcClient) BlockByNumber(ctx context.Context, number *big.Int) (head *evmtypes.Head, err error) { + hex := ToBlockNumArg(number) + err = r.CallContext(ctx, &head, "eth_getBlockByNumber", hex, false) + if err != nil { + return nil, err + } + if head == nil { + err = ethereum.NotFound + return + } + head.EVMChainID = utils.NewBig(r.chainID) + return +} + +func (r *rpcClient) BlockByHash(ctx context.Context, hash common.Hash) (head *evmtypes.Head, err error) { + err = r.CallContext(ctx, &head, "eth_getBlockByHash", hash.Hex(), false) + if err != nil { + return nil, err + } + if head == nil { + err = ethereum.NotFound + return + } + head.EVMChainID = utils.NewBig(r.chainID) + return +} + +func (r *rpcClient) BlockByHashGeth(ctx context.Context, hash common.Hash) (block *types.Block, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "hash", hash) + + lggr.Debug("RPC call: evmclient.Client#BlockByHash") + start := time.Now() + if http != nil { + block, err = http.geth.BlockByHash(ctx, hash) + err = r.wrapHTTP(err) + } else { + block, err = ws.geth.BlockByHash(ctx, hash) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "BlockByHash", + "block", block, + ) + + return +} + +func (r *rpcClient) BlockByNumberGeth(ctx context.Context, number *big.Int) (block *types.Block, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "number", number) + + lggr.Debug("RPC call: evmclient.Client#BlockByNumber") + start := time.Now() + if http != nil { + block, err = http.geth.BlockByNumber(ctx, number) + err = r.wrapHTTP(err) + } else { + block, err = ws.geth.BlockByNumber(ctx, number) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "BlockByNumber", + "block", block, + ) + + return +} + +func (r *rpcClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "tx", tx) + + lggr.Debug("RPC call: evmclient.Client#SendTransaction") + start := time.Now() + if http != nil { + err = r.wrapHTTP(http.geth.SendTransaction(ctx, tx)) + } else { + err = r.wrapWS(ws.geth.SendTransaction(ctx, tx)) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "SendTransaction") + + return err +} + +func (r *rpcClient) SimulateTransaction(ctx context.Context, tx *types.Transaction) error { + // Not Implemented + return errors.New("SimulateTransaction not implemented") +} + +func (r *rpcClient) SendEmptyTransaction( + ctx context.Context, + newTxAttempt func(nonce evmtypes.Nonce, feeLimit uint32, fee *assets.Wei, fromAddress common.Address) (attempt any, err error), + nonce evmtypes.Nonce, + gasLimit uint32, + fee *assets.Wei, + fromAddress common.Address, +) (txhash string, err error) { + // Not Implemented + return "", errors.New("SendEmptyTransaction not implemented") +} + +// PendingSequenceAt returns one higher than the highest nonce from both mempool and mined transactions +func (r *rpcClient) PendingSequenceAt(ctx context.Context, account common.Address) (nonce evmtypes.Nonce, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return 0, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "account", account) + + lggr.Debug("RPC call: evmclient.Client#PendingNonceAt") + start := time.Now() + var n uint64 + if http != nil { + n, err = http.geth.PendingNonceAt(ctx, account) + nonce = evmtypes.Nonce(int64(n)) + err = r.wrapHTTP(err) + } else { + n, err = ws.geth.PendingNonceAt(ctx, account) + nonce = evmtypes.Nonce(int64(n)) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "PendingNonceAt", + "nonce", nonce, + ) + + return +} + +// SequenceAt 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 (r *rpcClient) SequenceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (nonce evmtypes.Nonce, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return 0, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "account", account, "blockNumber", blockNumber) + + lggr.Debug("RPC call: evmclient.Client#NonceAt") + start := time.Now() + var n uint64 + if http != nil { + n, err = http.geth.NonceAt(ctx, account, blockNumber) + nonce = evmtypes.Nonce(int64(n)) + err = r.wrapHTTP(err) + } else { + n, err = ws.geth.NonceAt(ctx, account, blockNumber) + nonce = evmtypes.Nonce(int64(n)) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "NonceAt", + "nonce", nonce, + ) + + return +} + +func (r *rpcClient) PendingCodeAt(ctx context.Context, account common.Address) (code []byte, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "account", account) + + lggr.Debug("RPC call: evmclient.Client#PendingCodeAt") + start := time.Now() + if http != nil { + code, err = http.geth.PendingCodeAt(ctx, account) + err = r.wrapHTTP(err) + } else { + code, err = ws.geth.PendingCodeAt(ctx, account) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "PendingCodeAt", + "code", code, + ) + + return +} + +func (r *rpcClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) (code []byte, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "account", account, "blockNumber", blockNumber) + + lggr.Debug("RPC call: evmclient.Client#CodeAt") + start := time.Now() + if http != nil { + code, err = http.geth.CodeAt(ctx, account, blockNumber) + err = r.wrapHTTP(err) + } else { + code, err = ws.geth.CodeAt(ctx, account, blockNumber) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "CodeAt", + "code", code, + ) + + return +} + +func (r *rpcClient) EstimateGas(ctx context.Context, c interface{}) (gas uint64, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return 0, err + } + defer cancel() + call := c.(ethereum.CallMsg) + lggr := logger.With(r.newRqLggr(), "call", call) + + lggr.Debug("RPC call: evmclient.Client#EstimateGas") + start := time.Now() + if http != nil { + gas, err = http.geth.EstimateGas(ctx, call) + err = r.wrapHTTP(err) + } else { + gas, err = ws.geth.EstimateGas(ctx, call) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "EstimateGas", + "gas", gas, + ) + + return +} + +func (r *rpcClient) SuggestGasPrice(ctx context.Context) (price *big.Int, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := r.newRqLggr() + + lggr.Debug("RPC call: evmclient.Client#SuggestGasPrice") + start := time.Now() + if http != nil { + price, err = http.geth.SuggestGasPrice(ctx) + err = r.wrapHTTP(err) + } else { + price, err = ws.geth.SuggestGasPrice(ctx) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "SuggestGasPrice", + "price", price, + ) + + return +} + +func (r *rpcClient) CallContract(ctx context.Context, msg interface{}, blockNumber *big.Int) (val []byte, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "callMsg", msg, "blockNumber", blockNumber) + message := msg.(ethereum.CallMsg) + + lggr.Debug("RPC call: evmclient.Client#CallContract") + start := time.Now() + if http != nil { + val, err = http.geth.CallContract(ctx, message, blockNumber) + err = r.wrapHTTP(err) + } else { + val, err = ws.geth.CallContract(ctx, message, blockNumber) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "CallContract", + "val", val, + ) + + return + +} + +func (r *rpcClient) LatestBlockHeight(ctx context.Context) (*big.Int, error) { + var height big.Int + h, err := r.BlockNumber(ctx) + return height.SetUint64(h), err +} + +func (r *rpcClient) BlockNumber(ctx context.Context) (height uint64, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return 0, err + } + defer cancel() + lggr := r.newRqLggr() + + lggr.Debug("RPC call: evmclient.Client#BlockNumber") + start := time.Now() + if http != nil { + height, err = http.geth.BlockNumber(ctx) + err = r.wrapHTTP(err) + } else { + height, err = ws.geth.BlockNumber(ctx) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "BlockNumber", + "height", height, + ) + + return +} + +func (r *rpcClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (balance *big.Int, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "account", account.Hex(), "blockNumber", blockNumber) + + lggr.Debug("RPC call: evmclient.Client#BalanceAt") + start := time.Now() + if http != nil { + balance, err = http.geth.BalanceAt(ctx, account, blockNumber) + err = r.wrapHTTP(err) + } else { + balance, err = ws.geth.BalanceAt(ctx, account, blockNumber) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "BalanceAt", + "balance", balance, + ) + + return +} + +// TokenBalance returns the balance of the given address for the token contract address. +func (r *rpcClient) TokenBalance(ctx context.Context, address common.Address, contractAddress common.Address) (*big.Int, error) { + result := "" + numLinkBigInt := new(big.Int) + functionSelector := evmtypes.HexToFunctionSelector(BALANCE_OF_ADDRESS_FUNCTION_SELECTOR) // balanceOf(address) + data := utils.ConcatBytes(functionSelector.Bytes(), common.LeftPadBytes(address.Bytes(), utils.EVMWordByteLen)) + args := CallArgs{ + To: contractAddress, + Data: data, + } + err := r.CallContext(ctx, &result, "eth_call", args, "latest") + if err != nil { + return numLinkBigInt, err + } + if _, ok := numLinkBigInt.SetString(result, 0); !ok { + return nil, fmt.Errorf("failed to parse int: %s", result) + } + return numLinkBigInt, nil +} + +// LINKBalance returns the balance of LINK at the given address +func (r *rpcClient) LINKBalance(ctx context.Context, address common.Address, linkAddress common.Address) (*commonassets.Link, error) { + balance, err := r.TokenBalance(ctx, address, linkAddress) + if err != nil { + return commonassets.NewLinkFromJuels(0), err + } + return (*commonassets.Link)(balance), nil +} + +func (r *rpcClient) FilterEvents(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + return r.FilterLogs(ctx, q) +} + +func (r *rpcClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) (l []types.Log, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "q", q) + + lggr.Debug("RPC call: evmclient.Client#FilterLogs") + start := time.Now() + if http != nil { + l, err = http.geth.FilterLogs(ctx, q) + err = r.wrapHTTP(err) + } else { + l, err = ws.geth.FilterLogs(ctx, q) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "FilterLogs", + "log", l, + ) + + return +} + +func (r *rpcClient) ClientVersion(ctx context.Context) (version string, err error) { + err = r.CallContext(ctx, &version, "web3_clientVersion") + return +} + +func (r *rpcClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (sub ethereum.Subscription, err error) { + ctx, cancel, ws, _, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := logger.With(r.newRqLggr(), "q", q) + + lggr.Debug("RPC call: evmclient.Client#SubscribeFilterLogs") + start := time.Now() + sub, err = ws.geth.SubscribeFilterLogs(ctx, q, ch) + if err == nil { + r.registerSub(sub) + } + err = r.wrapWS(err) + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "SubscribeFilterLogs") + + return +} + +func (r *rpcClient) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := r.newRqLggr() + + lggr.Debug("RPC call: evmclient.Client#SuggestGasTipCap") + start := time.Now() + if http != nil { + tipCap, err = http.geth.SuggestGasTipCap(ctx) + err = r.wrapHTTP(err) + } else { + tipCap, err = ws.geth.SuggestGasTipCap(ctx) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "SuggestGasTipCap", + "tipCap", tipCap, + ) + + return +} + +// Returns the ChainID according to the geth client. This is useful for functions like verify() +// the common node. +func (r *rpcClient) ChainID(ctx context.Context) (chainID *big.Int, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + + defer cancel() + + if http != nil { + chainID, err = http.geth.ChainID(ctx) + err = r.wrapHTTP(err) + } else { + chainID, err = ws.geth.ChainID(ctx) + err = r.wrapWS(err) + } + return +} + +// newRqLggr generates a new logger with a unique request ID +func (r *rpcClient) newRqLggr() logger.Logger { + return logger.With(r.rpcLog, + "requestID", uuid.New(), + ) +} + +func wrapCallError(err error, tp string) error { + if err == nil { + return nil + } + if errors.Cause(err).Error() == "context deadline exceeded" { + err = errors.Wrap(err, "remote node timed out") + } + return errors.Wrapf(err, "%s call failed", tp) +} + +func (r *rpcClient) wrapWS(err error) error { + err = wrapCallError(err, fmt.Sprintf("%s websocket (%s)", r.tier.String(), r.ws.uri.Redacted())) + return err +} + +func (r *rpcClient) wrapHTTP(err error) error { + err = wrapCallError(err, fmt.Sprintf("%s http (%s)", r.tier.String(), r.http.uri.Redacted())) + if err != nil { + r.rpcLog.Debugw("Call failed", "err", err) + } else { + logger.Trace(r.rpcLog, "Call succeeded") + } + return err +} + +// makeLiveQueryCtxAndSafeGetClients wraps makeQueryCtx +func (r *rpcClient) makeLiveQueryCtxAndSafeGetClients(parentCtx context.Context) (ctx context.Context, cancel context.CancelFunc, ws rawclient, http *rawclient, err error) { + // Need to wrap in mutex because state transition can cancel and replace the + // context + r.stateMu.RLock() + cancelCh := r.chStopInFlight + ws = r.ws + if r.http != nil { + cp := *r.http + http = &cp + } + r.stateMu.RUnlock() + ctx, cancel = makeQueryCtx(parentCtx, cancelCh) + return +} + +func (r *rpcClient) makeQueryCtx(ctx context.Context) (context.Context, context.CancelFunc) { + return makeQueryCtx(ctx, r.getChStopInflight()) +} + +// getChStopInflight provides a convenience helper that mutex wraps a +// read to the chStopInFlight +func (r *rpcClient) getChStopInflight() chan struct{} { + r.stateMu.RLock() + defer r.stateMu.RUnlock() + return r.chStopInFlight +} + +func (r *rpcClient) Name() string { + return r.name +} + +func Name(r *rpcClient) string { + return r.name +} diff --git a/core/chains/evm/client/send_only_node.go b/core/chains/evm/client/send_only_node.go index a085ec47531..b6ad26696fc 100644 --- a/core/chains/evm/client/send_only_node.go +++ b/core/chains/evm/client/send_only_node.go @@ -14,13 +14,15 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/utils" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" ) //go:generate mockery --quiet --name SendOnlyNode --output ../mocks/ --case=underscore // SendOnlyNode represents one ethereum node used as a sendonly +// +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.SendOnlyNode] type SendOnlyNode interface { // Start may attempt to connect to the node, but should only return error for misconfiguration - never for temporary errors. Start(context.Context) error @@ -56,7 +58,7 @@ var _ SendOnlyNode = &sendOnlyNode{} // It only supports sending transactions // It must a http(s) url type sendOnlyNode struct { - utils.StartStopOnce + services.StateMachine stateMu sync.RWMutex // protects state* fields state NodeState @@ -68,15 +70,18 @@ type sendOnlyNode struct { dialed bool name string chainID *big.Int - chStop utils.StopChan + chStop services.StopChan wg sync.WaitGroup } // NewSendOnlyNode returns a new sendonly node +// +// Deprecated: use [pkg/github.com/smartcontractkit/chainlink/v2/common/client.NewSendOnlyNode] 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( + s.log = logger.Named(logger.Named(lggr, "SendOnlyNode"), name) + s.log = logger.With(s.log, "nodeTier", "sendonly", ) s.uri = httpuri @@ -206,7 +211,7 @@ func (s *sendOnlyNode) SendTransaction(parentCtx context.Context, tx *types.Tran 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") + s.logTiming(logger.With(s.log, "nBatchElems", len(b)), time.Since(start), err, "BatchCallContext") }(time.Now()) ctx, cancel := s.makeQueryCtx(parentCtx) diff --git a/core/chains/evm/client/send_only_node_lifecycle.go b/core/chains/evm/client/send_only_node_lifecycle.go index 509be53c8a3..9d704e49389 100644 --- a/core/chains/evm/client/send_only_node_lifecycle.go +++ b/core/chains/evm/client/send_only_node_lifecycle.go @@ -1,7 +1,6 @@ package client import ( - "context" "fmt" "time" @@ -14,12 +13,14 @@ import ( // It will continue checking until success and then exit permanently. func (s *sendOnlyNode) verifyLoop() { defer s.wg.Done() + ctx, cancel := s.chStop.NewCtx() + defer cancel() backoff := utils.NewRedialBackoff() for { select { case <-time.After(backoff.Duration()): - chainID, err := s.sender.ChainID(context.Background()) + chainID, err := s.sender.ChainID(ctx) if err != nil { ok := s.IfStarted(func() { if changed := s.setState(NodeStateUnreachable); changed { @@ -60,7 +61,7 @@ func (s *sendOnlyNode) verifyLoop() { s.log.Infow("Sendonly RPC Node is online", "nodeState", s.state) return } - case <-s.chStop: + case <-ctx.Done(): return } } diff --git a/core/chains/evm/client/send_only_node_test.go b/core/chains/evm/client/send_only_node_test.go index b37ee142533..760f7f4d3eb 100644 --- a/core/chains/evm/client/send_only_node_test.go +++ b/core/chains/evm/client/send_only_node_test.go @@ -16,13 +16,13 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" + "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestNewSendOnlyNode(t *testing.T) { @@ -32,7 +32,7 @@ func TestNewSendOnlyNode(t *testing.T) { password := "pass" url := testutils.MustParseURL(t, fmt.Sprintf(urlFormat, password)) redacted := fmt.Sprintf(urlFormat, "xxxxx") - lggr := logger.TestLogger(t) + lggr := logger.Test(t) name := "TestNewSendOnlyNode" chainID := testutils.NewRandomEVMChainID() @@ -52,7 +52,7 @@ func TestStartSendOnlyNode(t *testing.T) { chainID := testutils.NewRandomEVMChainID() r := chainIDResp{chainID.Int64(), nil} url := r.newHTTPServer(t) - lggr, observedLogs := logger.TestLoggerObserved(t, zap.WarnLevel) + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) s := evmclient.NewSendOnlyNode(lggr, *url, t.Name(), chainID) defer func() { assert.NoError(t, s.Close()) }() err := s.Start(testutils.Context(t)) @@ -62,7 +62,7 @@ func TestStartSendOnlyNode(t *testing.T) { t.Run("Start with ChainID=0", func(t *testing.T) { t.Parallel() - lggr, observedLogs := logger.TestLoggerObserved(t, zap.WarnLevel) + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) chainID := testutils.FixtureChainID r := chainIDResp{chainID.Int64(), nil} url := r.newHTTPServer(t) @@ -77,7 +77,7 @@ func TestStartSendOnlyNode(t *testing.T) { t.Run("becomes unusable (and remains undialed) if initial dial fails", func(t *testing.T) { t.Parallel() - lggr, observedLogs := logger.TestLoggerObserved(t, zap.WarnLevel) + lggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) invalidURL := url.URL{Scheme: "some rubbish", Host: "not a valid host"} s := evmclient.NewSendOnlyNode(lggr, invalidURL, t.Name(), testutils.FixtureChainID) @@ -109,7 +109,7 @@ func TestSendTransaction(t *testing.T) { t.Parallel() chainID := testutils.FixtureChainID - lggr, observedLogs := logger.TestLoggerObserved(t, zap.DebugLevel) + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) url := testutils.MustParseURL(t, "http://place.holder") s := evmclient.NewSendOnlyNode(lggr, *url, @@ -135,7 +135,7 @@ func TestSendTransaction(t *testing.T) { func TestBatchCallContext(t *testing.T) { t.Parallel() - lggr := logger.TestLogger(t) + lggr := logger.Test(t) chainID := testutils.FixtureChainID url := testutils.MustParseURL(t, "http://place.holder") s := evmclient.NewSendOnlyNode( diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index abab2046620..293bf64badc 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -18,13 +18,49 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) +func init() { + var err error + + balanceOfABI, err = abi.JSON(strings.NewReader(balanceOfABIString)) + if err != nil { + panic(fmt.Errorf("%w: while parsing erc20ABI", err)) + } +} + +var ( + balanceOfABIString = `[ + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +]` + + balanceOfABI abi.ABI +) + // SimulatedBackendClient is an Client implementation using a simulated // blockchain backend. Note that not all RPC methods are implemented here. type SimulatedBackendClient struct { @@ -51,69 +87,6 @@ func (c *SimulatedBackendClient) Dial(context.Context) error { // other simulated clients might still be using it func (c *SimulatedBackendClient) Close() {} -// checkEthCallArgs extracts and verifies the arguments for an eth_call RPC -func (c *SimulatedBackendClient) checkEthCallArgs( - args []interface{}) (*CallArgs, *big.Int, error) { - if len(args) != 2 { - return nil, nil, fmt.Errorf( - "should have two arguments after \"eth_call\", got %d", len(args)) - } - callArgs, ok := args[0].(map[string]interface{}) - if !ok { - return nil, nil, fmt.Errorf("third arg to SimulatedBackendClient.Call "+ - "must be an eth.CallArgs, got %+#v", args[0]) - } - blockNumber, err := c.blockNumber(args[1]) - if err != nil { - return nil, nil, fmt.Errorf("fourth arg to SimulatedBackendClient.Call "+ - "must be the string \"latest\", or a *big.Int, got %#+v", args[1]) - } - - // to and from need to map to a common.Address but could come in as a string - var ( - toAddr common.Address - frmAddr common.Address - ) - - toAddr, err = interfaceToAddress(callArgs["to"]) - if err != nil { - return nil, nil, err - } - - // from is optional in the standard client; default to 0x when missing - if value, ok := callArgs["from"]; ok { - addr, err := interfaceToAddress(value) - if err != nil { - return nil, nil, err - } - - frmAddr = addr - } else { - frmAddr = common.HexToAddress("0x") - } - - ca := CallArgs{ - To: toAddr, - From: frmAddr, - Data: callArgs["data"].(hexutil.Bytes), - } - - return &ca, blockNumber, nil -} - -func interfaceToAddress(value interface{}) (common.Address, error) { - switch v := value.(type) { - case common.Address: - return v, nil - case string: - return common.HexToAddress(v), nil - case *big.Int: - return common.BigToAddress(v), nil - default: - return common.HexToAddress("0x"), fmt.Errorf("unrecognized value type for converting value to common.Address; try string, *big.Int, or common.Address") - } -} - // CallContext mocks the ethereum client RPC calls used by chainlink, copying the // return value into result. // The simulated client avoids the old block error from the simulated backend by @@ -121,41 +94,16 @@ func interfaceToAddress(value interface{}) (common.Address, error) { // and will not return an error when an old block is used. func (c *SimulatedBackendClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { switch method { + case "eth_getTransactionReceipt": + return c.ethGetTransactionReceipt(ctx, result, args...) + case "eth_getBlockByNumber": + return c.ethGetBlockByNumber(ctx, result, args...) case "eth_call": - var ( - callArgs *CallArgs - b []byte - err error - ) - - if callArgs, _, err = c.checkEthCallArgs(args); err != nil { - return err - } - - callMsg := ethereum.CallMsg{From: callArgs.From, To: &callArgs.To, Data: callArgs.Data} - - if b, err = c.b.CallContract(ctx, callMsg, nil /* always latest block */); err != nil { - return fmt.Errorf("%w: while calling contract at address %x with "+ - "data %x", err, callArgs.To, callArgs.Data) - } - - switch r := result.(type) { - case *hexutil.Bytes: - *r = append(*r, b...) - - if !bytes.Equal(*r, b) { - return fmt.Errorf("was passed a non-empty array, or failed to copy "+ - "answer. Expected %x = %x", *r, b) - } - return nil - default: - return fmt.Errorf("first arg to SimulatedBackendClient.Call is an "+ - "unrecognized type: %T; add processing logic for it here", result) - } + return c.ethCall(ctx, result, args...) + case "eth_getHeaderByNumber": + return c.ethGetHeaderByNumber(ctx, result, args...) default: - return fmt.Errorf("second arg to SimulatedBackendClient.Call is an RPC "+ - "API method which has not yet been implemented: %s. Add processing for "+ - "it here", method) + return fmt.Errorf("second arg to SimulatedBackendClient.Call is an RPC API method which has not yet been implemented: %s. Add processing for it here", method) } } @@ -175,38 +123,6 @@ func (c *SimulatedBackendClient) currentBlockNumber() *big.Int { return c.b.Blockchain().CurrentBlock().Number } -var balanceOfABIString = `[ - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } -]` - -var balanceOfABI abi.ABI - -func init() { - var err error - balanceOfABI, err = abi.JSON(strings.NewReader(balanceOfABIString)) - if err != nil { - panic(fmt.Errorf("%w: while parsing erc20ABI", err)) - } -} - func (c *SimulatedBackendClient) TokenBalance(ctx context.Context, address common.Address, contractAddress common.Address) (balance *big.Int, err error) { callData, err := balanceOfABI.Pack("balanceOf", address) if err != nil { @@ -251,13 +167,12 @@ func (c *SimulatedBackendClient) blockNumber(number interface{}) (blockNumber *b case "earliest": return big.NewInt(0), nil case "pending": - panic("not implemented") // I don't understand the semantics of this. + panic("pending block not supported by simulated backend client") // I don't understand the semantics of this. // return big.NewInt(0).Add(c.currentBlockNumber(), big.NewInt(1)), nil default: - blockNumber, err = utils.HexToUint256(n) + blockNumber, err := hexutil.DecodeBig(n) if err != nil { - return nil, fmt.Errorf("%w: while parsing '%s' as hex-encoded"+ - "block number", err, n) + return nil, fmt.Errorf("%w: while parsing '%s' as hex-encoded block number", err, n) } return blockNumber, nil } @@ -318,7 +233,8 @@ func (c *SimulatedBackendClient) BlockByHash(ctx context.Context, hash common.Ha } func (c *SimulatedBackendClient) LatestBlockHeight(ctx context.Context) (*big.Int, error) { - panic("not implemented") + header, err := c.b.HeaderByNumber(ctx, nil) + return header.Number, err } // ChainID returns the ethereum ChainID. @@ -386,7 +302,7 @@ func (c *SimulatedBackendClient) SubscribeNewHead( case h := <-ch: var head *evmtypes.Head if h != nil { - head = &evmtypes.Head{Number: h.Number.Int64(), Hash: h.Hash(), ParentHash: h.ParentHash, Parent: lastHead, EVMChainID: utils.NewBig(c.chainId)} + head = &evmtypes.Head{Difficulty: h.Difficulty, Timestamp: time.Unix(int64(h.Time), 0), Number: h.Number.Int64(), Hash: h.Hash(), ParentHash: h.ParentHash, Parent: lastHead, EVMChainID: utils.NewBig(c.chainId)} lastHead = head } select { @@ -416,23 +332,23 @@ func (c *SimulatedBackendClient) HeaderByHash(ctx context.Context, h common.Hash return c.b.HeaderByHash(ctx, h) } -func (c *SimulatedBackendClient) SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, fromAddress common.Address) (clienttypes.SendTxReturnCode, error) { +func (c *SimulatedBackendClient) SendTransactionReturnCode(ctx context.Context, tx *types.Transaction, fromAddress common.Address) (commonclient.SendTxReturnCode, error) { err := c.SendTransaction(ctx, tx) if err == nil { - return clienttypes.Successful, nil + return commonclient.Successful, nil } if strings.Contains(err.Error(), "could not fetch parent") || strings.Contains(err.Error(), "invalid transaction") { - return clienttypes.Fatal, err + return commonclient.Fatal, err } // All remaining error messages returned from SendTransaction are considered Unknown. - return clienttypes.Unknown, err + return commonclient.Unknown, err } // SendTransaction sends a transaction. func (c *SimulatedBackendClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { sender, err := types.Sender(types.NewLondonSigner(c.chainId), tx) if err != nil { - logger.TestLogger(c.t).Panic(fmt.Errorf("invalid transaction: %v (tx: %#v)", err, tx)) + logger.Test(c.t).Panic(fmt.Errorf("invalid transaction: %v (tx: %#v)", err, tx)) } pendingNonce, err := c.b.PendingNonceAt(ctx, sender) if err != nil { @@ -520,114 +436,18 @@ func (c *SimulatedBackendClient) BatchCallContext(ctx context.Context, b []rpc.B for i, elem := range b { switch elem.Method { case "eth_getTransactionReceipt": - if _, ok := elem.Result.(*evmtypes.Receipt); !ok { - return fmt.Errorf("SimulatedBackendClient expected return type of *evmtypes.Receipt for eth_getTransactionReceipt, got type %T", elem.Result) - } - if len(elem.Args) != 1 { - return fmt.Errorf("SimulatedBackendClient expected 1 arg, got %d for eth_getTransactionReceipt", len(elem.Args)) - } - hash, is := elem.Args[0].(common.Hash) - if !is { - return fmt.Errorf("SimulatedBackendClient expected arg to be a hash, got: %T", elem.Args[0]) - } - receipt, err := c.b.TransactionReceipt(ctx, hash) - if receipt != nil { - *(b[i].Result.(*evmtypes.Receipt)) = *evmtypes.FromGethReceipt(receipt) - } - b[i].Error = err + b[i].Error = c.ethGetTransactionReceipt(ctx, b[i].Result, b[i].Args...) case "eth_getBlockByNumber": - switch v := elem.Result.(type) { - case *evmtypes.Head: - case *evmtypes.Block: - default: - return fmt.Errorf("SimulatedBackendClient expected return type of [*evmtypes.Head] or [*evmtypes.Block] for eth_getBlockByNumber, got type %T", v) - } - if len(elem.Args) != 2 { - return fmt.Errorf("SimulatedBackendClient expected 2 args, got %d for eth_getBlockByNumber", len(elem.Args)) - } - blockNumOrTag, is := elem.Args[0].(string) - if !is { - return fmt.Errorf("SimulatedBackendClient expected first arg to be a string for eth_getBlockByNumber, got: %T", elem.Args[0]) - } - _, is = elem.Args[1].(bool) - if !is { - return fmt.Errorf("SimulatedBackendClient expected second arg to be a boolean for eth_getBlockByNumber, got: %T", elem.Args[1]) - } - header, err := c.fetchHeader(ctx, blockNumOrTag) - if err != nil { - return err - } - switch res := elem.Result.(type) { - case *evmtypes.Head: - res.Number = header.Number.Int64() - res.Hash = header.Hash() - res.ParentHash = header.ParentHash - res.Timestamp = time.Unix(int64(header.Time), 0).UTC() - case *evmtypes.Block: - res.Number = header.Number.Int64() - res.Hash = header.Hash() - res.ParentHash = header.ParentHash - res.Timestamp = time.Unix(int64(header.Time), 0).UTC() - default: - return fmt.Errorf("SimulatedBackendClient Unexpected Type %T", elem.Result) - } - b[i].Error = err + b[i].Error = c.ethGetBlockByNumber(ctx, b[i].Result, b[i].Args...) case "eth_call": - if len(elem.Args) != 2 { - return fmt.Errorf("SimulatedBackendClient expected 2 args, got %d for eth_call", len(elem.Args)) - } - - _, ok := elem.Result.(*string) - if !ok { - return fmt.Errorf("SimulatedBackendClient expected result to be *string for eth_call, got: %T", elem.Result) - } - - params, ok := elem.Args[0].(map[string]interface{}) - if !ok { - return fmt.Errorf("SimulatedBackendClient expected first arg to be map[string]interface{} for eth_call, got: %T", elem.Args[0]) - } - - blockNum, ok := elem.Args[1].(string) - if !ok { - return fmt.Errorf("SimulatedBackendClient expected second arg to be a string for eth_call, got: %T", elem.Args[1]) - } - - if blockNum != "" { - if _, ok = new(big.Int).SetString(blockNum, 0); !ok { - return fmt.Errorf("error while converting block number string: %s to big.Int ", blockNum) - } - } - - callMsg := toCallMsg(params) - resp, err := c.b.CallContract(ctx, callMsg, nil) - *(b[i].Result.(*string)) = hexutil.Encode(resp) - b[i].Error = err + b[i].Error = c.ethCall(ctx, b[i].Result, b[i].Args...) case "eth_getHeaderByNumber": - if len(elem.Args) != 1 { - return fmt.Errorf("SimulatedBackendClient expected 2 args, got %d for eth_getHeaderByNumber", len(elem.Args)) - } - blockNum, is := elem.Args[0].(string) - if !is { - return fmt.Errorf("SimulatedBackendClient expected first arg to be a string for eth_getHeaderByNumber, got: %T", elem.Args[0]) - } - n, err := hexutil.DecodeBig(blockNum) - if err != nil { - return fmt.Errorf("error while converting hex block number %s to big.Int ", blockNum) - } - header, err := c.b.HeaderByNumber(ctx, n) - if err != nil { - return err - } - switch v := elem.Result.(type) { - case *types.Header: - b[i].Result = header - default: - return fmt.Errorf("SimulatedBackendClient Unexpected Type %T", v) - } + b[i].Error = c.ethGetHeaderByNumber(ctx, b[i].Result, b[i].Args...) default: return fmt.Errorf("SimulatedBackendClient got unsupported method %s", elem.Method) } } + return nil } @@ -654,32 +474,175 @@ func (c *SimulatedBackendClient) Commit() common.Hash { return c.b.Commit() } -func toCallMsg(params map[string]interface{}) ethereum.CallMsg { - var callMsg ethereum.CallMsg +func (c *SimulatedBackendClient) IsL2() bool { + return false +} - switch to := params["to"].(type) { - case string: - toAddr := common.HexToAddress(to) - callMsg.To = &toAddr - case common.Address: - callMsg.To = &to - case *common.Address: - callMsg.To = to +func (c *SimulatedBackendClient) fetchHeader(ctx context.Context, blockNumOrTag string) (*types.Header, error) { + switch blockNumOrTag { + case rpc.SafeBlockNumber.String(): + return c.b.Blockchain().CurrentSafeBlock(), nil + case rpc.LatestBlockNumber.String(): + return c.b.Blockchain().CurrentHeader(), nil + case rpc.FinalizedBlockNumber.String(): + return c.b.Blockchain().CurrentFinalBlock(), nil default: - panic("unexpected type of 'to' parameter") + blockNum, ok := new(big.Int).SetString(blockNumOrTag, 0) + if !ok { + return nil, fmt.Errorf("error while converting block number string: %s to big.Int ", blockNumOrTag) + } + return c.b.HeaderByNumber(ctx, blockNum) } +} - switch from := params["from"].(type) { - case nil: - // This parameter is not required so nil is acceptable - case string: - callMsg.From = common.HexToAddress(from) - case common.Address: - callMsg.From = from - case *common.Address: - callMsg.From = *from +func (c *SimulatedBackendClient) ethGetTransactionReceipt(ctx context.Context, result interface{}, args ...interface{}) error { + if len(args) != 1 { + return fmt.Errorf("SimulatedBackendClient expected 1 arg, got %d for eth_getTransactionReceipt", len(args)) + } + + hash, is := args[0].(common.Hash) + if !is { + return fmt.Errorf("SimulatedBackendClient expected arg to be a hash, got: %T", args[0]) + } + + receipt, err := c.b.TransactionReceipt(ctx, hash) + if err != nil { + return err + } + + // strongly typing the result here has the consequence of not being flexible in + // custom types where a real-world RPC client would allow for custom types with + // custom marshalling. + switch typed := result.(type) { + case *types.Receipt: + *typed = *receipt + case *evmtypes.Receipt: + *typed = *evmtypes.FromGethReceipt(receipt) default: - panic("unexpected type of 'from' parameter") + return fmt.Errorf("SimulatedBackendClient expected return type of *evmtypes.Receipt for eth_getTransactionReceipt, got type %T", result) + } + + return nil +} + +func (c *SimulatedBackendClient) ethGetBlockByNumber(ctx context.Context, result interface{}, args ...interface{}) error { + if len(args) != 2 { + return fmt.Errorf("SimulatedBackendClient expected 2 args, got %d for eth_getBlockByNumber", len(args)) + } + + blockNumOrTag, is := args[0].(string) + if !is { + return fmt.Errorf("SimulatedBackendClient expected first arg to be a string for eth_getBlockByNumber, got: %T", args[0]) + } + + _, is = args[1].(bool) + if !is { + return fmt.Errorf("SimulatedBackendClient expected second arg to be a boolean for eth_getBlockByNumber, got: %T", args[1]) + } + + header, err := c.fetchHeader(ctx, blockNumOrTag) + if err != nil { + return err + } + + switch res := result.(type) { + case *evmtypes.Head: + res.Number = header.Number.Int64() + res.Hash = header.Hash() + res.ParentHash = header.ParentHash + res.Timestamp = time.Unix(int64(header.Time), 0).UTC() + case *evmtypes.Block: + res.Number = header.Number.Int64() + res.Hash = header.Hash() + res.ParentHash = header.ParentHash + res.Timestamp = time.Unix(int64(header.Time), 0).UTC() + default: + return fmt.Errorf("SimulatedBackendClient Unexpected Type %T", res) + } + + return nil +} + +func (c *SimulatedBackendClient) ethCall(ctx context.Context, result interface{}, args ...interface{}) error { + if len(args) != 2 { + return fmt.Errorf("SimulatedBackendClient expected 2 args, got %d for eth_call", len(args)) + } + + params, ok := args[0].(map[string]interface{}) + if !ok { + return fmt.Errorf("SimulatedBackendClient expected first arg to be map[string]interface{} for eth_call, got: %T", args[0]) + } + + if _, err := c.blockNumber(args[1]); err != nil { + return fmt.Errorf("SimulatedBackendClient expected second arg to be the string 'latest' or a *big.Int for eth_call, got: %T", args[1]) + } + + resp, err := c.b.CallContract(ctx, toCallMsg(params), nil /* always latest block on simulated backend */) + if err != nil { + return err + } + + switch typedResult := result.(type) { + case *hexutil.Bytes: + *typedResult = append(*typedResult, resp...) + + if !bytes.Equal(*typedResult, resp) { + return fmt.Errorf("SimulatedBackendClient was passed a non-empty array, or failed to copy answer. Expected %x = %x", *typedResult, resp) + } + case *string: + *typedResult = hexutil.Encode(resp) + default: + return fmt.Errorf("SimulatedBackendClient unexpected type %T", result) + } + + return nil +} + +func (c *SimulatedBackendClient) ethGetHeaderByNumber(ctx context.Context, result interface{}, args ...interface{}) error { + if len(args) != 1 { + return fmt.Errorf("SimulatedBackendClient expected 1 arg, got %d for eth_getHeaderByNumber", len(args)) + } + + blockNumber, err := c.blockNumber(args[0]) + if err != nil { + return fmt.Errorf("SimulatedBackendClient expected first arg to be a string for eth_getHeaderByNumber: %w", err) + } + + header, err := c.b.HeaderByNumber(ctx, blockNumber) + if err != nil { + return err + } + + switch typedResult := result.(type) { + case *types.Header: + *typedResult = *header + default: + return fmt.Errorf("SimulatedBackendClient unexpected Type %T", typedResult) + } + + return nil +} + +func toCallMsg(params map[string]interface{}) ethereum.CallMsg { + var callMsg ethereum.CallMsg + + toAddr, err := interfaceToAddress(params["to"]) + if err != nil { + panic(fmt.Errorf("unexpected 'to' parameter: %s", err)) + } + + callMsg.To = &toAddr + + // from is optional in the standard client; default to 0x when missing + if value, ok := params["from"]; ok { + addr, err := interfaceToAddress(value) + if err != nil { + panic(fmt.Errorf("unexpected 'from' parameter: %s", err)) + } + + callMsg.From = addr + } else { + callMsg.From = common.HexToAddress("0x") } switch data := params["data"].(type) { @@ -690,7 +653,7 @@ func toCallMsg(params map[string]interface{}) ethereum.CallMsg { case []byte: callMsg.Data = data default: - panic("unexpected type of 'data' parameter") + panic("unexpected type of 'data' parameter; try hexutil.Bytes, []byte, or nil") } if value, ok := params["value"].(*big.Int); ok { @@ -708,23 +671,23 @@ func toCallMsg(params map[string]interface{}) ethereum.CallMsg { return callMsg } -func (c *SimulatedBackendClient) IsL2() bool { - return false -} +func interfaceToAddress(value interface{}) (common.Address, error) { + switch v := value.(type) { + case common.Address: + return v, nil + case string: + if ok := common.IsHexAddress(v); !ok { + return common.Address{}, fmt.Errorf("string not formatted as a hex encoded evm address") + } -func (c *SimulatedBackendClient) fetchHeader(ctx context.Context, blockNumOrTag string) (*types.Header, error) { - switch blockNumOrTag { - case rpc.SafeBlockNumber.String(): - return c.b.Blockchain().CurrentSafeBlock(), nil - case rpc.LatestBlockNumber.String(): - return c.b.Blockchain().CurrentHeader(), nil - case rpc.FinalizedBlockNumber.String(): - return c.b.Blockchain().CurrentFinalBlock(), nil - default: - blockNum, ok := new(big.Int).SetString(blockNumOrTag, 0) - if !ok { - return nil, fmt.Errorf("error while converting block number string: %s to big.Int ", blockNumOrTag) + return common.HexToAddress(v), nil + case *big.Int: + if v.Uint64() > 0 || len(v.Bytes()) > 20 { + return common.Address{}, fmt.Errorf("invalid *big.Int; value must be larger than 0 with a byte length <= 20") } - return c.b.HeaderByNumber(ctx, blockNum) + + return common.BigToAddress(v), nil + default: + return common.Address{}, fmt.Errorf("unrecognized value type for converting value to common.Address; use hex encoded string, *big.Int, or common.Address") } } diff --git a/core/chains/evm/config/chain_scoped.go b/core/chains/evm/config/chain_scoped.go index 7971a18d4db..fb6df26b1ad 100644 --- a/core/chains/evm/config/chain_scoped.go +++ b/core/chains/evm/config/chain_scoped.go @@ -9,13 +9,15 @@ import ( ocr "github.com/smartcontractkit/libocr/offchainreporting" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + commonconfig "github.com/smartcontractkit/chainlink/v2/common/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" - gencfg "github.com/smartcontractkit/chainlink/v2/core/config" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/config" ) -func NewTOMLChainScopedConfig(appCfg gencfg.AppConfig, tomlConfig *toml.EVMConfig, lggr logger.Logger) *ChainScoped { +func NewTOMLChainScopedConfig(appCfg config.AppConfig, tomlConfig *toml.EVMConfig, lggr logger.Logger) *ChainScoped { return &ChainScoped{ AppConfig: appCfg, evmConfig: &evmConfig{c: tomlConfig}, @@ -24,7 +26,7 @@ func NewTOMLChainScopedConfig(appCfg gencfg.AppConfig, tomlConfig *toml.EVMConfi // ChainScoped implements config.ChainScopedConfig with a gencfg.BasicConfig and EVMConfig. type ChainScoped struct { - gencfg.AppConfig + config.AppConfig lggr logger.Logger evmConfig *evmConfig @@ -140,11 +142,11 @@ func (e *evmConfig) BlockEmissionIdleWarningThreshold() time.Duration { return e.c.NoNewHeadsThreshold.Duration() } -func (e *evmConfig) ChainType() gencfg.ChainType { +func (e *evmConfig) ChainType() commonconfig.ChainType { if e.c.ChainType == nil { return "" } - return gencfg.ChainType(*e.c.ChainType) + return commonconfig.ChainType(*e.c.ChainType) } func (e *evmConfig) ChainID() *big.Int { diff --git a/core/chains/evm/config/chain_scoped_gas_estimator.go b/core/chains/evm/config/chain_scoped_gas_estimator.go index cc28ce75f03..0c52cf0a468 100644 --- a/core/chains/evm/config/chain_scoped_gas_estimator.go +++ b/core/chains/evm/config/chain_scoped_gas_estimator.go @@ -3,7 +3,7 @@ package config import ( gethcommon "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" ) diff --git a/core/chains/evm/config/chain_scoped_ocr.go b/core/chains/evm/config/chain_scoped_ocr.go index 0cdec34e388..f8e171cf632 100644 --- a/core/chains/evm/config/chain_scoped_ocr.go +++ b/core/chains/evm/config/chain_scoped_ocr.go @@ -25,3 +25,11 @@ func (o *ocrConfig) ObservationGracePeriod() time.Duration { func (o *ocrConfig) DatabaseTimeout() time.Duration { return o.c.DatabaseTimeout.Duration() } + +func (o *ocrConfig) DeltaCOverride() time.Duration { + return o.c.DeltaCOverride.Duration() +} + +func (o *ocrConfig) DeltaCJitterOverride() time.Duration { + return o.c.DeltaCJitterOverride.Duration() +} diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index f8ec030969e..33e2c85eee5 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -6,7 +6,9 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/v2/core/assets" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + commonconfig "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/config" ) @@ -24,7 +26,7 @@ type EVM interface { BlockBackfillSkip() bool BlockEmissionIdleWarningThreshold() time.Duration ChainID() *big.Int - ChainType() config.ChainType + ChainType() commonconfig.ChainType FinalityDepth() uint32 FinalityTagEnabled() bool FlagsContractAddress() string @@ -32,7 +34,7 @@ type EVM interface { LogBackfillBatchSize() uint32 LogKeepBlocksDepth() uint32 LogPollInterval() time.Duration - MinContractPayment() *assets.Link + MinContractPayment() *commonassets.Link MinIncomingConfirmations() uint32 NonceAutoSync() bool OperatorFactoryAddress() string @@ -48,6 +50,8 @@ type OCR interface { ContractTransmitterTransmitTimeout() time.Duration ObservationGracePeriod() time.Duration DatabaseTimeout() time.Duration + DeltaCOverride() time.Duration + DeltaCJitterOverride() time.Duration } type OCR2 interface { diff --git a/core/chains/evm/config/config_test.go b/core/chains/evm/config/config_test.go index 10984d45d1e..d34d1eae63e 100644 --- a/core/chains/evm/config/config_test.go +++ b/core/chains/evm/config/config_test.go @@ -11,11 +11,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + commonconfig "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" @@ -402,7 +403,7 @@ func Test_chainScopedConfig_Validate(t *testing.T) { t.Run("arbitrum-estimator", func(t *testing.T) { t.Run("custom", func(t *testing.T) { cfg := configWithChains(t, 0, &toml.Chain{ - ChainType: ptr(string(config.ChainArbitrum)), + ChainType: ptr(string(commonconfig.ChainArbitrum)), GasEstimator: toml.GasEstimator{ Mode: ptr("BlockHistory"), }, @@ -423,7 +424,7 @@ func Test_chainScopedConfig_Validate(t *testing.T) { t.Run("testnet", func(t *testing.T) { cfg := configWithChains(t, 421611, &toml.Chain{ GasEstimator: toml.GasEstimator{ - Mode: ptr("L2Suggested"), + Mode: ptr("SuggestedPrice"), }, }) assert.NoError(t, cfg.Validate()) @@ -433,7 +434,7 @@ func Test_chainScopedConfig_Validate(t *testing.T) { t.Run("optimism-estimator", func(t *testing.T) { t.Run("custom", func(t *testing.T) { cfg := configWithChains(t, 0, &toml.Chain{ - ChainType: ptr(string(config.ChainOptimismBedrock)), + ChainType: ptr(string(commonconfig.ChainOptimismBedrock)), GasEstimator: toml.GasEstimator{ Mode: ptr("BlockHistory"), }, diff --git a/core/chains/evm/config/mocks/gas_estimator.go b/core/chains/evm/config/mocks/gas_estimator.go index 8cb54132f58..6260b3cd501 100644 --- a/core/chains/evm/config/mocks/gas_estimator.go +++ b/core/chains/evm/config/mocks/gas_estimator.go @@ -4,7 +4,7 @@ package mocks import ( common "github.com/ethereum/go-ethereum/common" - assets "github.com/smartcontractkit/chainlink/v2/core/assets" + assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" config "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index cf2cde460e5..9e51d5be790 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -12,12 +12,13 @@ import ( "go.uber.org/multierr" "gopkg.in/guregu/null.v4" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/common/config" "github.com/smartcontractkit/chainlink/v2/core/chains" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/core/store/models" @@ -106,7 +107,7 @@ func (cs EVMConfigs) totalChains() int { } return total } -func (cs EVMConfigs) Chains(ids ...relay.ChainID) (r []relaytypes.ChainStatus, total int, err error) { +func (cs EVMConfigs) Chains(ids ...relay.ChainID) (r []commontypes.ChainStatus, total int, err error) { total = cs.totalChains() for _, ch := range cs { if ch == nil { @@ -125,7 +126,7 @@ func (cs EVMConfigs) Chains(ids ...relay.ChainID) (r []relaytypes.ChainStatus, t continue } } - ch2 := relaytypes.ChainStatus{ + ch2 := commontypes.ChainStatus{ ID: ch.ChainID.String(), Enabled: ch.IsEnabled(), } @@ -149,7 +150,7 @@ func (cs EVMConfigs) Node(name string) (types.Node, error) { return types.Node{}, fmt.Errorf("node %s: %w", name, chains.ErrNotFound) } -func (cs EVMConfigs) NodeStatus(name string) (relaytypes.NodeStatus, error) { +func (cs EVMConfigs) NodeStatus(name string) (commontypes.NodeStatus, error) { for i := range cs { for _, n := range cs[i].Nodes { if n.Name != nil && *n.Name == name { @@ -157,7 +158,7 @@ func (cs EVMConfigs) NodeStatus(name string) (relaytypes.NodeStatus, error) { } } } - return relaytypes.NodeStatus{}, fmt.Errorf("node %s: %w", name, chains.ErrNotFound) + return commontypes.NodeStatus{}, fmt.Errorf("node %s: %w", name, chains.ErrNotFound) } func legacyNode(n *Node, chainID *utils.Big) (v2 types.Node) { @@ -178,13 +179,13 @@ func legacyNode(n *Node, chainID *utils.Big) (v2 types.Node) { return } -func nodeStatus(n *Node, chainID relay.ChainID) (relaytypes.NodeStatus, error) { - var s relaytypes.NodeStatus +func nodeStatus(n *Node, chainID relay.ChainID) (commontypes.NodeStatus, error) { + var s commontypes.NodeStatus s.ChainID = chainID s.Name = *n.Name b, err := toml.Marshal(n) if err != nil { - return relaytypes.NodeStatus{}, err + return commontypes.NodeStatus{}, err } s.Config = string(b) return s, nil @@ -219,7 +220,7 @@ func (cs EVMConfigs) Nodes(chainID relay.ChainID) (ns []types.Node, err error) { return } -func (cs EVMConfigs) NodeStatuses(chainIDs ...relay.ChainID) (ns []relaytypes.NodeStatus, err error) { +func (cs EVMConfigs) NodeStatuses(chainIDs ...relay.ChainID) (ns []commontypes.NodeStatus, err error) { if len(chainIDs) == 0 { for i := range cs { for _, n := range cs[i].Nodes { @@ -354,7 +355,7 @@ type Chain struct { LogPollInterval *models.Duration LogKeepBlocksDepth *uint32 MinIncomingConfirmations *uint32 - MinContractPayment *assets.Link + MinContractPayment *commonassets.Link NonceAutoSync *bool NoNewHeadsThreshold *models.Duration OperatorFactoryAddress *ethkey.EIP55Address @@ -714,6 +715,8 @@ type OCR struct { ContractConfirmations *uint16 ContractTransmitterTransmitTimeout *models.Duration DatabaseTimeout *models.Duration + DeltaCOverride *models.Duration + DeltaCJitterOverride *models.Duration ObservationGracePeriod *models.Duration } @@ -727,6 +730,12 @@ func (o *OCR) setFrom(f *OCR) { if v := f.DatabaseTimeout; v != nil { o.DatabaseTimeout = v } + if v := f.DeltaCOverride; v != nil { + o.DeltaCOverride = v + } + if v := f.DeltaCJitterOverride; v != nil { + o.DeltaCJitterOverride = v + } if v := f.ObservationGracePeriod; v != nil { o.ObservationGracePeriod = v } diff --git a/core/chains/evm/config/toml/defaults.go b/core/chains/evm/config/toml/defaults.go index 68513383585..d362e9ac3dc 100644 --- a/core/chains/evm/config/toml/defaults.go +++ b/core/chains/evm/config/toml/defaults.go @@ -8,7 +8,7 @@ import ( "slices" "strings" - "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/common/config" "github.com/smartcontractkit/chainlink/v2/core/utils" configutils "github.com/smartcontractkit/chainlink/v2/core/utils/config" ) diff --git a/core/chains/evm/config/toml/defaults/Ethereum_Sepolia.toml b/core/chains/evm/config/toml/defaults/Ethereum_Sepolia.toml index 7b979c2fd8b..27dda602962 100644 --- a/core/chains/evm/config/toml/defaults/Ethereum_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Ethereum_Sepolia.toml @@ -1,5 +1,5 @@ ChainID = '11155111' -LinkContractAddress = '0xb227f007804c16546Bd054dfED2E7A1fD5437678' +LinkContractAddress = '0x779877A7B0D9E8603169DdbD7836e478b4624789' MinContractPayment = '0.1 link' [GasEstimator] diff --git a/core/chains/evm/config/toml/defaults/Fantom_Mainnet.toml b/core/chains/evm/config/toml/defaults/Fantom_Mainnet.toml index f7678c37eb0..c7fb6ba4736 100644 --- a/core/chains/evm/config/toml/defaults/Fantom_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Fantom_Mainnet.toml @@ -9,8 +9,8 @@ RPCBlockQueryDelay = 2 Enabled = true [GasEstimator] -PriceDefault = '15 gwei' -PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +# Fantom network has been slow to include txs at times when using the BlockHistory estimator, and the recommendation is to use SuggestedPrice mode. +Mode = 'SuggestedPrice' [OCR2.Automation] GasLimit = 3800000 \ No newline at end of file diff --git a/core/chains/evm/config/toml/defaults/Fantom_Testnet.toml b/core/chains/evm/config/toml/defaults/Fantom_Testnet.toml index c7a6f72f9aa..1e1aab14681 100644 --- a/core/chains/evm/config/toml/defaults/Fantom_Testnet.toml +++ b/core/chains/evm/config/toml/defaults/Fantom_Testnet.toml @@ -7,8 +7,7 @@ NoNewHeadsThreshold = '0' RPCBlockQueryDelay = 2 [GasEstimator] -PriceDefault = '15 gwei' -PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +Mode = 'SuggestedPrice' [OCR2.Automation] GasLimit = 3800000 \ No newline at end of file diff --git a/core/chains/evm/config/toml/defaults/Klaytn_Mainnet.toml b/core/chains/evm/config/toml/defaults/Klaytn_Mainnet.toml index 36dc04ae96b..c68f03b0446 100644 --- a/core/chains/evm/config/toml/defaults/Klaytn_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Klaytn_Mainnet.toml @@ -5,6 +5,6 @@ NoNewHeadsThreshold = '30s' OCR.ContractConfirmations = 1 [GasEstimator] -Mode = 'L2Suggested' +Mode = 'SuggestedPrice' PriceDefault = '750 gwei' # gwei = ston BumpThreshold = 0 diff --git a/core/chains/evm/config/toml/defaults/Klaytn_Testnet.toml b/core/chains/evm/config/toml/defaults/Klaytn_Testnet.toml index 34b15ca74b1..864aa0fa72a 100644 --- a/core/chains/evm/config/toml/defaults/Klaytn_Testnet.toml +++ b/core/chains/evm/config/toml/defaults/Klaytn_Testnet.toml @@ -5,6 +5,6 @@ NoNewHeadsThreshold = '30s' OCR.ContractConfirmations = 1 [GasEstimator] -Mode = 'L2Suggested' +Mode = 'SuggestedPrice' PriceDefault = '750 gwei' # gwei = ston BumpThreshold = 0 diff --git a/core/chains/evm/config/toml/defaults/Kroma_Mainnet.toml b/core/chains/evm/config/toml/defaults/Kroma_Mainnet.toml new file mode 100644 index 00000000000..55154bf766c --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Kroma_Mainnet.toml @@ -0,0 +1,26 @@ +ChainID = '255' +ChainType = 'kroma' # Kroma is based on the Optimism Bedrock architechture +FinalityDepth = 400 +LogPollInterval = '2s' +NoNewHeadsThreshold = '40s' +MinIncomingConfirmations = 1 + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 400 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 diff --git a/core/chains/evm/config/toml/defaults/Kroma_Sepolia.toml b/core/chains/evm/config/toml/defaults/Kroma_Sepolia.toml new file mode 100644 index 00000000000..643b0556b32 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Kroma_Sepolia.toml @@ -0,0 +1,26 @@ +ChainID = '2358' +ChainType = 'kroma' # Kroma is based on the Optimism Bedrock architechture +FinalityDepth = 400 +LogPollInterval = '2s' +NoNewHeadsThreshold = '40s' +MinIncomingConfirmations = 1 + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 400 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 diff --git a/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml b/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml index 855fef55a75..3e8efa531cc 100644 --- a/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml @@ -8,8 +8,8 @@ NoNewHeadsThreshold = '0' OCR.ContractConfirmations = 1 [GasEstimator] -Mode = 'L2Suggested' -# Metis uses the L2Suggested estimator; we don't want to place any limits on the minimum gas price +Mode = 'SuggestedPrice' +# Metis uses the SuggestedPrice estimator; we don't want to place any limits on the minimum gas price PriceMin = '0' # Never bump gas on metis BumpThreshold = 0 diff --git a/core/chains/evm/config/toml/defaults/Metis_Rinkeby.toml b/core/chains/evm/config/toml/defaults/Metis_Rinkeby.toml index 487cc224852..7d9fec9076f 100644 --- a/core/chains/evm/config/toml/defaults/Metis_Rinkeby.toml +++ b/core/chains/evm/config/toml/defaults/Metis_Rinkeby.toml @@ -9,7 +9,7 @@ OCR.ContractConfirmations = 1 Enabled = true [GasEstimator] -Mode = 'L2Suggested' +Mode = 'SuggestedPrice' PriceMin = '0' BumpThreshold = 0 diff --git a/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml b/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml index 63c08559016..56ed84c7f38 100644 --- a/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml @@ -7,8 +7,8 @@ NoNewHeadsThreshold = '0' OCR.ContractConfirmations = 1 [GasEstimator] -Mode = 'L2Suggested' -# Scroll uses the L2Suggested estimator; we don't want to place any limits on the minimum gas price +Mode = 'SuggestedPrice' +# Scroll uses the SuggestedPrice estimator; we don't want to place any limits on the minimum gas price PriceMin = '0' # Never bump gas on Scroll BumpThreshold = 0 diff --git a/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml b/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml index 5a1a0f9ba7d..af17c4d485e 100644 --- a/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml @@ -7,8 +7,8 @@ NoNewHeadsThreshold = '0' OCR.ContractConfirmations = 1 [GasEstimator] -Mode = 'L2Suggested' -# Scroll uses the L2Suggested estimator; we don't want to place any limits on the minimum gas price +Mode = 'SuggestedPrice' +# Scroll uses the SuggestedPrice estimator; we don't want to place any limits on the minimum gas price PriceMin = '0' # Never bump gas on Scroll BumpThreshold = 0 diff --git a/core/chains/evm/config/toml/defaults/WeMix_Mainnet.toml b/core/chains/evm/config/toml/defaults/WeMix_Mainnet.toml new file mode 100644 index 00000000000..ee50a9844a4 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/WeMix_Mainnet.toml @@ -0,0 +1,14 @@ +ChainID = '1111' +ChainType = 'wemix' +FinalityDepth = 1 +MinIncomingConfirmations = 1 +# WeMix emits a block every 1 second, regardless of transactions +LogPollInterval = '3s' +NoNewHeadsThreshold = '30s' + +[OCR] +ContractConfirmations = 1 + +[GasEstimator] +EIP1559DynamicFees = true +TipCapDefault = '100 gwei' diff --git a/core/chains/evm/config/toml/defaults/WeMix_Testnet.toml b/core/chains/evm/config/toml/defaults/WeMix_Testnet.toml new file mode 100644 index 00000000000..6cdb451eb1d --- /dev/null +++ b/core/chains/evm/config/toml/defaults/WeMix_Testnet.toml @@ -0,0 +1,14 @@ +ChainID = '1112' +ChainType = 'wemix' +FinalityDepth = 1 +MinIncomingConfirmations = 1 +# WeMix emits a block every 1 second, regardless of transactions +LogPollInterval = '3s' +NoNewHeadsThreshold = '30s' + +[OCR] +ContractConfirmations = 1 + +[GasEstimator] +EIP1559DynamicFees = true +TipCapDefault = '100 gwei' diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index a75cfa0bf3b..b19423fd13a 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -64,6 +64,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h' +DeltaCJitterOverride = '1h' ObservationGracePeriod = '1s' [OCR2.Automation] diff --git a/core/chains/evm/config/toml/defaults/zkSync_Goerli.toml b/core/chains/evm/config/toml/defaults/zkSync_Goerli.toml new file mode 100644 index 00000000000..04529a41b81 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/zkSync_Goerli.toml @@ -0,0 +1,14 @@ +ChainID = '280' +ChainType = 'zksync' +FinalityDepth = 1 +LogPollInterval = '5s' +MinIncomingConfirmations = 1 +NoNewHeadsThreshold = '1m' + +[GasEstimator] +LimitDefault = 3_500_000 +PriceMax = 18446744073709551615 +PriceMin = 0 + +[HeadTracker] +HistoryDepth = 5 diff --git a/core/chains/evm/config/toml/defaults/zkSync_Mainnet.toml b/core/chains/evm/config/toml/defaults/zkSync_Mainnet.toml new file mode 100644 index 00000000000..d7808edd15f --- /dev/null +++ b/core/chains/evm/config/toml/defaults/zkSync_Mainnet.toml @@ -0,0 +1,14 @@ +ChainID = '324' +ChainType = 'zksync' +FinalityDepth = 1 +LogPollInterval = '5s' +MinIncomingConfirmations = 1 +NoNewHeadsThreshold = '1m' + +[GasEstimator] +LimitDefault = 3_500_000 +PriceMax = 18446744073709551615 +PriceMin = 0 + +[HeadTracker] +HistoryDepth = 5 diff --git a/core/chains/evm/forwarders/forwarder_manager.go b/core/chains/evm/forwarders/forwarder_manager.go index 0c470e76d8c..eaf0c32afe3 100644 --- a/core/chains/evm/forwarders/forwarder_manager.go +++ b/core/chains/evm/forwarders/forwarder_manager.go @@ -9,7 +9,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmlogpoller "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -17,7 +21,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_forwarder" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_receiver" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/offchain_aggregator_wrapper" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -30,7 +33,7 @@ type Config interface { } type FwdMgr struct { - utils.StartStopOnce + services.StateMachine ORM ORM evmClient evmclient.Client cfg Config @@ -53,7 +56,7 @@ type FwdMgr struct { } func NewFwdMgr(db *sqlx.DB, client evmclient.Client, logpoller evmlogpoller.LogPoller, l logger.Logger, cfg Config, dbConfig pg.QConfig) *FwdMgr { - lggr := logger.Sugared(l.Named("EVMForwarderManager")) + lggr := logger.Sugared(logger.Named(l, "EVMForwarderManager")) fwdMgr := FwdMgr{ logger: lggr, cfg: cfg, @@ -61,9 +64,6 @@ func NewFwdMgr(db *sqlx.DB, client evmclient.Client, logpoller evmlogpoller.LogP ORM: NewORM(db, lggr, dbConfig), logpoller: logpoller, sendersCache: make(map[common.Address][]common.Address), - cacheMu: sync.RWMutex{}, - wg: sync.WaitGroup{}, - latestBlock: 0, } fwdMgr.ctx, fwdMgr.cancel = context.WithCancel(context.Background()) return &fwdMgr diff --git a/core/chains/evm/forwarders/forwarder_manager_test.go b/core/chains/evm/forwarders/forwarder_manager_test.go index 0117c2f2c07..1da638e743d 100644 --- a/core/chains/evm/forwarders/forwarder_manager_test.go +++ b/core/chains/evm/forwarders/forwarder_manager_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -19,10 +20,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_receiver" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/operator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -32,7 +32,7 @@ var GetAuthorisedSendersABI = evmtypes.MustGetABI(authorized_receiver.Authorized var SimpleOracleCallABI = evmtypes.MustGetABI(operator_wrapper.OperatorABI).Methods["getChainlinkToken"] func TestFwdMgr_MaybeForwardTransaction(t *testing.T) { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) evmcfg := evmtest.NewChainScopedConfig(t, cfg) @@ -60,7 +60,7 @@ func TestFwdMgr_MaybeForwardTransaction(t *testing.T) { evmClient := client.NewSimulatedBackendClient(t, ec, testutils.FixtureChainID) lp := logpoller.NewLogPoller(logpoller.NewORM(testutils.FixtureChainID, db, lggr, pgtest.NewQConfig(true)), evmClient, lggr, 100*time.Millisecond, false, 2, 3, 2, 1000) fwdMgr := forwarders.NewFwdMgr(db, evmClient, lp, lggr, evmcfg.EVM(), evmcfg.Database()) - fwdMgr.ORM = forwarders.NewORM(db, logger.TestLogger(t), cfg.Database()) + fwdMgr.ORM = forwarders.NewORM(db, logger.Test(t), cfg.Database()) fwd, err := fwdMgr.ORM.CreateForwarder(forwarderAddr, utils.Big(*testutils.FixtureChainID)) require.NoError(t, err) @@ -91,7 +91,7 @@ func TestFwdMgr_MaybeForwardTransaction(t *testing.T) { } func TestFwdMgr_AccountUnauthorizedToForward_SkipsForwarding(t *testing.T) { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) evmcfg := evmtest.NewChainScopedConfig(t, cfg) @@ -113,7 +113,7 @@ func TestFwdMgr_AccountUnauthorizedToForward_SkipsForwarding(t *testing.T) { evmClient := client.NewSimulatedBackendClient(t, ec, testutils.FixtureChainID) lp := logpoller.NewLogPoller(logpoller.NewORM(testutils.FixtureChainID, db, lggr, pgtest.NewQConfig(true)), evmClient, lggr, 100*time.Millisecond, false, 2, 3, 2, 1000) fwdMgr := forwarders.NewFwdMgr(db, evmClient, lp, lggr, evmcfg.EVM(), evmcfg.Database()) - fwdMgr.ORM = forwarders.NewORM(db, logger.TestLogger(t), cfg.Database()) + fwdMgr.ORM = forwarders.NewORM(db, logger.Test(t), cfg.Database()) _, err = fwdMgr.ORM.CreateForwarder(forwarderAddr, utils.Big(*testutils.FixtureChainID)) require.NoError(t, err) diff --git a/core/chains/evm/forwarders/orm.go b/core/chains/evm/forwarders/orm.go index 287698d22f6..104e2574252 100644 --- a/core/chains/evm/forwarders/orm.go +++ b/core/chains/evm/forwarders/orm.go @@ -4,10 +4,10 @@ import ( "database/sql" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/chains/evm/forwarders/orm_test.go b/core/chains/evm/forwarders/orm_test.go index a3d5c2831fe..ba9664c196a 100644 --- a/core/chains/evm/forwarders/orm_test.go +++ b/core/chains/evm/forwarders/orm_test.go @@ -9,13 +9,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" ) type TestORM struct { @@ -28,7 +28,7 @@ func setupORM(t *testing.T) *TestORM { var ( db = pgtest.NewSqlxDB(t) - lggr = logger.TestLogger(t) + lggr = logger.Test(t) orm = NewORM(db, lggr, pgtest.NewQConfig(true)) ) diff --git a/core/chains/evm/gas/arbitrum_estimator.go b/core/chains/evm/gas/arbitrum_estimator.go index 6c2b5e8b879..ee020f67002 100644 --- a/core/chains/evm/gas/arbitrum_estimator.go +++ b/core/chains/evm/gas/arbitrum_estimator.go @@ -13,12 +13,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -31,11 +31,12 @@ type ethClient interface { CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) } -// arbitrumEstimator is an Estimator which extends l2SuggestedPriceEstimator to use getPricesInArbGas() for gas limit estimation. +// arbitrumEstimator is an Estimator which extends SuggestedPriceEstimator to use getPricesInArbGas() for gas limit estimation. type arbitrumEstimator struct { + services.StateMachine cfg ArbConfig - EvmEstimator // *l2SuggestedPriceEstimator + EvmEstimator // *SuggestedPriceEstimator client ethClient pollPeriod time.Duration @@ -47,17 +48,15 @@ type arbitrumEstimator struct { chForceRefetch chan (chan struct{}) chInitialised chan struct{} - chStop utils.StopChan + chStop services.StopChan chDone chan struct{} - - utils.StartStopOnce } func NewArbitrumEstimator(lggr logger.Logger, cfg ArbConfig, rpcClient rpcClient, ethClient ethClient) EvmEstimator { - lggr = lggr.Named("ArbitrumEstimator") + lggr = logger.Named(lggr, "ArbitrumEstimator") return &arbitrumEstimator{ cfg: cfg, - EvmEstimator: NewL2SuggestedPriceEstimator(lggr, rpcClient), + EvmEstimator: NewSuggestedPriceEstimator(lggr, rpcClient), client: ethClient, pollPeriod: 10 * time.Second, logger: lggr, @@ -91,7 +90,7 @@ func (a *arbitrumEstimator) Close() error { }) } -func (a *arbitrumEstimator) Ready() error { return a.StartStopOnce.Ready() } +func (a *arbitrumEstimator) Ready() error { return a.StateMachine.Ready() } func (a *arbitrumEstimator) HealthReport() map[string]error { hp := map[string]error{a.Name(): a.Healthy()} @@ -100,7 +99,7 @@ func (a *arbitrumEstimator) HealthReport() map[string]error { } // GetLegacyGas estimates both the gas price and the gas limit. -// - Price is delegated to the embedded l2SuggestedPriceEstimator. +// - Price is delegated to the embedded SuggestedPriceEstimator. // - Limit is computed from the dynamic values perL2Tx and perL1CalldataUnit, provided by the getPricesInArbGas() method // of the precompilie contract at ArbGasInfoAddress. perL2Tx is a constant amount of gas, and perL1CalldataUnit is // multiplied by the length of the tx calldata. The sum of these two values plus the original l2GasLimit is returned. diff --git a/core/chains/evm/gas/arbitrum_estimator_test.go b/core/chains/evm/gas/arbitrum_estimator_test.go index b6e299190c5..53f81617988 100644 --- a/core/chains/evm/gas/arbitrum_estimator_test.go +++ b/core/chains/evm/gas/arbitrum_estimator_test.go @@ -14,11 +14,11 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type arbConfig struct { @@ -41,7 +41,7 @@ func TestArbitrumEstimator(t *testing.T) { t.Run("calling GetLegacyGas on unstarted estimator returns error", func(t *testing.T) { rpcClient := mocks.NewRPCClient(t) ethClient := mocks.NewETHClient(t) - o := gas.NewArbitrumEstimator(logger.TestLogger(t), &arbConfig{}, rpcClient, ethClient) + o := gas.NewArbitrumEstimator(logger.Test(t), &arbConfig{}, rpcClient, ethClient) _, _, err := o.GetLegacyGas(testutils.Context(t), calldata, gasLimit, maxGasPrice) assert.EqualError(t, err, "estimator is not started") }) @@ -65,7 +65,7 @@ func TestArbitrumEstimator(t *testing.T) { assert.Equal(t, big.NewInt(-1), blockNumber) }).Return(zeros.Bytes(), nil) - o := gas.NewArbitrumEstimator(logger.TestLogger(t), &arbConfig{v: maxGasLimit}, rpcClient, ethClient) + o := gas.NewArbitrumEstimator(logger.Test(t), &arbConfig{v: maxGasLimit}, rpcClient, ethClient) require.NoError(t, o.Start(testutils.Context(t))) t.Cleanup(func() { assert.NoError(t, o.Close()) }) gasPrice, chainSpecificGasLimit, err := o.GetLegacyGas(testutils.Context(t), calldata, gasLimit, maxGasPrice) @@ -78,7 +78,7 @@ func TestArbitrumEstimator(t *testing.T) { t.Run("gas price is lower than user specified max gas price", func(t *testing.T) { client := mocks.NewRPCClient(t) ethClient := mocks.NewETHClient(t) - o := gas.NewArbitrumEstimator(logger.TestLogger(t), &arbConfig{}, client, ethClient) + o := gas.NewArbitrumEstimator(logger.Test(t), &arbConfig{}, client, ethClient) client.On("CallContext", mock.Anything, mock.Anything, "eth_gasPrice").Return(nil).Run(func(args mock.Arguments) { res := args.Get(1).(*hexutil.Big) @@ -104,7 +104,7 @@ func TestArbitrumEstimator(t *testing.T) { t.Run("gas price is lower than global max gas price", func(t *testing.T) { ethClient := mocks.NewETHClient(t) client := mocks.NewRPCClient(t) - o := gas.NewArbitrumEstimator(logger.TestLogger(t), &arbConfig{}, client, ethClient) + o := gas.NewArbitrumEstimator(logger.Test(t), &arbConfig{}, client, ethClient) client.On("CallContext", mock.Anything, mock.Anything, "eth_gasPrice").Return(nil).Run(func(args mock.Arguments) { res := args.Get(1).(*hexutil.Big) @@ -129,15 +129,15 @@ func TestArbitrumEstimator(t *testing.T) { t.Run("calling BumpLegacyGas always returns error", func(t *testing.T) { rpcClient := mocks.NewRPCClient(t) ethClient := mocks.NewETHClient(t) - o := gas.NewArbitrumEstimator(logger.TestLogger(t), &arbConfig{}, rpcClient, ethClient) + o := gas.NewArbitrumEstimator(logger.Test(t), &arbConfig{}, rpcClient, ethClient) _, _, err := o.BumpLegacyGas(testutils.Context(t), assets.NewWeiI(42), gasLimit, assets.NewWeiI(10), nil) - assert.EqualError(t, err, "bump gas is not supported for this l2") + assert.EqualError(t, err, "bump gas is not supported for this chain") }) t.Run("calling GetLegacyGas on started estimator if initial call failed returns error", func(t *testing.T) { client := mocks.NewRPCClient(t) ethClient := mocks.NewETHClient(t) - o := gas.NewArbitrumEstimator(logger.TestLogger(t), &arbConfig{}, client, ethClient) + o := gas.NewArbitrumEstimator(logger.Test(t), &arbConfig{}, client, ethClient) client.On("CallContext", mock.Anything, mock.Anything, "eth_gasPrice").Return(errors.New("kaboom")) ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) { @@ -152,7 +152,7 @@ func TestArbitrumEstimator(t *testing.T) { t.Cleanup(func() { assert.NoError(t, o.Close()) }) _, _, err := o.GetLegacyGas(testutils.Context(t), calldata, gasLimit, maxGasPrice) - assert.EqualError(t, err, "failed to estimate l2 gas; gas price not set") + assert.EqualError(t, err, "failed to estimate gas; gas price not set") }) t.Run("limit computes", func(t *testing.T) { @@ -180,7 +180,7 @@ func TestArbitrumEstimator(t *testing.T) { assert.Equal(t, big.NewInt(-1), blockNumber) }).Return(b.Bytes(), nil) - o := gas.NewArbitrumEstimator(logger.TestLogger(t), &arbConfig{v: maxGasLimit}, rpcClient, ethClient) + o := gas.NewArbitrumEstimator(logger.Test(t), &arbConfig{v: maxGasLimit}, rpcClient, ethClient) require.NoError(t, o.Start(testutils.Context(t))) t.Cleanup(func() { assert.NoError(t, o.Close()) }) gasPrice, chainSpecificGasLimit, err := o.GetLegacyGas(testutils.Context(t), calldata, gasLimit, maxGasPrice) @@ -215,7 +215,7 @@ func TestArbitrumEstimator(t *testing.T) { assert.Equal(t, big.NewInt(-1), blockNumber) }).Return(b.Bytes(), nil) - o := gas.NewArbitrumEstimator(logger.TestLogger(t), &arbConfig{v: maxGasLimit}, rpcClient, ethClient) + o := gas.NewArbitrumEstimator(logger.Test(t), &arbConfig{v: maxGasLimit}, rpcClient, ethClient) require.NoError(t, o.Start(testutils.Context(t))) t.Cleanup(func() { assert.NoError(t, o.Close()) }) gasPrice, chainSpecificGasLimit, err := o.GetLegacyGas(testutils.Context(t), calldata, gasLimit, maxGasPrice) diff --git a/core/chains/evm/gas/block_history_estimator.go b/core/chains/evm/gas/block_history_estimator.go index 14b18ad66ba..64dc331f657 100644 --- a/core/chains/evm/gas/block_history_estimator.go +++ b/core/chains/evm/gas/block_history_estimator.go @@ -15,13 +15,15 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/common/config" commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/config" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/v2/core/utils/mathutil" ) @@ -96,7 +98,7 @@ type estimatorGasEstimatorConfig interface { //go:generate mockery --quiet --name Config --output ./mocks/ --case=underscore type ( BlockHistoryEstimator struct { - utils.StartStopOnce + services.StateMachine ethClient evmclient.Client chainID big.Int config chainConfig @@ -141,7 +143,7 @@ func NewBlockHistoryEstimator(lggr logger.Logger, ethClient evmclient.Client, cf wg: new(sync.WaitGroup), ctx: ctx, ctxCancel: cancel, - logger: logger.Sugared(lggr.Named("BlockHistoryEstimator")), + logger: logger.Sugared(logger.Named(lggr, "BlockHistoryEstimator")), } return b @@ -162,7 +164,7 @@ func (b *BlockHistoryEstimator) setLatest(head *evmtypes.Head) { if baseFee := head.BaseFeePerGas; baseFee != nil { promBlockHistoryEstimatorCurrentBaseFee.WithLabelValues(b.chainID.String()).Set(float64(baseFee.Int64())) } - b.logger.Debugw("Set latest block", "blockNum", head.Number, "blockHash", head.Hash, "baseFee", head.BaseFeePerGas) + b.logger.Debugw("Set latest block", "blockNum", head.Number, "blockHash", head.Hash, "baseFee", head.BaseFeePerGas, "baseFeeWei", head.BaseFeePerGas.ToInt()) b.latestMu.Lock() defer b.latestMu.Unlock() b.latest = head @@ -196,7 +198,7 @@ func (b *BlockHistoryEstimator) getBlocks() []evmtypes.Block { // 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") + logger.Trace(b.logger, "Starting") if b.bhConfig.CheckInclusionBlocks() > 0 { b.logger.Infof("Inclusion checking enabled, bumping will be prevented on transactions that have been priced above the %d percentile for %d blocks", b.bhConfig.CheckInclusionPercentile(), b.bhConfig.CheckInclusionBlocks()) @@ -226,7 +228,7 @@ func (b *BlockHistoryEstimator) Start(ctx context.Context) error { b.wg.Add(1) go b.runLoop() - b.logger.Trace("Started") + logger.Trace(b.logger, "Started") return nil }) } @@ -289,7 +291,7 @@ func (b *BlockHistoryEstimator) BumpLegacyGas(_ context.Context, originalGasPric if b.bhConfig.CheckInclusionBlocks() > 0 { if err = b.checkConnectivity(attempts); err != nil { if errors.Is(err, commonfee.ErrConnectivity) { - b.logger.Criticalw(BumpingHaltedLabel, "err", err) + logger.Criticalw(b.logger, BumpingHaltedLabel, "err", err) b.SvcErrBuffer.Append(err) promBlockHistoryEstimatorConnectivityFailureCount.WithLabelValues(b.chainID.String(), "legacy").Inc() } @@ -465,7 +467,7 @@ func (b *BlockHistoryEstimator) BumpDynamicFee(_ context.Context, originalFee Dy if b.bhConfig.CheckInclusionBlocks() > 0 { if err = b.checkConnectivity(attempts); err != nil { if errors.Is(err, commonfee.ErrConnectivity) { - b.logger.Criticalw(BumpingHaltedLabel, "err", err) + logger.Criticalw(b.logger, BumpingHaltedLabel, "err", err) b.SvcErrBuffer.Append(err) promBlockHistoryEstimatorConnectivityFailureCount.WithLabelValues(b.chainID.String(), "eip1559").Inc() } @@ -506,7 +508,7 @@ func (b *BlockHistoryEstimator) FetchBlocksAndRecalculate(ctx context.Context, h func (b *BlockHistoryEstimator) Recalculate(head *evmtypes.Head) { percentile := int(b.bhConfig.TransactionPercentile()) - lggr := b.logger.With("head", head) + lggr := logger.With(b.logger, "head", head) blockHistory := b.getBlocks() if len(blockHistory) == 0 { @@ -628,9 +630,9 @@ func (b *BlockHistoryEstimator) FetchBlocks(ctx context.Context, head *evmtypes. reqs = append(reqs, req) } - lggr := b.logger.With("head", head) + lggr := logger.With(b.logger, "head", head) - lggr.Tracew(fmt.Sprintf("Fetching %v blocks (%v in local history)", len(reqs), len(blocks)), "n", len(reqs), "inHistory", len(blocks), "blockNum", head.Number) + logger.Tracew(lggr, fmt.Sprintf("Fetching %v blocks (%v in local history)", len(reqs), len(blocks)), "n", len(reqs), "inHistory", len(blocks), "blockNum", head.Number) if err := b.batchFetch(ctx, reqs); err != nil { return err } @@ -711,7 +713,7 @@ func (b *BlockHistoryEstimator) batchFetch(ctx context.Context, reqs []rpc.Batch j = len(reqs) } - b.logger.Tracew(fmt.Sprintf("Batch fetching blocks %v thru %v", HexToInt64(reqs[i].Args[0]), HexToInt64(reqs[j-1].Args[0]))) + logger.Tracew(b.logger, fmt.Sprintf("Batch fetching blocks %v thru %v", HexToInt64(reqs[i].Args[0]), HexToInt64(reqs[j-1].Args[0]))) err := b.ethClient.BatchCallContext(ctx, reqs[i:j]) if errors.Is(err, context.DeadlineExceeded) { diff --git a/core/chains/evm/gas/block_history_estimator_test.go b/core/chains/evm/gas/block_history_estimator_test.go index 7f4d157e37a..d3edf212b6a 100644 --- a/core/chains/evm/gas/block_history_estimator_test.go +++ b/core/chains/evm/gas/block_history_estimator_test.go @@ -18,15 +18,16 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/common/config" commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -41,7 +42,7 @@ func newBlockHistoryConfig() *gas.MockBlockHistoryConfig { } func newBlockHistoryEstimatorWithChainID(t *testing.T, c evmclient.Client, cfg gas.Config, gCfg gas.GasEstimatorConfig, bhCfg gas.BlockHistoryConfig, cid big.Int) gas.EvmEstimator { - return gas.NewBlockHistoryEstimator(logger.TestLogger(t), c, cfg, gCfg, bhCfg, cid) + return gas.NewBlockHistoryEstimator(logger.Test(t), c, cfg, gCfg, bhCfg, cid) } func newBlockHistoryEstimator(t *testing.T, c evmclient.Client, cfg gas.Config, gCfg gas.GasEstimatorConfig, bhCfg gas.BlockHistoryConfig) *gas.BlockHistoryEstimator { @@ -1299,48 +1300,69 @@ func TestBlockHistoryEstimator_IsUsable(t *testing.T) { } t.Run("returns false if transaction has 0 gas limit", func(t *testing.T) { tx := evmtypes.Transaction{Type: 0x0, GasPrice: assets.NewWeiI(10), GasLimit: 0, Hash: utils.NewHash()} - assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) + assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) }) t.Run("returns false if transaction gas limit is nil and tx type is 0x0", func(t *testing.T) { tx := evmtypes.Transaction{Type: 0x0, GasPrice: nil, GasLimit: 42, Hash: utils.NewHash()} - assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) + assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) }) t.Run("returns false if transaction is of type 0x7e only on Optimism", func(t *testing.T) { cfg.ChainTypeF = "optimismBedrock" tx := evmtypes.Transaction{Type: 0x7e, GasPrice: assets.NewWeiI(10), GasLimit: 42, Hash: utils.NewHash()} - assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) + assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) cfg.ChainTypeF = "" - assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) + assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) }) t.Run("returns false if transaction is of type 0x7c or 0x7b only on Celo", func(t *testing.T) { cfg.ChainTypeF = "celo" tx := evmtypes.Transaction{Type: 0x7c, GasPrice: assets.NewWeiI(10), GasLimit: 42, Hash: utils.NewHash()} - assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) + assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) tx2 := evmtypes.Transaction{Type: 0x7b, GasPrice: assets.NewWeiI(10), GasLimit: 42, Hash: utils.NewHash()} - assert.Equal(t, false, bhe.IsUsable(tx2, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) + assert.Equal(t, false, bhe.IsUsable(tx2, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) cfg.ChainTypeF = "" - assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) - assert.Equal(t, true, bhe.IsUsable(tx2, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) + assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) + assert.Equal(t, true, bhe.IsUsable(tx2, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) + }) + + t.Run("returns false if transaction is of type 0x16 only on WeMix", func(t *testing.T) { + cfg.ChainTypeF = "wemix" + tx := evmtypes.Transaction{Type: 0x16, GasPrice: assets.NewWeiI(10), GasLimit: 42, Hash: utils.NewHash()} + assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) }) t.Run("returns false if transaction has base fee higher than the gas price only on Celo", func(t *testing.T) { cfg.ChainTypeF = "celo" tx := evmtypes.Transaction{Type: 0x0, GasPrice: assets.NewWeiI(10), GasLimit: 42, Hash: utils.NewHash()} - assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) + assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) tx2 := evmtypes.Transaction{Type: 0x2, MaxPriorityFeePerGas: assets.NewWeiI(200), MaxFeePerGas: assets.NewWeiI(250), GasPrice: assets.NewWeiI(50), GasLimit: 42, Hash: utils.NewHash()} - assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) + assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) + + cfg.ChainTypeF = "" + assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) + assert.Equal(t, true, bhe.IsUsable(tx2, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) + }) + + t.Run("returns false if transaction is of type 0x71 or 0xff only on zkSync", func(t *testing.T) { + cfg.ChainTypeF = string(config.ChainZkSync) + tx := evmtypes.Transaction{Type: 0x71, GasPrice: assets.NewWeiI(10), GasLimit: 42, Hash: utils.NewHash()} + assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) + + tx.Type = 0x02 + assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) + + tx.Type = 0xff + assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) cfg.ChainTypeF = "" - assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) - assert.Equal(t, true, bhe.IsUsable(tx2, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t))) + assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.Test(t))) }) } @@ -2018,7 +2040,7 @@ func TestBlockHistoryEstimator_CheckConnectivity(t *testing.T) { cfg := gas.NewMockConfig() bhCfg := newBlockHistoryConfig() bhCfg.CheckInclusionBlocksF = uint16(4) - lggr, obs := logger.TestLoggerObserved(t, zapcore.DebugLevel) + lggr, obs := logger.TestObserved(t, zapcore.DebugLevel) geCfg := &gas.MockGasEstimatorConfig{} geCfg.EIP1559DynamicFeesF = false @@ -2359,7 +2381,7 @@ func TestBlockHistoryEstimator_Bumps(t *testing.T) { gasPrice, gasLimit, err := bhe.BumpLegacyGas(testutils.Context(t), assets.NewWeiI(42), 100000, maxGasPrice, nil) require.NoError(t, err) - expectedGasPrice, expectedGasLimit, err := gas.BumpLegacyGasPriceOnly(geCfg, logger.TestLogger(t), nil, assets.NewWeiI(42), 100000, maxGasPrice) + expectedGasPrice, expectedGasLimit, err := gas.BumpLegacyGasPriceOnly(geCfg, logger.TestSugared(t), nil, assets.NewWeiI(42), 100000, maxGasPrice) require.NoError(t, err) assert.Equal(t, expectedGasLimit, gasLimit) @@ -2373,7 +2395,7 @@ func TestBlockHistoryEstimator_Bumps(t *testing.T) { massive := assets.NewWeiI(100000000000000) gas.SetGasPrice(bhe, massive) - expectedGasPrice, expectedGasLimit, err := gas.BumpLegacyGasPriceOnly(geCfg, logger.TestLogger(t), massive, assets.NewWeiI(42), 100000, maxGasPrice) + expectedGasPrice, expectedGasLimit, err := gas.BumpLegacyGasPriceOnly(geCfg, logger.TestSugared(t), massive, assets.NewWeiI(42), 100000, maxGasPrice) require.NoError(t, err) assert.Equal(t, expectedGasLimit, gasLimit) diff --git a/core/chains/evm/gas/chain_specific.go b/core/chains/evm/gas/chain_specific.go index cd38f49ee0b..c989cd0aa98 100644 --- a/core/chains/evm/gas/chain_specific.go +++ b/core/chains/evm/gas/chain_specific.go @@ -1,9 +1,9 @@ package gas import ( - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/config" ) // chainSpecificIsUsable allows for additional logic specific to a particular @@ -19,7 +19,7 @@ func chainSpecificIsUsable(tx evmtypes.Transaction, baseFee *assets.Wei, chainTy return false } } - if chainType == config.ChainOptimismBedrock { + if chainType == config.ChainOptimismBedrock || chainType == config.ChainKroma { // This is a special deposit transaction type introduced in Bedrock upgrade. // This is a system transaction that it will occur at least one time per block. // We should discard this type before even processing it to avoid flooding the @@ -42,5 +42,19 @@ func chainSpecificIsUsable(tx evmtypes.Transaction, baseFee *assets.Wei, chainTy return false } } + if chainType == config.ChainWeMix { + // WeMix specific transaction types that enables fee delegation. + // https://docs.wemix.com/v/en/design/fee-delegation + if tx.Type == 0x16 { + return false + } + } + if chainType == config.ChainZkSync { + // zKSync specific type for contract deployment & priority transactions + // https://era.zksync.io/docs/reference/concepts/transactions.html#eip-712-0x71 + if tx.Type == 0x71 || tx.Type == 0xff { + return false + } + } return true } diff --git a/core/chains/evm/gas/cmd/arbgas/main.go b/core/chains/evm/gas/cmd/arbgas/main.go index 4ceeb0009e7..e4fb1b4e882 100644 --- a/core/chains/evm/gas/cmd/arbgas/main.go +++ b/core/chains/evm/gas/cmd/arbgas/main.go @@ -9,12 +9,11 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" - "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink-common/pkg/logger" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func main() { @@ -22,9 +21,10 @@ func main() { log.Fatal("Expected one URL argument but got", l-1) } url := os.Args[1] - lggr, sync := logger.NewLogger() - defer func() { _ = sync() }() - lggr.SetLogLevel(zapcore.DebugLevel) + lggr, err := logger.New() + if err != nil { + log.Fatal("Failed to create logger:", err) + } ctx := context.Background() withEstimator(ctx, logger.Sugared(lggr), url, func(e gas.EvmEstimator) { diff --git a/core/chains/evm/gas/fixed_price_estimator.go b/core/chains/evm/gas/fixed_price_estimator.go index a45df741a27..4d9f45a1bd4 100644 --- a/core/chains/evm/gas/fixed_price_estimator.go +++ b/core/chains/evm/gas/fixed_price_estimator.go @@ -5,11 +5,11 @@ import ( "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) var _ EvmEstimator = (*fixedPriceEstimator)(nil) @@ -45,7 +45,7 @@ type fixedPriceEstimatorBlockHistoryConfig interface { // NewFixedPriceEstimator returns a new "FixedPrice" estimator which will // always use the config default values for gas prices and limits func NewFixedPriceEstimator(cfg fixedPriceEstimatorConfig, bhCfg fixedPriceEstimatorBlockHistoryConfig, lggr logger.Logger) EvmEstimator { - return &fixedPriceEstimator{cfg, bhCfg, logger.Sugared(lggr.Named("FixedPriceEstimator"))} + return &fixedPriceEstimator{cfg, bhCfg, logger.Sugared(logger.Named(lggr, "FixedPriceEstimator"))} } func (f *fixedPriceEstimator) Start(context.Context) error { diff --git a/core/chains/evm/gas/fixed_price_estimator_test.go b/core/chains/evm/gas/fixed_price_estimator_test.go index a6cb313e5e2..968275ace48 100644 --- a/core/chains/evm/gas/fixed_price_estimator_test.go +++ b/core/chains/evm/gas/fixed_price_estimator_test.go @@ -6,10 +6,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type blockHistoryConfig struct { @@ -26,7 +26,7 @@ func Test_FixedPriceEstimator(t *testing.T) { t.Run("GetLegacyGas returns EvmGasPriceDefault from config, with multiplier applied", func(t *testing.T) { config := &gas.MockGasEstimatorConfig{} - f := gas.NewFixedPriceEstimator(config, &blockHistoryConfig{}, logger.TestLogger(t)) + f := gas.NewFixedPriceEstimator(config, &blockHistoryConfig{}, logger.Test(t)) config.PriceDefaultF = assets.NewWeiI(42) config.LimitMultiplierF = float32(1.1) @@ -43,7 +43,7 @@ func Test_FixedPriceEstimator(t *testing.T) { config.PriceDefaultF = assets.NewWeiI(42) config.LimitMultiplierF = float32(1.1) config.PriceMaxF = assets.NewWeiI(35) - f := gas.NewFixedPriceEstimator(config, &blockHistoryConfig{}, logger.TestLogger(t)) + f := gas.NewFixedPriceEstimator(config, &blockHistoryConfig{}, logger.Test(t)) gasPrice, gasLimit, err := f.GetLegacyGas(testutils.Context(t), nil, 100000, assets.NewWeiI(30)) require.NoError(t, err) @@ -57,7 +57,7 @@ func Test_FixedPriceEstimator(t *testing.T) { config.LimitMultiplierF = float32(1.1) config.PriceMaxF = assets.NewWeiI(20) - f := gas.NewFixedPriceEstimator(config, &blockHistoryConfig{}, logger.TestLogger(t)) + f := gas.NewFixedPriceEstimator(config, &blockHistoryConfig{}, logger.Test(t)) gasPrice, gasLimit, err := f.GetLegacyGas(testutils.Context(t), nil, 100000, assets.NewWeiI(30)) require.NoError(t, err) assert.Equal(t, 110000, int(gasLimit)) @@ -72,7 +72,7 @@ func Test_FixedPriceEstimator(t *testing.T) { config.BumpPercentF = uint16(10) config.BumpMinF = assets.NewWeiI(150) - lggr := logger.TestLogger(t) + lggr := logger.TestSugared(t) f := gas.NewFixedPriceEstimator(config, &blockHistoryConfig{}, lggr) gasPrice, gasLimit, err := f.BumpLegacyGas(testutils.Context(t), assets.NewWeiI(42), 100000, maxGasPrice, nil) @@ -93,7 +93,7 @@ func Test_FixedPriceEstimator(t *testing.T) { config.FeeCapDefaultF = assets.NewWeiI(100) config.BumpThresholdF = uint64(3) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) f := gas.NewFixedPriceEstimator(config, &blockHistoryConfig{}, lggr) fee, gasLimit, err := f.GetDynamicFee(testutils.Context(t), 100000, maxGasPrice) @@ -130,7 +130,7 @@ func Test_FixedPriceEstimator(t *testing.T) { config.BumpMinF = assets.NewWeiI(150) config.BumpPercentF = uint16(10) - lggr := logger.TestLogger(t) + lggr := logger.TestSugared(t) f := gas.NewFixedPriceEstimator(config, &blockHistoryConfig{}, lggr) originalFee := gas.DynamicFee{FeeCap: assets.NewWeiI(100), TipCap: assets.NewWeiI(25)} diff --git a/core/chains/evm/gas/gas_test.go b/core/chains/evm/gas/gas_test.go index 4c6be2cf504..355d39b6ce8 100644 --- a/core/chains/evm/gas/gas_test.go +++ b/core/chains/evm/gas/gas_test.go @@ -8,9 +8,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func Test_BumpLegacyGasPriceOnly(t *testing.T) { @@ -95,7 +95,7 @@ func Test_BumpLegacyGasPriceOnly(t *testing.T) { cfg.BumpMinF = test.bumpMin cfg.PriceMaxF = test.priceMax cfg.LimitMultiplierF = test.limitMultiplierPercent - actual, limit, err := gas.BumpLegacyGasPriceOnly(cfg, logger.TestLogger(t), test.currentGasPrice, test.originalGasPrice, test.originalLimit, test.priceMax) + actual, limit, err := gas.BumpLegacyGasPriceOnly(cfg, logger.TestSugared(t), test.currentGasPrice, test.originalGasPrice, test.originalLimit, test.priceMax) require.NoError(t, err) if actual.Cmp(test.expectedGasPrice) != 0 { t.Fatalf("Expected %s but got %s", test.expectedGasPrice.String(), actual.String()) @@ -115,7 +115,7 @@ func Test_BumpLegacyGasPriceOnly_HitsMaxError(t *testing.T) { cfg.PriceMaxF = priceMax originalGasPrice := toWei("3e10") // 30 GWei - _, _, err := gas.BumpLegacyGasPriceOnly(cfg, logger.TestLogger(t), nil, originalGasPrice, 42, priceMax) + _, _, err := gas.BumpLegacyGasPriceOnly(cfg, logger.TestSugared(t), nil, originalGasPrice, 42, priceMax) require.Error(t, err) require.Contains(t, err.Error(), "bumped gas price of 45 gwei would exceed configured max gas price of 40 gwei (original price was 30 gwei)") } @@ -124,7 +124,7 @@ func Test_BumpLegacyGasPriceOnly_NoBumpError(t *testing.T) { t.Parallel() priceMax := assets.GWei(40) - lggr := logger.TestLogger(t) + lggr := logger.TestSugared(t) cfg := &gas.MockGasEstimatorConfig{} cfg.BumpPercentF = uint16(0) @@ -298,7 +298,7 @@ func Test_BumpDynamicFeeOnly(t *testing.T) { cfg.LimitMultiplierF = test.limitMultiplierPercent bufferBlocks := uint16(4) - actual, limit, err := gas.BumpDynamicFeeOnly(cfg, bufferBlocks, logger.TestLogger(t), test.currentTipCap, test.currentBaseFee, test.originalFee, test.originalLimit, test.priceMax) + actual, limit, err := gas.BumpDynamicFeeOnly(cfg, bufferBlocks, logger.TestSugared(t), test.currentTipCap, test.currentBaseFee, test.originalFee, test.originalLimit, test.priceMax) require.NoError(t, err) if actual.TipCap.Cmp(test.expectedFee.TipCap) != 0 { t.Fatalf("TipCap not equal, expected %s but got %s", test.expectedFee.TipCap.String(), actual.TipCap.String()) @@ -324,14 +324,14 @@ func Test_BumpDynamicFeeOnly_HitsMaxError(t *testing.T) { t.Run("tip cap hits max", func(t *testing.T) { originalFee := gas.DynamicFee{TipCap: assets.GWei(30), FeeCap: assets.GWei(100)} - _, _, err := gas.BumpDynamicFeeOnly(cfg, 0, logger.TestLogger(t), nil, nil, originalFee, 42, priceMax) + _, _, err := gas.BumpDynamicFeeOnly(cfg, 0, logger.TestSugared(t), nil, nil, originalFee, 42, priceMax) require.Error(t, err) require.Contains(t, err.Error(), "bumped tip cap of 45 gwei would exceed configured max gas price of 40 gwei (original fee: tip cap 30 gwei, fee cap 100 gwei)") }) t.Run("fee cap hits max", func(t *testing.T) { originalFee := gas.DynamicFee{TipCap: assets.GWei(10), FeeCap: assets.GWei(100)} - _, _, err := gas.BumpDynamicFeeOnly(cfg, 0, logger.TestLogger(t), nil, nil, originalFee, 42, priceMax) + _, _, err := gas.BumpDynamicFeeOnly(cfg, 0, logger.TestSugared(t), nil, nil, originalFee, 42, priceMax) require.Error(t, err) require.Contains(t, err.Error(), "bumped fee cap of 150 gwei would exceed configured max gas price of 40 gwei (original fee: tip cap 10 gwei, fee cap 100 gwei)") }) diff --git a/core/chains/evm/gas/helpers_test.go b/core/chains/evm/gas/helpers_test.go index ac1f5129e09..9a5076be6d6 100644 --- a/core/chains/evm/gas/helpers_test.go +++ b/core/chains/evm/gas/helpers_test.go @@ -6,9 +6,9 @@ import ( "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/config" ) func init() { diff --git a/core/chains/evm/gas/mocks/config.go b/core/chains/evm/gas/mocks/config.go index eabbd0f03e4..c09005b5e3d 100644 --- a/core/chains/evm/gas/mocks/config.go +++ b/core/chains/evm/gas/mocks/config.go @@ -3,7 +3,7 @@ package mocks import ( - config "github.com/smartcontractkit/chainlink/v2/core/config" + config "github.com/smartcontractkit/chainlink/v2/common/config" mock "github.com/stretchr/testify/mock" ) diff --git a/core/chains/evm/gas/mocks/evm_estimator.go b/core/chains/evm/gas/mocks/evm_estimator.go index 80ab3c68cc0..f2cb51f8560 100644 --- a/core/chains/evm/gas/mocks/evm_estimator.go +++ b/core/chains/evm/gas/mocks/evm_estimator.go @@ -5,7 +5,7 @@ package mocks import ( context "context" - assets "github.com/smartcontractkit/chainlink/v2/core/assets" + assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" diff --git a/core/chains/evm/gas/mocks/evm_fee_estimator.go b/core/chains/evm/gas/mocks/evm_fee_estimator.go index 42ea96cd50b..b9b16367796 100644 --- a/core/chains/evm/gas/mocks/evm_fee_estimator.go +++ b/core/chains/evm/gas/mocks/evm_fee_estimator.go @@ -5,7 +5,7 @@ package mocks import ( big "math/big" - assets "github.com/smartcontractkit/chainlink/v2/core/assets" + assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" context "context" diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index bd3106c2ad4..c7476d58ba4 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -9,21 +9,20 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" + "github.com/smartcontractkit/chainlink/v2/common/config" commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" commontypes "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/label" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/config" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/utils" - bigmath "github.com/smartcontractkit/chainlink/v2/core/utils/big_math" ) // EvmFeeEstimator provides a unified interface that wraps EvmEstimator and can determine if legacy or dynamic fee estimation should be used @@ -79,8 +78,8 @@ func NewEstimator(lggr logger.Logger, ethClient evmclient.Client, cfg Config, ge return NewWrappedEvmEstimator(NewBlockHistoryEstimator(lggr, ethClient, cfg, geCfg, bh, *ethClient.ConfiguredChainID()), df, l1Oracle) case "FixedPrice": return NewWrappedEvmEstimator(NewFixedPriceEstimator(geCfg, bh, lggr), df, l1Oracle) - case "Optimism2", "L2Suggested": - return NewWrappedEvmEstimator(NewL2SuggestedPriceEstimator(lggr, ethClient), df, l1Oracle) + case "L2Suggested", "SuggestedPrice": + return NewWrappedEvmEstimator(NewSuggestedPriceEstimator(lggr, ethClient), df, l1Oracle) default: lggr.Warnf("GasEstimator: unrecognised mode '%s', falling back to FixedPriceEstimator", s) return NewWrappedEvmEstimator(NewFixedPriceEstimator(geCfg, bh, lggr), df, l1Oracle) @@ -150,10 +149,10 @@ func (fee EvmFee) ValidDynamic() bool { // WrappedEvmEstimator provides a struct that wraps the EVM specific dynamic and legacy estimators into one estimator that conforms to the generic FeeEstimator type WrappedEvmEstimator struct { + services.StateMachine EvmEstimator EIP1559Enabled bool l1Oracle rollups.L1Oracle - utils.StartStopOnce } var _ EvmFeeEstimator = (*WrappedEvmEstimator)(nil) diff --git a/core/chains/evm/gas/models_test.go b/core/chains/evm/gas/models_test.go index c1dd9e44ffc..a2dce58ee3f 100644 --- a/core/chains/evm/gas/models_test.go +++ b/core/chains/evm/gas/models_test.go @@ -1,7 +1,6 @@ package gas_test import ( - "context" "math/big" "testing" @@ -10,15 +9,16 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" rollupMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" ) func TestWrappedEvmEstimator(t *testing.T) { t.Parallel() - ctx := context.Background() + ctx := testutils.Context(t) // fee values gasLimit := uint32(10) diff --git a/core/chains/evm/gas/rollups/l1_gas_price_oracle.go b/core/chains/evm/gas/rollups/l1_gas_price_oracle.go index 8cf10325d47..6a384fa9c54 100644 --- a/core/chains/evm/gas/rollups/l1_gas_price_oracle.go +++ b/core/chains/evm/gas/rollups/l1_gas_price_oracle.go @@ -12,10 +12,12 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/config" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -26,6 +28,7 @@ type ethClient interface { // Reads L2-specific precompiles and caches the l1GasPrice set by the L2. type l1GasPriceOracle struct { + services.StateMachine client ethClient pollPeriod time.Duration logger logger.Logger @@ -36,9 +39,8 @@ type l1GasPriceOracle struct { l1GasPrice *assets.Wei chInitialised chan struct{} - chStop utils.StopChan + chStop services.StopChan chDone chan struct{} - utils.StartStopOnce } const ( @@ -56,11 +58,18 @@ const ( // `function l1BaseFee() external view returns (uint256);` OPGasOracle_l1BaseFee = "519b4bd3" + // GasOracleAddress is the address of the precompiled contract that exists on Kroma chain. + // This is the case for Kroma. + KromaGasOracleAddress = "0x4200000000000000000000000000000000000005" + // GasOracle_l1BaseFee is the a hex encoded call to: + // `function l1BaseFee() external view returns (uint256);` + KromaGasOracle_l1BaseFee = "519b4bd3" + // Interval at which to poll for L1BaseFee. A good starting point is the L1 block time. PollPeriod = 12 * time.Second ) -var supportedChainTypes = []config.ChainType{config.ChainArbitrum, config.ChainOptimismBedrock} +var supportedChainTypes = []config.ChainType{config.ChainArbitrum, config.ChainOptimismBedrock, config.ChainKroma} func IsRollupWithL1Support(chainType config.ChainType) bool { return slices.Contains(supportedChainTypes, chainType) @@ -75,6 +84,9 @@ func NewL1GasPriceOracle(lggr logger.Logger, ethClient ethClient, chainType conf case config.ChainOptimismBedrock: address = OPGasOracleAddress callArgs = OPGasOracle_l1BaseFee + case config.ChainKroma: + address = KromaGasOracleAddress + callArgs = KromaGasOracle_l1BaseFee default: panic(fmt.Sprintf("Received unspported chaintype %s", chainType)) } @@ -82,7 +94,7 @@ func NewL1GasPriceOracle(lggr logger.Logger, ethClient ethClient, chainType conf return &l1GasPriceOracle{ client: ethClient, pollPeriod: PollPeriod, - logger: lggr.Named(fmt.Sprintf("L1GasPriceOracle(%s)", chainType)), + logger: logger.Named(lggr, fmt.Sprintf("L1GasPriceOracle(%s)", chainType)), address: address, callArgs: callArgs, chInitialised: make(chan struct{}), @@ -147,7 +159,7 @@ func (o *l1GasPriceOracle) refresh() (t *time.Timer) { } if len(b) != 32 { // returns uint256; - o.logger.Criticalf("return data length (%d) different than expected (%d)", len(b), 32) + logger.Criticalf(o.logger, "return data length (%d) different than expected (%d)", len(b), 32) return } price := new(big.Int).SetBytes(b) diff --git a/core/chains/evm/gas/rollups/l1_gas_price_oracle_test.go b/core/chains/evm/gas/rollups/l1_gas_price_oracle_test.go index 9fd2a66201c..2defedd6b47 100644 --- a/core/chains/evm/gas/rollups/l1_gas_price_oracle_test.go +++ b/core/chains/evm/gas/rollups/l1_gas_price_oracle_test.go @@ -11,12 +11,12 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" + "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink/v2/core/assets" - "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestL1GasPriceOracle(t *testing.T) { @@ -25,13 +25,13 @@ func TestL1GasPriceOracle(t *testing.T) { t.Run("Unsupported ChainType returns nil", func(t *testing.T) { ethClient := mocks.NewETHClient(t) - assert.Panicsf(t, func() { NewL1GasPriceOracle(logger.TestLogger(t), ethClient, config.ChainCelo) }, "Received unspported chaintype %s", config.ChainCelo) + assert.Panicsf(t, func() { NewL1GasPriceOracle(logger.Test(t), ethClient, config.ChainCelo) }, "Received unspported chaintype %s", config.ChainCelo) }) t.Run("Calling L1GasPrice on unstarted L1Oracle returns error", func(t *testing.T) { ethClient := mocks.NewETHClient(t) - oracle := NewL1GasPriceOracle(logger.TestLogger(t), ethClient, config.ChainOptimismBedrock) + oracle := NewL1GasPriceOracle(logger.Test(t), ethClient, config.ChainOptimismBedrock) _, err := oracle.GasPrice(testutils.Context(t)) assert.EqualError(t, err, "L1GasPriceOracle is not started; cannot estimate gas") @@ -49,7 +49,29 @@ func TestL1GasPriceOracle(t *testing.T) { assert.Nil(t, blockNumber) }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) - oracle := NewL1GasPriceOracle(logger.TestLogger(t), ethClient, config.ChainArbitrum) + oracle := NewL1GasPriceOracle(logger.Test(t), ethClient, config.ChainArbitrum) + require.NoError(t, oracle.Start(testutils.Context(t))) + t.Cleanup(func() { assert.NoError(t, oracle.Close()) }) + + gasPrice, err := oracle.GasPrice(testutils.Context(t)) + require.NoError(t, err) + + assert.Equal(t, assets.NewWei(l1BaseFee), gasPrice) + }) + + t.Run("Calling GasPrice on started Kroma L1Oracle returns Kroma l1GasPrice", func(t *testing.T) { + l1BaseFee := big.NewInt(200) + + ethClient := mocks.NewETHClient(t) + ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) { + callMsg := args.Get(1).(ethereum.CallMsg) + blockNumber := args.Get(2).(*big.Int) + assert.Equal(t, KromaGasOracleAddress, callMsg.To.String()) + assert.Equal(t, KromaGasOracle_l1BaseFee, fmt.Sprintf("%x", callMsg.Data)) + assert.Nil(t, blockNumber) + }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) + + oracle := NewL1GasPriceOracle(logger.Test(t), ethClient, config.ChainKroma) require.NoError(t, oracle.Start(testutils.Context(t))) t.Cleanup(func() { assert.NoError(t, oracle.Close()) }) @@ -71,7 +93,7 @@ func TestL1GasPriceOracle(t *testing.T) { assert.Nil(t, blockNumber) }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) - oracle := NewL1GasPriceOracle(logger.TestLogger(t), ethClient, config.ChainOptimismBedrock) + oracle := NewL1GasPriceOracle(logger.Test(t), ethClient, config.ChainOptimismBedrock) require.NoError(t, oracle.Start(testutils.Context(t))) t.Cleanup(func() { assert.NoError(t, oracle.Close()) }) diff --git a/core/chains/evm/gas/rollups/mocks/l1_oracle.go b/core/chains/evm/gas/rollups/mocks/l1_oracle.go index 31407300b64..f6f8cc736af 100644 --- a/core/chains/evm/gas/rollups/mocks/l1_oracle.go +++ b/core/chains/evm/gas/rollups/mocks/l1_oracle.go @@ -5,7 +5,7 @@ package mocks import ( context "context" - assets "github.com/smartcontractkit/chainlink/v2/core/assets" + assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" mock "github.com/stretchr/testify/mock" ) diff --git a/core/chains/evm/gas/rollups/models.go b/core/chains/evm/gas/rollups/models.go index 83ae29f4ea9..1659436071b 100644 --- a/core/chains/evm/gas/rollups/models.go +++ b/core/chains/evm/gas/rollups/models.go @@ -3,7 +3,7 @@ package rollups import ( "context" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/services" ) diff --git a/core/chains/evm/gas/l2_suggested_estimator.go b/core/chains/evm/gas/suggested_price_estimator.go similarity index 56% rename from core/chains/evm/gas/l2_suggested_estimator.go rename to core/chains/evm/gas/suggested_price_estimator.go index 1782e349302..2e0d32bfc94 100644 --- a/core/chains/evm/gas/l2_suggested_estimator.go +++ b/core/chains/evm/gas/suggested_price_estimator.go @@ -9,16 +9,18 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) var ( - _ EvmEstimator = &l2SuggestedPriceEstimator{} + _ EvmEstimator = &SuggestedPriceEstimator{} ) //go:generate mockery --quiet --name rpcClient --output ./mocks/ --case=underscore --structname RPCClient @@ -26,29 +28,29 @@ type rpcClient interface { CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error } -// l2SuggestedPriceEstimator is an Estimator which uses the L2 suggested gas price from eth_gasPrice. -type l2SuggestedPriceEstimator struct { - utils.StartStopOnce +// SuggestedPriceEstimator is an Estimator which uses the suggested gas price from eth_gasPrice. +type SuggestedPriceEstimator struct { + services.StateMachine client rpcClient pollPeriod time.Duration logger logger.Logger gasPriceMu sync.RWMutex - l2GasPrice *assets.Wei + GasPrice *assets.Wei chForceRefetch chan (chan struct{}) chInitialised chan struct{} - chStop utils.StopChan + chStop services.StopChan chDone chan struct{} } -// NewL2SuggestedPriceEstimator returns a new Estimator which uses the L2 suggested gas price. -func NewL2SuggestedPriceEstimator(lggr logger.Logger, client rpcClient) EvmEstimator { - return &l2SuggestedPriceEstimator{ +// NewSuggestedPriceEstimator returns a new Estimator which uses the suggested gas price. +func NewSuggestedPriceEstimator(lggr logger.Logger, client rpcClient) EvmEstimator { + return &SuggestedPriceEstimator{ client: client, pollPeriod: 10 * time.Second, - logger: lggr.Named("L2SuggestedEstimator"), + logger: logger.Named(lggr, "SuggestedPriceEstimator"), chForceRefetch: make(chan (chan struct{})), chInitialised: make(chan struct{}), chStop: make(chan struct{}), @@ -56,30 +58,30 @@ func NewL2SuggestedPriceEstimator(lggr logger.Logger, client rpcClient) EvmEstim } } -func (o *l2SuggestedPriceEstimator) Name() string { +func (o *SuggestedPriceEstimator) Name() string { return o.logger.Name() } -func (o *l2SuggestedPriceEstimator) Start(context.Context) error { - return o.StartOnce("L2SuggestedEstimator", func() error { +func (o *SuggestedPriceEstimator) Start(context.Context) error { + return o.StartOnce("SuggestedPriceEstimator", func() error { go o.run() <-o.chInitialised return nil }) } -func (o *l2SuggestedPriceEstimator) Close() error { - return o.StopOnce("L2SuggestedEstimator", func() error { +func (o *SuggestedPriceEstimator) Close() error { + return o.StopOnce("SuggestedPriceEstimator", func() error { close(o.chStop) <-o.chDone return nil }) } -func (o *l2SuggestedPriceEstimator) HealthReport() map[string]error { +func (o *SuggestedPriceEstimator) HealthReport() map[string]error { return map[string]error{o.Name(): o.Healthy()} } -func (o *l2SuggestedPriceEstimator) run() { +func (o *SuggestedPriceEstimator) run() { defer close(o.chDone) t := o.refreshPrice() @@ -99,7 +101,7 @@ func (o *l2SuggestedPriceEstimator) run() { } } -func (o *l2SuggestedPriceEstimator) refreshPrice() (t *time.Timer) { +func (o *SuggestedPriceEstimator) refreshPrice() (t *time.Timer) { t = time.NewTimer(utils.WithJitter(o.pollPeriod)) var res hexutil.Big @@ -112,28 +114,28 @@ func (o *l2SuggestedPriceEstimator) refreshPrice() (t *time.Timer) { } bi := (*assets.Wei)(&res) - o.logger.Debugw("refreshPrice", "l2GasPrice", bi) + o.logger.Debugw("refreshPrice", "GasPrice", bi) o.gasPriceMu.Lock() defer o.gasPriceMu.Unlock() - o.l2GasPrice = bi + o.GasPrice = bi return } -func (o *l2SuggestedPriceEstimator) OnNewLongestChain(context.Context, *evmtypes.Head) {} +func (o *SuggestedPriceEstimator) OnNewLongestChain(context.Context, *evmtypes.Head) {} -func (*l2SuggestedPriceEstimator) GetDynamicFee(_ context.Context, _ uint32, _ *assets.Wei) (fee DynamicFee, chainSpecificGasLimit uint32, err error) { +func (*SuggestedPriceEstimator) GetDynamicFee(_ context.Context, _ uint32, _ *assets.Wei) (fee DynamicFee, chainSpecificGasLimit uint32, err error) { err = errors.New("dynamic fees are not implemented for this layer 2") return } -func (*l2SuggestedPriceEstimator) BumpDynamicFee(_ context.Context, _ DynamicFee, _ uint32, _ *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, chainSpecificGasLimit uint32, err error) { +func (*SuggestedPriceEstimator) BumpDynamicFee(_ context.Context, _ DynamicFee, _ uint32, _ *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, chainSpecificGasLimit uint32, err error) { err = errors.New("dynamic fees are not implemented for this layer 2") return } -func (o *l2SuggestedPriceEstimator) GetLegacyGas(ctx context.Context, _ []byte, l2GasLimit uint32, maxGasPriceWei *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint32, err error) { - chainSpecificGasLimit = l2GasLimit +func (o *SuggestedPriceEstimator) GetLegacyGas(ctx context.Context, _ []byte, GasLimit uint32, maxGasPriceWei *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint32, err error) { + chainSpecificGasLimit = GasLimit ok := o.IfStarted(func() { if slices.Contains(opts, feetypes.OptForceRefetch) { @@ -158,10 +160,10 @@ func (o *l2SuggestedPriceEstimator) GetLegacyGas(ctx context.Context, _ []byte, } } if gasPrice = o.getGasPrice(); gasPrice == nil { - err = errors.New("failed to estimate l2 gas; gas price not set") + err = errors.New("failed to estimate gas; gas price not set") return } - o.logger.Debugw("GetLegacyGas", "l2GasPrice", gasPrice, "l2GasLimit", l2GasLimit) + o.logger.Debugw("GetLegacyGas", "GasPrice", gasPrice, "GasLimit", GasLimit) }) if !ok { return nil, 0, errors.New("estimator is not started") @@ -175,12 +177,12 @@ func (o *l2SuggestedPriceEstimator) GetLegacyGas(ctx context.Context, _ []byte, return } -func (o *l2SuggestedPriceEstimator) BumpLegacyGas(_ context.Context, _ *assets.Wei, _ uint32, _ *assets.Wei, _ []EvmPriorAttempt) (bumpedGasPrice *assets.Wei, chainSpecificGasLimit uint32, err error) { - return nil, 0, errors.New("bump gas is not supported for this l2") +func (o *SuggestedPriceEstimator) BumpLegacyGas(_ context.Context, _ *assets.Wei, _ uint32, _ *assets.Wei, _ []EvmPriorAttempt) (bumpedGasPrice *assets.Wei, chainSpecificGasLimit uint32, err error) { + return nil, 0, errors.New("bump gas is not supported for this chain") } -func (o *l2SuggestedPriceEstimator) getGasPrice() (l2GasPrice *assets.Wei) { +func (o *SuggestedPriceEstimator) getGasPrice() (GasPrice *assets.Wei) { o.gasPriceMu.RLock() defer o.gasPriceMu.RUnlock() - return o.l2GasPrice + return o.GasPrice } diff --git a/core/chains/evm/gas/l2_suggested_estimator_test.go b/core/chains/evm/gas/suggested_price_estimator_test.go similarity index 84% rename from core/chains/evm/gas/l2_suggested_estimator_test.go rename to core/chains/evm/gas/suggested_price_estimator_test.go index 69b36033024..304e5359107 100644 --- a/core/chains/evm/gas/l2_suggested_estimator_test.go +++ b/core/chains/evm/gas/suggested_price_estimator_test.go @@ -10,14 +10,14 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) -func TestL2SuggestedEstimator(t *testing.T) { +func TestSuggestedPriceEstimator(t *testing.T) { t.Parallel() maxGasPrice := assets.NewWeiI(100) @@ -27,7 +27,7 @@ func TestL2SuggestedEstimator(t *testing.T) { t.Run("calling GetLegacyGas on unstarted estimator returns error", func(t *testing.T) { client := mocks.NewRPCClient(t) - o := gas.NewL2SuggestedPriceEstimator(logger.TestLogger(t), client) + o := gas.NewSuggestedPriceEstimator(logger.Test(t), client) _, _, err := o.GetLegacyGas(testutils.Context(t), calldata, gasLimit, maxGasPrice) assert.EqualError(t, err, "estimator is not started") }) @@ -39,7 +39,7 @@ func TestL2SuggestedEstimator(t *testing.T) { (*big.Int)(res).SetInt64(42) }) - o := gas.NewL2SuggestedPriceEstimator(logger.TestLogger(t), client) + o := gas.NewSuggestedPriceEstimator(logger.Test(t), client) require.NoError(t, o.Start(testutils.Context(t))) t.Cleanup(func() { assert.NoError(t, o.Close()) }) gasPrice, chainSpecificGasLimit, err := o.GetLegacyGas(testutils.Context(t), calldata, gasLimit, maxGasPrice) @@ -50,7 +50,7 @@ func TestL2SuggestedEstimator(t *testing.T) { t.Run("gas price is lower than user specified max gas price", func(t *testing.T) { client := mocks.NewRPCClient(t) - o := gas.NewL2SuggestedPriceEstimator(logger.TestLogger(t), client) + o := gas.NewSuggestedPriceEstimator(logger.Test(t), client) client.On("CallContext", mock.Anything, mock.Anything, "eth_gasPrice").Return(nil).Run(func(args mock.Arguments) { res := args.Get(1).(*hexutil.Big) @@ -68,7 +68,7 @@ func TestL2SuggestedEstimator(t *testing.T) { t.Run("gas price is lower than global max gas price", func(t *testing.T) { client := mocks.NewRPCClient(t) - o := gas.NewL2SuggestedPriceEstimator(logger.TestLogger(t), client) + o := gas.NewSuggestedPriceEstimator(logger.Test(t), client) client.On("CallContext", mock.Anything, mock.Anything, "eth_gasPrice").Return(nil).Run(func(args mock.Arguments) { res := args.Get(1).(*hexutil.Big) @@ -85,14 +85,14 @@ func TestL2SuggestedEstimator(t *testing.T) { t.Run("calling BumpLegacyGas always returns error", func(t *testing.T) { client := mocks.NewRPCClient(t) - o := gas.NewL2SuggestedPriceEstimator(logger.TestLogger(t), client) + o := gas.NewSuggestedPriceEstimator(logger.Test(t), client) _, _, err := o.BumpLegacyGas(testutils.Context(t), assets.NewWeiI(42), gasLimit, assets.NewWeiI(10), nil) - assert.EqualError(t, err, "bump gas is not supported for this l2") + assert.EqualError(t, err, "bump gas is not supported for this chain") }) t.Run("calling GetLegacyGas on started estimator if initial call failed returns error", func(t *testing.T) { client := mocks.NewRPCClient(t) - o := gas.NewL2SuggestedPriceEstimator(logger.TestLogger(t), client) + o := gas.NewSuggestedPriceEstimator(logger.Test(t), client) client.On("CallContext", mock.Anything, mock.Anything, "eth_gasPrice").Return(errors.New("kaboom")) @@ -100,6 +100,6 @@ func TestL2SuggestedEstimator(t *testing.T) { t.Cleanup(func() { assert.NoError(t, o.Close()) }) _, _, err := o.GetLegacyGas(testutils.Context(t), calldata, gasLimit, maxGasPrice) - assert.EqualError(t, err, "failed to estimate l2 gas; gas price not set") + assert.EqualError(t, err, "failed to estimate gas; gas price not set") }) } diff --git a/core/chains/evm/headtracker/head_broadcaster.go b/core/chains/evm/headtracker/head_broadcaster.go index b47fbc2726f..9929646441a 100644 --- a/core/chains/evm/headtracker/head_broadcaster.go +++ b/core/chains/evm/headtracker/head_broadcaster.go @@ -3,10 +3,10 @@ package headtracker import ( "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/common/headtracker" commontypes "github.com/smartcontractkit/chainlink/v2/common/types" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type headBroadcaster = headtracker.HeadBroadcaster[*evmtypes.Head, common.Hash] diff --git a/core/chains/evm/headtracker/head_broadcaster_test.go b/core/chains/evm/headtracker/head_broadcaster_test.go index c478920e00e..6fb151bfe6c 100644 --- a/core/chains/evm/headtracker/head_broadcaster_test.go +++ b/core/chains/evm/headtracker/head_broadcaster_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" commonhtrk "github.com/smartcontractkit/chainlink/v2/common/headtracker" commonmocks "github.com/smartcontractkit/chainlink/v2/common/types/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" @@ -17,10 +18,9 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/store/models" @@ -48,7 +48,7 @@ func TestHeadBroadcaster_Subscribe(t *testing.T) { }) evmCfg := evmtest.NewChainScopedConfig(t, cfg) db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) sub := commonmocks.NewSubscription(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) @@ -101,7 +101,7 @@ func TestHeadBroadcaster_BroadcastNewLongestChain(t *testing.T) { t.Parallel() g := gomega.NewWithT(t) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) broadcaster := headtracker.NewHeadBroadcaster(lggr) err := broadcaster.Start(testutils.Context(t)) @@ -143,7 +143,7 @@ func TestHeadBroadcaster_BroadcastNewLongestChain(t *testing.T) { func TestHeadBroadcaster_TrackableCallbackTimeout(t *testing.T) { t.Parallel() - lggr := logger.TestLogger(t) + lggr := logger.Test(t) broadcaster := headtracker.NewHeadBroadcaster(lggr) err := broadcaster.Start(testutils.Context(t)) diff --git a/core/chains/evm/headtracker/head_listener.go b/core/chains/evm/headtracker/head_listener.go index 3c81c8895ea..242b59e9a82 100644 --- a/core/chains/evm/headtracker/head_listener.go +++ b/core/chains/evm/headtracker/head_listener.go @@ -6,11 +6,11 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/common/headtracker" commontypes "github.com/smartcontractkit/chainlink/v2/common/types" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type headListener = headtracker.HeadListener[*evmtypes.Head, ethereum.Subscription, *big.Int, common.Hash] diff --git a/core/chains/evm/headtracker/head_listener_test.go b/core/chains/evm/headtracker/head_listener_test.go index 63d22233836..8bb761bdfaa 100644 --- a/core/chains/evm/headtracker/head_listener_test.go +++ b/core/chains/evm/headtracker/head_listener_test.go @@ -12,14 +12,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/smartcontractkit/chainlink-common/pkg/logger" commonmocks "github.com/smartcontractkit/chainlink/v2/common/types/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/store/models" ) @@ -35,7 +35,7 @@ func Test_HeadListener_HappyPath(t *testing.T) { // - 3 heads is passed to callback // - ethClient methods are invoked - lggr := logger.TestLogger(t) + lggr := logger.Test(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { // no need to test head timeouts here @@ -96,7 +96,7 @@ func Test_HeadListener_NotReceivingHeads(t *testing.T) { // - send one head, make sure ReceivingHeads() is true // - do not send any heads within BlockEmissionIdleWarningThreshold and check ReceivingHeads() is false - lggr := logger.TestLogger(t) + lggr := logger.Test(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { @@ -162,7 +162,7 @@ func Test_HeadListener_SubscriptionErr(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - l := logger.TestLogger(t) + l := logger.Test(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) cfg := configtest.NewGeneralConfig(t, nil) evmcfg := evmtest.NewChainScopedConfig(t, cfg) diff --git a/core/chains/evm/headtracker/head_saver.go b/core/chains/evm/headtracker/head_saver.go index 6b4b20c89c5..92eedaf153e 100644 --- a/core/chains/evm/headtracker/head_saver.go +++ b/core/chains/evm/headtracker/head_saver.go @@ -5,10 +5,10 @@ import ( "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" commontypes "github.com/smartcontractkit/chainlink/v2/common/types" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type headSaver struct { @@ -26,7 +26,7 @@ func NewHeadSaver(lggr logger.Logger, orm ORM, config Config, htConfig HeadTrack orm: orm, config: config, htConfig: htConfig, - logger: lggr.Named("HeadSaver"), + logger: logger.Named(lggr, "HeadSaver"), heads: NewHeads(), } } diff --git a/core/chains/evm/headtracker/head_saver_test.go b/core/chains/evm/headtracker/head_saver_test.go index 5ab43679f4b..f541330bc98 100644 --- a/core/chains/evm/headtracker/head_saver_test.go +++ b/core/chains/evm/headtracker/head_saver_test.go @@ -6,13 +6,13 @@ import ( "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type headTrackerConfig struct { @@ -43,7 +43,7 @@ func (c *config) BlockEmissionIdleWarningThreshold() time.Duration { func configureSaver(t *testing.T) (httypes.HeadSaver, headtracker.ORM) { db := pgtest.NewSqlxDB(t) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) cfg := configtest.NewGeneralConfig(t, nil) htCfg := &config{finalityDepth: uint32(1)} orm := headtracker.NewORM(db, lggr, cfg.Database(), cltest.FixtureChainID) diff --git a/core/chains/evm/headtracker/head_tracker.go b/core/chains/evm/headtracker/head_tracker.go index dd94f96383b..b86a6b5fe22 100644 --- a/core/chains/evm/headtracker/head_tracker.go +++ b/core/chains/evm/headtracker/head_tracker.go @@ -8,12 +8,12 @@ import ( "github.com/ethereum/go-ethereum/common" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/common/headtracker" commontypes "github.com/smartcontractkit/chainlink/v2/common/types" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/chains/evm/headtracker/head_tracker_test.go b/core/chains/evm/headtracker/head_tracker_test.go index d30571f331d..4d3cebd24e2 100644 --- a/core/chains/evm/headtracker/head_tracker_test.go +++ b/core/chains/evm/headtracker/head_tracker_test.go @@ -18,9 +18,10 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/exp/maps" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" commonmocks "github.com/smartcontractkit/chainlink/v2/common/types/mocks" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" @@ -29,10 +30,9 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -49,7 +49,7 @@ func TestHeadTracker_New(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := configtest.NewGeneralConfig(t, nil) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(cltest.Head(0), nil) @@ -73,7 +73,7 @@ func TestHeadTracker_Save_InsertsAndTrimsTable(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := cltest.NewTestChainScopedConfig(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) @@ -118,7 +118,7 @@ func TestHeadTracker_Get(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := cltest.NewTestChainScopedConfig(t) orm := headtracker.NewORM(db, logger, config.Database(), cltest.FixtureChainID) @@ -165,7 +165,7 @@ func TestHeadTracker_Start_NewHeads(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := cltest.NewTestChainScopedConfig(t) orm := headtracker.NewORM(db, logger, config.Database(), cltest.FixtureChainID) @@ -190,7 +190,7 @@ func TestHeadTracker_Start_CancelContext(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := cltest.NewTestChainScopedConfig(t) orm := headtracker.NewORM(db, logger, config.Database(), cltest.FixtureChainID) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) @@ -230,7 +230,7 @@ func TestHeadTracker_CallsHeadTrackableCallbacks(t *testing.T) { g := gomega.NewWithT(t) db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := cltest.NewTestChainScopedConfig(t) orm := headtracker.NewORM(db, logger, config.Database(), cltest.FixtureChainID) @@ -269,7 +269,7 @@ func TestHeadTracker_ReconnectOnError(t *testing.T) { g := gomega.NewWithT(t) db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := cltest.NewTestChainScopedConfig(t) orm := headtracker.NewORM(db, logger, config.Database(), cltest.FixtureChainID) @@ -305,7 +305,7 @@ func TestHeadTracker_ResubscribeOnSubscriptionError(t *testing.T) { g := gomega.NewWithT(t) db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := cltest.NewTestChainScopedConfig(t) orm := headtracker.NewORM(db, logger, config.Database(), cltest.FixtureChainID) @@ -352,7 +352,7 @@ func TestHeadTracker_Start_LoadsLatestChain(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := cltest.NewTestChainScopedConfig(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) @@ -417,7 +417,7 @@ func TestHeadTracker_SwitchesToLongestChainWithHeadSamplingEnabled(t *testing.T) t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].FinalityDepth = ptr[uint32](50) @@ -545,7 +545,7 @@ func TestHeadTracker_SwitchesToLongestChainWithHeadSamplingDisabled(t *testing.T t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].FinalityDepth = ptr[uint32](50) @@ -773,7 +773,7 @@ func TestHeadTracker_Backfill(t *testing.T) { t.Run("does nothing if all the heads are in database", func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - logger := logger.TestLogger(t) + logger := logger.Test(t) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) for i := range heads { require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &heads[i])) @@ -790,7 +790,7 @@ func TestHeadTracker_Backfill(t *testing.T) { t.Run("fetches a missing head", func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - logger := logger.TestLogger(t) + logger := logger.Test(t) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) for i := range heads { require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &heads[i])) @@ -826,7 +826,7 @@ func TestHeadTracker_Backfill(t *testing.T) { t.Run("fetches only heads that are missing", func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - logger := logger.TestLogger(t) + logger := logger.Test(t) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) for i := range heads { require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &heads[i])) @@ -859,7 +859,7 @@ func TestHeadTracker_Backfill(t *testing.T) { t.Run("does not backfill if chain length is already greater than or equal to depth", func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - logger := logger.TestLogger(t) + logger := logger.Test(t) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) for i := range heads { require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &heads[i])) @@ -880,7 +880,7 @@ func TestHeadTracker_Backfill(t *testing.T) { t.Run("only backfills to height 0 if chain length would otherwise cause it to try and fetch a negative head", func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - logger := logger.TestLogger(t) + logger := logger.Test(t) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) ethClient := evmtest.NewEthClientMock(t) @@ -905,7 +905,7 @@ func TestHeadTracker_Backfill(t *testing.T) { t.Run("abandons backfill and returns error if the eth node returns not found", func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - logger := logger.TestLogger(t) + logger := logger.Test(t) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) for i := range heads { require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &heads[i])) @@ -936,7 +936,7 @@ func TestHeadTracker_Backfill(t *testing.T) { t.Run("abandons backfill and returns error if the context time budget is exceeded", func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - logger := logger.TestLogger(t) + logger := logger.Test(t) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) for i := range heads { require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), &heads[i])) @@ -965,7 +965,7 @@ func TestHeadTracker_Backfill(t *testing.T) { t.Run("abandons backfill and returns error when fetching a block by hash fails, indicating a reorg", func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - logger := logger.TestLogger(t) + logger := logger.Test(t) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) ethClient := evmtest.NewEthClientMock(t) ethClient.On("ConfiguredChainID", mock.Anything).Return(evmtest.MustGetDefaultChainID(t, cfg.EVMConfigs()), nil) @@ -989,7 +989,7 @@ func TestHeadTracker_Backfill(t *testing.T) { } func createHeadTracker(t *testing.T, ethClient evmclient.Client, config headtracker.Config, htConfig headtracker.HeadTrackerConfig, orm headtracker.ORM) *headTrackerUniverse { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) hb := headtracker.NewHeadBroadcaster(lggr) hs := headtracker.NewHeadSaver(lggr, orm, config, htConfig) mailMon := utils.NewMailboxMonitor(t.Name()) @@ -1004,7 +1004,7 @@ func createHeadTracker(t *testing.T, ethClient evmclient.Client, config headtrac func createHeadTrackerWithNeverSleeper(t *testing.T, ethClient evmclient.Client, cfg chainlink.GeneralConfig, orm headtracker.ORM) *headTrackerUniverse { evmcfg := evmtest.NewChainScopedConfig(t, cfg) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) hb := headtracker.NewHeadBroadcaster(lggr) hs := headtracker.NewHeadSaver(lggr, orm, evmcfg.EVM(), evmcfg.EVM().HeadTracker()) mailMon := utils.NewMailboxMonitor(t.Name()) @@ -1021,7 +1021,7 @@ func createHeadTrackerWithNeverSleeper(t *testing.T, ethClient evmclient.Client, } func createHeadTrackerWithChecker(t *testing.T, ethClient evmclient.Client, config headtracker.Config, htConfig headtracker.HeadTrackerConfig, orm headtracker.ORM, checker httypes.HeadTrackable) *headTrackerUniverse { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) hb := headtracker.NewHeadBroadcaster(lggr) hs := headtracker.NewHeadSaver(lggr, orm, config, htConfig) hb.Subscribe(checker) diff --git a/core/chains/evm/headtracker/orm.go b/core/chains/evm/headtracker/orm.go index 426df68b301..88d569b9a21 100644 --- a/core/chains/evm/headtracker/orm.go +++ b/core/chains/evm/headtracker/orm.go @@ -8,10 +8,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink-common/pkg/logger" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -36,7 +36,7 @@ type orm struct { } func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig, chainID big.Int) ORM { - return &orm{pg.NewQ(db, lggr.Named("HeadTrackerORM"), cfg), utils.Big(chainID)} + return &orm{pg.NewQ(db, logger.Named(lggr, "HeadTrackerORM"), cfg), utils.Big(chainID)} } func (orm *orm) IdempotentInsertHead(ctx context.Context, head *evmtypes.Head) error { diff --git a/core/chains/evm/headtracker/orm_test.go b/core/chains/evm/headtracker/orm_test.go index 5b106ac1018..c9a2146daf2 100644 --- a/core/chains/evm/headtracker/orm_test.go +++ b/core/chains/evm/headtracker/orm_test.go @@ -4,24 +4,24 @@ import ( "testing" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestORM_IdempotentInsertHead(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) cfg := configtest.NewGeneralConfig(t, nil) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) @@ -47,7 +47,7 @@ func TestORM_TrimOldHeads(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) cfg := configtest.NewGeneralConfig(t, nil) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) @@ -72,7 +72,7 @@ func TestORM_HeadByHash(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) cfg := configtest.NewGeneralConfig(t, nil) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) @@ -95,7 +95,7 @@ func TestORM_HeadByHash_NotFound(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) cfg := configtest.NewGeneralConfig(t, nil) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) @@ -110,7 +110,7 @@ func TestORM_LatestHeads_NoRows(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - logger := logger.TestLogger(t) + logger := logger.Test(t) cfg := configtest.NewGeneralConfig(t, nil) orm := headtracker.NewORM(db, logger, cfg.Database(), cltest.FixtureChainID) diff --git a/core/chains/evm/log/broadcaster.go b/core/chains/evm/log/broadcaster.go index bc9ba1cf6cf..f4528396093 100644 --- a/core/chains/evm/log/broadcaster.go +++ b/core/chains/evm/log/broadcaster.go @@ -12,13 +12,14 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/null" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -44,7 +45,7 @@ type ( // Of course, these backfilled logs + any new logs will only be sent after the NumConfirmations for given subscriber. Broadcaster interface { utils.DependentAwaiter - services.ServiceCtx + services.Service httypes.HeadTrackable // ReplayFromBlock enqueues a replay from the provided block number. If forceBroadcast is @@ -88,6 +89,7 @@ type ( } broadcaster struct { + services.StateMachine orm ORM config Config connected atomic.Bool @@ -106,10 +108,9 @@ type ( changeSubscriberStatus *utils.Mailbox[changeSubscriberStatus] newHeads *utils.Mailbox[*evmtypes.Head] - utils.StartStopOnce utils.DependentAwaiter - chStop utils.StopChan + chStop services.StopChan wgDone sync.WaitGroup trackedAddressesCount atomic.Uint32 replayChannel chan replayRequest @@ -166,7 +167,7 @@ var _ Broadcaster = (*broadcaster)(nil) // NewBroadcaster creates a new instance of the broadcaster func NewBroadcaster(orm ORM, ethClient evmclient.Client, config Config, lggr logger.Logger, highestSavedHead *evmtypes.Head, mailMon *utils.MailboxMonitor) *broadcaster { chStop := make(chan struct{}) - lggr = lggr.Named("LogBroadcaster") + lggr = logger.Named(lggr, "LogBroadcaster") chainId := ethClient.ConfiguredChainID() return &broadcaster{ orm: orm, @@ -442,7 +443,7 @@ func (b *broadcaster) eventLoop(chRawLogs <-chan types.Log, chErr <-chan error) // Do we have logs in the pool? // They are are invalid, since we may have missed 'removed' logs. if blockNum := b.invalidatePool(); blockNum > 0 { - lggr = lggr.With("blockNumber", blockNum) + lggr = logger.With(lggr, "blockNumber", blockNum) } lggr.Debugw("Subscription terminated. Backfilling after resubscribing") return true, err diff --git a/core/chains/evm/log/eth_subscriber.go b/core/chains/evm/log/eth_subscriber.go index cd1b18b474f..96a7a8248a6 100644 --- a/core/chains/evm/log/eth_subscriber.go +++ b/core/chains/evm/log/eth_subscriber.go @@ -10,8 +10,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -21,15 +23,15 @@ type ( ethClient evmclient.Client config Config logger logger.Logger - chStop utils.StopChan + chStop services.StopChan } ) -func newEthSubscriber(ethClient evmclient.Client, config Config, logger logger.Logger, chStop chan struct{}) *ethSubscriber { +func newEthSubscriber(ethClient evmclient.Client, config Config, lggr logger.Logger, chStop chan struct{}) *ethSubscriber { return ðSubscriber{ ethClient: ethClient, config: config, - logger: logger.Named("EthSubscriber"), + logger: logger.Named(lggr, "EthSubscriber"), chStop: chStop, } } diff --git a/core/chains/evm/log/helpers_internal_test.go b/core/chains/evm/log/helpers_internal_test.go index b3b4890e360..38f40bd329e 100644 --- a/core/chains/evm/log/helpers_internal_test.go +++ b/core/chains/evm/log/helpers_internal_test.go @@ -3,9 +3,9 @@ package log import ( "github.com/ethereum/go-ethereum/core/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/chains/evm/log/helpers_test.go b/core/chains/evm/log/helpers_test.go index 58e81132b0f..ac7eb863e62 100644 --- a/core/chains/evm/log/helpers_test.go +++ b/core/chains/evm/log/helpers_test.go @@ -18,8 +18,9 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink-common/pkg/logger" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" @@ -31,10 +32,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/flux_aggregator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pg" @@ -88,7 +88,7 @@ func newBroadcasterHelperWithEthClient(t *testing.T, ethClient evmclient.Client, } }) config := evmtest.NewChainScopedConfig(t, globalConfig) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) mailMon := srvctest.Start(t, utils.NewMailboxMonitor(t.Name())) db := pgtest.NewSqlxDB(t) @@ -265,7 +265,7 @@ func (helper *broadcasterHelper) newLogListenerWithJob(name string) *simpleLogLi var rec received return &simpleLogListener{ db: db, - lggr: logger.TestLogger(t), + lggr: logger.Test(t), cfg: helper.config.Database(), name: name, received: &rec, @@ -281,7 +281,7 @@ func (listener *simpleLogListener) SkipMarkingConsumed(skip bool) { func (listener *simpleLogListener) HandleLog(lb log.Broadcast) { listener.received.Lock() defer listener.received.Unlock() - listener.lggr.Tracef("Listener %v HandleLog for block %v %v received at %v %v", listener.name, lb.RawLog().BlockNumber, lb.RawLog().BlockHash, lb.LatestBlockNumber(), lb.LatestBlockHash()) + logger.Tracef(listener.lggr, "Listener %v HandleLog for block %v %v received at %v %v", listener.name, lb.RawLog().BlockNumber, lb.RawLog().BlockHash, lb.LatestBlockNumber(), lb.LatestBlockHash()) listener.received.logs = append(listener.received.logs, lb.RawLog()) listener.received.broadcasts = append(listener.received.broadcasts, lb) diff --git a/core/chains/evm/log/integration_test.go b/core/chains/evm/log/integration_test.go index 137b4c7292a..edc04a4ada4 100644 --- a/core/chains/evm/log/integration_test.go +++ b/core/chains/evm/log/integration_test.go @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" logmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log/mocks" @@ -25,7 +26,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/srvctest" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -266,7 +266,7 @@ func TestBroadcaster_BackfillUnconsumedAfterCrash(t *testing.T) { helper := newBroadcasterHelper(t, 0, 1, logs, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].FinalityDepth = ptr[uint32](confs) }) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) orm := log.NewORM(helper.db, lggr, helper.config.Database(), cltest.FixtureChainID) listener := helper.newLogListenerWithJob("one") @@ -292,7 +292,7 @@ func TestBroadcaster_BackfillUnconsumedAfterCrash(t *testing.T) { helper := newBroadcasterHelper(t, 2, 1, logs, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].FinalityDepth = ptr[uint32](confs) }) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) orm := log.NewORM(helper.db, lggr, helper.config.Database(), cltest.FixtureChainID) listener := helper.newLogListenerWithJob("one") @@ -317,7 +317,7 @@ func TestBroadcaster_BackfillUnconsumedAfterCrash(t *testing.T) { helper := newBroadcasterHelper(t, 4, 1, logs, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].FinalityDepth = ptr[uint32](confs) }) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) orm := log.NewORM(helper.db, lggr, helper.config.Database(), cltest.FixtureChainID) listener := helper.newLogListenerWithJob("one") @@ -342,7 +342,7 @@ func TestBroadcaster_BackfillUnconsumedAfterCrash(t *testing.T) { helper := newBroadcasterHelper(t, 7, 1, logs[1:], func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].FinalityDepth = ptr[uint32](confs) }) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) orm := log.NewORM(helper.db, lggr, helper.config.Database(), cltest.FixtureChainID) listener := helper.newLogListenerWithJob("one") listener2 := helper.newLogListenerWithJob("two") @@ -468,7 +468,7 @@ func TestBroadcaster_BackfillInBatches(t *testing.T) { var backfillCount atomic.Int64 - lggr := logger.TestLogger(t) + lggr := logger.Test(t) backfillStart := lastStoredBlockHeight - numConfirmations - int64(blockBackfillDepth) // the first backfill should start from before the last stored head mockEth.CheckFilterLogs = func(fromBlock int64, toBlock int64) { @@ -539,7 +539,7 @@ func TestBroadcaster_BackfillALargeNumberOfLogs(t *testing.T) { var backfillCount atomic.Int64 - lggr := logger.TestLogger(t) + lggr := logger.Test(t) mockEth.CheckFilterLogs = func(fromBlock int64, toBlock int64) { times := backfillCount.Add(1) - 1 lggr.Warnf("Log Batch: --------- times %v - %v, %v", times, fromBlock, toBlock) @@ -1325,7 +1325,7 @@ func TestBroadcaster_AppendLogChannel(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) mailMon := srvctest.Start(t, utils.NewMailboxMonitor(t.Name())) - lb := log.NewBroadcaster(nil, ethClient, nil, logger.TestLogger(t), nil, mailMon) + lb := log.NewBroadcaster(nil, ethClient, nil, logger.Test(t), nil, mailMon) chCombined := lb.ExportedAppendLogChannel(ch1, ch2) chCombined = lb.ExportedAppendLogChannel(chCombined, ch3) diff --git a/core/chains/evm/log/orm.go b/core/chains/evm/log/orm.go index 4e51940f344..a2bcab6e785 100644 --- a/core/chains/evm/log/orm.go +++ b/core/chains/evm/log/orm.go @@ -9,9 +9,9 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/chains/evm/log/orm_test.go b/core/chains/evm/log/orm_test.go index 365bb354338..758cfa1d690 100644 --- a/core/chains/evm/log/orm_test.go +++ b/core/chains/evm/log/orm_test.go @@ -10,18 +10,18 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) func TestORM_broadcasts(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() orm := log.NewORM(db, lggr, cfg.Database(), cltest.FixtureChainID) @@ -134,7 +134,7 @@ func TestORM_broadcasts(t *testing.T) { func TestORM_pending(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) orm := log.NewORM(db, lggr, cfg.Database(), cltest.FixtureChainID) num, err := orm.GetPendingMinBlock() @@ -160,7 +160,7 @@ func TestORM_pending(t *testing.T) { func TestORM_MarkUnconsumed(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() orm := log.NewORM(db, lggr, cfg.Database(), cltest.FixtureChainID) @@ -259,7 +259,7 @@ func TestORM_Reinitialize(t *testing.T) { t.Run(tt.name, func(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) orm := log.NewORM(db, lggr, cfg.Database(), cltest.FixtureChainID) jobID := cltest.MustInsertV2JobSpec(t, db, common.BigToAddress(big.NewInt(rand.Int63()))).ID diff --git a/core/chains/evm/log/pool.go b/core/chains/evm/log/pool.go index 4511c26eb60..7d534ff4103 100644 --- a/core/chains/evm/log/pool.go +++ b/core/chains/evm/log/pool.go @@ -8,7 +8,7 @@ import ( heaps "github.com/theodesp/go-heaps" pairingHeap "github.com/theodesp/go-heaps/pairing" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" ) // The Log Pool interface. @@ -59,7 +59,7 @@ func newLogPool(lggr logger.Logger) *logPool { hashesByBlockNumbers: make(map[uint64]map[common.Hash]struct{}), logsByBlockHash: make(map[common.Hash]map[uint]map[uint]types.Log), heap: pairingHeap.New(), - logger: lggr.Named("LogPool"), + logger: logger.Named(lggr, "LogPool"), } } diff --git a/core/chains/evm/log/pool_test.go b/core/chains/evm/log/pool_test.go index 9e760f57262..c4789f5acd4 100644 --- a/core/chains/evm/log/pool_test.go +++ b/core/chains/evm/log/pool_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -49,7 +49,7 @@ var ( func TestUnit_AddLog(t *testing.T) { t.Parallel() - var p iLogPool = newLogPool(logger.TestLogger(t)) + var p iLogPool = newLogPool(logger.Test(t)) blockHash := common.BigToHash(big.NewInt(1)) l1 := types.Log{ @@ -105,7 +105,7 @@ func TestUnit_AddLog(t *testing.T) { func TestUnit_GetAndDeleteAll(t *testing.T) { t.Parallel() - var p iLogPool = newLogPool(logger.TestLogger(t)) + var p iLogPool = newLogPool(logger.Test(t)) p.addLog(L1) p.addLog(L1) // duplicate an add p.addLog(L21) @@ -139,7 +139,7 @@ func TestUnit_GetAndDeleteAll(t *testing.T) { func TestUnit_GetLogsToSendWhenEmptyPool(t *testing.T) { t.Parallel() - var p iLogPool = newLogPool(logger.TestLogger(t)) + var p iLogPool = newLogPool(logger.Test(t)) logsOnBlocks, minBlockNumToSend := p.getLogsToSend(1) assert.Equal(t, int64(0), minBlockNumToSend) assert.ElementsMatch(t, []logsOnBlock{}, logsOnBlocks) @@ -206,7 +206,7 @@ func TestUnit_GetLogsToSend(t *testing.T) { }, } - var p iLogPool = newLogPool(logger.TestLogger(t)) + var p iLogPool = newLogPool(logger.Test(t)) p.addLog(L1) p.addLog(L21) p.addLog(L3) @@ -222,7 +222,7 @@ func TestUnit_GetLogsToSend(t *testing.T) { func TestUnit_DeleteOlderLogsWhenEmptyPool(t *testing.T) { t.Parallel() - var p iLogPool = newLogPool(logger.TestLogger(t)) + var p iLogPool = newLogPool(logger.Test(t)) keptDepth := p.deleteOlderLogs(1) var expectedKeptDepth *int64 require.Equal(t, expectedKeptDepth, keptDepth) @@ -286,7 +286,7 @@ func TestUnit_DeleteOlderLogs(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - var p iLogPool = newLogPool(logger.TestLogger(t)) + var p iLogPool = newLogPool(logger.Test(t)) p.addLog(L1) p.addLog(L21) p.addLog(L3) @@ -302,7 +302,7 @@ func TestUnit_DeleteOlderLogs(t *testing.T) { func TestUnit_RemoveBlockWhenEmptyPool(t *testing.T) { t.Parallel() - var p iLogPool = newLogPool(logger.TestLogger(t)) + var p iLogPool = newLogPool(logger.Test(t)) p.removeBlock(L1.BlockHash, L1.BlockNumber) } @@ -343,7 +343,7 @@ func TestUnit_RemoveBlock(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - var p iLogPool = newLogPool(logger.TestLogger(t)) + var p iLogPool = newLogPool(logger.Test(t)) p.addLog(L21) p.addLog(L22) p.addLog(L23) diff --git a/core/chains/evm/log/registrations.go b/core/chains/evm/log/registrations.go index f9cc933d0d0..73f197a6ab6 100644 --- a/core/chains/evm/log/registrations.go +++ b/core/chains/evm/log/registrations.go @@ -9,10 +9,10 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) @@ -70,13 +70,13 @@ type ( subscribers map[*subscriber][][]Topic ) -func newRegistrations(logger logger.Logger, evmChainID big.Int) *registrations { +func newRegistrations(lggr logger.Logger, evmChainID big.Int) *registrations { return ®istrations{ registeredSubs: make(map[*subscriber]struct{}), jobIDAddrs: make(map[int32]map[common.Address]struct{}), handlersByConfs: make(map[uint32]*handler), evmChainID: evmChainID, - logger: logger.Named("Registrations"), + logger: logger.Named(lggr, "Registrations"), } } @@ -85,7 +85,7 @@ func (r *registrations) addSubscriber(sub *subscriber) (needsResubscribe bool) { r.logger.Panicw(err.Error(), "err", err, "addr", sub.opts.Contract.Hex(), "jobID", sub.listener.JobID()) } - r.logger.Tracef("Added subscription %p with job ID %v", sub, sub.listener.JobID()) + logger.Tracef(r.logger, "Added subscription %p with job ID %v", sub, sub.listener.JobID()) handler, exists := r.handlersByConfs[sub.opts.MinIncomingConfirmations] if !exists { @@ -142,7 +142,7 @@ func (r *registrations) removeSubscriber(sub *subscriber) (needsResubscribe bool if err := r.checkRemoveSubscriber(sub); err != nil { r.logger.Panicw(err.Error(), "err", err, "addr", sub.opts.Contract.Hex(), "jobID", sub.listener.JobID()) } - r.logger.Tracef("Removed subscription %p with job ID %v", sub, sub.listener.JobID()) + logger.Tracef(r.logger, "Removed subscription %p with job ID %v", sub, sub.listener.JobID()) handlers, exists := r.handlersByConfs[sub.opts.MinIncomingConfirmations] if !exists { @@ -284,7 +284,7 @@ func (r *handler) addSubscriber(sub *subscriber, handlersWithGreaterConfs []*han for topic, topicValueFilters := range sub.opts.LogsWithTopics { if _, exists := r.lookupSubs[addr][topic]; !exists { - r.logger.Tracef("No existing sub for addr %s and topic %s at this MinIncomingConfirmations of %v", addr.Hex(), topic.Hex(), sub.opts.MinIncomingConfirmations) + logger.Tracef(r.logger, "No existing sub for addr %s and topic %s at this MinIncomingConfirmations of %v", addr.Hex(), topic.Hex(), sub.opts.MinIncomingConfirmations) r.lookupSubs[addr][topic] = make(subscribers) func() { @@ -295,11 +295,11 @@ func (r *handler) addSubscriber(sub *subscriber, handlersWithGreaterConfs []*han // again since even the worst case lookback is already covered for _, existingHandler := range handlersWithGreaterConfs { if _, exists := existingHandler.lookupSubs[addr][topic]; exists { - r.logger.Tracef("Sub already exists for addr %s and topic %s at greater than this MinIncomingConfirmations of %v. Resubscribe is not required", addr.Hex(), topic.Hex(), sub.opts.MinIncomingConfirmations) + logger.Tracef(r.logger, "Sub already exists for addr %s and topic %s at greater than this MinIncomingConfirmations of %v. Resubscribe is not required", addr.Hex(), topic.Hex(), sub.opts.MinIncomingConfirmations) return } } - r.logger.Tracef("No sub exists for addr %s and topic %s at this or greater MinIncomingConfirmations of %v. Resubscribe is required", addr.Hex(), topic.Hex(), sub.opts.MinIncomingConfirmations) + logger.Tracef(r.logger, "No sub exists for addr %s and topic %s at this or greater MinIncomingConfirmations of %v. Resubscribe is required", addr.Hex(), topic.Hex(), sub.opts.MinIncomingConfirmations) needsResubscribe = true } }() @@ -332,7 +332,7 @@ func (r *handler) removeSubscriber(sub *subscriber, allHandlers map[uint32]*hand // cleanup and resubscribe if necessary if len(topicMap) == 0 { - r.logger.Tracef("No subs left for addr %s and topic %s at this MinIncomingConfirmations of %v", addr.Hex(), topic.Hex(), sub.opts.MinIncomingConfirmations) + logger.Tracef(r.logger, "No subs left for addr %s and topic %s at this MinIncomingConfirmations of %v", addr.Hex(), topic.Hex(), sub.opts.MinIncomingConfirmations) func() { if !needsResubscribe { @@ -344,12 +344,12 @@ func (r *handler) removeSubscriber(sub *subscriber, allHandlers map[uint32]*hand continue } if _, exists := otherHandler.lookupSubs[addr][topic]; exists { - r.logger.Tracef("Sub still exists for addr %s and topic %s. Resubscribe will not be performed", addr.Hex(), topic.Hex()) + logger.Tracef(r.logger, "Sub still exists for addr %s and topic %s. Resubscribe will not be performed", addr.Hex(), topic.Hex()) return } } - r.logger.Tracef("No sub exists for addr %s and topic %s. Resubscribe will be performed", addr.Hex(), topic.Hex()) + logger.Tracef(r.logger, "No sub exists for addr %s and topic %s. Resubscribe will be performed", addr.Hex(), topic.Hex()) needsResubscribe = true } }() diff --git a/core/chains/evm/log/registrations_test.go b/core/chains/evm/log/registrations_test.go index c9d4eba5898..0682564fe72 100644 --- a/core/chains/evm/log/registrations_test.go +++ b/core/chains/evm/log/registrations_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -26,7 +26,7 @@ func newTestListener(t *testing.T, jobID int32) testListener { } func newTestRegistrations(t *testing.T) *registrations { - return newRegistrations(logger.TestLogger(t), *testutils.FixtureChainID) + return newRegistrations(logger.Test(t), *testutils.FixtureChainID) } func newTopic() Topic { diff --git a/core/chains/evm/logpoller/disabled.go b/core/chains/evm/logpoller/disabled.go index 4bcf1c50863..05d591042f4 100644 --- a/core/chains/evm/logpoller/disabled.go +++ b/core/chains/evm/logpoller/disabled.go @@ -39,7 +39,9 @@ func (disabled) UnregisterFilter(name string, qopts ...pg.QOpt) error { return E func (disabled) HasFilter(name string) bool { return false } -func (disabled) LatestBlock(qopts ...pg.QOpt) (int64, error) { return -1, ErrDisabled } +func (disabled) LatestBlock(qopts ...pg.QOpt) (LogPollerBlock, error) { + return LogPollerBlock{}, ErrDisabled +} func (disabled) GetBlocksRange(ctx context.Context, numbers []uint64, qopts ...pg.QOpt) ([]LogPollerBlock, error) { return nil, ErrDisabled @@ -69,7 +71,7 @@ func (disabled) IndexedLogsByBlockRange(start, end int64, eventSig common.Hash, return nil, ErrDisabled } -func (d disabled) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { +func (d disabled) IndexedLogsByTxHash(eventSig common.Hash, address common.Address, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { return nil, ErrDisabled } @@ -104,3 +106,7 @@ func (d disabled) IndexedLogsCreatedAfter(eventSig common.Hash, address common.A func (d disabled) LatestBlockByEventSigsAddrsWithConfs(fromBlock int64, eventSigs []common.Hash, addresses []common.Address, confs Confirmations, qopts ...pg.QOpt) (int64, error) { return 0, ErrDisabled } + +func (d disabled) LogsDataWordBetween(eventSig common.Hash, address common.Address, wordIndexMin, wordIndexMax int, wordValue common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) { + return nil, ErrDisabled +} diff --git a/core/chains/evm/logpoller/helper_test.go b/core/chains/evm/logpoller/helper_test.go index 8415641c402..9e48690a249 100644 --- a/core/chains/evm/logpoller/helper_test.go +++ b/core/chains/evm/logpoller/helper_test.go @@ -19,12 +19,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) @@ -46,7 +46,7 @@ type TestHarness struct { } func SetupTH(t testing.TB, useFinalityTag bool, finalityDepth, backfillBatchSize, rpcBatchSize, keepFinalizedBlocksDepth int64) TestHarness { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) chainID := testutils.NewRandomEVMChainID() chainID2 := testutils.NewRandomEVMChainID() db := pgtest.NewSqlxDB(t) @@ -92,7 +92,7 @@ func SetupTH(t testing.TB, useFinalityTag bool, finalityDepth, backfillBatchSize func (th *TestHarness) PollAndSaveLogs(ctx context.Context, currentBlockNumber int64) int64 { th.LogPoller.PollAndSaveLogs(ctx, currentBlockNumber) latest, _ := th.LogPoller.LatestBlock(pg.WithParentCtx(ctx)) - return latest + 1 + return latest.BlockNumber + 1 } func (th *TestHarness) assertDontHave(t *testing.T, start, end int) { diff --git a/core/chains/evm/logpoller/log_poller.go b/core/chains/evm/logpoller/log_poller.go index 54999cbdfb1..de1999da260 100644 --- a/core/chains/evm/logpoller/log_poller.go +++ b/core/chains/evm/logpoller/log_poller.go @@ -20,10 +20,11 @@ import ( "github.com/pkg/errors" "golang.org/x/exp/maps" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/v2/core/utils/mathutil" @@ -31,13 +32,13 @@ import ( //go:generate mockery --quiet --name LogPoller --output ./mocks/ --case=underscore --structname LogPoller --filename log_poller.go type LogPoller interface { - services.ServiceCtx + services.Service Replay(ctx context.Context, fromBlock int64) error ReplayAsync(fromBlock int64) RegisterFilter(filter Filter, qopts ...pg.QOpt) error UnregisterFilter(name string, qopts ...pg.QOpt) error HasFilter(name string) bool - LatestBlock(qopts ...pg.QOpt) (int64, error) + LatestBlock(qopts ...pg.QOpt) (LogPollerBlock, error) GetBlocksRange(ctx context.Context, numbers []uint64, qopts ...pg.QOpt) ([]LogPollerBlock, error) // General querying @@ -52,12 +53,13 @@ type LogPoller interface { IndexedLogs(eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) IndexedLogsByBlockRange(start, end int64, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, qopts ...pg.QOpt) ([]Log, error) IndexedLogsCreatedAfter(eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, after time.Time, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) - IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) + IndexedLogsByTxHash(eventSig common.Hash, address common.Address, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) IndexedLogsTopicGreaterThan(eventSig common.Hash, address common.Address, topicIndex int, topicValueMin common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) IndexedLogsTopicRange(eventSig common.Hash, address common.Address, topicIndex int, topicValueMin common.Hash, topicValueMax common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) IndexedLogsWithSigsExcluding(address common.Address, eventSigA, eventSigB common.Hash, topicIndex int, fromBlock, toBlock int64, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) LogsDataWordRange(eventSig common.Hash, address common.Address, wordIndex int, wordValueMin, wordValueMax common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) LogsDataWordGreaterThan(eventSig common.Hash, address common.Address, wordIndex int, wordValueMin common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) + LogsDataWordBetween(eventSig common.Hash, address common.Address, wordIndexMin, wordIndexMax int, wordValue common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) } type Confirmations int @@ -92,7 +94,7 @@ var ( ) type logPoller struct { - utils.StartStopOnce + services.StateMachine ec Client orm ORM lggr logger.Logger @@ -129,11 +131,13 @@ type logPoller struct { // support chain, polygon, which has 2s block times, we need RPCs roughly with <= 500ms latency func NewLogPoller(orm ORM, ec Client, lggr logger.Logger, pollPeriod time.Duration, useFinalityTag bool, finalityDepth int64, backfillBatchSize int64, rpcBatchSize int64, keepFinalizedBlocksDepth int64) *logPoller { - + ctx, cancel := context.WithCancel(context.Background()) return &logPoller{ + ctx: ctx, + cancel: cancel, ec: ec, orm: orm, - lggr: lggr.Named("LogPoller"), + lggr: logger.Named(lggr, "LogPoller"), replayStart: make(chan int64), replayComplete: make(chan error), pollPeriod: pollPeriod, @@ -370,18 +374,15 @@ func (lp *logPoller) recvReplayComplete() { func (lp *logPoller) ReplayAsync(fromBlock int64) { lp.wg.Add(1) go func() { - if err := lp.Replay(context.Background(), fromBlock); err != nil { + if err := lp.Replay(lp.ctx, fromBlock); err != nil { lp.lggr.Error(err) } lp.wg.Done() }() } -func (lp *logPoller) Start(parentCtx context.Context) error { +func (lp *logPoller) Start(context.Context) error { return lp.StartOnce("LogPoller", func() error { - ctx, cancel := context.WithCancel(parentCtx) - lp.ctx = ctx - lp.cancel = cancel lp.wg.Add(1) go lp.run() return nil @@ -465,6 +466,7 @@ func (lp *logPoller) run() { // Serially process replay requests. lp.lggr.Infow("Executing replay", "fromBlock", fromBlock, "requested", fromBlockReq) lp.PollAndSaveLogs(lp.ctx, fromBlock) + lp.lggr.Infow("Executing replay finished", "fromBlock", fromBlock, "requested", fromBlockReq) } } else { lp.lggr.Errorw("Error executing replay, could not get fromBlock", "err", err) @@ -573,13 +575,14 @@ func (lp *logPoller) BackupPollAndSaveLogs(ctx context.Context, backupPollerBloc lastSafeBackfillBlock := latestFinalizedBlockNumber - 1 if lastSafeBackfillBlock >= lp.backupPollerNextBlock { - lp.lggr.Infow("Backup poller backfilling logs", "start", lp.backupPollerNextBlock, "end", lastSafeBackfillBlock) + lp.lggr.Infow("Backup poller started backfilling logs", "start", lp.backupPollerNextBlock, "end", lastSafeBackfillBlock) if err = lp.backfill(ctx, lp.backupPollerNextBlock, lastSafeBackfillBlock); err != nil { // If there's an error backfilling, we can just return and retry from the last block saved // since we don't save any blocks on backfilling. We may re-insert the same logs but thats ok. lp.lggr.Warnw("Backup poller failed", "err", err) return } + lp.lggr.Infow("Backup poller finished backfilling", "start", lp.backupPollerNextBlock, "end", lastSafeBackfillBlock) lp.backupPollerNextBlock = lastSafeBackfillBlock + 1 } } @@ -659,7 +662,7 @@ func (lp *logPoller) backfill(ctx context.Context, start, end int64) error { } } if batchSize == 1 { - lp.lggr.Criticalw("Too many log results in a single block, failed to retrieve logs! Node may be running in a degraded state.", "err", err, "from", from, "to", to, "LogBackfillBatchSize", lp.backfillBatchSize) + logger.Criticalw(lp.lggr, "Too many log results in a single block, failed to retrieve logs! Node may be running in a degraded state.", "err", err, "from", from, "to", to, "LogBackfillBatchSize", lp.backfillBatchSize) return err } batchSize /= 2 @@ -676,9 +679,7 @@ func (lp *logPoller) backfill(ctx context.Context, start, end int64) error { } lp.lggr.Debugw("Backfill found logs", "from", from, "to", to, "logs", len(gethLogs), "blocks", blocks) - err = lp.orm.Q().WithOpts(pg.WithParentCtx(ctx)).Transaction(func(tx pg.Queryer) error { - return lp.orm.InsertLogs(convertLogs(gethLogs, blocks, lp.lggr, lp.ec.ConfiguredChainID()), pg.WithQueryer(tx)) - }) + err = lp.orm.InsertLogs(convertLogs(gethLogs, blocks, lp.lggr, lp.ec.ConfiguredChainID()), pg.WithParentCtx(ctx)) if err != nil { lp.lggr.Warnw("Unable to insert logs, retrying", "err", err, "from", from, "to", to) return err @@ -747,21 +748,7 @@ func (lp *logPoller) getCurrentBlockMaybeHandleReorg(ctx context.Context, curren // the canonical set per read. Typically, if an application took action on a log // it would be saved elsewhere e.g. evm.txes, so it seems better to just support the fast reads. // Its also nicely analogous to reading from the chain itself. - err2 = lp.orm.Q().WithOpts(pg.WithParentCtx(ctx)).Transaction(func(tx pg.Queryer) error { - // These deletes are bounded by reorg depth, so they are - // fast and should not slow down the log readers. - err3 := lp.orm.DeleteBlocksAfter(blockAfterLCA.Number, pg.WithQueryer(tx)) - if err3 != nil { - lp.lggr.Warnw("Unable to clear reorged blocks, retrying", "err", err3) - return err3 - } - err3 = lp.orm.DeleteLogsAfter(blockAfterLCA.Number, pg.WithQueryer(tx)) - if err3 != nil { - lp.lggr.Warnw("Unable to clear reorged logs, retrying", "err", err3) - return err3 - } - return nil - }) + err2 = lp.orm.DeleteLogsAndBlocksAfter(blockAfterLCA.Number, pg.WithParentCtx(ctx)) if err2 != nil { // If we error on db commit, we can't know if the tx went through or not. // We return an error here which will cause us to restart polling from lastBlockSaved + 1 @@ -846,20 +833,11 @@ func (lp *logPoller) PollAndSaveLogs(ctx context.Context, currentBlockNumber int return } lp.lggr.Debugw("Unfinalized log query", "logs", len(logs), "currentBlockNumber", currentBlockNumber, "blockHash", currentBlock.Hash, "timestamp", currentBlock.Timestamp.Unix()) - err = lp.orm.Q().WithOpts(pg.WithParentCtx(ctx)).Transaction(func(tx pg.Queryer) error { - if err2 := lp.orm.InsertBlock(h, currentBlockNumber, currentBlock.Timestamp, latestFinalizedBlockNumber, pg.WithQueryer(tx)); err2 != nil { - return err2 - } - if len(logs) == 0 { - return nil - } - return lp.orm.InsertLogs(convertLogs(logs, - []LogPollerBlock{{BlockNumber: currentBlockNumber, - BlockTimestamp: currentBlock.Timestamp}}, - lp.lggr, - lp.ec.ConfiguredChainID(), - ), pg.WithQueryer(tx)) - }) + block := NewLogPollerBlock(h, currentBlockNumber, currentBlock.Timestamp, latestFinalizedBlockNumber) + err = lp.orm.InsertLogsWithBlock( + convertLogs(logs, []LogPollerBlock{block}, lp.lggr, lp.ec.ConfiguredChainID()), + block, + ) if err != nil { lp.lggr.Warnw("Unable to save logs resuming from last saved block + 1", "err", err, "block", currentBlockNumber) return @@ -939,7 +917,7 @@ func (lp *logPoller) findBlockAfterLCA(ctx context.Context, current *evmtypes.He return nil, err } } - lp.lggr.Criticalw("Reorg greater than finality depth detected", "finalityTag", lp.useFinalityTag, "current", current.Number, "latestFinalized", latestFinalizedBlockNumber) + logger.Criticalw(lp.lggr, "Reorg greater than finality depth detected", "finalityTag", lp.useFinalityTag, "current", current.Number, "latestFinalized", latestFinalizedBlockNumber) rerr := errors.New("Reorg greater than finality depth") lp.SvcErrBuffer.Append(rerr) return nil, rerr @@ -992,8 +970,8 @@ func (lp *logPoller) IndexedLogsCreatedAfter(eventSig common.Hash, address commo return lp.orm.SelectIndexedLogsCreatedAfter(address, eventSig, topicIndex, topicValues, after, confs, qopts...) } -func (lp *logPoller) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { - return lp.orm.SelectIndexedLogsByTxHash(eventSig, txHash, qopts...) +func (lp *logPoller) IndexedLogsByTxHash(eventSig common.Hash, address common.Address, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { + return lp.orm.SelectIndexedLogsByTxHash(address, eventSig, txHash, qopts...) } // LogsDataWordGreaterThan note index is 0 based. @@ -1018,13 +996,13 @@ func (lp *logPoller) IndexedLogsTopicRange(eventSig common.Hash, address common. // LatestBlock returns the latest block the log poller is on. It tracks blocks to be able // to detect reorgs. -func (lp *logPoller) LatestBlock(qopts ...pg.QOpt) (int64, error) { +func (lp *logPoller) LatestBlock(qopts ...pg.QOpt) (LogPollerBlock, error) { b, err := lp.orm.SelectLatestBlock(qopts...) if err != nil { - return 0, err + return LogPollerBlock{}, err } - return b.BlockNumber, nil + return *b, nil } func (lp *logPoller) BlockByNumber(n int64, qopts ...pg.QOpt) (*LogPollerBlock, error) { @@ -1044,6 +1022,19 @@ func (lp *logPoller) LatestBlockByEventSigsAddrsWithConfs(fromBlock int64, event return lp.orm.SelectLatestBlockByEventSigsAddrsWithConfs(fromBlock, eventSigs, addresses, confs, qopts...) } +// LogsDataWordBetween retrieves a slice of Log records that match specific criteria. +// Besides generic filters like eventSig, address and confs, it also verifies data content against wordValue +// data[wordIndexMin] <= wordValue <= data[wordIndexMax]. +// +// Passing the same value for wordIndexMin and wordIndexMax will check the equality of the wordValue at that index. +// Leading to returning logs matching: data[wordIndexMin] == wordValue. +// +// This function is particularly useful for filtering logs by data word values and their positions within the event data. +// It returns an empty slice if no logs match the provided criteria. +func (lp *logPoller) LogsDataWordBetween(eventSig common.Hash, address common.Address, wordIndexMin, wordIndexMax int, wordValue common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) { + return lp.orm.SelectLogsDataWordBetween(address, eventSig, wordIndexMin, wordIndexMax, wordValue, confs, qopts...) +} + // GetBlocksRange tries to get the specified block numbers from the log pollers // blocks table. It falls back to the RPC for any unfulfilled requested blocks. func (lp *logPoller) GetBlocksRange(ctx context.Context, numbers []uint64, qopts ...pg.QOpt) ([]LogPollerBlock, error) { diff --git a/core/chains/evm/logpoller/log_poller_internal_test.go b/core/chains/evm/logpoller/log_poller_internal_test.go index b9474158a6b..e3ba8b655e8 100644 --- a/core/chains/evm/logpoller/log_poller_internal_test.go +++ b/core/chains/evm/logpoller/log_poller_internal_test.go @@ -20,12 +20,14 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -54,7 +56,7 @@ func TestLogPoller_RegisterFilter(t *testing.T) { a1 := common.HexToAddress("0x2ab9a2dc53736b361b72d900cdf9f78f9406fbbb") a2 := common.HexToAddress("0x2ab9a2dc53736b361b72d900cdf9f78f9406fbbc") - lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.ErrorLevel) + lggr, observedLogs := logger.TestObserved(t, zapcore.ErrorLevel) chainID := testutils.NewRandomEVMChainID() db := pgtest.NewSqlxDB(t) @@ -136,7 +138,7 @@ func TestLogPoller_RegisterFilter(t *testing.T) { func TestLogPoller_ConvertLogs(t *testing.T) { t.Parallel() - lggr := logger.TestLogger(t) + lggr := logger.Test(t) topics := []common.Hash{EmitterABI.Events["Log1"].ID} @@ -191,7 +193,7 @@ func TestFilterName(t *testing.T) { func TestLogPoller_BackupPollerStartup(t *testing.T) { addr := common.HexToAddress("0x2ab9a2dc53736b361b72d900cdf9f78f9406fbbc") - lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.WarnLevel) + lggr, observedLogs := logger.TestObserved(t, zapcore.WarnLevel) chainID := testutils.FixtureChainID db := pgtest.NewSqlxDB(t) orm := NewORM(chainID, db, lggr, pgtest.NewQConfig(true)) @@ -233,9 +235,8 @@ func TestLogPoller_BackupPollerStartup(t *testing.T) { func TestLogPoller_Replay(t *testing.T) { t.Parallel() addr := common.HexToAddress("0x2ab9a2dc53736b361b72d900cdf9f78f9406fbbc") - tctx := testutils.Context(t) - lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.ErrorLevel) + lggr, observedLogs := logger.TestObserved(t, zapcore.ErrorLevel) chainID := testutils.FixtureChainID db := pgtest.NewSqlxDB(t) orm := NewORM(chainID, db, lggr, pgtest.NewQConfig(true)) @@ -259,48 +260,39 @@ func TestLogPoller_Replay(t *testing.T) { lp := NewLogPoller(orm, ec, lggr, time.Hour, false, 3, 3, 3, 20) // process 1 log in block 3 - lp.PollAndSaveLogs(tctx, 4) + lp.PollAndSaveLogs(testutils.Context(t), 4) latest, err := lp.LatestBlock() require.NoError(t, err) - require.Equal(t, int64(4), latest) + require.Equal(t, int64(4), latest.BlockNumber) t.Run("abort before replayStart received", func(t *testing.T) { // Replay() should abort immediately if caller's context is cancelled before request signal is read - ctx, cancel := context.WithCancel(tctx) + ctx, cancel := context.WithCancel(testutils.Context(t)) cancel() err = lp.Replay(ctx, 3) assert.ErrorIs(t, err, ErrReplayRequestAborted) }) - recvStartReplay := func(parentCtx context.Context, block int64, withTimeout bool) { - var err error - var ctx context.Context - var cancel context.CancelFunc - if withTimeout { - ctx, cancel = context.WithTimeout(parentCtx, testutils.WaitTimeout(t)) - } else { - ctx, cancel = context.WithCancel(parentCtx) - } - defer cancel() + recvStartReplay := func(ctx context.Context, block int64) { select { case fromBlock := <-lp.replayStart: assert.Equal(t, block, fromBlock) case <-ctx.Done(): - err = ctx.Err() + assert.NoError(t, ctx.Err(), "Timed out waiting to receive replay request from lp.replayStart") } - assert.NoError(t, err, "Timed out waiting to receive replay request from lp.replayStart") } // Replay() should return error code received from replayComplete t.Run("returns error code on replay complete", func(t *testing.T) { + ctx := testutils.Context(t) anyErr := errors.New("any error") done := make(chan struct{}) go func() { defer close(done) - recvStartReplay(tctx, 1, true) + recvStartReplay(ctx, 1) lp.replayComplete <- anyErr }() - assert.ErrorIs(t, lp.Replay(tctx, 1), anyErr) + assert.ErrorIs(t, lp.Replay(ctx, 1), anyErr) <-done }) @@ -310,7 +302,7 @@ func TestLogPoller_Replay(t *testing.T) { done := make(chan struct{}) go func() { defer close(done) - recvStartReplay(ctx, 4, false) + recvStartReplay(ctx, 4) cancel() }() assert.ErrorIs(t, lp.Replay(ctx, 4), ErrReplayInProgress) @@ -323,29 +315,33 @@ func TestLogPoller_Replay(t *testing.T) { t.Run("client abort doesnt hang run loop", func(t *testing.T) { lp.backupPollerNextBlock = 0 - timeLeft := testutils.WaitTimeout(t) - timeout := time.After(timeLeft) - ctx, cancel := context.WithCancel(tctx) + ctx := testutils.Context(t) - var wg sync.WaitGroup pass := make(chan struct{}) cancelled := make(chan struct{}) + rctx, rcancel := context.WithCancel(testutils.Context(t)) + var wg sync.WaitGroup + defer func() { wg.Wait() }() ec.On("FilterLogs", mock.Anything, mock.Anything).Once().Return([]types.Log{log1}, nil).Run(func(args mock.Arguments) { wg.Add(1) go func() { defer wg.Done() - assert.ErrorIs(t, lp.Replay(ctx, 4), ErrReplayInProgress) + assert.ErrorIs(t, lp.Replay(rctx, 4), ErrReplayInProgress) close(cancelled) }() }) ec.On("FilterLogs", mock.Anything, mock.Anything).Once().Return([]types.Log{log1}, nil).Run(func(args mock.Arguments) { - cancel() + rcancel() wg.Add(1) go func() { defer wg.Done() - lp.replayStart <- 4 - close(pass) + select { + case lp.replayStart <- 4: + close(pass) + case <-ctx.Done(): + return + } }() // We cannot return until we're sure that Replay() received the cancellation signal, // otherwise replayComplete<- might be sent first @@ -354,24 +350,13 @@ func TestLogPoller_Replay(t *testing.T) { ec.On("FilterLogs", mock.Anything, mock.Anything).Return([]types.Log{log1}, nil).Maybe() // in case task gets delayed by >= 100ms - lp.ctx, lp.cancel = context.WithCancel(tctx) - lp.wg.Add(1) - defer func() { - select { - case <-lp.replayStart: - default: - } - wg.Wait() - lp.cancel() - lp.wg.Wait() - }() + t.Cleanup(lp.reset) + require.NoError(t, lp.Start(ctx)) + t.Cleanup(func() { assert.NoError(t, lp.Close()) }) - go func() { - lp.run() - }() select { - case <-timeout: - assert.Failf(t, "lp.run() got stuck--failed to respond to second replay event within %s", timeLeft.String()) + case <-ctx.Done(): + t.Errorf("timed out waiting for lp.run() to respond to second replay event") case <-pass: } }) @@ -383,13 +368,18 @@ func TestLogPoller_Replay(t *testing.T) { t.Run("shutdown during replay", func(t *testing.T) { lp.backupPollerNextBlock = 0 - safeToExit := make(chan struct{}) pass := make(chan struct{}) + done := make(chan struct{}) + defer func() { <-done }() + ctx := testutils.Context(t) ec.On("FilterLogs", mock.Anything, mock.Anything).Once().Return([]types.Log{log1}, nil).Run(func(args mock.Arguments) { go func() { - lp.replayStart <- 4 - close(safeToExit) + defer close(done) + select { + case lp.replayStart <- 4: + case <-ctx.Done(): + } }() }) ec.On("FilterLogs", mock.Anything, mock.Anything).Once().Return([]types.Log{log1}, nil).Run(func(args mock.Arguments) { @@ -398,71 +388,57 @@ func TestLogPoller_Replay(t *testing.T) { }) ec.On("FilterLogs", mock.Anything, mock.Anything).Return([]types.Log{log1}, nil).Maybe() // in case task gets delayed by >= 100ms - timeLeft := testutils.WaitTimeout(t) - timeout := time.After(timeLeft) - require.NoError(t, lp.Start(tctx)) - - defer func() { - select { - case <-lp.replayStart: // unblock replayStart<- goroutine if it's stuck - default: - } - <-safeToExit - lp.Close() - }() + t.Cleanup(lp.reset) + require.NoError(t, lp.Start(ctx)) + t.Cleanup(func() { assert.NoError(t, lp.Close()) }) select { - case <-timeout: - assert.Failf(t, "lp.run() failed to respond to shutdown event during replay within %s", timeLeft.String()) + case <-ctx.Done(): + t.Error("timed out waiting for lp.run() to respond to shutdown event during replay") case <-pass: } }) // ReplayAsync should return as soon as replayStart is received t.Run("ReplayAsync success", func(t *testing.T) { - lp.ctx, lp.cancel = context.WithTimeout(tctx, testutils.WaitTimeout(t)) - defer func() { - lp.replayComplete <- nil - lp.cancel() - lp.wg.Wait() - }() + t.Cleanup(lp.reset) + require.NoError(t, lp.Start(testutils.Context(t))) + t.Cleanup(func() { assert.NoError(t, lp.Close()) }) - done := make(chan struct{}) - go func() { - lp.ReplayAsync(1) - close(done) - }() - recvStartReplay(tctx, 1, true) - <-done + lp.ReplayAsync(1) + + recvStartReplay(testutils.Context(t), 1) }) t.Run("ReplayAsync error", func(t *testing.T) { - timeLeft := testutils.WaitTimeout(t) - lp.ctx, lp.cancel = context.WithTimeout(tctx, timeLeft) - defer func() { - lp.cancel() - lp.wg.Wait() - }() + t.Cleanup(lp.reset) + require.NoError(t, lp.Start(testutils.Context(t))) + t.Cleanup(func() { assert.NoError(t, lp.Close()) }) + anyErr := errors.New("async error") observedLogs.TakeAll() lp.ReplayAsync(4) - recvStartReplay(tctx, 4, true) + recvStartReplay(testutils.Context(t), 4) select { case lp.replayComplete <- anyErr: time.Sleep(2 * time.Second) case <-lp.ctx.Done(): - assert.Failf(t, "failed to receive replayComplete signal within %s", timeLeft.String()) + t.Error("timed out waiting to send replaceComplete") } require.Equal(t, 1, observedLogs.Len()) assert.Equal(t, observedLogs.All()[0].Message, anyErr.Error()) }) } +func (lp *logPoller) reset() { + lp.StateMachine = services.StateMachine{} + lp.ctx, lp.cancel = context.WithCancel(context.Background()) +} + func Test_latestBlockAndFinalityDepth(t *testing.T) { - tctx := testutils.Context(t) - lggr, _ := logger.TestLoggerObserved(t, zapcore.ErrorLevel) + lggr := logger.Test(t) chainID := testutils.FixtureChainID db := pgtest.NewSqlxDB(t) orm := NewORM(chainID, db, lggr, pgtest.NewQConfig(true)) @@ -474,7 +450,7 @@ func Test_latestBlockAndFinalityDepth(t *testing.T) { ec.On("HeadByNumber", mock.Anything, mock.Anything).Return(&head, nil) lp := NewLogPoller(orm, ec, lggr, time.Hour, false, finalityDepth, 3, 3, 20) - latestBlock, lastFinalizedBlockNumber, err := lp.latestBlocks(tctx) + latestBlock, lastFinalizedBlockNumber, err := lp.latestBlocks(testutils.Context(t)) require.NoError(t, err) require.Equal(t, latestBlock.Number, head.Number) require.Equal(t, finalityDepth, latestBlock.Number-lastFinalizedBlockNumber) @@ -499,7 +475,7 @@ func Test_latestBlockAndFinalityDepth(t *testing.T) { lp := NewLogPoller(orm, ec, lggr, time.Hour, true, 3, 3, 3, 20) - latestBlock, lastFinalizedBlockNumber, err := lp.latestBlocks(tctx) + latestBlock, lastFinalizedBlockNumber, err := lp.latestBlocks(testutils.Context(t)) require.NoError(t, err) require.Equal(t, expectedLatestBlockNumber, latestBlock.Number) require.Equal(t, expectedLastFinalizedBlockNumber, lastFinalizedBlockNumber) @@ -516,7 +492,7 @@ func Test_latestBlockAndFinalityDepth(t *testing.T) { }) lp := NewLogPoller(orm, ec, lggr, time.Hour, true, 3, 3, 3, 20) - _, _, err := lp.latestBlocks(tctx) + _, _, err := lp.latestBlocks(testutils.Context(t)) require.Error(t, err) }) @@ -525,14 +501,14 @@ func Test_latestBlockAndFinalityDepth(t *testing.T) { ec.On("BatchCallContext", mock.Anything, mock.Anything).Return(fmt.Errorf("some error")) lp := NewLogPoller(orm, ec, lggr, time.Hour, true, 3, 3, 3, 20) - _, _, err := lp.latestBlocks(tctx) + _, _, err := lp.latestBlocks(testutils.Context(t)) require.Error(t, err) }) }) } func benchmarkFilter(b *testing.B, nFilters, nAddresses, nEvents int) { - lggr := logger.TestLogger(b) + lggr := logger.Test(b) lp := NewLogPoller(nil, nil, lggr, 1*time.Hour, false, 2, 3, 2, 1000) for i := 0; i < nFilters; i++ { var addresses []common.Address diff --git a/core/chains/evm/logpoller/log_poller_test.go b/core/chains/evm/logpoller/log_poller_test.go index 1ee8f4dcb78..82447bdb5f4 100644 --- a/core/chains/evm/logpoller/log_poller_test.go +++ b/core/chains/evm/logpoller/log_poller_test.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -34,7 +35,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -85,8 +85,8 @@ func populateDatabase(t testing.TB, o *logpoller.DbORM, chainID *big.Int) (commo func BenchmarkSelectLogsCreatedAfter(b *testing.B) { chainId := big.NewInt(137) - _, db := heavyweight.FullTestDBV2(b, "logs_scale", nil) - o := logpoller.NewORM(chainId, db, logger.TestLogger(b), pgtest.NewQConfig(false)) + _, db := heavyweight.FullTestDBV2(b, nil) + o := logpoller.NewORM(chainId, db, logger.Test(b), pgtest.NewQConfig(false)) event, address, _ := populateDatabase(b, o, chainId) // Setting searchDate to pick around 5k logs @@ -103,10 +103,10 @@ func BenchmarkSelectLogsCreatedAfter(b *testing.B) { func TestPopulateLoadedDB(t *testing.T) { t.Skip("Only for local load testing and query analysis") - _, db := heavyweight.FullTestDBV2(t, "logs_scale", nil) + _, db := heavyweight.FullTestDBV2(t, nil) chainID := big.NewInt(137) - o := logpoller.NewORM(big.NewInt(137), db, logger.TestLogger(t), pgtest.NewQConfig(true)) + o := logpoller.NewORM(big.NewInt(137), db, logger.Test(t), pgtest.NewQConfig(true)) event1, address1, address2 := populateDatabase(t, o, chainID) func() { @@ -311,8 +311,8 @@ func Test_BackupLogPoller(t *testing.T) { body.Transactions = types.Transactions{} // number of tx's must match # of logs for GetLogs() to succeed rawdb.WriteBody(th.EthDB, h.Hash(), h.Number.Uint64(), body) - currentBlock := th.PollAndSaveLogs(ctx, 1) - assert.Equal(t, int64(35), currentBlock) + currentBlockNumber := th.PollAndSaveLogs(ctx, 1) + assert.Equal(t, int64(35), currentBlockNumber) // simulate logs becoming available rawdb.WriteReceipts(th.EthDB, h.Hash(), h.Number.Uint64(), receipts) @@ -342,12 +342,12 @@ func Test_BackupLogPoller(t *testing.T) { markBlockAsFinalized(t, th, 34) // Run ordinary poller + backup poller at least once - currentBlock, _ = th.LogPoller.LatestBlock(pg.WithParentCtx(testutils.Context(t))) - th.LogPoller.PollAndSaveLogs(ctx, currentBlock+1) + currentBlock, _ := th.LogPoller.LatestBlock(pg.WithParentCtx(testutils.Context(t))) + th.LogPoller.PollAndSaveLogs(ctx, currentBlock.BlockNumber+1) th.LogPoller.BackupPollAndSaveLogs(ctx, 100) currentBlock, _ = th.LogPoller.LatestBlock(pg.WithParentCtx(testutils.Context(t))) - require.Equal(t, int64(37), currentBlock+1) + require.Equal(t, int64(37), currentBlock.BlockNumber+1) // logs still shouldn't show up, because we don't want to backfill the last finalized log // to help with reorg detection @@ -359,11 +359,11 @@ func Test_BackupLogPoller(t *testing.T) { markBlockAsFinalized(t, th, 35) // Run ordinary poller + backup poller at least once more - th.LogPoller.PollAndSaveLogs(ctx, currentBlock+1) + th.LogPoller.PollAndSaveLogs(ctx, currentBlockNumber+1) th.LogPoller.BackupPollAndSaveLogs(ctx, 100) currentBlock, _ = th.LogPoller.LatestBlock(pg.WithParentCtx(testutils.Context(t))) - require.Equal(t, int64(38), currentBlock+1) + require.Equal(t, int64(38), currentBlock.BlockNumber+1) // all 3 logs in block 34 should show up now, thanks to backup logger logs, err = th.LogPoller.Logs(30, 37, EmitterABI.Events["Log1"].ID, th.EmitterAddress1, @@ -471,6 +471,13 @@ func TestLogPoller_BackupPollAndSaveLogsWithDeepBlockDelay(t *testing.T) { // 1 -> 2 -> ... th.PollAndSaveLogs(ctx, 1) + // Check that latest block has the same properties as the head + latestBlock, err := th.LogPoller.LatestBlock() + require.NoError(t, err) + assert.Equal(t, latestBlock.BlockNumber, header.Number.Int64()) + assert.Equal(t, latestBlock.FinalizedBlockNumber, header.Number.Int64()) + assert.Equal(t, latestBlock.BlockHash, header.Hash()) + // Register filter err = th.LogPoller.RegisterFilter(logpoller.Filter{ Name: "Test Emitter", @@ -619,19 +626,19 @@ func TestLogPoller_BlockTimestamps(t *testing.T) { require.Len(t, gethLogs, 2) lb, _ := th.LogPoller.LatestBlock(pg.WithParentCtx(testutils.Context(t))) - th.PollAndSaveLogs(context.Background(), lb+1) + th.PollAndSaveLogs(ctx, lb.BlockNumber+1) lg1, err := th.LogPoller.Logs(0, 20, EmitterABI.Events["Log1"].ID, th.EmitterAddress1, - pg.WithParentCtx(testutils.Context(t))) + pg.WithParentCtx(ctx)) require.NoError(t, err) lg2, err := th.LogPoller.Logs(0, 20, EmitterABI.Events["Log2"].ID, th.EmitterAddress2, - pg.WithParentCtx(testutils.Context(t))) + pg.WithParentCtx(ctx)) require.NoError(t, err) // Logs should have correct timestamps - b, _ := th.Client.BlockByHash(context.Background(), lg1[0].BlockHash) + b, _ := th.Client.BlockByHash(ctx, lg1[0].BlockHash) t.Log(len(lg1), lg1[0].BlockTimestamp) assert.Equal(t, int64(b.Time()), lg1[0].BlockTimestamp.UTC().Unix(), time1) - b2, _ := th.Client.BlockByHash(context.Background(), lg2[0].BlockHash) + b2, _ := th.Client.BlockByHash(ctx, lg2[0].BlockHash) assert.Equal(t, int64(b2.Time()), lg2[0].BlockTimestamp.UTC().Unix(), time2) } @@ -644,7 +651,7 @@ func TestLogPoller_SynchronizedWithGeth(t *testing.T) { p := gopter.NewProperties(testParams) numChainInserts := 3 finalityDepth := 5 - lggr := logger.TestLogger(t) + lggr := logger.Test(t) db := pgtest.NewSqlxDB(t) owner := testutils.MustNewSimTransactor(t) @@ -667,9 +674,9 @@ func TestLogPoller_SynchronizedWithGeth(t *testing.T) { for i := 0; i < finalityDepth; i++ { // Have enough blocks that we could reorg the full finalityDepth-1. ec.Commit() } - currentBlock := int64(1) - lp.PollAndSaveLogs(testutils.Context(t), currentBlock) - currentBlock, err = lp.LatestBlock(pg.WithParentCtx(testutils.Context(t))) + currentBlockNumber := int64(1) + lp.PollAndSaveLogs(testutils.Context(t), currentBlockNumber) + currentBlock, err := lp.LatestBlock(pg.WithParentCtx(testutils.Context(t))) require.NoError(t, err) matchesGeth := func() bool { // Check every block is identical @@ -719,7 +726,7 @@ func TestLogPoller_SynchronizedWithGeth(t *testing.T) { require.NoError(t, err1) t.Logf("New latest (%v, %x), latest parent %x)\n", latest.NumberU64(), latest.Hash(), latest.ParentHash()) } - lp.PollAndSaveLogs(testutils.Context(t), currentBlock) + lp.PollAndSaveLogs(testutils.Context(t), currentBlock.BlockNumber) currentBlock, err = lp.LatestBlock(pg.WithParentCtx(testutils.Context(t))) require.NoError(t, err) } @@ -1245,7 +1252,7 @@ func TestGetReplayFromBlock(t *testing.T) { require.NoError(t, err) latest, err := th.LogPoller.LatestBlock(pg.WithParentCtx(testutils.Context(t))) require.NoError(t, err) - assert.Equal(t, latest, fromBlock) + assert.Equal(t, latest.BlockNumber, fromBlock) // Should take min(latest, requested) in this case requested. requested = int64(7) @@ -1257,7 +1264,7 @@ func TestGetReplayFromBlock(t *testing.T) { func TestLogPoller_DBErrorHandling(t *testing.T) { t.Parallel() ctx := testutils.Context(t) - lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.WarnLevel) + lggr, observedLogs := logger.TestObserved(t, zapcore.WarnLevel) chainID1 := testutils.NewRandomEVMChainID() chainID2 := testutils.NewRandomEVMChainID() db := pgtest.NewSqlxDB(t) @@ -1321,11 +1328,11 @@ func TestNotifyAfterInsert(t *testing.T) { // Use a non-transactional db for this test because notify events // are not delivered until the transaction is committed. var dbURL string - _, sqlxDB := heavyweight.FullTestDBV2(t, "notify_after_insert_log", func(c *chainlink.Config, s *chainlink.Secrets) { + _, sqlxDB := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { dbURL = s.Database.URL.URL().String() }) - lggr, _ := logger.TestLoggerObserved(t, zapcore.WarnLevel) + lggr, _ := logger.TestObserved(t, zapcore.WarnLevel) chainID := big.NewInt(1337) o := logpoller.NewORM(chainID, sqlxDB, lggr, pgtest.NewQConfig(true)) @@ -1379,7 +1386,7 @@ type getLogErrData struct { func TestTooManyLogResults(t *testing.T) { ctx := testutils.Context(t) ec := evmtest.NewEthClientMockWithDefaultChain(t) - lggr, obs := logger.TestLoggerObserved(t, zapcore.DebugLevel) + lggr, obs := logger.TestObserved(t, zapcore.DebugLevel) chainID := testutils.NewRandomEVMChainID() db := pgtest.NewSqlxDB(t) o := logpoller.NewORM(chainID, db, lggr, pgtest.NewQConfig(true)) @@ -1551,6 +1558,10 @@ func Test_PollAndSavePersistsFinalityInBlocks(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { th := SetupTH(t, tt.useFinalityTag, tt.finalityDepth, 3, 2, 1000) + // Should return error before the first poll and save + _, err := th.LogPoller.LatestBlock() + require.Error(t, err) + // Mark first block as finalized h := th.Client.Blockchain().CurrentHeader() th.Client.Blockchain().SetFinalized(h) @@ -1562,7 +1573,7 @@ func Test_PollAndSavePersistsFinalityInBlocks(t *testing.T) { th.PollAndSaveLogs(ctx, 1) - latestBlock, err := th.ORM.SelectLatestBlock() + latestBlock, err := th.LogPoller.LatestBlock() require.NoError(t, err) require.Equal(t, int64(numberOfBlocks), latestBlock.BlockNumber) require.Equal(t, tt.expectedFinalizedBlock, latestBlock.FinalizedBlockNumber) diff --git a/core/chains/evm/logpoller/mocks/log_poller.go b/core/chains/evm/logpoller/mocks/log_poller.go index f4357341646..fe4ccc965cc 100644 --- a/core/chains/evm/logpoller/mocks/log_poller.go +++ b/core/chains/evm/logpoller/mocks/log_poller.go @@ -164,32 +164,32 @@ func (_m *LogPoller) IndexedLogsByBlockRange(start int64, end int64, eventSig co return r0, r1 } -// IndexedLogsByTxHash provides a mock function with given fields: eventSig, txHash, qopts -func (_m *LogPoller) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]logpoller.Log, error) { +// IndexedLogsByTxHash provides a mock function with given fields: eventSig, address, txHash, qopts +func (_m *LogPoller) IndexedLogsByTxHash(eventSig common.Hash, address common.Address, txHash common.Hash, qopts ...pg.QOpt) ([]logpoller.Log, error) { _va := make([]interface{}, len(qopts)) for _i := range qopts { _va[_i] = qopts[_i] } var _ca []interface{} - _ca = append(_ca, eventSig, txHash) + _ca = append(_ca, eventSig, address, txHash) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 []logpoller.Log var r1 error - if rf, ok := ret.Get(0).(func(common.Hash, common.Hash, ...pg.QOpt) ([]logpoller.Log, error)); ok { - return rf(eventSig, txHash, qopts...) + if rf, ok := ret.Get(0).(func(common.Hash, common.Address, common.Hash, ...pg.QOpt) ([]logpoller.Log, error)); ok { + return rf(eventSig, address, txHash, qopts...) } - if rf, ok := ret.Get(0).(func(common.Hash, common.Hash, ...pg.QOpt) []logpoller.Log); ok { - r0 = rf(eventSig, txHash, qopts...) + if rf, ok := ret.Get(0).(func(common.Hash, common.Address, common.Hash, ...pg.QOpt) []logpoller.Log); ok { + r0 = rf(eventSig, address, txHash, qopts...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]logpoller.Log) } } - if rf, ok := ret.Get(1).(func(common.Hash, common.Hash, ...pg.QOpt) error); ok { - r1 = rf(eventSig, txHash, qopts...) + if rf, ok := ret.Get(1).(func(common.Hash, common.Address, common.Hash, ...pg.QOpt) error); ok { + r1 = rf(eventSig, address, txHash, qopts...) } else { r1 = ret.Error(1) } @@ -330,7 +330,7 @@ func (_m *LogPoller) IndexedLogsWithSigsExcluding(address common.Address, eventS } // LatestBlock provides a mock function with given fields: qopts -func (_m *LogPoller) LatestBlock(qopts ...pg.QOpt) (int64, error) { +func (_m *LogPoller) LatestBlock(qopts ...pg.QOpt) (logpoller.LogPollerBlock, error) { _va := make([]interface{}, len(qopts)) for _i := range qopts { _va[_i] = qopts[_i] @@ -339,15 +339,15 @@ func (_m *LogPoller) LatestBlock(qopts ...pg.QOpt) (int64, error) { _ca = append(_ca, _va...) ret := _m.Called(_ca...) - var r0 int64 + var r0 logpoller.LogPollerBlock var r1 error - if rf, ok := ret.Get(0).(func(...pg.QOpt) (int64, error)); ok { + if rf, ok := ret.Get(0).(func(...pg.QOpt) (logpoller.LogPollerBlock, error)); ok { return rf(qopts...) } - if rf, ok := ret.Get(0).(func(...pg.QOpt) int64); ok { + if rf, ok := ret.Get(0).(func(...pg.QOpt) logpoller.LogPollerBlock); ok { r0 = rf(qopts...) } else { - r0 = ret.Get(0).(int64) + r0 = ret.Get(0).(logpoller.LogPollerBlock) } if rf, ok := ret.Get(1).(func(...pg.QOpt) error); ok { @@ -522,6 +522,39 @@ func (_m *LogPoller) LogsCreatedAfter(eventSig common.Hash, address common.Addre return r0, r1 } +// LogsDataWordBetween provides a mock function with given fields: eventSig, address, wordIndexMin, wordIndexMax, wordValue, confs, qopts +func (_m *LogPoller) LogsDataWordBetween(eventSig common.Hash, address common.Address, wordIndexMin int, wordIndexMax int, wordValue common.Hash, confs logpoller.Confirmations, qopts ...pg.QOpt) ([]logpoller.Log, error) { + _va := make([]interface{}, len(qopts)) + for _i := range qopts { + _va[_i] = qopts[_i] + } + var _ca []interface{} + _ca = append(_ca, eventSig, address, wordIndexMin, wordIndexMax, wordValue, confs) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []logpoller.Log + var r1 error + if rf, ok := ret.Get(0).(func(common.Hash, common.Address, int, int, common.Hash, logpoller.Confirmations, ...pg.QOpt) ([]logpoller.Log, error)); ok { + return rf(eventSig, address, wordIndexMin, wordIndexMax, wordValue, confs, qopts...) + } + if rf, ok := ret.Get(0).(func(common.Hash, common.Address, int, int, common.Hash, logpoller.Confirmations, ...pg.QOpt) []logpoller.Log); ok { + r0 = rf(eventSig, address, wordIndexMin, wordIndexMax, wordValue, confs, qopts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]logpoller.Log) + } + } + + if rf, ok := ret.Get(1).(func(common.Hash, common.Address, int, int, common.Hash, logpoller.Confirmations, ...pg.QOpt) error); ok { + r1 = rf(eventSig, address, wordIndexMin, wordIndexMax, wordValue, confs, qopts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // LogsDataWordGreaterThan provides a mock function with given fields: eventSig, address, wordIndex, wordValueMin, confs, qopts func (_m *LogPoller) LogsDataWordGreaterThan(eventSig common.Hash, address common.Address, wordIndex int, wordValueMin common.Hash, confs logpoller.Confirmations, qopts ...pg.QOpt) ([]logpoller.Log, error) { _va := make([]interface{}, len(qopts)) diff --git a/core/chains/evm/logpoller/models.go b/core/chains/evm/logpoller/models.go index 9c55786777c..87ddd079a5b 100644 --- a/core/chains/evm/logpoller/models.go +++ b/core/chains/evm/logpoller/models.go @@ -56,3 +56,12 @@ func (l *Log) ToGethLog() types.Log { Index: uint(l.LogIndex), } } + +func NewLogPollerBlock(blockHash common.Hash, blockNumber int64, timestamp time.Time, finalizedBlockNumber int64) LogPollerBlock { + return LogPollerBlock{ + BlockHash: blockHash, + BlockNumber: blockNumber, + BlockTimestamp: timestamp, + FinalizedBlockNumber: finalizedBlockNumber, + } +} diff --git a/core/chains/evm/logpoller/observability.go b/core/chains/evm/logpoller/observability.go index 7f54fa9f09a..a7a0d3c03d5 100644 --- a/core/chains/evm/logpoller/observability.go +++ b/core/chains/evm/logpoller/observability.go @@ -5,15 +5,23 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/smartcontractkit/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) +type queryType string + +const ( + create queryType = "create" + read queryType = "read" + del queryType = "delete" +) + var ( sqlLatencyBuckets = []float64{ float64(1 * time.Millisecond), @@ -41,51 +49,63 @@ var ( Name: "log_poller_query_duration", Help: "Measures duration of Log Poller's queries fetching logs", Buckets: sqlLatencyBuckets, - }, []string{"evmChainID", "query"}) + }, []string{"evmChainID", "query", "type"}) lpQueryDataSets = promauto.NewGaugeVec(prometheus.GaugeOpts{ Name: "log_poller_query_dataset_size", Help: "Measures size of the datasets returned by Log Poller's queries", - }, []string{"evmChainID", "query"}) + }, []string{"evmChainID", "query", "type"}) + lpLogsInserted = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "log_poller_logs_inserted", + Help: "Counter to track number of logs inserted by Log Poller", + }, []string{"evmChainID"}) + lpBlockInserted = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "log_poller_blocks_inserted", + Help: "Counter to track number of blocks inserted by Log Poller", + }, []string{"evmChainID"}) ) // ObservedORM is a decorator layer for ORM used by LogPoller, responsible for pushing Prometheus metrics reporting duration and size of result set for the queries. // It doesn't change internal logic, because all calls are delegated to the origin ORM type ObservedORM struct { ORM - queryDuration *prometheus.HistogramVec - datasetSize *prometheus.GaugeVec - chainId string + queryDuration *prometheus.HistogramVec + datasetSize *prometheus.GaugeVec + logsInserted *prometheus.CounterVec + blocksInserted *prometheus.CounterVec + chainId string } // NewObservedORM creates an observed version of log poller's ORM created by NewORM // Please see ObservedLogPoller for more details on how latencies are measured func NewObservedORM(chainID *big.Int, db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig) *ObservedORM { return &ObservedORM{ - ORM: NewORM(chainID, db, lggr, cfg), - queryDuration: lpQueryDuration, - datasetSize: lpQueryDataSets, - chainId: chainID.String(), + ORM: NewORM(chainID, db, lggr, cfg), + queryDuration: lpQueryDuration, + datasetSize: lpQueryDataSets, + logsInserted: lpLogsInserted, + blocksInserted: lpBlockInserted, + chainId: chainID.String(), } } -func (o *ObservedORM) Q() pg.Q { - return o.ORM.Q() -} - func (o *ObservedORM) InsertLogs(logs []Log, qopts ...pg.QOpt) error { - return withObservedExec(o, "InsertLogs", func() error { + err := withObservedExec(o, "InsertLogs", create, func() error { return o.ORM.InsertLogs(logs, qopts...) }) + trackInsertedLogsAndBlock(o, logs, nil, err) + return err } -func (o *ObservedORM) InsertBlock(hash common.Hash, blockNumber int64, blockTimestamp time.Time, lastFinalizedBlock int64, qopts ...pg.QOpt) error { - return withObservedExec(o, "InsertBlock", func() error { - return o.ORM.InsertBlock(hash, blockNumber, blockTimestamp, lastFinalizedBlock, qopts...) +func (o *ObservedORM) InsertLogsWithBlock(logs []Log, block LogPollerBlock, qopts ...pg.QOpt) error { + err := withObservedExec(o, "InsertLogsWithBlock", create, func() error { + return o.ORM.InsertLogsWithBlock(logs, block, qopts...) }) + trackInsertedLogsAndBlock(o, logs, &block, err) + return err } func (o *ObservedORM) InsertFilter(filter Filter, qopts ...pg.QOpt) error { - return withObservedExec(o, "InsertFilter", func() error { + return withObservedExec(o, "InsertFilter", create, func() error { return o.ORM.InsertFilter(filter, qopts...) }) } @@ -97,31 +117,25 @@ func (o *ObservedORM) LoadFilters(qopts ...pg.QOpt) (map[string]Filter, error) { } func (o *ObservedORM) DeleteFilter(name string, qopts ...pg.QOpt) error { - return withObservedExec(o, "DeleteFilter", func() error { + return withObservedExec(o, "DeleteFilter", del, func() error { return o.ORM.DeleteFilter(name, qopts...) }) } -func (o *ObservedORM) DeleteBlocksAfter(start int64, qopts ...pg.QOpt) error { - return withObservedExec(o, "DeleteBlocksAfter", func() error { - return o.ORM.DeleteBlocksAfter(start, qopts...) - }) -} - func (o *ObservedORM) DeleteBlocksBefore(end int64, qopts ...pg.QOpt) error { - return withObservedExec(o, "DeleteBlocksBefore", func() error { + return withObservedExec(o, "DeleteBlocksBefore", del, func() error { return o.ORM.DeleteBlocksBefore(end, qopts...) }) } -func (o *ObservedORM) DeleteLogsAfter(start int64, qopts ...pg.QOpt) error { - return withObservedExec(o, "DeleteLogsAfter", func() error { - return o.ORM.DeleteLogsAfter(start, qopts...) +func (o *ObservedORM) DeleteLogsAndBlocksAfter(start int64, qopts ...pg.QOpt) error { + return withObservedExec(o, "DeleteLogsAndBlocksAfter", del, func() error { + return o.ORM.DeleteLogsAndBlocksAfter(start, qopts...) }) } func (o *ObservedORM) DeleteExpiredLogs(qopts ...pg.QOpt) error { - return withObservedExec(o, "DeleteExpiredLogs", func() error { + return withObservedExec(o, "DeleteExpiredLogs", del, func() error { return o.ORM.DeleteExpiredLogs(qopts...) }) } @@ -186,9 +200,9 @@ func (o *ObservedORM) SelectLogs(start, end int64, address common.Address, event }) } -func (o *ObservedORM) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { - return withObservedQueryAndResults(o, "IndexedLogsByTxHash", func() ([]Log, error) { - return o.ORM.SelectIndexedLogsByTxHash(eventSig, txHash, qopts...) +func (o *ObservedORM) SelectIndexedLogsByTxHash(address common.Address, eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectIndexedLogsByTxHash", func() ([]Log, error) { + return o.ORM.SelectIndexedLogsByTxHash(address, eventSig, txHash, qopts...) }) } @@ -222,6 +236,12 @@ func (o *ObservedORM) SelectLogsDataWordGreaterThan(address common.Address, even }) } +func (o *ObservedORM) SelectLogsDataWordBetween(address common.Address, eventSig common.Hash, wordIndexMin int, wordIndexMax int, wordValue common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "SelectLogsDataWordBetween", func() ([]Log, error) { + return o.ORM.SelectLogsDataWordBetween(address, eventSig, wordIndexMin, wordIndexMax, wordValue, confs, qopts...) + }) +} + func (o *ObservedORM) SelectIndexedLogsTopicGreaterThan(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) { return withObservedQueryAndResults(o, "SelectIndexedLogsTopicGreaterThan", func() ([]Log, error) { return o.ORM.SelectIndexedLogsTopicGreaterThan(address, eventSig, topicIndex, topicValueMin, confs, qopts...) @@ -238,7 +258,7 @@ func withObservedQueryAndResults[T any](o *ObservedORM, queryName string, query results, err := withObservedQuery(o, queryName, query) if err == nil { o.datasetSize. - WithLabelValues(o.chainId, queryName). + WithLabelValues(o.chainId, queryName, string(read)). Set(float64(len(results))) } return results, err @@ -248,18 +268,33 @@ func withObservedQuery[T any](o *ObservedORM, queryName string, query func() (T, queryStarted := time.Now() defer func() { o.queryDuration. - WithLabelValues(o.chainId, queryName). + WithLabelValues(o.chainId, queryName, string(read)). Observe(float64(time.Since(queryStarted))) }() return query() } -func withObservedExec(o *ObservedORM, query string, exec func() error) error { +func withObservedExec(o *ObservedORM, query string, queryType queryType, exec func() error) error { queryStarted := time.Now() defer func() { o.queryDuration. - WithLabelValues(o.chainId, query). + WithLabelValues(o.chainId, query, string(queryType)). Observe(float64(time.Since(queryStarted))) }() return exec() } + +func trackInsertedLogsAndBlock(o *ObservedORM, logs []Log, block *LogPollerBlock, err error) { + if err != nil { + return + } + o.logsInserted. + WithLabelValues(o.chainId). + Add(float64(len(logs))) + + if block != nil { + o.blocksInserted. + WithLabelValues(o.chainId). + Inc() + } +} diff --git a/core/chains/evm/logpoller/observability_test.go b/core/chains/evm/logpoller/observability_test.go index 0d3eadf47d7..76575f46ca4 100644 --- a/core/chains/evm/logpoller/observability_test.go +++ b/core/chains/evm/logpoller/observability_test.go @@ -9,15 +9,17 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/prometheus/client_golang/prometheus" io_prometheus_client "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/utils" ) func TestMultipleMetricsArePublished(t *testing.T) { @@ -38,7 +40,7 @@ func TestMultipleMetricsArePublished(t *testing.T) { _, _ = orm.SelectLatestLogEventSigsAddrsWithConfs(0, []common.Address{{}}, []common.Hash{{}}, 1, pg.WithParentCtx(ctx)) _, _ = orm.SelectIndexedLogsCreatedAfter(common.Address{}, common.Hash{}, 1, []common.Hash{}, time.Now(), 0, pg.WithParentCtx(ctx)) _ = orm.InsertLogs([]Log{}, pg.WithParentCtx(ctx)) - _ = orm.InsertBlock(common.Hash{}, 1, time.Now(), 0, pg.WithParentCtx(ctx)) + _ = orm.InsertLogsWithBlock([]Log{}, NewLogPollerBlock(common.Hash{}, 1, time.Now(), 0), pg.WithParentCtx(ctx)) require.Equal(t, 13, testutil.CollectAndCount(orm.queryDuration)) require.Equal(t, 10, testutil.CollectAndCount(orm.datasetSize)) @@ -54,7 +56,7 @@ func TestShouldPublishDurationInCaseOfError(t *testing.T) { require.Error(t, err) require.Equal(t, 1, testutil.CollectAndCount(orm.queryDuration)) - require.Equal(t, 1, counterFromHistogramByLabels(t, orm.queryDuration, "200", "SelectLatestLogByEventSigWithConfs")) + require.Equal(t, 1, counterFromHistogramByLabels(t, orm.queryDuration, "200", "SelectLatestLogByEventSigWithConfs", "read")) } func TestMetricsAreProperlyPopulatedWithLabels(t *testing.T) { @@ -68,14 +70,14 @@ func TestMetricsAreProperlyPopulatedWithLabels(t *testing.T) { require.NoError(t, err) } - require.Equal(t, expectedCount, counterFromHistogramByLabels(t, orm.queryDuration, "420", "query")) - require.Equal(t, expectedSize, counterFromGaugeByLabels(orm.datasetSize, "420", "query")) + require.Equal(t, expectedCount, counterFromHistogramByLabels(t, orm.queryDuration, "420", "query", "read")) + require.Equal(t, expectedSize, counterFromGaugeByLabels(orm.datasetSize, "420", "query", "read")) - require.Equal(t, 0, counterFromHistogramByLabels(t, orm.queryDuration, "420", "other_query")) - require.Equal(t, 0, counterFromHistogramByLabels(t, orm.queryDuration, "5", "query")) + require.Equal(t, 0, counterFromHistogramByLabels(t, orm.queryDuration, "420", "other_query", "read")) + require.Equal(t, 0, counterFromHistogramByLabels(t, orm.queryDuration, "5", "query", "read")) - require.Equal(t, 0, counterFromGaugeByLabels(orm.datasetSize, "420", "other_query")) - require.Equal(t, 0, counterFromGaugeByLabels(orm.datasetSize, "5", "query")) + require.Equal(t, 0, counterFromGaugeByLabels(orm.datasetSize, "420", "other_query", "read")) + require.Equal(t, 0, counterFromGaugeByLabels(orm.datasetSize, "5", "query", "read")) } func TestNotPublishingDatasetSizeInCaseOfError(t *testing.T) { @@ -84,20 +86,64 @@ func TestNotPublishingDatasetSizeInCaseOfError(t *testing.T) { _, err := withObservedQueryAndResults(orm, "errorQuery", func() ([]string, error) { return nil, fmt.Errorf("error") }) require.Error(t, err) - require.Equal(t, 1, counterFromHistogramByLabels(t, orm.queryDuration, "420", "errorQuery")) - require.Equal(t, 0, counterFromGaugeByLabels(orm.datasetSize, "420", "errorQuery")) + require.Equal(t, 1, counterFromHistogramByLabels(t, orm.queryDuration, "420", "errorQuery", "read")) + require.Equal(t, 0, counterFromGaugeByLabels(orm.datasetSize, "420", "errorQuery", "read")) } func TestMetricsAreProperlyPopulatedForWrites(t *testing.T) { orm := createObservedORM(t, 420) - require.NoError(t, withObservedExec(orm, "execQuery", func() error { return nil })) - require.Error(t, withObservedExec(orm, "execQuery", func() error { return fmt.Errorf("error") })) + require.NoError(t, withObservedExec(orm, "execQuery", create, func() error { return nil })) + require.Error(t, withObservedExec(orm, "execQuery", create, func() error { return fmt.Errorf("error") })) - require.Equal(t, 2, counterFromHistogramByLabels(t, orm.queryDuration, "420", "execQuery")) + require.Equal(t, 2, counterFromHistogramByLabels(t, orm.queryDuration, "420", "execQuery", "create")) +} + +func TestCountersAreProperlyPopulatedForWrites(t *testing.T) { + orm := createObservedORM(t, 420) + logs := generateRandomLogs(420, 20) + + // First insert 10 logs + require.NoError(t, orm.InsertLogs(logs[:10])) + assert.Equal(t, float64(10), testutil.ToFloat64(orm.logsInserted.WithLabelValues("420"))) + + // Insert 5 more logs with block + require.NoError(t, orm.InsertLogsWithBlock(logs[10:15], NewLogPollerBlock(utils.RandomBytes32(), 10, time.Now(), 5))) + assert.Equal(t, float64(15), testutil.ToFloat64(orm.logsInserted.WithLabelValues("420"))) + assert.Equal(t, float64(1), testutil.ToFloat64(orm.blocksInserted.WithLabelValues("420"))) + + // Insert 5 more logs with block + require.NoError(t, orm.InsertLogsWithBlock(logs[15:], NewLogPollerBlock(utils.RandomBytes32(), 15, time.Now(), 5))) + assert.Equal(t, float64(20), testutil.ToFloat64(orm.logsInserted.WithLabelValues("420"))) + assert.Equal(t, float64(2), testutil.ToFloat64(orm.blocksInserted.WithLabelValues("420"))) + + // Don't update counters in case of an error + require.Error(t, orm.InsertLogsWithBlock(logs, NewLogPollerBlock(utils.RandomBytes32(), 0, time.Now(), 0))) + assert.Equal(t, float64(20), testutil.ToFloat64(orm.logsInserted.WithLabelValues("420"))) + assert.Equal(t, float64(2), testutil.ToFloat64(orm.blocksInserted.WithLabelValues("420"))) +} + +func generateRandomLogs(chainId, count int) []Log { + logs := make([]Log, count) + for i := range logs { + logs[i] = Log{ + EvmChainId: utils.NewBigI(int64(chainId)), + LogIndex: int64(i + 1), + BlockHash: utils.RandomBytes32(), + BlockNumber: int64(i + 1), + BlockTimestamp: time.Now(), + Topics: [][]byte{}, + EventSig: utils.RandomBytes32(), + Address: utils.RandomAddress(), + TxHash: utils.RandomBytes32(), + Data: []byte{}, + CreatedAt: time.Now(), + } + } + return logs } func createObservedORM(t *testing.T, chainId int64) *ObservedORM { - lggr, _ := logger.TestLoggerObserved(t, zapcore.ErrorLevel) + lggr, _ := logger.TestObserved(t, zapcore.ErrorLevel) db := pgtest.NewSqlxDB(t) return NewObservedORM( big.NewInt(chainId), db, lggr, pgtest.NewQConfig(true), @@ -107,6 +153,8 @@ func createObservedORM(t *testing.T, chainId int64) *ObservedORM { func resetMetrics(lp ObservedORM) { lp.queryDuration.Reset() lp.datasetSize.Reset() + lp.logsInserted.Reset() + lp.blocksInserted.Reset() } func counterFromGaugeByLabels(gaugeVec *prometheus.GaugeVec, labels ...string) int { diff --git a/core/chains/evm/logpoller/orm.go b/core/chains/evm/logpoller/orm.go index 06f4acbb4f1..c24d423b253 100644 --- a/core/chains/evm/logpoller/orm.go +++ b/core/chains/evm/logpoller/orm.go @@ -8,10 +8,10 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -20,17 +20,15 @@ import ( // it exposes some of the database implementation details (e.g. pg.Q). Ideally it should be agnostic and could be applied to any persistence layer. // What is more, LogPoller should not be aware of the underlying database implementation and delegate all the queries to the ORM. type ORM interface { - Q() pg.Q InsertLogs(logs []Log, qopts ...pg.QOpt) error - InsertBlock(blockHash common.Hash, blockNumber int64, blockTimestamp time.Time, lastFinalizedBlockNumber int64, qopts ...pg.QOpt) error + InsertLogsWithBlock(logs []Log, block LogPollerBlock, qopts ...pg.QOpt) error InsertFilter(filter Filter, qopts ...pg.QOpt) error LoadFilters(qopts ...pg.QOpt) (map[string]Filter, error) DeleteFilter(name string, qopts ...pg.QOpt) error - DeleteBlocksAfter(start int64, qopts ...pg.QOpt) error DeleteBlocksBefore(end int64, qopts ...pg.QOpt) error - DeleteLogsAfter(start int64, qopts ...pg.QOpt) error + DeleteLogsAndBlocksAfter(start int64, qopts ...pg.QOpt) error DeleteExpiredLogs(qopts ...pg.QOpt) error GetBlocksRange(start int64, end int64, qopts ...pg.QOpt) ([]LogPollerBlock, error) @@ -50,30 +48,29 @@ type ORM interface { SelectIndexedLogsTopicGreaterThan(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) SelectIndexedLogsTopicRange(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin, topicValueMax common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) SelectIndexedLogsWithSigsExcluding(sigA, sigB common.Hash, topicIndex int, address common.Address, startBlock, endBlock int64, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) - SelectIndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) + SelectIndexedLogsByTxHash(address common.Address, eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) SelectLogsDataWordRange(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin, wordValueMax common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) SelectLogsDataWordGreaterThan(address common.Address, eventSig common.Hash, wordIndex int, wordValueMin common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) + SelectLogsDataWordBetween(address common.Address, eventSig common.Hash, wordIndexMin int, wordIndexMax int, wordValue common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) } type DbORM struct { chainID *big.Int q pg.Q + lggr logger.Logger } // NewORM creates a DbORM scoped to chainID. func NewORM(chainID *big.Int, db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig) *DbORM { - namedLogger := lggr.Named("Configs") + namedLogger := logger.Named(lggr, "Configs") q := pg.NewQ(db, namedLogger, cfg) return &DbORM{ chainID: chainID, q: q, + lggr: lggr, } } -func (o *DbORM) Q() pg.Q { - return o.q -} - // InsertBlock is idempotent to support replays. func (o *DbORM) InsertBlock(blockHash common.Hash, blockNumber int64, blockTimestamp time.Time, finalizedBlock int64, qopts ...pg.QOpt) error { args, err := newQueryArgs(o.chainID). @@ -191,12 +188,6 @@ func (o *DbORM) SelectLatestLogByEventSigWithConfs(eventSig common.Hash, address return &l, nil } -// DeleteBlocksAfter delete all blocks after and including start. -func (o *DbORM) DeleteBlocksAfter(start int64, qopts ...pg.QOpt) error { - q := o.q.WithOpts(qopts...) - return q.ExecQ(`DELETE FROM evm.log_poller_blocks WHERE block_number >= $1 AND evm_chain_id = $2`, start, utils.NewBig(o.chainID)) -} - // DeleteBlocksBefore delete all blocks before and including end. func (o *DbORM) DeleteBlocksBefore(end int64, qopts ...pg.QOpt) error { q := o.q.WithOpts(qopts...) @@ -204,9 +195,31 @@ func (o *DbORM) DeleteBlocksBefore(end int64, qopts ...pg.QOpt) error { return err } -func (o *DbORM) DeleteLogsAfter(start int64, qopts ...pg.QOpt) error { - q := o.q.WithOpts(qopts...) - return q.ExecQ(`DELETE FROM evm.logs WHERE block_number >= $1 AND evm_chain_id = $2`, start, utils.NewBig(o.chainID)) +func (o *DbORM) DeleteLogsAndBlocksAfter(start int64, qopts ...pg.QOpt) error { + // These deletes are bounded by reorg depth, so they are + // fast and should not slow down the log readers. + return o.q.WithOpts(qopts...).Transaction(func(tx pg.Queryer) error { + args, err := newQueryArgs(o.chainID). + withStartBlock(start). + toArgs() + if err != nil { + o.lggr.Error("Cant build args for DeleteLogsAndBlocksAfter queries", "err", err) + return err + } + + _, err = tx.NamedExec(`DELETE FROM evm.log_poller_blocks WHERE block_number >= :start_block AND evm_chain_id = :evm_chain_id`, args) + if err != nil { + o.lggr.Warnw("Unable to clear reorged blocks, retrying", "err", err) + return err + } + + _, err = tx.NamedExec(`DELETE FROM evm.logs WHERE block_number >= :start_block AND evm_chain_id = :evm_chain_id`, args) + if err != nil { + o.lggr.Warnw("Unable to clear reorged logs, retrying", "err", err) + return err + } + return nil + }) } type Exp struct { @@ -233,13 +246,35 @@ func (o *DbORM) DeleteExpiredLogs(qopts ...pg.QOpt) error { // InsertLogs is idempotent to support replays. func (o *DbORM) InsertLogs(logs []Log, qopts ...pg.QOpt) error { - for _, log := range logs { - if o.chainID.Cmp(log.EvmChainId.ToInt()) != 0 { - return errors.Errorf("invalid chainID in log got %v want %v", log.EvmChainId.ToInt(), o.chainID) - } + if err := o.validateLogs(logs); err != nil { + return err } - q := o.q.WithOpts(qopts...) + return o.q.WithOpts(qopts...).Transaction(func(tx pg.Queryer) error { + return o.insertLogsWithinTx(logs, tx) + }) +} + +func (o *DbORM) InsertLogsWithBlock(logs []Log, block LogPollerBlock, qopts ...pg.QOpt) error { + // Optimization, don't open TX when there is only a block to be persisted + if len(logs) == 0 { + return o.InsertBlock(block.BlockHash, block.BlockNumber, block.BlockTimestamp, block.FinalizedBlockNumber, qopts...) + } + + if err := o.validateLogs(logs); err != nil { + return err + } + + // Block and logs goes with the same TX to ensure atomicity + return o.q.WithOpts(qopts...).Transaction(func(tx pg.Queryer) error { + if err := o.InsertBlock(block.BlockHash, block.BlockNumber, block.BlockTimestamp, block.FinalizedBlockNumber, pg.WithQueryer(tx)); err != nil { + return err + } + return o.insertLogsWithinTx(logs, tx) + }) +} + +func (o *DbORM) insertLogsWithinTx(logs []Log, tx pg.Queryer) error { batchInsertSize := 4000 for i := 0; i < len(logs); i += batchInsertSize { start, end := i, i+batchInsertSize @@ -247,12 +282,14 @@ func (o *DbORM) InsertLogs(logs []Log, qopts ...pg.QOpt) error { end = len(logs) } - err := q.ExecQNamed(` - INSERT INTO evm.logs - (evm_chain_id, log_index, block_hash, block_number, block_timestamp, address, event_sig, topics, tx_hash, data, created_at) + _, err := tx.NamedExec(` + INSERT INTO evm.logs + (evm_chain_id, log_index, block_hash, block_number, block_timestamp, address, event_sig, topics, tx_hash, data, created_at) VALUES - (:evm_chain_id, :log_index, :block_hash, :block_number, :block_timestamp, :address, :event_sig, :topics, :tx_hash, :data, NOW()) - ON CONFLICT DO NOTHING`, logs[start:end]) + (:evm_chain_id, :log_index, :block_hash, :block_number, :block_timestamp, :address, :event_sig, :topics, :tx_hash, :data, NOW()) + ON CONFLICT DO NOTHING`, + logs[start:end], + ) if err != nil { if errors.Is(err, context.DeadlineExceeded) && batchInsertSize > 500 { @@ -267,6 +304,15 @@ func (o *DbORM) InsertLogs(logs []Log, qopts ...pg.QOpt) error { return nil } +func (o *DbORM) validateLogs(logs []Log) error { + for _, log := range logs { + if o.chainID.Cmp(log.EvmChainId.ToInt()) != 0 { + return errors.Errorf("invalid chainID in log got %v want %v", log.EvmChainId.ToInt(), o.chainID) + } + } + return nil +} + func (o *DbORM) SelectLogsByBlockRange(start, end int64) ([]Log, error) { args, err := newQueryArgs(o.chainID). withStartBlock(start). @@ -490,6 +536,32 @@ func (o *DbORM) SelectLogsDataWordGreaterThan(address common.Address, eventSig c return logs, nil } +func (o *DbORM) SelectLogsDataWordBetween(address common.Address, eventSig common.Hash, wordIndexMin int, wordIndexMax int, wordValue common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) { + args, err := newQueryArgsForEvent(o.chainID, address, eventSig). + withWordIndexMin(wordIndexMin). + withWordIndexMax(wordIndexMax). + withWordValue(wordValue). + withConfs(confs). + toArgs() + if err != nil { + return nil, err + } + query := fmt.Sprintf(` + SELECT * FROM evm.logs + WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + AND substring(data from 32*:word_index_min+1 for 32) <= :word_value + AND substring(data from 32*:word_index_max+1 for 32) >= :word_value + AND block_number <= %s + ORDER BY (block_number, log_index)`, nestedBlockNumberQuery(confs)) + var logs []Log + if err = o.q.WithOpts(qopts...).SelectNamed(&logs, query, args); err != nil { + return nil, err + } + return logs, nil +} + func (o *DbORM) SelectIndexedLogsTopicGreaterThan(address common.Address, eventSig common.Hash, topicIndex int, topicValueMin common.Hash, confs Confirmations, qopts ...pg.QOpt) ([]Log, error) { args, err := newQueryArgsForEvent(o.chainID, address, eventSig). withTopicIndex(topicIndex). @@ -619,9 +691,10 @@ func (o *DbORM) SelectIndexedLogsCreatedAfter(address common.Address, eventSig c return logs, nil } -func (o *DbORM) SelectIndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { +func (o *DbORM) SelectIndexedLogsByTxHash(address common.Address, eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { args, err := newQueryArgs(o.chainID). withTxHash(txHash). + withAddress(address). withEventSig(eventSig). toArgs() if err != nil { @@ -631,8 +704,9 @@ func (o *DbORM) SelectIndexedLogsByTxHash(eventSig common.Hash, txHash common.Ha err = o.q.WithOpts(qopts...).SelectNamed(&logs, ` SELECT * FROM evm.logs WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig AND tx_hash = :tx_hash - AND event_sig = :event_sig ORDER BY (block_number, log_index)`, args) if err != nil { return nil, err diff --git a/core/chains/evm/logpoller/orm_test.go b/core/chains/evm/logpoller/orm_test.go index 66e1afdc939..2a6ebb2c04e 100644 --- a/core/chains/evm/logpoller/orm_test.go +++ b/core/chains/evm/logpoller/orm_test.go @@ -4,6 +4,7 @@ import ( "bytes" "database/sql" "fmt" + "math" "math/big" "testing" "time" @@ -13,9 +14,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -45,6 +50,21 @@ func GenLogWithTimestamp(chainID *big.Int, logIndex int64, blockNum int64, block } } +func GenLogWithData(chainID *big.Int, address common.Address, eventSig common.Hash, logIndex int64, blockNum int64, data []byte) logpoller.Log { + return logpoller.Log{ + EvmChainId: utils.NewBig(chainID), + LogIndex: logIndex, + BlockHash: utils.RandomBytes32(), + BlockNumber: blockNum, + EventSig: eventSig, + Topics: [][]byte{}, + Address: address, + TxHash: utils.RandomBytes32(), + Data: data, + BlockTimestamp: time.Now(), + } +} + func TestLogPoller_Batching(t *testing.T) { t.Parallel() th := SetupTH(t, false, 2, 3, 2, 1000) @@ -179,13 +199,13 @@ func TestORM(t *testing.T) { assert.Equal(t, int64(12), latest.BlockNumber) // Delete a block (only 10 on chain). - require.NoError(t, o1.DeleteBlocksAfter(10)) + require.NoError(t, o1.DeleteLogsAndBlocksAfter(10)) _, err = o1.SelectBlockByHash(common.HexToHash("0x1234")) require.Error(t, err) assert.True(t, errors.Is(err, sql.ErrNoRows)) // Delete blocks from another chain. - require.NoError(t, o2.DeleteBlocksAfter(11)) + require.NoError(t, o2.DeleteLogsAndBlocksAfter(11)) _, err = o2.SelectBlockByHash(common.HexToHash("0x1234")) require.Error(t, err) assert.True(t, errors.Is(err, sql.ErrNoRows)) @@ -318,7 +338,6 @@ func TestORM(t *testing.T) { require.Error(t, err) assert.True(t, errors.Is(err, sql.ErrNoRows)) // With block 12, anything <=2 should work - require.NoError(t, o1.DeleteBlocksAfter(10)) require.NoError(t, o1.InsertBlock(common.HexToHash("0x1234"), 11, time.Now(), 0)) require.NoError(t, o1.InsertBlock(common.HexToHash("0x1235"), 12, time.Now(), 0)) _, err = o1.SelectLatestLogByEventSigWithConfs(topic, common.HexToAddress("0x1234"), 0) @@ -421,7 +440,7 @@ func TestORM(t *testing.T) { assert.Len(t, logs, 7) // Delete logs after should delete all logs. - err = o1.DeleteLogsAfter(1) + err = o1.DeleteLogsAndBlocksAfter(1) require.NoError(t, err) logs, err = o1.SelectLogsByBlockRange(1, latest.BlockNumber) require.NoError(t, err) @@ -565,7 +584,7 @@ func TestORM_SelectIndexedLogsByTxHash(t *testing.T) { } require.NoError(t, o1.InsertLogs(logs)) - retrievedLogs, err := o1.SelectIndexedLogsByTxHash(eventSig, txHash) + retrievedLogs, err := o1.SelectIndexedLogsByTxHash(addr, eventSig, txHash) require.NoError(t, err) require.Equal(t, 2, len(retrievedLogs)) @@ -1301,3 +1320,247 @@ func TestNestedLogPollerBlocksQuery(t *testing.T) { require.NoError(t, err) require.Len(t, logs, 0) } + +func TestInsertLogsWithBlock(t *testing.T) { + chainID := testutils.NewRandomEVMChainID() + event := utils.RandomBytes32() + address := utils.RandomAddress() + + // We need full db here, because we want to test transaction rollbacks. + // Using pgtest.NewSqlxDB(t) will run all tests in TXs which is not desired for this type of test + // (inner tx rollback will rollback outer tx, blocking rest of execution) + _, db := heavyweight.FullTestDBV2(t, nil) + o := logpoller.NewORM(chainID, db, logger.Test(t), pgtest.NewQConfig(true)) + + correctLog := GenLog(chainID, 1, 1, utils.RandomAddress().String(), event[:], address) + invalidLog := GenLog(chainID, -10, -10, utils.RandomAddress().String(), event[:], address) + correctBlock := logpoller.NewLogPollerBlock(utils.RandomBytes32(), 20, time.Now(), 10) + invalidBlock := logpoller.NewLogPollerBlock(utils.RandomBytes32(), -10, time.Now(), -10) + + tests := []struct { + name string + logs []logpoller.Log + block logpoller.LogPollerBlock + shouldRollback bool + }{ + { + name: "properly persist all data", + logs: []logpoller.Log{correctLog}, + block: correctBlock, + shouldRollback: false, + }, + { + name: "rollbacks transaction when block is invalid", + logs: []logpoller.Log{correctLog}, + block: invalidBlock, + shouldRollback: true, + }, + { + name: "rollbacks transaction when log is invalid", + logs: []logpoller.Log{invalidLog}, + block: correctBlock, + shouldRollback: true, + }, + { + name: "rollback when only some logs are invalid", + logs: []logpoller.Log{correctLog, invalidLog}, + block: correctBlock, + shouldRollback: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // clean all logs and blocks between test cases + defer func() { _ = o.DeleteLogsAndBlocksAfter(0) }() + insertError := o.InsertLogsWithBlock(tt.logs, tt.block) + + logs, logsErr := o.SelectLogs(0, math.MaxInt, address, event) + block, blockErr := o.SelectLatestBlock() + + if tt.shouldRollback { + assert.Error(t, insertError) + + assert.NoError(t, logsErr) + assert.Len(t, logs, 0) + + assert.Error(t, blockErr) + } else { + assert.NoError(t, insertError) + + assert.NoError(t, logsErr) + assert.Len(t, logs, len(tt.logs)) + + assert.NoError(t, blockErr) + assert.Equal(t, block.BlockNumber, tt.block.BlockNumber) + } + }) + } +} + +func TestInsertLogsInTx(t *testing.T) { + chainID := testutils.NewRandomEVMChainID() + event := utils.RandomBytes32() + address := utils.RandomAddress() + maxLogsSize := 9000 + + // We need full db here, because we want to test transaction rollbacks. + _, db := heavyweight.FullTestDBV2(t, nil) + o := logpoller.NewORM(chainID, db, logger.Test(t), pgtest.NewQConfig(true)) + + logs := make([]logpoller.Log, maxLogsSize, maxLogsSize+1) + for i := 0; i < maxLogsSize; i++ { + logs[i] = GenLog(chainID, int64(i+1), int64(i+1), utils.RandomAddress().String(), event[:], address) + } + invalidLog := GenLog(chainID, -10, -10, utils.RandomAddress().String(), event[:], address) + + tests := []struct { + name string + logs []logpoller.Log + shouldRollback bool + }{ + { + name: "all logs persisted", + logs: logs, + shouldRollback: false, + }, + { + name: "rollback when invalid log is passed", + logs: append(logs, invalidLog), + shouldRollback: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // clean all logs and blocks between test cases + defer func() { _ = o.DeleteLogsAndBlocksAfter(0) }() + + insertErr := o.InsertLogs(tt.logs) + logsFromDb, err := o.SelectLogs(0, math.MaxInt, address, event) + assert.NoError(t, err) + + if tt.shouldRollback { + assert.Error(t, insertErr) + assert.Len(t, logsFromDb, 0) + } else { + assert.NoError(t, insertErr) + assert.Len(t, logsFromDb, len(tt.logs)) + } + }) + } +} + +func TestSelectLogsDataWordBetween(t *testing.T) { + address := utils.RandomAddress() + eventSig := utils.RandomBytes32() + th := SetupTH(t, false, 2, 3, 2, 1000) + + firstLogData := make([]byte, 0, 64) + firstLogData = append(firstLogData, logpoller.EvmWord(1).Bytes()...) + firstLogData = append(firstLogData, logpoller.EvmWord(10).Bytes()...) + + secondLogData := make([]byte, 0, 64) + secondLogData = append(secondLogData, logpoller.EvmWord(5).Bytes()...) + secondLogData = append(secondLogData, logpoller.EvmWord(20).Bytes()...) + + err := th.ORM.InsertLogsWithBlock( + []logpoller.Log{ + GenLogWithData(th.ChainID, address, eventSig, 1, 1, firstLogData), + GenLogWithData(th.ChainID, address, eventSig, 2, 2, secondLogData), + }, + logpoller.NewLogPollerBlock(utils.RandomBytes32(), 10, time.Now(), 1), + ) + require.NoError(t, err) + + tests := []struct { + name string + wordValue uint64 + expectedLogs []int64 + }{ + { + name: "returns only first log", + wordValue: 2, + expectedLogs: []int64{1}, + }, + { + name: "returns only second log", + wordValue: 11, + expectedLogs: []int64{2}, + }, + { + name: "returns both logs if word value is between", + wordValue: 5, + expectedLogs: []int64{1, 2}, + }, + { + name: "returns no logs if word value is outside of the range", + wordValue: 21, + expectedLogs: []int64{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logs, err1 := th.ORM.SelectLogsDataWordBetween(address, eventSig, 0, 1, logpoller.EvmWord(tt.wordValue), logpoller.Unconfirmed) + assert.NoError(t, err1) + assert.Len(t, logs, len(tt.expectedLogs)) + + for index := range logs { + assert.Equal(t, tt.expectedLogs[index], logs[index].BlockNumber) + } + }) + } +} + +func Benchmark_LogsDataWordBetween(b *testing.B) { + chainId := big.NewInt(137) + _, db := heavyweight.FullTestDBV2(b, nil) + o := logpoller.NewORM(chainId, db, logger.Test(b), pgtest.NewQConfig(false)) + + numberOfReports := 100_000 + numberOfMessagesPerReport := 256 + + commitStoreAddress := utils.RandomAddress() + commitReportAccepted := utils.RandomBytes32() + + var dbLogs []logpoller.Log + for i := 0; i < numberOfReports; i++ { + data := make([]byte, 64) + // MinSeqNr + data = append(data, logpoller.EvmWord(uint64(numberOfMessagesPerReport*i+1)).Bytes()...) + // MaxSeqNr + data = append(data, logpoller.EvmWord(uint64(numberOfMessagesPerReport*(i+1))).Bytes()...) + + dbLogs = append(dbLogs, logpoller.Log{ + EvmChainId: utils.NewBig(chainId), + LogIndex: int64(i + 1), + BlockHash: utils.RandomBytes32(), + BlockNumber: int64(i + 1), + BlockTimestamp: time.Now(), + EventSig: commitReportAccepted, + Topics: [][]byte{}, + Address: commitStoreAddress, + TxHash: utils.RandomAddress().Hash(), + Data: data, + CreatedAt: time.Now(), + }) + } + require.NoError(b, o.InsertBlock(utils.RandomAddress().Hash(), int64(numberOfReports*numberOfMessagesPerReport), time.Now(), int64(numberOfReports*numberOfMessagesPerReport))) + require.NoError(b, o.InsertLogs(dbLogs)) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + logs, err := o.SelectLogsDataWordBetween( + commitStoreAddress, + commitReportAccepted, + 2, + 3, + logpoller.EvmWord(uint64(numberOfReports*numberOfMessagesPerReport/2)), // Pick the middle report + logpoller.Unconfirmed, + ) + assert.NoError(b, err) + assert.Len(b, logs, 1) + } +} diff --git a/core/chains/evm/logpoller/query.go b/core/chains/evm/logpoller/query.go index 7443a860a85..b7fbfade680 100644 --- a/core/chains/evm/logpoller/query.go +++ b/core/chains/evm/logpoller/query.go @@ -82,6 +82,18 @@ func (q *queryArgs) withWordValueMax(wordValueMax common.Hash) *queryArgs { return q.withCustomHashArg("word_value_max", wordValueMax) } +func (q *queryArgs) withWordIndexMin(wordIndex int) *queryArgs { + return q.withCustomArg("word_index_min", wordIndex) +} + +func (q *queryArgs) withWordIndexMax(wordIndex int) *queryArgs { + return q.withCustomArg("word_index_max", wordIndex) +} + +func (q *queryArgs) withWordValue(wordValue common.Hash) *queryArgs { + return q.withCustomHashArg("word_value", wordValue) +} + func (q *queryArgs) withConfs(confs Confirmations) *queryArgs { return q.withCustomArg("confs", confs) } diff --git a/core/chains/evm/mocks/balance_monitor.go b/core/chains/evm/mocks/balance_monitor.go index 2a5e66bbc02..cdda18b7f03 100644 --- a/core/chains/evm/mocks/balance_monitor.go +++ b/core/chains/evm/mocks/balance_monitor.go @@ -4,7 +4,7 @@ package mocks import ( common "github.com/ethereum/go-ethereum/common" - assets "github.com/smartcontractkit/chainlink/v2/core/assets" + assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" context "context" diff --git a/core/chains/evm/mocks/node.go b/core/chains/evm/mocks/node.go index 85db2e0ca0f..69020d411f4 100644 --- a/core/chains/evm/mocks/node.go +++ b/core/chains/evm/mocks/node.go @@ -19,8 +19,6 @@ import ( rpc "github.com/ethereum/go-ethereum/rpc" types "github.com/ethereum/go-ethereum/core/types" - - utils "github.com/smartcontractkit/chainlink/v2/core/utils" ) // Node is an autogenerated mock type for the Node type @@ -519,13 +517,13 @@ func (_m *Node) State() client.NodeState { } // StateAndLatest provides a mock function with given fields: -func (_m *Node) StateAndLatest() (client.NodeState, int64, *utils.Big) { +func (_m *Node) StateAndLatest() (client.NodeState, int64, *big.Int) { ret := _m.Called() var r0 client.NodeState var r1 int64 - var r2 *utils.Big - if rf, ok := ret.Get(0).(func() (client.NodeState, int64, *utils.Big)); ok { + var r2 *big.Int + if rf, ok := ret.Get(0).(func() (client.NodeState, int64, *big.Int)); ok { return rf() } if rf, ok := ret.Get(0).(func() client.NodeState); ok { @@ -540,11 +538,11 @@ func (_m *Node) StateAndLatest() (client.NodeState, int64, *utils.Big) { r1 = ret.Get(1).(int64) } - if rf, ok := ret.Get(2).(func() *utils.Big); ok { + if rf, ok := ret.Get(2).(func() *big.Int); ok { r2 = rf() } else { if ret.Get(2) != nil { - r2 = ret.Get(2).(*utils.Big) + r2 = ret.Get(2).(*big.Int) } } diff --git a/core/chains/evm/monitor/balance.go b/core/chains/evm/monitor/balance.go index 94434b733e6..c3b9a49c7af 100644 --- a/core/chains/evm/monitor/balance.go +++ b/core/chains/evm/monitor/balance.go @@ -13,12 +13,13 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -29,11 +30,11 @@ type ( BalanceMonitor interface { httypes.HeadTrackable GetEthBalance(gethCommon.Address) *assets.Eth - services.ServiceCtx + services.Service } balanceMonitor struct { - utils.StartStopOnce + services.StateMachine logger logger.Logger ethClient evmclient.Client chainID *big.Int @@ -50,11 +51,11 @@ type ( var _ BalanceMonitor = (*balanceMonitor)(nil) // NewBalanceMonitor returns a new balanceMonitor -func NewBalanceMonitor(ethClient evmclient.Client, ethKeyStore keystore.Eth, logger logger.Logger) *balanceMonitor { +func NewBalanceMonitor(ethClient evmclient.Client, ethKeyStore keystore.Eth, lggr logger.Logger) *balanceMonitor { chainId := ethClient.ConfiguredChainID() bm := &balanceMonitor{ - utils.StartStopOnce{}, - logger.Named("BalanceMonitor"), + services.StateMachine{}, + logger.Named(lggr, "BalanceMonitor"), ethClient, chainId, chainId.String(), @@ -118,7 +119,8 @@ func (bm *balanceMonitor) updateBalance(ethBal assets.Eth, address gethCommon.Ad bm.ethBalances[address] = ðBal bm.ethBalancesMtx.Unlock() - lgr := bm.logger.Named("BalanceLog").With( + lgr := logger.Named(bm.logger, "BalanceLog") + lgr = logger.With(lgr, "address", address.Hex(), "ethBalance", ethBal.String(), "weiBalance", ethBal.ToInt()) diff --git a/core/chains/evm/monitor/balance_test.go b/core/chains/evm/monitor/balance_test.go index dbb2003b695..c2c976e78da 100644 --- a/core/chains/evm/monitor/balance_test.go +++ b/core/chains/evm/monitor/balance_test.go @@ -13,14 +13,14 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/monitor" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) var nilBigInt *big.Int @@ -43,7 +43,7 @@ func TestBalanceMonitor_Start(t *testing.T) { _, k1Addr := cltest.MustInsertRandomKey(t, ethKeyStore) _, k0Addr := cltest.MustInsertRandomKey(t, ethKeyStore) - bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.Test(t)) defer func() { assert.NoError(t, bm.Close()) }() k0bal := big.NewInt(42) @@ -71,7 +71,7 @@ func TestBalanceMonitor_Start(t *testing.T) { _, k0Addr := cltest.MustInsertRandomKey(t, ethKeyStore) - bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.Test(t)) defer func() { assert.NoError(t, bm.Close()) }() k0bal := big.NewInt(42) @@ -91,7 +91,7 @@ func TestBalanceMonitor_Start(t *testing.T) { _, k0Addr := cltest.MustInsertRandomKey(t, ethKeyStore) - bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.Test(t)) defer func() { assert.NoError(t, bm.Close()) }() ctxCancelledAwaiter := cltest.NewAwaiter() @@ -121,7 +121,7 @@ func TestBalanceMonitor_Start(t *testing.T) { _, k0Addr := cltest.MustInsertRandomKey(t, ethKeyStore) - bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.Test(t)) defer func() { assert.NoError(t, bm.Close()) }() ethClient.On("BalanceAt", mock.Anything, k0Addr, nilBigInt). @@ -149,7 +149,7 @@ func TestBalanceMonitor_OnNewLongestChain_UpdatesBalance(t *testing.T) { _, k0Addr := cltest.MustInsertRandomKey(t, ethKeyStore) _, k1Addr := cltest.MustInsertRandomKey(t, ethKeyStore) - bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.Test(t)) k0bal := big.NewInt(42) // Deliberately larger than a 64 bit unsigned integer to test overflow k1bal := big.NewInt(0) @@ -201,7 +201,7 @@ func TestBalanceMonitor_FewerRPCCallsWhenBehind(t *testing.T) { ethClient := newEthClientMock(t) - bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.TestLogger(t)) + bm := monitor.NewBalanceMonitor(ethClient, ethKeyStore, logger.Test(t)) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything). Once(). Return(big.NewInt(1), nil) diff --git a/core/chains/evm/txmgr/attempts.go b/core/chains/evm/txmgr/attempts.go index 7b2a05a34c4..37626f4550e 100644 --- a/core/chains/evm/txmgr/attempts.go +++ b/core/chains/evm/txmgr/attempts.go @@ -9,13 +9,13 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" commontypes "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type TxAttemptSigner[ADDR commontypes.Hashable] interface { diff --git a/core/chains/evm/txmgr/attempts_test.go b/core/chains/evm/txmgr/attempts_test.go index 099143f0ce4..131115e6fae 100644 --- a/core/chains/evm/txmgr/attempts_test.go +++ b/core/chains/evm/txmgr/attempts_test.go @@ -13,15 +13,15 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" ksmocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks" ) @@ -93,7 +93,7 @@ func TestTxm_NewDynamicFeeTx(t *testing.T) { kst := ksmocks.NewEth(t) kst.On("SignTx", addr, mock.Anything, big.NewInt(1)).Return(tx, nil) var n evmtypes.Nonce - lggr := logger.TestLogger(t) + lggr := logger.Test(t) t.Run("creates attempt with fields", func(t *testing.T) { feeCfg := newFeeConfig() @@ -165,7 +165,7 @@ func TestTxm_NewLegacyAttempt(t *testing.T) { gc.priceMin = assets.NewWeiI(10) gc.priceMax = assets.NewWeiI(50) cks := txmgr.NewEvmTxAttemptBuilder(*big.NewInt(1), gc, kst, nil) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) t.Run("creates attempt with fields", func(t *testing.T) { var n evmtypes.Nonce @@ -189,7 +189,7 @@ func TestTxm_NewCustomTxAttempt_NonRetryableErrors(t *testing.T) { t.Parallel() kst := ksmocks.NewEth(t) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) cks := txmgr.NewEvmTxAttemptBuilder(*big.NewInt(1), newFeeConfig(), kst, nil) dynamicFee := gas.DynamicFee{TipCap: assets.GWei(100), FeeCap: assets.GWei(200)} @@ -222,7 +222,7 @@ func TestTxm_EvmTxAttemptBuilder_RetryableEstimatorError(t *testing.T) { est.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{}, uint32(0), errors.New("fail")) kst := ksmocks.NewEth(t) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) ctx := testutils.Context(t) cks := txmgr.NewEvmTxAttemptBuilder(*big.NewInt(1), &feeConfig{eip1559DynamicFees: true}, kst, est) diff --git a/core/chains/evm/txmgr/broadcaster_test.go b/core/chains/evm/txmgr/broadcaster_test.go index dd2a124be49..c6e05b0954b 100644 --- a/core/chains/evm/txmgr/broadcaster_test.go +++ b/core/chains/evm/txmgr/broadcaster_test.go @@ -15,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/google/uuid" - "github.com/onsi/gomega" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -23,11 +22,14 @@ import ( "go.uber.org/zap/zapcore" "gopkg.in/guregu/null.v4" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" @@ -37,16 +39,12 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" ksmocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks" - "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/services/pg/datatypes" - pgmocks "github.com/smartcontractkit/chainlink/v2/core/services/pg/mocks" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -54,24 +52,21 @@ import ( func NewTestEthBroadcaster( t testing.TB, txStore txmgr.TestEvmTxStore, - ethClient evmclient.Client, + ethClient client.Client, keyStore keystore.Eth, config evmconfig.ChainScopedConfig, checkerFactory txmgr.TransmitCheckerFactory, nonceAutoSync bool, ) *txmgr.Broadcaster { t.Helper() - eb := cltest.NewEventBroadcaster(t, config.Database().URL()) ctx := testutils.Context(t) - require.NoError(t, eb.Start(ctx)) - t.Cleanup(func() { assert.NoError(t, eb.Close()) }) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) ge := config.EVM().GasEstimator() estimator := gas.NewWrappedEvmEstimator(gas.NewFixedPriceEstimator(config.EVM().GasEstimator(), ge.BlockHistory(), lggr), ge.EIP1559DynamicFees(), nil) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, keyStore, estimator) txNonceSyncer := txmgr.NewNonceSyncer(txStore, lggr, ethClient) - ethBroadcaster := txmgr.NewEvmBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), config.Database().Listener(), keyStore, eb, txBuilder, txNonceSyncer, lggr, checkerFactory, nonceAutoSync) + ethBroadcaster := txmgr.NewEvmBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), config.Database().Listener(), keyStore, txBuilder, txNonceSyncer, lggr, checkerFactory, nonceAutoSync) // Mark instance as test ethBroadcaster.XXXTestDisableUnstartedTxAutoProcessing() @@ -81,11 +76,7 @@ func NewTestEthBroadcaster( } func TestEthBroadcaster_Lifecycle(t *testing.T) { - cfg, db := heavyweight.FullTestDBV2(t, "eth_broadcaster_optimistic_locking", nil) - eventBroadcaster := cltest.NewEventBroadcaster(t, cfg.Database().URL()) - err := eventBroadcaster.Start(testutils.Context(t)) - require.NoError(t, err) - t.Cleanup(func() { assert.NoError(t, eventBroadcaster.Close()) }) + cfg, db := heavyweight.FullTestDBV2(t, nil) txStore := cltest.NewTestTxStore(t, db, cfg.Database()) evmcfg := evmtest.NewChainScopedConfig(t, cfg) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) @@ -102,16 +93,15 @@ func TestEthBroadcaster_Lifecycle(t *testing.T) { evmcfg.EVM().Transactions(), evmcfg.Database().Listener(), ethKeyStore, - eventBroadcaster, txBuilder, nil, - logger.TestLogger(t), + logger.Test(t), &testCheckerFactory{}, false, ) // Can't close an unstarted instance - err = eb.Close() + err := eb.Close() require.Error(t, err) ctx := testutils.Context(t) @@ -123,10 +113,10 @@ func TestEthBroadcaster_Lifecycle(t *testing.T) { err = eb.Close() require.NoError(t, err) - // Can't start more than once (Broadcaster implements utils.StartStopOnce) + // Can't start more than once (Broadcaster uses services.StateMachine) err = eb.Start(ctx) require.Error(t, err) - // Can't close more than once (Broadcaster implements utils.StartStopOnce) + // Can't close more than once (Broadcaster uses services.StateMachine) err = eb.Close() require.Error(t, err) @@ -134,9 +124,9 @@ func TestEthBroadcaster_Lifecycle(t *testing.T) { require.Error(t, eb.XXXTestCloseInternal()) // Can successfully startInternal a previously closed instance - require.NoError(t, eb.XXXTestStartInternal()) + require.NoError(t, eb.XXXTestStartInternal(ctx)) // Can't startInternal already started instance - require.Error(t, eb.XXXTestStartInternal()) + require.Error(t, eb.XXXTestStartInternal(ctx)) // Can successfully closeInternal again require.NoError(t, eb.XXXTestCloseInternal()) } @@ -145,10 +135,6 @@ func TestEthBroadcaster_Lifecycle(t *testing.T) { func TestEthBroadcaster_LoadNextSequenceMapFailure_StartupSuccess(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - eventBroadcaster := cltest.NewEventBroadcaster(t, cfg.Database().URL()) - err := eventBroadcaster.Start(testutils.Context(t)) - require.NoError(t, err) - t.Cleanup(func() { assert.NoError(t, eventBroadcaster.Close()) }) txStore := cltest.NewTestTxStore(t, db, cfg.Database()) evmcfg := evmtest.NewChainScopedConfig(t, cfg) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) @@ -165,17 +151,17 @@ func TestEthBroadcaster_LoadNextSequenceMapFailure_StartupSuccess(t *testing.T) evmcfg.EVM().Transactions(), evmcfg.Database().Listener(), ethKeyStore, - eventBroadcaster, txBuilder, nil, - logger.TestLogger(t), + logger.Test(t), &testCheckerFactory{}, false, ) // Instance starts without error even if loading next sequence map fails - err = eb.Start(testutils.Context(t)) + err := eb.Start(testutils.Context(t)) require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, eb.Close()) }) } func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { @@ -211,7 +197,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { }) t.Run("eth_txes exist for a different from address", func(t *testing.T) { - cltest.MustCreateUnstartedTx(t, txStore, otherAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + mustCreateUnstartedTx(t, txStore, otherAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) retryable, err := eb.ProcessUnstartedTxs(testutils.Context(t), fromAddress) assert.NoError(t, err) assert.False(t, retryable) @@ -265,13 +251,13 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { } ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(2) && tx.Value().Cmp(big.NewInt(242)) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() // Earlier tr := int32(99) b, err := json.Marshal(txmgr.TxMeta{JobID: &tr}) require.NoError(t, err) - meta := datatypes.JSON(b) + meta := sqlutil.JSON(b) earlierEthTx := txmgr.Tx{ FromAddress: fromAddress, ToAddress: toAddress, @@ -293,7 +279,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { require.Equal(t, value.String(), tx.Value().String()) require.Equal(t, earlierEthTx.EncodedPayload, tx.Data()) return true - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() // Later laterEthTx := txmgr.Tx{ @@ -316,7 +302,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { require.Equal(t, value.String(), tx.Value().String()) require.Equal(t, laterEthTx.EncodedPayload, tx.Data()) return true - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() // Insertion order deliberately reversed to test ordering require.NoError(t, txStore.InsertTx(&expensiveEthTx)) @@ -397,9 +383,9 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { t.Run("sends transactions with type 0x2 in EIP-1559 mode", func(t *testing.T) { ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(343) && tx.Value().Cmp(big.NewInt(242)) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() - etx := cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, []byte{42, 42, 0}, gasLimit, big.Int(assets.NewEthValue(242)), &cltest.FixtureChainID) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, []byte{42, 42, 0}, gasLimit, big.Int(assets.NewEthValue(242)), &cltest.FixtureChainID) // Do the thing { retryable, err := eb.ProcessUnstartedTxs(testutils.Context(t), fromAddress) @@ -448,7 +434,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { } ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(344) && tx.Value().Cmp(big.NewInt(442)) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() ethClient.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", mock.MatchedBy(func(callarg map[string]interface{}) bool { if fmt.Sprintf("%s", callarg["value"]) == "0x1ba" { // 442 assert.Equal(t, txRequest.FromAddress, callarg["from"]) @@ -464,7 +450,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { return false }), "latest").Return(nil).Once() - ethTx := cltest.MustCreateUnstartedTxFromEvmTxRequest(t, txStore, txRequest, &cltest.FixtureChainID) + ethTx := mustCreateUnstartedTxFromEvmTxRequest(t, txStore, txRequest, &cltest.FixtureChainID) { retryable, err := eb.ProcessUnstartedTxs(testutils.Context(t), fromAddress) @@ -481,14 +467,14 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { t.Run("with unknown error, sends tx as normal", func(t *testing.T) { ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(345) && tx.Value().Cmp(big.NewInt(542)) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() ethClient.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", mock.MatchedBy(func(callarg map[string]interface{}) bool { return fmt.Sprintf("%s", callarg["value"]) == "0x21e" // 542 }), "latest").Return(errors.New("this is not a revert, something unexpected went wrong")).Once() - ethTx := cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, - cltest.EvmTxRequestWithChecker(checker), - cltest.EvmTxRequestWithValue(big.Int(assets.NewEthValue(542)))) + ethTx := mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, + txRequestWithChecker(checker), + txRequestWithValue(big.Int(assets.NewEthValue(542)))) { retryable, err := eb.ProcessUnstartedTxs(testutils.Context(t), fromAddress) @@ -502,7 +488,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { }) t.Run("on revert, marks tx as fatally errored and does not send", func(t *testing.T) { - jerr := evmclient.JsonError{ + jerr := client.JsonError{ Code: 42, Message: "oh no, it reverted", Data: []byte{42, 166, 34}, @@ -511,9 +497,9 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { return fmt.Sprintf("%s", callarg["value"]) == "0x282" // 642 }), "latest").Return(&jerr).Once() - ethTx := cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, - cltest.EvmTxRequestWithChecker(checker), - cltest.EvmTxRequestWithValue(big.Int(assets.NewEthValue(642)))) + ethTx := mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, + txRequestWithChecker(checker), + txRequestWithValue(big.Int(assets.NewEthValue(642)))) { retryable, err := eb.ProcessUnstartedTxs(testutils.Context(t), fromAddress) assert.NoError(t, err) @@ -551,11 +537,11 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == 0 && tx.Value().Cmp(big.NewInt(442)) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() - ethTx := cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, - cltest.EvmTxRequestWithValue(big.Int(assets.NewEthValue(442))), - cltest.EvmTxRequestWithChecker(checker)) + ethTx := mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, + txRequestWithValue(big.Int(assets.NewEthValue(442))), + txRequestWithChecker(checker)) { retryable, err := eb.ProcessUnstartedTxs(testutils.Context(t), fromAddress) assert.NoError(t, err) @@ -574,11 +560,11 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == 1 && tx.Value().Cmp(big.NewInt(442)) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() - ethTx := cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, - cltest.EvmTxRequestWithValue(big.Int(assets.NewEthValue(442))), - cltest.EvmTxRequestWithChecker(checker)) + ethTx := mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, + txRequestWithValue(big.Int(assets.NewEthValue(442))), + txRequestWithChecker(checker)) { retryable, err := eb.ProcessUnstartedTxs(testutils.Context(t), fromAddress) assert.NoError(t, err) @@ -595,7 +581,7 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { // Checker will return a fatal error checkerFactory.err = errors.New("fatal checker error") - ethTx := cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, cltest.EvmTxRequestWithChecker(checker)) + ethTx := mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, txRequestWithChecker(checker)) { retryable, err := eb.ProcessUnstartedTxs(testutils.Context(t), fromAddress) assert.NoError(t, err) @@ -613,10 +599,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.FullTestDBV2(t, "eth_broadcaster_optimistic_locking", nil) - eventBroadcaster := cltest.NewEventBroadcaster(t, cfg.Database().URL()) - require.NoError(t, eventBroadcaster.Start(testutils.Context(t))) - t.Cleanup(func() { assert.NoError(t, eventBroadcaster.Close()) }) + cfg, db := heavyweight.FullTestDBV2(t, nil) txStore := cltest.NewTestTxStore(t, db, cfg.Database()) ccfg := evmtest.NewChainScopedConfig(t, cfg) evmcfg := txmgr.NewEvmTxmConfig(ccfg.EVM()) @@ -642,10 +625,9 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi ccfg.EVM().Transactions(), cfg.Database().Listener(), ethKeyStore, - eventBroadcaster, txBuilder, nil, - logger.TestLogger(t), + logger.Test(t), &testCheckerFactory{}, false, ) @@ -655,7 +637,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi require.NoError(t, eb.Start(testutils.Context(t))) t.Cleanup(func() { assert.NoError(t, eb.Close()) }) - cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) + mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) go func() { select { @@ -698,7 +680,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success_WithMultiplier(t *testing ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { assert.Equal(t, int(1600), int(tx.Gas())) return true - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() txRequest := txmgr.TxRequest{ FromAddress: fromAddress, @@ -708,7 +690,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success_WithMultiplier(t *testing FeeLimit: 1231, Strategy: txmgrcommon.NewSendEveryStrategy(), } - cltest.MustCreateUnstartedTxFromEvmTxRequest(t, txStore, txRequest, &cltest.FixtureChainID) + mustCreateUnstartedTxFromEvmTxRequest(t, txStore, txRequest, &cltest.FixtureChainID) // Do the thing { @@ -778,11 +760,11 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { // Crashed right after we commit the database transaction that saved // the nonce to the eth_tx so evm.key_states.next_nonce has not been // incremented yet - inProgressEthTx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) + inProgressEthTx := mustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(firstNonce) - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() // Do the thing { @@ -814,11 +796,11 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx - inProgressEthTx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) + inProgressEthTx := mustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(firstNonce) - }), fromAddress).Return(clienttypes.Fatal, errors.New("exceeds block gas limit")).Once() + }), fromAddress).Return(commonclient.Fatal, errors.New("exceeds block gas limit")).Once() // Do the thing { @@ -850,11 +832,11 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx - inProgressEthTx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) + inProgressEthTx := mustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(firstNonce) - }), fromAddress).Return(clienttypes.Successful, errors.New("known transaction: a1313bd99a81fb4d8ad1d2e90b67c6b3fa77545c990d6251444b83b70b6f8980")).Once() + }), fromAddress).Return(commonclient.Successful, errors.New("known transaction: a1313bd99a81fb4d8ad1d2e90b67c6b3fa77545c990d6251444b83b70b6f8980")).Once() // Do the thing { @@ -885,11 +867,11 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx - inProgressEthTx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) + inProgressEthTx := mustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(firstNonce) - }), fromAddress).Return(clienttypes.TransactionAlreadyKnown, errors.New("nonce too low")).Once() + }), fromAddress).Return(commonclient.TransactionAlreadyKnown, errors.New("nonce too low")).Once() // Do the thing { @@ -922,11 +904,11 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx - inProgressEthTx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) + inProgressEthTx := mustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(firstNonce) - }), fromAddress).Return(clienttypes.Retryable, failedToReachNodeError).Once() + }), fromAddress).Return(commonclient.Retryable, failedToReachNodeError).Once() // Do the thing retryable, err := eb.ProcessUnstartedTxs(testutils.Context(t), fromAddress) @@ -963,7 +945,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx - inProgressEthTx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) + inProgressEthTx := mustInsertInProgressEthTxWithAttempt(t, txStore, firstNonce, fromAddress) require.Len(t, inProgressEthTx.TxAttempts, 1) attempt := inProgressEthTx.TxAttempts[0] @@ -972,7 +954,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { s, e := txmgr.GetGethSignedTx(attempt.SignedRawTx) require.NoError(t, e) return tx.Nonce() == uint64(firstNonce) && tx.GasPrice().Int64() == s.GasPrice().Int64() - }), fromAddress).Return(clienttypes.Successful, errors.New("known transaction: a1313bd99a81fb4d8ad1d2e90b67c6b3fa77545c990d6251444b83b70b6f8980")).Once() + }), fromAddress).Return(commonclient.Successful, errors.New("known transaction: a1313bd99a81fb4d8ad1d2e90b67c6b3fa77545c990d6251444b83b70b6f8980")).Once() // Do the thing { @@ -1029,11 +1011,11 @@ 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) { - cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) // First send, replacement underpriced ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(0) - }), fromAddress).Return(clienttypes.Successful, errors.New("replacement transaction underpriced")).Once() + }), fromAddress).Return(commonclient.Successful, errors.New("replacement transaction underpriced")).Once() // Do the thing retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) @@ -1067,10 +1049,10 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { localNextNonce := getLocalNextNonce(t, eb, fromAddress) t.Run("without callback", func(t *testing.T) { - etx := cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.Fatal, errors.New(fatalErrorExample)).Once() + }), fromAddress).Return(commonclient.Fatal, errors.New(fatalErrorExample)).Once() retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) assert.NoError(t, err) @@ -1108,6 +1090,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { FeeLimit: gasLimit, State: txmgrcommon.TxUnstarted, PipelineTaskRunID: uuid.NullUUID{UUID: tr.ID, Valid: true}, + SignalCallback: true, } t.Run("with erroring callback bails out", func(t *testing.T) { @@ -1120,7 +1103,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.Fatal, errors.New(fatalErrorExample)).Once() + }), fromAddress).Return(commonclient.Fatal, errors.New(fatalErrorExample)).Once() retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) require.Error(t, err) @@ -1141,7 +1124,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.Fatal, errors.New(fatalErrorExample)).Once() + }), fromAddress).Return(commonclient.Fatal, errors.New(fatalErrorExample)).Once() { retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) @@ -1151,17 +1134,12 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { // same as the parent test, but callback is set by ctor t.Run("callback set by ctor", func(t *testing.T) { - eventBroadcaster := pg.NewEventBroadcaster(cfg.Database().URL(), 0, 0, logger.TestLogger(t), uuid.New()) - err := eventBroadcaster.Start(testutils.Context(t)) - require.NoError(t, err) - t.Cleanup(func() { assert.NoError(t, eventBroadcaster.Close()) }) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) estimator := gas.NewWrappedEvmEstimator(gas.NewFixedPriceEstimator(evmcfg.EVM().GasEstimator(), evmcfg.EVM().GasEstimator().BlockHistory(), lggr), evmcfg.EVM().GasEstimator().EIP1559DynamicFees(), nil) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), evmcfg.EVM().GasEstimator(), ethKeyStore, estimator) localNextNonce = getLocalNextNonce(t, eb, fromAddress) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(localNextNonce), nil).Once() - eb2 := txmgr.NewEvmBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(evmcfg.EVM()), txmgr.NewEvmTxmFeeConfig(evmcfg.EVM().GasEstimator()), evmcfg.EVM().Transactions(), evmcfg.Database().Listener(), ethKeyStore, eventBroadcaster, txBuilder, nil, lggr, &testCheckerFactory{}, false) - require.NoError(t, err) + eb2 := txmgr.NewEvmBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(evmcfg.EVM()), txmgr.NewEvmTxmFeeConfig(evmcfg.EVM().GasEstimator()), evmcfg.EVM().Transactions(), evmcfg.Database().Listener(), ethKeyStore, txBuilder, nil, lggr, &testCheckerFactory{}, false) retryable, err := eb2.ProcessUnstartedTxs(ctx, fromAddress) assert.NoError(t, err) assert.False(t, retryable) @@ -1175,10 +1153,10 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { t.Run("geth Client fails with error indicating that the transaction was too expensive", func(t *testing.T) { TxFeeExceedsCapError := "tx fee (1.10 ether) exceeds the configured cap (1.00 ether)" localNextNonce := getLocalNextNonce(t, eb, fromAddress) - etx := cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.ExceedsMaxFee, errors.New(TxFeeExceedsCapError)).Twice() + }), fromAddress).Return(commonclient.ExceedsMaxFee, errors.New(TxFeeExceedsCapError)).Twice() // In the first case, the tx was NOT accepted into the mempool. In the case // of multiple RPC nodes, it is possible that it can be accepted by // another node even if the primary one returns "exceeds the configured @@ -1233,10 +1211,10 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { t.Run("eth Client call fails with an unexpected random error, and transaction was not accepted into mempool", func(t *testing.T) { retryableErrorExample := "some unknown error" localNextNonce := getLocalNextNonce(t, eb, fromAddress) - etx := cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(localNextNonce) - }), fromAddress).Return(clienttypes.Unknown, errors.New(retryableErrorExample)).Once() + }), fromAddress).Return(commonclient.Unknown, errors.New(retryableErrorExample)).Once() // Nonce is the same as localNextNonce, implying that this sent transaction has not been accepted ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(localNextNonce), nil).Once() @@ -1262,7 +1240,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { // Now on the second run, it is successful ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() retryable, err = eb.ProcessUnstartedTxs(ctx, fromAddress) assert.NoError(t, err) @@ -1285,10 +1263,10 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { t.Run("eth client call fails with an unexpected random error, and the nonce check also subsequently fails", func(t *testing.T) { retryableErrorExample := "some unknown error" localNextNonce := getLocalNextNonce(t, eb, fromAddress) - etx := cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == uint64(localNextNonce) - }), fromAddress).Return(clienttypes.Unknown, errors.New(retryableErrorExample)).Once() + }), fromAddress).Return(commonclient.Unknown, errors.New(retryableErrorExample)).Once() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), errors.New("pending nonce fetch failed")).Once() // Do the thing @@ -1314,7 +1292,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { // Now on the second run, it is successful ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() retryable, err = eb.ProcessUnstartedTxs(ctx, fromAddress) assert.NoError(t, err) @@ -1337,10 +1315,10 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { t.Run("eth Client call fails with an unexpected random error, and transaction was accepted into mempool", func(t *testing.T) { retryableErrorExample := "some strange RPC returns an unexpected thing" localNextNonce := getLocalNextNonce(t, eb, fromAddress) - etx := cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.Unknown, errors.New(retryableErrorExample)).Once() + }), fromAddress).Return(commonclient.Unknown, errors.New(retryableErrorExample)).Once() // Nonce is one higher than localNextNonce, implying that despite the error, this sent transaction has been accepted into the mempool ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(localNextNonce+1), nil).Once() @@ -1369,22 +1347,22 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { // 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, eb, fromAddress) - etx := cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) // First was underpriced ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce && tx.GasPrice().Cmp(evmcfg.EVM().GasEstimator().PriceDefault().ToInt()) == 0 - }), fromAddress).Return(clienttypes.Underpriced, errors.New(underpricedError)).Once() + }), fromAddress).Return(commonclient.Underpriced, errors.New(underpricedError)).Once() // Second with gas bump was still underpriced ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce && tx.GasPrice().Cmp(big.NewInt(25000000000)) == 0 - }), fromAddress).Return(clienttypes.Underpriced, errors.New(underpricedError)).Once() + }), fromAddress).Return(commonclient.Underpriced, errors.New(underpricedError)).Once() // Third succeeded ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce && tx.GasPrice().Cmp(big.NewInt(30000000000)) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() // Do the thing retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) @@ -1420,7 +1398,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.Retryable, failedToReachNodeError).Once() + }), fromAddress).Return(commonclient.Retryable, failedToReachNodeError).Once() // Do the thing retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) @@ -1451,7 +1429,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.Successful, errors.New(temporarilyUnderpricedError)).Once() + }), fromAddress).Return(commonclient.Successful, errors.New(temporarilyUnderpricedError)).Once() // Do the thing retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) @@ -1486,12 +1464,12 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { })) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(localNextNonce), nil).Once() eb2 := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg2, &testCheckerFactory{}, false) - cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) // First was underpriced ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce && tx.GasPrice().Cmp(evmcfg2.EVM().GasEstimator().PriceDefault().ToInt()) == 0 - }), fromAddress).Return(clienttypes.Underpriced, errors.New(underpricedError)).Once() + }), fromAddress).Return(commonclient.Underpriced, errors.New(underpricedError)).Once() // Do the thing retryable, err := eb2.ProcessUnstartedTxs(ctx, fromAddress) @@ -1506,10 +1484,10 @@ 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, eb, fromAddress) - etx := cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.InsufficientFunds, errors.New(insufficientEthError)).Once() + }), fromAddress).Return(commonclient.InsufficientFunds, errors.New(insufficientEthError)).Once() retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) require.Error(t, err) @@ -1536,10 +1514,10 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { t.Run("eth tx is left in progress if nonce is too high", func(t *testing.T) { localNextNonce := getLocalNextNonce(t, eb, fromAddress) nonceGapError := "NonceGap, Future nonce. Expected nonce: " + strconv.FormatUint(localNextNonce, 10) - etx := cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce - }), fromAddress).Return(clienttypes.Retryable, errors.New(nonceGapError)).Once() + }), fromAddress).Return(commonclient.Retryable, errors.New(nonceGapError)).Once() retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) require.Error(t, err) @@ -1578,12 +1556,12 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { localNextNonce := getLocalNextNonce(t, eb, fromAddress) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(localNextNonce), nil).Once() eb2 := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg2, &testCheckerFactory{}, false) - cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) underpricedError := "transaction underpriced" localNextNonce = getLocalNextNonce(t, eb, fromAddress) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce && tx.GasTipCap().Cmp(big.NewInt(1)) == 0 - }), fromAddress).Return(clienttypes.Underpriced, errors.New(underpricedError)).Once() + }), fromAddress).Return(commonclient.Underpriced, errors.New(underpricedError)).Once() // Check gas tip cap verification retryable, err := eb2.ProcessUnstartedTxs(ctx, fromAddress) @@ -1600,7 +1578,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { // 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, eb, fromAddress) - cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) // Check gas tip cap verification evmcfg2 := evmtest.NewChainScopedConfig(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { @@ -1627,15 +1605,15 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { // Second was underpriced but above minimum ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce && tx.GasTipCap().Cmp(gasTipCapDefault.ToInt()) == 0 - }), fromAddress).Return(clienttypes.Underpriced, errors.New(underpricedError)).Once() + }), fromAddress).Return(commonclient.Underpriced, errors.New(underpricedError)).Once() // Resend at the bumped price ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce && tx.GasTipCap().Cmp(big.NewInt(0).Add(gasTipCapDefault.ToInt(), evmcfg2.EVM().GasEstimator().BumpMin().ToInt())) == 0 - }), fromAddress).Return(clienttypes.Underpriced, errors.New(underpricedError)).Once() + }), fromAddress).Return(commonclient.Underpriced, errors.New(underpricedError)).Once() // Final bump succeeds ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == localNextNonce && tx.GasTipCap().Cmp(big.NewInt(0).Add(gasTipCapDefault.ToInt(), big.NewInt(0).Mul(evmcfg2.EVM().GasEstimator().BumpMin().ToInt(), big.NewInt(2)))) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() retryable, err = eb2.ProcessUnstartedTxs(ctx, fromAddress) require.NoError(t, err) @@ -1674,7 +1652,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) { require.NoError(t, err) t.Run("tx signing fails", func(t *testing.T) { - etx := cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, &cltest.FixtureChainID) tx := *gethTypes.NewTx(&gethTypes.LegacyTx{}) kst.On("SignTx", fromAddress, @@ -1764,34 +1742,11 @@ func TestEthBroadcaster_Trigger(t *testing.T) { eb.Trigger(testutils.NewAddress()) } -func TestEthBroadcaster_EthTxInsertEventCausesTriggerToFire(t *testing.T) { - // NOTE: Testing triggers requires committing transactions and does not work with transactional tests - cfg, db := heavyweight.FullTestDBV2(t, "eth_tx_triggers", nil) - txStore := cltest.NewTestTxStore(t, db, cfg.Database()) - - evmcfg := evmtest.NewChainScopedConfig(t, cfg) - - ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() - _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) - eventBroadcaster := cltest.NewEventBroadcaster(t, evmcfg.Database().URL()) - require.NoError(t, eventBroadcaster.Start(testutils.Context(t))) - t.Cleanup(func() { require.NoError(t, eventBroadcaster.Close()) }) - - ethTxInsertListener, err := eventBroadcaster.Subscribe(pg.ChannelInsertOnTx, "") - require.NoError(t, err) - - // Give it some time to start listening - time.Sleep(100 * time.Millisecond) - - cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) - gomega.NewWithT(t).Eventually(ethTxInsertListener.Events()).Should(gomega.Receive()) -} - func TestEthBroadcaster_SyncNonce(t *testing.T) { db := pgtest.NewSqlxDB(t) ctx := testutils.Context(t) - lggr, observed := logger.TestLoggerObserved(t, zapcore.DebugLevel) + lggr, observed := logger.TestObserved(t, zapcore.DebugLevel) cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(true) }) @@ -1805,11 +1760,6 @@ func TestEthBroadcaster_SyncNonce(t *testing.T) { ethNodeNonce := uint64(22) - eventBroadcaster := pgmocks.NewEventBroadcaster(t) - sub := pgmocks.NewSubscription(t) - sub.On("Events").Return(make(<-chan pg.Event)) - sub.On("Close") - eventBroadcaster.On("Subscribe", "evm.insert_on_txes", "").Return(sub, nil) estimator := gas.NewWrappedEvmEstimator(gas.NewFixedPriceEstimator(evmcfg.EVM().GasEstimator(), evmcfg.EVM().GasEstimator().BlockHistory(), lggr), evmcfg.EVM().GasEstimator().EIP1559DynamicFees(), nil) checkerFactory := &testCheckerFactory{} @@ -1823,7 +1773,7 @@ func TestEthBroadcaster_SyncNonce(t *testing.T) { addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", &cltest.FixtureChainID).Return(addresses, nil).Once() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - eb := txmgr.NewEvmBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), evmTxmCfg, txmgr.NewEvmTxmFeeConfig(ge), evmcfg.EVM().Transactions(), cfg.Database().Listener(), kst, eventBroadcaster, txBuilder, nil, lggr, checkerFactory, false) + eb := txmgr.NewEvmBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), evmTxmCfg, txmgr.NewEvmTxmFeeConfig(ge), evmcfg.EVM().Transactions(), cfg.Database().Listener(), kst, txBuilder, nil, lggr, checkerFactory, false) err := eb.Start(ctx) assert.NoError(t, err) @@ -1841,7 +1791,7 @@ func TestEthBroadcaster_SyncNonce(t *testing.T) { addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", &cltest.FixtureChainID).Return(addresses, nil).Once() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - eb := txmgr.NewEvmBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), evmTxmCfg, txmgr.NewEvmTxmFeeConfig(ge), evmcfg.EVM().Transactions(), cfg.Database().Listener(), kst, eventBroadcaster, txBuilder, txNonceSyncer, lggr, checkerFactory, true) + eb := txmgr.NewEvmBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), evmTxmCfg, txmgr.NewEvmTxmFeeConfig(ge), evmcfg.EVM().Transactions(), cfg.Database().Listener(), kst, txBuilder, txNonceSyncer, lggr, checkerFactory, true) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(ethNodeNonce), nil).Once() require.NoError(t, eb.Start(ctx)) @@ -1872,7 +1822,7 @@ func TestEthBroadcaster_SyncNonce(t *testing.T) { kst.On("EnabledAddressesForChain", &cltest.FixtureChainID).Return(addresses, nil).Once() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - eb := txmgr.NewEvmBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), evmTxmCfg, txmgr.NewEvmTxmFeeConfig(evmcfg.EVM().GasEstimator()), evmcfg.EVM().Transactions(), cfg.Database().Listener(), kst, eventBroadcaster, txBuilder, txNonceSyncer, lggr, checkerFactory, true) + eb := txmgr.NewEvmBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), evmTxmCfg, txmgr.NewEvmTxmFeeConfig(evmcfg.EVM().GasEstimator()), evmcfg.EVM().Transactions(), cfg.Database().Listener(), kst, txBuilder, txNonceSyncer, lggr, checkerFactory, true) eb.XXXTestDisableUnstartedTxAutoProcessing() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), errors.New("something exploded")).Once() diff --git a/core/chains/evm/txmgr/builder.go b/core/chains/evm/txmgr/builder.go index 39781e83f4c..f0cbcbf8d92 100644 --- a/core/chains/evm/txmgr/builder.go +++ b/core/chains/evm/txmgr/builder.go @@ -4,8 +4,9 @@ import ( "math/big" "time" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" @@ -14,9 +15,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" - "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) // NewTxm constructs the necessary dependencies for the EvmTxm (broadcaster, confirmer, etc) and returns a new EvmTxManager @@ -31,7 +30,6 @@ func NewTxm( lggr logger.Logger, logPoller logpoller.LogPoller, keyStore keystore.Eth, - eventBroadcaster pg.EventBroadcaster, estimator gas.EvmFeeEstimator, ) (txm TxManager, err error, @@ -52,13 +50,15 @@ func NewTxm( txmCfg := NewEvmTxmConfig(chainConfig) // wrap Evm specific config feeCfg := NewEvmTxmFeeConfig(fCfg) // wrap Evm specific config txmClient := NewEvmTxmClient(client) // wrap Evm specific client - ethBroadcaster := NewEvmBroadcaster(txStore, txmClient, txmCfg, feeCfg, txConfig, listenerConfig, keyStore, eventBroadcaster, txAttemptBuilder, txNonceSyncer, lggr, checker, chainConfig.NonceAutoSync()) - ethConfirmer := NewEvmConfirmer(txStore, txmClient, txmCfg, feeCfg, txConfig, dbConfig, keyStore, txAttemptBuilder, lggr) - var ethResender *Resender + chainID := txmClient.ConfiguredChainID() + evmBroadcaster := NewEvmBroadcaster(txStore, txmClient, txmCfg, feeCfg, txConfig, listenerConfig, keyStore, txAttemptBuilder, txNonceSyncer, lggr, checker, chainConfig.NonceAutoSync()) + evmTracker := NewEvmTracker(txStore, keyStore, chainID, lggr) + evmConfirmer := NewEvmConfirmer(txStore, txmClient, txmCfg, feeCfg, txConfig, dbConfig, keyStore, txAttemptBuilder, lggr) + var evmResender *Resender if txConfig.ResendAfterThreshold() > 0 { - ethResender = NewEvmResender(lggr, txStore, txmClient, keyStore, txmgr.DefaultResenderPollInterval, chainConfig, txConfig) + evmResender = NewEvmResender(lggr, txStore, txmClient, evmTracker, keyStore, txmgr.DefaultResenderPollInterval, chainConfig, txConfig) } - txm = NewEvmTxm(txmClient.ConfiguredChainID(), txmCfg, txConfig, keyStore, lggr, checker, fwdMgr, txAttemptBuilder, txStore, txNonceSyncer, ethBroadcaster, ethConfirmer, ethResender) + txm = NewEvmTxm(chainID, txmCfg, txConfig, keyStore, lggr, checker, fwdMgr, txAttemptBuilder, txStore, txNonceSyncer, evmBroadcaster, evmConfirmer, evmResender, evmTracker) return txm, nil } @@ -77,21 +77,23 @@ func NewEvmTxm( broadcaster *Broadcaster, confirmer *Confirmer, resender *Resender, + tracker *Tracker, ) *Txm { - return txmgr.NewTxm(chainId, cfg, txCfg, keyStore, lggr, checkerFactory, fwdMgr, txAttemptBuilder, txStore, nonceSyncer, broadcaster, confirmer, resender) + return txmgr.NewTxm(chainId, cfg, txCfg, keyStore, lggr, checkerFactory, fwdMgr, txAttemptBuilder, txStore, nonceSyncer, broadcaster, confirmer, resender, tracker) } -// NewEvnResender creates a new concrete EvmResender +// NewEvmResender creates a new concrete EvmResender func NewEvmResender( lggr logger.Logger, txStore TransactionStore, client TransactionClient, + tracker *Tracker, ks KeyStore, pollInterval time.Duration, config EvmResenderConfig, txConfig txmgrtypes.ResenderTransactionsConfig, ) *Resender { - return txmgr.NewResender(lggr, txStore, client, ks, pollInterval, config, txConfig) + return txmgr.NewResender(lggr, txStore, client, tracker, ks, pollInterval, config, txConfig) } // NewEvmReaper instantiates a new EVM-specific reaper object @@ -114,6 +116,16 @@ func NewEvmConfirmer( return txmgr.NewConfirmer(txStore, client, chainConfig, feeConfig, txConfig, dbConfig, keystore, txAttemptBuilder, lggr, func(r *evmtypes.Receipt) bool { return r == nil }) } +// NewEvmTracker instantiates a new EVM tracker for abandoned transactions +func NewEvmTracker( + txStore TxStore, + keyStore KeyStore, + chainID *big.Int, + lggr logger.Logger, +) *Tracker { + return txmgr.NewTracker(txStore, keyStore, chainID, lggr) +} + // NewEvmBroadcaster returns a new concrete EvmBroadcaster func NewEvmBroadcaster( txStore TransactionStore, @@ -123,12 +135,11 @@ func NewEvmBroadcaster( txConfig txmgrtypes.BroadcasterTransactionsConfig, listenerConfig txmgrtypes.BroadcasterListenerConfig, keystore KeyStore, - eventBroadcaster pg.EventBroadcaster, txAttemptBuilder TxAttemptBuilder, nonceSyncer NonceSyncer, logger logger.Logger, checkerFactory TransmitCheckerFactory, autoSyncNonce bool, ) *Broadcaster { - return txmgr.NewBroadcaster(txStore, client, chainConfig, feeConfig, txConfig, listenerConfig, keystore, eventBroadcaster, txAttemptBuilder, nonceSyncer, logger, checkerFactory, autoSyncNonce, stringToGethAddress, evmtypes.GenerateNextNonce) + return txmgr.NewBroadcaster(txStore, client, chainConfig, feeConfig, txConfig, listenerConfig, keystore, txAttemptBuilder, nonceSyncer, logger, checkerFactory, autoSyncNonce, evmtypes.GenerateNextNonce) } diff --git a/core/chains/evm/txmgr/client.go b/core/chains/evm/txmgr/client.go index 150ee277577..d08274f74b6 100644 --- a/core/chains/evm/txmgr/client.go +++ b/core/chains/evm/txmgr/client.go @@ -13,21 +13,21 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) var _ TxmClient = (*evmTxmClient)(nil) type evmTxmClient struct { - client evmclient.Client + client client.Client } -func NewEvmTxmClient(c evmclient.Client) *evmTxmClient { +func NewEvmTxmClient(c client.Client) *evmTxmClient { return &evmTxmClient{client: c} } @@ -45,14 +45,14 @@ func (c *evmTxmClient) BatchSendTransactions( batchSize int, lggr logger.Logger, ) ( - codes []clienttypes.SendTxReturnCode, + codes []commonclient.SendTxReturnCode, txErrs []error, broadcastTime time.Time, successfulTxIDs []int64, err error, ) { // preallocate - codes = make([]clienttypes.SendTxReturnCode, len(attempts)) + codes = make([]commonclient.SendTxReturnCode, len(attempts)) txErrs = make([]error, len(attempts)) reqs, broadcastTime, successfulTxIDs, batchErr := batchSendTransactions(ctx, attempts, batchSize, lggr, c.client) @@ -62,7 +62,7 @@ func (c *evmTxmClient) BatchSendTransactions( if len(reqs) != len(attempts) { lenErr := fmt.Errorf("Returned request data length (%d) != number of tx attempts (%d)", len(reqs), len(attempts)) err = errors.Join(err, lenErr) - lggr.Criticalw("Mismatched length", "err", err) + logger.Criticalw(lggr, "Mismatched length", "err", err) return } @@ -80,7 +80,7 @@ func (c *evmTxmClient) BatchSendTransactions( processingErr[i] = fmt.Errorf("failed to process tx (index %d): %w", i, signedErr) return } - codes[i], txErrs[i] = evmclient.NewSendErrorReturnCode(reqs[i].Error, lggr, tx, attempts[i].Tx.FromAddress, c.client.IsL2()) + codes[i], txErrs[i] = client.ClassifySendError(reqs[i].Error, lggr, tx, attempts[i].Tx.FromAddress, c.client.IsL2()) }(index) } wg.Wait() @@ -88,11 +88,11 @@ func (c *evmTxmClient) BatchSendTransactions( return } -func (c *evmTxmClient) SendTransactionReturnCode(ctx context.Context, etx Tx, attempt TxAttempt, lggr logger.Logger) (clienttypes.SendTxReturnCode, error) { +func (c *evmTxmClient) SendTransactionReturnCode(ctx context.Context, etx Tx, attempt TxAttempt, lggr logger.Logger) (commonclient.SendTxReturnCode, error) { signedTx, err := GetGethSignedTx(attempt.SignedRawTx) if err != nil { - lggr.Criticalw("Fatal error signing transaction", "err", err, "etx", etx) - return clienttypes.Fatal, err + logger.Criticalw(lggr, "Fatal error signing transaction", "err", err, "etx", etx) + return commonclient.Fatal, err } return c.client.SendTransactionReturnCode(ctx, signedTx, etx.FromAddress) } @@ -174,5 +174,5 @@ func (c *evmTxmClient) CallContract(ctx context.Context, a TxAttempt, blockNumbe Data: a.Tx.EncodedPayload, AccessList: nil, }, blockNumber) - return evmclient.ExtractRPCError(errCall) + return client.ExtractRPCError(errCall) } diff --git a/core/chains/evm/txmgr/common.go b/core/chains/evm/txmgr/common.go index 5dbb2ef9611..1956476f8dd 100644 --- a/core/chains/evm/txmgr/common.go +++ b/core/chains/evm/txmgr/common.go @@ -10,8 +10,8 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) // Tries to send transactions in batches. Even if some batch(es) fail to get sent, it tries all remaining batches, @@ -69,10 +69,3 @@ func batchSendTransactions( } return reqs, now, successfulBroadcast, nil } - -func stringToGethAddress(s string) (common.Address, error) { - if !common.IsHexAddress(s) { - return common.Address{}, fmt.Errorf("invalid hex address: %s", s) - } - return common.HexToAddress(s), nil -} diff --git a/core/chains/evm/txmgr/config.go b/core/chains/evm/txmgr/config.go index 0e6b46574ae..8346a0d0551 100644 --- a/core/chains/evm/txmgr/config.go +++ b/core/chains/evm/txmgr/config.go @@ -5,9 +5,9 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink/v2/common/config" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" - coreconfig "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" ) // ChainConfig encompasses config used by txmgr package @@ -15,7 +15,7 @@ import ( // //go:generate mockery --quiet --recursive --name ChainConfig --output ./mocks/ --case=underscore --structname Config --filename config.go type ChainConfig interface { - ChainType() coreconfig.ChainType + ChainType() config.ChainType FinalityDepth() uint32 FinalityTagEnabled() bool NonceAutoSync() bool diff --git a/core/chains/evm/txmgr/confirmer_test.go b/core/chains/evm/txmgr/confirmer_test.go index 32246b06cea..84c42cd00f1 100644 --- a/core/chains/evm/txmgr/confirmer_test.go +++ b/core/chains/evm/txmgr/confirmer_test.go @@ -20,12 +20,13 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" @@ -33,11 +34,11 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore" ksmocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -84,7 +85,7 @@ func newInProgressLegacyEthTxAttempt(t *testing.T, etxID int64, gasPrice ...int6 } func mustInsertInProgressEthTx(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress gethCommon.Address) txmgr.Tx { - etx := cltest.NewEthTx(t, fromAddress) + etx := cltest.NewEthTx(fromAddress) etx.State = txmgrcommon.TxInProgress n := evmtypes.Nonce(nonce) etx.Sequence = &n @@ -94,7 +95,7 @@ func mustInsertInProgressEthTx(t *testing.T, txStore txmgr.TestEvmTxStore, nonce } func mustInsertConfirmedEthTx(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress gethCommon.Address) txmgr.Tx { - etx := cltest.NewEthTx(t, fromAddress) + etx := cltest.NewEthTx(fromAddress) etx.State = txmgrcommon.TxConfirmed n := evmtypes.Nonce(nonce) etx.Sequence = &n @@ -111,7 +112,7 @@ func TestEthConfirmer_Lifecycle(t *testing.T) { db := pgtest.NewSqlxDB(t) config := newTestChainScopedConfig(t) - txStore := cltest.NewTxStore(t, db, config.Database()) + txStore := newTxStore(t, db, config.Database()) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethKeyStore := cltest.NewKeyStore(t, db, config.Database()).Eth() @@ -120,7 +121,7 @@ func TestEthConfirmer_Lifecycle(t *testing.T) { cltest.MustInsertRandomKey(t, ethKeyStore) cltest.MustInsertRandomKey(t, ethKeyStore) estimator := gasmocks.NewEvmEstimator(t) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) ge := config.EVM().GasEstimator() feeEstimator := gas.NewWrappedEvmEstimator(estimator, ge.EIP1559DynamicFees(), nil) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, feeEstimator) @@ -157,10 +158,10 @@ func TestEthConfirmer_Lifecycle(t *testing.T) { err = ec.Close() require.NoError(t, err) - // Can't start more than once (Confirmer implements utils.StartStopOnce) + // Can't start more than once (Confirmer uses services.StateMachine) err = ec.Start(ctx) require.Error(t, err) - // Can't close more than once (Confirmer implements utils.StartStopOnce) + // Can't close more than once (Confirmer use services.StateMachine) err = ec.Close() require.Error(t, err) @@ -187,21 +188,21 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) nonce := int64(0) ctx := testutils.Context(t) blockNum := int64(0) t.Run("only finds eth_txes in unconfirmed state with at least one broadcast attempt", func(t *testing.T) { - cltest.MustInsertFatalErrorEthTx(t, txStore, fromAddress) + mustInsertFatalErrorEthTx(t, txStore, fromAddress) mustInsertInProgressEthTx(t, txStore, nonce, fromAddress) nonce++ cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, nonce, 1, fromAddress) nonce++ - cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, nonce, fromAddress) + mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, nonce, fromAddress) nonce++ - cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, config.EVM().ChainID()) + mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, config.EVM().ChainID()) // Do the thing require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) @@ -450,7 +451,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { require.Len(t, attempt3_1.Receipts, 0) }) t.Run("handles case where eth_receipt already exists somehow", func(t *testing.T) { - ethReceipt := cltest.MustInsertEthReceipt(t, txStore, 42, utils.NewHash(), attempt3_1.Hash) + ethReceipt := mustInsertEthReceipt(t, txStore, 42, utils.NewHash(), attempt3_1.Hash) txmReceipt := evmtypes.Receipt{ TxHash: attempt3_1.Hash, BlockHash: ethReceipt.BlockHash, @@ -564,7 +565,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { data, err := utils.ABIEncode(`[{"type":"uint256"}]`, big.NewInt(10)) require.NoError(t, err) sig := utils.Keccak256Fixed([]byte(`MyError(uint256)`)) - ethClient.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(nil, &evmclient.JsonError{ + ethClient.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(nil, &client.JsonError{ Code: 1, Message: "reverted", Data: utils.ConcatBytes(sig[:4], data), @@ -604,7 +605,7 @@ func TestEthConfirmer_CheckForReceipts_batching(t *testing.T) { evmcfg := evmtest.NewChainScopedConfig(t, cfg) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) ctx := testutils.Context(t) etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, 0, fromAddress) @@ -664,7 +665,7 @@ func TestEthConfirmer_CheckForReceipts_HandlesNonFwdTxsWithForwardingEnabled(t * evmcfg := evmtest.NewChainScopedConfig(t, cfg) _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) ctx := testutils.Context(t) // tx is not forwarded and doesn't have meta set. EthConfirmer should handle nil meta values etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, 0, fromAddress) @@ -717,7 +718,7 @@ func TestEthConfirmer_CheckForReceipts_only_likely_confirmed(t *testing.T) { evmcfg := evmtest.NewChainScopedConfig(t, cfg) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) ctx := testutils.Context(t) var attempts []txmgr.TxAttempt @@ -771,7 +772,7 @@ func TestEthConfirmer_CheckForReceipts_should_not_check_for_likely_unconfirmed(t ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) ctx := testutils.Context(t) etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, 1, fromAddress) @@ -802,7 +803,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt_scoped_to_key(t ethClient.On("SequenceAt", mock.Anything, mock.Anything, mock.Anything).Return(evmtypes.Nonce(20), nil) evmcfg := evmtest.NewChainScopedConfig(t, cfg) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) ctx := testutils.Context(t) // STATE @@ -869,7 +870,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { evmcfg := evmtest.NewChainScopedConfig(t, cfg) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) ctx := testutils.Context(t) // STATE @@ -1129,7 +1130,7 @@ func TestEthConfirmer_CheckConfirmedMissingReceipt(t *testing.T) { evmcfg := evmtest.NewChainScopedConfig(t, cfg) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) ctx := testutils.Context(t) // STATE @@ -1137,18 +1138,18 @@ func TestEthConfirmer_CheckConfirmedMissingReceipt(t *testing.T) { // 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( + etx0 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 0, 1, originalBroadcastAt, fromAddress) attempt0_2 := newBroadcastLegacyEthTxAttempt(t, etx0.ID, int64(2)) require.NoError(t, txStore.InsertTxAttempt(&attempt0_2)) - etx1 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx1 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 1, 1, originalBroadcastAt, fromAddress) attempt1_2 := newBroadcastLegacyEthTxAttempt(t, etx1.ID, int64(2)) require.NoError(t, txStore.InsertTxAttempt(&attempt1_2)) - etx2 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx2 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 2, 1, originalBroadcastAt, fromAddress) attempt2_1 := etx2.TxAttempts[0] - etx3 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx3 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 3, 1, originalBroadcastAt, fromAddress) attempt3_1 := etx3.TxAttempts[0] @@ -1208,7 +1209,7 @@ func TestEthConfirmer_CheckConfirmedMissingReceipt_batchSendTransactions_fails(t evmcfg := evmtest.NewChainScopedConfig(t, cfg) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) ctx := testutils.Context(t) // STATE @@ -1216,15 +1217,15 @@ func TestEthConfirmer_CheckConfirmedMissingReceipt_batchSendTransactions_fails(t // 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( + etx0 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 0, 1, originalBroadcastAt, fromAddress) attempt0_2 := newBroadcastLegacyEthTxAttempt(t, etx0.ID, int64(2)) require.NoError(t, txStore.InsertTxAttempt(&attempt0_2)) - etx1 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx1 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 1, 1, originalBroadcastAt, fromAddress) attempt1_2 := newBroadcastLegacyEthTxAttempt(t, etx1.ID, int64(2)) require.NoError(t, txStore.InsertTxAttempt(&attempt1_2)) - etx2 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx2 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 2, 1, originalBroadcastAt, fromAddress) attempt2_1 := etx2.TxAttempts[0] @@ -1272,7 +1273,7 @@ func TestEthConfirmer_CheckConfirmedMissingReceipt_smallEvmRPCBatchSize_middleBa evmcfg := evmtest.NewChainScopedConfig(t, cfg) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) ctx := testutils.Context(t) // STATE @@ -1280,15 +1281,15 @@ func TestEthConfirmer_CheckConfirmedMissingReceipt_smallEvmRPCBatchSize_middleBa // 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( + etx0 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 0, 1, originalBroadcastAt, fromAddress) attempt0_2 := newBroadcastLegacyEthTxAttempt(t, etx0.ID, int64(2)) require.NoError(t, txStore.InsertTxAttempt(&attempt0_2)) - etx1 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx1 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 1, 1, originalBroadcastAt, fromAddress) attempt1_2 := newBroadcastLegacyEthTxAttempt(t, etx1.ID, int64(2)) require.NoError(t, txStore.InsertTxAttempt(&attempt1_2)) - etx2 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx2 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 2, 1, originalBroadcastAt, fromAddress) // Expect eth_sendRawTransaction in 3 batches. First batch will pass, 2nd will fail, 3rd never attempted. @@ -1352,9 +1353,9 @@ func TestEthConfirmer_FindTxsRequiringRebroadcast(t *testing.T) { _, otherAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) evmOtherAddress := otherAddress - lggr := logger.TestLogger(t) + lggr := logger.Test(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) t.Run("returns nothing when there are no transactions", func(t *testing.T) { etxs, err := ec.FindTxsRequiringRebroadcast(testutils.Context(t), lggr, evmFromAddress, currentHead, gasBumpThreshold, 10, 0, &cltest.FixtureChainID) @@ -1416,7 +1417,7 @@ func TestEthConfirmer_FindTxsRequiringRebroadcast(t *testing.T) { assert.Len(t, etxs, 0) }) - etxWithoutAttempts := cltest.NewEthTx(t, fromAddress) + etxWithoutAttempts := cltest.NewEthTx(fromAddress) { n := evmtypes.Nonce(nonce) etxWithoutAttempts.Sequence = &n @@ -1577,13 +1578,13 @@ func TestEthConfirmer_FindTxsRequiringRebroadcast(t *testing.T) { attempt4_2.TxFee = gas.EvmFee{Legacy: assets.NewWeiI(40000)} require.NoError(t, txStore.InsertTxAttempt(&attempt4_2)) - etx5 := cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, nonce, fromAddress) + etx5 := mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, nonce, fromAddress) nonce++ // This etx has one attempt that is too new, which would exclude it from // the gas bumping query, but it should still be caught by the insufficient // eth query - etx6 := cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, nonce, fromAddress) + etx6 := mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, nonce, fromAddress) attempt6_2 := newBroadcastLegacyEthTxAttempt(t, etx3.ID) attempt6_2.BroadcastBeforeBlockNum = &tooNew attempt6_2.TxFee = gas.EvmFee{Legacy: assets.NewWeiI(30001)} @@ -1618,7 +1619,7 @@ func TestEthConfirmer_FindTxsRequiringRebroadcast(t *testing.T) { func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing.T) { t.Parallel() - lggr := logger.TestLogger(t) + lggr := logger.Test(t) db := pgtest.NewSqlxDB(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) @@ -1658,7 +1659,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing require.NoError(t, db.Get(&dbAttempt, `UPDATE evm.tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, oldEnough, attempt1.ID)) // Send transaction and assume success. - ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return(clienttypes.Successful, nil).Once() + ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return(commonclient.Successful, nil).Once() err := ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead) require.NoError(t, err) @@ -1696,14 +1697,14 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing nonce := int64(0) originalBroadcastAt := time.Unix(1616509100, 0) - etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t, txStore, nonce, fromAddress, originalBroadcastAt) + etx := mustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t, txStore, nonce, fromAddress, originalBroadcastAt) attempt1 := etx.TxAttempts[0] var dbAttempt txmgr.DbEthTxAttempt dbAttempt.FromTxAttempt(&attempt1) require.NoError(t, db.Get(&dbAttempt, `UPDATE evm.tx_attempts SET broadcast_before_block_num=$1 WHERE id=$2 RETURNING *`, oldEnough, attempt1.ID)) // Send transaction and assume success. - ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return(clienttypes.Successful, nil).Once() + ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return(commonclient.Successful, nil).Once() err := ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead) require.NoError(t, err) @@ -1735,7 +1736,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", &cltest.FixtureChainID).Return(addresses, nil).Maybe() // Use a mock keystore for this test - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, kst, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, kst, nil) currentHead := int64(30) oldEnough := int64(19) nonce := int64(0) @@ -1787,7 +1788,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(ðTx, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Nonce() == uint64(*etx.Sequence) - }), fromAddress).Return(clienttypes.Fatal, errors.New("exceeds block gas limit")).Once() + }), fromAddress).Return(commonclient.Fatal, errors.New("exceeds block gas limit")).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -1819,7 +1820,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { // Once for the bumped attempt which exceeds limit ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Nonce() == uint64(*etx.Sequence) && tx.GasPrice().Int64() == int64(20000000000) - }), fromAddress).Return(clienttypes.ExceedsMaxFee, errors.New("tx fee (1.10 ether) exceeds the configured cap (1.00 ether)")).Once() + }), fromAddress).Return(commonclient.ExceedsMaxFee, errors.New("tx fee (1.10 ether) exceeds the configured cap (1.00 ether)")).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -1858,7 +1859,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { })).Return(ðTx, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -1904,7 +1905,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { mock.Anything).Return(ðTx, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.Successful, fmt.Errorf("known transaction: %s", ethTx.Hash().Hex())).Once() + }), fromAddress).Return(commonclient.Successful, fmt.Errorf("known transaction: %s", ethTx.Hash().Hex())).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -1944,7 +1945,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { mock.Anything).Return(ðTx, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.TransactionAlreadyKnown, errors.New("nonce too low")).Once() + }), fromAddress).Return(commonclient.TransactionAlreadyKnown, errors.New("nonce too low")).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -1996,7 +1997,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { mock.Anything).Return(ðTx, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == n && expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.Unknown, errors.New("some network error")).Once() + }), fromAddress).Return(commonclient.Unknown, errors.New("some network error")).Once() // Do the thing err := ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead) @@ -2024,7 +2025,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { n = *etx2.Sequence ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == n && expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2063,7 +2064,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { mock.Anything).Return(ðTx, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == n && expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.TransactionAlreadyKnown, errors.New("nonce too low")).Once() + }), fromAddress).Return(commonclient.TransactionAlreadyKnown, errors.New("nonce too low")).Once() // Creates new attempt as normal if currentHead is not high enough require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2104,7 +2105,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { mock.Anything).Return(ðTx, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == *etx3.Sequence && expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.Successful, errors.New("replacement transaction underpriced")).Once() + }), fromAddress).Return(commonclient.Successful, errors.New("replacement transaction underpriced")).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2141,7 +2142,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { mock.Anything).Return(ðTx, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == *etx3.Sequence && expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.Successful, fmt.Errorf("known transaction: %s", ethTx.Hash().Hex())).Once() + }), fromAddress).Return(commonclient.Successful, fmt.Errorf("known transaction: %s", ethTx.Hash().Hex())).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2180,7 +2181,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { mock.Anything).Return(ðTx, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == *etx3.Sequence && expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.Successful, errors.New(temporarilyUnderpricedError)).Once() + }), fromAddress).Return(commonclient.Successful, errors.New(temporarilyUnderpricedError)).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2205,11 +2206,11 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { c.EVM[0].GasEstimator.PriceMax = assets.NewWeiI(60500000000) }) newCfg := evmtest.NewChainScopedConfig(t, gcfg) - ec2 := cltest.NewEthConfirmer(t, txStore, ethClient, newCfg, ethKeyStore, nil) + ec2 := newEthConfirmer(t, txStore, ethClient, newCfg, ethKeyStore, nil) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == *etx3.Sequence && gasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.Successful, errors.New("already known")).Once() // we already submitted at this price, now it's time to bump and submit again but since we simply resubmitted rather than increasing gas price, geth already knows about this tx + }), fromAddress).Return(commonclient.Successful, errors.New("already known")).Once() // we already submitted at this price, now it's 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, ec2.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2235,11 +2236,11 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { c.EVM[0].GasEstimator.PriceMax = assets.NewWeiI(60480000000) }) newCfg := evmtest.NewChainScopedConfig(t, gcfg) - ec2 := cltest.NewEthConfirmer(t, txStore, ethClient, newCfg, ethKeyStore, nil) + ec2 := newEthConfirmer(t, txStore, ethClient, newCfg, ethKeyStore, nil) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == *etx3.Sequence && gasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.Successful, errors.New("already known")).Once() // we already submitted at this price, now it's time to bump and submit again but since we simply resubmitted rather than increasing gas price, geth already knows about this tx + }), fromAddress).Return(commonclient.Successful, errors.New("already known")).Once() // we already submitted at this price, now it's 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, ec2.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2256,7 +2257,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { }) // The EIP-1559 etx and attempt - etx4 := cltest.MustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t, txStore, nonce, fromAddress) + etx4 := mustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t, txStore, nonce, fromAddress) attempt4_1 := etx4.TxAttempts[0] require.NoError(t, db.Get(&dbAttempt, `UPDATE evm.tx_attempts SET broadcast_before_block_num=$1, gas_tip_cap=$2, gas_fee_cap=$3 WHERE id=$4 RETURNING *`, oldEnough, assets.GWei(35), assets.GWei(100), attempt4_1.ID)) @@ -2278,7 +2279,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { gasTipCap := assets.GWei(42) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == *etx4.Sequence && gasTipCap.ToInt().Cmp(tx.GasTipCap()) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) var err error etx4, err = txStore.FindTxWithAttempts(etx4.ID) @@ -2303,12 +2304,12 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { c.EVM[0].GasEstimator.PriceMax = assets.GWei(1000) }) newCfg := evmtest.NewChainScopedConfig(t, gcfg) - ec2 := cltest.NewEthConfirmer(t, txStore, ethClient, newCfg, ethKeyStore, nil) + ec2 := newEthConfirmer(t, txStore, ethClient, newCfg, ethKeyStore, nil) // Third attempt failed to bump, resubmits old one instead ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == *etx4.Sequence && attempt4_2.Hash.String() == tx.Hash().String() - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() require.NoError(t, ec2.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) var err error @@ -2344,7 +2345,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { mock.Anything).Return(ðTx, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return evmtypes.Nonce(tx.Nonce()) == *etx4.Sequence && expectedBumpedTipCap.ToInt().Cmp(tx.GasTipCap()) == 0 - }), fromAddress).Return(clienttypes.Successful, errors.New("replacement transaction underpriced")).Once() + }), fromAddress).Return(commonclient.Successful, errors.New("replacement transaction underpriced")).Once() // Do it require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2389,10 +2390,10 @@ func TestEthConfirmer_RebroadcastWhereNecessary_TerminallyUnderpriced_ThenGoesTh t.Run("terminally underpriced transaction with in_progress attempt is retried with more gas", func(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, kst, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, kst, nil) originalBroadcastAt := time.Unix(1616509100, 0) - etx := cltest.MustInsertUnconfirmedEthTxWithAttemptState(t, txStore, nonce, fromAddress, txmgrtypes.TxAttemptInProgress, originalBroadcastAt) + etx := mustInsertUnconfirmedEthTxWithAttemptState(t, txStore, nonce, fromAddress, txmgrtypes.TxAttemptInProgress, originalBroadcastAt) require.Equal(t, originalBroadcastAt, *etx.BroadcastAt) nonce++ attempt := etx.TxAttempts[0] @@ -2401,10 +2402,10 @@ func TestEthConfirmer_RebroadcastWhereNecessary_TerminallyUnderpriced_ThenGoesTh // Fail the first time with terminally underpriced. ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return( - clienttypes.Underpriced, errors.New("Transaction gas price is too low. It does not satisfy your node's minimal gas price")).Once() + commonclient.Underpriced, errors.New("Transaction gas price is too low. It does not satisfy your node's minimal gas price")).Once() // Succeed the second time after bumping gas. ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return( - clienttypes.Successful, nil).Once() + commonclient.Successful, nil).Once() kst.On("SignTx", mock.Anything, mock.Anything, mock.Anything).Return( signedTx, nil, ).Once() @@ -2413,7 +2414,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_TerminallyUnderpriced_ThenGoesTh t.Run("multiple gas bumps with existing broadcast attempts are retried with more gas until success in legacy mode", func(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, kst, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, kst, nil) etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, nonce, fromAddress) nonce++ @@ -2424,10 +2425,10 @@ func TestEthConfirmer_RebroadcastWhereNecessary_TerminallyUnderpriced_ThenGoesTh // Fail a few times with terminally underpriced ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return( - clienttypes.Underpriced, errors.New("Transaction gas price is too low. It does not satisfy your node's minimal gas price")).Times(3) + commonclient.Underpriced, errors.New("Transaction gas price is too low. It does not satisfy your node's minimal gas price")).Times(3) // Succeed the second time after bumping gas. ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return( - clienttypes.Successful, nil).Once() + commonclient.Successful, nil).Once() signedLegacyTx := new(types.Transaction) kst.On("SignTx", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Type() == 0x0 && tx.Nonce() == uint64(*etx.Sequence) @@ -2445,9 +2446,9 @@ func TestEthConfirmer_RebroadcastWhereNecessary_TerminallyUnderpriced_ThenGoesTh t.Run("multiple gas bumps with existing broadcast attempts are retried with more gas until success in EIP-1559 mode", func(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, kst, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, kst, nil) - etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t, txStore, nonce, fromAddress) + etx := mustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t, txStore, nonce, fromAddress) nonce++ dxFeeAttempt := etx.TxAttempts[0] var dbAttempt txmgr.DbEthTxAttempt @@ -2456,10 +2457,10 @@ func TestEthConfirmer_RebroadcastWhereNecessary_TerminallyUnderpriced_ThenGoesTh // Fail a few times with terminally underpriced ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return( - clienttypes.Underpriced, errors.New("transaction underpriced")).Times(3) + commonclient.Underpriced, errors.New("transaction underpriced")).Times(3) // Succeed the second time after bumping gas. ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return( - clienttypes.Successful, nil).Once() + commonclient.Successful, nil).Once() signedDxFeeTx := new(types.Transaction) kst.On("SignTx", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Type() == 0x2 && tx.Nonce() == uint64(*etx.Sequence) @@ -2510,14 +2511,14 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { insufficientEthError := errors.New("insufficient funds for gas * price + value") t.Run("saves attempt with state 'insufficient_eth' if eth node returns this error", func(t *testing.T) { - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) expectedBumpedGasPrice := big.NewInt(20000000000) require.Greater(t, expectedBumpedGasPrice.Int64(), attempt1_1.TxFee.Legacy.ToInt().Int64()) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.InsufficientFunds, insufficientEthError).Once() + }), fromAddress).Return(commonclient.InsufficientFunds, insufficientEthError).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2536,14 +2537,14 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { }) t.Run("does not bump gas when previous error was 'out of eth', instead resubmits existing transaction", func(t *testing.T) { - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) expectedBumpedGasPrice := big.NewInt(20000000000) require.Greater(t, expectedBumpedGasPrice.Int64(), attempt1_1.TxFee.Legacy.ToInt().Int64()) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.InsufficientFunds, insufficientEthError).Once() + }), fromAddress).Return(commonclient.InsufficientFunds, insufficientEthError).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2561,14 +2562,14 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { }) t.Run("saves the attempt as broadcast after node wallet has been topped up with sufficient balance", func(t *testing.T) { - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) expectedBumpedGasPrice := big.NewInt(20000000000) require.Greater(t, expectedBumpedGasPrice.Int64(), attempt1_1.TxFee.Legacy.ToInt().Int64()) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return expectedBumpedGasPrice.Cmp(tx.GasPrice()) == 0 - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() // Do the thing require.NoError(t, ec.RebroadcastWhereNecessary(testutils.Context(t), currentHead)) @@ -2593,14 +2594,14 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WhenOutOfEth(t *testing.T) { c.EVM[0].GasEstimator.BumpTxDepth = ptr(uint32(depth)) }) evmcfg := evmtest.NewChainScopedConfig(t, cfg) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, nil) for i := 0; i < etxCount; i++ { n := nonce - cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, nonce, fromAddress) + mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, nonce, fromAddress) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Nonce() == uint64(n) - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() nonce++ } @@ -2628,7 +2629,7 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) config := newTestChainScopedConfig(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) head := evmtypes.Head{ Hash: utils.NewHash(), @@ -2661,7 +2662,7 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { t.Run("does nothing to confirmed transactions with receipts within head height of the chain and included in the chain", func(t *testing.T) { etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 2, 1, fromAddress) - cltest.MustInsertEthReceipt(t, txStore, head.Number, head.Hash, etx.TxAttempts[0].Hash) + mustInsertEthReceipt(t, txStore, head.Number, head.Hash, etx.TxAttempts[0].Hash) // Do the thing require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) @@ -2674,7 +2675,7 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { t.Run("does nothing to confirmed transactions that only have receipts older than the start of the chain", func(t *testing.T) { etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 3, 1, fromAddress) // Add receipt that is older than the lowest block of the chain - cltest.MustInsertEthReceipt(t, txStore, head.Parent.Parent.Number-1, utils.NewHash(), etx.TxAttempts[0].Hash) + mustInsertEthReceipt(t, txStore, head.Parent.Parent.Number-1, utils.NewHash(), etx.TxAttempts[0].Hash) // Do the thing require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) @@ -2688,14 +2689,14 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 4, 1, fromAddress) attempt := etx.TxAttempts[0] // Include one within head height but a different block hash - cltest.MustInsertEthReceipt(t, txStore, head.Parent.Number, utils.NewHash(), attempt.Hash) + mustInsertEthReceipt(t, txStore, head.Parent.Number, utils.NewHash(), attempt.Hash) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { atx, err := txmgr.GetGethSignedTx(attempt.SignedRawTx) require.NoError(t, err) // Keeps gas price and nonce the same return atx.GasPrice().Cmp(tx.GasPrice()) == 0 && atx.Nonce() == tx.Nonce() - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() // Do the thing require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) @@ -2713,12 +2714,12 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { attempt := etx.TxAttempts[0] attemptHash := attempt.Hash // Add receipt that is older than the lowest block of the chain - cltest.MustInsertEthReceipt(t, txStore, head.Parent.Parent.Number-1, utils.NewHash(), attemptHash) + mustInsertEthReceipt(t, txStore, head.Parent.Parent.Number-1, utils.NewHash(), attemptHash) // Include one within head height but a different block hash - cltest.MustInsertEthReceipt(t, txStore, head.Parent.Number, utils.NewHash(), attemptHash) + mustInsertEthReceipt(t, txStore, head.Parent.Number, utils.NewHash(), attemptHash) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return( - clienttypes.Successful, nil).Once() + commonclient.Successful, nil).Once() // Do the thing require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) @@ -2745,15 +2746,15 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { require.NoError(t, txStore.InsertTxAttempt(&attempt3)) // Receipt is within head height but a different block hash - cltest.MustInsertEthReceipt(t, txStore, head.Parent.Number, utils.NewHash(), attempt2.Hash) + mustInsertEthReceipt(t, txStore, head.Parent.Number, utils.NewHash(), attempt2.Hash) // Receipt is within head height but a different block hash - cltest.MustInsertEthReceipt(t, txStore, head.Parent.Number, utils.NewHash(), attempt3.Hash) + mustInsertEthReceipt(t, txStore, head.Parent.Number, utils.NewHash(), attempt3.Hash) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { s, err := txmgr.GetGethSignedTx(attempt3.SignedRawTx) require.NoError(t, err) return tx.Hash() == s.Hash() - }), fromAddress).Return(clienttypes.Successful, nil).Once() + }), fromAddress).Return(commonclient.Successful, nil).Once() // Do the thing require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) @@ -2774,7 +2775,7 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 7, 1, fromAddress) attempt := etx.TxAttempts[0] // Add receipt that is higher than head - cltest.MustInsertEthReceipt(t, txStore, head.Number+1, utils.NewHash(), attempt.Hash) + mustInsertEthReceipt(t, txStore, head.Number+1, utils.NewHash(), attempt.Hash) require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(testutils.Context(t), &head)) @@ -2799,7 +2800,7 @@ func TestEthConfirmer_ForceRebroadcast(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) config := newTestChainScopedConfig(t) - cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, config.EVM().ChainID()) + mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, config.EVM().ChainID()) mustInsertInProgressEthTx(t, txStore, 0, fromAddress) etx1 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 1, fromAddress) etx2 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 2, fromAddress) @@ -2809,7 +2810,7 @@ func TestEthConfirmer_ForceRebroadcast(t *testing.T) { t.Run("rebroadcasts one eth_tx if it falls within in nonce range", func(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Nonce() == uint64(*etx1.Sequence) && @@ -2817,14 +2818,14 @@ func TestEthConfirmer_ForceRebroadcast(t *testing.T) { tx.Gas() == uint64(overrideGasLimit) && reflect.DeepEqual(tx.Data(), etx1.EncodedPayload) && tx.To().String() == etx1.ToAddress.String() - }), mock.Anything).Return(clienttypes.Successful, nil).Once() + }), mock.Anything).Return(commonclient.Successful, nil).Once() - require.NoError(t, ec.ForceRebroadcast([]evmtypes.Nonce{1}, gasPriceWei, fromAddress, overrideGasLimit)) + require.NoError(t, ec.ForceRebroadcast(testutils.Context(t), []evmtypes.Nonce{1}, gasPriceWei, fromAddress, overrideGasLimit)) }) t.Run("uses default gas limit if overrideGasLimit is 0", func(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Nonce() == uint64(*etx1.Sequence) && @@ -2832,35 +2833,35 @@ func TestEthConfirmer_ForceRebroadcast(t *testing.T) { tx.Gas() == uint64(etx1.FeeLimit) && reflect.DeepEqual(tx.Data(), etx1.EncodedPayload) && tx.To().String() == etx1.ToAddress.String() - }), mock.Anything).Return(clienttypes.Successful, nil).Once() + }), mock.Anything).Return(commonclient.Successful, nil).Once() - require.NoError(t, ec.ForceRebroadcast([]evmtypes.Nonce{(1)}, gasPriceWei, fromAddress, 0)) + require.NoError(t, ec.ForceRebroadcast(testutils.Context(t), []evmtypes.Nonce{(1)}, gasPriceWei, fromAddress, 0)) }) t.Run("rebroadcasts several eth_txes in nonce range", func(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Nonce() == uint64(*etx1.Sequence) && tx.GasPrice().Int64() == gasPriceWei.Legacy.Int64() && tx.Gas() == uint64(overrideGasLimit) - }), mock.Anything).Return(clienttypes.Successful, nil).Once() + }), mock.Anything).Return(commonclient.Successful, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Nonce() == uint64(*etx2.Sequence) && tx.GasPrice().Int64() == gasPriceWei.Legacy.Int64() && tx.Gas() == uint64(overrideGasLimit) - }), mock.Anything).Return(clienttypes.Successful, nil).Once() + }), mock.Anything).Return(commonclient.Successful, nil).Once() - require.NoError(t, ec.ForceRebroadcast([]evmtypes.Nonce{(1), (2)}, gasPriceWei, fromAddress, overrideGasLimit)) + require.NoError(t, ec.ForceRebroadcast(testutils.Context(t), []evmtypes.Nonce{(1), (2)}, gasPriceWei, fromAddress, overrideGasLimit)) }) t.Run("broadcasts zero transactions if eth_tx doesn't exist for that nonce", func(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Nonce() == uint64(1) - }), mock.Anything).Return(clienttypes.Successful, nil).Once() + }), mock.Anything).Return(commonclient.Successful, nil).Once() ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Nonce() == uint64(2) - }), mock.Anything).Return(clienttypes.Successful, nil).Once() + }), mock.Anything).Return(commonclient.Successful, nil).Once() for i := 3; i <= 5; i++ { nonce := i ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { @@ -2870,22 +2871,22 @@ func TestEthConfirmer_ForceRebroadcast(t *testing.T) { *tx.To() == fromAddress && tx.Value().Cmp(big.NewInt(0)) == 0 && len(tx.Data()) == 0 - }), mock.Anything).Return(clienttypes.Successful, nil).Once() + }), mock.Anything).Return(commonclient.Successful, nil).Once() } nonces := []evmtypes.Nonce{(1), (2), (3), (4), (5)} - require.NoError(t, ec.ForceRebroadcast(nonces, gasPriceWei, fromAddress, overrideGasLimit)) + require.NoError(t, ec.ForceRebroadcast(testutils.Context(t), nonces, gasPriceWei, fromAddress, overrideGasLimit)) }) t.Run("zero transactions use default gas limit if override wasn't specified", func(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - ec := cltest.NewEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) + ec := newEthConfirmer(t, txStore, ethClient, config, ethKeyStore, nil) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { return tx.Nonce() == uint64(0) && tx.GasPrice().Int64() == gasPriceWei.Legacy.Int64() && uint32(tx.Gas()) == config.EVM().GasEstimator().LimitDefault() - }), mock.Anything).Return(clienttypes.Successful, nil).Once() + }), mock.Anything).Return(commonclient.Successful, nil).Once() - require.NoError(t, ec.ForceRebroadcast([]evmtypes.Nonce{(0)}, gasPriceWei, fromAddress, 0)) + require.NoError(t, ec.ForceRebroadcast(testutils.Context(t), []evmtypes.Nonce{(0)}, gasPriceWei, fromAddress, 0)) }) } @@ -2923,7 +2924,7 @@ func TestEthConfirmer_ResumePendingRuns(t *testing.T) { pgtest.MustExec(t, db, `SET CONSTRAINTS pipeline_runs_pipeline_spec_id_fkey DEFERRED`) t.Run("doesn't process task runs that are not suspended (possibly already previously resumed)", func(t *testing.T) { - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, func(uuid.UUID, interface{}, error) error { + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, func(uuid.UUID, interface{}, error) error { t.Fatal("No value expected") return nil }) @@ -2932,16 +2933,17 @@ func TestEthConfirmer_ResumePendingRuns(t *testing.T) { tr := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run.ID) etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 1, 1, fromAddress) - cltest.MustInsertEthReceipt(t, txStore, head.Number-minConfirmations, head.Hash, etx.TxAttempts[0].Hash) - pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2 WHERE id = $3`, &tr.ID, minConfirmations, etx.ID) + mustInsertEthReceipt(t, txStore, head.Number-minConfirmations, head.Hash, etx.TxAttempts[0].Hash) + // Setting both signal_callback and callback_completed to TRUE to simulate a completed pipeline task + // It would only be in a state past suspended if the resume callback was called and callback_completed was set to TRUE + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE, callback_completed = TRUE WHERE id = $3`, &tr.ID, minConfirmations, etx.ID) err := ec.ResumePendingTaskRuns(testutils.Context(t), &head) require.NoError(t, err) - }) t.Run("doesn't process task runs where the receipt is younger than minConfirmations", func(t *testing.T) { - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, func(uuid.UUID, interface{}, error) error { + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, func(uuid.UUID, interface{}, error) error { t.Fatal("No value expected") return nil }) @@ -2950,19 +2952,19 @@ func TestEthConfirmer_ResumePendingRuns(t *testing.T) { tr := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run.ID) etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 2, 1, fromAddress) - cltest.MustInsertEthReceipt(t, txStore, head.Number, head.Hash, etx.TxAttempts[0].Hash) + mustInsertEthReceipt(t, txStore, head.Number, head.Hash, etx.TxAttempts[0].Hash) - pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2 WHERE id = $3`, &tr.ID, minConfirmations, etx.ID) + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE WHERE id = $3`, &tr.ID, minConfirmations, etx.ID) err := ec.ResumePendingTaskRuns(testutils.Context(t), &head) require.NoError(t, err) - }) t.Run("processes eth_txes with receipts older than minConfirmations", func(t *testing.T) { ch := make(chan interface{}) + nonce := evmtypes.Nonce(3) var err error - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, func(id uuid.UUID, value interface{}, thisErr error) error { + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, func(id uuid.UUID, value interface{}, thisErr error) error { err = thisErr ch <- value return nil @@ -2972,15 +2974,19 @@ func TestEthConfirmer_ResumePendingRuns(t *testing.T) { tr := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run.ID) pgtest.MustExec(t, db, `UPDATE pipeline_runs SET state = 'suspended' WHERE id = $1`, run.ID) - etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 3, 1, fromAddress) + etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, int64(nonce), 1, fromAddress) pgtest.MustExec(t, db, `UPDATE evm.txes SET meta='{"FailOnRevert": true}'`) - receipt := cltest.MustInsertEthReceipt(t, txStore, head.Number-minConfirmations, head.Hash, etx.TxAttempts[0].Hash) + receipt := mustInsertEthReceipt(t, txStore, head.Number-minConfirmations, head.Hash, etx.TxAttempts[0].Hash) - pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2 WHERE id = $3`, &tr.ID, minConfirmations, etx.ID) + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE WHERE id = $3`, &tr.ID, minConfirmations, etx.ID) go func() { err2 := ec.ResumePendingTaskRuns(testutils.Context(t), &head) require.NoError(t, err2) + // Retrieve Tx to check if callback completed flag was set to true + updateTx, err3 := txStore.FindTxWithSequence(testutils.Context(t), fromAddress, nonce) + require.NoError(t, err3) + require.Equal(t, true, updateTx.CallbackCompleted) }() select { @@ -3000,8 +3006,9 @@ func TestEthConfirmer_ResumePendingRuns(t *testing.T) { t.Run("processes eth_txes with receipt older than minConfirmations that reverted", func(t *testing.T) { ch := make(chan interface{}) + nonce := evmtypes.Nonce(4) var err error - ec := cltest.NewEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, func(id uuid.UUID, value interface{}, thisErr error) error { + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, func(id uuid.UUID, value interface{}, thisErr error) error { err = thisErr ch <- value return nil @@ -3011,17 +3018,21 @@ func TestEthConfirmer_ResumePendingRuns(t *testing.T) { tr := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run.ID) pgtest.MustExec(t, db, `UPDATE pipeline_runs SET state = 'suspended' WHERE id = $1`, run.ID) - etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 4, 1, fromAddress) + etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, int64(nonce), 1, fromAddress) pgtest.MustExec(t, db, `UPDATE evm.txes SET meta='{"FailOnRevert": true}'`) // receipt is not passed through as a value since it reverted and caused an error - cltest.MustInsertRevertedEthReceipt(t, txStore, head.Number-minConfirmations, head.Hash, etx.TxAttempts[0].Hash) + mustInsertRevertedEthReceipt(t, txStore, head.Number-minConfirmations, head.Hash, etx.TxAttempts[0].Hash) - pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2 WHERE id = $3`, &tr.ID, minConfirmations, etx.ID) + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE WHERE id = $3`, &tr.ID, minConfirmations, etx.ID) go func() { err2 := ec.ResumePendingTaskRuns(testutils.Context(t), &head) require.NoError(t, err2) + // Retrieve Tx to check if callback completed flag was set to true + updateTx, err3 := txStore.FindTxWithSequence(testutils.Context(t), fromAddress, nonce) + require.NoError(t, err3) + require.Equal(t, true, updateTx.CallbackCompleted) }() select { @@ -3036,6 +3047,39 @@ func TestEthConfirmer_ResumePendingRuns(t *testing.T) { t.Fatal("no value received") } }) + + t.Run("does not mark callback complete if callback fails", func(t *testing.T) { + nonce := evmtypes.Nonce(5) + ec := newEthConfirmer(t, txStore, ethClient, evmcfg, ethKeyStore, func(uuid.UUID, interface{}, error) error { + return errors.New("error") + }) + + run := cltest.MustInsertPipelineRun(t, db) + tr := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run.ID) + + etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, int64(nonce), 1, fromAddress) + mustInsertEthReceipt(t, txStore, head.Number-minConfirmations, head.Hash, etx.TxAttempts[0].Hash) + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE WHERE id = $3`, &tr.ID, minConfirmations, etx.ID) + + err := ec.ResumePendingTaskRuns(testutils.Context(t), &head) + require.Error(t, err) + + // Retrieve Tx to check if callback completed flag was left unchanged + updateTx, err := txStore.FindTxWithSequence(testutils.Context(t), fromAddress, nonce) + require.NoError(t, err) + require.Equal(t, false, updateTx.CallbackCompleted) + }) } func ptr[T any](t T) *T { return &t } + +func newEthConfirmer(t testing.TB, txStore txmgr.EvmTxStore, ethClient client.Client, config evmconfig.ChainScopedConfig, ks keystore.Eth, fn txmgrcommon.ResumeCallback) *txmgr.Confirmer { + lggr := logger.Test(t) + ge := config.EVM().GasEstimator() + estimator := gas.NewWrappedEvmEstimator(gas.NewFixedPriceEstimator(ge, ge.BlockHistory(), lggr), ge.EIP1559DynamicFees(), nil) + txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ks, estimator) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), config.Database(), ks, txBuilder, lggr) + ec.SetResumeCallback(fn) + require.NoError(t, ec.Start(testutils.Context(t))) + return ec +} diff --git a/core/chains/evm/txmgr/evm_tx_store.go b/core/chains/evm/txmgr/evm_tx_store.go index 7b1ef8948c1..730809e8dda 100644 --- a/core/chains/evm/txmgr/evm_tx_store.go +++ b/core/chains/evm/txmgr/evm_tx_store.go @@ -13,21 +13,21 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" "github.com/jackc/pgconn" + "github.com/jmoiron/sqlx" "github.com/lib/pq" pkgerrors "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" nullv4 "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/label" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/services/pg/datatypes" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -69,6 +69,7 @@ type TestEvmTxStore interface { FindTxAttemptsByTxIDs(ids []int64) ([]TxAttempt, error) InsertTxAttempt(attempt *TxAttempt) error LoadTxesAttempts(etxs []*Tx, qopts ...pg.QOpt) error + GetFatalTransactions(ctx context.Context) (txes []*Tx, err error) } type evmTxStore struct { @@ -115,7 +116,7 @@ type rawOnchainReceipt = evmtypes.Receipt // Does not map to a single database table. // It's comprised of fields from different tables. type dbReceiptPlus struct { - ID uuid.UUID `db:"id"` + ID uuid.UUID `db:"pipeline_task_run_id"` Receipt evmtypes.Receipt `db:"receipt"` FailOnRevert bool `db:"FailOnRevert"` } @@ -171,19 +172,24 @@ type DbEthTx struct { // Marshalled EvmTxMeta // Used for additional context around transactions which you want to log // at send time. - Meta *datatypes.JSON + Meta *sqlutil.JSON Subject uuid.NullUUID PipelineTaskRunID uuid.NullUUID MinConfirmations null.Uint32 EVMChainID utils.Big // TransmitChecker defines the check that should be performed before a transaction is submitted on // chain. - TransmitChecker *datatypes.JSON + TransmitChecker *sqlutil.JSON InitialBroadcastAt *time.Time + // Marks tx requiring callback + SignalCallback bool + // Marks tx callback as signaled + CallbackCompleted bool } func (db *DbEthTx) FromTx(tx *Tx) { db.ID = tx.ID + db.IdempotencyKey = tx.IdempotencyKey db.FromAddress = tx.FromAddress db.ToAddress = tx.ToAddress db.EncodedPayload = tx.EncodedPayload @@ -199,6 +205,8 @@ func (db *DbEthTx) FromTx(tx *Tx) { db.MinConfirmations = tx.MinConfirmations db.TransmitChecker = tx.TransmitChecker db.InitialBroadcastAt = tx.InitialBroadcastAt + db.SignalCallback = tx.SignalCallback + db.CallbackCompleted = tx.CallbackCompleted if tx.ChainID != nil { db.EVMChainID = *utils.NewBig(tx.ChainID) @@ -232,6 +240,8 @@ func (db DbEthTx) ToTx(tx *Tx) { tx.ChainID = db.EVMChainID.ToInt() tx.TransmitChecker = db.TransmitChecker tx.InitialBroadcastAt = db.InitialBroadcastAt + tx.SignalCallback = db.SignalCallback + tx.CallbackCompleted = db.CallbackCompleted } func dbEthTxsToEvmEthTxs(dbEthTxs []DbEthTx) []Tx { @@ -324,7 +334,7 @@ func NewTxStore( lggr logger.Logger, cfg pg.QConfig, ) *evmTxStore { - namedLogger := lggr.Named("TxmStore") + namedLogger := logger.Named(lggr, "TxmStore") ctx, cancel := context.WithCancel(context.Background()) q := pg.NewQ(db, namedLogger, cfg, pg.WithParentCtx(ctx)) return &evmTxStore{ @@ -511,8 +521,8 @@ func (o *evmTxStore) InsertTx(etx *Tx) error { if etx.CreatedAt == (time.Time{}) { etx.CreatedAt = time.Now() } - const insertEthTxSQL = `INSERT INTO evm.txes (nonce, from_address, to_address, encoded_payload, value, gas_limit, error, broadcast_at, initial_broadcast_at, created_at, state, meta, subject, pipeline_task_run_id, min_confirmations, evm_chain_id, transmit_checker) VALUES ( -:nonce, :from_address, :to_address, :encoded_payload, :value, :gas_limit, :error, :broadcast_at, :initial_broadcast_at, :created_at, :state, :meta, :subject, :pipeline_task_run_id, :min_confirmations, :evm_chain_id, :transmit_checker + const insertEthTxSQL = `INSERT INTO evm.txes (nonce, from_address, to_address, encoded_payload, value, gas_limit, error, broadcast_at, initial_broadcast_at, created_at, state, meta, subject, pipeline_task_run_id, min_confirmations, evm_chain_id, transmit_checker, idempotency_key, signal_callback, callback_completed) VALUES ( +:nonce, :from_address, :to_address, :encoded_payload, :value, :gas_limit, :error, :broadcast_at, :initial_broadcast_at, :created_at, :state, :meta, :subject, :pipeline_task_run_id, :min_confirmations, :evm_chain_id, :transmit_checker, :idempotency_key, :signal_callback, :callback_completed ) RETURNING *` var dbTx DbEthTx dbTx.FromTx(etx) @@ -543,19 +553,42 @@ func (o *evmTxStore) InsertReceipt(receipt *evmtypes.Receipt) (int64, error) { return r.ID, pkgerrors.Wrap(err, "InsertReceipt failed") } +func (o *evmTxStore) GetFatalTransactions(ctx context.Context) (txes []*Tx, err error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + err = qq.Transaction(func(tx pg.Queryer) error { + stmt := `SELECT * FROM evm.txes WHERE state = 'fatal_error'` + var dbEtxs []DbEthTx + if err = tx.Select(&dbEtxs, stmt); err != nil { + return fmt.Errorf("failed to load evm.txes: %w", err) + } + txes = make([]*Tx, len(dbEtxs)) + dbEthTxsToEvmEthTxPtrs(dbEtxs, txes) + err = o.LoadTxesAttempts(txes, pg.WithParentCtx(ctx), pg.WithQueryer(tx)) + if err != nil { + return fmt.Errorf("failed to load evm.tx_attempts: %w", err) + } + return nil + }, pg.OptReadOnlyTx()) + + return txes, nil +} + // FindTxWithAttempts finds the Tx with its attempts and receipts preloaded func (o *evmTxStore) FindTxWithAttempts(etxID int64) (etx Tx, err error) { err = o.q.Transaction(func(tx pg.Queryer) error { var dbEtx DbEthTx if err = tx.Get(&dbEtx, `SELECT * FROM evm.txes WHERE id = $1 ORDER BY created_at ASC, id ASC`, etxID); err != nil { - return pkgerrors.Wrapf(err, "failed to find eth_tx with id %d", etxID) + return pkgerrors.Wrapf(err, "failed to find evm.tx with id %d", etxID) } dbEtx.ToTx(&etx) if err = o.loadTxAttemptsAtomic(&etx, pg.WithQueryer(tx)); err != nil { - return pkgerrors.Wrapf(err, "failed to load evm.tx_attempts for eth_tx with id %d", etxID) + return pkgerrors.Wrapf(err, "failed to load evm.tx_attempts for evm.tx with id %d", etxID) } if err = loadEthTxAttemptsReceipts(tx, &etx); err != nil { - return pkgerrors.Wrapf(err, "failed to load evm.receipts for eth_tx with id %d", etxID) + return pkgerrors.Wrapf(err, "failed to load evm.receipts for evm.tx with id %d", etxID) } return nil }, pg.OptReadOnlyTx()) @@ -637,6 +670,8 @@ func loadEthTxesAttemptsReceipts(q pg.Queryer, etxs []*Tx) (err error) { for _, receipt := range receipts { attempt := attemptHashM[receipt.TxHash] + // Although the attempts struct supports multiple receipts, the expectation for EVM is that there is only one receipt + // per tx and therefore attempt too. attempt.Receipts = append(attempt.Receipts, receipt) } return nil @@ -938,25 +973,40 @@ WHERE evm.tx_attempts.state = 'in_progress' AND evm.txes.from_address = $1 AND e return attempts, pkgerrors.Wrap(err, "getInProgressEthTxAttempts failed") } -func (o *evmTxStore) FindReceiptsPendingConfirmation(ctx context.Context, blockNum int64, chainID *big.Int) (receiptsPlus []ReceiptPlus, err error) { +// Find confirmed txes requiring callback but have not yet been signaled +func (o *evmTxStore) FindTxesPendingCallback(ctx context.Context, blockNum int64, chainID *big.Int) (receiptsPlus []ReceiptPlus, err error) { var rs []dbReceiptPlus var cancel context.CancelFunc ctx, cancel = o.mergeContexts(ctx) defer cancel() err = o.q.SelectContext(ctx, &rs, ` - SELECT pipeline_task_runs.id, evm.receipts.receipt, COALESCE((evm.txes.meta->>'FailOnRevert')::boolean, false) "FailOnRevert" FROM pipeline_task_runs - INNER JOIN pipeline_runs ON pipeline_runs.id = pipeline_task_runs.pipeline_run_id - INNER JOIN evm.txes ON evm.txes.pipeline_task_run_id = pipeline_task_runs.id + SELECT evm.txes.pipeline_task_run_id, evm.receipts.receipt, COALESCE((evm.txes.meta->>'FailOnRevert')::boolean, false) "FailOnRevert" FROM evm.txes INNER JOIN evm.tx_attempts ON evm.txes.id = evm.tx_attempts.eth_tx_id INNER JOIN evm.receipts ON evm.tx_attempts.hash = evm.receipts.tx_hash - WHERE pipeline_runs.state = 'suspended' AND evm.receipts.block_number <= ($1 - evm.txes.min_confirmations) AND evm.txes.evm_chain_id = $2 + WHERE evm.txes.pipeline_task_run_id IS NOT NULL AND evm.txes.signal_callback = TRUE AND evm.txes.callback_completed = FALSE + AND evm.receipts.block_number <= ($1 - evm.txes.min_confirmations) AND evm.txes.evm_chain_id = $2 `, blockNum, chainID.String()) - + if err != nil { + return nil, fmt.Errorf("failed to retrieve transactions pending pipeline resume callback: %w", err) + } receiptsPlus = fromDBReceiptsPlus(rs) return } +// Update tx to mark that its callback has been signaled +func (o *evmTxStore) UpdateTxCallbackCompleted(ctx context.Context, pipelineTaskRunId uuid.UUID, chainId *big.Int) error { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + _, err := qq.Exec(`UPDATE evm.txes SET callback_completed = TRUE WHERE pipeline_task_run_id = $1 AND evm_chain_id = $2`, pipelineTaskRunId, chainId.String()) + if err != nil { + return fmt.Errorf("failed to mark callback completed for transaction: %w", err) + } + return nil +} + func (o *evmTxStore) FindLatestSequence(ctx context.Context, fromAddress common.Address, chainId *big.Int) (nonce evmtypes.Nonce, err error) { var cancel context.CancelFunc ctx, cancel = o.mergeContexts(ctx) @@ -1081,11 +1131,61 @@ ORDER BY nonce ASC return etxs, pkgerrors.Wrap(err, "FindTransactionsConfirmedInBlockRange failed") } -func saveAttemptWithNewState(q pg.Queryer, timeout time.Duration, logger logger.Logger, attempt TxAttempt, broadcastAt time.Time) error { - ctx, cancel := context.WithTimeout(context.Background(), timeout) +func (o *evmTxStore) FindEarliestUnconfirmedBroadcastTime(ctx context.Context, chainID *big.Int) (broadcastAt nullv4.Time, err error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + err = qq.Transaction(func(tx pg.Queryer) error { + if err = qq.QueryRowContext(ctx, `SELECT min(initial_broadcast_at) FROM evm.txes WHERE state = 'unconfirmed' AND evm_chain_id = $1`, chainID.String()).Scan(&broadcastAt); err != nil { + return fmt.Errorf("failed to query for unconfirmed eth_tx count: %w", err) + } + return nil + }, pg.OptReadOnlyTx()) + return broadcastAt, err +} + +func (o *evmTxStore) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context, chainID *big.Int) (earliestUnconfirmedTxBlock nullv4.Int, err error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + err = qq.Transaction(func(tx pg.Queryer) error { + err = qq.QueryRowContext(ctx, ` +SELECT MIN(broadcast_before_block_num) FROM evm.tx_attempts +JOIN evm.txes ON evm.txes.id = evm.tx_attempts.eth_tx_id +WHERE evm.txes.state = 'unconfirmed' +AND evm_chain_id = $1`, chainID.String()).Scan(&earliestUnconfirmedTxBlock) + if err != nil { + return fmt.Errorf("failed to query for earliest unconfirmed tx block: %w", err) + } + return nil + }, pg.OptReadOnlyTx()) + return earliestUnconfirmedTxBlock, err +} + +func (o *evmTxStore) IsTxFinalized(ctx context.Context, blockHeight int64, txID int64, chainID *big.Int) (finalized bool, err error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + + var count int32 + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + err = qq.GetContext(ctx, &count, ` + SELECT COUNT(evm.receipts.receipt) FROM evm.txes + INNER JOIN evm.tx_attempts ON evm.txes.id = evm.tx_attempts.eth_tx_id + INNER JOIN evm.receipts ON evm.tx_attempts.hash = evm.receipts.tx_hash + WHERE evm.receipts.block_number <= ($1 - evm.txes.min_confirmations) + AND evm.txes.id = $2 AND evm.txes.evm_chain_id = $3`, blockHeight, txID, chainID.String()) + if err != nil { + return false, fmt.Errorf("failed to retrieve transaction reciepts: %w", err) + } + return count > 0, nil +} + +func saveAttemptWithNewState(ctx context.Context, q pg.Queryer, logger logger.Logger, attempt TxAttempt, broadcastAt time.Time) error { var dbAttempt DbEthTxAttempt dbAttempt.FromTxAttempt(&attempt) - defer cancel() return pg.SqlxTransaction(ctx, q, logger, func(tx pg.Queryer) error { // In case of null broadcast_at (shouldn't happen) we don't want to // update anyway because it indicates a state where broadcast_at makes @@ -1107,15 +1207,19 @@ func (o *evmTxStore) SaveInsufficientFundsAttempt(ctx context.Context, timeout t return errors.New("expected state to be either in_progress or insufficient_eth") } attempt.State = txmgrtypes.TxAttemptInsufficientFunds - return pkgerrors.Wrap(saveAttemptWithNewState(qq, timeout, o.logger, *attempt, broadcastAt), "saveInsufficientEthAttempt failed") + ctx, cancel = context.WithTimeout(ctx, timeout) + defer cancel() + return pkgerrors.Wrap(saveAttemptWithNewState(ctx, qq, o.logger, *attempt, broadcastAt), "saveInsufficientEthAttempt failed") } -func saveSentAttempt(q pg.Queryer, timeout time.Duration, logger logger.Logger, attempt *TxAttempt, broadcastAt time.Time) error { +func saveSentAttempt(ctx context.Context, q pg.Queryer, timeout time.Duration, logger logger.Logger, attempt *TxAttempt, broadcastAt time.Time) error { if attempt.State != txmgrtypes.TxAttemptInProgress { return errors.New("expected state to be in_progress") } attempt.State = txmgrtypes.TxAttemptBroadcast - return pkgerrors.Wrap(saveAttemptWithNewState(q, timeout, logger, *attempt, broadcastAt), "saveSentAttempt failed") + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return pkgerrors.Wrap(saveAttemptWithNewState(ctx, q, logger, *attempt, broadcastAt), "saveSentAttempt failed") } func (o *evmTxStore) SaveSentAttempt(ctx context.Context, timeout time.Duration, attempt *TxAttempt, broadcastAt time.Time) error { @@ -1123,7 +1227,7 @@ func (o *evmTxStore) SaveSentAttempt(ctx context.Context, timeout time.Duration, ctx, cancel = o.mergeContexts(ctx) defer cancel() qq := o.q.WithOpts(pg.WithParentCtx(ctx)) - return saveSentAttempt(qq, timeout, o.logger, attempt, broadcastAt) + return saveSentAttempt(ctx, qq, timeout, o.logger, attempt, broadcastAt) } func (o *evmTxStore) SaveConfirmedMissingReceiptAttempt(ctx context.Context, timeout time.Duration, attempt *TxAttempt, broadcastAt time.Time) error { @@ -1132,7 +1236,7 @@ func (o *evmTxStore) SaveConfirmedMissingReceiptAttempt(ctx context.Context, tim defer cancel() qq := o.q.WithOpts(pg.WithParentCtx(ctx)) err := qq.Transaction(func(tx pg.Queryer) error { - if err := saveSentAttempt(tx, timeout, o.logger, attempt, broadcastAt); err != nil { + if err := saveSentAttempt(ctx, tx, timeout, o.logger, attempt, broadcastAt); err != nil { return err } if _, err := tx.Exec(`UPDATE evm.txes SET state = 'confirmed_missing_receipt' WHERE id = $1`, attempt.TxID); err != nil { @@ -1195,6 +1299,57 @@ func (o *evmTxStore) SaveInProgressAttempt(ctx context.Context, attempt *TxAttem return nil } +func (o *evmTxStore) GetNonFatalTransactions(ctx context.Context, chainID *big.Int) (txes []*Tx, err error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + err = qq.Transaction(func(tx pg.Queryer) error { + stmt := `SELECT * FROM evm.txes WHERE state <> 'fatal_error' AND evm_chain_id = $1` + var dbEtxs []DbEthTx + if err = tx.Select(&dbEtxs, stmt, chainID.String()); err != nil { + return fmt.Errorf("failed to load evm.txes: %w", err) + } + txes = make([]*Tx, len(dbEtxs)) + dbEthTxsToEvmEthTxPtrs(dbEtxs, txes) + err = o.LoadTxesAttempts(txes, pg.WithParentCtx(ctx), pg.WithQueryer(tx)) + if err != nil { + return fmt.Errorf("failed to load evm.txes: %w", err) + } + return nil + }, pg.OptReadOnlyTx()) + + return txes, nil +} + +func (o *evmTxStore) GetTxByID(ctx context.Context, id int64) (txe *Tx, err error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + + err = qq.Transaction(func(tx pg.Queryer) error { + stmt := `SELECT * FROM evm.txes WHERE id = $1` + var dbEtxs []DbEthTx + if err = tx.Select(&dbEtxs, stmt, id); err != nil { + return fmt.Errorf("failed to load evm.txes: %w", err) + } + txes := make([]*Tx, len(dbEtxs)) + dbEthTxsToEvmEthTxPtrs(dbEtxs, txes) + if len(txes) != 1 { + return fmt.Errorf("failed to get tx with id %v", id) + } + txe = txes[0] + err = o.LoadTxesAttempts(txes, pg.WithParentCtx(ctx), pg.WithQueryer(tx)) + if err != nil { + return fmt.Errorf("failed to load evm.tx_attempts: %w", err) + } + return nil + }, pg.OptReadOnlyTx()) + + return txe, nil +} + // FindTxsRequiringGasBump returns transactions that have all // attempts which are unconfirmed for at least gasBumpThreshold blocks, // limited by limit pending transactions @@ -1343,7 +1498,7 @@ GROUP BY e.id txHashesHex[i] = common.BytesToAddress(r.TxHashes[i]) } - o.logger.Criticalw(fmt.Sprintf("eth_tx with ID %v expired without ever getting a receipt for any of our attempts. "+ + logger.Criticalw(o.logger, fmt.Sprintf("eth_tx with ID %v expired without ever getting a receipt for any of our attempts. "+ "Current block height is %v, transaction was broadcast before block height %v. This transaction may not have not been sent and will be marked as fatally errored. "+ "This can happen if there is another instance of chainlink running that is using the same private key, or if "+ "an external wallet has been used to send a transaction from account %s with nonce %v."+ @@ -1423,7 +1578,6 @@ func (o *evmTxStore) UpdateTxFatalError(ctx context.Context, etx *Tx) error { } // Updates eth attempt from in_progress to broadcast. Also updates the eth tx to unconfirmed. -// One of the more complicated signatures. We have to accept variable pg.QOpt and QueryerFunc arguments func (o *evmTxStore) UpdateTxAttemptInProgressToBroadcast(ctx context.Context, etx *Tx, attempt TxAttempt, NewAttemptState txmgrtypes.TxAttemptState) error { var cancel context.CancelFunc ctx, cancel = o.mergeContexts(ctx) @@ -1486,14 +1640,23 @@ func (o *evmTxStore) UpdateTxUnstartedToInProgress(ctx context.Context, etx *Tx, // Note: the record of the original abandoned transaction will remain in evm.txes, only the attempt is replaced. (Any receipt // associated with the abandoned attempt would also be lost, although this shouldn't happen since only unconfirmed transactions // can be abandoned.) - _, err := tx.Exec(`DELETE FROM evm.tx_attempts a USING evm.txes t + res, err2 := tx.Exec(`DELETE FROM evm.tx_attempts a USING evm.txes t WHERE t.id = a.eth_tx_id AND a.hash = $1 AND t.state = $2 AND t.error = 'abandoned'`, attempt.Hash, txmgr.TxFatalError, ) - if err == nil { + + if err2 != nil { + // If the DELETE fails, we don't want to abort before at least attempting the INSERT. tx hash conflicts with + // abandoned transactions can only happen after a nonce reset. If the node is operating normally but there is + // some unexpected issue with the DELETE query, blocking the txmgr from sending transactions would be risky + // and could potentially get the node stuck. If the INSERT is going to succeed then we definitely want to continue. + // And even if the INSERT fails, an error message showing the txmgr is having trouble inserting tx's in the db may be + // easier to understand quickly if there is a problem with the node. + o.logger.Errorw("Ignoring unexpected db error while checking for txhash conflict", "err", err2) + } else if rows, err := res.RowsAffected(); err != nil { + o.logger.Errorw("Ignoring unexpected db error reading rows affected while checking for txhash conflict", "err", err) + } else if rows > 0 { o.logger.Debugf("Replacing abandoned tx with tx hash %s with tx_id=%d with identical tx hash", attempt.Hash, attempt.TxID) - } else if errors.Is(err, sql.ErrNoRows) { - return err } var dbAttempt DbEthTxAttempt @@ -1502,7 +1665,7 @@ func (o *evmTxStore) UpdateTxUnstartedToInProgress(ctx context.Context, etx *Tx, if e != nil { return pkgerrors.Wrap(e, "failed to BindNamed") } - err = tx.Get(&dbAttempt, query, args...) + err := tx.Get(&dbAttempt, query, args...) if err != nil { var pqErr *pgconn.PgError if isPqErr := errors.As(err, &pqErr); isPqErr && @@ -1603,6 +1766,20 @@ func (o *evmTxStore) CountUnconfirmedTransactions(ctx context.Context, fromAddre return o.countTransactionsWithState(ctx, fromAddress, txmgr.TxUnconfirmed, chainID) } +// CountTransactionsByState returns the number of transactions with any fromAddress in the given state +func (o *evmTxStore) CountTransactionsByState(ctx context.Context, state txmgrtypes.TxState, chainID *big.Int) (count uint32, err error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + err = qq.Get(&count, `SELECT count(*) FROM evm.txes WHERE state = $1 AND evm_chain_id = $2`, + state, chainID.String()) + if err != nil { + return 0, fmt.Errorf("failed to CountTransactionsByState: %w", err) + } + return count, nil +} + // CountUnstartedTransactions returns the number of unconfirmed transactions func (o *evmTxStore) CountUnstartedTransactions(ctx context.Context, fromAddress common.Address, chainID *big.Int) (count uint32, err error) { return o.countTransactionsWithState(ctx, fromAddress, txmgr.TxUnstarted, chainID) @@ -1649,12 +1826,12 @@ func (o *evmTxStore) CreateTransaction(ctx context.Context, txRequest TxRequest, } } err = tx.Get(&dbEtx, ` -INSERT INTO evm.txes (from_address, to_address, encoded_payload, value, gas_limit, state, created_at, meta, subject, evm_chain_id, min_confirmations, pipeline_task_run_id, transmit_checker, idempotency_key) +INSERT INTO evm.txes (from_address, to_address, encoded_payload, value, gas_limit, state, created_at, meta, subject, evm_chain_id, min_confirmations, pipeline_task_run_id, transmit_checker, idempotency_key, signal_callback) VALUES ( -$1,$2,$3,$4,$5,'unstarted',NOW(),$6,$7,$8,$9,$10,$11,$12 +$1,$2,$3,$4,$5,'unstarted',NOW(),$6,$7,$8,$9,$10,$11,$12,$13 ) RETURNING "txes".* -`, txRequest.FromAddress, txRequest.ToAddress, txRequest.EncodedPayload, assets.Eth(txRequest.Value), txRequest.FeeLimit, txRequest.Meta, txRequest.Strategy.Subject(), chainID.String(), txRequest.MinConfirmations, txRequest.PipelineTaskRunID, txRequest.Checker, txRequest.IdempotencyKey) +`, txRequest.FromAddress, txRequest.ToAddress, txRequest.EncodedPayload, assets.Eth(txRequest.Value), txRequest.FeeLimit, txRequest.Meta, txRequest.Strategy.Subject(), chainID.String(), txRequest.MinConfirmations, txRequest.PipelineTaskRunID, txRequest.Checker, txRequest.IdempotencyKey, txRequest.SignalCallback) if err != nil { return pkgerrors.Wrap(err, "CreateEthTransaction failed to insert evm tx") } @@ -1767,6 +1944,72 @@ func (o *evmTxStore) Abandon(ctx context.Context, chainID *big.Int, addr common. return err } +// Find transactions by a field in the TxMeta blob and transaction states +func (o *evmTxStore) FindTxesByMetaFieldAndStates(ctx context.Context, metaField string, metaValue string, states []txmgrtypes.TxState, chainID *big.Int) ([]*Tx, error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + var dbEtxs []DbEthTx + sql := fmt.Sprintf("SELECT * FROM evm.txes WHERE evm_chain_id = $1 AND meta->>'%s' = $2 AND state = ANY($3)", metaField) + err := qq.Select(&dbEtxs, sql, chainID.String(), metaValue, pq.Array(states)) + txes := make([]*Tx, len(dbEtxs)) + dbEthTxsToEvmEthTxPtrs(dbEtxs, txes) + return txes, pkgerrors.Wrap(err, "failed to FindTxesByMetaFieldAndStates") +} + +// Find transactions with a non-null TxMeta field that was provided by transaction states +func (o *evmTxStore) FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []txmgrtypes.TxState, chainID *big.Int) (txes []*Tx, err error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + var dbEtxs []DbEthTx + sql := fmt.Sprintf("SELECT * FROM evm.txes WHERE meta->'%s' IS NOT NULL AND state = ANY($1) AND evm_chain_id = $2", metaField) + err = qq.Select(&dbEtxs, sql, pq.Array(states), chainID.String()) + txes = make([]*Tx, len(dbEtxs)) + dbEthTxsToEvmEthTxPtrs(dbEtxs, txes) + return txes, pkgerrors.Wrap(err, "failed to FindTxesWithMetaFieldByStates") +} + +// Find transactions with a non-null TxMeta field that was provided and a receipt block number greater than or equal to the one provided +func (o *evmTxStore) FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) (txes []*Tx, err error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + var dbEtxs []DbEthTx + sql := fmt.Sprintf("SELECT et.* FROM evm.txes et JOIN evm.tx_attempts eta on et.id = eta.eth_tx_id JOIN evm.receipts er on eta.hash = er.tx_hash WHERE et.meta->'%s' IS NOT NULL AND er.block_number >= $1 AND et.evm_chain_id = $2", metaField) + err = qq.Select(&dbEtxs, sql, blockNum, chainID.String()) + txes = make([]*Tx, len(dbEtxs)) + dbEthTxsToEvmEthTxPtrs(dbEtxs, txes) + return txes, pkgerrors.Wrap(err, "failed to FindTxesWithMetaFieldByReceiptBlockNum") +} + +// Find transactions loaded with transaction attempts and receipts by transaction IDs and states +func (o *evmTxStore) FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx context.Context, ids []big.Int, states []txmgrtypes.TxState, chainID *big.Int) (txes []*Tx, err error) { + var cancel context.CancelFunc + ctx, cancel = o.mergeContexts(ctx) + defer cancel() + qq := o.q.WithOpts(pg.WithParentCtx(ctx)) + err = qq.Transaction(func(tx pg.Queryer) error { + var dbEtxs []DbEthTx + if err = tx.Select(&dbEtxs, `SELECT * FROM evm.txes WHERE id = ANY($1) AND state = ANY($2) AND evm_chain_id = $3`, pq.Array(ids), pq.Array(states), chainID.String()); err != nil { + return pkgerrors.Wrapf(err, "failed to find evm.txes") + } + txes = make([]*Tx, len(dbEtxs)) + dbEthTxsToEvmEthTxPtrs(dbEtxs, txes) + if err = o.LoadTxesAttempts(txes, pg.WithQueryer(tx)); err != nil { + return pkgerrors.Wrapf(err, "failed to load evm.tx_attempts for evm.tx") + } + if err = loadEthTxesAttemptsReceipts(tx, txes); err != nil { + return pkgerrors.Wrapf(err, "failed to load evm.receipts for evm.tx") + } + return nil + }) + return txes, pkgerrors.Wrap(err, "FindTxesWithAttemptsAndReceiptsByIdsAndState failed") +} + // Returns a context that contains the values of the provided context, // and which is canceled when either the provided contextg or TxStore parent context is canceled. func (o *evmTxStore) mergeContexts(ctx context.Context) (context.Context, context.CancelFunc) { diff --git a/core/chains/evm/txmgr/evm_tx_store_test.go b/core/chains/evm/txmgr/evm_tx_store_test.go index e5b47c457b4..0b4287b6f6c 100644 --- a/core/chains/evm/txmgr/evm_tx_store_test.go +++ b/core/chains/evm/txmgr/evm_tx_store_test.go @@ -7,20 +7,21 @@ import ( "testing" "time" + "github.com/smartcontractkit/chainlink-common/pkg/logger" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -51,7 +52,7 @@ func TestORM_TransactionsWithAttempts(t *testing.T) { require.NoError(t, txStore.InsertTxAttempt(&attempt)) // tx 3 has no attempts - cltest.MustCreateUnstartedGeneratedTx(t, txStore, from, &cltest.FixtureChainID) + mustCreateUnstartedGeneratedTx(t, txStore, from, &cltest.FixtureChainID) var count int err := db.Get(&count, `SELECT count(*) FROM evm.txes`) @@ -96,7 +97,7 @@ func TestORM_Transactions(t *testing.T) { require.NoError(t, txStore.InsertTxAttempt(&attempt)) // tx 3 has no attempts - cltest.MustCreateUnstartedGeneratedTx(t, txStore, from, &cltest.FixtureChainID) + mustCreateUnstartedGeneratedTx(t, txStore, from, &cltest.FixtureChainID) var count int err := db.Get(&count, `SELECT count(*) FROM evm.txes`) @@ -124,7 +125,7 @@ func TestORM(t *testing.T) { var etx txmgr.Tx t.Run("InsertTx", func(t *testing.T) { - etx = cltest.NewEthTx(t, fromAddress) + etx = cltest.NewEthTx(fromAddress) require.NoError(t, orm.InsertTx(&etx)) assert.Greater(t, int(etx.ID), 0) cltest.AssertCount(t, db, "evm.txes", 1) @@ -146,7 +147,7 @@ func TestORM(t *testing.T) { }) var r txmgr.Receipt t.Run("InsertReceipt", func(t *testing.T) { - r = cltest.NewEthReceipt(t, 42, utils.NewHash(), attemptD.Hash, 0x1) + r = newEthReceipt(42, utils.NewHash(), attemptD.Hash, 0x1) id, err := orm.InsertReceipt(&r.Receipt) r.ID = id require.NoError(t, err) @@ -202,12 +203,12 @@ func TestORM_FindTxAttemptConfirmedByTxIDs(t *testing.T) { require.NoError(t, orm.InsertTxAttempt(&attempt)) // add receipt for the second attempt - r := cltest.NewEthReceipt(t, 4, utils.NewHash(), attempt.Hash, 0x1) + r := newEthReceipt(4, utils.NewHash(), attempt.Hash, 0x1) _, err := orm.InsertReceipt(&r.Receipt) require.NoError(t, err) // tx 3 has no attempts - cltest.MustCreateUnstartedGeneratedTx(t, orm, from, &cltest.FixtureChainID) + mustCreateUnstartedGeneratedTx(t, orm, from, &cltest.FixtureChainID) cltest.MustInsertUnconfirmedEthTx(t, orm, 3, from) // tx4 cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, orm, 4, from) // tx5 @@ -251,7 +252,7 @@ func TestORM_FindTxAttemptsRequiringResend(t *testing.T) { // Mix up the insert order to assure that they come out sorted by nonce not implicitly or by ID e1 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 1, fromAddress, time.Unix(1616509200, 0)) - e3 := cltest.MustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t, txStore, 3, fromAddress, time.Unix(1616509400, 0)) + e3 := mustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t, txStore, 3, fromAddress, time.Unix(1616509400, 0)) e0 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 0, fromAddress, time.Unix(1616509100, 0)) e2 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 2, fromAddress, time.Unix(1616509300, 0)) @@ -330,7 +331,7 @@ func TestORM_UpdateBroadcastAts(t *testing.T) { t.Run("does not update when broadcast_at is NULL", func(t *testing.T) { t.Parallel() - etx := cltest.MustCreateUnstartedGeneratedTx(t, orm, fromAddress, &cltest.FixtureChainID) + etx := mustCreateUnstartedGeneratedTx(t, orm, fromAddress, &cltest.FixtureChainID) var nullTime *time.Time assert.Equal(t, nullTime, etx.BroadcastAt) @@ -348,7 +349,7 @@ func TestORM_UpdateBroadcastAts(t *testing.T) { t.Parallel() time1 := time.Now() - etx := cltest.NewEthTx(t, fromAddress) + etx := cltest.NewEthTx(fromAddress) etx.Sequence = new(evmtypes.Nonce) etx.State = txmgrcommon.TxUnconfirmed etx.BroadcastAt = &time1 @@ -445,7 +446,7 @@ func TestORM_FindTxAttemptsConfirmedMissingReceipt(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) originalBroadcastAt := time.Unix(1616509100, 0) - etx0 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx0 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 0, 1, originalBroadcastAt, fromAddress) attempts, err := txStore.FindTxAttemptsConfirmedMissingReceipt(testutils.Context(t), ethClient.ConfiguredChainID()) @@ -467,7 +468,7 @@ func TestORM_UpdateTxsUnconfirmed(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) originalBroadcastAt := time.Unix(1616509100, 0) - etx0 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx0 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 0, 1, originalBroadcastAt, fromAddress) assert.Equal(t, etx0.State, txmgrcommon.TxConfirmedMissingReceipt) require.NoError(t, txStore.UpdateTxsUnconfirmed(testutils.Context(t), []int64{etx0.ID})) @@ -488,7 +489,7 @@ func TestORM_FindTxAttemptsRequiringReceiptFetch(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) originalBroadcastAt := time.Unix(1616509100, 0) - etx0 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx0 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 0, 1, originalBroadcastAt, fromAddress) attempts, err := txStore.FindTxAttemptsRequiringReceiptFetch(testutils.Context(t), ethClient.ConfiguredChainID()) @@ -509,7 +510,7 @@ func TestORM_SaveFetchedReceipts(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) originalBroadcastAt := time.Unix(1616509100, 0) - etx0 := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + etx0 := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, txStore, 0, 1, originalBroadcastAt, fromAddress) require.Len(t, etx0.TxAttempts, 1) @@ -551,7 +552,7 @@ func TestORM_MarkAllConfirmedMissingReceipt(t *testing.T) { assert.Equal(t, txmgrcommon.TxUnconfirmed, etx0.State) // create transaction 1 (nonce 1) that is confirmed (block 77) - etx1 := cltest.MustInsertConfirmedEthTxBySaveFetchedReceipts(t, txStore, fromAddress, int64(1), int64(77), *ethClient.ConfiguredChainID()) + etx1 := mustInsertConfirmedEthTxBySaveFetchedReceipts(t, txStore, fromAddress, int64(1), int64(77), *ethClient.ConfiguredChainID()) assert.Equal(t, etx1.State, txmgrcommon.TxConfirmed) // mark transaction 0 confirmed_missing_receipt @@ -607,7 +608,7 @@ func TestORM_GetInProgressTxAttempts(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) // insert etx with attempt - etx := cltest.MustInsertUnconfirmedEthTxWithAttemptState(t, txStore, int64(7), fromAddress, txmgrtypes.TxAttemptInProgress) + etx := mustInsertUnconfirmedEthTxWithAttemptState(t, txStore, int64(7), fromAddress, txmgrtypes.TxAttemptInProgress) // fetch attempt attempts, err := txStore.GetInProgressTxAttempts(testutils.Context(t), fromAddress, ethClient.ConfiguredChainID()) @@ -617,7 +618,7 @@ func TestORM_GetInProgressTxAttempts(t *testing.T) { assert.Equal(t, etx.TxAttempts[0].ID, attempts[0].ID) } -func TestORM_FindReceiptsPendingConfirmation(t *testing.T) { +func TestORM_FindTxesPendingCallback(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) @@ -645,21 +646,50 @@ func TestORM_FindReceiptsPendingConfirmation(t *testing.T) { minConfirmations := int64(2) - run := cltest.MustInsertPipelineRun(t, db) - tr := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run.ID) - pgtest.MustExec(t, db, `UPDATE pipeline_runs SET state = 'suspended' WHERE id = $1`, run.ID) - - etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 3, 1, fromAddress) + // Suspended run waiting for callback + run1 := cltest.MustInsertPipelineRun(t, db) + tr1 := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run1.ID) + pgtest.MustExec(t, db, `UPDATE pipeline_runs SET state = 'suspended' WHERE id = $1`, run1.ID) + etx1 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 3, 1, fromAddress) pgtest.MustExec(t, db, `UPDATE evm.txes SET meta='{"FailOnRevert": true}'`) - attempt := etx.TxAttempts[0] - cltest.MustInsertEthReceipt(t, txStore, head.Number-minConfirmations, head.Hash, attempt.Hash) - - pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2 WHERE id = $3`, &tr.ID, minConfirmations, etx.ID) - - receiptsPlus, err := txStore.FindReceiptsPendingConfirmation(testutils.Context(t), head.Number, ethClient.ConfiguredChainID()) + attempt1 := etx1.TxAttempts[0] + mustInsertEthReceipt(t, txStore, head.Number-minConfirmations, head.Hash, attempt1.Hash) + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE WHERE id = $3`, &tr1.ID, minConfirmations, etx1.ID) + + // Callback to pipeline service completed. Should be ignored + run2 := cltest.MustInsertPipelineRunWithStatus(t, db, 0, pipeline.RunStatusCompleted) + tr2 := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run2.ID) + etx2 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 4, 1, fromAddress) + pgtest.MustExec(t, db, `UPDATE evm.txes SET meta='{"FailOnRevert": false}'`) + attempt2 := etx2.TxAttempts[0] + mustInsertEthReceipt(t, txStore, head.Number-minConfirmations, head.Hash, attempt2.Hash) + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE, callback_completed = TRUE WHERE id = $3`, &tr2.ID, minConfirmations, etx2.ID) + + // Suspended run younger than minConfirmations. Should be ignored + run3 := cltest.MustInsertPipelineRun(t, db) + tr3 := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run3.ID) + pgtest.MustExec(t, db, `UPDATE pipeline_runs SET state = 'suspended' WHERE id = $1`, run3.ID) + etx3 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 5, 1, fromAddress) + pgtest.MustExec(t, db, `UPDATE evm.txes SET meta='{"FailOnRevert": false}'`) + attempt3 := etx3.TxAttempts[0] + mustInsertEthReceipt(t, txStore, head.Number, head.Hash, attempt3.Hash) + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE WHERE id = $3`, &tr3.ID, minConfirmations, etx3.ID) + + // Tx not marked for callback. Should be ignore + etx4 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 6, 1, fromAddress) + attempt4 := etx4.TxAttempts[0] + mustInsertEthReceipt(t, txStore, head.Number, head.Hash, attempt4.Hash) + pgtest.MustExec(t, db, `UPDATE evm.txes SET min_confirmations = $1 WHERE id = $2`, minConfirmations, etx4.ID) + + // Unconfirmed Tx without receipts. Should be ignored + etx5 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 7, 1, fromAddress) + pgtest.MustExec(t, db, `UPDATE evm.txes SET min_confirmations = $1 WHERE id = $2`, minConfirmations, etx5.ID) + + // Search evm.txes table for tx requiring callback + receiptsPlus, err := txStore.FindTxesPendingCallback(testutils.Context(t), head.Number, ethClient.ConfiguredChainID()) require.NoError(t, err) assert.Len(t, receiptsPlus, 1) - assert.Equal(t, tr.ID, receiptsPlus[0].ID) + assert.Equal(t, tr1.ID, receiptsPlus[0].ID) } func Test_FindTxWithIdempotencyKey(t *testing.T) { @@ -680,8 +710,8 @@ func Test_FindTxWithIdempotencyKey(t *testing.T) { t.Run("returns transaction if it exists", func(t *testing.T) { idempotencyKey := "777" cfg.EVM().ChainID() - etx := cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, big.NewInt(0), - cltest.EvmTxRequestWithIdempotencyKey(idempotencyKey)) + etx := mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, big.NewInt(0), + txRequestWithIdempotencyKey(idempotencyKey)) require.Equal(t, idempotencyKey, *etx.IdempotencyKey) res, err := txStore.FindTxWithIdempotencyKey(testutils.Context(t), idempotencyKey, big.NewInt(0)) @@ -726,7 +756,7 @@ func TestORM_UpdateTxForRebroadcast(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) t.Run("delete all receipts for eth transaction", func(t *testing.T) { - etx := cltest.MustInsertConfirmedEthTxWithReceipt(t, txStore, fromAddress, 777, 1) + etx := mustInsertConfirmedEthTxWithReceipt(t, txStore, fromAddress, 777, 1) etx, err := txStore.FindTxWithAttempts(etx.ID) assert.NoError(t, err) // assert attempt state @@ -756,6 +786,31 @@ func TestORM_UpdateTxForRebroadcast(t *testing.T) { }) } +func TestORM_IsTxFinalized(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := newTestChainScopedConfig(t) + txStore := cltest.NewTestTxStore(t, db, cfg.Database()) + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + + t.Run("confirmed tx not past finality_depth", func(t *testing.T) { + confirmedAddr := cltest.MustGenerateRandomKey(t).Address + tx := mustInsertConfirmedEthTxWithReceipt(t, txStore, confirmedAddr, 123, 1) + finalized, err := txStore.IsTxFinalized(testutils.Context(t), 2, tx.ID, ethClient.ConfiguredChainID()) + require.NoError(t, err) + require.False(t, finalized) + }) + + t.Run("confirmed tx past finality_depth", func(t *testing.T) { + confirmedAddr := cltest.MustGenerateRandomKey(t).Address + tx := mustInsertConfirmedEthTxWithReceipt(t, txStore, confirmedAddr, 123, 1) + finalized, err := txStore.IsTxFinalized(testutils.Context(t), 10, tx.ID, ethClient.ConfiguredChainID()) + require.NoError(t, err) + require.True(t, finalized) + }) +} + func TestORM_FindTransactionsConfirmedInBlockRange(t *testing.T) { t.Parallel() @@ -781,8 +836,8 @@ func TestORM_FindTransactionsConfirmedInBlockRange(t *testing.T) { } t.Run("find all transactions confirmed in range", func(t *testing.T) { - etx_8 := cltest.MustInsertConfirmedEthTxWithReceipt(t, txStore, fromAddress, 700, 8) - etx_9 := cltest.MustInsertConfirmedEthTxWithReceipt(t, txStore, fromAddress, 777, 9) + etx_8 := mustInsertConfirmedEthTxWithReceipt(t, txStore, fromAddress, 700, 8) + etx_9 := mustInsertConfirmedEthTxWithReceipt(t, txStore, fromAddress, 777, 9) etxes, err := txStore.FindTransactionsConfirmedInBlockRange(testutils.Context(t), head.Number, 8, ethClient.ConfiguredChainID()) require.NoError(t, err) @@ -792,6 +847,61 @@ func TestORM_FindTransactionsConfirmedInBlockRange(t *testing.T) { }) } +func TestORM_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := newTestChainScopedConfig(t) + txStore := cltest.NewTestTxStore(t, db, cfg.Database()) + ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) + + t.Run("no unconfirmed eth txes", func(t *testing.T) { + broadcastAt, err := txStore.FindEarliestUnconfirmedBroadcastTime(testutils.Context(t), ethClient.ConfiguredChainID()) + require.NoError(t, err) + require.False(t, broadcastAt.Valid) + }) + + t.Run("verify broadcast time", func(t *testing.T) { + tx := cltest.MustInsertUnconfirmedEthTx(t, txStore, 123, fromAddress) + broadcastAt, err := txStore.FindEarliestUnconfirmedBroadcastTime(testutils.Context(t), ethClient.ConfiguredChainID()) + require.NoError(t, err) + require.True(t, broadcastAt.Ptr().Equal(*tx.BroadcastAt)) + }) +} + +func TestORM_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := newTestChainScopedConfig(t) + txStore := cltest.NewTestTxStore(t, db, cfg.Database()) + ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) + _, fromAddress2 := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) + + t.Run("no earliest unconfirmed tx block", func(t *testing.T) { + earliestBlock, err := txStore.FindEarliestUnconfirmedTxAttemptBlock(testutils.Context(t), ethClient.ConfiguredChainID()) + require.NoError(t, err) + require.False(t, earliestBlock.Valid) + }) + + t.Run("verify earliest unconfirmed tx block", func(t *testing.T) { + var blockNum int64 = 2 + tx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 123, blockNum, time.Now(), fromAddress) + _ = mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 123, blockNum, time.Now().Add(time.Minute), fromAddress2) + err := txStore.UpdateTxsUnconfirmed(testutils.Context(t), []int64{tx.ID}) + require.NoError(t, err) + + earliestBlock, err := txStore.FindEarliestUnconfirmedTxAttemptBlock(testutils.Context(t), ethClient.ConfiguredChainID()) + require.NoError(t, err) + require.True(t, earliestBlock.Valid) + require.Equal(t, blockNum, earliestBlock.Int64) + }) +} + func TestORM_SaveInsufficientEthAttempt(t *testing.T) { t.Parallel() @@ -804,7 +914,7 @@ func TestORM_SaveInsufficientEthAttempt(t *testing.T) { require.NoError(t, err) t.Run("updates attempt state", func(t *testing.T) { - etx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, 1, fromAddress) + etx := mustInsertInProgressEthTxWithAttempt(t, txStore, 1, fromAddress) now := time.Now() err = txStore.SaveInsufficientFundsAttempt(testutils.Context(t), defaultDuration, &etx.TxAttempts[0], now) @@ -828,7 +938,7 @@ func TestORM_SaveSentAttempt(t *testing.T) { require.NoError(t, err) t.Run("updates attempt state to 'broadcast'", func(t *testing.T) { - etx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, 1, fromAddress) + etx := mustInsertInProgressEthTxWithAttempt(t, txStore, 1, fromAddress) require.Nil(t, etx.BroadcastAt) now := time.Now() @@ -853,7 +963,7 @@ func TestORM_SaveConfirmedMissingReceiptAttempt(t *testing.T) { require.NoError(t, err) t.Run("updates attempt to 'broadcast' and transaction to 'confirm_missing_receipt'", func(t *testing.T) { - etx := cltest.MustInsertUnconfirmedEthTxWithAttemptState(t, txStore, 1, fromAddress, txmgrtypes.TxAttemptInProgress) + etx := mustInsertUnconfirmedEthTxWithAttemptState(t, txStore, 1, fromAddress, txmgrtypes.TxAttemptInProgress) now := time.Now() err = txStore.SaveConfirmedMissingReceiptAttempt(testutils.Context(t), defaultDuration, &etx.TxAttempts[0], now) @@ -876,7 +986,7 @@ func TestORM_DeleteInProgressAttempt(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) t.Run("deletes in_progress attempt", func(t *testing.T) { - etx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, 1, fromAddress) + etx := mustInsertInProgressEthTxWithAttempt(t, txStore, 1, fromAddress) attempt := etx.TxAttempts[0] err := txStore.DeleteInProgressAttempt(testutils.Context(t), etx.TxAttempts[0]) @@ -912,7 +1022,7 @@ func TestORM_SaveInProgressAttempt(t *testing.T) { }) t.Run("updates old attempt to in_progress when insufficient_eth", func(t *testing.T) { - etx := cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 23, fromAddress) + etx := mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 23, fromAddress) attempt := etx.TxAttempts[0] require.Equal(t, txmgrtypes.TxAttemptInsufficientFunds, attempt.State) require.NotEqual(t, 0, attempt.ID) @@ -942,7 +1052,7 @@ func TestORM_FindTxsRequiringGasBump(t *testing.T) { currentBlockNum := int64(10) t.Run("gets txs requiring gas bump", func(t *testing.T) { - etx := cltest.MustInsertUnconfirmedEthTxWithAttemptState(t, txStore, 1, fromAddress, txmgrtypes.TxAttemptBroadcast) + etx := mustInsertUnconfirmedEthTxWithAttemptState(t, txStore, 1, fromAddress, txmgrtypes.TxAttemptBroadcast) err := txStore.SetBroadcastBeforeBlockNum(testutils.Context(t), currentBlockNum, ethClient.ConfiguredChainID()) require.NoError(t, err) @@ -955,7 +1065,7 @@ func TestORM_FindTxsRequiringGasBump(t *testing.T) { assert.Equal(t, currentBlockNum, *attempts[0].BroadcastBeforeBlockNum) // this tx will not require gas bump - cltest.MustInsertUnconfirmedEthTxWithAttemptState(t, txStore, 2, fromAddress, txmgrtypes.TxAttemptBroadcast) + mustInsertUnconfirmedEthTxWithAttemptState(t, txStore, 2, fromAddress, txmgrtypes.TxAttemptBroadcast) err = txStore.SetBroadcastBeforeBlockNum(testutils.Context(t), currentBlockNum+1, ethClient.ConfiguredChainID()) require.NoError(t, err) @@ -982,18 +1092,18 @@ func TestEthConfirmer_FindTxsRequiringResubmissionDueToInsufficientEth(t *testin _, otherAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) // Insert order is mixed up to test sorting - etx2 := cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 1, fromAddress) + etx2 := mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 1, fromAddress) etx3 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 2, fromAddress) attempt3_2 := cltest.NewLegacyEthTxAttempt(t, etx3.ID) attempt3_2.State = txmgrtypes.TxAttemptInsufficientFunds attempt3_2.TxFee.Legacy = assets.NewWeiI(100) require.NoError(t, txStore.InsertTxAttempt(&attempt3_2)) - etx1 := cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 0, fromAddress) + etx1 := mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 0, fromAddress) // These should never be returned cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 3, fromAddress) cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 4, 100, fromAddress) - cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 0, otherAddress) + mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 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 := txStore.FindTxsRequiringResubmissionDueToInsufficientFunds(testutils.Context(t), fromAddress, &cltest.FixtureChainID) @@ -1043,7 +1153,7 @@ func TestORM_MarkOldTxesMissingReceiptAsErrored(t *testing.T) { // tx state should be confirmed missing receipt // attempt should be broadcast before cutoff time t.Run("successfully mark errored transactions", func(t *testing.T) { - etx := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 1, 7, time.Now(), fromAddress) + etx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 1, 7, time.Now(), fromAddress) err := txStore.MarkOldTxesMissingReceiptAsErrored(testutils.Context(t), 10, 2, ethClient.ConfiguredChainID()) require.NoError(t, err) @@ -1054,7 +1164,7 @@ func TestORM_MarkOldTxesMissingReceiptAsErrored(t *testing.T) { }) t.Run("successfully mark errored transactions w/ qopt passing in sql.Tx", func(t *testing.T) { - etx := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 1, 7, time.Now(), fromAddress) + etx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 1, 7, time.Now(), fromAddress) err := txStore.MarkOldTxesMissingReceiptAsErrored(testutils.Context(t), 10, 2, ethClient.ConfiguredChainID()) require.NoError(t, err) @@ -1075,7 +1185,7 @@ func TestORM_LoadEthTxesAttempts(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) t.Run("load eth tx attempt", func(t *testing.T) { - etx := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 1, 7, time.Now(), fromAddress) + etx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 1, 7, time.Now(), fromAddress) etx.TxAttempts = []txmgr.TxAttempt{} err := txStore.LoadTxesAttempts([]*txmgr.Tx{&etx}) @@ -1084,10 +1194,10 @@ func TestORM_LoadEthTxesAttempts(t *testing.T) { }) t.Run("load new attempt inserted in current postgres transaction", func(t *testing.T) { - etx := cltest.MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 3, 9, time.Now(), fromAddress) + etx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 3, 9, time.Now(), fromAddress) etx.TxAttempts = []txmgr.TxAttempt{} - q := pg.NewQ(db, logger.TestLogger(t), cfg.Database()) + q := pg.NewQ(db, logger.Test(t), cfg.Database()) newAttempt := cltest.NewDynamicFeeEthTxAttempt(t, etx.ID) var dbAttempt txmgr.DbEthTxAttempt @@ -1124,7 +1234,7 @@ func TestORM_SaveReplacementInProgressAttempt(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) t.Run("replace eth tx attempt", func(t *testing.T) { - etx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, 123, fromAddress) + etx := mustInsertInProgressEthTxWithAttempt(t, txStore, 123, fromAddress) oldAttempt := etx.TxAttempts[0] newAttempt := cltest.NewDynamicFeeEthTxAttempt(t, etx.ID) @@ -1150,7 +1260,7 @@ func TestORM_FindNextUnstartedTransactionFromAddress(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) t.Run("cannot find unstarted tx", func(t *testing.T) { - cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, 13, fromAddress) + mustInsertInProgressEthTxWithAttempt(t, txStore, 13, fromAddress) resultEtx := new(txmgr.Tx) err := txStore.FindNextUnstartedTransactionFromAddress(testutils.Context(t), resultEtx, fromAddress, ethClient.ConfiguredChainID()) @@ -1158,7 +1268,7 @@ func TestORM_FindNextUnstartedTransactionFromAddress(t *testing.T) { }) t.Run("finds unstarted tx", func(t *testing.T) { - cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) + mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) resultEtx := new(txmgr.Tx) err := txStore.FindNextUnstartedTransactionFromAddress(testutils.Context(t), resultEtx, fromAddress, ethClient.ConfiguredChainID()) require.NoError(t, err) @@ -1176,7 +1286,7 @@ func TestORM_UpdateTxFatalError(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) t.Run("update successful", func(t *testing.T) { - etx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, 13, fromAddress) + etx := mustInsertInProgressEthTxWithAttempt(t, txStore, 13, fromAddress) etxPretendError := null.StringFrom("no more toilet paper") etx.Error = etxPretendError @@ -1199,7 +1309,7 @@ func TestORM_UpdateTxAttemptInProgressToBroadcast(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) t.Run("update successful", func(t *testing.T) { - etx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, 13, fromAddress) + etx := mustInsertInProgressEthTxWithAttempt(t, txStore, 13, fromAddress) attempt := etx.TxAttempts[0] require.Equal(t, txmgrtypes.TxAttemptInProgress, attempt.State) @@ -1228,11 +1338,11 @@ func TestORM_UpdateTxUnstartedToInProgress(t *testing.T) { txStore := cltest.NewTestTxStore(t, db, cfg.Database()) ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) - q := pg.NewQ(db, logger.TestLogger(t), cfg.Database()) + q := pg.NewQ(db, logger.Test(t), cfg.Database()) nonce := evmtypes.Nonce(123) t.Run("update successful", func(t *testing.T) { - etx := cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) + etx := mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) etx.Sequence = &nonce attempt := cltest.NewLegacyEthTxAttempt(t, etx.ID) @@ -1246,7 +1356,7 @@ func TestORM_UpdateTxUnstartedToInProgress(t *testing.T) { }) t.Run("update fails because tx is removed", func(t *testing.T) { - etx := cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) + etx := mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) etx.Sequence = &nonce attempt := cltest.NewLegacyEthTxAttempt(t, etx.ID) @@ -1263,10 +1373,10 @@ func TestORM_UpdateTxUnstartedToInProgress(t *testing.T) { txStore = cltest.NewTestTxStore(t, db, cfg.Database()) ethKeyStore = cltest.NewKeyStore(t, db, cfg.Database()).Eth() _, fromAddress = cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) - q = pg.NewQ(db, logger.TestLogger(t), cfg.Database()) + q = pg.NewQ(db, logger.Test(t), cfg.Database()) t.Run("update replaces abandoned tx with same hash", func(t *testing.T) { - etx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, nonce, fromAddress) + etx := mustInsertInProgressEthTxWithAttempt(t, txStore, nonce, fromAddress) require.Len(t, etx.TxAttempts, 1) zero := models.MustNewDuration(time.Duration(0)) @@ -1279,12 +1389,12 @@ func TestORM_UpdateTxUnstartedToInProgress(t *testing.T) { ccfg := evmtest.NewChainScopedConfig(t, evmCfg) evmTxmCfg := txmgr.NewEvmTxmConfig(ccfg.EVM()) ec := evmtest.NewEthClientMockWithDefaultChain(t) - txMgr := txmgr.NewEvmTxm(ec.ConfiguredChainID(), evmTxmCfg, ccfg.EVM().Transactions(), nil, logger.TestLogger(t), nil, nil, - nil, txStore, nil, nil, nil, nil) + txMgr := txmgr.NewEvmTxm(ec.ConfiguredChainID(), evmTxmCfg, ccfg.EVM().Transactions(), nil, logger.Test(t), nil, nil, + nil, txStore, nil, nil, nil, nil, nil) err := txMgr.XXXTestAbandon(fromAddress) // mark transaction as abandoned require.NoError(t, err) - etx2 := cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) + etx2 := mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) etx2.Sequence = &nonce attempt2 := cltest.NewLegacyEthTxAttempt(t, etx2.ID) attempt2.Hash = etx.TxAttempts[0].Hash @@ -1299,7 +1409,7 @@ func TestORM_UpdateTxUnstartedToInProgress(t *testing.T) { // Same flow as previous test, but without calling txMgr.Abandon() t.Run("duplicate tx hash disallowed in tx_eth_attempts", func(t *testing.T) { - etx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, nonce, fromAddress) + etx := mustInsertInProgressEthTxWithAttempt(t, txStore, nonce, fromAddress) require.Len(t, etx.TxAttempts, 1) etx.State = txmgrcommon.TxUnstarted @@ -1327,7 +1437,7 @@ func TestORM_GetTxInProgress(t *testing.T) { }) t.Run("get 1 in progress eth transaction", func(t *testing.T) { - etx := cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, 123, fromAddress) + etx := mustInsertInProgressEthTxWithAttempt(t, txStore, 123, fromAddress) etxResult, err := txStore.GetTxInProgress(testutils.Context(t), fromAddress) require.NoError(t, err) @@ -1335,6 +1445,81 @@ func TestORM_GetTxInProgress(t *testing.T) { }) } +func TestORM_GetNonFatalTransactions(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := newTestChainScopedConfig(t) + txStore := cltest.NewTestTxStore(t, db, cfg.Database()) + ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) + + t.Run("gets 0 non finalized eth transaction", func(t *testing.T) { + txes, err := txStore.GetNonFatalTransactions(testutils.Context(t), ethClient.ConfiguredChainID()) + require.NoError(t, err) + require.Empty(t, txes) + }) + + t.Run("get in progress, unstarted, and unconfirmed eth transactions", func(t *testing.T) { + inProgressTx := mustInsertInProgressEthTxWithAttempt(t, txStore, 123, fromAddress) + unstartedTx := mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, ethClient.ConfiguredChainID()) + + txes, err := txStore.GetNonFatalTransactions(testutils.Context(t), ethClient.ConfiguredChainID()) + require.NoError(t, err) + + for _, tx := range txes { + require.True(t, tx.ID == inProgressTx.ID || tx.ID == unstartedTx.ID) + } + }) +} + +func TestORM_GetTxByID(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := newTestChainScopedConfig(t) + txStore := cltest.NewTestTxStore(t, db, cfg.Database()) + ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() + _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) + + t.Run("no transaction", func(t *testing.T) { + tx, err := txStore.GetTxByID(testutils.Context(t), int64(0)) + require.NoError(t, err) + require.Nil(t, tx) + }) + + t.Run("get transaction by ID", func(t *testing.T) { + insertedTx := mustInsertInProgressEthTxWithAttempt(t, txStore, 123, fromAddress) + tx, err := txStore.GetTxByID(testutils.Context(t), insertedTx.ID) + require.NoError(t, err) + require.NotNil(t, tx) + }) +} + +func TestORM_GetFatalTransactions(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := newTestChainScopedConfig(t) + txStore := cltest.NewTestTxStore(t, db, cfg.Database()) + ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() + _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) + + t.Run("gets 0 fatal eth transactions", func(t *testing.T) { + txes, err := txStore.GetFatalTransactions(testutils.Context(t)) + require.NoError(t, err) + require.Empty(t, txes) + }) + + t.Run("get fatal transactions", func(t *testing.T) { + fatalTx := mustInsertFatalErrorEthTx(t, txStore, fromAddress) + txes, err := txStore.GetFatalTransactions(testutils.Context(t)) + require.NoError(t, err) + require.Equal(t, txes[0].ID, fatalTx.ID) + }) +} + func TestORM_HasInProgressTransaction(t *testing.T) { t.Parallel() @@ -1352,7 +1537,7 @@ func TestORM_HasInProgressTransaction(t *testing.T) { }) t.Run("has in progress eth transaction", func(t *testing.T) { - cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, 123, fromAddress) + mustInsertInProgressEthTxWithAttempt(t, txStore, 123, fromAddress) exists, err := txStore.HasInProgressTransaction(testutils.Context(t), fromAddress, ethClient.ConfiguredChainID()) require.NoError(t, err) @@ -1381,6 +1566,27 @@ func TestORM_CountUnconfirmedTransactions(t *testing.T) { assert.Equal(t, int(count), 3) } +func TestORM_CountTransactionsByState(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := configtest.NewGeneralConfig(t, nil) + txStore := cltest.NewTestTxStore(t, db, cfg.Database()) + ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() + + _, fromAddress1 := cltest.MustInsertRandomKey(t, ethKeyStore) + _, fromAddress2 := cltest.MustInsertRandomKey(t, ethKeyStore) + _, fromAddress3 := cltest.MustInsertRandomKey(t, ethKeyStore) + + cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 0, fromAddress1) + cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 1, fromAddress2) + cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 2, fromAddress3) + + count, err := txStore.CountTransactionsByState(testutils.Context(t), txmgrcommon.TxUnconfirmed, &cltest.FixtureChainID) + require.NoError(t, err) + assert.Equal(t, int(count), 3) +} + func TestORM_CountUnstartedTransactions(t *testing.T) { t.Parallel() @@ -1392,9 +1598,9 @@ func TestORM_CountUnstartedTransactions(t *testing.T) { _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) _, otherAddress := cltest.MustInsertRandomKey(t, ethKeyStore) - cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) - cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) - cltest.MustCreateUnstartedGeneratedTx(t, txStore, otherAddress, &cltest.FixtureChainID) + mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) + mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID) + mustCreateUnstartedGeneratedTx(t, txStore, otherAddress, &cltest.FixtureChainID) cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 2, fromAddress) count, err := txStore.CountUnstartedTransactions(testutils.Context(t), fromAddress, &cltest.FixtureChainID) @@ -1426,7 +1632,7 @@ func TestORM_CheckTxQueueCapacity(t *testing.T) { // deliberately one extra to exceed limit for i := 0; i <= int(maxUnconfirmedTransactions); i++ { - cltest.MustCreateUnstartedTx(t, txStore, otherAddress, toAddress, encodedPayload, feeLimit, value, &cltest.FixtureChainID) + mustCreateUnstartedTx(t, txStore, otherAddress, toAddress, encodedPayload, feeLimit, value, &cltest.FixtureChainID) } t.Run("with eth_txes from another address returns nil", func(t *testing.T) { @@ -1435,7 +1641,7 @@ func TestORM_CheckTxQueueCapacity(t *testing.T) { }) for i := 0; i <= int(maxUnconfirmedTransactions); i++ { - cltest.MustInsertFatalErrorEthTx(t, txStore, otherAddress) + mustInsertFatalErrorEthTx(t, txStore, otherAddress) } t.Run("ignores fatally_errored transactions", func(t *testing.T) { @@ -1444,7 +1650,7 @@ func TestORM_CheckTxQueueCapacity(t *testing.T) { }) var n int64 - cltest.MustInsertInProgressEthTxWithAttempt(t, txStore, evmtypes.Nonce(n), fromAddress) + mustInsertInProgressEthTxWithAttempt(t, txStore, evmtypes.Nonce(n), fromAddress) n++ cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, n, fromAddress) n++ @@ -1466,7 +1672,7 @@ func TestORM_CheckTxQueueCapacity(t *testing.T) { }) for i := 0; i < int(maxUnconfirmedTransactions)-1; i++ { - cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, feeLimit, value, &cltest.FixtureChainID) + mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, feeLimit, value, &cltest.FixtureChainID) } t.Run("with fewer unstarted eth_txes than limit returns nil", func(t *testing.T) { @@ -1474,14 +1680,14 @@ func TestORM_CheckTxQueueCapacity(t *testing.T) { require.NoError(t, err) }) - cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, feeLimit, value, &cltest.FixtureChainID) + mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, feeLimit, value, &cltest.FixtureChainID) t.Run("with equal or more unstarted eth_txes than limit returns error", func(t *testing.T) { err := txStore.CheckTxQueueCapacity(testutils.Context(t), fromAddress, maxUnconfirmedTransactions, &cltest.FixtureChainID) require.Error(t, err) require.Contains(t, err.Error(), fmt.Sprintf("cannot create transaction; too many unstarted transactions in the queue (2/%d). WARNING: Hitting EVM.Transactions.MaxQueued", maxUnconfirmedTransactions)) - cltest.MustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, feeLimit, value, &cltest.FixtureChainID) + mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, feeLimit, value, &cltest.FixtureChainID) err = txStore.CheckTxQueueCapacity(testutils.Context(t), fromAddress, maxUnconfirmedTransactions, &cltest.FixtureChainID) require.Error(t, err) require.Contains(t, err.Error(), fmt.Sprintf("cannot create transaction; too many unstarted transactions in the queue (3/%d). WARNING: Hitting EVM.Transactions.MaxQueued", maxUnconfirmedTransactions)) @@ -1503,7 +1709,7 @@ func TestORM_CreateTransaction(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - txStore := cltest.NewTxStore(t, db, cfg.Database()) + txStore := newTxStore(t, db, cfg.Database()) kst := cltest.NewKeyStore(t, db, cfg.Database()) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -1569,6 +1775,35 @@ func TestORM_CreateTransaction(t *testing.T) { assert.Equal(t, tx1.GetID(), tx2.GetID()) }) + + t.Run("sets signal callback flag", func(t *testing.T) { + subject := uuid.New() + strategy := newMockTxStrategy(t) + strategy.On("Subject").Return(uuid.NullUUID{UUID: subject, Valid: true}) + strategy.On("PruneQueue", mock.Anything, mock.AnythingOfType("*txmgr.evmTxStore")).Return(int64(0), nil) + etx, err := txStore.CreateTransaction(testutils.Context(t), txmgr.TxRequest{ + FromAddress: fromAddress, + ToAddress: toAddress, + EncodedPayload: payload, + FeeLimit: gasLimit, + Meta: nil, + Strategy: strategy, + SignalCallback: true, + }, ethClient.ConfiguredChainID()) + assert.NoError(t, err) + + assert.Greater(t, etx.ID, int64(0)) + assert.Equal(t, fromAddress, etx.FromAddress) + assert.Equal(t, true, etx.SignalCallback) + + cltest.AssertCount(t, db, "evm.txes", 3) + + var dbEthTx txmgr.DbEthTx + require.NoError(t, db.Get(&dbEthTx, `SELECT * FROM evm.txes ORDER BY id DESC LIMIT 1`)) + + assert.Equal(t, fromAddress, dbEthTx.FromAddress) + assert.Equal(t, true, dbEthTx.SignalCallback) + }) } func TestORM_PruneUnstartedTxQueue(t *testing.T) { @@ -1576,7 +1811,7 @@ func TestORM_PruneUnstartedTxQueue(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := newTestChainScopedConfig(t) - txStore := cltest.NewTxStore(t, db, cfg.Database()) + txStore := newTxStore(t, db, cfg.Database()) ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() evmtest.NewEthClientMockWithDefaultChain(t) _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) @@ -1585,7 +1820,7 @@ func TestORM_PruneUnstartedTxQueue(t *testing.T) { subject1 := uuid.New() strategy1 := txmgrcommon.NewDropOldestStrategy(subject1, uint32(5), cfg.Database().DefaultQueryTimeout()) for i := 0; i < 5; i++ { - cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, cltest.EvmTxRequestWithStrategy(strategy1)) + mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, txRequestWithStrategy(strategy1)) } testutils.AssertCountPerSubject(t, db, int64(5), subject1) }) @@ -1594,7 +1829,7 @@ func TestORM_PruneUnstartedTxQueue(t *testing.T) { subject2 := uuid.New() strategy2 := txmgrcommon.NewDropOldestStrategy(subject2, uint32(3), cfg.Database().DefaultQueryTimeout()) for i := 0; i < 5; i++ { - cltest.MustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, cltest.EvmTxRequestWithStrategy(strategy2)) + mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, &cltest.FixtureChainID, txRequestWithStrategy(strategy2)) } testutils.AssertCountPerSubject(t, db, int64(3), subject2) }) diff --git a/core/chains/evm/txmgr/mocks/config.go b/core/chains/evm/txmgr/mocks/config.go index d6f961ccb50..ab98d686c85 100644 --- a/core/chains/evm/txmgr/mocks/config.go +++ b/core/chains/evm/txmgr/mocks/config.go @@ -3,7 +3,7 @@ package mocks import ( - config "github.com/smartcontractkit/chainlink/v2/core/config" + config "github.com/smartcontractkit/chainlink/v2/common/config" mock "github.com/stretchr/testify/mock" ) diff --git a/core/chains/evm/txmgr/mocks/evm_tx_store.go b/core/chains/evm/txmgr/mocks/evm_tx_store.go index 69a0d257f7a..bf3088b2aa1 100644 --- a/core/chains/evm/txmgr/mocks/evm_tx_store.go +++ b/core/chains/evm/txmgr/mocks/evm_tx_store.go @@ -14,6 +14,8 @@ import ( mock "github.com/stretchr/testify/mock" + null "gopkg.in/guregu/null.v4" + time "time" types "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" @@ -59,6 +61,30 @@ func (_m *EvmTxStore) Close() { _m.Called() } +// CountTransactionsByState provides a mock function with given fields: ctx, state, chainID +func (_m *EvmTxStore) CountTransactionsByState(ctx context.Context, state types.TxState, chainID *big.Int) (uint32, error) { + ret := _m.Called(ctx, state, chainID) + + var r0 uint32 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, types.TxState, *big.Int) (uint32, error)); ok { + return rf(ctx, state, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, types.TxState, *big.Int) uint32); ok { + r0 = rf(ctx, state, chainID) + } else { + r0 = ret.Get(0).(uint32) + } + + if rf, ok := ret.Get(1).(func(context.Context, types.TxState, *big.Int) error); ok { + r1 = rf(ctx, state, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // CountUnconfirmedTransactions provides a mock function with given fields: ctx, fromAddress, chainID func (_m *EvmTxStore) CountUnconfirmedTransactions(ctx context.Context, fromAddress common.Address, chainID *big.Int) (uint32, error) { ret := _m.Called(ctx, fromAddress, chainID) @@ -145,6 +171,54 @@ func (_m *EvmTxStore) DeleteInProgressAttempt(ctx context.Context, attempt types return r0 } +// FindEarliestUnconfirmedBroadcastTime provides a mock function with given fields: ctx, chainID +func (_m *EvmTxStore) FindEarliestUnconfirmedBroadcastTime(ctx context.Context, chainID *big.Int) (null.Time, error) { + ret := _m.Called(ctx, chainID) + + var r0 null.Time + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (null.Time, error)); ok { + return rf(ctx, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) null.Time); ok { + r0 = rf(ctx, chainID) + } else { + r0 = ret.Get(0).(null.Time) + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindEarliestUnconfirmedTxAttemptBlock provides a mock function with given fields: ctx, chainID +func (_m *EvmTxStore) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context, chainID *big.Int) (null.Int, error) { + ret := _m.Called(ctx, chainID) + + var r0 null.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (null.Int, error)); ok { + return rf(ctx, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) null.Int); ok { + r0 = rf(ctx, chainID) + } else { + r0 = ret.Get(0).(null.Int) + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // FindLatestSequence provides a mock function with given fields: ctx, fromAddress, chainId func (_m *EvmTxStore) FindLatestSequence(ctx context.Context, fromAddress common.Address, chainId *big.Int) (evmtypes.Nonce, error) { ret := _m.Called(ctx, fromAddress, chainId) @@ -183,32 +257,6 @@ func (_m *EvmTxStore) FindNextUnstartedTransactionFromAddress(ctx context.Contex return r0 } -// FindReceiptsPendingConfirmation provides a mock function with given fields: ctx, blockNum, chainID -func (_m *EvmTxStore) FindReceiptsPendingConfirmation(ctx context.Context, blockNum int64, chainID *big.Int) ([]types.ReceiptPlus[*evmtypes.Receipt], error) { - ret := _m.Called(ctx, blockNum, chainID) - - var r0 []types.ReceiptPlus[*evmtypes.Receipt] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int64, *big.Int) ([]types.ReceiptPlus[*evmtypes.Receipt], error)); ok { - return rf(ctx, blockNum, chainID) - } - if rf, ok := ret.Get(0).(func(context.Context, int64, *big.Int) []types.ReceiptPlus[*evmtypes.Receipt]); ok { - r0 = rf(ctx, blockNum, chainID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.ReceiptPlus[*evmtypes.Receipt]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, int64, *big.Int) error); ok { - r1 = rf(ctx, blockNum, chainID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // FindTransactionsConfirmedInBlockRange provides a mock function with given fields: ctx, highBlockNumber, lowBlockNumber, chainID func (_m *EvmTxStore) FindTransactionsConfirmedInBlockRange(ctx context.Context, highBlockNumber int64, lowBlockNumber int64, chainID *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { ret := _m.Called(ctx, highBlockNumber, lowBlockNumber, chainID) @@ -467,6 +515,136 @@ func (_m *EvmTxStore) FindTxWithSequence(ctx context.Context, fromAddress common return r0, r1 } +// FindTxesByMetaFieldAndStates provides a mock function with given fields: ctx, metaField, metaValue, states, chainID +func (_m *EvmTxStore) FindTxesByMetaFieldAndStates(ctx context.Context, metaField string, metaValue string, states []types.TxState, chainID *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { + ret := _m.Called(ctx, metaField, metaValue, states, chainID) + + var r0 []*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []types.TxState, *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error)); ok { + return rf(ctx, metaField, metaValue, states, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, []types.TxState, *big.Int) []*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]); ok { + r0 = rf(ctx, metaField, metaValue, states, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, []types.TxState, *big.Int) error); ok { + r1 = rf(ctx, metaField, metaValue, states, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesPendingCallback provides a mock function with given fields: ctx, blockNum, chainID +func (_m *EvmTxStore) FindTxesPendingCallback(ctx context.Context, blockNum int64, chainID *big.Int) ([]types.ReceiptPlus[*evmtypes.Receipt], error) { + ret := _m.Called(ctx, blockNum, chainID) + + var r0 []types.ReceiptPlus[*evmtypes.Receipt] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, *big.Int) ([]types.ReceiptPlus[*evmtypes.Receipt], error)); ok { + return rf(ctx, blockNum, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, *big.Int) []types.ReceiptPlus[*evmtypes.Receipt]); ok { + r0 = rf(ctx, blockNum, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.ReceiptPlus[*evmtypes.Receipt]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, *big.Int) error); ok { + r1 = rf(ctx, blockNum, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesWithAttemptsAndReceiptsByIdsAndState provides a mock function with given fields: ctx, ids, states, chainID +func (_m *EvmTxStore) FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx context.Context, ids []big.Int, states []types.TxState, chainID *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { + ret := _m.Called(ctx, ids, states, chainID) + + var r0 []*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []big.Int, []types.TxState, *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error)); ok { + return rf(ctx, ids, states, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, []big.Int, []types.TxState, *big.Int) []*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]); ok { + r0 = rf(ctx, ids, states, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []big.Int, []types.TxState, *big.Int) error); ok { + r1 = rf(ctx, ids, states, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesWithMetaFieldByReceiptBlockNum provides a mock function with given fields: ctx, metaField, blockNum, chainID +func (_m *EvmTxStore) FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { + ret := _m.Called(ctx, metaField, blockNum, chainID) + + var r0 []*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error)); ok { + return rf(ctx, metaField, blockNum, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *big.Int) []*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]); ok { + r0 = rf(ctx, metaField, blockNum, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, int64, *big.Int) error); ok { + r1 = rf(ctx, metaField, blockNum, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTxesWithMetaFieldByStates provides a mock function with given fields: ctx, metaField, states, chainID +func (_m *EvmTxStore) FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []types.TxState, chainID *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { + ret := _m.Called(ctx, metaField, states, chainID) + + var r0 []*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []types.TxState, *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error)); ok { + return rf(ctx, metaField, states, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []types.TxState, *big.Int) []*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]); ok { + r0 = rf(ctx, metaField, states, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []types.TxState, *big.Int) error); ok { + r1 = rf(ctx, metaField, states, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // FindTxsRequiringGasBump provides a mock function with given fields: ctx, address, blockNum, gasBumpThreshold, depth, chainID func (_m *EvmTxStore) FindTxsRequiringGasBump(ctx context.Context, address common.Address, blockNum int64, gasBumpThreshold int64, depth int64, chainID *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { ret := _m.Called(ctx, address, blockNum, gasBumpThreshold, depth, chainID) @@ -545,6 +723,58 @@ func (_m *EvmTxStore) GetInProgressTxAttempts(ctx context.Context, address commo return r0, r1 } +// GetNonFatalTransactions provides a mock function with given fields: ctx, chainID +func (_m *EvmTxStore) GetNonFatalTransactions(ctx context.Context, chainID *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { + ret := _m.Called(ctx, chainID) + + var r0 []*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) ([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error)); ok { + return rf(ctx, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) []*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]); ok { + r0 = rf(ctx, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTxByID provides a mock function with given fields: ctx, id +func (_m *EvmTxStore) GetTxByID(ctx context.Context, id int64) (*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { + ret := _m.Called(ctx, id) + + var r0 *types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) (*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) *types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetTxInProgress provides a mock function with given fields: ctx, fromAddress func (_m *EvmTxStore) GetTxInProgress(ctx context.Context, fromAddress common.Address) (*types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { ret := _m.Called(ctx, fromAddress) @@ -595,6 +825,30 @@ func (_m *EvmTxStore) HasInProgressTransaction(ctx context.Context, account comm return r0, r1 } +// IsTxFinalized provides a mock function with given fields: ctx, blockHeight, txID, chainID +func (_m *EvmTxStore) IsTxFinalized(ctx context.Context, blockHeight int64, txID int64, chainID *big.Int) (bool, error) { + ret := _m.Called(ctx, blockHeight, txID, chainID) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, int64, *big.Int) (bool, error)); ok { + return rf(ctx, blockHeight, txID, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, int64, *big.Int) bool); ok { + r0 = rf(ctx, blockHeight, txID, chainID) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, int64, *big.Int) error); ok { + r1 = rf(ctx, blockHeight, txID, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // LoadTxAttempts provides a mock function with given fields: ctx, etx func (_m *EvmTxStore) LoadTxAttempts(ctx context.Context, etx *types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) error { ret := _m.Called(ctx, etx) @@ -914,6 +1168,20 @@ func (_m *EvmTxStore) UpdateTxAttemptInProgressToBroadcast(ctx context.Context, return r0 } +// UpdateTxCallbackCompleted provides a mock function with given fields: ctx, pipelineTaskRunRid, chainId +func (_m *EvmTxStore) UpdateTxCallbackCompleted(ctx context.Context, pipelineTaskRunRid uuid.UUID, chainId *big.Int) error { + ret := _m.Called(ctx, pipelineTaskRunRid, chainId) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID, *big.Int) error); ok { + r0 = rf(ctx, pipelineTaskRunRid, chainId) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // UpdateTxFatalError provides a mock function with given fields: ctx, etx func (_m *EvmTxStore) UpdateTxFatalError(ctx context.Context, etx *types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) error { ret := _m.Called(ctx, etx) diff --git a/core/chains/evm/txmgr/models.go b/core/chains/evm/txmgr/models.go index 9044c52c9ae..4c622ec945a 100644 --- a/core/chains/evm/txmgr/models.go +++ b/core/chains/evm/txmgr/models.go @@ -19,7 +19,8 @@ import ( type ( Confirmer = txmgr.Confirmer[*big.Int, *evmtypes.Head, common.Address, common.Hash, common.Hash, *evmtypes.Receipt, evmtypes.Nonce, gas.EvmFee] Broadcaster = txmgr.Broadcaster[*big.Int, *evmtypes.Head, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] - Resender = txmgr.Resender[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] + Resender = txmgr.Resender[*big.Int, common.Address, common.Hash, common.Hash, *evmtypes.Receipt, evmtypes.Nonce, gas.EvmFee] + Tracker = txmgr.Tracker[*big.Int, common.Address, common.Hash, common.Hash, *evmtypes.Receipt, evmtypes.Nonce, gas.EvmFee] Reaper = txmgr.Reaper[*big.Int] TxStore = txmgrtypes.TxStore[common.Address, *big.Int, common.Hash, common.Hash, *evmtypes.Receipt, evmtypes.Nonce, gas.EvmFee] TransactionStore = txmgrtypes.TransactionStore[common.Address, *big.Int, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] diff --git a/core/chains/evm/txmgr/nonce_syncer.go b/core/chains/evm/txmgr/nonce_syncer.go index dc0d27e6414..2936736b3b3 100644 --- a/core/chains/evm/txmgr/nonce_syncer.go +++ b/core/chains/evm/txmgr/nonce_syncer.go @@ -8,10 +8,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/common/txmgr" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) // NonceSyncer manages the delicate task of syncing the local nonce with the @@ -61,7 +61,7 @@ func NewNonceSyncer( lggr logger.Logger, ethClient evmclient.Client, ) NonceSyncer { - lggr = lggr.Named("NonceSyncer") + lggr = logger.Named(lggr, "NonceSyncer") return &nonceSyncerImpl{ txStore: txStore, client: NewEvmTxmClient(ethClient), diff --git a/core/chains/evm/txmgr/nonce_syncer_test.go b/core/chains/evm/txmgr/nonce_syncer_test.go index 13e5fd02e8c..f757b8863d1 100644 --- a/core/chains/evm/txmgr/nonce_syncer_test.go +++ b/core/chains/evm/txmgr/nonce_syncer_test.go @@ -3,14 +3,14 @@ package txmgr_test import ( "testing" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -30,7 +30,7 @@ func Test_NonceSyncer_Sync(t *testing.T) { _, from := cltest.MustInsertRandomKey(t, ethKeyStore) - ns := txmgr.NewNonceSyncer(txStore, logger.TestLogger(t), ethClient) + ns := txmgr.NewNonceSyncer(txStore, logger.Test(t), ethClient) ethClient.On("PendingNonceAt", mock.Anything, from).Return(uint64(0), errors.New("something exploded")) _, err := ns.Sync(testutils.Context(t), from, types.Nonce(0)) @@ -50,7 +50,7 @@ func Test_NonceSyncer_Sync(t *testing.T) { _, from := cltest.MustInsertRandomKey(t, ethKeyStore) - ns := txmgr.NewNonceSyncer(txStore, logger.TestLogger(t), ethClient) + ns := txmgr.NewNonceSyncer(txStore, logger.Test(t), ethClient) ethClient.On("PendingNonceAt", mock.Anything, from).Return(uint64(0), nil) @@ -72,7 +72,7 @@ func Test_NonceSyncer_Sync(t *testing.T) { _, fromAddress := cltest.RandomKey{Nonce: 32}.MustInsert(t, ks) - ns := txmgr.NewNonceSyncer(txStore, logger.TestLogger(t), ethClient) + ns := txmgr.NewNonceSyncer(txStore, logger.Test(t), ethClient) // Used to mock the chain nonce ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(5), nil) @@ -97,7 +97,7 @@ func Test_NonceSyncer_Sync(t *testing.T) { key1LocalNonce := types.Nonce(0) key2LocalNonce := types.Nonce(32) - ns := txmgr.NewNonceSyncer(txStore, logger.TestLogger(t), ethClient) + ns := txmgr.NewNonceSyncer(txStore, logger.Test(t), ethClient) // Used to mock the chain nonce ethClient.On("PendingNonceAt", mock.Anything, key1).Return(uint64(5), nil).Once() diff --git a/core/chains/evm/txmgr/reaper_test.go b/core/chains/evm/txmgr/reaper_test.go index 830ed1ac17f..2da8e7a93c8 100644 --- a/core/chains/evm/txmgr/reaper_test.go +++ b/core/chains/evm/txmgr/reaper_test.go @@ -8,18 +8,18 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" txmgrmocks "github.com/smartcontractkit/chainlink/v2/common/txmgr/types/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) func newReaperWithChainID(t *testing.T, db txmgrtypes.TxHistoryReaper[*big.Int], cfg txmgrtypes.ReaperChainConfig, txConfig txmgrtypes.ReaperTransactionsConfig, cid *big.Int) *txmgr.Reaper { - return txmgr.NewEvmReaper(logger.TestLogger(t), db, cfg, txConfig, cid) + return txmgr.NewEvmReaper(logger.Test(t), db, cfg, txConfig, cid) } func newReaper(t *testing.T, db txmgrtypes.TxHistoryReaper[*big.Int], cfg txmgrtypes.ReaperChainConfig, txConfig txmgrtypes.ReaperTransactionsConfig) *txmgr.Reaper { @@ -64,7 +64,7 @@ func TestReaper_ReapTxes(t *testing.T) { }) // Confirmed in block number 5 - cltest.MustInsertConfirmedEthTxWithReceipt(t, txStore, from, nonce, 5) + mustInsertConfirmedEthTxWithReceipt(t, txStore, from, nonce, 5) t.Run("skips if threshold=0", func(t *testing.T) { config := txmgrmocks.NewReaperConfig(t) @@ -119,7 +119,7 @@ func TestReaper_ReapTxes(t *testing.T) { cltest.AssertCount(t, db, "evm.txes", 0) }) - cltest.MustInsertFatalErrorEthTx(t, txStore, from) + mustInsertFatalErrorEthTx(t, txStore, from) t.Run("deletes errored evm.txes that exceed the age threshold", func(t *testing.T) { config := txmgrmocks.NewReaperConfig(t) diff --git a/core/chains/evm/txmgr/resender_test.go b/core/chains/evm/txmgr/resender_test.go index d17156f4525..0e86c0d4f8c 100644 --- a/core/chains/evm/txmgr/resender_test.go +++ b/core/chains/evm/txmgr/resender_test.go @@ -13,14 +13,14 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -31,7 +31,7 @@ func Test_EthResender_resendUnconfirmed(t *testing.T) { db := pgtest.NewSqlxDB(t) logCfg := pgtest.NewQConfig(true) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) ethKeyStore := cltest.NewKeyStore(t, db, logCfg).Eth() ethClient := evmtest.NewEthClientMockWithDefaultChain(t) cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) {}) @@ -65,7 +65,7 @@ func Test_EthResender_resendUnconfirmed(t *testing.T) { addr3TxesRawHex = append(addr3TxesRawHex, hexutil.Encode(etx.TxAttempts[0].SignedRawTx)) } - er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) + er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) var resentHex = make(map[string]struct{}) ethClient.On("BatchCallContextAll", mock.Anything, mock.MatchedBy(func(elems []rpc.BatchElem) bool { @@ -102,7 +102,7 @@ func Test_EthResender_alertUnconfirmed(t *testing.T) { db := pgtest.NewSqlxDB(t) logCfg := pgtest.NewQConfig(true) - lggr, o := logger.TestLoggerObserved(t, zapcore.DebugLevel) + lggr, o := logger.TestObserved(t, zapcore.DebugLevel) ethKeyStore := cltest.NewKeyStore(t, db, logCfg).Eth() ethClient := evmtest.NewEthClientMockWithDefaultChain(t) // Set this to the smallest non-zero value possible for the attempt to be eligible for resend @@ -121,7 +121,7 @@ func Test_EthResender_alertUnconfirmed(t *testing.T) { txStore := cltest.NewTestTxStore(t, db, logCfg) originalBroadcastAt := time.Unix(1616509100, 0) - er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) + er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) t.Run("alerts only once for unconfirmed transaction attempt within the unconfirmedTxAlertDelay duration", func(t *testing.T) { _ = cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, int64(1), fromAddress, originalBroadcastAt) @@ -152,12 +152,12 @@ func Test_EthResender_Start(t *testing.T) { ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() ccfg := evmtest.NewChainScopedConfig(t, cfg) _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) t.Run("resends transactions that have been languishing unconfirmed for too long", func(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) + er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) originalBroadcastAt := time.Unix(1616509100, 0) etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 0, fromAddress, originalBroadcastAt) diff --git a/core/chains/evm/txmgr/strategies_test.go b/core/chains/evm/txmgr/strategies_test.go index 9c5a1be37f1..765b43e78f2 100644 --- a/core/chains/evm/txmgr/strategies_test.go +++ b/core/chains/evm/txmgr/strategies_test.go @@ -11,7 +11,7 @@ import ( txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" ) func Test_SendEveryStrategy(t *testing.T) { diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go new file mode 100644 index 00000000000..e279dddf5fa --- /dev/null +++ b/core/chains/evm/txmgr/test_helpers.go @@ -0,0 +1,152 @@ +package txmgr + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + + commonconfig "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" + + evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" +) + +func ptr[T any](t T) *T { return &t } + +type TestDatabaseConfig struct { + config.Database + defaultQueryTimeout time.Duration +} + +func (d *TestDatabaseConfig) DefaultQueryTimeout() time.Duration { + return d.defaultQueryTimeout +} + +func (d *TestDatabaseConfig) LogSQL() bool { + return false +} + +type TestListenerConfig struct { + config.Listener +} + +func (l *TestListenerConfig) FallbackPollInterval() time.Duration { + return 1 * time.Minute +} + +func (d *TestDatabaseConfig) Listener() config.Listener { + return &TestListenerConfig{} +} + +type TestEvmConfig struct { + evmconfig.EVM + MaxInFlight uint32 + ReaperInterval time.Duration + ReaperThreshold time.Duration + ResendAfterThreshold time.Duration + BumpThreshold uint64 + MaxQueued uint64 +} + +func (e *TestEvmConfig) Transactions() evmconfig.Transactions { + return &transactionsConfig{e: e} +} + +func (e *TestEvmConfig) NonceAutoSync() bool { return true } + +func (e *TestEvmConfig) FinalityDepth() uint32 { return 42 } + +type TestGasEstimatorConfig struct { + bumpThreshold uint64 +} + +func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { + return &TestBlockHistoryConfig{} +} + +func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false } +func (g *TestGasEstimatorConfig) LimitDefault() uint32 { return 42 } +func (g *TestGasEstimatorConfig) BumpPercent() uint16 { return 42 } +func (g *TestGasEstimatorConfig) BumpThreshold() uint64 { return g.bumpThreshold } +func (g *TestGasEstimatorConfig) BumpMin() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) FeeCapDefault() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) PriceDefault() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) TipCapDefault() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) TipCapMin() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) LimitMax() uint32 { return 0 } +func (g *TestGasEstimatorConfig) LimitMultiplier() float32 { return 0 } +func (g *TestGasEstimatorConfig) BumpTxDepth() uint32 { return 42 } +func (g *TestGasEstimatorConfig) LimitTransfer() uint32 { return 42 } +func (g *TestGasEstimatorConfig) PriceMax() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) PriceMin() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) Mode() string { return "FixedPrice" } +func (g *TestGasEstimatorConfig) LimitJobType() evmconfig.LimitJobType { + return &TestLimitJobTypeConfig{} +} +func (g *TestGasEstimatorConfig) PriceMaxKey(addr common.Address) *assets.Wei { + return assets.NewWeiI(42) +} + +func (e *TestEvmConfig) GasEstimator() evmconfig.GasEstimator { + return &TestGasEstimatorConfig{bumpThreshold: e.BumpThreshold} +} + +type TestLimitJobTypeConfig struct { +} + +func (l *TestLimitJobTypeConfig) OCR() *uint32 { return ptr(uint32(0)) } +func (l *TestLimitJobTypeConfig) OCR2() *uint32 { return ptr(uint32(0)) } +func (l *TestLimitJobTypeConfig) DR() *uint32 { return ptr(uint32(0)) } +func (l *TestLimitJobTypeConfig) FM() *uint32 { return ptr(uint32(0)) } +func (l *TestLimitJobTypeConfig) Keeper() *uint32 { return ptr(uint32(0)) } +func (l *TestLimitJobTypeConfig) VRF() *uint32 { return ptr(uint32(0)) } + +type TestBlockHistoryConfig struct { + evmconfig.BlockHistory +} + +func (b *TestBlockHistoryConfig) BatchSize() uint32 { return 42 } +func (b *TestBlockHistoryConfig) BlockDelay() uint16 { return 42 } +func (b *TestBlockHistoryConfig) BlockHistorySize() uint16 { return 42 } +func (b *TestBlockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 } +func (b *TestBlockHistoryConfig) TransactionPercentile() uint16 { return 42 } + +type transactionsConfig struct { + evmconfig.Transactions + e *TestEvmConfig +} + +func (*transactionsConfig) ForwardersEnabled() bool { return true } +func (t *transactionsConfig) MaxInFlight() uint32 { return t.e.MaxInFlight } +func (t *transactionsConfig) MaxQueued() uint64 { return t.e.MaxQueued } +func (t *transactionsConfig) ReaperInterval() time.Duration { return t.e.ReaperInterval } +func (t *transactionsConfig) ReaperThreshold() time.Duration { return t.e.ReaperThreshold } +func (t *transactionsConfig) ResendAfterThreshold() time.Duration { return t.e.ResendAfterThreshold } + +type MockConfig struct { + EvmConfig *TestEvmConfig + RpcDefaultBatchSize uint32 + finalityDepth uint32 + finalityTagEnabled bool +} + +func (c *MockConfig) EVM() evmconfig.EVM { + return c.EvmConfig +} + +func (c *MockConfig) NonceAutoSync() bool { return true } +func (c *MockConfig) ChainType() commonconfig.ChainType { return "" } +func (c *MockConfig) FinalityDepth() uint32 { return c.finalityDepth } +func (c *MockConfig) SetFinalityDepth(fd uint32) { c.finalityDepth = fd } +func (c *MockConfig) FinalityTagEnabled() bool { return c.finalityTagEnabled } +func (c *MockConfig) RPCDefaultBatchSize() uint32 { return c.RpcDefaultBatchSize } + +func MakeTestConfigs(t *testing.T) (*MockConfig, *TestDatabaseConfig, *TestEvmConfig) { + db := &TestDatabaseConfig{defaultQueryTimeout: pg.DefaultQueryTimeout} + ec := &TestEvmConfig{BumpThreshold: 42, MaxInFlight: uint32(42), MaxQueued: uint64(0), ReaperInterval: time.Duration(0), ReaperThreshold: time.Duration(0)} + config := &MockConfig{EvmConfig: ec} + return config, db, ec +} diff --git a/core/chains/evm/txmgr/tracker_test.go b/core/chains/evm/txmgr/tracker_test.go new file mode 100644 index 00000000000..af41aaeffa5 --- /dev/null +++ b/core/chains/evm/txmgr/tracker_test.go @@ -0,0 +1,165 @@ +package txmgr_test + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/utils" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +const waitTime = 5 * time.Millisecond + +func newTestEvmTrackerSetup(t *testing.T) (*txmgr.Tracker, txmgr.TestEvmTxStore, keystore.Eth, []common.Address) { + db := pgtest.NewSqlxDB(t) + cfg := newTestChainScopedConfig(t) + txStore := cltest.NewTestTxStore(t, db, cfg.Database()) + ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() + chainID := big.NewInt(0) + enabledAddresses := generateEnabledAddresses(t, ethKeyStore, chainID) + lggr := logger.TestLogger(t) + return txmgr.NewEvmTracker(txStore, ethKeyStore, chainID, lggr), txStore, ethKeyStore, enabledAddresses +} + +func generateEnabledAddresses(t *testing.T, keyStore keystore.Eth, chainID *big.Int) []common.Address { + var enabledAddresses []common.Address + _, addr1 := cltest.MustInsertRandomKey(t, keyStore, *utils.NewBigI(chainID.Int64())) + _, addr2 := cltest.MustInsertRandomKey(t, keyStore, *utils.NewBigI(chainID.Int64())) + enabledAddresses = append(enabledAddresses, addr1, addr2) + return enabledAddresses +} + +func containsID(txes []*txmgr.Tx, id int64) bool { + for _, tx := range txes { + if tx.ID == id { + return true + } + } + return false +} + +func TestEvmTracker_Initialization(t *testing.T) { + t.Skip("BCI-2638 tracker disabled") + t.Parallel() + + tracker, _, _, _ := newTestEvmTrackerSetup(t) + + err := tracker.Start(context.Background()) + require.NoError(t, err) + require.True(t, tracker.IsStarted()) + + t.Run("stop tracker", func(t *testing.T) { + err := tracker.Close() + require.NoError(t, err) + require.False(t, tracker.IsStarted()) + }) +} + +func TestEvmTracker_AddressTracking(t *testing.T) { + t.Skip("BCI-2638 tracker disabled") + t.Parallel() + + t.Run("track abandoned addresses", func(t *testing.T) { + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + tracker, txStore, _, _ := newTestEvmTrackerSetup(t) + inProgressAddr := cltest.MustGenerateRandomKey(t).Address + unstartedAddr := cltest.MustGenerateRandomKey(t).Address + unconfirmedAddr := cltest.MustGenerateRandomKey(t).Address + confirmedAddr := cltest.MustGenerateRandomKey(t).Address + _ = mustInsertInProgressEthTxWithAttempt(t, txStore, 123, inProgressAddr) + _ = cltest.MustInsertUnconfirmedEthTx(t, txStore, 123, unconfirmedAddr) + _ = mustInsertConfirmedEthTxWithReceipt(t, txStore, confirmedAddr, 123, 1) + _ = mustCreateUnstartedTx(t, txStore, unstartedAddr, cltest.MustGenerateRandomKey(t).Address, []byte{}, 0, big.Int{}, ethClient.ConfiguredChainID()) + + err := tracker.Start(context.Background()) + require.NoError(t, err) + defer func(tracker *txmgr.Tracker) { + err = tracker.Close() + require.NoError(t, err) + }(tracker) + + addrs := tracker.GetAbandonedAddresses() + require.NotContains(t, addrs, inProgressAddr) + require.NotContains(t, addrs, unstartedAddr) + require.Contains(t, addrs, confirmedAddr) + require.Contains(t, addrs, unconfirmedAddr) + }) + + t.Run("stop tracking finalized tx", func(t *testing.T) { + t.Skip("BCI-2638 tracker disabled") + tracker, txStore, _, _ := newTestEvmTrackerSetup(t) + confirmedAddr := cltest.MustGenerateRandomKey(t).Address + _ = mustInsertConfirmedEthTxWithReceipt(t, txStore, confirmedAddr, 123, 1) + + err := tracker.Start(context.Background()) + require.NoError(t, err) + defer func(tracker *txmgr.Tracker) { + err = tracker.Close() + require.NoError(t, err) + }(tracker) + + addrs := tracker.GetAbandonedAddresses() + require.Contains(t, addrs, confirmedAddr) + + // deliver block past minConfirmations to finalize tx + tracker.XXXDeliverBlock(10) + time.Sleep(waitTime) + + addrs = tracker.GetAbandonedAddresses() + require.NotContains(t, addrs, confirmedAddr) + }) +} + +func TestEvmTracker_ExceedingTTL(t *testing.T) { + t.Skip("BCI-2638 tracker disabled") + t.Parallel() + + t.Run("confirmed but unfinalized transaction still tracked", func(t *testing.T) { + tracker, txStore, _, _ := newTestEvmTrackerSetup(t) + addr1 := cltest.MustGenerateRandomKey(t).Address + _ = mustInsertConfirmedEthTxWithReceipt(t, txStore, addr1, 123, 1) + + err := tracker.Start(context.Background()) + require.NoError(t, err) + defer func(tracker *txmgr.Tracker) { + err = tracker.Close() + require.NoError(t, err) + }(tracker) + + require.Contains(t, tracker.GetAbandonedAddresses(), addr1) + }) + + t.Run("exceeding ttl", func(t *testing.T) { + tracker, txStore, _, _ := newTestEvmTrackerSetup(t) + addr1 := cltest.MustGenerateRandomKey(t).Address + addr2 := cltest.MustGenerateRandomKey(t).Address + tx1 := mustInsertInProgressEthTxWithAttempt(t, txStore, 123, addr1) + tx2 := cltest.MustInsertUnconfirmedEthTx(t, txStore, 123, addr2) + + tracker.XXXTestSetTTL(time.Nanosecond) + err := tracker.Start(context.Background()) + require.NoError(t, err) + defer func(tracker *txmgr.Tracker) { + err = tracker.Close() + require.NoError(t, err) + }(tracker) + + time.Sleep(waitTime) + require.NotContains(t, tracker.GetAbandonedAddresses(), addr1, addr2) + + fatalTxes, err := txStore.GetFatalTransactions(context.Background()) + require.NoError(t, err) + require.True(t, containsID(fatalTxes, tx1.ID)) + require.True(t, containsID(fatalTxes, tx2.ID)) + }) +} diff --git a/core/chains/evm/txmgr/transmitchecker.go b/core/chains/evm/txmgr/transmitchecker.go index 4636b708489..76dfcb9d51c 100644 --- a/core/chains/evm/txmgr/transmitchecker.go +++ b/core/chains/evm/txmgr/transmitchecker.go @@ -11,6 +11,9 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" + "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" @@ -20,9 +23,7 @@ import ( v1 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_coordinator_interface" v2 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2plus_interface" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" - bigmath "github.com/smartcontractkit/chainlink/v2/core/utils/big_math" ) type ( @@ -147,7 +148,7 @@ func (s *SimulateChecker) Check( err := s.Client.CallContext(ctx, &b, "eth_call", callArg, evmclient.ToBlockNumArg(nil)) if err != nil { if jErr := evmclient.ExtractRPCErrorOrNil(err); jErr != nil { - l.Criticalw("Transaction reverted during simulation", + logger.Criticalw(l, "Transaction reverted during simulation", "ethTxAttemptID", a.ID, "txHash", a.Hash, "err", err, "rpcErr", jErr.String(), "returnValue", b.String()) return errors.Errorf("transaction reverted during simulation: %s", jErr.String()) } @@ -217,7 +218,7 @@ func (v *VRFV1Checker) Check( requestTransactionReceipt := &gethtypes.Receipt{} batch := []rpc.BatchElem{{ Method: "eth_getBlockByNumber", - Args: []interface{}{nil}, + Args: []interface{}{"latest", false}, Result: mostRecentHead, }, { Method: "eth_getTransactionReceipt", diff --git a/core/chains/evm/txmgr/transmitchecker_test.go b/core/chains/evm/txmgr/transmitchecker_test.go index 79868e6a641..6dd4edd91c6 100644 --- a/core/chains/evm/txmgr/transmitchecker_test.go +++ b/core/chains/evm/txmgr/transmitchecker_test.go @@ -15,21 +15,22 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/mock" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" v1 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/pg/datatypes" ) func TestFactory(t *testing.T) { @@ -105,7 +106,7 @@ func TestFactory(t *testing.T) { func TestTransmitCheckers(t *testing.T) { client := evmtest.NewEthClientMockWithDefaultChain(t) - log := logger.TestLogger(t) + log := logger.Test(t) ctx := testutils.Context(t) t.Run("no checker", func(t *testing.T) { @@ -192,7 +193,7 @@ func TestTransmitCheckers(t *testing.T) { b, err := json.Marshal(meta) require.NoError(t, err) - metaJson := datatypes.JSON(b) + metaJson := sqlutil.JSON(b) tx := txmgr.Tx{ FromAddress: common.HexToAddress("0xfe0629509E6CB8dfa7a99214ae58Ceb465d5b5A9"), @@ -296,7 +297,7 @@ func TestTransmitCheckers(t *testing.T) { b, err := json.Marshal(meta) require.NoError(t, err) - metaJson := datatypes.JSON(b) + metaJson := sqlutil.JSON(b) tx := txmgr.Tx{ FromAddress: common.HexToAddress("0xfe0629509E6CB8dfa7a99214ae58Ceb465d5b5A9"), diff --git a/core/chains/evm/txmgr/txmgr_test.go b/core/chains/evm/txmgr/txmgr_test.go index de8c6ff4ef8..745623ed77e 100644 --- a/core/chains/evm/txmgr/txmgr_test.go +++ b/core/chains/evm/txmgr/txmgr_test.go @@ -1,6 +1,7 @@ package txmgr_test import ( + "bytes" "context" "encoding/json" "fmt" @@ -9,41 +10,42 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink-common/pkg/logger" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" commontxmmocks "github.com/smartcontractkit/chainlink/v2/common/txmgr/types/mocks" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/config" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" ksmocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/pg" - pgmocks "github.com/smartcontractkit/chainlink/v2/core/services/pg/mocks" "github.com/smartcontractkit/chainlink/v2/core/utils" ) func makeTestEvmTxm( - t *testing.T, db *sqlx.DB, ethClient evmclient.Client, estimator gas.EvmFeeEstimator, ccfg txmgr.ChainConfig, fcfg txmgr.FeeConfig, txConfig evmconfig.Transactions, dbConfig txmgr.DatabaseConfig, listenerConfig txmgr.ListenerConfig, keyStore keystore.Eth, eventBroadcaster pg.EventBroadcaster) (txmgr.TxManager, error) { - lggr := logger.TestLogger(t) + t *testing.T, db *sqlx.DB, ethClient evmclient.Client, estimator gas.EvmFeeEstimator, ccfg txmgr.ChainConfig, fcfg txmgr.FeeConfig, txConfig evmconfig.Transactions, dbConfig txmgr.DatabaseConfig, listenerConfig txmgr.ListenerConfig, keyStore keystore.Eth) (txmgr.TxManager, error) { + lggr := logger.Test(t) lp := logpoller.NewLogPoller(logpoller.NewORM(testutils.FixtureChainID, db, lggr, pgtest.NewQConfig(true)), ethClient, lggr, 100*time.Millisecond, false, 2, 3, 2, 1000) // logic for building components (from evm/evm_txm.go) ------- @@ -66,7 +68,6 @@ func makeTestEvmTxm( lggr, lp, keyStore, - eventBroadcaster, estimator) } @@ -78,12 +79,12 @@ func TestTxm_SendNativeToken_DoesNotSendToZero(t *testing.T) { to := utils.ZeroAddress value := assets.NewEth(1).ToInt() - config, dbConfig, evmConfig := makeConfigs(t) + config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) keyStore := cltest.NewKeyStore(t, db, dbConfig).Eth() ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - estimator := gas.NewEstimator(logger.TestLogger(t), ethClient, config, evmConfig.GasEstimator()) - txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), keyStore, nil) + estimator := gas.NewEstimator(logger.Test(t), ethClient, config, evmConfig.GasEstimator()) + txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), keyStore) require.NoError(t, err) _, err = txm.SendNativeToken(testutils.Context(t), big.NewInt(0), from, to, *value, 21000) @@ -104,12 +105,12 @@ func TestTxm_CreateTransaction(t *testing.T) { gasLimit := uint32(1000) payload := []byte{1, 2, 3} - config, dbConfig, evmConfig := makeConfigs(t) + config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - estimator := gas.NewEstimator(logger.TestLogger(t), ethClient, config, evmConfig.GasEstimator()) - txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), kst.Eth(), nil) + estimator := gas.NewEstimator(logger.Test(t), ethClient, config, evmConfig.GasEstimator()) + txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), kst.Eth()) require.NoError(t, err) t.Run("with queue under capacity inserts eth_tx", func(t *testing.T) { @@ -117,7 +118,7 @@ func TestTxm_CreateTransaction(t *testing.T) { strategy := newMockTxStrategy(t) strategy.On("Subject").Return(uuid.NullUUID{UUID: subject, Valid: true}) strategy.On("PruneQueue", mock.Anything, mock.Anything).Return(int64(0), nil) - evmConfig.maxQueued = uint64(1) + evmConfig.MaxQueued = uint64(1) etx, err := txm.CreateTransaction(testutils.Context(t), txmgr.TxRequest{ FromAddress: fromAddress, ToAddress: toAddress, @@ -150,10 +151,10 @@ func TestTxm_CreateTransaction(t *testing.T) { assert.Equal(t, subject, etx.Subject.UUID) }) - cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 0, fromAddress) + mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 0, fromAddress) t.Run("with queue at capacity does not insert eth_tx", func(t *testing.T) { - evmConfig.maxQueued = uint64(1) + evmConfig.MaxQueued = uint64(1) _, err := txm.CreateTransaction(testutils.Context(t), txmgr.TxRequest{ FromAddress: fromAddress, ToAddress: testutils.NewAddress(), @@ -167,7 +168,7 @@ func TestTxm_CreateTransaction(t *testing.T) { }) t.Run("doesn't insert eth_tx if a matching tx already exists for that pipeline_task_run_id", func(t *testing.T) { - evmConfig.maxQueued = uint64(3) + evmConfig.MaxQueued = uint64(3) id := uuid.New() tx1, err := txm.CreateTransaction(testutils.Context(t), txmgr.TxRequest{ FromAddress: fromAddress, @@ -223,7 +224,7 @@ func TestTxm_CreateTransaction(t *testing.T) { checker := txmgr.TransmitCheckerSpec{ CheckerType: txmgr.TransmitCheckerTypeSimulate, } - evmConfig.maxQueued = uint64(1) + evmConfig.MaxQueued = uint64(1) etx, err := txm.CreateTransaction(testutils.Context(t), txmgr.TxRequest{ FromAddress: fromAddress, ToAddress: toAddress, @@ -251,8 +252,8 @@ func TestTxm_CreateTransaction(t *testing.T) { // max uint256 is 1.1579209e+77 testDefaultGlobalSubID := crypto.Keccak256Hash([]byte("sub id")).String() jobID := int32(25) - requestID := gethcommon.HexToHash("abcd") - requestTxHash := gethcommon.HexToHash("dcba") + requestID := common.HexToHash("abcd") + requestTxHash := common.HexToHash("dcba") meta := &txmgr.TxMeta{ JobID: &jobID, RequestID: &requestID, @@ -262,7 +263,7 @@ func TestTxm_CreateTransaction(t *testing.T) { SubID: &testDefaultSubID, GlobalSubID: &testDefaultGlobalSubID, } - evmConfig.maxQueued = uint64(1) + evmConfig.MaxQueued = uint64(1) checker := txmgr.TransmitCheckerSpec{ CheckerType: txmgr.TransmitCheckerTypeVRFV2, VRFCoordinatorAddress: testutils.NewAddressPtr(), @@ -294,10 +295,10 @@ func TestTxm_CreateTransaction(t *testing.T) { t.Run("forwards tx when a proper forwarder is set up", func(t *testing.T) { pgtest.MustExec(t, db, `DELETE FROM evm.txes`) pgtest.MustExec(t, db, `DELETE FROM evm.forwarders`) - evmConfig.maxQueued = uint64(1) + evmConfig.MaxQueued = uint64(1) // Create mock forwarder, mock authorizedsenders call. - form := forwarders.NewORM(db, logger.TestLogger(t), cfg.Database()) + form := forwarders.NewORM(db, logger.Test(t), cfg.Database()) fwdrAddr := testutils.NewAddress() fwdr, err := form.CreateForwarder(fwdrAddr, utils.Big(cltest.FixtureChainID)) require.NoError(t, err) @@ -324,7 +325,7 @@ func TestTxm_CreateTransaction(t *testing.T) { }) t.Run("insert Tx successfully with a IdempotencyKey", func(t *testing.T) { - evmConfig.maxQueued = uint64(3) + evmConfig.MaxQueued = uint64(3) id := uuid.New() idempotencyKey := "1" _, err := txm.CreateTransaction(testutils.Context(t), txmgr.TxRequest{ @@ -340,7 +341,7 @@ func TestTxm_CreateTransaction(t *testing.T) { }) t.Run("doesn't insert eth_tx if a matching tx already exists for that IdempotencyKey", func(t *testing.T) { - evmConfig.maxQueued = uint64(3) + evmConfig.MaxQueued = uint64(3) id := uuid.New() idempotencyKey := "2" tx1, err := txm.CreateTransaction(testutils.Context(t), txmgr.TxRequest{ @@ -373,138 +374,6 @@ func newMockTxStrategy(t *testing.T) *commontxmmocks.TxStrategy { return commontxmmocks.NewTxStrategy(t) } -type databaseConfig struct { - config.Database - defaultQueryTimeout time.Duration -} - -func (d *databaseConfig) DefaultQueryTimeout() time.Duration { - return d.defaultQueryTimeout -} - -func (d *databaseConfig) LogSQL() bool { - return false -} - -type listenerConfig struct { - config.Listener -} - -func (l *listenerConfig) FallbackPollInterval() time.Duration { - return 1 * time.Minute -} - -func (d *databaseConfig) Listener() config.Listener { - return &listenerConfig{} -} - -type evmConfig struct { - evmconfig.EVM - maxInFlight uint32 - reaperInterval time.Duration - reaperThreshold time.Duration - resendAfterThreshold time.Duration - bumpThreshold uint64 - maxQueued uint64 -} - -func (e *evmConfig) Transactions() evmconfig.Transactions { - return &transactionsConfig{e: e} -} - -func (e *evmConfig) GasEstimator() evmconfig.GasEstimator { - return &gasEstimatorConfig{bumpThreshold: e.bumpThreshold} -} - -func (e *evmConfig) NonceAutoSync() bool { return true } - -func (e *evmConfig) FinalityDepth() uint32 { return 42 } - -type gasEstimatorConfig struct { - bumpThreshold uint64 -} - -func (g *gasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { - return &blockHistoryConfig{} -} - -func (g *gasEstimatorConfig) EIP1559DynamicFees() bool { return false } -func (g *gasEstimatorConfig) LimitDefault() uint32 { return 42 } -func (g *gasEstimatorConfig) BumpPercent() uint16 { return 42 } -func (g *gasEstimatorConfig) BumpThreshold() uint64 { return g.bumpThreshold } -func (g *gasEstimatorConfig) BumpMin() *assets.Wei { return assets.NewWeiI(42) } -func (g *gasEstimatorConfig) FeeCapDefault() *assets.Wei { return assets.NewWeiI(42) } -func (g *gasEstimatorConfig) PriceDefault() *assets.Wei { return assets.NewWeiI(42) } -func (g *gasEstimatorConfig) TipCapDefault() *assets.Wei { return assets.NewWeiI(42) } -func (g *gasEstimatorConfig) TipCapMin() *assets.Wei { return assets.NewWeiI(42) } -func (g *gasEstimatorConfig) LimitMax() uint32 { return 0 } -func (g *gasEstimatorConfig) LimitMultiplier() float32 { return 0 } -func (g *gasEstimatorConfig) BumpTxDepth() uint32 { return 42 } -func (g *gasEstimatorConfig) LimitTransfer() uint32 { return 42 } -func (g *gasEstimatorConfig) PriceMax() *assets.Wei { return assets.NewWeiI(42) } -func (g *gasEstimatorConfig) PriceMin() *assets.Wei { return assets.NewWeiI(42) } -func (g *gasEstimatorConfig) Mode() string { return "FixedPrice" } -func (g *gasEstimatorConfig) LimitJobType() evmconfig.LimitJobType { return &limitJobTypeConfig{} } -func (g *gasEstimatorConfig) PriceMaxKey(addr common.Address) *assets.Wei { - return assets.NewWeiI(42) -} - -type limitJobTypeConfig struct { -} - -func (l *limitJobTypeConfig) OCR() *uint32 { return ptr(uint32(0)) } -func (l *limitJobTypeConfig) OCR2() *uint32 { return ptr(uint32(0)) } -func (l *limitJobTypeConfig) DR() *uint32 { return ptr(uint32(0)) } -func (l *limitJobTypeConfig) FM() *uint32 { return ptr(uint32(0)) } -func (l *limitJobTypeConfig) Keeper() *uint32 { return ptr(uint32(0)) } -func (l *limitJobTypeConfig) VRF() *uint32 { return ptr(uint32(0)) } - -type blockHistoryConfig struct { - evmconfig.BlockHistory -} - -func (b *blockHistoryConfig) BatchSize() uint32 { return 42 } -func (b *blockHistoryConfig) BlockDelay() uint16 { return 42 } -func (b *blockHistoryConfig) BlockHistorySize() uint16 { return 42 } -func (b *blockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 } -func (b *blockHistoryConfig) TransactionPercentile() uint16 { return 42 } - -type transactionsConfig struct { - evmconfig.Transactions - e *evmConfig -} - -func (*transactionsConfig) ForwardersEnabled() bool { return true } -func (t *transactionsConfig) MaxInFlight() uint32 { return t.e.maxInFlight } -func (t *transactionsConfig) MaxQueued() uint64 { return t.e.maxQueued } -func (t *transactionsConfig) ReaperInterval() time.Duration { return t.e.reaperInterval } -func (t *transactionsConfig) ReaperThreshold() time.Duration { return t.e.reaperThreshold } -func (t *transactionsConfig) ResendAfterThreshold() time.Duration { return t.e.resendAfterThreshold } - -type mockConfig struct { - evmConfig *evmConfig - rpcDefaultBatchSize uint32 - finalityDepth uint32 - finalityTagEnabled bool -} - -func (c *mockConfig) EVM() evmconfig.EVM { - return c.evmConfig -} - -func (c *mockConfig) NonceAutoSync() bool { return true } -func (c *mockConfig) ChainType() config.ChainType { return "" } -func (c *mockConfig) FinalityDepth() uint32 { return c.finalityDepth } -func (c *mockConfig) FinalityTagEnabled() bool { return c.finalityTagEnabled } -func (c *mockConfig) RPCDefaultBatchSize() uint32 { return c.rpcDefaultBatchSize } - -func makeConfigs(t *testing.T) (*mockConfig, *databaseConfig, *evmConfig) { - db := &databaseConfig{defaultQueryTimeout: pg.DefaultQueryTimeout} - ec := &evmConfig{bumpThreshold: 42, maxInFlight: uint32(42), maxQueued: uint64(0), reaperInterval: time.Duration(0), reaperThreshold: time.Duration(0)} - config := &mockConfig{evmConfig: ec} - return config, db, ec -} - func TestTxm_CreateTransaction_OutOfEth(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) @@ -519,18 +388,18 @@ func TestTxm_CreateTransaction_OutOfEth(t *testing.T) { gasLimit := uint32(1000) toAddress := testutils.NewAddress() - config, dbConfig, evmConfig := makeConfigs(t) + config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - estimator := gas.NewEstimator(logger.TestLogger(t), ethClient, config, evmConfig.GasEstimator()) - txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), etKeyStore, nil) + estimator := gas.NewEstimator(logger.Test(t), ethClient, config, evmConfig.GasEstimator()) + txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), etKeyStore) require.NoError(t, err) t.Run("if another key has any transactions with insufficient eth errors, transmits as normal", func(t *testing.T) { payload := cltest.MustRandomBytes(t, 100) - evmConfig.maxQueued = uint64(1) - cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 0, otherKey.Address) + evmConfig.MaxQueued = uint64(1) + mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 0, otherKey.Address) strategy := newMockTxStrategy(t) strategy.On("Subject").Return(uuid.NullUUID{}) strategy.On("PruneQueue", mock.Anything, mock.Anything).Return(int64(0), nil) @@ -552,9 +421,9 @@ func TestTxm_CreateTransaction_OutOfEth(t *testing.T) { t.Run("if this key has any transactions with insufficient eth errors, inserts it anyway", func(t *testing.T) { payload := cltest.MustRandomBytes(t, 100) - evmConfig.maxQueued = uint64(1) + evmConfig.MaxQueued = uint64(1) - cltest.MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 0, thisKey.Address) + mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, txStore, 0, thisKey.Address) strategy := newMockTxStrategy(t) strategy.On("Subject").Return(uuid.NullUUID{}) strategy.On("PruneQueue", mock.Anything, mock.Anything).Return(int64(0), nil) @@ -567,7 +436,7 @@ func TestTxm_CreateTransaction_OutOfEth(t *testing.T) { Meta: nil, Strategy: strategy, }) - assert.NoError(t, err) + require.NoError(t, err) require.Equal(t, payload, etx.EncodedPayload) }) @@ -580,7 +449,7 @@ func TestTxm_CreateTransaction_OutOfEth(t *testing.T) { strategy.On("Subject").Return(uuid.NullUUID{}) strategy.On("PruneQueue", mock.Anything, mock.Anything).Return(int64(0), nil) - evmConfig.maxQueued = uint64(1) + evmConfig.MaxQueued = uint64(1) etx, err := txm.CreateTransaction(testutils.Context(t), txmgr.TxRequest{ FromAddress: evmFromAddress, ToAddress: toAddress, @@ -589,7 +458,7 @@ func TestTxm_CreateTransaction_OutOfEth(t *testing.T) { Meta: nil, Strategy: strategy, }) - assert.NoError(t, err) + require.NoError(t, err) require.Equal(t, payload, etx.EncodedPayload) }) } @@ -599,33 +468,29 @@ func TestTxm_Lifecycle(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) kst := ksmocks.NewEth(t) - eventBroadcaster := pgmocks.NewEventBroadcaster(t) - config, dbConfig, evmConfig := makeConfigs(t) - config.finalityDepth = uint32(42) - config.rpcDefaultBatchSize = uint32(4) + config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) + config.SetFinalityDepth(uint32(42)) + config.RpcDefaultBatchSize = uint32(4) - evmConfig.resendAfterThreshold = 1 * time.Hour - evmConfig.reaperThreshold = 1 * time.Hour - evmConfig.reaperInterval = 1 * time.Hour + evmConfig.ResendAfterThreshold = 1 * time.Hour + evmConfig.ReaperThreshold = 1 * time.Hour + evmConfig.ReaperInterval = 1 * time.Hour - kst.On("EnabledAddressesForChain", &cltest.FixtureChainID).Return([]gethcommon.Address{}, nil) + kst.On("EnabledAddressesForChain", &cltest.FixtureChainID).Return([]common.Address{}, nil) keyChangeCh := make(chan struct{}) unsub := cltest.NewAwaiter() kst.On("SubscribeToKeyChanges").Return(keyChangeCh, unsub.ItHappened) - estimator := gas.NewEstimator(logger.TestLogger(t), ethClient, config, evmConfig.GasEstimator()) - txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), kst, eventBroadcaster) + estimator := gas.NewEstimator(logger.Test(t), ethClient, config, evmConfig.GasEstimator()) + txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), kst) require.NoError(t, err) head := cltest.Head(42) // It should not hang or panic txm.OnNewLongestChain(testutils.Context(t), head) - sub := pgmocks.NewSubscription(t) - sub.On("Events").Return(make(<-chan pg.Event)) - eventBroadcaster.On("Subscribe", "evm.insert_on_txes", "").Return(sub, nil) - evmConfig.bumpThreshold = uint64(1) + evmConfig.BumpThreshold = uint64(1) require.NoError(t, txm.Start(testutils.Context(t))) @@ -636,10 +501,9 @@ func TestTxm_Lifecycle(t *testing.T) { keyState := cltest.MustGenerateRandomKeyState(t) - addr := []gethcommon.Address{keyState.Address.Address()} + addr := []common.Address{keyState.Address.Address()} kst.On("EnabledAddressesForChain", &cltest.FixtureChainID).Return(addr, nil) - sub.On("Close").Return() - ethClient.On("PendingNonceAt", mock.AnythingOfType("*context.cancelCtx"), gethcommon.Address{}).Return(uint64(0), nil).Maybe() + ethClient.On("PendingNonceAt", mock.AnythingOfType("*context.cancelCtx"), common.Address{}).Return(uint64(0), nil).Maybe() keyChangeCh <- struct{}{} require.NoError(t, txm.Close()) @@ -655,8 +519,8 @@ func TestTxm_Reset(t *testing.T) { cfg := evmtest.NewChainScopedConfig(t, gcfg) kst := cltest.NewKeyStore(t, db, cfg.Database()) - _, addr := cltest.RandomKey{Nonce: 5}.MustInsert(t, kst.Eth()) - _, addr2 := cltest.RandomKey{Nonce: 3}.MustInsert(t, kst.Eth()) + _, addr := cltest.RandomKey{}.MustInsert(t, kst.Eth()) + _, addr2 := cltest.RandomKey{}.MustInsert(t, kst.Eth()) txStore := cltest.NewTestTxStore(t, db, cfg.Database()) // 4 confirmed tx from addr1 for i := int64(0); i < 4; i++ { @@ -670,14 +534,11 @@ func TestTxm_Reset(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(nil, nil) ethClient.On("BatchCallContextAll", mock.Anything, mock.Anything).Return(nil).Maybe() - eventBroadcaster := pgmocks.NewEventBroadcaster(t) - sub := pgmocks.NewSubscription(t) - sub.On("Events").Return(make(<-chan pg.Event)) - sub.On("Close") - eventBroadcaster.On("Subscribe", "evm.insert_on_txes", "").Return(sub, nil) - - estimator := gas.NewEstimator(logger.TestLogger(t), ethClient, cfg.EVM(), cfg.EVM().GasEstimator()) - txm, err := makeTestEvmTxm(t, db, ethClient, estimator, cfg.EVM(), cfg.EVM().GasEstimator(), cfg.EVM().Transactions(), cfg.Database(), cfg.Database().Listener(), kst.Eth(), eventBroadcaster) + ethClient.On("PendingNonceAt", mock.Anything, addr).Return(uint64(128), nil).Maybe() + ethClient.On("PendingNonceAt", mock.Anything, addr2).Return(uint64(44), nil).Maybe() + + estimator := gas.NewEstimator(logger.Test(t), ethClient, cfg.EVM(), cfg.EVM().GasEstimator()) + txm, err := makeTestEvmTxm(t, db, ethClient, estimator, cfg.EVM(), cfg.EVM().GasEstimator(), cfg.EVM().Transactions(), cfg.Database(), cfg.Database().Listener(), kst.Eth()) require.NoError(t, err) cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 2, addr2) @@ -715,3 +576,244 @@ func TestTxm_Reset(t *testing.T) { assert.Equal(t, 0, count) }) } + +func newTxStore(t *testing.T, db *sqlx.DB, cfg pg.QConfig) txmgr.EvmTxStore { + return txmgr.NewTxStore(db, logger.Test(t), cfg) +} + +func newEthReceipt(blockNumber int64, blockHash common.Hash, txHash common.Hash, status uint64) txmgr.Receipt { + transactionIndex := uint(cltest.NewRandomPositiveInt64()) + + receipt := evmtypes.Receipt{ + BlockNumber: big.NewInt(blockNumber), + BlockHash: blockHash, + TxHash: txHash, + TransactionIndex: transactionIndex, + Status: status, + } + + r := txmgr.Receipt{ + BlockNumber: blockNumber, + BlockHash: blockHash, + TxHash: txHash, + TransactionIndex: transactionIndex, + Receipt: receipt, + } + return r +} + +func mustInsertEthReceipt(t *testing.T, txStore txmgr.TestEvmTxStore, blockNumber int64, blockHash common.Hash, txHash common.Hash) txmgr.Receipt { + r := newEthReceipt(blockNumber, blockHash, txHash, 0x1) + id, err := txStore.InsertReceipt(&r.Receipt) + require.NoError(t, err) + r.ID = id + return r +} + +func mustInsertRevertedEthReceipt(t *testing.T, txStore txmgr.TestEvmTxStore, blockNumber int64, blockHash common.Hash, txHash common.Hash) txmgr.Receipt { + r := newEthReceipt(blockNumber, blockHash, txHash, 0x0) + id, err := txStore.InsertReceipt(&r.Receipt) + require.NoError(t, err) + r.ID = id + return r +} + +// Inserts into evm.receipts but does not update evm.txes or evm.tx_attempts +func mustInsertConfirmedEthTxWithReceipt(t *testing.T, txStore txmgr.TestEvmTxStore, fromAddress common.Address, nonce, blockNum int64) (etx txmgr.Tx) { + etx = cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, nonce, blockNum, fromAddress) + mustInsertEthReceipt(t, txStore, blockNum, utils.NewHash(), etx.TxAttempts[0].Hash) + return etx +} + +func mustInsertConfirmedEthTxBySaveFetchedReceipts(t *testing.T, txStore txmgr.TestEvmTxStore, fromAddress common.Address, nonce int64, blockNum int64, chainID big.Int) (etx txmgr.Tx) { + etx = cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, nonce, blockNum, fromAddress) + receipt := evmtypes.Receipt{ + TxHash: etx.TxAttempts[0].Hash, + BlockHash: utils.NewHash(), + BlockNumber: big.NewInt(nonce), + TransactionIndex: uint(1), + } + err := txStore.SaveFetchedReceipts(testutils.Context(t), []*evmtypes.Receipt{&receipt}, &chainID) + require.NoError(t, err) + return etx +} + +func mustInsertFatalErrorEthTx(t *testing.T, txStore txmgr.TestEvmTxStore, fromAddress common.Address) txmgr.Tx { + etx := cltest.NewEthTx(fromAddress) + etx.Error = null.StringFrom("something exploded") + etx.State = txmgrcommon.TxFatalError + + require.NoError(t, txStore.InsertTx(&etx)) + return etx +} + +func mustInsertUnconfirmedEthTxWithAttemptState(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address, txAttemptState txmgrtypes.TxAttemptState, opts ...interface{}) txmgr.Tx { + etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, nonce, fromAddress, opts...) + attempt := cltest.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 = txAttemptState + require.NoError(t, txStore.InsertTxAttempt(&attempt)) + etx, err := txStore.FindTxWithAttempts(etx.ID) + require.NoError(t, err) + return etx +} + +func mustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address, opts ...interface{}) txmgr.Tx { + etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, nonce, fromAddress, opts...) + attempt := cltest.NewDynamicFeeEthTxAttempt(t, etx.ID) + + addr := testutils.NewAddress() + dtx := types.DynamicFeeTx{ + ChainID: big.NewInt(0), + Nonce: uint64(nonce), + GasTipCap: big.NewInt(1), + GasFeeCap: big.NewInt(1), + Gas: 242, + To: &addr, + Value: big.NewInt(342), + Data: []byte{2, 3, 4}, + } + tx := types.NewTx(&dtx) + rlp := new(bytes.Buffer) + require.NoError(t, tx.EncodeRLP(rlp)) + attempt.SignedRawTx = rlp.Bytes() + + attempt.State = txmgrtypes.TxAttemptBroadcast + require.NoError(t, txStore.InsertTxAttempt(&attempt)) + etx, err := txStore.FindTxWithAttempts(etx.ID) + require.NoError(t, err) + return etx +} + +func mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address) txmgr.Tx { + timeNow := time.Now() + etx := cltest.NewEthTx(fromAddress) + + etx.BroadcastAt = &timeNow + etx.InitialBroadcastAt = &timeNow + n := evmtypes.Nonce(nonce) + etx.Sequence = &n + etx.State = txmgrcommon.TxUnconfirmed + require.NoError(t, txStore.InsertTx(&etx)) + attempt := cltest.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 = txmgrtypes.TxAttemptInsufficientFunds + require.NoError(t, txStore.InsertTxAttempt(&attempt)) + etx, err := txStore.FindTxWithAttempts(etx.ID) + require.NoError(t, err) + return etx +} + +func mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, broadcastBeforeBlockNum int64, + broadcastAt time.Time, fromAddress common.Address) txmgr.Tx { + etx := cltest.NewEthTx(fromAddress) + + etx.BroadcastAt = &broadcastAt + etx.InitialBroadcastAt = &broadcastAt + n := evmtypes.Nonce(nonce) + etx.Sequence = &n + etx.State = txmgrcommon.TxConfirmedMissingReceipt + require.NoError(t, txStore.InsertTx(&etx)) + attempt := cltest.NewLegacyEthTxAttempt(t, etx.ID) + attempt.BroadcastBeforeBlockNum = &broadcastBeforeBlockNum + attempt.State = txmgrtypes.TxAttemptBroadcast + require.NoError(t, txStore.InsertTxAttempt(&attempt)) + etx.TxAttempts = append(etx.TxAttempts, attempt) + return etx +} + +func mustInsertInProgressEthTxWithAttempt(t *testing.T, txStore txmgr.TestEvmTxStore, nonce evmtypes.Nonce, fromAddress common.Address) txmgr.Tx { + etx := cltest.NewEthTx(fromAddress) + + etx.Sequence = &nonce + etx.State = txmgrcommon.TxInProgress + require.NoError(t, txStore.InsertTx(&etx)) + attempt := cltest.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 = txmgrtypes.TxAttemptInProgress + require.NoError(t, txStore.InsertTxAttempt(&attempt)) + etx, err := txStore.FindTxWithAttempts(etx.ID) + require.NoError(t, err) + return etx +} + +func mustCreateUnstartedGeneratedTx(t testing.TB, txStore txmgr.EvmTxStore, fromAddress common.Address, chainID *big.Int, opts ...func(*txmgr.TxRequest)) (tx txmgr.Tx) { + txRequest := txmgr.TxRequest{ + FromAddress: fromAddress, + } + + // Apply the default options + withDefaults()(&txRequest) + // Apply the optional parameters + for _, opt := range opts { + opt(&txRequest) + } + return mustCreateUnstartedTxFromEvmTxRequest(t, txStore, txRequest, chainID) +} + +func withDefaults() func(*txmgr.TxRequest) { + return func(tx *txmgr.TxRequest) { + tx.ToAddress = testutils.NewAddress() + tx.EncodedPayload = []byte{1, 2, 3} + tx.Value = big.Int(assets.NewEthValue(142)) + tx.FeeLimit = uint32(1000000000) + tx.Strategy = txmgrcommon.NewSendEveryStrategy() + // Set default values for other fields if needed + } +} + +func mustCreateUnstartedTx(t testing.TB, txStore txmgr.EvmTxStore, fromAddress common.Address, toAddress common.Address, encodedPayload []byte, gasLimit uint32, value big.Int, chainID *big.Int, opts ...interface{}) (tx txmgr.Tx) { + txRequest := txmgr.TxRequest{ + FromAddress: fromAddress, + ToAddress: toAddress, + EncodedPayload: encodedPayload, + Value: value, + FeeLimit: gasLimit, + Strategy: txmgrcommon.NewSendEveryStrategy(), + } + + return mustCreateUnstartedTxFromEvmTxRequest(t, txStore, txRequest, chainID) +} + +func mustCreateUnstartedTxFromEvmTxRequest(t testing.TB, txStore txmgr.EvmTxStore, txRequest txmgr.TxRequest, chainID *big.Int) (tx txmgr.Tx) { + tx, err := txStore.CreateTransaction(testutils.Context(t), txRequest, chainID) + require.NoError(t, err) + return tx +} + +func txRequestWithStrategy(strategy txmgrtypes.TxStrategy) func(*txmgr.TxRequest) { + return func(tx *txmgr.TxRequest) { + tx.Strategy = strategy + } +} + +func txRequestWithChecker(checker txmgr.TransmitCheckerSpec) func(*txmgr.TxRequest) { + return func(tx *txmgr.TxRequest) { + tx.Checker = checker + } +} +func txRequestWithValue(value big.Int) func(*txmgr.TxRequest) { + return func(tx *txmgr.TxRequest) { + tx.Value = value + } +} + +func txRequestWithIdempotencyKey(idempotencyKey string) func(*txmgr.TxRequest) { + return func(tx *txmgr.TxRequest) { + tx.IdempotencyKey = &idempotencyKey + } +} diff --git a/core/chains/evm/types/block_json_benchmark_test.go b/core/chains/evm/types/block_json_benchmark_test.go index 8c5f766d632..21c58bd1987 100644 --- a/core/chains/evm/types/block_json_benchmark_test.go +++ b/core/chains/evm/types/block_json_benchmark_test.go @@ -9,7 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) diff --git a/core/chains/evm/types/models.go b/core/chains/evm/types/models.go index 6210226120f..314180a7e98 100644 --- a/core/chains/evm/types/models.go +++ b/core/chains/evm/types/models.go @@ -18,7 +18,7 @@ import ( htrktypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types" commontypes "github.com/smartcontractkit/chainlink/v2/common/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types/internal/blocks" "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -39,8 +39,8 @@ type Head struct { ReceiptsRoot common.Hash TransactionsRoot common.Hash StateRoot common.Hash - Difficulty *utils.Big - TotalDifficulty *utils.Big + Difficulty *big.Int + TotalDifficulty *big.Int } var _ commontypes.Head[common.Hash] = &Head{} @@ -76,6 +76,14 @@ func (h *Head) GetParent() commontypes.Head[common.Hash] { return h.Parent } +func (h *Head) GetTimestamp() time.Time { + return h.Timestamp +} + +func (h *Head) BlockDifficulty() *big.Int { + return h.Difficulty +} + // EarliestInChain recurses through parents until it finds the earliest one func (h *Head) EarliestInChain() *Head { for h.Parent != nil { @@ -223,6 +231,21 @@ func (h *Head) NextInt() *big.Int { return new(big.Int).Add(h.ToInt(), big.NewInt(1)) } +// AsSlice returns a slice of heads up to length k +// len(heads) may be less than k if the available chain is not long enough +func (h *Head) AsSlice(k int) (heads []*Head) { + if k < 1 || h == nil { + return + } + heads = make([]*Head, 1) + heads[0] = h + for len(heads) < k && h.Parent != nil { + h = h.Parent + heads = append(heads, h) + } + return +} + func (h *Head) UnmarshalJSON(bs []byte) error { type head struct { Hash common.Hash `json:"hash"` @@ -260,8 +283,8 @@ func (h *Head) UnmarshalJSON(bs []byte) error { h.ReceiptsRoot = jsonHead.ReceiptsRoot h.TransactionsRoot = jsonHead.TransactionsRoot h.StateRoot = jsonHead.StateRoot - h.Difficulty = utils.NewBig(jsonHead.Difficulty.ToInt()) - h.TotalDifficulty = utils.NewBig(jsonHead.TotalDifficulty.ToInt()) + h.Difficulty = jsonHead.Difficulty.ToInt() + h.TotalDifficulty = jsonHead.TotalDifficulty.ToInt() return nil } diff --git a/core/chains/evm/types/models_test.go b/core/chains/evm/types/models_test.go index 683a49692b6..3507b6a1318 100644 --- a/core/chains/evm/types/models_test.go +++ b/core/chains/evm/types/models_test.go @@ -17,12 +17,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -129,6 +129,29 @@ func TestHead_ChainLength(t *testing.T) { assert.Equal(t, uint32(0), head2.ChainLength()) } +func TestHead_AsSlice(t *testing.T) { + h1 := &evmtypes.Head{ + Number: 1, + } + h2 := &evmtypes.Head{ + Number: 2, + Parent: h1, + } + h3 := &evmtypes.Head{ + Number: 3, + Parent: h2, + } + + assert.Len(t, (*evmtypes.Head)(nil).AsSlice(0), 0) + assert.Len(t, (*evmtypes.Head)(nil).AsSlice(1), 0) + + assert.Len(t, h3.AsSlice(0), 0) + assert.Equal(t, []*evmtypes.Head{h3}, h3.AsSlice(1)) + assert.Equal(t, []*evmtypes.Head{h3, h2}, h3.AsSlice(2)) + assert.Equal(t, []*evmtypes.Head{h3, h2, h1}, h3.AsSlice(3)) + assert.Equal(t, []*evmtypes.Head{h3, h2, h1}, h3.AsSlice(4)) +} + func TestModels_HexToFunctionSelector(t *testing.T) { t.Parallel() fid := evmtypes.HexToFunctionSelector("0xb3f98adc") diff --git a/core/chains/evm/types/types.go b/core/chains/evm/types/types.go index 7d756485d00..d0e7292b204 100644 --- a/core/chains/evm/types/types.go +++ b/core/chains/evm/types/types.go @@ -12,7 +12,7 @@ import ( "github.com/pkg/errors" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/chains/evm/chain.go b/core/chains/legacyevm/chain.go similarity index 88% rename from core/chains/evm/chain.go rename to core/chains/legacyevm/chain.go index 2646d25867b..4b4c69f1ab6 100644 --- a/core/chains/evm/chain.go +++ b/core/chains/legacyevm/chain.go @@ -1,4 +1,4 @@ -package evm +package legacyevm import ( "context" @@ -11,12 +11,14 @@ import ( gotoml "github.com/pelletier/go-toml/v2" "go.uber.org/multierr" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - relaychains "github.com/smartcontractkit/chainlink-relay/pkg/chains" - "github.com/smartcontractkit/chainlink-relay/pkg/services" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + common "github.com/smartcontractkit/chainlink-common/pkg/chains" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/types" + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + commonconfig "github.com/smartcontractkit/chainlink/v2/common/config" "github.com/smartcontractkit/chainlink/v2/core/chains" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" @@ -108,7 +110,7 @@ func (c *LegacyChains) Get(id string) (Chain, error) { } type chain struct { - utils.StartStopOnce + services.StateMachine id *big.Int cfg *evmconfig.ChainScoped client evmclient.Client @@ -219,11 +221,7 @@ func newChain(ctx context.Context, cfg *evmconfig.ChainScoped, nodes []*toml.Nod if !cfg.EVMRPCEnabled() { client = evmclient.NewNullClient(chainID, l) } else if opts.GenEthClient == nil { - var err2 error - client, err2 = newEthClientFromChain(cfg.EVM().NodePool(), cfg.EVM().NodeNoNewHeadsThreshold(), l, chainID, chainType, nodes) - if err2 != nil { - return nil, fmt.Errorf("failed to instantiate eth client for chain with ID %s: %w", cfg.EVM().ChainID().String(), err2) - } + client = newEthClientFromCfg(cfg.EVM().NodePool(), cfg.EVM().NodeNoNewHeadsThreshold(), l, chainID, chainType, nodes) } else { client = opts.GenEthClient(chainID) } @@ -366,7 +364,7 @@ func (c *chain) Close() error { func (c *chain) Ready() (merr error) { merr = multierr.Combine( - c.StartStopOnce.Ready(), + c.StateMachine.Ready(), c.txm.Ready(), c.headBroadcaster.Ready(), c.headTracker.Ready(), @@ -421,7 +419,7 @@ func (c *chain) listNodeStatuses(start, end int) ([]types.NodeStatus, int, error nodes := c.cfg.Nodes() total := len(nodes) if start >= total { - return nil, total, relaychains.ErrOutOfRange + return nil, total, common.ErrOutOfRange } if end > total { end = total @@ -458,7 +456,7 @@ func (c *chain) listNodeStatuses(start, end int) ([]types.NodeStatus, int, error } func (c *chain) ListNodeStatuses(ctx context.Context, pageSize int32, pageToken string) (stats []types.NodeStatus, nextPageToken string, total int, err error) { - return relaychains.ListNodeStatuses(int(pageSize), pageToken, c.listNodeStatuses) + return common.ListNodeStatuses(int(pageSize), pageToken, c.listNodeStatuses) } func (c *chain) ID() *big.Int { return c.id } @@ -473,28 +471,27 @@ func (c *chain) Logger() logger.Logger { return c.logger } func (c *chain) BalanceMonitor() monitor.BalanceMonitor { return c.balanceMonitor } func (c *chain) GasEstimator() gas.EvmFeeEstimator { return c.gasEstimator } -func newEthClientFromChain(cfg evmconfig.NodePool, noNewHeadsThreshold time.Duration, lggr logger.Logger, chainID *big.Int, chainType config.ChainType, nodes []*toml.Node) (evmclient.Client, error) { - var primaries []evmclient.Node - var sendonlys []evmclient.SendOnlyNode +func newEthClientFromCfg(cfg evmconfig.NodePool, noNewHeadsThreshold time.Duration, lggr logger.Logger, chainID *big.Int, chainType commonconfig.ChainType, nodes []*toml.Node) evmclient.Client { + var empty url.URL + var primaries []commonclient.Node[*big.Int, *evmtypes.Head, evmclient.RPCCLient] + var sendonlys []commonclient.SendOnlyNode[*big.Int, evmclient.RPCCLient] for i, node := range nodes { if node.SendOnly != nil && *node.SendOnly { - sendonly := evmclient.NewSendOnlyNode(lggr, (url.URL)(*node.HTTPURL), *node.Name, chainID) + name := fmt.Sprintf("eth-sendonly-rpc-%d", i) + rpc := evmclient.NewRPCClient(lggr, empty, (*url.URL)(node.HTTPURL), name, int32(i), chainID, + commonclient.Secondary) + sendonly := commonclient.NewSendOnlyNode[*big.Int, evmclient.RPCCLient](lggr, (url.URL)(*node.HTTPURL), + *node.Name, chainID, rpc) sendonlys = append(sendonlys, sendonly) } else { - primary, err := newPrimary(cfg, noNewHeadsThreshold, lggr, node, int32(i), chainID) - if err != nil { - return nil, err - } - primaries = append(primaries, primary) + name := fmt.Sprintf("eth-primary-rpc-%d", i) + rpc := evmclient.NewRPCClient(lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), name, int32(i), + chainID, commonclient.Primary) + primaryNode := commonclient.NewNode[*big.Int, *evmtypes.Head, evmclient.RPCCLient](cfg, noNewHeadsThreshold, + lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, *node.Order, + rpc, "EVM") + primaries = append(primaries, primaryNode) } } - return evmclient.NewClientWithNodes(lggr, cfg.SelectionMode(), cfg.LeaseDuration(), noNewHeadsThreshold, primaries, sendonlys, chainID, chainType) -} - -func newPrimary(cfg evmconfig.NodePool, noNewHeadsThreshold time.Duration, lggr logger.Logger, n *toml.Node, id int32, chainID *big.Int) (evmclient.Node, error) { - if n.SendOnly != nil && *n.SendOnly { - return nil, errors.New("cannot cast send-only node to primary") - } - - return evmclient.NewNode(cfg, noNewHeadsThreshold, lggr, (url.URL)(*n.WSURL), (*url.URL)(n.HTTPURL), *n.Name, id, chainID, *n.Order), nil + return evmclient.NewChainClient(lggr, cfg.SelectionMode(), cfg.LeaseDuration(), noNewHeadsThreshold, primaries, sendonlys, chainID, chainType) } diff --git a/core/chains/evm/chain_test.go b/core/chains/legacyevm/chain_test.go similarity index 75% rename from core/chains/evm/chain_test.go rename to core/chains/legacyevm/chain_test.go index 09395ff4c97..4fcd51c39d9 100644 --- a/core/chains/evm/chain_test.go +++ b/core/chains/legacyevm/chain_test.go @@ -1,28 +1,29 @@ -package evm_test +package legacyevm_test import ( "math/big" "testing" - "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/assert" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/mocks" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm/mocks" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) func TestLegacyChains(t *testing.T) { - evmCfg := configtest.NewGeneralConfig(t, nil) + legacyevmCfg := configtest.NewGeneralConfig(t, nil) c := mocks.NewChain(t) c.On("ID").Return(big.NewInt(7)) - m := map[string]evm.Chain{c.ID().String(): c} + m := map[string]legacyevm.Chain{c.ID().String(): c} - l := evm.NewLegacyChains(m, evmCfg.EVMConfigs()) + l := legacyevm.NewLegacyChains(m, legacyevmCfg.EVMConfigs()) assert.NotNil(t, l.ChainNodeConfigs()) got, err := l.Get(c.ID().String()) assert.NoError(t, err) @@ -32,7 +33,7 @@ func TestLegacyChains(t *testing.T) { func TestChainOpts_Validate(t *testing.T) { type fields struct { - AppConfig evm.AppConfig + AppConfig legacyevm.AppConfig EventBroadcaster pg.EventBroadcaster MailMon *utils.MailboxMonitor DB *sqlx.DB @@ -64,7 +65,7 @@ func TestChainOpts_Validate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - o := evm.ChainOpts{ + o := legacyevm.ChainOpts{ AppConfig: tt.fields.AppConfig, EventBroadcaster: tt.fields.EventBroadcaster, MailMon: tt.fields.MailMon, diff --git a/core/chains/evm/evm_txm.go b/core/chains/legacyevm/evm_txm.go similarity index 96% rename from core/chains/evm/evm_txm.go rename to core/chains/legacyevm/evm_txm.go index d2f4178c7d9..1606ea1b244 100644 --- a/core/chains/evm/evm_txm.go +++ b/core/chains/legacyevm/evm_txm.go @@ -1,9 +1,9 @@ -package evm +package legacyevm import ( "fmt" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" @@ -61,7 +61,6 @@ func newEvmTxm( lggr, logPoller, opts.KeyStore, - opts.EventBroadcaster, estimator) } else { txm = opts.GenTxManager(chainID) diff --git a/core/chains/evm/mocks/chain.go b/core/chains/legacyevm/mocks/chain.go similarity index 99% rename from core/chains/evm/mocks/chain.go rename to core/chains/legacyevm/mocks/chain.go index f0f202b0938..3a686887d76 100644 --- a/core/chains/evm/mocks/chain.go +++ b/core/chains/legacyevm/mocks/chain.go @@ -30,7 +30,7 @@ import ( txmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" - types "github.com/smartcontractkit/chainlink-relay/pkg/types" + types "github.com/smartcontractkit/chainlink-common/pkg/types" ) // Chain is an autogenerated mock type for the Chain type diff --git a/core/chains/evm/mocks/legacy_chain_container.go b/core/chains/legacyevm/mocks/legacy_chain_container.go similarity index 73% rename from core/chains/evm/mocks/legacy_chain_container.go rename to core/chains/legacyevm/mocks/legacy_chain_container.go index 9180a8a2fc5..9ebacb890aa 100644 --- a/core/chains/evm/mocks/legacy_chain_container.go +++ b/core/chains/legacyevm/mocks/legacy_chain_container.go @@ -3,7 +3,7 @@ package mocks import ( - evm "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + legacyevm "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" mock "github.com/stretchr/testify/mock" types "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -31,19 +31,19 @@ func (_m *LegacyChainContainer) ChainNodeConfigs() types.Configs { } // Get provides a mock function with given fields: id -func (_m *LegacyChainContainer) Get(id string) (evm.Chain, error) { +func (_m *LegacyChainContainer) Get(id string) (legacyevm.Chain, error) { ret := _m.Called(id) - var r0 evm.Chain + var r0 legacyevm.Chain var r1 error - if rf, ok := ret.Get(0).(func(string) (evm.Chain, error)); ok { + if rf, ok := ret.Get(0).(func(string) (legacyevm.Chain, error)); ok { return rf(id) } - if rf, ok := ret.Get(0).(func(string) evm.Chain); ok { + if rf, ok := ret.Get(0).(func(string) legacyevm.Chain); ok { r0 = rf(id) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(evm.Chain) + r0 = ret.Get(0).(legacyevm.Chain) } } @@ -71,7 +71,7 @@ func (_m *LegacyChainContainer) Len() int { } // List provides a mock function with given fields: ids -func (_m *LegacyChainContainer) List(ids ...string) ([]evm.Chain, error) { +func (_m *LegacyChainContainer) List(ids ...string) ([]legacyevm.Chain, error) { _va := make([]interface{}, len(ids)) for _i := range ids { _va[_i] = ids[_i] @@ -80,16 +80,16 @@ func (_m *LegacyChainContainer) List(ids ...string) ([]evm.Chain, error) { _ca = append(_ca, _va...) ret := _m.Called(_ca...) - var r0 []evm.Chain + var r0 []legacyevm.Chain var r1 error - if rf, ok := ret.Get(0).(func(...string) ([]evm.Chain, error)); ok { + if rf, ok := ret.Get(0).(func(...string) ([]legacyevm.Chain, error)); ok { return rf(ids...) } - if rf, ok := ret.Get(0).(func(...string) []evm.Chain); ok { + if rf, ok := ret.Get(0).(func(...string) []legacyevm.Chain); ok { r0 = rf(ids...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]evm.Chain) + r0 = ret.Get(0).([]legacyevm.Chain) } } @@ -103,15 +103,15 @@ func (_m *LegacyChainContainer) List(ids ...string) ([]evm.Chain, error) { } // Slice provides a mock function with given fields: -func (_m *LegacyChainContainer) Slice() []evm.Chain { +func (_m *LegacyChainContainer) Slice() []legacyevm.Chain { ret := _m.Called() - var r0 []evm.Chain - if rf, ok := ret.Get(0).(func() []evm.Chain); ok { + var r0 []legacyevm.Chain + if rf, ok := ret.Get(0).(func() []legacyevm.Chain); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]evm.Chain) + r0 = ret.Get(0).([]legacyevm.Chain) } } diff --git a/core/cmd/admin_commands_test.go b/core/cmd/admin_commands_test.go index a5512fdddaa..fc4c1b7e959 100644 --- a/core/cmd/admin_commands_test.go +++ b/core/cmd/admin_commands_test.go @@ -43,7 +43,7 @@ func TestShell_CreateUser(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.CreateUser, set, "") + flagSetApplyFromAction(client.CreateUser, set, "") require.NoError(t, set.Set("email", test.email)) require.NoError(t, set.Set("role", test.role)) @@ -62,7 +62,7 @@ func TestShell_ChangeRole(t *testing.T) { app := startNewApplicationV2(t, nil) client, _ := app.NewShellAndRenderer() user := cltest.MustRandomUser(t) - require.NoError(t, app.SessionORM().CreateUser(&user)) + require.NoError(t, app.AuthenticationProvider().CreateUser(&user)) tests := []struct { name string @@ -83,7 +83,7 @@ func TestShell_ChangeRole(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.ChangeRole, set, "") + flagSetApplyFromAction(client.ChangeRole, set, "") require.NoError(t, set.Set("email", test.email)) require.NoError(t, set.Set("new-role", test.role)) @@ -101,7 +101,7 @@ func TestShell_DeleteUser(t *testing.T) { app := startNewApplicationV2(t, nil) client, _ := app.NewShellAndRenderer() user := cltest.MustRandomUser(t) - require.NoError(t, app.SessionORM().CreateUser(&user)) + require.NoError(t, app.BasicAdminUsersORM().CreateUser(&user)) tests := []struct { name string @@ -118,7 +118,7 @@ func TestShell_DeleteUser(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteUser, set, "") + flagSetApplyFromAction(client.DeleteUser, set, "") require.NoError(t, set.Set("email", test.email)) c := cli.NewContext(nil, set, nil) @@ -135,10 +135,10 @@ func TestShell_ListUsers(t *testing.T) { app := startNewApplicationV2(t, nil) client, _ := app.NewShellAndRenderer() user := cltest.MustRandomUser(t) - require.NoError(t, app.SessionORM().CreateUser(&user)) + require.NoError(t, app.AuthenticationProvider().CreateUser(&user)) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.ListUsers, set, "") + flagSetApplyFromAction(client.ListUsers, set, "") c := cli.NewContext(nil, set, nil) buffer := bytes.NewBufferString("") diff --git a/core/cmd/app_test.go b/core/cmd/app_test.go index 78331bf7063..e5e29406426 100644 --- a/core/cmd/app_test.go +++ b/core/cmd/app_test.go @@ -9,7 +9,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink/v2/core/config/toml" - testtomlutils "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2/toml" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/store/models" ) @@ -88,7 +88,7 @@ func Test_initServerConfig(t *testing.T) { name: "files only", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, }, wantCfg: withDefaults(t, testConfigFileContents, chainlink.Secrets{}), }, @@ -104,7 +104,7 @@ func Test_initServerConfig(t *testing.T) { name: "env overlay of file", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, envVar: testEnvContents, }, wantCfg: withDefaults(t, chainlink.Config{ @@ -124,7 +124,7 @@ func Test_initServerConfig(t *testing.T) { name: "failed to read secrets", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, secretsFiles: []string{"/doesnt-exist"}, }, wantErr: true, @@ -133,8 +133,8 @@ func Test_initServerConfig(t *testing.T) { name: "reading secrets", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, - secretsFiles: []string{testtomlutils.WriteTOMLFile(t, testSecretsFileContents, "test_secrets.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + secretsFiles: []string{configtest.WriteTOMLFile(t, testSecretsFileContents, "test_secrets.toml")}, }, wantCfg: withDefaults(t, testConfigFileContents, testSecretsRedactedContents), }, @@ -142,7 +142,7 @@ func Test_initServerConfig(t *testing.T) { name: "reading multiple secrets", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, secretsFiles: []string{ "../services/chainlink/testdata/mergingsecretsdata/secrets-database.toml", "../services/chainlink/testdata/mergingsecretsdata/secrets-password.toml", @@ -151,6 +151,7 @@ func Test_initServerConfig(t *testing.T) { "../services/chainlink/testdata/mergingsecretsdata/secrets-mercury-split-one.toml", "../services/chainlink/testdata/mergingsecretsdata/secrets-mercury-split-two.toml", "../services/chainlink/testdata/mergingsecretsdata/secrets-threshold.toml", + "../services/chainlink/testdata/mergingsecretsdata/secrets-webserver-ldap.toml", }, }, wantErr: false, @@ -159,7 +160,7 @@ func Test_initServerConfig(t *testing.T) { name: "reading multiple secrets with overrides: Database", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, secretsFiles: []string{ "../testdata/mergingsecretsdata/secrets-database.toml", "../testdata/mergingsecretsdata/secrets-database.toml", @@ -171,7 +172,7 @@ func Test_initServerConfig(t *testing.T) { name: "reading multiple secrets with overrides: Password", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, secretsFiles: []string{ "../testdata/mergingsecretsdata/secrets-password.toml", "../testdata/mergingsecretsdata/secrets-password.toml", @@ -183,7 +184,7 @@ func Test_initServerConfig(t *testing.T) { name: "reading multiple secrets with overrides: Pyroscope", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, secretsFiles: []string{ "../testdata/mergingsecretsdata/secrets-pyroscope.toml", "../testdata/mergingsecretsdata/secrets-pyroscope.toml", @@ -195,7 +196,7 @@ func Test_initServerConfig(t *testing.T) { name: "reading multiple secrets with overrides: Prometheus", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, secretsFiles: []string{ "../testdata/mergingsecretsdata/secrets-prometheus.toml", "../testdata/mergingsecretsdata/secrets-prometheus.toml", @@ -207,7 +208,7 @@ func Test_initServerConfig(t *testing.T) { name: "reading multiple secrets with overrides: Mercury", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, secretsFiles: []string{ "../testdata/mergingsecretsdata/secrets-mercury-split-one.toml", "../testdata/mergingsecretsdata/secrets-mercury-split-one.toml", @@ -219,7 +220,7 @@ func Test_initServerConfig(t *testing.T) { name: "reading multiple secrets with overrides: Threshold", args: args{ opts: new(chainlink.GeneralConfigOpts), - fileNames: []string{testtomlutils.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, + fileNames: []string{configtest.WriteTOMLFile(t, testConfigFileContents, "test.toml")}, secretsFiles: []string{ "../testdata/mergingsecretsdata/secrets-threshold.toml", "../testdata/mergingsecretsdata/secrets-threshold.toml", diff --git a/core/cmd/blocks_commands_test.go b/core/cmd/blocks_commands_test.go index a972df67d64..d0c0e118f9d 100644 --- a/core/cmd/blocks_commands_test.go +++ b/core/cmd/blocks_commands_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" "github.com/urfave/cli" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -24,7 +23,7 @@ func Test_ReplayFromBlock(t *testing.T) { client, _ := app.NewShellAndRenderer() set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.ReplayFromBlock, set, "") + flagSetApplyFromAction(client.ReplayFromBlock, set, "") //Incorrect block number require.NoError(t, set.Set("block-number", "0")) diff --git a/core/cmd/bridge_commands_test.go b/core/cmd/bridge_commands_test.go index 4f043ff87e8..fae5d68e678 100644 --- a/core/cmd/bridge_commands_test.go +++ b/core/cmd/bridge_commands_test.go @@ -111,7 +111,7 @@ func TestShell_ShowBridge(t *testing.T) { require.NoError(t, app.BridgeORM().CreateBridgeType(bt)) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.ShowBridge, set, "") + flagSetApplyFromAction(client.ShowBridge, set, "") require.NoError(t, set.Parse([]string{bt.Name.String()})) @@ -148,7 +148,7 @@ func TestShell_CreateBridge(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { set := flag.NewFlagSet("bridge", 0) - cltest.FlagSetApplyFromAction(client.CreateBridge, set, "") + flagSetApplyFromAction(client.CreateBridge, set, "") require.NoError(t, set.Parse([]string{test.param})) @@ -177,7 +177,7 @@ func TestShell_RemoveBridge(t *testing.T) { require.NoError(t, err) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoveBridge, set, "") + flagSetApplyFromAction(client.RemoveBridge, set, "") require.NoError(t, set.Parse([]string{bt.Name.String()})) diff --git a/core/cmd/cosmos_chains_commands_test.go b/core/cmd/cosmos_chains_commands_test.go index 55e6a60d1ce..a0d2052d836 100644 --- a/core/cmd/cosmos_chains_commands_test.go +++ b/core/cmd/cosmos_chains_commands_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" @@ -17,7 +17,7 @@ func TestShell_IndexCosmosChains(t *testing.T) { t.Parallel() chainID := cosmostest.RandomChainID() - chain := cosmos.CosmosConfig{ + chain := coscfg.TOMLConfig{ ChainID: ptr(chainID), Enabled: ptr(true), } diff --git a/core/cmd/cosmos_keys_commands_test.go b/core/cmd/cosmos_keys_commands_test.go index 05a26fe84d7..2cab11379d0 100644 --- a/core/cmd/cosmos_keys_commands_test.go +++ b/core/cmd/cosmos_keys_commands_test.go @@ -95,7 +95,7 @@ func TestShell_CosmosKeys(t *testing.T) { require.NoError(t, err) requireCosmosKeyCount(t, app, 1) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(cmd.NewCosmosKeysClient(client).DeleteKey, set, "cosmos") + flagSetApplyFromAction(cmd.NewCosmosKeysClient(client).DeleteKey, set, "cosmos") strID := key.ID() require.NoError(tt, set.Set("yes", "true")) @@ -121,7 +121,7 @@ func TestShell_CosmosKeys(t *testing.T) { // Export test invalid id set := flag.NewFlagSet("test Cosmos export", 0) - cltest.FlagSetApplyFromAction(cmd.NewCosmosKeysClient(client).ExportKey, set, "cosmos") + flagSetApplyFromAction(cmd.NewCosmosKeysClient(client).ExportKey, set, "cosmos") require.NoError(tt, set.Parse([]string{"0"})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -135,7 +135,7 @@ func TestShell_CosmosKeys(t *testing.T) { // Export test set = flag.NewFlagSet("test Cosmos export", 0) - cltest.FlagSetApplyFromAction(cmd.NewCosmosKeysClient(client).ExportKey, set, "cosmos") + flagSetApplyFromAction(cmd.NewCosmosKeysClient(client).ExportKey, set, "cosmos") require.NoError(tt, set.Parse([]string{fmt.Sprint(key.ID())})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -150,7 +150,7 @@ func TestShell_CosmosKeys(t *testing.T) { requireCosmosKeyCount(t, app, 0) set = flag.NewFlagSet("test Cosmos import", 0) - cltest.FlagSetApplyFromAction(cmd.NewCosmosKeysClient(client).ImportKey, set, "cosmos") + flagSetApplyFromAction(cmd.NewCosmosKeysClient(client).ImportKey, set, "cosmos") require.NoError(tt, set.Parse([]string{keyName})) require.NoError(tt, set.Set("old-password", "../internal/fixtures/incorrect_password.txt")) diff --git a/core/cmd/cosmos_node_commands_test.go b/core/cmd/cosmos_node_commands_test.go index c19749ecd12..728be9396f9 100644 --- a/core/cmd/cosmos_node_commands_test.go +++ b/core/cmd/cosmos_node_commands_test.go @@ -9,18 +9,16 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/config" coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - "github.com/smartcontractkit/chainlink-relay/pkg/utils" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" ) -func cosmosStartNewApplication(t *testing.T, cfgs ...*cosmos.CosmosConfig) *cltest.TestApplication { +func cosmosStartNewApplication(t *testing.T, cfgs ...*coscfg.TOMLConfig) *cltest.TestApplication { for i := range cfgs { cfgs[i].SetDefaults() } @@ -36,12 +34,12 @@ func TestShell_IndexCosmosNodes(t *testing.T) { chainID := cosmostest.RandomChainID() node := coscfg.Node{ Name: ptr("second"), - TendermintURL: utils.MustParseURL("http://tender.mint.test/bombay-12"), + TendermintURL: config.MustParseURL("http://tender.mint.test/bombay-12"), } - chain := cosmos.CosmosConfig{ + chain := coscfg.TOMLConfig{ ChainID: ptr(chainID), Enabled: ptr(true), - Nodes: cosmos.CosmosNodes{&node}, + Nodes: coscfg.Nodes{&node}, } app := cosmosStartNewApplication(t, &chain) client, r := app.NewShellAndRenderer() diff --git a/core/cmd/cosmos_transaction_commands_test.go b/core/cmd/cosmos_transaction_commands_test.go index 04858d2956a..5b5454eed44 100644 --- a/core/cmd/cosmos_transaction_commands_test.go +++ b/core/cmd/cosmos_transaction_commands_test.go @@ -5,7 +5,6 @@ package cmd_test import ( "flag" "os" - "strconv" "testing" "time" @@ -14,21 +13,16 @@ import ( "github.com/stretchr/testify/require" "github.com/urfave/cli" + "github.com/smartcontractkit/chainlink-common/pkg/config" cosmosclient "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/client" coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" cosmosdb "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/db" "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/denom" "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/params" - "github.com/smartcontractkit/chainlink-relay/pkg/utils" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos/cosmostxm" "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/cosmoskey" ) @@ -52,13 +46,13 @@ func TestShell_SendCosmosCoins(t *testing.T) { cosmosChain.SetDefaults() accounts, _, url := cosmosclient.SetupLocalCosmosNode(t, chainID, *cosmosChain.GasToken) require.Greater(t, len(accounts), 1) - nodes := cosmos.CosmosNodes{ + nodes := coscfg.Nodes{ &coscfg.Node{ Name: ptr("random"), - TendermintURL: utils.MustParseURL(url), + TendermintURL: config.MustParseURL(url), }, } - chainConfig := cosmos.CosmosConfig{ChainID: &chainID, Enabled: ptr(true), Chain: cosmosChain, Nodes: nodes} + chainConfig := coscfg.TOMLConfig{ChainID: &chainID, Enabled: ptr(true), Chain: cosmosChain, Nodes: nodes} app := cosmosStartNewApplication(t, &chainConfig) from := accounts[0] @@ -78,9 +72,6 @@ func TestShell_SendCosmosCoins(t *testing.T) { return coin.IsPositive() }, time.Minute, 5*time.Second) - db := app.GetSqlxDB() - orm := cosmostxm.NewORM(chainID, db, logger.TestLogger(t), pgtest.NewQConfig(true)) - client, r := app.NewShellAndRenderer() cliapp := cli.NewApp() @@ -101,7 +92,7 @@ func TestShell_SendCosmosCoins(t *testing.T) { require.NoError(t, err) set := flag.NewFlagSet("sendcosmoscoins", 0) - cltest.FlagSetApplyFromAction(client.CosmosSendNativeToken, set, "cosmos") + flagSetApplyFromAction(client.CosmosSendNativeToken, set, "cosmos") require.NoError(t, set.Set("id", chainID)) require.NoError(t, set.Parse([]string{nativeToken, tt.amount, from.Address.String(), to.Address.String()})) @@ -123,49 +114,18 @@ func TestShell_SendCosmosCoins(t *testing.T) { require.NotEmpty(t, renderedMsg.ID) assert.Equal(t, string(cosmosdb.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, cosmosdb.Errored, msg.State) - switch msg.State { - case cosmosdb.Unstarted: - assert.Nil(t, msg.TxHash) - case cosmosdb.Broadcasted, cosmosdb.Confirmed: - assert.NotNil(t, msg.TxHash) - } - - // Maybe wait for confirmation - if msg.State != cosmosdb.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 == cosmosdb.Confirmed - } - } - return false - }, testutils.WaitTimeout(t), time.Second) - require.NotNil(t, msg.TxHash) - } // Check balance - endBal, err := reader.Balance(from.Address, *cosmosChain.GasToken) + sent, err := denom.ConvertDecCoinToDenom(sdk.NewDecCoinFromDec(nativeToken, sdk.MustNewDecFromStr(tt.amount)), *cosmosChain.GasToken) require.NoError(t, err) - if assert.NotNil(t, startBal) && assert.NotNil(t, endBal) { - diff := startBal.Sub(*endBal).Amount - sent, err := denom.ConvertDecCoinToDenom(sdk.NewDecCoinFromDec(nativeToken, sdk.MustNewDecFromStr(tt.amount)), *cosmosChain.GasToken) + expBal := startBal.Sub(sent) + + testutils.AssertEventually(t, func() bool { + endBal, err := reader.Balance(from.Address, *cosmosChain.GasToken) require.NoError(t, err) - if assert.True(t, diff.IsInt64()) && assert.True(t, sent.Amount.IsInt64()) { - require.Greater(t, diff.Int64(), sent.Amount.Int64()) - } - } + t.Logf("%s <= %s", endBal, expBal) + return endBal.IsLTE(expBal) + }) }) } } diff --git a/core/cmd/csa_keys_commands_test.go b/core/cmd/csa_keys_commands_test.go index 23749476ac8..869608fa72b 100644 --- a/core/cmd/csa_keys_commands_test.go +++ b/core/cmd/csa_keys_commands_test.go @@ -98,7 +98,7 @@ func TestShell_ImportExportCsaKey(t *testing.T) { // Export test invalid id set := flag.NewFlagSet("test CSA export", 0) - cltest.FlagSetApplyFromAction(client.ExportCSAKey, set, "") + flagSetApplyFromAction(client.ExportCSAKey, set, "") require.NoError(t, set.Parse([]string{"0"})) require.NoError(t, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -111,7 +111,7 @@ func TestShell_ImportExportCsaKey(t *testing.T) { // Export test set = flag.NewFlagSet("test CSA export", 0) - cltest.FlagSetApplyFromAction(client.ExportCSAKey, set, "") + flagSetApplyFromAction(client.ExportCSAKey, set, "") require.NoError(t, set.Parse([]string{fmt.Sprint(key.ID())})) require.NoError(t, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -127,7 +127,7 @@ func TestShell_ImportExportCsaKey(t *testing.T) { //Import test set = flag.NewFlagSet("test CSA import", 0) - cltest.FlagSetApplyFromAction(client.ImportCSAKey, set, "") + flagSetApplyFromAction(client.ImportCSAKey, set, "") require.NoError(t, set.Parse([]string{keyName})) require.NoError(t, set.Set("old-password", "../internal/fixtures/incorrect_password.txt")) diff --git a/core/cmd/dkgencrypt_keys_commands_test.go b/core/cmd/dkgencrypt_keys_commands_test.go index 61e343569cb..a7505ce46dc 100644 --- a/core/cmd/dkgencrypt_keys_commands_test.go +++ b/core/cmd/dkgencrypt_keys_commands_test.go @@ -94,7 +94,7 @@ func TestShell_DKGEncryptKeys(t *testing.T) { assert.NoError(tt, err) requireDKGEncryptKeyCount(tt, app, 1) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(cmd.NewDKGEncryptKeysClient(client).DeleteKey, set, "") + flagSetApplyFromAction(cmd.NewDKGEncryptKeysClient(client).DeleteKey, set, "") require.NoError(tt, set.Set("yes", "true")) @@ -122,7 +122,7 @@ func TestShell_DKGEncryptKeys(t *testing.T) { // Export test invalid id set := flag.NewFlagSet("test DKGEncrypt export", 0) - cltest.FlagSetApplyFromAction(cmd.NewDKGEncryptKeysClient(client).ExportKey, set, "") + flagSetApplyFromAction(cmd.NewDKGEncryptKeysClient(client).ExportKey, set, "") require.NoError(tt, set.Parse([]string{"0"})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -135,7 +135,7 @@ func TestShell_DKGEncryptKeys(t *testing.T) { // Export test set = flag.NewFlagSet("test DKGEncrypt export", 0) - cltest.FlagSetApplyFromAction(cmd.NewDKGEncryptKeysClient(client).ExportKey, set, "") + flagSetApplyFromAction(cmd.NewDKGEncryptKeysClient(client).ExportKey, set, "") require.NoError(tt, set.Parse([]string{fmt.Sprint(key.ID())})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -151,7 +151,7 @@ func TestShell_DKGEncryptKeys(t *testing.T) { //Import test set = flag.NewFlagSet("test DKGEncrypt import", 0) - cltest.FlagSetApplyFromAction(cmd.NewDKGEncryptKeysClient(client).ImportKey, set, "") + flagSetApplyFromAction(cmd.NewDKGEncryptKeysClient(client).ImportKey, set, "") require.NoError(tt, set.Parse([]string{keyName})) require.NoError(tt, set.Set("old-password", "../internal/fixtures/incorrect_password.txt")) diff --git a/core/cmd/dkgsign_keys_commands_test.go b/core/cmd/dkgsign_keys_commands_test.go index 27413e6ae7b..1948800d677 100644 --- a/core/cmd/dkgsign_keys_commands_test.go +++ b/core/cmd/dkgsign_keys_commands_test.go @@ -94,7 +94,7 @@ func TestShell_DKGSignKeys(t *testing.T) { assert.NoError(tt, err) requireDKGSignKeyCount(tt, app, 1) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(cmd.NewDKGSignKeysClient(client).DeleteKey, set, "") + flagSetApplyFromAction(cmd.NewDKGSignKeysClient(client).DeleteKey, set, "") require.NoError(tt, set.Set("yes", "true")) strID := key.ID() @@ -121,7 +121,7 @@ func TestShell_DKGSignKeys(t *testing.T) { // Export test invalid id set := flag.NewFlagSet("test DKGSign export", 0) - cltest.FlagSetApplyFromAction(cmd.NewDKGSignKeysClient(client).ExportKey, set, "") + flagSetApplyFromAction(cmd.NewDKGSignKeysClient(client).ExportKey, set, "") require.NoError(tt, set.Parse([]string{"0"})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -134,7 +134,7 @@ func TestShell_DKGSignKeys(t *testing.T) { // Export test set = flag.NewFlagSet("test DKGSign export", 0) - cltest.FlagSetApplyFromAction(cmd.NewDKGSignKeysClient(client).ExportKey, set, "") + flagSetApplyFromAction(cmd.NewDKGSignKeysClient(client).ExportKey, set, "") require.NoError(tt, set.Parse([]string{fmt.Sprint(key.ID())})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -149,7 +149,7 @@ func TestShell_DKGSignKeys(t *testing.T) { requireDKGSignKeyCount(tt, app, 0) set = flag.NewFlagSet("test DKGSign import", 0) - cltest.FlagSetApplyFromAction(cmd.NewDKGSignKeysClient(client).ImportKey, set, "") + flagSetApplyFromAction(cmd.NewDKGSignKeysClient(client).ImportKey, set, "") require.NoError(tt, set.Parse([]string{keyName})) require.NoError(tt, set.Set("old-password", "../internal/fixtures/incorrect_password.txt")) diff --git a/core/cmd/eth_keys_commands_test.go b/core/cmd/eth_keys_commands_test.go index 630e76783a2..3eb45e27bd0 100644 --- a/core/cmd/eth_keys_commands_test.go +++ b/core/cmd/eth_keys_commands_test.go @@ -12,7 +12,8 @@ import ( "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/assets" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" @@ -34,7 +35,7 @@ func TestEthKeysPresenter_RenderTable(t *testing.T) { var ( address = "0x5431F5F973781809D18643b87B44921b11355d81" ethBalance = assets.NewEth(1) - linkBalance = assets.NewLinkFromJuels(2) + linkBalance = commonassets.NewLinkFromJuels(2) isDisabled = true createdAt = time.Now() updatedAt = time.Now().Add(time.Second) @@ -89,7 +90,7 @@ func TestShell_ListETHKeys(t *testing.T) { ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) - ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(assets.NewLinkFromJuels(13), nil) + ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(commonassets.NewLinkFromJuels(13), nil) ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].Enabled = ptr(true) @@ -168,7 +169,7 @@ func TestShell_CreateETHKey(t *testing.T) { ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) - ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(assets.NewLinkFromJuels(42), nil) + ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(commonassets.NewLinkFromJuels(42), nil) ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { @@ -190,7 +191,7 @@ func TestShell_CreateETHKey(t *testing.T) { id := big.NewInt(0) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.CreateETHKey, set, "") + flagSetApplyFromAction(client.CreateETHKey, set, "") require.NoError(t, set.Set("evm-chain-id", testutils.FixtureChainID.String())) @@ -223,7 +224,7 @@ func TestShell_DeleteETHKey(t *testing.T) { // Delete the key set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteETHKey, set, "") + flagSetApplyFromAction(client.DeleteETHKey, set, "") require.NoError(t, set.Set("yes", "true")) require.NoError(t, set.Parse([]string{key.Address.Hex()})) @@ -243,7 +244,7 @@ func TestShell_ImportExportETHKey_NoChains(t *testing.T) { ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) - ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(assets.NewLinkFromJuels(42), nil) + ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(commonassets.NewLinkFromJuels(42), nil) ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].Enabled = ptr(true) @@ -256,7 +257,7 @@ func TestShell_ImportExportETHKey_NoChains(t *testing.T) { ethKeyStore := app.GetKeyStore().Eth() set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") require.NoError(t, set.Set("file", "internal/fixtures/apicredentials")) require.NoError(t, set.Set("bypass-version-check", "true")) @@ -280,7 +281,7 @@ func TestShell_ImportExportETHKey_NoChains(t *testing.T) { defer os.RemoveAll(testdir) keyfilepath := filepath.Join(testdir, "key") set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.ExportETHKey, set, "") + flagSetApplyFromAction(client.ExportETHKey, set, "") require.NoError(t, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) require.NoError(t, set.Set("output", keyfilepath)) @@ -292,7 +293,7 @@ func TestShell_ImportExportETHKey_NoChains(t *testing.T) { // Delete the key set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteETHKey, set, "") + flagSetApplyFromAction(client.DeleteETHKey, set, "") require.NoError(t, set.Set("yes", "true")) require.NoError(t, set.Parse([]string{address})) @@ -307,7 +308,7 @@ func TestShell_ImportExportETHKey_NoChains(t *testing.T) { // Import the key set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.ImportETHKey, set, "") + flagSetApplyFromAction(client.ImportETHKey, set, "") require.NoError(t, set.Set("evmChainID", testutils.FixtureChainID.String())) require.NoError(t, set.Set("old-password", "../internal/fixtures/incorrect_password.txt")) @@ -320,7 +321,7 @@ func TestShell_ImportExportETHKey_NoChains(t *testing.T) { r.Renders = nil set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.ListETHKeys, set, "") + flagSetApplyFromAction(client.ListETHKeys, set, "") c = cli.NewContext(nil, set, nil) err = client.ListETHKeys(c) require.NoError(t, err) @@ -331,7 +332,7 @@ func TestShell_ImportExportETHKey_NoChains(t *testing.T) { // Export test invalid id keyName := keyNameForTest(t) set = flag.NewFlagSet("test Eth export invalid id", 0) - cltest.FlagSetApplyFromAction(client.ExportETHKey, set, "") + flagSetApplyFromAction(client.ExportETHKey, set, "") require.NoError(t, set.Parse([]string{"999"})) require.NoError(t, set.Set("new-password", "../internal/fixtures/apicredentials")) @@ -361,10 +362,10 @@ func TestShell_ImportExportETHKey_WithChains(t *testing.T) { ethClient.On("Dial", mock.Anything).Maybe() ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) - ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(assets.NewLinkFromJuels(42), nil) + ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(commonassets.NewLinkFromJuels(42), nil) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") require.NoError(t, set.Set("file", "internal/fixtures/apicredentials")) require.NoError(t, set.Set("bypass-version-check", "true")) @@ -388,7 +389,7 @@ func TestShell_ImportExportETHKey_WithChains(t *testing.T) { defer os.RemoveAll(testdir) keyfilepath := filepath.Join(testdir, "key") set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.ExportETHKey, set, "") + flagSetApplyFromAction(client.ExportETHKey, set, "") require.NoError(t, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) require.NoError(t, set.Set("output", keyfilepath)) @@ -400,7 +401,7 @@ func TestShell_ImportExportETHKey_WithChains(t *testing.T) { // Delete the key set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteETHKey, set, "") + flagSetApplyFromAction(client.DeleteETHKey, set, "") require.NoError(t, set.Set("yes", "true")) require.NoError(t, set.Parse([]string{address})) @@ -413,7 +414,7 @@ func TestShell_ImportExportETHKey_WithChains(t *testing.T) { // Import the key set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.ImportETHKey, set, "") + flagSetApplyFromAction(client.ImportETHKey, set, "") require.NoError(t, set.Set("evmChainID", testutils.FixtureChainID.String())) require.NoError(t, set.Set("evmChainID", testutils.FixtureChainID.String())) @@ -427,7 +428,7 @@ func TestShell_ImportExportETHKey_WithChains(t *testing.T) { r.Renders = nil set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.ListETHKeys, set, "") + flagSetApplyFromAction(client.ListETHKeys, set, "") c = cli.NewContext(nil, set, nil) err = client.ListETHKeys(c) require.NoError(t, err) @@ -438,7 +439,7 @@ func TestShell_ImportExportETHKey_WithChains(t *testing.T) { // Export test invalid id keyName := keyNameForTest(t) set = flag.NewFlagSet("test Eth export invalid id", 0) - cltest.FlagSetApplyFromAction(client.ExportETHKey, set, "") + flagSetApplyFromAction(client.ExportETHKey, set, "") require.NoError(t, set.Parse([]string{"999"})) require.NoError(t, set.Set("new-password", "../internal/fixtures/apicredentials")) diff --git a/core/cmd/evm_transaction_commands.go b/core/cmd/evm_transaction_commands.go index 68fffde303e..d702bc3b799 100644 --- a/core/cmd/evm_transaction_commands.go +++ b/core/cmd/evm_transaction_commands.go @@ -10,7 +10,7 @@ import ( "github.com/urfave/cli" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/v2/core/utils/stringutils" diff --git a/core/cmd/evm_transaction_commands_test.go b/core/cmd/evm_transaction_commands_test.go index 484b1ccd3da..6c079a12495 100644 --- a/core/cmd/evm_transaction_commands_test.go +++ b/core/cmd/evm_transaction_commands_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/urfave/cli" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/cmd" @@ -37,7 +37,7 @@ func TestShell_IndexTransactions(t *testing.T) { // page 1 set := flag.NewFlagSet("test transactions", 0) - cltest.FlagSetApplyFromAction(client.IndexTransactions, set, "") + flagSetApplyFromAction(client.IndexTransactions, set, "") require.NoError(t, set.Set("page", "1")) @@ -51,7 +51,7 @@ func TestShell_IndexTransactions(t *testing.T) { // page 2 which doesn't exist set = flag.NewFlagSet("test txattempts", 0) - cltest.FlagSetApplyFromAction(client.IndexTransactions, set, "") + flagSetApplyFromAction(client.IndexTransactions, set, "") require.NoError(t, set.Set("page", "2")) @@ -77,7 +77,7 @@ func TestShell_ShowTransaction(t *testing.T) { attempt := tx.TxAttempts[0] set := flag.NewFlagSet("test get tx", 0) - cltest.FlagSetApplyFromAction(client.ShowTransaction, set, "") + flagSetApplyFromAction(client.ShowTransaction, set, "") require.NoError(t, set.Parse([]string{attempt.Hash.String()})) @@ -101,7 +101,7 @@ func TestShell_IndexTxAttempts(t *testing.T) { // page 1 set := flag.NewFlagSet("test txattempts", 0) - cltest.FlagSetApplyFromAction(client.IndexTxAttempts, set, "") + flagSetApplyFromAction(client.IndexTxAttempts, set, "") require.NoError(t, set.Set("page", "1")) @@ -115,7 +115,7 @@ func TestShell_IndexTxAttempts(t *testing.T) { // page 2 which doesn't exist set = flag.NewFlagSet("test transactions", 0) - cltest.FlagSetApplyFromAction(client.IndexTxAttempts, set, "") + flagSetApplyFromAction(client.IndexTxAttempts, set, "") require.NoError(t, set.Set("page", "2")) @@ -158,7 +158,7 @@ func TestShell_SendEther_From_Txm(t *testing.T) { db := app.GetSqlxDB() set := flag.NewFlagSet("sendether", 0) - cltest.FlagSetApplyFromAction(client.SendEther, set, "") + flagSetApplyFromAction(client.SendEther, set, "") amount := "100.5" to := "0x342156c8d3bA54Abc67920d35ba1d1e67201aC9C" @@ -218,7 +218,7 @@ func TestShell_SendEther_From_Txm_WEI(t *testing.T) { db := app.GetSqlxDB() set := flag.NewFlagSet("sendether", 0) - cltest.FlagSetApplyFromAction(client.SendEther, set, "") + flagSetApplyFromAction(client.SendEther, set, "") require.NoError(t, set.Set("id", testutils.FixtureChainID.String())) require.NoError(t, set.Set("wei", "false")) diff --git a/core/cmd/forwarders_commands_test.go b/core/cmd/forwarders_commands_test.go index b08d94f64dc..179216b8e41 100644 --- a/core/cmd/forwarders_commands_test.go +++ b/core/cmd/forwarders_commands_test.go @@ -73,7 +73,7 @@ func TestShell_TrackEVMForwarder(t *testing.T) { // Create the fwdr set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.TrackForwarder, set, "") + flagSetApplyFromAction(client.TrackForwarder, set, "") require.NoError(t, set.Set("address", utils.RandomAddress().Hex())) require.NoError(t, set.Set("evm-chain-id", id.String())) @@ -92,7 +92,7 @@ func TestShell_TrackEVMForwarder(t *testing.T) { // Delete fwdr set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteForwarder, set, "") + flagSetApplyFromAction(client.DeleteForwarder, set, "") require.NoError(t, set.Parse([]string{createOutput.ID})) @@ -118,7 +118,7 @@ func TestShell_TrackEVMForwarder_BadAddress(t *testing.T) { // Create the fwdr set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.TrackForwarder, set, "") + flagSetApplyFromAction(client.TrackForwarder, set, "") require.NoError(t, set.Set("address", "0xWrongFormatAddress")) require.NoError(t, set.Set("evm-chain-id", id.String())) @@ -137,7 +137,7 @@ func TestShell_DeleteEVMForwarders_MissingFwdId(t *testing.T) { // Delete fwdr without id set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteForwarder, set, "") + flagSetApplyFromAction(client.DeleteForwarder, set, "") 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/jobs_commands_test.go b/core/cmd/jobs_commands_test.go index b83b17f0be6..04908e18ff3 100644 --- a/core/cmd/jobs_commands_test.go +++ b/core/cmd/jobs_commands_test.go @@ -313,7 +313,7 @@ func TestShell_ListFindJobs(t *testing.T) { // Create the job fs := flag.NewFlagSet("", flag.ExitOnError) - cltest.FlagSetApplyFromAction(client.CreateJob, fs, "") + flagSetApplyFromAction(client.CreateJob, fs, "") require.NoError(t, fs.Parse([]string{getDirectRequestSpec()})) @@ -339,7 +339,7 @@ func TestShell_ShowJob(t *testing.T) { // Create the job fs := flag.NewFlagSet("", flag.ExitOnError) - cltest.FlagSetApplyFromAction(client.CreateJob, fs, "") + flagSetApplyFromAction(client.CreateJob, fs, "") require.NoError(t, fs.Parse([]string{getDirectRequestSpec()})) @@ -382,7 +382,7 @@ func TestShell_CreateJobV2(t *testing.T) { requireJobsCount(t, app.JobORM(), 0) fs := flag.NewFlagSet("", flag.ExitOnError) - cltest.FlagSetApplyFromAction(client.CreateJob, fs, "") + flagSetApplyFromAction(client.CreateJob, fs, "") nameAndExternalJobID := uuid.New() spec := fmt.Sprintf(ocrBootstrapSpec, nameAndExternalJobID, nameAndExternalJobID) @@ -413,7 +413,7 @@ func TestShell_DeleteJob(t *testing.T) { // Create the job fs := flag.NewFlagSet("", flag.ExitOnError) - cltest.FlagSetApplyFromAction(client.CreateJob, fs, "") + flagSetApplyFromAction(client.CreateJob, fs, "") require.NoError(t, fs.Parse([]string{getDirectRequestSpec()})) @@ -432,12 +432,12 @@ func TestShell_DeleteJob(t *testing.T) { // Must supply job id set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteJob, set, "") + flagSetApplyFromAction(client.DeleteJob, set, "") c := cli.NewContext(nil, set, nil) require.Equal(t, "must pass the job id to be archived", client.DeleteJob(c).Error()) set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteJob, set, "") + flagSetApplyFromAction(client.DeleteJob, set, "") require.NoError(t, set.Parse([]string{output.ID})) diff --git a/core/cmd/ocr2_keys_commands_test.go b/core/cmd/ocr2_keys_commands_test.go index 88ea426747e..dd2ac2544da 100644 --- a/core/cmd/ocr2_keys_commands_test.go +++ b/core/cmd/ocr2_keys_commands_test.go @@ -97,7 +97,7 @@ func TestShell_OCR2Keys(t *testing.T) { client, r := app.NewShellAndRenderer() set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.CreateOCR2KeyBundle, set, "") + flagSetApplyFromAction(client.CreateOCR2KeyBundle, set, "") require.NoError(tt, set.Parse([]string{"evm"})) @@ -119,7 +119,7 @@ func TestShell_OCR2Keys(t *testing.T) { require.NoError(t, err) requireOCR2KeyCount(t, app, 1) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteOCR2KeyBundle, set, "") + flagSetApplyFromAction(client.DeleteOCR2KeyBundle, set, "") require.NoError(tt, set.Parse([]string{key.ID()})) require.NoError(tt, set.Set("yes", "true")) @@ -147,7 +147,7 @@ func TestShell_OCR2Keys(t *testing.T) { // Export test invalid id set := flag.NewFlagSet("test OCR2 export", 0) - cltest.FlagSetApplyFromAction(client.ExportOCR2Key, set, "") + flagSetApplyFromAction(client.ExportOCR2Key, set, "") require.NoError(tt, set.Parse([]string{"0"})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/new_password.txt")) @@ -160,7 +160,7 @@ func TestShell_OCR2Keys(t *testing.T) { // Export set = flag.NewFlagSet("test OCR2 export", 0) - cltest.FlagSetApplyFromAction(client.ExportOCR2Key, set, "") + flagSetApplyFromAction(client.ExportOCR2Key, set, "") require.NoError(tt, set.Parse([]string{key.ID()})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/new_password.txt")) @@ -175,7 +175,7 @@ func TestShell_OCR2Keys(t *testing.T) { requireOCR2KeyCount(t, app, 0) set = flag.NewFlagSet("test OCR2 import", 0) - cltest.FlagSetApplyFromAction(client.ImportOCR2Key, set, "") + flagSetApplyFromAction(client.ImportOCR2Key, set, "") require.NoError(tt, set.Parse([]string{keyName})) require.NoError(tt, set.Set("old-password", "../internal/fixtures/new_password.txt")) diff --git a/core/cmd/ocr2vrf_configure_commands.go b/core/cmd/ocr2vrf_configure_commands.go index a3feddd611d..cf014d5e5dc 100644 --- a/core/cmd/ocr2vrf_configure_commands.go +++ b/core/cmd/ocr2vrf_configure_commands.go @@ -14,7 +14,7 @@ import ( "github.com/pkg/errors" "github.com/urfave/cli" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_forwarder" @@ -126,6 +126,7 @@ chainID = %d const forwarderAdditionalEOACount = 4 func (s *Shell) ConfigureOCR2VRFNode(c *cli.Context, owner *bind.TransactOpts, ec *ethclient.Client) (*SetupOCR2VRFNodePayload, error) { + ctx := s.ctx() lggr := logger.Sugared(s.Logger.Named("ConfigureOCR2VRFNode")) lggr.Infow( fmt.Sprintf("Configuring Chainlink Node for job type %s %s at commit %s", c.String("job-type"), static.Version, static.Sha), @@ -156,15 +157,13 @@ func (s *Shell) ConfigureOCR2VRFNode(c *cli.Context, owner *bind.TransactOpts, e cfg := s.Config ldb := pg.NewLockedDB(cfg.AppID(), cfg.Database(), cfg.Database().Lock(), lggr) - rootCtx, cancel := context.WithCancel(context.Background()) - defer cancel() - if err = ldb.Open(rootCtx); err != nil { + if err = ldb.Open(ctx); err != nil { return nil, s.errorOut(errors.Wrap(err, "opening db")) } defer lggr.ErrorIfFn(ldb.Close, "Error closing db") - app, err := s.AppFactory.NewApplication(rootCtx, s.Config, lggr, ldb.DB()) + app, err := s.AppFactory.NewApplication(ctx, s.Config, lggr, ldb.DB()) if err != nil { return nil, s.errorOut(errors.Wrap(err, "fatal error instantiating application")) } @@ -179,7 +178,7 @@ func (s *Shell) ConfigureOCR2VRFNode(c *cli.Context, owner *bind.TransactOpts, e } // Start application. - err = app.Start(rootCtx) + err = app.Start(ctx) if err != nil { return nil, s.errorOut(err) } @@ -243,10 +242,10 @@ func (s *Shell) ConfigureOCR2VRFNode(c *cli.Context, owner *bind.TransactOpts, e if c.Bool("isBootstrapper") { // Set up bootstrapper job if bootstrapper. - err = createBootstrapperJob(lggr, c, app) + err = createBootstrapperJob(ctx, lggr, c, app) } else if c.String("job-type") == "DKG" { // Set up DKG job. - err = createDKGJob(lggr, app, dkgTemplateArgs{ + err = createDKGJob(ctx, lggr, app, dkgTemplateArgs{ contractID: c.String("contractID"), ocrKeyBundleID: ocr2.ID(), p2pv2BootstrapperPeerID: peerID, @@ -260,7 +259,7 @@ func (s *Shell) ConfigureOCR2VRFNode(c *cli.Context, owner *bind.TransactOpts, e }) } else if c.String("job-type") == "OCR2VRF" { // Set up OCR2VRF job. - err = createOCR2VRFJob(lggr, app, ocr2vrfTemplateArgs{ + err = createOCR2VRFJob(ctx, lggr, app, ocr2vrfTemplateArgs{ dkgTemplateArgs: dkgTemplateArgs{ contractID: c.String("dkg-address"), ocrKeyBundleID: ocr2.ID(), @@ -320,12 +319,13 @@ func (s *Shell) appendForwarders(chainID int64, ks keystore.Eth, sendingKeys []s } func (s *Shell) authorizeForwarder(c *cli.Context, db *sqlx.DB, lggr logger.Logger, chainID int64, ec *ethclient.Client, owner *bind.TransactOpts, sendingKeysAddresses []common.Address) error { + ctx := s.ctx() // Replace the transmitter ID with the forwarder address. forwarderAddress := c.String("forwarder-address") // We have to set the authorized senders on-chain here, otherwise the job spawner will fail as the // forwarder will not be recognized. - ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) + ctx, cancel := context.WithTimeout(ctx, 300*time.Second) defer cancel() f, err := authorized_forwarder.NewAuthorizedForwarder(common.HexToAddress(forwarderAddress), ec) if err != nil { @@ -400,7 +400,7 @@ func setupKeystore(cli *Shell, app chainlink.Application, keyStore keystore.Mast return nil } -func createBootstrapperJob(lggr logger.Logger, c *cli.Context, app chainlink.Application) error { +func createBootstrapperJob(ctx context.Context, lggr logger.Logger, c *cli.Context, app chainlink.Application) error { sp := fmt.Sprintf(BootstrapTemplate, c.Int64("chainID"), c.String("contractID"), @@ -418,7 +418,7 @@ func createBootstrapperJob(lggr logger.Logger, c *cli.Context, app chainlink.App } jb.BootstrapSpec = &os - err = app.AddJobV2(context.Background(), &jb) + err = app.AddJobV2(ctx, &jb) if err != nil { return errors.Wrap(err, "failed to add job") } @@ -430,7 +430,7 @@ func createBootstrapperJob(lggr logger.Logger, c *cli.Context, app chainlink.App return nil } -func createDKGJob(lggr logger.Logger, app chainlink.Application, args dkgTemplateArgs) error { +func createDKGJob(ctx context.Context, lggr logger.Logger, app chainlink.Application, args dkgTemplateArgs) error { sp := fmt.Sprintf(DKGTemplate, args.contractID, args.ocrKeyBundleID, @@ -455,7 +455,7 @@ func createDKGJob(lggr logger.Logger, app chainlink.Application, args dkgTemplat } jb.OCR2OracleSpec = &os - err = app.AddJobV2(context.Background(), &jb) + err = app.AddJobV2(ctx, &jb) if err != nil { return errors.Wrap(err, "failed to add job") } @@ -464,7 +464,7 @@ func createDKGJob(lggr logger.Logger, app chainlink.Application, args dkgTemplat return nil } -func createOCR2VRFJob(lggr logger.Logger, app chainlink.Application, args ocr2vrfTemplateArgs) error { +func createOCR2VRFJob(ctx context.Context, lggr logger.Logger, app chainlink.Application, args ocr2vrfTemplateArgs) error { var sendingKeysString = fmt.Sprintf(`"%s"`, args.sendingKeys[0]) for x := 1; x < len(args.sendingKeys); x++ { sendingKeysString = fmt.Sprintf(`%s,"%s"`, sendingKeysString, args.sendingKeys[x]) @@ -498,7 +498,7 @@ func createOCR2VRFJob(lggr logger.Logger, app chainlink.Application, args ocr2vr } jb.OCR2OracleSpec = &os - err = app.AddJobV2(context.Background(), &jb) + err = app.AddJobV2(ctx, &jb) if err != nil { return errors.Wrap(err, "failed to add job") } diff --git a/core/cmd/ocr_keys_commands_test.go b/core/cmd/ocr_keys_commands_test.go index 2ae765d4d2a..aeda9006610 100644 --- a/core/cmd/ocr_keys_commands_test.go +++ b/core/cmd/ocr_keys_commands_test.go @@ -111,7 +111,7 @@ func TestShell_DeleteOCRKeyBundle(t *testing.T) { requireOCRKeyCount(t, app, 1) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteOCRKeyBundle, set, "") + flagSetApplyFromAction(client.DeleteOCRKeyBundle, set, "") require.NoError(t, set.Parse([]string{key.ID()})) require.NoError(t, set.Set("yes", "true")) @@ -140,7 +140,7 @@ func TestShell_ImportExportOCRKey(t *testing.T) { // Export test invalid id set := flag.NewFlagSet("test OCR export", 0) - cltest.FlagSetApplyFromAction(client.ExportOCRKey, set, "") + flagSetApplyFromAction(client.ExportOCRKey, set, "") require.NoError(t, set.Parse([]string{"0"})) require.NoError(t, set.Set("new-password", "../internal/fixtures/new_password.txt")) @@ -153,7 +153,7 @@ func TestShell_ImportExportOCRKey(t *testing.T) { // Export set = flag.NewFlagSet("test OCR export", 0) - cltest.FlagSetApplyFromAction(client.ExportOCRKey, set, "") + flagSetApplyFromAction(client.ExportOCRKey, set, "") require.NoError(t, set.Parse([]string{key.ID()})) require.NoError(t, set.Set("new-password", "../internal/fixtures/new_password.txt")) @@ -168,7 +168,7 @@ func TestShell_ImportExportOCRKey(t *testing.T) { requireOCRKeyCount(t, app, 0) set = flag.NewFlagSet("test OCR import", 0) - cltest.FlagSetApplyFromAction(client.ImportOCRKey, set, "") + flagSetApplyFromAction(client.ImportOCRKey, set, "") require.NoError(t, set.Parse([]string{keyName})) require.NoError(t, set.Set("old-password", "../internal/fixtures/new_password.txt")) diff --git a/core/cmd/p2p_keys_commands_test.go b/core/cmd/p2p_keys_commands_test.go index cf107ba5071..683129fafa7 100644 --- a/core/cmd/p2p_keys_commands_test.go +++ b/core/cmd/p2p_keys_commands_test.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -24,7 +25,7 @@ func TestP2PKeyPresenter_RenderTable(t *testing.T) { var ( id = "1" - peerID = "12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X" + peerID = configtest.DefaultPeerID pubKey = "somepubkey" buffer = bytes.NewBufferString("") r = cmd.RendererTable{Writer: buffer} @@ -101,7 +102,7 @@ func TestShell_DeleteP2PKey(t *testing.T) { requireP2PKeyCount(t, app, 1) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteP2PKey, set, "") + flagSetApplyFromAction(client.DeleteP2PKey, set, "") require.NoError(t, set.Set("yes", "true")) @@ -131,7 +132,7 @@ func TestShell_ImportExportP2PKeyBundle(t *testing.T) { // Export test invalid id set := flag.NewFlagSet("test P2P export", 0) - cltest.FlagSetApplyFromAction(client.ExportP2PKey, set, "") + flagSetApplyFromAction(client.ExportP2PKey, set, "") require.NoError(t, set.Parse([]string{"0"})) require.NoError(t, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -144,7 +145,7 @@ func TestShell_ImportExportP2PKeyBundle(t *testing.T) { // Export test set = flag.NewFlagSet("test P2P export", 0) - cltest.FlagSetApplyFromAction(client.ExportP2PKey, set, "") + flagSetApplyFromAction(client.ExportP2PKey, set, "") require.NoError(t, set.Parse([]string{fmt.Sprint(key.ID())})) require.NoError(t, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -159,7 +160,7 @@ func TestShell_ImportExportP2PKeyBundle(t *testing.T) { requireP2PKeyCount(t, app, 0) set = flag.NewFlagSet("test P2P import", 0) - cltest.FlagSetApplyFromAction(client.ImportP2PKey, set, "") + flagSetApplyFromAction(client.ImportP2PKey, set, "") require.NoError(t, set.Parse([]string{keyName})) require.NoError(t, set.Set("old-password", "../internal/fixtures/incorrect_password.txt")) diff --git a/core/cmd/shell.go b/core/cmd/shell.go index fbbce4becbc..2e382be4ccf 100644 --- a/core/cmd/shell.go +++ b/core/cmd/shell.go @@ -29,12 +29,12 @@ import ( "go.uber.org/zap/zapcore" "golang.org/x/sync/errgroup" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink/v2/core/build" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" @@ -43,6 +43,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/periodicbackup" "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/cache" "github.com/smartcontractkit/chainlink/v2/core/services/versioning" "github.com/smartcontractkit/chainlink/v2/core/services/webhook" "github.com/smartcontractkit/chainlink/v2/core/sessions" @@ -60,7 +62,7 @@ var ( grpcOpts loop.GRPCOpts ) -func initGlobals(cfgProm config.Prometheus, cfgTracing config.Tracing) error { +func initGlobals(cfgProm config.Prometheus, cfgTracing config.Tracing, logger logger.Logger) error { // Avoid double initializations, but does not prevent relay methods from being called multiple times. var err error initGlobalsOnce.Do(func() { @@ -71,6 +73,7 @@ func initGlobals(cfgProm config.Prometheus, cfgTracing config.Tracing) error { CollectorTarget: cfgTracing.CollectorTarget(), NodeAttributes: cfgTracing.Attributes(), SamplingRatio: cfgTracing.SamplingRatio(), + OnDialError: func(error) { logger.Errorw("Failed to dial", "err", err) }, }) }) return err @@ -134,7 +137,7 @@ type ChainlinkAppFactory struct{} // NewApplication returns a new instance of the node with the given config. func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.GeneralConfig, appLggr logger.Logger, db *sqlx.DB) (app chainlink.Application, err error) { - err = initGlobals(cfg.Prometheus(), cfg.Tracing()) + err = initGlobals(cfg.Prometheus(), cfg.Tracing(), appLggr) if err != nil { appLggr.Errorf("Failed to initialize globals: %v", err) } @@ -144,7 +147,7 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G return nil, err } - err = handleNodeVersioning(db, appLggr, cfg.RootDir(), cfg.Database(), cfg.WebServer().HTTPPort()) + err = handleNodeVersioning(ctx, db, appLggr, cfg.RootDir(), cfg.Database(), cfg.WebServer().HTTPPort()) if err != nil { return nil, err } @@ -156,16 +159,23 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G eventBroadcaster := pg.NewEventBroadcaster(cfg.Database().URL(), dbListener.MinReconnectInterval(), dbListener.MaxReconnectDuration(), appLggr, cfg.AppID()) loopRegistry := plugins.NewLoopRegistry(appLggr, cfg.Tracing()) + mercuryPool := wsrpc.NewPool(appLggr, cache.Config{ + LatestReportTTL: cfg.Mercury().Cache().LatestReportTTL(), + MaxStaleAge: cfg.Mercury().Cache().MaxStaleAge(), + LatestReportDeadline: cfg.Mercury().Cache().LatestReportDeadline(), + }) + // create the relayer-chain interoperators from application configuration relayerFactory := chainlink.RelayerFactory{ Logger: appLggr, LoopRegistry: loopRegistry, GRPCOpts: grpcOpts, + MercuryPool: mercuryPool, } evmFactoryCfg := chainlink.EVMFactoryConfig{ CSAETHKeystore: keyStore, - ChainOpts: evm.ChainOpts{AppConfig: cfg, EventBroadcaster: eventBroadcaster, MailMon: mailMon, DB: db}, + ChainOpts: legacyevm.ChainOpts{AppConfig: cfg, EventBroadcaster: eventBroadcaster, MailMon: mailMon, DB: db}, } // evm always enabled for backward compatibility // TODO BCF-2510 this needs to change in order to clear the path for EVM extraction @@ -173,9 +183,10 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G if cfg.CosmosEnabled() { cosmosCfg := chainlink.CosmosFactoryConfig{ - Keystore: keyStore.Cosmos(), - CosmosConfigs: cfg.CosmosConfigs(), - EventBroadcaster: eventBroadcaster, + Keystore: keyStore.Cosmos(), + TOMLConfigs: cfg.CosmosConfigs(), + DB: db, + QConfig: cfg.Database(), } initOps = append(initOps, chainlink.InitCosmos(ctx, relayerFactory, cosmosCfg)) } @@ -225,11 +236,12 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G SecretGenerator: chainlink.FilePersistedSecretGenerator{}, LoopRegistry: loopRegistry, GRPCOpts: grpcOpts, + MercuryPool: mercuryPool, }) } // handleNodeVersioning is a setup-time helper to encapsulate version changes and db migration -func handleNodeVersioning(db *sqlx.DB, appLggr logger.Logger, rootDir string, cfg config.Database, healthReportPort uint16) error { +func handleNodeVersioning(ctx context.Context, db *sqlx.DB, appLggr logger.Logger, rootDir string, cfg config.Database, healthReportPort uint16) error { var err error // Set up the versioning Configs verORM := versioning.NewORM(db, appLggr, cfg.DefaultQueryTimeout()) @@ -260,7 +272,7 @@ func handleNodeVersioning(db *sqlx.DB, appLggr logger.Logger, rootDir string, cf // Migrate the database if cfg.MigrateDatabase() { - if err = migrate.Migrate(db.DB, appLggr); err != nil { + if err = migrate.Migrate(ctx, db.DB, appLggr); err != nil { return fmt.Errorf("initializeORM#Migrate: %w", err) } } @@ -777,8 +789,8 @@ func (f *fileSessionRequestBuilder) Build(file string) (sessions.SessionRequest, // APIInitializer is the interface used to create the API User credentials // needed to access the API. Does nothing if API user already exists. type APIInitializer interface { - // Initialize creates a new user for API access, or does nothing if one exists. - Initialize(orm sessions.ORM, lggr logger.Logger) (sessions.User, error) + // Initialize creates a new local Admin user for API access, or does nothing if one exists. + Initialize(orm sessions.BasicAdminUsersORM, lggr logger.Logger) (sessions.User, error) } type promptingAPIInitializer struct { @@ -792,11 +804,11 @@ func NewPromptingAPIInitializer(prompter Prompter) APIInitializer { } // Initialize uses the terminal to get credentials that it then saves in the store. -func (t *promptingAPIInitializer) Initialize(orm sessions.ORM, lggr logger.Logger) (sessions.User, error) { +func (t *promptingAPIInitializer) Initialize(orm sessions.BasicAdminUsersORM, lggr logger.Logger) (sessions.User, error) { // Load list of users to determine which to assume, or if a user needs to be created dbUsers, err := orm.ListUsers() if err != nil { - return sessions.User{}, err + return sessions.User{}, errors.Wrap(err, "Unable to List users for initialization") } // If there are no users in the database, prompt for initial admin user creation @@ -846,7 +858,7 @@ func NewFileAPIInitializer(file string) APIInitializer { return fileAPIInitializer{file: file} } -func (f fileAPIInitializer) Initialize(orm sessions.ORM, lggr logger.Logger) (sessions.User, error) { +func (f fileAPIInitializer) Initialize(orm sessions.BasicAdminUsersORM, lggr logger.Logger) (sessions.User, error) { request, err := credentialsFromFile(f.file, lggr) if err != nil { return sessions.User{}, err @@ -855,7 +867,7 @@ func (f fileAPIInitializer) Initialize(orm sessions.ORM, lggr logger.Logger) (se // Load list of users to determine which to assume, or if a user needs to be created dbUsers, err := orm.ListUsers() if err != nil { - return sessions.User{}, err + return sessions.User{}, errors.Wrap(err, "Unable to List users for initialization") } // If there are no users in the database, create initial admin user from session request from file creds diff --git a/core/cmd/shell_local.go b/core/cmd/shell_local.go index 401375238d8..a1f7fdb857c 100644 --- a/core/cmd/shell_local.go +++ b/core/cmd/shell_local.go @@ -2,6 +2,7 @@ package cmd import ( "context" + crand "crypto/rand" "database/sql" "fmt" "log" @@ -21,6 +22,7 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/fatih/color" + "github.com/lib/pq" "github.com/kylelemons/godebug/diff" "github.com/pkg/errors" @@ -29,10 +31,10 @@ import ( "golang.org/x/sync/errgroup" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/build" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -362,7 +364,8 @@ func (s *Shell) runNode(c *cli.Context) error { return s.errorOut(errors.Wrap(err, "fatal error instantiating application")) } - sessionORM := app.SessionORM() + // Local shell initialization always uses local auth users table for admin auth + authProviderORM := app.BasicAdminUsersORM() keyStore := app.GetKeyStore() err = s.KeyStoreAuthenticator.authenticate(keyStore, s.Config.Password()) if err != nil { @@ -449,11 +452,11 @@ func (s *Shell) runNode(c *cli.Context) error { } var user sessions.User - if user, err = NewFileAPIInitializer(c.String("api")).Initialize(sessionORM, lggr); err != nil { + if user, err = NewFileAPIInitializer(c.String("api")).Initialize(authProviderORM, lggr); err != nil { if !errors.Is(err, ErrNoCredentialFile) { return errors.Wrap(err, "error creating api initializer") } - if user, err = s.FallbackAPIInitializer.Initialize(sessionORM, lggr); err != nil { + if user, err = s.FallbackAPIInitializer.Initialize(authProviderORM, lggr); err != nil { if errors.Is(err, ErrorNoAPICredentialsAvailable) { return errors.WithStack(err) } @@ -557,6 +560,7 @@ func checkFilePermissions(lggr logger.Logger, rootDir string) error { // RebroadcastTransactions run locally to force manual rebroadcasting of // transactions in a given nonce range. func (s *Shell) RebroadcastTransactions(c *cli.Context) (err error) { + ctx := s.ctx() beginningNonce := c.Int64("beginningNonce") endingNonce := c.Int64("endingNonce") gasPriceWei := c.Uint64("gasPriceWei") @@ -586,7 +590,7 @@ func (s *Shell) RebroadcastTransactions(c *cli.Context) (err error) { } defer lggr.ErrorIfFn(db.Close, "Error closing db") - app, err := s.AppFactory.NewApplication(context.TODO(), s.Config, lggr, db) + app, err := s.AppFactory.NewApplication(ctx, s.Config, lggr, db) if err != nil { return s.errorOut(errors.Wrap(err, "fatal error instantiating application")) } @@ -602,7 +606,7 @@ func (s *Shell) RebroadcastTransactions(c *cli.Context) (err error) { ethClient := chain.Client() - err = ethClient.Dial(context.TODO()) + err = ethClient.Dial(ctx) if err != nil { return err } @@ -641,7 +645,7 @@ func (s *Shell) RebroadcastTransactions(c *cli.Context) (err error) { for i := int64(0); i < totalNonces; i++ { nonces[i] = evmtypes.Nonce(beginningNonce + i) } - err = ec.ForceRebroadcast(nonces, gas.EvmFee{Legacy: assets.NewWeiI(int64(gasPriceWei))}, address, uint32(overrideGasLimit)) + err = ec.ForceRebroadcast(ctx, nonces, gas.EvmFee{Legacy: assets.NewWeiI(int64(gasPriceWei))}, address, uint32(overrideGasLimit)) return s.errorOut(err) } @@ -704,9 +708,17 @@ func (s *Shell) validateDB(c *cli.Context) error { return s.configExitErr(s.Config.ValidateDB) } +// ctx returns a context.Context that will be cancelled when SIGINT|SIGTERM is received +func (s *Shell) ctx() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + go shutdown.HandleShutdown(func(_ string) { cancel() }) + return ctx +} + // ResetDatabase drops, creates and migrates the database specified by CL_DATABASE_URL or Database.URL // in secrets TOML. This is useful to set up the database for testing func (s *Shell) ResetDatabase(c *cli.Context) error { + ctx := s.ctx() cfg := s.Config.Database() parsed := cfg.URL() if parsed.String() == "" { @@ -726,7 +738,7 @@ func (s *Shell) ResetDatabase(c *cli.Context) error { return s.errorOut(err) } lggr.Debugf("Migrating database: %#v", parsed.String()) - if err := migrateDB(cfg, lggr); err != nil { + if err := migrateDB(ctx, cfg, lggr); err != nil { return s.errorOut(err) } schema, err := dumpSchema(parsed) @@ -735,7 +747,7 @@ func (s *Shell) ResetDatabase(c *cli.Context) error { } lggr.Debugf("Testing rollback and re-migrate for database: %#v", parsed.String()) var baseVersionID int64 = 54 - if err := downAndUpDB(cfg, lggr, baseVersionID); err != nil { + if err := downAndUpDB(ctx, cfg, lggr, baseVersionID); err != nil { return s.errorOut(err) } if err := checkSchema(parsed, schema); err != nil { @@ -768,11 +780,13 @@ func (s *Shell) PrepareTestDatabase(c *cli.Context) error { if userOnly { fixturePath = "../store/fixtures/users_only_fixture.sql" } - if err := insertFixtures(dbUrl, fixturePath); err != nil { + if err = insertFixtures(dbUrl, fixturePath); err != nil { return s.errorOut(err) } - - return s.errorOut(dropDanglingTestDBs(s.Logger, db)) + if err = dropDanglingTestDBs(s.Logger, db); err != nil { + return s.errorOut(err) + } + return s.errorOut(randomizeTestDBSequences(db)) } func dropDanglingTestDBs(lggr logger.Logger, db *sqlx.DB) (err error) { @@ -812,6 +826,53 @@ func dropDanglingTestDBs(lggr logger.Logger, db *sqlx.DB) (err error) { return } +type failedToRandomizeTestDBSequencesError struct{} + +func (m *failedToRandomizeTestDBSequencesError) Error() string { + return "failed to randomize test db sequences" +} + +// randomizeTestDBSequences randomizes sequenced table columns sequence +// This is necessary as to avoid false positives in some test cases. +func randomizeTestDBSequences(db *sqlx.DB) error { + // not ideal to hard code this, but also not safe to do it programmatically :( + schemas := pq.Array([]string{"public", "evm"}) + seqRows, err := db.Query(`SELECT sequence_schema, sequence_name, minimum_value FROM information_schema.sequences WHERE sequence_schema IN ($1)`, schemas) + if err != nil { + return fmt.Errorf("%s: error fetching sequences: %s", failedToRandomizeTestDBSequencesError{}, err) + } + + defer seqRows.Close() + for seqRows.Next() { + var sequenceSchema, sequenceName string + var minimumSequenceValue int64 + if err = seqRows.Scan(&sequenceSchema, &sequenceName, &minimumSequenceValue); err != nil { + return fmt.Errorf("%s: failed scanning sequence rows: %s", failedToRandomizeTestDBSequencesError{}, err) + } + + if sequenceName == "goose_migrations_id_seq" || sequenceName == "configurations_id_seq" { + continue + } + + var randNum *big.Int + randNum, err = crand.Int(crand.Reader, utils.NewBigI(10000).ToInt()) + if err != nil { + return fmt.Errorf("%s: failed to generate random number", failedToRandomizeTestDBSequencesError{}) + } + randNum.Add(randNum, big.NewInt(minimumSequenceValue)) + + if _, err = db.Exec(fmt.Sprintf("ALTER SEQUENCE %s.%s RESTART WITH %d", sequenceSchema, sequenceName, randNum)); err != nil { + return fmt.Errorf("%s: failed to alter and restart %s sequence: %w", failedToRandomizeTestDBSequencesError{}, sequenceName, err) + } + } + + if err = seqRows.Err(); err != nil { + return fmt.Errorf("%s: failed to iterate through sequences: %w", failedToRandomizeTestDBSequencesError{}, err) + } + + return nil +} + // PrepareTestDatabaseUserOnly calls ResetDatabase then loads only user fixtures required for local // testing against testnets. Does not include fake chain fixtures. func (s *Shell) PrepareTestDatabaseUserOnly(c *cli.Context) error { @@ -827,6 +888,7 @@ func (s *Shell) PrepareTestDatabaseUserOnly(c *cli.Context) error { // MigrateDatabase migrates the database func (s *Shell) MigrateDatabase(_ *cli.Context) error { + ctx := s.ctx() cfg := s.Config.Database() parsed := cfg.URL() if parsed.String() == "" { @@ -839,7 +901,7 @@ func (s *Shell) MigrateDatabase(_ *cli.Context) error { } s.Logger.Infof("Migrating database: %#v", parsed.String()) - if err := migrateDB(cfg, s.Logger); err != nil { + if err := migrateDB(ctx, cfg, s.Logger); err != nil { return s.errorOut(err) } return nil @@ -847,6 +909,7 @@ func (s *Shell) MigrateDatabase(_ *cli.Context) error { // RollbackDatabase rolls back the database via down migrations. func (s *Shell) RollbackDatabase(c *cli.Context) error { + ctx := s.ctx() var version null.Int if c.Args().Present() { arg := c.Args().First() @@ -862,7 +925,7 @@ func (s *Shell) RollbackDatabase(c *cli.Context) error { return fmt.Errorf("failed to initialize orm: %v", err) } - if err := migrate.Rollback(db.DB, s.Logger, version); err != nil { + if err := migrate.Rollback(ctx, db.DB, s.Logger, version); err != nil { return fmt.Errorf("migrateDB failed: %v", err) } @@ -871,12 +934,13 @@ func (s *Shell) RollbackDatabase(c *cli.Context) error { // VersionDatabase displays the current database version. func (s *Shell) VersionDatabase(_ *cli.Context) error { + ctx := s.ctx() db, err := newConnection(s.Config.Database()) if err != nil { return fmt.Errorf("failed to initialize orm: %v", err) } - version, err := migrate.Current(db.DB, s.Logger) + version, err := migrate.Current(ctx, db.DB, s.Logger) if err != nil { return fmt.Errorf("migrateDB failed: %v", err) } @@ -887,12 +951,13 @@ func (s *Shell) VersionDatabase(_ *cli.Context) error { // StatusDatabase displays the database migration status func (s *Shell) StatusDatabase(_ *cli.Context) error { + ctx := s.ctx() db, err := newConnection(s.Config.Database()) if err != nil { return fmt.Errorf("failed to initialize orm: %v", err) } - if err = migrate.Status(db.DB, s.Logger); err != nil { + if err = migrate.Status(ctx, db.DB, s.Logger); err != nil { return fmt.Errorf("Status failed: %v", err) } return nil @@ -936,23 +1001,36 @@ func (s *Shell) CleanupChainTables(c *cli.Context) error { if err != nil { return s.errorOut(errors.Wrap(err, "error connecting to the database")) } - defer db.Close() - tablesToDeleteFromQuery := `SELECT table_name FROM information_schema.columns WHERE "column_name"=$1;` + // some tables with evm_chain_id (mostly job specs) are in public schema + tablesToDeleteFromQuery := `SELECT table_name, table_schema FROM information_schema.columns WHERE "column_name"=$1;` // Delete rows from each table based on the chain_id. if strings.EqualFold("EVM", c.String("type")) { - var tables []string - if err = db.Select(&tables, tablesToDeleteFromQuery, "evm_chain_id"); err != nil { + rows, err := db.Query(tablesToDeleteFromQuery, "evm_chain_id") + if err != nil { return err + } else if rows.Err() != nil { + return rows.Err() + } + + var tablesToDeleteFrom []string + for rows.Next() { + var name string + var schema string + if err = rows.Scan(&name, &schema); err != nil { + return err + } + tablesToDeleteFrom = append(tablesToDeleteFrom, schema+"."+name) } - for _, tableName := range tables { + + for _, tableName := range tablesToDeleteFrom { query := fmt.Sprintf(`DELETE FROM %s WHERE "evm_chain_id"=$1;`, tableName) _, err = db.Exec(query, c.String("id")) if err != nil { - fmt.Printf("Error deleting rows from %s: %v\n", tableName, err) + fmt.Printf("Error deleting rows containing evm_chain_id from %s: %v\n", tableName, err) } else { - fmt.Printf("Rows with chain_id %s deleted from %s.\n", c.String("id"), tableName) + fmt.Printf("Rows with evm_chain_id %s deleted from %s.\n", c.String("id"), tableName) } } } else { @@ -1016,27 +1094,27 @@ func dropAndCreatePristineDB(db *sqlx.DB, template string) (err error) { return nil } -func migrateDB(config dbConfig, lggr logger.Logger) error { +func migrateDB(ctx context.Context, config dbConfig, lggr logger.Logger) error { db, err := newConnection(config) if err != nil { return fmt.Errorf("failed to initialize orm: %v", err) } - if err = migrate.Migrate(db.DB, lggr); err != nil { + if err = migrate.Migrate(ctx, db.DB, lggr); err != nil { return fmt.Errorf("migrateDB failed: %v", err) } return db.Close() } -func downAndUpDB(cfg dbConfig, lggr logger.Logger, baseVersionID int64) error { +func downAndUpDB(ctx context.Context, cfg dbConfig, lggr logger.Logger, baseVersionID int64) error { db, err := newConnection(cfg) if err != nil { return fmt.Errorf("failed to initialize orm: %v", err) } - if err = migrate.Rollback(db.DB, lggr, null.IntFrom(baseVersionID)); err != nil { + if err = migrate.Rollback(ctx, db.DB, lggr, null.IntFrom(baseVersionID)); err != nil { return fmt.Errorf("test rollback failed: %v", err) } - if err = migrate.Migrate(db.DB, lggr); err != nil { + if err = migrate.Migrate(ctx, db.DB, lggr); err != nil { return fmt.Errorf("second migrateDB failed: %v", err) } return db.Close() diff --git a/core/cmd/shell_local_test.go b/core/cmd/shell_local_test.go index be8d5c94056..fac2d7f040b 100644 --- a/core/cmd/shell_local_test.go +++ b/core/cmd/shell_local_test.go @@ -8,15 +8,15 @@ import ( "testing" "time" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/common/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/cmd" cmdMocks "github.com/smartcontractkit/chainlink/v2/core/cmd/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -25,7 +25,7 @@ import ( chainlinkmocks "github.com/smartcontractkit/chainlink/v2/core/services/chainlink/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/pg" evmrelayer "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" - "github.com/smartcontractkit/chainlink/v2/core/sessions" + "github.com/smartcontractkit/chainlink/v2/core/sessions/localauth" "github.com/smartcontractkit/chainlink/v2/core/store/dialects" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -39,7 +39,7 @@ import ( "github.com/urfave/cli" ) -func genTestEVMRelayers(t *testing.T, opts evm.ChainRelayExtenderConfig, ks evmrelayer.CSAETHKeystore) *chainlink.CoreRelayerChainInteroperators { +func genTestEVMRelayers(t *testing.T, opts legacyevm.ChainRelayExtenderConfig, ks evmrelayer.CSAETHKeystore) *chainlink.CoreRelayerChainInteroperators { f := chainlink.RelayerFactory{ Logger: opts.Logger, LoopRegistry: plugins.NewLoopRegistry(opts.Logger, opts.AppConfig.Tracing()), @@ -79,14 +79,14 @@ func TestShell_RunNodeWithPasswords(t *testing.T) { }) db := pgtest.NewSqlxDB(t) keyStore := cltest.NewKeyStore(t, db, cfg.Database()) - sessionORM := sessions.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger) + authProviderORM := localauth.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger) lggr := logger.TestLogger(t) - opts := evm.ChainRelayExtenderConfig{ + opts := legacyevm.ChainRelayExtenderConfig{ Logger: lggr, KeyStore: keyStore.Eth(), - ChainOpts: evm.ChainOpts{ + ChainOpts: legacyevm.ChainOpts{ AppConfig: cfg, EventBroadcaster: pg.NewNullEventBroadcaster(), MailMon: &utils.MailboxMonitor{}, @@ -100,7 +100,8 @@ func TestShell_RunNodeWithPasswords(t *testing.T) { pgtest.MustExec(t, db, "DELETE FROM users;") app := mocks.NewApplication(t) - app.On("SessionORM").Return(sessionORM).Maybe() + app.On("AuthenticationProvider").Return(authProviderORM).Maybe() + app.On("BasicAdminUsersORM").Return(authProviderORM).Maybe() app.On("GetKeyStore").Return(keyStore).Maybe() app.On("GetRelayers").Return(testRelayers).Maybe() app.On("Start", mock.Anything).Maybe().Return(nil) @@ -123,7 +124,7 @@ func TestShell_RunNodeWithPasswords(t *testing.T) { } set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RunNode, set, "") + flagSetApplyFromAction(client.RunNode, set, "") require.NoError(t, set.Set("password", test.pwdfile)) @@ -171,7 +172,7 @@ func TestShell_RunNodeWithAPICredentialsFile(t *testing.T) { c.Insecure.OCRDevelopmentMode = nil }) db := pgtest.NewSqlxDB(t) - sessionORM := sessions.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger) + authProviderORM := localauth.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger) // Clear out fixture users/users created from the other test cases // This asserts that on initial run with an empty users table that the credentials file will instantiate and @@ -187,10 +188,10 @@ func TestShell_RunNodeWithAPICredentialsFile(t *testing.T) { ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(10), nil).Maybe() lggr := logger.TestLogger(t) - opts := evm.ChainRelayExtenderConfig{ + opts := legacyevm.ChainRelayExtenderConfig{ Logger: lggr, KeyStore: keyStore.Eth(), - ChainOpts: evm.ChainOpts{ + ChainOpts: legacyevm.ChainOpts{ AppConfig: cfg, EventBroadcaster: pg.NewNullEventBroadcaster(), MailMon: &utils.MailboxMonitor{}, @@ -199,7 +200,7 @@ func TestShell_RunNodeWithAPICredentialsFile(t *testing.T) { } testRelayers := genTestEVMRelayers(t, opts, keyStore) app := mocks.NewApplication(t) - app.On("SessionORM").Return(sessionORM) + app.On("BasicAdminUsersORM").Return(authProviderORM) app.On("GetKeyStore").Return(keyStore) app.On("GetRelayers").Return(testRelayers).Maybe() app.On("Start", mock.Anything).Maybe().Return(nil) @@ -220,7 +221,7 @@ func TestShell_RunNodeWithAPICredentialsFile(t *testing.T) { } set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RunNode, set, "") + flagSetApplyFromAction(client.RunNode, set, "") require.NoError(t, set.Set("api", test.apiFile)) @@ -279,7 +280,7 @@ func TestShell_RebroadcastTransactions_Txm(t *testing.T) { // Use 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. - config, sqlxDB := heavyweight.FullTestDBV2(t, "rebroadcasttransactions", func(c *chainlink.Config, s *chainlink.Secrets) { + config, sqlxDB := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Database.Dialect = dialects.Postgres // evm config is used in this test. but if set, it must be pass config validation. // simplest to make it nil @@ -302,12 +303,11 @@ func TestShell_RebroadcastTransactions_Txm(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) legacy := cltest.NewLegacyChainsWithMockChain(t, ethClient, config) - mockRelayerChainInteroperators := chainlinkmocks.NewRelayerChainInteroperators(t) - mockRelayerChainInteroperators.On("LegacyEVMChains").Return(legacy, nil) + mockRelayerChainInteroperators := &chainlinkmocks.FakeRelayerChainInteroperators{EVMChains: legacy} app.On("GetRelayers").Return(mockRelayerChainInteroperators).Maybe() ethClient.On("Dial", mock.Anything).Return(nil) - client := cmd.Shell{ + c := cmd.Shell{ Config: config, AppFactory: cltest.InstanceAppFactory{App: app}, FallbackAPIInitializer: cltest.NewMockAPIInitializer(t), @@ -318,7 +318,7 @@ func TestShell_RebroadcastTransactions_Txm(t *testing.T) { beginningNonce := uint64(7) endingNonce := uint64(10) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RebroadcastTransactions, set, "") + flagSetApplyFromAction(c.RebroadcastTransactions, set, "") require.NoError(t, set.Set("evmChainID", testutils.FixtureChainID.String())) require.NoError(t, set.Set("beginningNonce", strconv.FormatUint(beginningNonce, 10))) @@ -328,16 +328,16 @@ func TestShell_RebroadcastTransactions_Txm(t *testing.T) { require.NoError(t, set.Set("address", fromAddress.Hex())) require.NoError(t, set.Set("password", "../internal/fixtures/correct_password.txt")) - c := cli.NewContext(nil, set, nil) + ctx := cli.NewContext(nil, set, nil) for i := beginningNonce; i <= endingNonce; i++ { n := i ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return tx.Nonce() == n - }), mock.Anything).Once().Return(clienttypes.Successful, nil) + }), mock.Anything).Once().Return(client.Successful, nil) } - assert.NoError(t, client.RebroadcastTransactions(c)) + assert.NoError(t, c.RebroadcastTransactions(ctx)) } func TestShell_RebroadcastTransactions_OutsideRange_Txm(t *testing.T) { @@ -359,7 +359,7 @@ func TestShell_RebroadcastTransactions_OutsideRange_Txm(t *testing.T) { // Use the 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. - config, sqlxDB := heavyweight.FullTestDBV2(t, "rebroadcasttransactions_outsiderange", func(c *chainlink.Config, s *chainlink.Secrets) { + config, sqlxDB := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Database.Dialect = dialects.Postgres // evm config is used in this test. but if set, it must be pass config validation. // simplest to make it nil @@ -385,11 +385,10 @@ func TestShell_RebroadcastTransactions_OutsideRange_Txm(t *testing.T) { ethClient.On("Dial", mock.Anything).Return(nil) legacy := cltest.NewLegacyChainsWithMockChain(t, ethClient, config) - mockRelayerChainInteroperators := chainlinkmocks.NewRelayerChainInteroperators(t) - mockRelayerChainInteroperators.On("LegacyEVMChains").Return(legacy, nil) + mockRelayerChainInteroperators := &chainlinkmocks.FakeRelayerChainInteroperators{EVMChains: legacy} app.On("GetRelayers").Return(mockRelayerChainInteroperators).Maybe() - client := cmd.Shell{ + c := cmd.Shell{ Config: config, AppFactory: cltest.InstanceAppFactory{App: app}, FallbackAPIInitializer: cltest.NewMockAPIInitializer(t), @@ -398,7 +397,7 @@ func TestShell_RebroadcastTransactions_OutsideRange_Txm(t *testing.T) { } set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RebroadcastTransactions, set, "") + flagSetApplyFromAction(c.RebroadcastTransactions, set, "") require.NoError(t, set.Set("evmChainID", testutils.FixtureChainID.String())) require.NoError(t, set.Set("beginningNonce", strconv.FormatUint(uint64(beginningNonce), 10))) @@ -408,16 +407,16 @@ func TestShell_RebroadcastTransactions_OutsideRange_Txm(t *testing.T) { require.NoError(t, set.Set("address", fromAddress.Hex())) require.NoError(t, set.Set("password", "../internal/fixtures/correct_password.txt")) - c := cli.NewContext(nil, set, nil) + ctx := cli.NewContext(nil, set, nil) for i := beginningNonce; i <= endingNonce; i++ { n := i ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { return uint(tx.Nonce()) == n - }), mock.Anything).Once().Return(clienttypes.Successful, nil) + }), mock.Anything).Once().Return(client.Successful, nil) } - assert.NoError(t, client.RebroadcastTransactions(c)) + assert.NoError(t, c.RebroadcastTransactions(ctx)) cltest.AssertEthTxAttemptCountStays(t, app.GetSqlxDB(), 1) }) @@ -438,7 +437,7 @@ func TestShell_RebroadcastTransactions_AddressCheck(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - config, sqlxDB := heavyweight.FullTestDBV2(t, "rebroadcasttransactions_outsiderange", func(c *chainlink.Config, s *chainlink.Secrets) { + config, sqlxDB := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Database.Dialect = dialects.Postgres c.EVM = nil @@ -465,10 +464,9 @@ func TestShell_RebroadcastTransactions_AddressCheck(t *testing.T) { ethClient.On("Dial", mock.Anything).Return(nil) legacy := cltest.NewLegacyChainsWithMockChain(t, ethClient, config) - mockRelayerChainInteroperators := chainlinkmocks.NewRelayerChainInteroperators(t) - mockRelayerChainInteroperators.On("LegacyEVMChains").Return(legacy, nil) + mockRelayerChainInteroperators := &chainlinkmocks.FakeRelayerChainInteroperators{EVMChains: legacy} app.On("GetRelayers").Return(mockRelayerChainInteroperators).Maybe() - ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, mock.Anything).Maybe().Return(clienttypes.Successful, nil) + ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, mock.Anything).Maybe().Return(client.Successful, nil) client := cmd.Shell{ Config: config, @@ -479,7 +477,7 @@ func TestShell_RebroadcastTransactions_AddressCheck(t *testing.T) { } set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RebroadcastTransactions, set, "") + flagSetApplyFromAction(client.RebroadcastTransactions, set, "") require.NoError(t, set.Set("evmChainID", testutils.FixtureChainID.String())) require.NoError(t, set.Set("address", fromAddress.Hex())) @@ -494,3 +492,23 @@ func TestShell_RebroadcastTransactions_AddressCheck(t *testing.T) { }) } } + +func TestShell_CleanupChainTables(t *testing.T) { + // Just check if it doesn't error, command itself shouldn't be changed unless major schema changes were made. + // It would be really hard to write a test that accounts for schema changes, so this should be enough to alarm us that something broke. + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Database.Dialect = dialects.Postgres }) + client := cmd.Shell{ + Config: config, + Logger: logger.TestLogger(t), + } + + set := flag.NewFlagSet("test", 0) + flagSetApplyFromAction(client.CleanupChainTables, set, "") + require.NoError(t, set.Set("id", testutils.FixtureChainID.String())) + require.NoError(t, set.Set("type", "EVM")) + // heavyweight creates test db named chainlink_test_uid, while usual naming is chainlink_test + // CleanupChainTables handles test db name with chainlink_test, but because of heavyweight test db naming we have to set danger flag + require.NoError(t, set.Set("danger", "true")) + c := cli.NewContext(nil, set, nil) + require.NoError(t, client.CleanupChainTables(c)) +} diff --git a/core/cmd/shell_remote_test.go b/core/cmd/shell_remote_test.go index c1d15df9ec8..6143d678a90 100644 --- a/core/cmd/shell_remote_test.go +++ b/core/cmd/shell_remote_test.go @@ -25,7 +25,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -58,7 +58,7 @@ func startNewApplicationV2(t *testing.T, overrideFn func(c *chainlink.Config, s fn(sopts) } - config := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.JobPipeline.HTTPRequest.DefaultTimeout = models.MustNewDuration(30 * time.Millisecond) f := false c.EVM[0].Enabled = &f @@ -123,7 +123,7 @@ func TestShell_ReplayBlocks(t *testing.T) { client, _ := app.NewShellAndRenderer() set := flag.NewFlagSet("flagset", 0) - cltest.FlagSetApplyFromAction(client.ReplayFromBlock, set, "") + flagSetApplyFromAction(client.ReplayFromBlock, set, "") require.NoError(t, set.Set("block-number", "42")) require.NoError(t, set.Set("evm-chain-id", "12345678")) @@ -156,7 +156,7 @@ func TestShell_CreateExternalInitiator(t *testing.T) { client, _ := app.NewShellAndRenderer() set := flag.NewFlagSet("create", 0) - cltest.FlagSetApplyFromAction(client.CreateExternalInitiator, set, "") + flagSetApplyFromAction(client.CreateExternalInitiator, set, "") assert.NoError(t, set.Parse(test.args)) c := cli.NewContext(nil, set, nil) @@ -197,7 +197,7 @@ func TestShell_CreateExternalInitiator_Errors(t *testing.T) { initialExis := len(cltest.AllExternalInitiators(t, app.GetSqlxDB())) set := flag.NewFlagSet("create", 0) - cltest.FlagSetApplyFromAction(client.CreateExternalInitiator, set, "") + flagSetApplyFromAction(client.CreateExternalInitiator, set, "") assert.NoError(t, set.Parse(test.args)) c := cli.NewContext(nil, set, nil) @@ -228,7 +228,7 @@ func TestShell_DestroyExternalInitiator(t *testing.T) { require.NoError(t, err) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteExternalInitiator, set, "") + flagSetApplyFromAction(client.DeleteExternalInitiator, set, "") require.NoError(t, set.Parse([]string{exi.Name})) @@ -246,7 +246,7 @@ func TestShell_DestroyExternalInitiator_NotFound(t *testing.T) { client, r := app.NewShellAndRenderer() set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteExternalInitiator, set, "") + flagSetApplyFromAction(client.DeleteExternalInitiator, set, "") require.NoError(t, set.Parse([]string{"bogus-ID"})) @@ -258,7 +258,7 @@ func TestShell_DestroyExternalInitiator_NotFound(t *testing.T) { func TestShell_RemoteLogin(t *testing.T) { app := startNewApplicationV2(t, nil) - orm := app.SessionORM() + orm := app.AuthenticationProvider() u := cltest.NewUserWithSession(t, orm) @@ -280,7 +280,7 @@ func TestShell_RemoteLogin(t *testing.T) { client := app.NewAuthenticatingShell(prompter) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") require.NoError(t, set.Set("file", test.file)) require.NoError(t, set.Set("bypass-version-check", "true")) @@ -301,7 +301,7 @@ func TestShell_RemoteBuildCompatibility(t *testing.T) { t.Parallel() app := startNewApplicationV2(t, nil) - u := cltest.NewUserWithSession(t, app.SessionORM()) + u := cltest.NewUserWithSession(t, app.AuthenticationProvider()) enteredStrings := []string{u.Email, cltest.Password} prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: append(enteredStrings, enteredStrings...)} client := app.NewAuthenticatingShell(prompter) @@ -318,7 +318,7 @@ func TestShell_RemoteBuildCompatibility(t *testing.T) { // Fails without bypass set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") require.NoError(t, set.Set("bypass-version-check", "false")) @@ -329,7 +329,7 @@ func TestShell_RemoteBuildCompatibility(t *testing.T) { // Defaults to false set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") c = cli.NewContext(nil, set, nil) err = client.RemoteLogin(c) assert.Error(t, err) @@ -340,7 +340,7 @@ func TestShell_CheckRemoteBuildCompatibility(t *testing.T) { t.Parallel() app := startNewApplicationV2(t, nil) - u := cltest.NewUserWithSession(t, app.SessionORM()) + u := cltest.NewUserWithSession(t, app.AuthenticationProvider()) tests := []struct { name string remoteVersion, remoteSha string @@ -416,7 +416,7 @@ func TestShell_ChangePassword(t *testing.T) { t.Parallel() app := startNewApplicationV2(t, nil) - u := cltest.NewUserWithSession(t, app.SessionORM()) + u := cltest.NewUserWithSession(t, app.AuthenticationProvider()) enteredStrings := []string{u.Email, cltest.Password} prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings} @@ -425,7 +425,7 @@ func TestShell_ChangePassword(t *testing.T) { otherClient := app.NewAuthenticatingShell(prompter) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") require.NoError(t, set.Set("file", "../internal/fixtures/apicredentials")) require.NoError(t, set.Set("bypass-version-check", "true")) @@ -466,14 +466,14 @@ func TestShell_Profile_InvalidSecondsParam(t *testing.T) { t.Parallel() app := startNewApplicationV2(t, nil) - u := cltest.NewUserWithSession(t, app.SessionORM()) + u := cltest.NewUserWithSession(t, app.AuthenticationProvider()) enteredStrings := []string{u.Email, cltest.Password} prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings} client := app.NewAuthenticatingShell(prompter) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") require.NoError(t, set.Set("file", "../internal/fixtures/apicredentials")) require.NoError(t, set.Set("bypass-version-check", "true")) @@ -497,14 +497,14 @@ func TestShell_Profile(t *testing.T) { t.Parallel() app := startNewApplicationV2(t, nil) - u := cltest.NewUserWithSession(t, app.SessionORM()) + u := cltest.NewUserWithSession(t, app.AuthenticationProvider()) enteredStrings := []string{u.Email, cltest.Password} prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings} client := app.NewAuthenticatingShell(prompter) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") require.NoError(t, set.Set("file", "../internal/fixtures/apicredentials")) require.NoError(t, set.Set("bypass-version-check", "true")) @@ -595,7 +595,7 @@ func TestShell_RunOCRJob_HappyPath(t *testing.T) { require.NoError(t, err) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") require.NoError(t, set.Set("bypass-version-check", "true")) require.NoError(t, set.Parse([]string{strconv.FormatInt(int64(jb.ID), 10)})) @@ -613,7 +613,7 @@ func TestShell_RunOCRJob_MissingJobID(t *testing.T) { client, _ := app.NewShellAndRenderer() set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") require.NoError(t, set.Set("bypass-version-check", "true")) @@ -630,7 +630,7 @@ func TestShell_RunOCRJob_JobNotFound(t *testing.T) { client, _ := app.NewShellAndRenderer() set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.RemoteLogin, set, "") + flagSetApplyFromAction(client.RemoteLogin, set, "") require.NoError(t, set.Parse([]string{"1"})) require.NoError(t, set.Set("bypass-version-check", "true")) @@ -648,7 +648,7 @@ func TestShell_AutoLogin(t *testing.T) { app := startNewApplicationV2(t, nil) user := cltest.MustRandomUser(t) - require.NoError(t, app.SessionORM().CreateUser(&user)) + require.NoError(t, app.BasicAdminUsersORM().CreateUser(&user)) sr := sessions.SessionRequest{ Email: user.Email, @@ -659,7 +659,7 @@ func TestShell_AutoLogin(t *testing.T) { client.HTTP = cmd.NewAuthenticatedHTTPClient(app.Logger, app.NewClientOpts(), client.CookieAuthenticator, sr) fs := flag.NewFlagSet("", flag.ExitOnError) - cltest.FlagSetApplyFromAction(client.ListJobs, fs, "") + flagSetApplyFromAction(client.ListJobs, fs, "") err := client.ListJobs(cli.NewContext(nil, fs, nil)) require.NoError(t, err) @@ -676,7 +676,7 @@ func TestShell_AutoLogin_AuthFails(t *testing.T) { app := startNewApplicationV2(t, nil) user := cltest.MustRandomUser(t) - require.NoError(t, app.SessionORM().CreateUser(&user)) + require.NoError(t, app.BasicAdminUsersORM().CreateUser(&user)) sr := sessions.SessionRequest{ Email: user.Email, @@ -687,7 +687,7 @@ func TestShell_AutoLogin_AuthFails(t *testing.T) { client.HTTP = cmd.NewAuthenticatedHTTPClient(app.Logger, app.NewClientOpts(), client.CookieAuthenticator, sr) fs := flag.NewFlagSet("", flag.ExitOnError) - cltest.FlagSetApplyFromAction(client.ListJobs, fs, "") + flagSetApplyFromAction(client.ListJobs, fs, "") err := client.ListJobs(cli.NewContext(nil, fs, nil)) require.Error(t, err) } @@ -716,7 +716,7 @@ func TestShell_SetLogConfig(t *testing.T) { logLevel := "warn" set := flag.NewFlagSet("loglevel", 0) - cltest.FlagSetApplyFromAction(client.SetLogLevel, set, "") + flagSetApplyFromAction(client.SetLogLevel, set, "") require.NoError(t, set.Set("level", logLevel)) @@ -728,7 +728,7 @@ func TestShell_SetLogConfig(t *testing.T) { sqlEnabled := true set = flag.NewFlagSet("logsql", 0) - cltest.FlagSetApplyFromAction(client.SetLogSQL, set, "") + flagSetApplyFromAction(client.SetLogSQL, set, "") require.NoError(t, set.Set("enable", strconv.FormatBool(sqlEnabled))) c = cli.NewContext(nil, set, nil) @@ -739,7 +739,7 @@ func TestShell_SetLogConfig(t *testing.T) { sqlEnabled = false set = flag.NewFlagSet("logsql", 0) - cltest.FlagSetApplyFromAction(client.SetLogSQL, set, "") + flagSetApplyFromAction(client.SetLogSQL, set, "") require.NoError(t, set.Set("disable", "true")) c = cli.NewContext(nil, set, nil) diff --git a/core/cmd/shell_test.go b/core/cmd/shell_test.go index 74768a21928..ade14aa0d8a 100644 --- a/core/cmd/shell_test.go +++ b/core/cmd/shell_test.go @@ -2,15 +2,20 @@ package cmd_test import ( "crypto/rand" + "flag" "fmt" "math/big" "os" "path/filepath" + "reflect" + "runtime" + "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/urfave/cli" "github.com/smartcontractkit/chainlink-solana/pkg/solana" solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" @@ -19,13 +24,14 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks" "github.com/smartcontractkit/chainlink/v2/core/sessions" + "github.com/smartcontractkit/chainlink/v2/core/sessions/localauth" "github.com/smartcontractkit/chainlink/v2/plugins" ) @@ -33,7 +39,7 @@ func TestTerminalCookieAuthenticator_AuthenticateWithoutSession(t *testing.T) { t.Parallel() app := cltest.NewApplicationEVMDisabled(t) - u := cltest.NewUserWithSession(t, app.SessionORM()) + u := cltest.NewUserWithSession(t, app.AuthenticationProvider()) tests := []struct { name, email, pwd string @@ -65,7 +71,7 @@ func TestTerminalCookieAuthenticator_AuthenticateWithSession(t *testing.T) { app := cltest.NewApplicationEVMDisabled(t) require.NoError(t, app.Start(testutils.Context(t))) - u := cltest.NewUserWithSession(t, app.SessionORM()) + u := cltest.NewUserWithSession(t, app.AuthenticationProvider()) tests := []struct { name, email, pwd string @@ -155,7 +161,7 @@ func TestTerminalAPIInitializer_InitializeWithoutAPIUser(t *testing.T) { t.Run(test.name, func(t *testing.T) { db := pgtest.NewSqlxDB(t) lggr := logger.TestLogger(t) - orm := sessions.NewORM(db, time.Minute, lggr, pgtest.NewQConfig(true), audit.NoopLogger) + orm := localauth.NewORM(db, time.Minute, lggr, pgtest.NewQConfig(true), audit.NoopLogger) mock := &cltest.MockCountingPrompter{T: t, EnteredStrings: test.enteredStrings, NotTerminal: !test.isTerminal} tai := cmd.NewPromptingAPIInitializer(mock) @@ -186,7 +192,7 @@ func TestTerminalAPIInitializer_InitializeWithExistingAPIUser(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) lggr := logger.TestLogger(t) - orm := sessions.NewORM(db, time.Minute, lggr, cfg.Database(), audit.NoopLogger) + orm := localauth.NewORM(db, time.Minute, lggr, cfg.Database(), audit.NoopLogger) // Clear out fixture users/users created from the other test cases // This asserts that on initial run with an empty users table that the credentials file will instantiate and @@ -223,7 +229,7 @@ func TestFileAPIInitializer_InitializeWithoutAPIUser(t *testing.T) { t.Run(test.name, func(t *testing.T) { db := pgtest.NewSqlxDB(t) lggr := logger.TestLogger(t) - orm := sessions.NewORM(db, time.Minute, lggr, pgtest.NewQConfig(true), audit.NoopLogger) + orm := localauth.NewORM(db, time.Minute, lggr, pgtest.NewQConfig(true), audit.NoopLogger) // Clear out fixture users/users created from the other test cases // This asserts that on initial run with an empty users table that the credentials file will instantiate and @@ -248,7 +254,7 @@ func TestFileAPIInitializer_InitializeWithoutAPIUser(t *testing.T) { func TestFileAPIInitializer_InitializeWithExistingAPIUser(t *testing.T) { db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, nil) - orm := sessions.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger) + orm := localauth.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger) tests := []struct { name string @@ -513,3 +519,47 @@ func TestSetupStarkNetRelayer(t *testing.T) { require.Error(t, err) }) } + +// flagSetApplyFromAction applies the flags from action to the flagSet. +// `parentCommand` will filter the app commands and only applies the flags if the command/subcommand has a parent with that name, if left empty no filtering is done +func flagSetApplyFromAction(action interface{}, flagSet *flag.FlagSet, parentCommand string) { + cliApp := cmd.Shell{} + app := cmd.NewApp(&cliApp) + + foundName := parentCommand == "" + actionFuncName := getFuncName(action) + + for _, command := range app.Commands { + flags := recursiveFindFlagsWithName(actionFuncName, command, parentCommand, foundName) + + for _, flag := range flags { + flag.Apply(flagSet) + } + } + +} + +func recursiveFindFlagsWithName(actionFuncName string, command cli.Command, parent string, foundName bool) []cli.Flag { + + if command.Action != nil { + if actionFuncName == getFuncName(command.Action) && foundName { + return command.Flags + } + } + + for _, subcommand := range command.Subcommands { + if !foundName { + foundName = strings.EqualFold(subcommand.Name, parent) + } + + found := recursiveFindFlagsWithName(actionFuncName, subcommand, parent, foundName) + if found != nil { + return found + } + } + return nil +} + +func getFuncName(i interface{}) string { + return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() +} diff --git a/core/cmd/solana_keys_commands_test.go b/core/cmd/solana_keys_commands_test.go index 32b44fc8947..e72343be45c 100644 --- a/core/cmd/solana_keys_commands_test.go +++ b/core/cmd/solana_keys_commands_test.go @@ -95,7 +95,7 @@ func TestShell_SolanaKeys(t *testing.T) { require.NoError(t, err) requireSolanaKeyCount(t, app, 1) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(cmd.NewSolanaKeysClient(client).DeleteKey, set, "solana") + flagSetApplyFromAction(cmd.NewSolanaKeysClient(client).DeleteKey, set, "solana") require.NoError(tt, set.Set("yes", "true")) @@ -122,7 +122,7 @@ func TestShell_SolanaKeys(t *testing.T) { // Export test invalid id set := flag.NewFlagSet("test Solana export", 0) - cltest.FlagSetApplyFromAction(cmd.NewSolanaKeysClient(client).ExportKey, set, "solana") + flagSetApplyFromAction(cmd.NewSolanaKeysClient(client).ExportKey, set, "solana") require.NoError(tt, set.Parse([]string{"0"})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -135,7 +135,7 @@ func TestShell_SolanaKeys(t *testing.T) { // Export test set = flag.NewFlagSet("test Solana export", 0) - cltest.FlagSetApplyFromAction(cmd.NewSolanaKeysClient(client).ExportKey, set, "solana") + flagSetApplyFromAction(cmd.NewSolanaKeysClient(client).ExportKey, set, "solana") require.NoError(tt, set.Parse([]string{fmt.Sprint(key.ID())})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -150,7 +150,7 @@ func TestShell_SolanaKeys(t *testing.T) { requireSolanaKeyCount(t, app, 0) set = flag.NewFlagSet("test Solana import", 0) - cltest.FlagSetApplyFromAction(cmd.NewSolanaKeysClient(client).ImportKey, set, "solana") + flagSetApplyFromAction(cmd.NewSolanaKeysClient(client).ImportKey, set, "solana") require.NoError(tt, set.Parse([]string{keyName})) require.NoError(tt, set.Set("old-password", "../internal/fixtures/incorrect_password.txt")) diff --git a/core/cmd/solana_node_commands_test.go b/core/cmd/solana_node_commands_test.go index 3f95ebc0d84..316cf16212d 100644 --- a/core/cmd/solana_node_commands_test.go +++ b/core/cmd/solana_node_commands_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-relay/pkg/utils" + "github.com/smartcontractkit/chainlink-common/pkg/config" solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana" @@ -35,11 +35,11 @@ func TestShell_IndexSolanaNodes(t *testing.T) { id := solanatest.RandomChainID() node1 := solcfg.Node{ Name: ptr("first"), - URL: utils.MustParseURL("https://solana1.example"), + URL: config.MustParseURL("https://solana1.example"), } node2 := solcfg.Node{ Name: ptr("second"), - URL: utils.MustParseURL("https://solana2.example"), + URL: config.MustParseURL("https://solana2.example"), } chain := solana.TOMLConfig{ ChainID: &id, diff --git a/core/cmd/solana_transaction_commands_test.go b/core/cmd/solana_transaction_commands_test.go index cdb182cba41..b190caec51b 100644 --- a/core/cmd/solana_transaction_commands_test.go +++ b/core/cmd/solana_transaction_commands_test.go @@ -15,13 +15,12 @@ import ( "github.com/stretchr/testify/require" "github.com/urfave/cli" - "github.com/smartcontractkit/chainlink-relay/pkg/utils" + "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-solana/pkg/solana" solanaClient "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - "github.com/smartcontractkit/chainlink-solana/pkg/solana" "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" ) func TestShell_SolanaSendSol(t *testing.T) { @@ -29,7 +28,7 @@ func TestShell_SolanaSendSol(t *testing.T) { url := solanaClient.SetupLocalSolNode(t) node := solcfg.Node{ Name: ptr(t.Name()), - URL: utils.MustParseURL(url), + URL: config.MustParseURL(url), } cfg := solana.TOMLConfig{ ChainID: &chainID, @@ -69,7 +68,7 @@ func TestShell_SolanaSendSol(t *testing.T) { require.NoError(t, err) set := flag.NewFlagSet("sendsolcoins", 0) - cltest.FlagSetApplyFromAction(client.SolanaSendSol, set, "solana") + flagSetApplyFromAction(client.SolanaSendSol, set, "solana") require.NoError(t, set.Set("id", chainID)) require.NoError(t, set.Parse([]string{tt.amount, from.PublicKey().String(), to.PublicKey().String()})) diff --git a/core/cmd/starknet_keys_commands_test.go b/core/cmd/starknet_keys_commands_test.go index 17584eefe05..b1fa4d88f05 100644 --- a/core/cmd/starknet_keys_commands_test.go +++ b/core/cmd/starknet_keys_commands_test.go @@ -94,7 +94,7 @@ func TestShell_StarkNetKeys(t *testing.T) { require.NoError(t, err) requireStarkNetKeyCount(t, app, 1) set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(cmd.NewStarkNetKeysClient(client).DeleteKey, set, "starknet") + flagSetApplyFromAction(cmd.NewStarkNetKeysClient(client).DeleteKey, set, "starknet") require.NoError(tt, set.Set("yes", "true")) @@ -121,7 +121,7 @@ func TestShell_StarkNetKeys(t *testing.T) { // Export test invalid id set := flag.NewFlagSet("test StarkNet export", 0) - cltest.FlagSetApplyFromAction(cmd.NewStarkNetKeysClient(client).ExportKey, set, "starknet") + flagSetApplyFromAction(cmd.NewStarkNetKeysClient(client).ExportKey, set, "starknet") require.NoError(tt, set.Parse([]string{"0"})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -134,7 +134,7 @@ func TestShell_StarkNetKeys(t *testing.T) { // Export test set = flag.NewFlagSet("test StarkNet export", 0) - cltest.FlagSetApplyFromAction(cmd.NewStarkNetKeysClient(client).ExportKey, set, "starknet") + flagSetApplyFromAction(cmd.NewStarkNetKeysClient(client).ExportKey, set, "starknet") require.NoError(tt, set.Parse([]string{fmt.Sprint(key.ID())})) require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt")) @@ -149,7 +149,7 @@ func TestShell_StarkNetKeys(t *testing.T) { requireStarkNetKeyCount(t, app, 0) set = flag.NewFlagSet("test StarkNet import", 0) - cltest.FlagSetApplyFromAction(cmd.NewStarkNetKeysClient(client).ImportKey, set, "starknet") + flagSetApplyFromAction(cmd.NewStarkNetKeysClient(client).ImportKey, set, "starknet") require.NoError(tt, set.Parse([]string{keyName})) require.NoError(tt, set.Set("old-password", "../internal/fixtures/incorrect_password.txt")) diff --git a/core/cmd/starknet_node_commands_test.go b/core/cmd/starknet_node_commands_test.go index e799e5aac07..0347cdd18f7 100644 --- a/core/cmd/starknet_node_commands_test.go +++ b/core/cmd/starknet_node_commands_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-relay/pkg/utils" + commoncfg "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" "github.com/smartcontractkit/chainlink/v2/core/cmd" @@ -34,11 +34,11 @@ func TestShell_IndexStarkNetNodes(t *testing.T) { id := "starknet chain ID" node1 := config.Node{ Name: ptr("first"), - URL: utils.MustParseURL("https://starknet1.example"), + URL: commoncfg.MustParseURL("https://starknet1.example"), } node2 := config.Node{ Name: ptr("second"), - URL: utils.MustParseURL("https://starknet2.example"), + URL: commoncfg.MustParseURL("https://starknet2.example"), } chain := config.TOMLConfig{ ChainID: &id, diff --git a/core/cmd/vrf_keys_commands_test.go b/core/cmd/vrf_keys_commands_test.go index 20552604446..a061067771d 100644 --- a/core/cmd/vrf_keys_commands_test.go +++ b/core/cmd/vrf_keys_commands_test.go @@ -105,7 +105,7 @@ func TestShellVRF_CRUD(t *testing.T) { // Now do a hard delete and ensure its completely removes the key set := flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteVRFKey, set, "") + flagSetApplyFromAction(client.DeleteVRFKey, set, "") require.NoError(t, set.Parse([]string{k2.Compressed})) require.NoError(t, set.Set("hard", "true")) @@ -141,7 +141,7 @@ func TestVRF_ImportExport(t *testing.T) { // Export it, encrypted with cltest.Password instead keyName := "vrfkey1" set := flag.NewFlagSet("test VRF export", 0) - cltest.FlagSetApplyFromAction(client.ExportVRFKey, set, "") + flagSetApplyFromAction(client.ExportVRFKey, set, "") require.NoError(t, set.Parse([]string{k1.Compressed})) // Arguments require.NoError(t, set.Set("new-password", "../internal/fixtures/correct_password.txt")) @@ -157,7 +157,7 @@ func TestVRF_ImportExport(t *testing.T) { // Should error if we try to import a duplicate key importSet := flag.NewFlagSet("test VRF import", 0) - cltest.FlagSetApplyFromAction(client.ImportVRFKey, importSet, "") + flagSetApplyFromAction(client.ImportVRFKey, importSet, "") require.NoError(t, importSet.Parse([]string{keyName})) require.NoError(t, importSet.Set("old-password", "../internal/fixtures/correct_password.txt")) @@ -167,7 +167,7 @@ func TestVRF_ImportExport(t *testing.T) { // Lets delete the key and import it set = flag.NewFlagSet("test", 0) - cltest.FlagSetApplyFromAction(client.DeleteVRFKey, set, "") + flagSetApplyFromAction(client.DeleteVRFKey, set, "") require.NoError(t, set.Parse([]string{k1.Compressed})) require.NoError(t, set.Set("hard", "true")) diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index c8b5395d6d7..711889b3fa5 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -14,7 +14,7 @@ BlockBackfillDepth = 10 # Default # BlockBackfillSkip enables skipping of very long backfills. BlockBackfillSkip = false # Default # ChainType is automatically detected from chain ID. Set this to force a certain chain type regardless of chain ID. -# Available types: arbitrum, metis, optimismBedrock, xdai +# Available types: arbitrum, metis, optimismBedrock, xdai, celo, kroma, wemix, zksync ChainType = 'arbitrum' # Example # FinalityDepth is the number of blocks after which an ethereum transaction is considered "final". Note that the default is automatically set based on chain ID so it should not be necessary to change this under normal operation. # BlocksConsideredFinal determines how deeply we look back to ensure that transactions are confirmed onto the longest chain @@ -112,7 +112,8 @@ Enabled = true # Default # # - `FixedPrice` uses static configured values for gas price (can be set via API call). # - `BlockHistory` dynamically adjusts default gas price based on heuristics from mined blocks. -# - `L2Suggested` is a special mode only for use with L2 blockchains. This mode will use the gas price suggested by the rpc endpoint via `eth_gasPrice`. +# - `L2Suggested` mode is deprecated and replaced with `SuggestedPrice`. +# - `SuggestedPrice` is a mode which uses the gas price suggested by the rpc endpoint via `eth_gasPrice`. # - `Arbitrum` is a special mode only for use with Arbitrum blockchains. It uses the suggested gas price (up to `ETH_MAX_GAS_PRICE_WEI`, with `1000 gwei` default) as well as an estimated gas limit (up to `ETH_GAS_LIMIT_MAX`, with `1,000,000,000` default). # # Chainlink nodes decide what gas price to use using an `Estimator`. It ships with several simple and battle-hardened built-in estimators that should work well for almost all use-cases. Note that estimators will change their behaviour slightly depending on if you are in EIP-1559 mode or not. @@ -334,6 +335,14 @@ ContractConfirmations = 4 # Default ContractTransmitterTransmitTimeout = '10s' # Default # DatabaseTimeout sets `OCR.DatabaseTimeout` for this EVM chain. DatabaseTimeout = '10s' # Default +# **ADVANCED** +# DeltaCOverride (and `DeltaCJitterOverride`) determine the config override DeltaC. +# DeltaC is the maximum age of the latest report in the contract. If the maximum age is exceeded, a new report will be +# created by the report generation protocol. +DeltaCOverride = "168h" # Default +# **ADVANCED** +# DeltaCJitterOverride is the range for jitter to add to `DeltaCOverride`. +DeltaCJitterOverride = "1h" # Default # ObservationGracePeriod sets `OCR.ObservationGracePeriod` for this EVM chain. ObservationGracePeriod = '1s' # Default diff --git a/core/config/docs/core.toml b/core/config/docs/core.toml index 1ca4c656a7f..e438f4553fe 100644 --- a/core/config/docs/core.toml +++ b/core/config/docs/core.toml @@ -161,6 +161,8 @@ MaxAgeDays = 0 # Default MaxBackups = 1 # Default [WebServer] +# AuthenticationMethod defines which pluggable auth interface to use for user login and role assumption. Options include 'local' and 'ldap'. See docs for more details +AuthenticationMethod = 'local' # Default # AllowOrigins controls the URLs Chainlink nodes emit in the `Allow-Origins` header of its API responses. The setting can be a comma-separated list with no spaces. You might experience CORS issues if this is not set correctly. # # You should set this to the external URL that you use to access the Chainlink UI. @@ -191,6 +193,44 @@ StartTimeout = '15s' # Default # ListenIP specifies the IP to bind the HTTP server to ListenIP = '0.0.0.0' # Default +# Optional LDAP config if WebServer.AuthenticationMethod is set to 'ldap' +# LDAP queries are all parameterized to support custom LDAP 'dn', 'cn', and attributes +[WebServer.LDAP] +# ServerTLS defines the option to require the secure ldaps +ServerTLS = true # Default +# SessionTimeout determines the amount of idle time to elapse before session cookies expire. This signs out GUI users from their sessions. +SessionTimeout = '15m0s' # Default +# QueryTimeout defines how long queries should wait before timing out, defined in seconds +QueryTimeout = '2m0s' # Default +# BaseUserAttr defines the base attribute used to populate LDAP queries such as "uid=$", default is example +BaseUserAttr = 'uid' # Default +# BaseDN defines the base LDAP 'dn' search filter to apply to every LDAP query, replace example,com with the appropriate LDAP server's structure +BaseDN = 'dc=custom,dc=example,dc=com' # Example +# UsersDN defines the 'dn' query to use when querying for the 'users' 'ou' group +UsersDN = 'ou=users' # Default +# GroupsDN defines the 'dn' query to use when querying for the 'groups' 'ou' group +GroupsDN = 'ou=groups' # Default +# ActiveAttribute is an optional user field to check truthiness for if a user is valid/active. This is only required if the LDAP provider lists inactive users as members of groups +ActiveAttribute = '' # Default +# ActiveAttributeAllowedValue is the value to check against for the above optional user attribute +ActiveAttributeAllowedValue = '' # Default +# AdminUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Admin' role +AdminUserGroupCN = 'NodeAdmins' # Default +# EditUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Edit' role +EditUserGroupCN = 'NodeEditors' # Default +# RunUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Run' role +RunUserGroupCN = 'NodeRunners' # Default +# ReadUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Read' role +ReadUserGroupCN = 'NodeReadOnly' # Default +# UserApiTokenEnabled enables the users to issue API tokens with the same access of their role +UserApiTokenEnabled = false # Default +# UserAPITokenDuration is the duration of time an API token is active for before expiring +UserAPITokenDuration = '240h0m0s' # Default +# UpstreamSyncInterval is the interval at which the background LDAP sync task will be called. A '0s' value disables the background sync being run on an interval. This check is already performed during login/logout actions, all sessions and API tokens stored in the local ldap tables are updated to match the remote server +UpstreamSyncInterval = '0s' # Default +# UpstreamSyncRateLimit defines a duration to limit the number of query/API calls to the upstream LDAP provider. It prevents the sync functionality from being called multiple times within the defined duration +UpstreamSyncRateLimit = '2m0s' # Default + [WebServer.RateLimit] # Authenticated defines the threshold to which authenticated requests get limited. More than this many authenticated requests per `AuthenticatedRateLimitPeriod` will be rejected. Authenticated = 1000 # Default @@ -312,7 +352,7 @@ KeyBundleID = '7a5f66bbe6594259325bf2b4f5b1a9c900000000000000000000000000000000' # CaptureEATelemetry toggles collecting extra information from External Adaptares CaptureEATelemetry = false # Default # CaptureAutomationCustomTelemetry toggles collecting automation specific telemetry -CaptureAutomationCustomTelemetry = false # Default +CaptureAutomationCustomTelemetry = true # Default # DefaultTransactionQueueDepth controls the queue size for `DropOldestStrategy` in OCR2. Set to 0 to use `SendEvery` strategy instead. DefaultTransactionQueueDepth = 1 # Default # SimulateTransactions enables transaction simulation for OCR2. @@ -547,13 +587,40 @@ DisableRateLimiting = false # Default # Enabled turns trace collection on or off. On requires an OTEL Tracing Collector. Enabled = false # Default # CollectorTarget is the logical address of the OTEL Tracing Collector. -CollectorTarget = "localhost:4317" # Example +CollectorTarget = 'localhost:4317' # Example # NodeID is an unique name for this node relative to any other node traces are collected for. -NodeID = "NodeID" # Example +NodeID = 'NodeID' # Example # SamplingRatio is the ratio of traces to sample for this node. SamplingRatio = 1.0 # Example +# Mode is a string value. `tls` or `unencrypted` are the only values allowed. If set to `unencrypted`, `TLSCertPath` can be unset, meaning traces will be sent over plaintext to the collector. +Mode = 'tls' # Default +# TLSCertPath is the file path to the TLS certificate used for secure communication with an OTEL Tracing Collector. +TLSCertPath = '/path/to/cert.pem' # Example # Tracing.Attributes are user specified key-value pairs to associate in the context of the traces [Tracing.Attributes] # env is an example user specified key-value pair -env = "test" # Example +env = 'test' # Example + +[Mercury] + +# Mercury.Cache controls settings for the price retrieval cache querying a mercury server +[Mercury.Cache] +# LatestReportTTL controls how "stale" we will allow a price to be e.g. if +# set to 1s, a new price will always be fetched if the last result was +# from 1 second ago or older. +# +# Another way of looking at it is such: the cache will _never_ return a +# price that was queried from now-LatestReportTTL or before. +# +# Setting to zero disables caching entirely. +LatestReportTTL = "1s" # Default +# MaxStaleAge is that maximum amount of time that a value can be stale +# before it is deleted from the cache (a form of garbage collection). +# +# This should generally be set to something much larger than +# LatestReportTTL. Setting to zero disables garbage collection. +MaxStaleAge = "1h" # Default +# LatestReportDeadline controls how long to wait for a response from the +# mercury server before retrying. Setting this to zero will wait indefinitely. +LatestReportDeadline = "5s" # Default diff --git a/core/config/docs/docs_test.go b/core/config/docs/docs_test.go index 276d0239941..74fa6682c96 100644 --- a/core/config/docs/docs_test.go +++ b/core/config/docs/docs_test.go @@ -10,11 +10,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana" stkcfg "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" - "github.com/smartcontractkit/chainlink/v2/core/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/config/docs" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -92,7 +92,7 @@ func TestDoc(t *testing.T) { }) t.Run("Cosmos", func(t *testing.T) { - var fallbackDefaults cosmos.CosmosConfig + var fallbackDefaults coscfg.TOMLConfig fallbackDefaults.SetDefaults() assertTOML(t, fallbackDefaults.Chain, defaults.Cosmos[0].Chain) diff --git a/core/config/docs/secrets.toml b/core/config/docs/secrets.toml index 2b491a77497..4ed2325dfb2 100644 --- a/core/config/docs/secrets.toml +++ b/core/config/docs/secrets.toml @@ -14,6 +14,15 @@ BackupURL = "postgresql://user:pass@read-replica.example.com:5432/dbname?sslmode # Environment variable: `CL_DATABASE_ALLOW_SIMPLE_PASSWORDS` AllowSimplePasswords = false # Default +# Optional LDAP config +[WebServer.LDAP] +# ServerAddress is the full ldaps:// address of the ldap server to authenticate with and query +ServerAddress = 'ldaps://127.0.0.1' # Example +# ReadOnlyUserLogin is the username of the read only root user used to authenticate the requested LDAP queries +ReadOnlyUserLogin = 'viewer@example.com' # Example +# ReadOnlyUserPass is the password for the above account +ReadOnlyUserPass = 'password' # Example + [Password] # Keystore is the password for the node's account. # diff --git a/core/config/mercury_config.go b/core/config/mercury_config.go index d7e985d14e6..e530af6338f 100644 --- a/core/config/mercury_config.go +++ b/core/config/mercury_config.go @@ -1,7 +1,18 @@ package config -import ocr2models "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" +import ( + "time" + + ocr2models "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" +) + +type MercuryCache interface { + LatestReportTTL() time.Duration + MaxStaleAge() time.Duration + LatestReportDeadline() time.Duration +} type Mercury interface { Credentials(credName string) *ocr2models.MercuryCredentials + Cache() MercuryCache } diff --git a/core/config/parse/parsers.go b/core/config/parse/parsers.go index 93e519064bf..6243b74dd52 100644 --- a/core/config/parse/parsers.go +++ b/core/config/parse/parsers.go @@ -13,7 +13,8 @@ import ( "github.com/pkg/errors" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink/v2/core/assets" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/static" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -22,10 +23,10 @@ func String(str string) (string, error) { return str, nil } -func Link(str string) (*assets.Link, error) { - i, ok := new(assets.Link).SetString(str, 10) +func Link(str string) (*commonassets.Link, error) { + i, ok := new(commonassets.Link).SetString(str, 10) if !ok { - return i, fmt.Errorf("unable to parse '%v' into *assets.Link(base 10)", str) + return i, fmt.Errorf("unable to parse '%s'", str) } return i, nil } diff --git a/core/config/presenters.go b/core/config/presenters.go deleted file mode 100644 index f4da66fd151..00000000000 --- a/core/config/presenters.go +++ /dev/null @@ -1,14 +0,0 @@ -package config - -import ( - "fmt" - "math/big" - - "golang.org/x/exp/constraints" -) - -// FriendlyNumber returns a string printing the integer or big.Int in both -// decimal and hexadecimal formats. -func FriendlyNumber[N constraints.Integer | *big.Int](n N) string { - return fmt.Sprintf("#%[1]v (0x%[1]x)", n) -} diff --git a/core/config/toml/types.go b/core/config/toml/types.go index b7c8cfbc473..c420d7f3f47 100644 --- a/core/config/toml/types.go +++ b/core/config/toml/types.go @@ -20,6 +20,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/config/parse" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "github.com/smartcontractkit/chainlink/v2/core/sessions" "github.com/smartcontractkit/chainlink/v2/core/store/dialects" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -53,6 +54,7 @@ type Core struct { Sentry Sentry `toml:",omitempty"` Insecure Insecure `toml:",omitempty"` Tracing Tracing `toml:",omitempty"` + Mercury Mercury `toml:",omitempty"` } // SetFrom updates c with any non-nil values from f. (currently TOML field only!) @@ -81,6 +83,7 @@ func (c *Core) SetFrom(f *Core) { c.OCR.setFrom(&f.OCR) c.P2P.setFrom(&f.P2P) c.Keeper.setFrom(&f.Keeper) + c.Mercury.setFrom(&f.Mercury) c.AutoPprof.setFrom(&f.AutoPprof) c.Pyroscope.setFrom(&f.Pyroscope) @@ -101,6 +104,7 @@ func (c *Core) ValidateConfig() (err error) { type Secrets struct { Database DatabaseSecrets `toml:",omitempty"` Password Passwords `toml:",omitempty"` + WebServer WebServerSecrets `toml:",omitempty"` Pyroscope PyroscopeSecrets `toml:",omitempty"` Prometheus PrometheusSecrets `toml:",omitempty"` Mercury MercurySecrets `toml:",omitempty"` @@ -592,6 +596,7 @@ func (l *LogFile) setFrom(f *LogFile) { } type WebServer struct { + AuthenticationMethod *string AllowOrigins *string BridgeResponseURL *models.URL BridgeCacheTTL *models.Duration @@ -604,12 +609,16 @@ type WebServer struct { StartTimeout *models.Duration ListenIP *net.IP + LDAP WebServerLDAP `toml:",omitempty"` MFA WebServerMFA `toml:",omitempty"` RateLimit WebServerRateLimit `toml:",omitempty"` TLS WebServerTLS `toml:",omitempty"` } func (w *WebServer) setFrom(f *WebServer) { + if v := f.AuthenticationMethod; v != nil { + w.AuthenticationMethod = v + } if v := f.AllowOrigins; v != nil { w.AllowOrigins = v } @@ -644,11 +653,46 @@ func (w *WebServer) setFrom(f *WebServer) { w.HTTPMaxSize = v } + w.LDAP.setFrom(&f.LDAP) w.MFA.setFrom(&f.MFA) w.RateLimit.setFrom(&f.RateLimit) w.TLS.setFrom(&f.TLS) } +func (w *WebServer) ValidateConfig() (err error) { + // Validate LDAP fields when authentication method is LDAPAuth + if *w.AuthenticationMethod != string(sessions.LDAPAuth) { + return + } + + // Assert LDAP fields when AuthMethod set to LDAP + if *w.LDAP.BaseDN == "" { + err = multierr.Append(err, configutils.ErrInvalid{Name: "LDAP.BaseDN", Msg: "LDAP BaseDN can not be empty"}) + } + if *w.LDAP.BaseUserAttr == "" { + err = multierr.Append(err, configutils.ErrInvalid{Name: "LDAP.BaseUserAttr", Msg: "LDAP BaseUserAttr can not be empty"}) + } + if *w.LDAP.UsersDN == "" { + err = multierr.Append(err, configutils.ErrInvalid{Name: "LDAP.UsersDN", Msg: "LDAP UsersDN can not be empty"}) + } + if *w.LDAP.GroupsDN == "" { + err = multierr.Append(err, configutils.ErrInvalid{Name: "LDAP.GroupsDN", Msg: "LDAP GroupsDN can not be empty"}) + } + if *w.LDAP.AdminUserGroupCN == "" { + err = multierr.Append(err, configutils.ErrInvalid{Name: "LDAP.AdminUserGroupCN", Msg: "LDAP AdminUserGroupCN can not be empty"}) + } + if *w.LDAP.EditUserGroupCN == "" { + err = multierr.Append(err, configutils.ErrInvalid{Name: "LDAP.RunUserGroupCN", Msg: "LDAP ReadUserGroupCN can not be empty"}) + } + if *w.LDAP.RunUserGroupCN == "" { + err = multierr.Append(err, configutils.ErrInvalid{Name: "LDAP.RunUserGroupCN", Msg: "LDAP RunUserGroupCN can not be empty"}) + } + if *w.LDAP.ReadUserGroupCN == "" { + err = multierr.Append(err, configutils.ErrInvalid{Name: "LDAP.ReadUserGroupCN", Msg: "LDAP ReadUserGroupCN can not be empty"}) + } + return err +} + type WebServerMFA struct { RPID *string RPOrigin *string @@ -715,6 +759,110 @@ func (w *WebServerTLS) setFrom(f *WebServerTLS) { } } +type WebServerLDAP struct { + ServerTLS *bool + SessionTimeout *models.Duration + QueryTimeout *models.Duration + BaseUserAttr *string + BaseDN *string + UsersDN *string + GroupsDN *string + ActiveAttribute *string + ActiveAttributeAllowedValue *string + AdminUserGroupCN *string + EditUserGroupCN *string + RunUserGroupCN *string + ReadUserGroupCN *string + UserApiTokenEnabled *bool + UserAPITokenDuration *models.Duration + UpstreamSyncInterval *models.Duration + UpstreamSyncRateLimit *models.Duration +} + +func (w *WebServerLDAP) setFrom(f *WebServerLDAP) { + if v := f.ServerTLS; v != nil { + w.ServerTLS = v + } + if v := f.SessionTimeout; v != nil { + w.SessionTimeout = v + } + if v := f.SessionTimeout; v != nil { + w.SessionTimeout = v + } + if v := f.QueryTimeout; v != nil { + w.QueryTimeout = v + } + if v := f.BaseUserAttr; v != nil { + w.BaseUserAttr = v + } + if v := f.BaseDN; v != nil { + w.BaseDN = v + } + if v := f.UsersDN; v != nil { + w.UsersDN = v + } + if v := f.GroupsDN; v != nil { + w.GroupsDN = v + } + if v := f.ActiveAttribute; v != nil { + w.ActiveAttribute = v + } + if v := f.ActiveAttributeAllowedValue; v != nil { + w.ActiveAttributeAllowedValue = v + } + if v := f.AdminUserGroupCN; v != nil { + w.AdminUserGroupCN = v + } + if v := f.EditUserGroupCN; v != nil { + w.EditUserGroupCN = v + } + if v := f.RunUserGroupCN; v != nil { + w.RunUserGroupCN = v + } + if v := f.ReadUserGroupCN; v != nil { + w.ReadUserGroupCN = v + } + if v := f.UserApiTokenEnabled; v != nil { + w.UserApiTokenEnabled = v + } + if v := f.UserAPITokenDuration; v != nil { + w.UserAPITokenDuration = v + } + if v := f.UpstreamSyncInterval; v != nil { + w.UpstreamSyncInterval = v + } + if v := f.UpstreamSyncRateLimit; v != nil { + w.UpstreamSyncRateLimit = v + } +} + +type WebServerLDAPSecrets struct { + ServerAddress *models.SecretURL + ReadOnlyUserLogin *models.Secret + ReadOnlyUserPass *models.Secret +} + +func (w *WebServerLDAPSecrets) setFrom(f *WebServerLDAPSecrets) { + if v := f.ServerAddress; v != nil { + w.ServerAddress = v + } + if v := f.ReadOnlyUserLogin; v != nil { + w.ReadOnlyUserLogin = v + } + if v := f.ReadOnlyUserPass; v != nil { + w.ReadOnlyUserPass = v + } +} + +type WebServerSecrets struct { + LDAP WebServerLDAPSecrets `toml:",omitempty"` +} + +func (w *WebServerSecrets) SetFrom(f *WebServerSecrets) error { + w.LDAP.setFrom(&f.LDAP) + return nil +} + type JobPipeline struct { ExternalInitiatorsEnabled *bool MaxRunDuration *models.Duration @@ -1212,6 +1360,32 @@ func (ins *Insecure) setFrom(f *Insecure) { } } +type MercuryCache struct { + LatestReportTTL *models.Duration + MaxStaleAge *models.Duration + LatestReportDeadline *models.Duration +} + +func (mc *MercuryCache) setFrom(f *MercuryCache) { + if v := f.LatestReportTTL; v != nil { + mc.LatestReportTTL = v + } + if v := f.MaxStaleAge; v != nil { + mc.MaxStaleAge = v + } + if v := f.LatestReportDeadline; v != nil { + mc.LatestReportDeadline = v + } +} + +type Mercury struct { + Cache MercuryCache `toml:",omitempty"` +} + +func (m *Mercury) setFrom(f *Mercury) { + m.Cache.setFrom(&f.Cache) +} + type MercuryCredentials struct { // LegacyURL is the legacy base URL for mercury v0.2 API LegacyURL *models.SecretURL @@ -1309,6 +1483,8 @@ type Tracing struct { CollectorTarget *string NodeID *string SamplingRatio *float64 + Mode *string + TLSCertPath *string Attributes map[string]string `toml:",omitempty"` } @@ -1328,6 +1504,12 @@ func (t *Tracing) setFrom(f *Tracing) { if v := f.SamplingRatio; v != nil { t.SamplingRatio = f.SamplingRatio } + if v := f.Mode; v != nil { + t.Mode = f.Mode + } + if v := f.TLSCertPath; v != nil { + t.TLSCertPath = f.TLSCertPath + } } func (t *Tracing) ValidateConfig() (err error) { @@ -1341,10 +1523,39 @@ func (t *Tracing) ValidateConfig() (err error) { } } - if t.CollectorTarget != nil { - ok := isValidURI(*t.CollectorTarget) - if !ok { - err = multierr.Append(err, configutils.ErrInvalid{Name: "CollectorTarget", Value: *t.CollectorTarget, Msg: "must be a valid URI"}) + if t.Mode != nil { + switch *t.Mode { + case "tls": + // TLSCertPath must be set + if t.TLSCertPath == nil { + err = multierr.Append(err, configutils.ErrMissing{Name: "TLSCertPath", Msg: "must be set when Tracing.Mode is tls"}) + } else { + ok := isValidFilePath(*t.TLSCertPath) + if !ok { + err = multierr.Append(err, configutils.ErrInvalid{Name: "TLSCertPath", Value: *t.TLSCertPath, Msg: "must be a valid file path"}) + } + } + case "unencrypted": + // no-op + default: + // Mode must be either "tls" or "unencrypted" + err = multierr.Append(err, configutils.ErrInvalid{Name: "Mode", Value: *t.Mode, Msg: "must be either 'tls' or 'unencrypted'"}) + } + } + + if t.CollectorTarget != nil && t.Mode != nil { + switch *t.Mode { + case "tls": + if !isValidURI(*t.CollectorTarget) { + err = multierr.Append(err, configutils.ErrInvalid{Name: "CollectorTarget", Value: *t.CollectorTarget, Msg: "must be a valid URI"}) + } + case "unencrypted": + // Unencrypted traces can not be sent to external networks + if !isValidLocalURI(*t.CollectorTarget) { + err = multierr.Append(err, configutils.ErrInvalid{Name: "CollectorTarget", Value: *t.CollectorTarget, Msg: "must be a valid local URI"}) + } + default: + // no-op } } @@ -1353,15 +1564,19 @@ func (t *Tracing) ValidateConfig() (err error) { var hostnameRegex = regexp.MustCompile(`^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$`) +// Validates uri is valid external or local URI func isValidURI(uri string) bool { if strings.Contains(uri, "://") { - // Standard URI check - _, _ = url.ParseRequestURI(uri) - // TODO: BCF-2703. Handle error. All external addresses currently fail validation until we have secure transport to external networks. - return false + _, err := url.ParseRequestURI(uri) + return err == nil } - // For URIs like "otel-collector:4317" + return isValidLocalURI(uri) +} + +// isValidLocalURI returns true if uri is a valid local URI +// External URIs (e.g. http://) are not valid local URIs, and will return false. +func isValidLocalURI(uri string) bool { parts := strings.Split(uri, ":") if len(parts) == 2 { host, port := parts[0], parts[1] @@ -1384,3 +1599,7 @@ func isValidURI(uri string) bool { func isValidHostname(hostname string) bool { return hostnameRegex.MatchString(hostname) } + +func isValidFilePath(path string) bool { + return len(path) > 0 && len(path) < 4096 +} diff --git a/core/config/toml/types_test.go b/core/config/toml/types_test.go index 92aaa024304..e16d3a864da 100644 --- a/core/config/toml/types_test.go +++ b/core/config/toml/types_test.go @@ -185,55 +185,115 @@ func TestTracing_ValidateCollectorTarget(t *testing.T) { tests := []struct { name string collectorTarget *string + mode *string wantErr bool errMsg string }{ { - name: "valid http address", + name: "valid http address in tls mode", + collectorTarget: ptr("https://testing.collector.dev"), + mode: ptr("tls"), + wantErr: false, + }, + { + name: "valid http address in unencrypted mode", collectorTarget: ptr("https://localhost:4317"), - // TODO: BCF-2703. Re-enable when we have secure transport to otel collectors in external networks - wantErr: true, - errMsg: "CollectorTarget: invalid value (https://localhost:4317): must be a valid URI", + mode: ptr("unencrypted"), + wantErr: true, + errMsg: "CollectorTarget: invalid value (https://localhost:4317): must be a valid local URI", }, + // Tracing.Mode = 'tls' { name: "valid localhost address", collectorTarget: ptr("localhost:4317"), + mode: ptr("tls"), wantErr: false, }, { name: "valid docker address", collectorTarget: ptr("otel-collector:4317"), + mode: ptr("tls"), wantErr: false, }, { name: "valid IP address", collectorTarget: ptr("192.168.1.1:4317"), + mode: ptr("tls"), wantErr: false, }, { name: "invalid port", collectorTarget: ptr("localhost:invalid"), wantErr: true, + mode: ptr("tls"), errMsg: "CollectorTarget: invalid value (localhost:invalid): must be a valid URI", }, { name: "invalid address", collectorTarget: ptr("invalid address"), wantErr: true, + mode: ptr("tls"), errMsg: "CollectorTarget: invalid value (invalid address): must be a valid URI", }, { name: "nil CollectorTarget", collectorTarget: ptr(""), wantErr: true, + mode: ptr("tls"), errMsg: "CollectorTarget: invalid value (): must be a valid URI", }, + // Tracing.Mode = 'unencrypted' + { + name: "valid localhost address", + collectorTarget: ptr("localhost:4317"), + mode: ptr("unencrypted"), + wantErr: false, + }, + { + name: "valid docker address", + collectorTarget: ptr("otel-collector:4317"), + mode: ptr("unencrypted"), + wantErr: false, + }, + { + name: "valid IP address", + collectorTarget: ptr("192.168.1.1:4317"), + mode: ptr("unencrypted"), + wantErr: false, + }, + { + name: "invalid port", + collectorTarget: ptr("localhost:invalid"), + wantErr: true, + mode: ptr("unencrypted"), + errMsg: "CollectorTarget: invalid value (localhost:invalid): must be a valid local URI", + }, + { + name: "invalid address", + collectorTarget: ptr("invalid address"), + wantErr: true, + mode: ptr("unencrypted"), + errMsg: "CollectorTarget: invalid value (invalid address): must be a valid local URI", + }, + { + name: "nil CollectorTarget", + collectorTarget: ptr(""), + wantErr: true, + mode: ptr("unencrypted"), + errMsg: "CollectorTarget: invalid value (): must be a valid local URI", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + var tlsCertPath string + if *tt.mode == "tls" { + tlsCertPath = "/path/to/cert.pem" + } tracing := &Tracing{ Enabled: ptr(true), + TLSCertPath: &tlsCertPath, + Mode: tt.mode, CollectorTarget: tt.collectorTarget, } @@ -309,5 +369,167 @@ func TestTracing_ValidateSamplingRatio(t *testing.T) { } } +func TestTracing_ValidateTLSCertPath(t *testing.T) { + // tests for Tracing.Mode = 'tls' + tls_tests := []struct { + name string + tlsCertPath *string + wantErr bool + errMsg string + }{ + { + name: "valid file path", + tlsCertPath: ptr("/etc/ssl/certs/cert.pem"), + wantErr: false, + }, + { + name: "relative file path", + tlsCertPath: ptr("certs/cert.pem"), + wantErr: false, + }, + { + name: "excessively long file path", + tlsCertPath: ptr(strings.Repeat("z", 4097)), + wantErr: true, + errMsg: "TLSCertPath: invalid value (" + strings.Repeat("z", 4097) + "): must be a valid file path", + }, + { + name: "empty file path", + tlsCertPath: ptr(""), + wantErr: true, + errMsg: "TLSCertPath: invalid value (): must be a valid file path", + }, + } + + // tests for Tracing.Mode = 'unencrypted' + unencrypted_tests := []struct { + name string + tlsCertPath *string + wantErr bool + errMsg string + }{ + { + name: "valid file path", + tlsCertPath: ptr("/etc/ssl/certs/cert.pem"), + wantErr: false, + }, + { + name: "relative file path", + tlsCertPath: ptr("certs/cert.pem"), + wantErr: false, + }, + { + name: "excessively long file path", + tlsCertPath: ptr(strings.Repeat("z", 4097)), + wantErr: false, + }, + { + name: "empty file path", + tlsCertPath: ptr(""), + wantErr: false, + }, + } + + for _, tt := range tls_tests { + t.Run(tt.name, func(t *testing.T) { + tracing := &Tracing{ + Mode: ptr("tls"), + TLSCertPath: tt.tlsCertPath, + Enabled: ptr(true), + } + + err := tracing.ValidateConfig() + + if tt.wantErr { + assert.Error(t, err) + assert.Equal(t, tt.errMsg, err.Error()) + } else { + assert.NoError(t, err) + } + }) + } + + for _, tt := range unencrypted_tests { + t.Run(tt.name, func(t *testing.T) { + tracing := &Tracing{ + Mode: ptr("unencrypted"), + TLSCertPath: tt.tlsCertPath, + Enabled: ptr(true), + } + + err := tracing.ValidateConfig() + + if tt.wantErr { + assert.Error(t, err) + assert.Equal(t, tt.errMsg, err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestTracing_ValidateMode(t *testing.T) { + tests := []struct { + name string + mode *string + tlsCertPath *string + wantErr bool + errMsg string + }{ + { + name: "tls mode with valid TLS path", + mode: ptr("tls"), + tlsCertPath: ptr("/path/to/cert.pem"), + wantErr: false, + }, + { + name: "tls mode without TLS path", + mode: ptr("tls"), + tlsCertPath: nil, + wantErr: true, + errMsg: "TLSCertPath: missing: must be set when Tracing.Mode is tls", + }, + { + name: "unencrypted mode with TLS path", + mode: ptr("unencrypted"), + tlsCertPath: ptr("/path/to/cert.pem"), + wantErr: false, + }, + { + name: "unencrypted mode without TLS path", + mode: ptr("unencrypted"), + tlsCertPath: nil, + wantErr: false, + }, + { + name: "invalid mode", + mode: ptr("unknown"), + tlsCertPath: nil, + wantErr: true, + errMsg: "Mode: invalid value (unknown): must be either 'tls' or 'unencrypted'", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tracing := &Tracing{ + Enabled: ptr(true), + Mode: tt.mode, + TLSCertPath: tt.tlsCertPath, + } + + err := tracing.ValidateConfig() + + if tt.wantErr { + assert.Error(t, err) + assert.Equal(t, tt.errMsg, err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + // ptr is a utility function for converting a value to a pointer to the value. func ptr[T any](t T) *T { return &t } diff --git a/core/config/tracing_config.go b/core/config/tracing_config.go index 28584a9cde4..307a010c486 100644 --- a/core/config/tracing_config.go +++ b/core/config/tracing_config.go @@ -4,6 +4,8 @@ type Tracing interface { Enabled() bool CollectorTarget() string NodeID() string - Attributes() map[string]string SamplingRatio() float64 + TLSCertPath() string + Mode() string + Attributes() map[string]string } diff --git a/core/config/web_config.go b/core/config/web_config.go index 12209a02670..429a31e7e82 100644 --- a/core/config/web_config.go +++ b/core/config/web_config.go @@ -32,7 +32,31 @@ type MFA interface { RPOrigin() string } +type LDAP interface { + ServerAddress() string + ReadOnlyUserLogin() string + ReadOnlyUserPass() string + ServerTLS() bool + SessionTimeout() models.Duration + QueryTimeout() time.Duration + BaseUserAttr() string + BaseDN() string + UsersDN() string + GroupsDN() string + ActiveAttribute() string + ActiveAttributeAllowedValue() string + AdminUserGroupCN() string + EditUserGroupCN() string + RunUserGroupCN() string + ReadUserGroupCN() string + UserApiTokenEnabled() bool + UserAPITokenDuration() models.Duration + UpstreamSyncInterval() models.Duration + UpstreamSyncRateLimit() models.Duration +} + type WebServer interface { + AuthenticationMethod() string AllowOrigins() string BridgeCacheTTL() time.Duration BridgeResponseURL() *url.URL @@ -49,4 +73,5 @@ type WebServer interface { TLS() TLS RateLimit() RateLimit MFA() MFA + LDAP() LDAP } diff --git a/core/gethwrappers/abigen.go b/core/gethwrappers/abigen.go index 5affe546576..d96d9f8c306 100644 --- a/core/gethwrappers/abigen.go +++ b/core/gethwrappers/abigen.go @@ -159,9 +159,17 @@ func getContractName(fileNode *ast.File) string { return contractName } +// Add the `.address` and `.abi` fields to the contract struct. func addContractStructFields(contractName string, fileNode *ast.File) *ast.File { - // Add the `.address` and `.abi` fields to the contract struct - fileNode = astutil.Apply(fileNode, func(cursor *astutil.Cursor) bool { + fileNode = addContractStructFieldsToStruct(contractName, fileNode) + fileNode = addContractStructFieldsToConstructor(contractName, fileNode) + fileNode = addContractStructFieldsToDeployMethod(contractName, fileNode) + return fileNode +} + +// Add the fields to the contract struct. +func addContractStructFieldsToStruct(contractName string, fileNode *ast.File) *ast.File { + return astutil.Apply(fileNode, func(cursor *astutil.Cursor) bool { x, is := cursor.Node().(*ast.StructType) if !is { return true @@ -188,14 +196,14 @@ func addContractStructFields(contractName string, fileNode *ast.File) *ast.File Sel: ast.NewIdent("ABI"), }, } - x.Fields.List = append([]*ast.Field{addrField, abiField}, x.Fields.List...) - return false }, nil).(*ast.File) +} - // Add the fields to the return value of the constructor - fileNode = astutil.Apply(fileNode, func(cursor *astutil.Cursor) bool { +// Add the fields to the return value of the constructor. +func addContractStructFieldsToConstructor(contractName string, fileNode *ast.File) *ast.File { + return astutil.Apply(fileNode, func(cursor *astutil.Cursor) bool { x, is := cursor.Node().(*ast.FuncDecl) if !is { return true @@ -260,11 +268,48 @@ func addContractStructFields(contractName string, fileNode *ast.File) *ast.File } x.Body.List = append([]ast.Stmt{parseABIStmt, checkParseABIErrStmt}, x.Body.List...) - return false }, nil).(*ast.File) +} - return fileNode +// Add the fields to the returned struct in the 'Deploy' method. +func addContractStructFieldsToDeployMethod(contractName string, fileNode *ast.File) *ast.File { + return astutil.Apply(fileNode, func(cursor *astutil.Cursor) bool { + x, is := cursor.Node().(*ast.FuncDecl) + if !is { + return true + } else if x.Name.Name != "Deploy"+contractName { + return false + } + + for _, stmt := range x.Body.List { + returnStmt, is := stmt.(*ast.ReturnStmt) + if !is { + continue + } + if len(returnStmt.Results) < 3 { + continue + } + rs, is := returnStmt.Results[2].(*ast.UnaryExpr) + if !is { + return true + } + lit, is := rs.X.(*ast.CompositeLit) + if !is { + continue + } + addressExpr := &ast.KeyValueExpr{ + Key: ast.NewIdent("address"), + Value: ast.NewIdent("address"), + } + abiExpr := &ast.KeyValueExpr{ + Key: ast.NewIdent("abi"), + Value: ast.NewIdent("*parsed"), + } + lit.Elts = append([]ast.Expr{addressExpr, abiExpr}, lit.Elts...) + } + return false + }, nil).(*ast.File) } func getLogNames(fileNode *ast.File) []string { diff --git a/core/gethwrappers/abigen_test.go b/core/gethwrappers/abigen_test.go new file mode 100644 index 00000000000..7c206f59dcd --- /dev/null +++ b/core/gethwrappers/abigen_test.go @@ -0,0 +1,29 @@ +package gethwrappers + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" +) + +// Test that the generated Deploy method fill all the required fields and returns the correct address. +// We perform this test using the generated LogEmitter wrapper. +func TestGeneratedDeployMethodAddressField(t *testing.T) { + owner := testutils.MustNewSimTransactor(t) + ec := backends.NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), map[common.Address]core.GenesisAccount{ + owner.From: { + Balance: big.NewInt(0).Mul(big.NewInt(10), big.NewInt(1e18)), + }, + }, 10e6) + emitterAddr, _, emitter, err := log_emitter.DeployLogEmitter(owner, ec) + require.NoError(t, err) + require.Equal(t, emitterAddr, emitter.Address()) +} diff --git a/core/gethwrappers/functions/generated/functions/functions.go b/core/gethwrappers/functions/generated/functions/functions.go index 419c21ca058..0c35806b66f 100644 --- a/core/gethwrappers/functions/generated/functions/functions.go +++ b/core/gethwrappers/functions/generated/functions/functions.go @@ -50,7 +50,7 @@ func DeployFunctions(auth *bind.TransactOpts, backend bind.ContractBackend) (com if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &Functions{FunctionsCaller: FunctionsCaller{contract: contract}, FunctionsTransactor: FunctionsTransactor{contract: contract}, FunctionsFilterer: FunctionsFilterer{contract: contract}}, nil + return address, tx, &Functions{address: address, abi: *parsed, FunctionsCaller: FunctionsCaller{contract: contract}, FunctionsTransactor: FunctionsTransactor{contract: contract}, FunctionsFilterer: FunctionsFilterer{contract: contract}}, nil } type Functions struct { diff --git a/core/gethwrappers/functions/generated/functions_allow_list/functions_allow_list.go b/core/gethwrappers/functions/generated/functions_allow_list/functions_allow_list.go index d86c07f1705..0ccb08cdaa9 100644 --- a/core/gethwrappers/functions/generated/functions_allow_list/functions_allow_list.go +++ b/core/gethwrappers/functions/generated/functions_allow_list/functions_allow_list.go @@ -57,7 +57,7 @@ func DeployTermsOfServiceAllowList(auth *bind.TransactOpts, backend bind.Contrac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &TermsOfServiceAllowList{TermsOfServiceAllowListCaller: TermsOfServiceAllowListCaller{contract: contract}, TermsOfServiceAllowListTransactor: TermsOfServiceAllowListTransactor{contract: contract}, TermsOfServiceAllowListFilterer: TermsOfServiceAllowListFilterer{contract: contract}}, nil + return address, tx, &TermsOfServiceAllowList{address: address, abi: *parsed, TermsOfServiceAllowListCaller: TermsOfServiceAllowListCaller{contract: contract}, TermsOfServiceAllowListTransactor: TermsOfServiceAllowListTransactor{contract: contract}, TermsOfServiceAllowListFilterer: TermsOfServiceAllowListFilterer{contract: contract}}, nil } type TermsOfServiceAllowList struct { diff --git a/core/gethwrappers/functions/generated/functions_client_example/functions_client_example.go b/core/gethwrappers/functions/generated/functions_client_example/functions_client_example.go index 6ae90b45edb..00bf6a438fc 100644 --- a/core/gethwrappers/functions/generated/functions_client_example/functions_client_example.go +++ b/core/gethwrappers/functions/generated/functions_client_example/functions_client_example.go @@ -52,7 +52,7 @@ func DeployFunctionsClientExample(auth *bind.TransactOpts, backend bind.Contract if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &FunctionsClientExample{FunctionsClientExampleCaller: FunctionsClientExampleCaller{contract: contract}, FunctionsClientExampleTransactor: FunctionsClientExampleTransactor{contract: contract}, FunctionsClientExampleFilterer: FunctionsClientExampleFilterer{contract: contract}}, nil + return address, tx, &FunctionsClientExample{address: address, abi: *parsed, FunctionsClientExampleCaller: FunctionsClientExampleCaller{contract: contract}, FunctionsClientExampleTransactor: FunctionsClientExampleTransactor{contract: contract}, FunctionsClientExampleFilterer: FunctionsClientExampleFilterer{contract: contract}}, nil } type FunctionsClientExample struct { diff --git a/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go b/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go index 32db52a029f..5f0d2d45f2d 100644 --- a/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go +++ b/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go @@ -71,8 +71,8 @@ type FunctionsResponseRequestMeta struct { } var FunctionsCoordinatorMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"linkToNativeFeed\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"EmptyPublicKey\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InconsistentReportData\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidCalldata\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"linkWei\",\"type\":\"int256\"}],\"name\":\"InvalidLinkWeiPrice\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSubscription\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"MustBeSubOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoTransmittersSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByRouter\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByRouterOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PaymentTooLarge\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ReportInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RouterMustBeSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedPublicKeyChange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedSender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsupportedRequestDataVersion\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"CommitmentDeleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"ConfigUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"requestingContract\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"requestInitiator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"subscriptionOwner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"dataVersion\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"flags\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"callbackGasLimit\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"coordinator\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"estimatedTotalCostJuels\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"client\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint32\",\"name\":\"timeoutTimestamp\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"structFunctionsResponse.Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"name\":\"OracleRequest\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"transmitter\",\"type\":\"address\"}],\"name\":\"OracleResponse\",\"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\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"deleteCommitment\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint256\",\"name\":\"gasPriceWei\",\"type\":\"uint256\"}],\"name\":\"estimateCost\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAdminFee\",\"outputs\":[{\"internalType\":\"uint72\",\"name\":\"\",\"type\":\"uint72\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"getDONFee\",\"outputs\":[{\"internalType\":\"uint72\",\"name\":\"\",\"type\":\"uint72\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDONPublicKey\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getThresholdPublicKey\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getWeiPerUnitLink\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDetails\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blockNumber\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDigestAndEpoch\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"scanLogs\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"oracleWithdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oracleWithdrawAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_transmitters\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"_onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"_offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"_offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"donPublicKey\",\"type\":\"bytes\"}],\"name\":\"setDONPublicKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"thresholdPublicKey\",\"type\":\"bytes\"}],\"name\":\"setThresholdPublicKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"flags\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"requestingContract\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"availableBalance\",\"type\":\"uint96\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"initiatedRequests\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"dataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint64\",\"name\":\"completedRequests\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"subscriptionOwner\",\"type\":\"address\"}],\"internalType\":\"structFunctionsResponse.RequestMeta\",\"name\":\"request\",\"type\":\"tuple\"}],\"name\":\"startRequest\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"coordinator\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"estimatedTotalCostJuels\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"client\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint32\",\"name\":\"timeoutTimestamp\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsResponse.Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[3]\",\"name\":\"reportContext\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"rs\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"ss\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32\",\"name\":\"rawVs\",\"type\":\"bytes32\"}],\"name\":\"transmit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"transmitters\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"updateConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x60c06040523480156200001157600080fd5b506040516200529938038062005299833981016040819052620000349162000474565b8282828260013380600081620000915760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000c457620000c48162000140565b50505015156080526001600160a01b038116620000f457604051632530e88560e11b815260040160405180910390fd5b6001600160a01b0390811660a052600b80549183166c01000000000000000000000000026001600160601b039092169190911790556200013482620001eb565b50505050505062000633565b336001600160a01b038216036200019a5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000088565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b620001f562000349565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff16600160f01b026001600160f01b0364ffffffffff909216600160c81b0264ffffffffff60c81b196001600160481b03909416600160801b0293909316600160801b600160f01b031963ffffffff9586166c010000000000000000000000000263ffffffff60601b19978716680100000000000000000297909716600160401b600160801b0319998716640100000000026001600160401b0319909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e0830151610100840151909216600160e01b026001600160e01b0390921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f5184862906200033e9083906200057d565b60405180910390a150565b6200035362000355565b565b6000546001600160a01b03163314620003535760405162461bcd60e51b815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640162000088565b80516001600160a01b0381168114620003c957600080fd5b919050565b60405161012081016001600160401b03811182821017156200040057634e487b7160e01b600052604160045260246000fd5b60405290565b805163ffffffff81168114620003c957600080fd5b80516001600160481b0381168114620003c957600080fd5b805164ffffffffff81168114620003c957600080fd5b805161ffff81168114620003c957600080fd5b80516001600160e01b0381168114620003c957600080fd5b60008060008385036101608112156200048c57600080fd5b6200049785620003b1565b935061012080601f1983011215620004ae57600080fd5b620004b8620003ce565b9150620004c86020870162000406565b8252620004d86040870162000406565b6020830152620004eb6060870162000406565b6040830152620004fe6080870162000406565b60608301526200051160a087016200041b565b60808301526200052460c0870162000433565b60a08301526200053760e0870162000449565b60c08301526101006200054c8188016200045c565b60e08401526200055e82880162000406565b90830152509150620005746101408501620003b1565b90509250925092565b815163ffffffff908116825260208084015182169083015260408084015182169083015260608084015191821690830152610120820190506080830151620005d060808401826001600160481b03169052565b5060a0830151620005ea60a084018264ffffffffff169052565b5060c08301516200060160c084018261ffff169052565b5060e08301516200061d60e08401826001600160e01b03169052565b506101009283015163ffffffff16919092015290565b60805160a051614c166200068360003960008181610845015281816109d301528181610ca601528181610f3a0152818161104501528181611830015261332c0152600061126e0152614c166000f3fe608060405234801561001057600080fd5b506004361061018d5760003560e01c806381ff7048116100e3578063c3f909d41161008c578063e3d0e71211610066578063e3d0e71214610560578063e4ddcea614610573578063f2fde38b1461058957600080fd5b8063c3f909d4146103b0578063d227d24514610528578063d328a91e1461055857600080fd5b8063a631571e116100bd578063a631571e1461035d578063afcb95d71461037d578063b1dc65a41461039d57600080fd5b806381ff7048146102b557806385b214cf146103225780638da5cb5b1461033557600080fd5b806366316d8d116101455780637f15e1661161011f5780637f15e16614610285578063814118341461029857806381f1b938146102ad57600080fd5b806366316d8d1461026257806379ba5097146102755780637d4807871461027d57600080fd5b8063181f5a7711610176578063181f5a77146101ba5780632a905ccc1461020c57806359b5b7ac1461022e57600080fd5b8063083a5466146101925780631112dadc146101a7575b600080fd5b6101a56101a036600461361f565b61059c565b005b6101a56101b53660046137c8565b6105f1565b6101f66040518060400160405280601c81526020017f46756e6374696f6e7320436f6f7264696e61746f722076312e312e300000000081525081565b60405161020391906138e2565b60405180910390f35b610214610841565b60405168ffffffffffffffffff9091168152602001610203565b61021461023c36600461398a565b50600854700100000000000000000000000000000000900468ffffffffffffffffff1690565b6101a5610270366004613a19565b6108d7565b6101a5610a90565b6101a5610b92565b6101a561029336600461361f565b610d92565b6102a0610de2565b6040516102039190613aa3565b6101f6610e51565b6102ff60015460025463ffffffff74010000000000000000000000000000000000000000830481169378010000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff948516815293909216602084015290820152606001610203565b6101a5610330366004613ab6565b610f22565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610203565b61037061036b366004613acf565b610fd4565b6040516102039190613c24565b604080516001815260006020820181905291810191909152606001610203565b6101a56103ab366004613c78565b611175565b61051b6040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915250604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c01000000000000000000000000810483166060830152700100000000000000000000000000000000810468ffffffffffffffffff166080830152790100000000000000000000000000000000000000000000000000810464ffffffffff1660a08301527e01000000000000000000000000000000000000000000000000000000000000900461ffff1660c08201526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08301527c0100000000000000000000000000000000000000000000000000000000900490911661010082015290565b6040516102039190613d2f565b61053b610536366004613e1f565b61182c565b6040516bffffffffffffffffffffffff9091168152602001610203565b6101f661198c565b6101a561056e366004613f38565b6119e3565b61057b61240f565b604051908152602001610203565b6101a5610597366004614005565b612668565b6105a461267c565b60008190036105df576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d6105ec8284836140bb565b505050565b6105f96126ff565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff167e01000000000000000000000000000000000000000000000000000000000000027dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff64ffffffffff909216790100000000000000000000000000000000000000000000000000027fffff0000000000ffffffffffffffffffffffffffffffffffffffffffffffffff68ffffffffffffffffff90941670010000000000000000000000000000000002939093167fffff0000000000000000000000000000ffffffffffffffffffffffffffffffff63ffffffff9586166c01000000000000000000000000027fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff9787166801000000000000000002979097167fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff998716640100000000027fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e08301516101008401519092167c0100000000000000000000000000000000000000000000000000000000027bffffffffffffffffffffffffffffffffffffffffffffffffffffffff90921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f518486290610836908390613d2f565b60405180910390a150565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16632a905ccc6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d291906141e1565b905090565b6108df612707565b806bffffffffffffffffffffffff166000036109195750336000908152600a60205260409020546bffffffffffffffffffffffff16610973565b336000908152600a60205260409020546bffffffffffffffffffffffff80831691161015610973576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600a6020526040812080548392906109a09084906bffffffffffffffffffffffff1661422d565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055506109f57f000000000000000000000000000000000000000000000000000000000000000090565b6040517f66316d8d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301526bffffffffffffffffffffffff8416602483015291909116906366316d8d90604401600060405180830381600087803b158015610a7457600080fd5b505af1158015610a88573d6000803e3d6000fd5b505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610b16576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610b9a6126ff565b610ba2612707565b6000610bac610de2565b905060005b8151811015610d8e576000600a6000848481518110610bd257610bd2614252565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252810191909152604001600020546bffffffffffffffffffffffff1690508015610d7d576000600a6000858581518110610c3157610c31614252565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550610cc87f000000000000000000000000000000000000000000000000000000000000000090565b73ffffffffffffffffffffffffffffffffffffffff166366316d8d848481518110610cf557610cf5614252565b6020026020010151836040518363ffffffff1660e01b8152600401610d4a92919073ffffffffffffffffffffffffffffffffffffffff9290921682526bffffffffffffffffffffffff16602082015260400190565b600060405180830381600087803b158015610d6457600080fd5b505af1158015610d78573d6000803e3d6000fd5b505050505b50610d8781614281565b9050610bb1565b5050565b610d9a61267c565b6000819003610dd5576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c6105ec8284836140bb565b60606006805480602002602001604051908101604052809291908181526020018280548015610e4757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610e1c575b5050505050905090565b6060600d8054610e6090614022565b9050600003610e9b576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d8054610ea890614022565b80601f0160208091040260200160405190810160405280929190818152602001828054610ed490614022565b8015610e475780601f10610ef657610100808354040283529160200191610e47565b820191906000526020600020905b815481529060010190602001808311610f0457509395945050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610f91576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526007602052604080822091909155517f8a4b97add3359bd6bcf5e82874363670eb5ad0f7615abddbd0ed0a3a98f0f416906108369083815260200190565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101919091523373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461109c576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110ad6110a8836142b9565b6128b2565b90506110bf6060830160408401614005565b815173ffffffffffffffffffffffffffffffffffffffff91909116907fbf50768ccf13bd0110ca6d53a9c4f1f3271abdd4c24a56878863ed25b20598ff3261110d60c0870160a088016143a6565b61111f61016088016101408901614005565b61112988806143c3565b61113b6101208b016101008c01614428565b60208b01356111516101008d0160e08e01614443565b8b60405161116799989796959493929190614460565b60405180910390a35b919050565b60005a604080518b3580825262ffffff6020808f0135600881901c929092169084015293945092917fb04e63db38c49950639fa09d29872f21f5d49d614f3a969d8adf3d4b52e41a62910160405180910390a16040805160608101825260025480825260035460ff8082166020850152610100909104169282019290925290831461125c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f636f6e666967446967657374206d69736d6174636800000000000000000000006044820152606401610b0d565b61126a8b8b8b8b8b8b612d50565b60007f0000000000000000000000000000000000000000000000000000000000000000156112c7576002826020015183604001516112a89190614508565b6112b29190614550565b6112bd906001614508565b60ff1690506112dd565b60208201516112d7906001614508565b60ff1690505b888114611346576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f77726f6e67206e756d626572206f66207369676e6174757265730000000000006044820152606401610b0d565b8887146113af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f7369676e617475726573206f7574206f6620726567697374726174696f6e00006044820152606401610b0d565b3360009081526004602090815260408083208151808301909252805460ff808216845292939192918401916101009091041660028111156113f2576113f2614572565b600281111561140357611403614572565b905250905060028160200151600281111561142057611420614572565b14801561146757506006816000015160ff168154811061144257611442614252565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1633145b6114cd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f756e617574686f72697a6564207472616e736d697474657200000000000000006044820152606401610b0d565b50505050506114da6135b7565b6000808a8a6040516114ed9291906145a1565b604051908190038120611504918e906020016145b1565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120838301909252600080845290830152915060005b8981101561180e57600060018489846020811061156d5761156d614252565b61157a91901a601b614508565b8e8e8681811061158c5761158c614252565b905060200201358d8d878181106115a5576115a5614252565b90506020020135604051600081526020016040526040516115e2949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015611604573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff811660009081526004602090815290849020838501909452835460ff8082168552929650929450840191610100900416600281111561168457611684614572565b600281111561169557611695614572565b90525092506001836020015160028111156116b2576116b2614572565b14611719576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f61646472657373206e6f7420617574686f72697a656420746f207369676e00006044820152606401610b0d565b8251600090879060ff16601f811061173357611733614252565b602002015173ffffffffffffffffffffffffffffffffffffffff16146117b5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6e6f6e2d756e69717565207369676e61747572650000000000000000000000006044820152606401610b0d565b8086846000015160ff16601f81106117cf576117cf614252565b73ffffffffffffffffffffffffffffffffffffffff90921660209290920201526117fa600186614508565b9450508061180790614281565b905061154e565b50505061181f833383858e8e612e07565b5050505050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006040517f10fc49c100000000000000000000000000000000000000000000000000000000815267ffffffffffffffff8816600482015263ffffffff8516602482015273ffffffffffffffffffffffffffffffffffffffff91909116906310fc49c19060440160006040518083038186803b1580156118cc57600080fd5b505afa1580156118e0573d6000803e3d6000fd5b5050505066038d7ea4c68000821115611925576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061192f610841565b9050600061197287878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061023c92505050565b905061198085858385612fd5565b98975050505050505050565b6060600c805461199b90614022565b90506000036119d6576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c8054610ea890614022565b855185518560ff16601f831115611a56576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f746f6f206d616e79207369676e657273000000000000000000000000000000006044820152606401610b0d565b80600003611ac0576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f66206d75737420626520706f73697469766500000000000000000000000000006044820152606401610b0d565b818314611b4e576040517f89a61989000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6f7261636c6520616464726573736573206f7574206f6620726567697374726160448201527f74696f6e000000000000000000000000000000000000000000000000000000006064820152608401610b0d565b611b598160036145c5565b8311611bc1576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661756c74792d6f7261636c65206620746f6f206869676800000000000000006044820152606401610b0d565b611bc961267c565b6040805160c0810182528a8152602081018a905260ff89169181018290526060810188905267ffffffffffffffff8716608082015260a0810186905290611c10908861311d565b60055415611dc557600554600090611c2a906001906145dc565b9050600060058281548110611c4157611c41614252565b60009182526020822001546006805473ffffffffffffffffffffffffffffffffffffffff90921693509084908110611c7b57611c7b614252565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff85811684526004909252604080842080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090811690915592909116808452922080549091169055600580549192509080611cfb57611cfb6145ef565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690550190556006805480611d6457611d646145ef565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff000000000000000000000000000000000000000016905501905550611c10915050565b60005b81515181101561222c5760006004600084600001518481518110611dee57611dee614252565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115611e3857611e38614572565b14611e9f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265706561746564207369676e657220616464726573730000000000000000006044820152606401610b0d565b6040805180820190915260ff82168152600160208201528251805160049160009185908110611ed057611ed0614252565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001617610100836002811115611f7157611f71614572565b021790555060009150611f819050565b6004600084602001518481518110611f9b57611f9b614252565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115611fe557611fe5614572565b1461204c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f7265706561746564207472616e736d69747465722061646472657373000000006044820152606401610b0d565b6040805180820190915260ff82168152602081016002815250600460008460200151848151811061207f5761207f614252565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000161761010083600281111561212057612120614572565b02179055505082518051600592508390811061213e5761213e614252565b602090810291909101810151825460018101845560009384529282902090920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90931692909217909155820151805160069190839081106121ba576121ba614252565b60209081029190910181015182546001810184556000938452919092200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9092169190911790558061222481614281565b915050611dc8565b506040810151600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff909216919091179055600180547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff8116780100000000000000000000000000000000000000000000000063ffffffff43811682029290921780855592048116929182916014916122e49184917401000000000000000000000000000000000000000090041661461e565b92506101000a81548163ffffffff021916908363ffffffff1602179055506123434630600160149054906101000a900463ffffffff1663ffffffff16856000015186602001518760400151886060015189608001518a60a00151613136565b600281905582518051600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010060ff9093169290920291909117905560015460208501516040808701516060880151608089015160a08a015193517f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e05986123fa988b9891977401000000000000000000000000000000000000000090920463ffffffff1696909591949193919261463b565b60405180910390a15050505050505050505050565b604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116838501526c0100000000000000000000000080830482166060850152700100000000000000000000000000000000830468ffffffffffffffffff166080850152790100000000000000000000000000000000000000000000000000830464ffffffffff1660a0808601919091527e0100000000000000000000000000000000000000000000000000000000000090930461ffff1660c08501526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08601527c01000000000000000000000000000000000000000000000000000000009004909116610100840152600b5484517ffeaf968c00000000000000000000000000000000000000000000000000000000815294516000958694859490930473ffffffffffffffffffffffffffffffffffffffff169263feaf968c926004808401938290030181865afa15801561259d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125c191906146eb565b5093505092505080426125d491906145dc565b836020015163ffffffff161080156125f657506000836020015163ffffffff16115b1561262457505060e001517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16919050565b60008213612661576040517f43d4cf6600000000000000000000000000000000000000000000000000000000815260048101839052602401610b0d565b5092915050565b61267061267c565b612679816131e1565b50565b60005473ffffffffffffffffffffffffffffffffffffffff1633146126fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610b0d565b565b6126fd61267c565b600b546bffffffffffffffffffffffff1660000361272157565b600061272b610de2565b90508051600003612768576040517f30274b3a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8051600b54600091612787916bffffffffffffffffffffffff1661473b565b905060005b82518110156128535781600a60008584815181106127ac576127ac614252565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282829054906101000a90046bffffffffffffffffffffffff166128149190614766565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508061284c90614281565b905061278c565b508151612860908261478b565b600b80546000906128809084906bffffffffffffffffffffffff1661422d565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055505050565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081018290526101208101829052610140810191909152604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c0100000000000000000000000081048316606083015268ffffffffffffffffff700100000000000000000000000000000000820416608083015264ffffffffff79010000000000000000000000000000000000000000000000000082041660a083015261ffff7e01000000000000000000000000000000000000000000000000000000000000909104811660c083018190526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08501527c0100000000000000000000000000000000000000000000000000000000900490931661010080840191909152850151919291161115612a6d576040517fdada758700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600854600090700100000000000000000000000000000000900468ffffffffffffffffff1690506000612aaa8560e001513a848860800151612fd5565b9050806bffffffffffffffffffffffff1685606001516bffffffffffffffffffffffff161015612b06576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083610100015163ffffffff1642612b1f91906147b3565b905060003087604001518860a001518960c001516001612b3f91906147c6565b8a5180516020918201206101008d015160e08e0151604051612bf398979695948c918c9132910173ffffffffffffffffffffffffffffffffffffffff9a8b168152988a1660208a015267ffffffffffffffff97881660408a0152959096166060880152608087019390935261ffff9190911660a086015263ffffffff90811660c08601526bffffffffffffffffffffffff9190911660e0850152919091166101008301529091166101208201526101400190565b6040516020818303038152906040528051906020012090506040518061016001604052808281526020013073ffffffffffffffffffffffffffffffffffffffff168152602001846bffffffffffffffffffffffff168152602001886040015173ffffffffffffffffffffffffffffffffffffffff1681526020018860a0015167ffffffffffffffff1681526020018860e0015163ffffffff168152602001886080015168ffffffffffffffffff1681526020018568ffffffffffffffffff168152602001866040015163ffffffff1664ffffffffff168152602001866060015163ffffffff1664ffffffffff1681526020018363ffffffff16815250955085604051602001612d029190613c24565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152918152815160209283012060009384526007909252909120555092949350505050565b6000612d5d8260206145c5565b612d688560206145c5565b612d74886101446147b3565b612d7e91906147b3565b612d8891906147b3565b612d939060006147b3565b9050368114612dfe576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f63616c6c64617461206c656e677468206d69736d6174636800000000000000006044820152606401610b0d565b50505050505050565b606080808080612e19868801886148c2565b8451949950929750909550935091501580612e3657508351855114155b80612e4357508251855114155b80612e5057508151855114155b80612e5d57508051855114155b15612e94576040517f0be3632800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b8551811015612fc7576000612f2c878381518110612eb757612eb7614252565b6020026020010151878481518110612ed157612ed1614252565b6020026020010151878581518110612eeb57612eeb614252565b6020026020010151878681518110612f0557612f05614252565b6020026020010151878781518110612f1f57612f1f614252565b60200260200101516132d6565b90506000816006811115612f4257612f42614572565b1480612f5f57506001816006811115612f5d57612f5d614572565b145b15612fb657868281518110612f7657612f76614252565b60209081029190910181015160405133815290917fc708e0440951fd63499c0f7a73819b469ee5dd3ecc356c0ab4eb7f18389009d9910160405180910390a25b50612fc081614281565b9050612e97565b505050505050505050505050565b6008546000908190869061300d9063ffffffff6c0100000000000000000000000082048116916801000000000000000090041661461e565b613017919061461e565b60085463ffffffff919091169150790100000000000000000000000000000000000000000000000000900464ffffffffff1685101561307a57600854790100000000000000000000000000000000000000000000000000900464ffffffffff1694505b600854600090612710906130949063ffffffff16886145c5565b61309e9190614994565b6130a890876147b3565b905060006130b5826134e6565b905060006130d1846bffffffffffffffffffffffff84166145c5565b905060006130ed68ffffffffffffffffff808916908a16614766565b905061310f61310a6bffffffffffffffffffffffff8316846147b3565b613515565b9a9950505050505050505050565b6000613127610de2565b511115610d8e57610d8e612707565b6000808a8a8a8a8a8a8a8a8a60405160200161315a999897969594939291906149a8565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179150509998505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603613260576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610b0d565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600080838060200190518101906132ed9190614a74565b905060006132fa3a6134e6565b905060008261012001518361010001516133149190614b3c565b6133259064ffffffffff168361478b565b90506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663330605298b8b878960e0015168ffffffffffffffffff16886133849190614766565b338b6040518763ffffffff1660e01b81526004016133a796959493929190614b5a565b60408051808303816000875af11580156133c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133e99190614bd6565b9092509050600082600681111561340257613402614572565b148061341f5750600182600681111561341d5761341d614572565b145b156134d85760008b81526007602052604081205561343d8184614766565b336000908152600a6020526040812080547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff93841617905560e0870151600b805468ffffffffffffffffff909216939092916134a991859116614766565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055505b509998505050505050505050565b600061350f6134f361240f565b61350584670de0b6b3a76400006145c5565b61310a9190614994565b92915050565b60006bffffffffffffffffffffffff8211156135b3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f36206269747300000000000000000000000000000000000000000000000000006064820152608401610b0d565b5090565b604051806103e00160405280601f906020820280368337509192915050565b60008083601f8401126135e857600080fd5b50813567ffffffffffffffff81111561360057600080fd5b60208301915083602082850101111561361857600080fd5b9250929050565b6000806020838503121561363257600080fd5b823567ffffffffffffffff81111561364957600080fd5b613655858286016135d6565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610120810167ffffffffffffffff811182821017156136b4576136b4613661565b60405290565b604051610160810167ffffffffffffffff811182821017156136b4576136b4613661565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561372557613725613661565b604052919050565b63ffffffff8116811461267957600080fd5b80356111708161372d565b68ffffffffffffffffff8116811461267957600080fd5b80356111708161374a565b64ffffffffff8116811461267957600080fd5b80356111708161376c565b803561ffff8116811461117057600080fd5b80357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116811461117057600080fd5b600061012082840312156137db57600080fd5b6137e3613690565b6137ec8361373f565b81526137fa6020840161373f565b602082015261380b6040840161373f565b604082015261381c6060840161373f565b606082015261382d60808401613761565b608082015261383e60a0840161377f565b60a082015261384f60c0840161378a565b60c082015261386060e0840161379c565b60e082015261010061387381850161373f565b908201529392505050565b6000815180845260005b818110156138a457602081850181015186830182015201613888565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b6020815260006138f5602083018461387e565b9392505050565b600082601f83011261390d57600080fd5b813567ffffffffffffffff81111561392757613927613661565b61395860207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016136de565b81815284602083860101111561396d57600080fd5b816020850160208301376000918101602001919091529392505050565b60006020828403121561399c57600080fd5b813567ffffffffffffffff8111156139b357600080fd5b6139bf848285016138fc565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461267957600080fd5b8035611170816139c7565b6bffffffffffffffffffffffff8116811461267957600080fd5b8035611170816139f4565b60008060408385031215613a2c57600080fd5b8235613a37816139c7565b91506020830135613a47816139f4565b809150509250929050565b600081518084526020808501945080840160005b83811015613a9857815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101613a66565b509495945050505050565b6020815260006138f56020830184613a52565b600060208284031215613ac857600080fd5b5035919050565b600060208284031215613ae157600080fd5b813567ffffffffffffffff811115613af857600080fd5b820161016081850312156138f557600080fd5b805182526020810151613b36602084018273ffffffffffffffffffffffffffffffffffffffff169052565b506040810151613b5660408401826bffffffffffffffffffffffff169052565b506060810151613b7e606084018273ffffffffffffffffffffffffffffffffffffffff169052565b506080810151613b9a608084018267ffffffffffffffff169052565b5060a0810151613bb260a084018263ffffffff169052565b5060c0810151613bcf60c084018268ffffffffffffffffff169052565b5060e0810151613bec60e084018268ffffffffffffffffff169052565b506101008181015164ffffffffff9081169184019190915261012080830151909116908301526101409081015163ffffffff16910152565b610160810161350f8284613b0b565b60008083601f840112613c4557600080fd5b50813567ffffffffffffffff811115613c5d57600080fd5b6020830191508360208260051b850101111561361857600080fd5b60008060008060008060008060e0898b031215613c9457600080fd5b606089018a811115613ca557600080fd5b8998503567ffffffffffffffff80821115613cbf57600080fd5b613ccb8c838d016135d6565b909950975060808b0135915080821115613ce457600080fd5b613cf08c838d01613c33565b909750955060a08b0135915080821115613d0957600080fd5b50613d168b828c01613c33565b999c989b50969995989497949560c00135949350505050565b815163ffffffff908116825260208084015182169083015260408084015182169083015260608084015191821690830152610120820190506080830151613d83608084018268ffffffffffffffffff169052565b5060a0830151613d9c60a084018264ffffffffff169052565b5060c0830151613db260c084018261ffff169052565b5060e0830151613de260e08401827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169052565b506101008381015163ffffffff8116848301525b505092915050565b67ffffffffffffffff8116811461267957600080fd5b803561117081613dfe565b600080600080600060808688031215613e3757600080fd5b8535613e4281613dfe565b9450602086013567ffffffffffffffff811115613e5e57600080fd5b613e6a888289016135d6565b9095509350506040860135613e7e8161372d565b949793965091946060013592915050565b600067ffffffffffffffff821115613ea957613ea9613661565b5060051b60200190565b600082601f830112613ec457600080fd5b81356020613ed9613ed483613e8f565b6136de565b82815260059290921b84018101918181019086841115613ef857600080fd5b8286015b84811015613f1c578035613f0f816139c7565b8352918301918301613efc565b509695505050505050565b803560ff8116811461117057600080fd5b60008060008060008060c08789031215613f5157600080fd5b863567ffffffffffffffff80821115613f6957600080fd5b613f758a838b01613eb3565b97506020890135915080821115613f8b57600080fd5b613f978a838b01613eb3565b9650613fa560408a01613f27565b95506060890135915080821115613fbb57600080fd5b613fc78a838b016138fc565b9450613fd560808a01613e14565b935060a0890135915080821115613feb57600080fd5b50613ff889828a016138fc565b9150509295509295509295565b60006020828403121561401757600080fd5b81356138f5816139c7565b600181811c9082168061403657607f821691505b60208210810361406f577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156105ec57600081815260208120601f850160051c8101602086101561409c5750805b601f850160051c820191505b81811015610a88578281556001016140a8565b67ffffffffffffffff8311156140d3576140d3613661565b6140e7836140e18354614022565b83614075565b6000601f84116001811461413957600085156141035750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b1783556141cf565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b828110156141885786850135825560209485019460019092019101614168565b50868210156141c3577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b80516111708161374a565b6000602082840312156141f357600080fd5b81516138f58161374a565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6bffffffffffffffffffffffff828116828216039080821115612661576126616141fe565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036142b2576142b26141fe565b5060010190565b600061016082360312156142cc57600080fd5b6142d46136ba565b823567ffffffffffffffff8111156142eb57600080fd5b6142f7368286016138fc565b82525060208301356020820152614310604084016139e9565b604082015261432160608401613a0e565b606082015261433260808401613761565b608082015261434360a08401613e14565b60a082015261435460c08401613e14565b60c082015261436560e0840161373f565b60e082015261010061437881850161378a565b9082015261012061438a848201613e14565b9082015261014061439c8482016139e9565b9082015292915050565b6000602082840312156143b857600080fd5b81356138f581613dfe565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126143f857600080fd5b83018035915067ffffffffffffffff82111561441357600080fd5b60200191503681900382131561361857600080fd5b60006020828403121561443a57600080fd5b6138f58261378a565b60006020828403121561445557600080fd5b81356138f58161372d565b73ffffffffffffffffffffffffffffffffffffffff8a8116825267ffffffffffffffff8a166020830152881660408201526102406060820181905281018690526000610260878982850137600083890182015261ffff8716608084015260a0830186905263ffffffff851660c0840152601f88017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016830101905061310f60e0830184613b0b565b60ff818116838216019081111561350f5761350f6141fe565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600060ff83168061456357614563614521565b8060ff84160491505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8183823760009101908152919050565b828152606082602083013760800192915050565b808202811582820484141761350f5761350f6141fe565b8181038181111561350f5761350f6141fe565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b63ffffffff818116838216019080821115612661576126616141fe565b600061012063ffffffff808d1684528b6020850152808b1660408501525080606084015261466b8184018a613a52565b9050828103608084015261467f8189613a52565b905060ff871660a084015282810360c084015261469c818761387e565b905067ffffffffffffffff851660e08401528281036101008401526146c1818561387e565b9c9b505050505050505050505050565b805169ffffffffffffffffffff8116811461117057600080fd5b600080600080600060a0868803121561470357600080fd5b61470c866146d1565b945060208601519350604086015192506060860151915061472f608087016146d1565b90509295509295909350565b60006bffffffffffffffffffffffff8084168061475a5761475a614521565b92169190910492915050565b6bffffffffffffffffffffffff818116838216019080821115612661576126616141fe565b6bffffffffffffffffffffffff818116838216028082169190828114613df657613df66141fe565b8082018082111561350f5761350f6141fe565b67ffffffffffffffff818116838216019080821115612661576126616141fe565b600082601f8301126147f857600080fd5b81356020614808613ed483613e8f565b82815260059290921b8401810191818101908684111561482757600080fd5b8286015b84811015613f1c578035835291830191830161482b565b600082601f83011261485357600080fd5b81356020614863613ed483613e8f565b82815260059290921b8401810191818101908684111561488257600080fd5b8286015b84811015613f1c57803567ffffffffffffffff8111156148a65760008081fd5b6148b48986838b01016138fc565b845250918301918301614886565b600080600080600060a086880312156148da57600080fd5b853567ffffffffffffffff808211156148f257600080fd5b6148fe89838a016147e7565b9650602088013591508082111561491457600080fd5b61492089838a01614842565b9550604088013591508082111561493657600080fd5b61494289838a01614842565b9450606088013591508082111561495857600080fd5b61496489838a01614842565b9350608088013591508082111561497a57600080fd5b5061498788828901614842565b9150509295509295909350565b6000826149a3576149a3614521565b500490565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b1660408501528160608501526149ef8285018b613a52565b91508382036080850152614a03828a613a52565b915060ff881660a085015283820360c0850152614a20828861387e565b90861660e085015283810361010085015290506146c1818561387e565b8051611170816139c7565b8051611170816139f4565b805161117081613dfe565b80516111708161372d565b80516111708161376c565b60006101608284031215614a8757600080fd5b614a8f6136ba565b82518152614a9f60208401614a3d565b6020820152614ab060408401614a48565b6040820152614ac160608401614a3d565b6060820152614ad260808401614a53565b6080820152614ae360a08401614a5e565b60a0820152614af460c084016141d6565b60c0820152614b0560e084016141d6565b60e0820152610100614b18818501614a69565b90820152610120614b2a848201614a69565b90820152610140613873848201614a5e565b64ffffffffff818116838216019080821115612661576126616141fe565b6000610200808352614b6e8184018a61387e565b90508281036020840152614b82818961387e565b6bffffffffffffffffffffffff88811660408601528716606085015273ffffffffffffffffffffffffffffffffffffffff861660808501529150614bcb905060a0830184613b0b565b979650505050505050565b60008060408385031215614be957600080fd5b825160078110614bf857600080fd5b6020840151909250613a47816139f456fea164736f6c6343000813000a", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"linkToNativeFeed\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"EmptyPublicKey\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InconsistentReportData\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidCalldata\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"linkWei\",\"type\":\"int256\"}],\"name\":\"InvalidLinkWeiPrice\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSubscription\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"MustBeSubOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoTransmittersSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByRouter\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByRouterOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PaymentTooLarge\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"ReportInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RouterMustBeSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedPublicKeyChange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedSender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsupportedRequestDataVersion\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"CommitmentDeleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"ConfigUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"requestingContract\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"requestInitiator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"subscriptionOwner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"dataVersion\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"flags\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"callbackGasLimit\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"coordinator\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"estimatedTotalCostJuels\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"client\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint32\",\"name\":\"timeoutTimestamp\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"structFunctionsResponse.Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"name\":\"OracleRequest\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"transmitter\",\"type\":\"address\"}],\"name\":\"OracleResponse\",\"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\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"juelsPerGas\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"l1FeeShareWei\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"callbackCostJuels\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"totalCostJuels\",\"type\":\"uint96\"}],\"name\":\"RequestBilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"deleteCommitment\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint256\",\"name\":\"gasPriceWei\",\"type\":\"uint256\"}],\"name\":\"estimateCost\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAdminFee\",\"outputs\":[{\"internalType\":\"uint72\",\"name\":\"\",\"type\":\"uint72\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"getDONFee\",\"outputs\":[{\"internalType\":\"uint72\",\"name\":\"\",\"type\":\"uint72\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDONPublicKey\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getThresholdPublicKey\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getWeiPerUnitLink\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDetails\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blockNumber\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDigestAndEpoch\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"scanLogs\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"oracleWithdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oracleWithdrawAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_transmitters\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"_onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"_offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"_offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"donPublicKey\",\"type\":\"bytes\"}],\"name\":\"setDONPublicKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"thresholdPublicKey\",\"type\":\"bytes\"}],\"name\":\"setThresholdPublicKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"flags\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"requestingContract\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"availableBalance\",\"type\":\"uint96\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"initiatedRequests\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"dataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint64\",\"name\":\"completedRequests\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"subscriptionOwner\",\"type\":\"address\"}],\"internalType\":\"structFunctionsResponse.RequestMeta\",\"name\":\"request\",\"type\":\"tuple\"}],\"name\":\"startRequest\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"coordinator\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"estimatedTotalCostJuels\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"client\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint32\",\"name\":\"timeoutTimestamp\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsResponse.Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[3]\",\"name\":\"reportContext\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"rs\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"ss\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32\",\"name\":\"rawVs\",\"type\":\"bytes32\"}],\"name\":\"transmit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"transmitters\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"updateConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60a06040523480156200001157600080fd5b50604051620056d0380380620056d083398101604081905262000034916200046d565b8282828233806000816200008f5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000c257620000c28162000139565b5050506001600160a01b038116620000ed57604051632530e88560e11b815260040160405180910390fd5b6001600160a01b03908116608052600b80549183166c01000000000000000000000000026001600160601b039092169190911790556200012d82620001e4565b5050505050506200062c565b336001600160a01b03821603620001935760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000086565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b620001ee62000342565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff16600160f01b026001600160f01b0364ffffffffff909216600160c81b0264ffffffffff60c81b196001600160481b03909416600160801b0293909316600160801b600160f01b031963ffffffff9586166c010000000000000000000000000263ffffffff60601b19978716680100000000000000000297909716600160401b600160801b0319998716640100000000026001600160401b0319909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e0830151610100840151909216600160e01b026001600160e01b0390921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f5184862906200033790839062000576565b60405180910390a150565b6200034c6200034e565b565b6000546001600160a01b031633146200034c5760405162461bcd60e51b815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640162000086565b80516001600160a01b0381168114620003c257600080fd5b919050565b60405161012081016001600160401b0381118282101715620003f957634e487b7160e01b600052604160045260246000fd5b60405290565b805163ffffffff81168114620003c257600080fd5b80516001600160481b0381168114620003c257600080fd5b805164ffffffffff81168114620003c257600080fd5b805161ffff81168114620003c257600080fd5b80516001600160e01b0381168114620003c257600080fd5b60008060008385036101608112156200048557600080fd5b6200049085620003aa565b935061012080601f1983011215620004a757600080fd5b620004b1620003c7565b9150620004c160208701620003ff565b8252620004d160408701620003ff565b6020830152620004e460608701620003ff565b6040830152620004f760808701620003ff565b60608301526200050a60a0870162000414565b60808301526200051d60c087016200042c565b60a08301526200053060e0870162000442565b60c08301526101006200054581880162000455565b60e084015262000557828801620003ff565b908301525091506200056d6101408501620003aa565b90509250925092565b815163ffffffff908116825260208084015182169083015260408084015182169083015260608084015191821690830152610120820190506080830151620005c960808401826001600160481b03169052565b5060a0830151620005e360a084018264ffffffffff169052565b5060c0830151620005fa60c084018261ffff169052565b5060e08301516200061660e08401826001600160e01b03169052565b506101009283015163ffffffff16919092015290565b60805161505e6200067260003960008181610845015281816109d301528181610ca601528181610f3a01528181611045015281816117890152613490015261505e6000f3fe608060405234801561001057600080fd5b506004361061018d5760003560e01c806381ff7048116100e3578063c3f909d41161008c578063e3d0e71211610066578063e3d0e71214610560578063e4ddcea614610573578063f2fde38b1461058957600080fd5b8063c3f909d4146103b0578063d227d24514610528578063d328a91e1461055857600080fd5b8063a631571e116100bd578063a631571e1461035d578063afcb95d71461037d578063b1dc65a41461039d57600080fd5b806381ff7048146102b557806385b214cf146103225780638da5cb5b1461033557600080fd5b806366316d8d116101455780637f15e1661161011f5780637f15e16614610285578063814118341461029857806381f1b938146102ad57600080fd5b806366316d8d1461026257806379ba5097146102755780637d4807871461027d57600080fd5b8063181f5a7711610176578063181f5a77146101ba5780632a905ccc1461020c57806359b5b7ac1461022e57600080fd5b8063083a5466146101925780631112dadc146101a7575b600080fd5b6101a56101a03660046139d4565b61059c565b005b6101a56101b5366004613b7d565b6105f1565b6101f66040518060400160405280601c81526020017f46756e6374696f6e7320436f6f7264696e61746f722076312e312e300000000081525081565b6040516102039190613ca1565b60405180910390f35b610214610841565b60405168ffffffffffffffffff9091168152602001610203565b61021461023c366004613d42565b50600854700100000000000000000000000000000000900468ffffffffffffffffff1690565b6101a5610270366004613dd1565b6108d7565b6101a5610a90565b6101a5610b92565b6101a56102933660046139d4565b610d92565b6102a0610de2565b6040516102039190613e5b565b6101f6610e51565b6102ff60015460025463ffffffff74010000000000000000000000000000000000000000830481169378010000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff948516815293909216602084015290820152606001610203565b6101a5610330366004613e6e565b610f22565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610203565b61037061036b366004613e87565b610fd4565b6040516102039190613fdc565b604080516001815260006020820181905291810191909152606001610203565b6101a56103ab366004614030565b611175565b61051b6040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915250604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c01000000000000000000000000810483166060830152700100000000000000000000000000000000810468ffffffffffffffffff166080830152790100000000000000000000000000000000000000000000000000810464ffffffffff1660a08301527e01000000000000000000000000000000000000000000000000000000000000900461ffff1660c08201526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08301527c0100000000000000000000000000000000000000000000000000000000900490911661010082015290565b60405161020391906140e7565b61053b6105363660046141d7565b611785565b6040516bffffffffffffffffffffffff9091168152602001610203565b6101f66118e5565b6101a561056e3660046142f0565b61193c565b61057b6124b8565b604051908152602001610203565b6101a56105973660046143bd565b612711565b6105a4612725565b60008190036105df576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d6105ec828483614473565b505050565b6105f96127a8565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff167e01000000000000000000000000000000000000000000000000000000000000027dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff64ffffffffff909216790100000000000000000000000000000000000000000000000000027fffff0000000000ffffffffffffffffffffffffffffffffffffffffffffffffff68ffffffffffffffffff90941670010000000000000000000000000000000002939093167fffff0000000000000000000000000000ffffffffffffffffffffffffffffffff63ffffffff9586166c01000000000000000000000000027fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff9787166801000000000000000002979097167fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff998716640100000000027fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e08301516101008401519092167c0100000000000000000000000000000000000000000000000000000000027bffffffffffffffffffffffffffffffffffffffffffffffffffffffff90921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f5184862906108369083906140e7565b60405180910390a150565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16632a905ccc6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d29190614599565b905090565b6108df6127b0565b806bffffffffffffffffffffffff166000036109195750336000908152600a60205260409020546bffffffffffffffffffffffff16610973565b336000908152600a60205260409020546bffffffffffffffffffffffff80831691161015610973576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600a6020526040812080548392906109a09084906bffffffffffffffffffffffff166145e5565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055506109f57f000000000000000000000000000000000000000000000000000000000000000090565b6040517f66316d8d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301526bffffffffffffffffffffffff8416602483015291909116906366316d8d90604401600060405180830381600087803b158015610a7457600080fd5b505af1158015610a88573d6000803e3d6000fd5b505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610b16576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610b9a6127a8565b610ba26127b0565b6000610bac610de2565b905060005b8151811015610d8e576000600a6000848481518110610bd257610bd261460a565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252810191909152604001600020546bffffffffffffffffffffffff1690508015610d7d576000600a6000858581518110610c3157610c3161460a565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550610cc87f000000000000000000000000000000000000000000000000000000000000000090565b73ffffffffffffffffffffffffffffffffffffffff166366316d8d848481518110610cf557610cf561460a565b6020026020010151836040518363ffffffff1660e01b8152600401610d4a92919073ffffffffffffffffffffffffffffffffffffffff9290921682526bffffffffffffffffffffffff16602082015260400190565b600060405180830381600087803b158015610d6457600080fd5b505af1158015610d78573d6000803e3d6000fd5b505050505b50610d8781614639565b9050610bb1565b5050565b610d9a612725565b6000819003610dd5576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c6105ec828483614473565b60606006805480602002602001604051908101604052809291908181526020018280548015610e4757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610e1c575b5050505050905090565b6060600d8054610e60906143da565b9050600003610e9b576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d8054610ea8906143da565b80601f0160208091040260200160405190810160405280929190818152602001828054610ed4906143da565b8015610e475780601f10610ef657610100808354040283529160200191610e47565b820191906000526020600020905b815481529060010190602001808311610f0457509395945050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610f91576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526007602052604080822091909155517f8a4b97add3359bd6bcf5e82874363670eb5ad0f7615abddbd0ed0a3a98f0f416906108369083815260200190565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101919091523373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461109c576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110ad6110a883614671565b61295c565b90506110bf60608301604084016143bd565b815173ffffffffffffffffffffffffffffffffffffffff91909116907fbf50768ccf13bd0110ca6d53a9c4f1f3271abdd4c24a56878863ed25b20598ff3261110d60c0870160a0880161475e565b61111f610160880161014089016143bd565b611129888061477b565b61113b6101208b016101008c016147e0565b60208b01356111516101008d0160e08e016147fb565b8b60405161116799989796959493929190614818565b60405180910390a35b919050565b60005a604080518b3580825262ffffff6020808f0135600881901c929092169084015293945092917fb04e63db38c49950639fa09d29872f21f5d49d614f3a969d8adf3d4b52e41a62910160405180910390a16111d68a8a8a8a8a8a612dfa565b6003546000906002906111f49060ff808216916101009004166148c0565b6111fe9190614908565b6112099060016148c0565b60ff169050878114611277576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f77726f6e67206e756d626572206f66207369676e6174757265730000000000006044820152606401610b0d565b878614611306576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f7265706f727420727320616e64207373206d757374206265206f66206571756160448201527f6c206c656e6774680000000000000000000000000000000000000000000000006064820152608401610b0d565b3360009081526004602090815260408083208151808301909252805460ff808216845292939192918401916101009091041660028111156113495761134961492a565b600281111561135a5761135a61492a565b90525090506002816020015160028111156113775761137761492a565b141580156113c057506006816000015160ff168154811061139a5761139a61460a565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff163314155b15611427576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f756e617574686f72697a6564207472616e736d697474657200000000000000006044820152606401610b0d565b5050505061143361396c565b6000808a8a604051611446929190614959565b60405190819003812061145d918e90602001614969565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120838301909252600080845290830152915060005b898110156117675760006001848984602081106114c6576114c661460a565b6114d391901a601b6148c0565b8e8e868181106114e5576114e561460a565b905060200201358d8d878181106114fe576114fe61460a565b905060200201356040516000815260200160405260405161153b949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa15801561155d573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff811660009081526004602090815290849020838501909452835460ff808216855292965092945084019161010090041660028111156115dd576115dd61492a565b60028111156115ee576115ee61492a565b905250925060018360200151600281111561160b5761160b61492a565b14611672576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f61646472657373206e6f7420617574686f72697a656420746f207369676e00006044820152606401610b0d565b8251600090879060ff16601f811061168c5761168c61460a565b602002015173ffffffffffffffffffffffffffffffffffffffff161461170e576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6e6f6e2d756e69717565207369676e61747572650000000000000000000000006044820152606401610b0d565b8086846000015160ff16601f81106117285761172861460a565b73ffffffffffffffffffffffffffffffffffffffff90921660209290920201526117536001866148c0565b9450508061176090614639565b90506114a7565b505050611778833383858e8e612eb1565b5050505050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006040517f10fc49c100000000000000000000000000000000000000000000000000000000815267ffffffffffffffff8816600482015263ffffffff8516602482015273ffffffffffffffffffffffffffffffffffffffff91909116906310fc49c19060440160006040518083038186803b15801561182557600080fd5b505afa158015611839573d6000803e3d6000fd5b5050505066038d7ea4c6800082111561187e576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611888610841565b905060006118cb87878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061023c92505050565b90506118d9858583856130b0565b98975050505050505050565b6060600c80546118f4906143da565b905060000361192f576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c8054610ea8906143da565b855185518560ff16601f8311156119af576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f746f6f206d616e79207369676e657273000000000000000000000000000000006044820152606401610b0d565b80600003611a19576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f66206d75737420626520706f73697469766500000000000000000000000000006044820152606401610b0d565b818314611aa7576040517f89a61989000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6f7261636c6520616464726573736573206f7574206f6620726567697374726160448201527f74696f6e000000000000000000000000000000000000000000000000000000006064820152608401610b0d565b611ab281600361497d565b8311611b1a576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661756c74792d6f7261636c65206620746f6f206869676800000000000000006044820152606401610b0d565b611b22612725565b6040805160c0810182528a8152602081018a905260ff89169181018290526060810188905267ffffffffffffffff8716608082015260a0810186905290611b69908861321d565b60055415611d1e57600554600090611b8390600190614994565b9050600060058281548110611b9a57611b9a61460a565b60009182526020822001546006805473ffffffffffffffffffffffffffffffffffffffff90921693509084908110611bd457611bd461460a565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff85811684526004909252604080842080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090811690915592909116808452922080549091169055600580549192509080611c5457611c546149a7565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690550190556006805480611cbd57611cbd6149a7565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff000000000000000000000000000000000000000016905501905550611b69915050565b60005b8151518110156122d557815180516000919083908110611d4357611d4361460a565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603611dc8576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f7369676e6572206d757374206e6f7420626520656d70747900000000000000006044820152606401610b0d565b600073ffffffffffffffffffffffffffffffffffffffff1682602001518281518110611df657611df661460a565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603611e7b576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f7472616e736d6974746572206d757374206e6f7420626520656d7074790000006044820152606401610b0d565b60006004600084600001518481518110611e9757611e9761460a565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115611ee157611ee161492a565b14611f48576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265706561746564207369676e657220616464726573730000000000000000006044820152606401610b0d565b6040805180820190915260ff82168152600160208201528251805160049160009185908110611f7957611f7961460a565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000161761010083600281111561201a5761201a61492a565b02179055506000915061202a9050565b60046000846020015184815181106120445761204461460a565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff16600281111561208e5761208e61492a565b146120f5576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f7265706561746564207472616e736d69747465722061646472657373000000006044820152606401610b0d565b6040805180820190915260ff8216815260208101600281525060046000846020015184815181106121285761212861460a565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000016176101008360028111156121c9576121c961492a565b0217905550508251805160059250839081106121e7576121e761460a565b602090810291909101810151825460018101845560009384529282902090920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90931692909217909155820151805160069190839081106122635761226361460a565b60209081029190910181015182546001810184556000938452919092200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909216919091179055806122cd81614639565b915050611d21565b506040810151600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff909216919091179055600180547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff8116780100000000000000000000000000000000000000000000000063ffffffff438116820292909217808555920481169291829160149161238d918491740100000000000000000000000000000000000000009004166149d6565b92506101000a81548163ffffffff021916908363ffffffff1602179055506123ec4630600160149054906101000a900463ffffffff1663ffffffff16856000015186602001518760400151886060015189608001518a60a00151613236565b600281905582518051600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010060ff9093169290920291909117905560015460208501516040808701516060880151608089015160a08a015193517f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e05986124a3988b9891977401000000000000000000000000000000000000000090920463ffffffff169690959194919391926149f3565b60405180910390a15050505050505050505050565b604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116838501526c0100000000000000000000000080830482166060850152700100000000000000000000000000000000830468ffffffffffffffffff166080850152790100000000000000000000000000000000000000000000000000830464ffffffffff1660a0808601919091527e0100000000000000000000000000000000000000000000000000000000000090930461ffff1660c08501526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08601527c01000000000000000000000000000000000000000000000000000000009004909116610100840152600b5484517ffeaf968c00000000000000000000000000000000000000000000000000000000815294516000958694859490930473ffffffffffffffffffffffffffffffffffffffff169263feaf968c926004808401938290030181865afa158015612646573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061266a9190614aa3565b50935050925050804261267d9190614994565b836020015163ffffffff1610801561269f57506000836020015163ffffffff16115b156126cd57505060e001517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16919050565b6000821361270a576040517f43d4cf6600000000000000000000000000000000000000000000000000000000815260048101839052602401610b0d565b5092915050565b612719612725565b612722816132e1565b50565b60005473ffffffffffffffffffffffffffffffffffffffff1633146127a6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610b0d565b565b6127a6612725565b600b546bffffffffffffffffffffffff166000036127ca57565b60006127d4610de2565b80519091506000819003612814576040517f30274b3a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600b546000906128339083906bffffffffffffffffffffffff16614af3565b905060005b828110156128fe5781600a60008684815181106128575761285761460a565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282829054906101000a90046bffffffffffffffffffffffff166128bf9190614b1e565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550806128f790614639565b9050612838565b506129098282614b43565b600b80546000906129299084906bffffffffffffffffffffffff166145e5565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505050565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081018290526101208101829052610140810191909152604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c0100000000000000000000000081048316606083015268ffffffffffffffffff700100000000000000000000000000000000820416608083015264ffffffffff79010000000000000000000000000000000000000000000000000082041660a083015261ffff7e01000000000000000000000000000000000000000000000000000000000000909104811660c083018190526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08501527c0100000000000000000000000000000000000000000000000000000000900490931661010080840191909152850151919291161115612b17576040517fdada758700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600854600090700100000000000000000000000000000000900468ffffffffffffffffff1690506000612b548560e001513a8488608001516130b0565b9050806bffffffffffffffffffffffff1685606001516bffffffffffffffffffffffff161015612bb0576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083610100015163ffffffff1642612bc99190614b6b565b905060003087604001518860a001518960c001516001612be99190614b7e565b8a5180516020918201206101008d015160e08e0151604051612c9d98979695948c918c9132910173ffffffffffffffffffffffffffffffffffffffff9a8b168152988a1660208a015267ffffffffffffffff97881660408a0152959096166060880152608087019390935261ffff9190911660a086015263ffffffff90811660c08601526bffffffffffffffffffffffff9190911660e0850152919091166101008301529091166101208201526101400190565b6040516020818303038152906040528051906020012090506040518061016001604052808281526020013073ffffffffffffffffffffffffffffffffffffffff168152602001846bffffffffffffffffffffffff168152602001886040015173ffffffffffffffffffffffffffffffffffffffff1681526020018860a0015167ffffffffffffffff1681526020018860e0015163ffffffff168152602001886080015168ffffffffffffffffff1681526020018568ffffffffffffffffff168152602001866040015163ffffffff1664ffffffffff168152602001866060015163ffffffff1664ffffffffff1681526020018363ffffffff16815250955085604051602001612dac9190613fdc565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152918152815160209283012060009384526007909252909120555092949350505050565b6000612e0782602061497d565b612e1285602061497d565b612e1e88610144614b6b565b612e289190614b6b565b612e329190614b6b565b612e3d906000614b6b565b9050368114612ea8576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f63616c6c64617461206c656e677468206d69736d6174636800000000000000006044820152606401610b0d565b50505050505050565b600080808080612ec386880188614c7a565b84519499509297509095509350915060ff16801580612ee3575084518114155b80612eef575083518114155b80612efb575082518114155b80612f07575081518114155b15612f6e576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f4669656c6473206d75737420626520657175616c206c656e67746800000000006044820152606401610b0d565b60005b818110156130a1576000613006888381518110612f9057612f9061460a565b6020026020010151888481518110612faa57612faa61460a565b6020026020010151888581518110612fc457612fc461460a565b6020026020010151888681518110612fde57612fde61460a565b6020026020010151888781518110612ff857612ff861460a565b6020026020010151886133d6565b9050600081600681111561301c5761301c61492a565b1480613039575060018160068111156130375761303761492a565b145b15613090578782815181106130505761305061460a565b60209081029190910181015160405133815290917fc708e0440951fd63499c0f7a73819b469ee5dd3ecc356c0ab4eb7f18389009d9910160405180910390a25b5061309a81614639565b9050612f71565b50505050505050505050505050565b600854600090790100000000000000000000000000000000000000000000000000900464ffffffffff1684101561310b57600854790100000000000000000000000000000000000000000000000000900464ffffffffff1693505b600854600090612710906131259063ffffffff168761497d565b61312f9190614d4c565b6131399086614b6b565b60085490915060009087906131729063ffffffff6c010000000000000000000000008204811691680100000000000000009004166149d6565b61317c91906149d6565b63ffffffff16905060006131c66000368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506136ea92505050565b905060006131e7826131d8858761497d565b6131e29190614b6b565b61382c565b9050600061320368ffffffffffffffffff808916908a16614b1e565b905061320f8183614b1e565b9a9950505050505050505050565b6000613227610de2565b511115610d8e57610d8e6127b0565b6000808a8a8a8a8a8a8a8a8a60405160200161325a99989796959493929190614d60565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179150509998505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603613360576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610b0d565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600080848060200190518101906133ed9190614e2c565b905060003a8261012001518361010001516134089190614ef4565b64ffffffffff16613419919061497d565b905060008460ff166134616000368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506136ea92505050565b61346b9190614d4c565b9050600061347c6131e28385614b6b565b905060006134893a61382c565b90506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663330605298e8e868b60e0015168ffffffffffffffffff16896134e89190614b1e565b338d6040518763ffffffff1660e01b815260040161350b96959493929190614f12565b60408051808303816000875af1158015613529573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061354d9190614f8e565b909250905060008260068111156135665761356661492a565b1480613583575060018260068111156135815761358161492a565b145b156136d95760008e8152600760205260408120556135a18185614b1e565b336000908152600a6020526040812080547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff93841617905560e0890151600b805468ffffffffffffffffff9092169390929161360d91859116614b1e565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508d7f90815c2e624694e8010bffad2bcefaf96af282ef1bc2ebc0042d1b89a585e0468487848b60c0015168ffffffffffffffffff168c60e0015168ffffffffffffffffff16878b61368c9190614b1e565b6136969190614b1e565b6136a09190614b1e565b604080516bffffffffffffffffffffffff9586168152602081019490945291841683830152909216606082015290519081900360800190a25b509c9b505050505050505050505050565b6000466136f681613860565b1561377257606c73ffffffffffffffffffffffffffffffffffffffff1663c6f7de0e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015613747573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061376b9190614fc1565b9392505050565b61377b81613883565b156138235773420000000000000000000000000000000000000f73ffffffffffffffffffffffffffffffffffffffff166349948e0e8460405180608001604052806048815260200161500a604891396040516020016137db929190614fda565b6040516020818303038152906040526040518263ffffffff1660e01b81526004016138069190613ca1565b602060405180830381865afa158015613747573d6000803e3d6000fd5b50600092915050565b600061385a6138396124b8565b61384b84670de0b6b3a764000061497d565b6138559190614d4c565b6138ca565b92915050565b600061a4b1821480613874575062066eed82145b8061385a57505062066eee1490565b6000600a82148061389557506101a482145b806138a2575062aa37dc82145b806138ae575061210582145b806138bb575062014a3382145b8061385a57505062014a341490565b60006bffffffffffffffffffffffff821115613968576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f36206269747300000000000000000000000000000000000000000000000000006064820152608401610b0d565b5090565b604051806103e00160405280601f906020820280368337509192915050565b60008083601f84011261399d57600080fd5b50813567ffffffffffffffff8111156139b557600080fd5b6020830191508360208285010111156139cd57600080fd5b9250929050565b600080602083850312156139e757600080fd5b823567ffffffffffffffff8111156139fe57600080fd5b613a0a8582860161398b565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610120810167ffffffffffffffff81118282101715613a6957613a69613a16565b60405290565b604051610160810167ffffffffffffffff81118282101715613a6957613a69613a16565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613ada57613ada613a16565b604052919050565b63ffffffff8116811461272257600080fd5b803561117081613ae2565b68ffffffffffffffffff8116811461272257600080fd5b803561117081613aff565b64ffffffffff8116811461272257600080fd5b803561117081613b21565b803561ffff8116811461117057600080fd5b80357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116811461117057600080fd5b60006101208284031215613b9057600080fd5b613b98613a45565b613ba183613af4565b8152613baf60208401613af4565b6020820152613bc060408401613af4565b6040820152613bd160608401613af4565b6060820152613be260808401613b16565b6080820152613bf360a08401613b34565b60a0820152613c0460c08401613b3f565b60c0820152613c1560e08401613b51565b60e0820152610100613c28818501613af4565b908201529392505050565b60005b83811015613c4e578181015183820152602001613c36565b50506000910152565b60008151808452613c6f816020860160208601613c33565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061376b6020830184613c57565b600082601f830112613cc557600080fd5b813567ffffffffffffffff811115613cdf57613cdf613a16565b613d1060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601613a93565b818152846020838601011115613d2557600080fd5b816020850160208301376000918101602001919091529392505050565b600060208284031215613d5457600080fd5b813567ffffffffffffffff811115613d6b57600080fd5b613d7784828501613cb4565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461272257600080fd5b803561117081613d7f565b6bffffffffffffffffffffffff8116811461272257600080fd5b803561117081613dac565b60008060408385031215613de457600080fd5b8235613def81613d7f565b91506020830135613dff81613dac565b809150509250929050565b600081518084526020808501945080840160005b83811015613e5057815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101613e1e565b509495945050505050565b60208152600061376b6020830184613e0a565b600060208284031215613e8057600080fd5b5035919050565b600060208284031215613e9957600080fd5b813567ffffffffffffffff811115613eb057600080fd5b8201610160818503121561376b57600080fd5b805182526020810151613eee602084018273ffffffffffffffffffffffffffffffffffffffff169052565b506040810151613f0e60408401826bffffffffffffffffffffffff169052565b506060810151613f36606084018273ffffffffffffffffffffffffffffffffffffffff169052565b506080810151613f52608084018267ffffffffffffffff169052565b5060a0810151613f6a60a084018263ffffffff169052565b5060c0810151613f8760c084018268ffffffffffffffffff169052565b5060e0810151613fa460e084018268ffffffffffffffffff169052565b506101008181015164ffffffffff9081169184019190915261012080830151909116908301526101409081015163ffffffff16910152565b610160810161385a8284613ec3565b60008083601f840112613ffd57600080fd5b50813567ffffffffffffffff81111561401557600080fd5b6020830191508360208260051b85010111156139cd57600080fd5b60008060008060008060008060e0898b03121561404c57600080fd5b606089018a81111561405d57600080fd5b8998503567ffffffffffffffff8082111561407757600080fd5b6140838c838d0161398b565b909950975060808b013591508082111561409c57600080fd5b6140a88c838d01613feb565b909750955060a08b01359150808211156140c157600080fd5b506140ce8b828c01613feb565b999c989b50969995989497949560c00135949350505050565b815163ffffffff90811682526020808401518216908301526040808401518216908301526060808401519182169083015261012082019050608083015161413b608084018268ffffffffffffffffff169052565b5060a083015161415460a084018264ffffffffff169052565b5060c083015161416a60c084018261ffff169052565b5060e083015161419a60e08401827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169052565b506101008381015163ffffffff8116848301525b505092915050565b67ffffffffffffffff8116811461272257600080fd5b8035611170816141b6565b6000806000806000608086880312156141ef57600080fd5b85356141fa816141b6565b9450602086013567ffffffffffffffff81111561421657600080fd5b6142228882890161398b565b909550935050604086013561423681613ae2565b949793965091946060013592915050565b600067ffffffffffffffff82111561426157614261613a16565b5060051b60200190565b600082601f83011261427c57600080fd5b8135602061429161428c83614247565b613a93565b82815260059290921b840181019181810190868411156142b057600080fd5b8286015b848110156142d45780356142c781613d7f565b83529183019183016142b4565b509695505050505050565b803560ff8116811461117057600080fd5b60008060008060008060c0878903121561430957600080fd5b863567ffffffffffffffff8082111561432157600080fd5b61432d8a838b0161426b565b9750602089013591508082111561434357600080fd5b61434f8a838b0161426b565b965061435d60408a016142df565b9550606089013591508082111561437357600080fd5b61437f8a838b01613cb4565b945061438d60808a016141cc565b935060a08901359150808211156143a357600080fd5b506143b089828a01613cb4565b9150509295509295509295565b6000602082840312156143cf57600080fd5b813561376b81613d7f565b600181811c908216806143ee57607f821691505b602082108103614427577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156105ec57600081815260208120601f850160051c810160208610156144545750805b601f850160051c820191505b81811015610a8857828155600101614460565b67ffffffffffffffff83111561448b5761448b613a16565b61449f8361449983546143da565b8361442d565b6000601f8411600181146144f157600085156144bb5750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355614587565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b828110156145405786850135825560209485019460019092019101614520565b508682101561457b577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b805161117081613aff565b6000602082840312156145ab57600080fd5b815161376b81613aff565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6bffffffffffffffffffffffff82811682821603908082111561270a5761270a6145b6565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361466a5761466a6145b6565b5060010190565b6000610160823603121561468457600080fd5b61468c613a6f565b823567ffffffffffffffff8111156146a357600080fd5b6146af36828601613cb4565b825250602083013560208201526146c860408401613da1565b60408201526146d960608401613dc6565b60608201526146ea60808401613b16565b60808201526146fb60a084016141cc565b60a082015261470c60c084016141cc565b60c082015261471d60e08401613af4565b60e0820152610100614730818501613b3f565b908201526101206147428482016141cc565b90820152610140614754848201613da1565b9082015292915050565b60006020828403121561477057600080fd5b813561376b816141b6565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126147b057600080fd5b83018035915067ffffffffffffffff8211156147cb57600080fd5b6020019150368190038213156139cd57600080fd5b6000602082840312156147f257600080fd5b61376b82613b3f565b60006020828403121561480d57600080fd5b813561376b81613ae2565b73ffffffffffffffffffffffffffffffffffffffff8a8116825267ffffffffffffffff8a166020830152881660408201526102406060820181905281018690526000610260878982850137600083890182015261ffff8716608084015260a0830186905263ffffffff851660c0840152601f88017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016830101905061320f60e0830184613ec3565b60ff818116838216019081111561385a5761385a6145b6565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600060ff83168061491b5761491b6148d9565b8060ff84160491505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8183823760009101908152919050565b828152606082602083013760800192915050565b808202811582820484141761385a5761385a6145b6565b8181038181111561385a5761385a6145b6565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b63ffffffff81811683821601908082111561270a5761270a6145b6565b600061012063ffffffff808d1684528b6020850152808b16604085015250806060840152614a238184018a613e0a565b90508281036080840152614a378189613e0a565b905060ff871660a084015282810360c0840152614a548187613c57565b905067ffffffffffffffff851660e0840152828103610100840152614a798185613c57565b9c9b505050505050505050505050565b805169ffffffffffffffffffff8116811461117057600080fd5b600080600080600060a08688031215614abb57600080fd5b614ac486614a89565b9450602086015193506040860151925060608601519150614ae760808701614a89565b90509295509295909350565b60006bffffffffffffffffffffffff80841680614b1257614b126148d9565b92169190910492915050565b6bffffffffffffffffffffffff81811683821601908082111561270a5761270a6145b6565b6bffffffffffffffffffffffff8181168382160280821691908281146141ae576141ae6145b6565b8082018082111561385a5761385a6145b6565b67ffffffffffffffff81811683821601908082111561270a5761270a6145b6565b600082601f830112614bb057600080fd5b81356020614bc061428c83614247565b82815260059290921b84018101918181019086841115614bdf57600080fd5b8286015b848110156142d45780358352918301918301614be3565b600082601f830112614c0b57600080fd5b81356020614c1b61428c83614247565b82815260059290921b84018101918181019086841115614c3a57600080fd5b8286015b848110156142d457803567ffffffffffffffff811115614c5e5760008081fd5b614c6c8986838b0101613cb4565b845250918301918301614c3e565b600080600080600060a08688031215614c9257600080fd5b853567ffffffffffffffff80821115614caa57600080fd5b614cb689838a01614b9f565b96506020880135915080821115614ccc57600080fd5b614cd889838a01614bfa565b95506040880135915080821115614cee57600080fd5b614cfa89838a01614bfa565b94506060880135915080821115614d1057600080fd5b614d1c89838a01614bfa565b93506080880135915080821115614d3257600080fd5b50614d3f88828901614bfa565b9150509295509295909350565b600082614d5b57614d5b6148d9565b500490565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b166040850152816060850152614da78285018b613e0a565b91508382036080850152614dbb828a613e0a565b915060ff881660a085015283820360c0850152614dd88288613c57565b90861660e08501528381036101008501529050614a798185613c57565b805161117081613d7f565b805161117081613dac565b8051611170816141b6565b805161117081613ae2565b805161117081613b21565b60006101608284031215614e3f57600080fd5b614e47613a6f565b82518152614e5760208401614df5565b6020820152614e6860408401614e00565b6040820152614e7960608401614df5565b6060820152614e8a60808401614e0b565b6080820152614e9b60a08401614e16565b60a0820152614eac60c0840161458e565b60c0820152614ebd60e0840161458e565b60e0820152610100614ed0818501614e21565b90820152610120614ee2848201614e21565b90820152610140613c28848201614e16565b64ffffffffff81811683821601908082111561270a5761270a6145b6565b6000610200808352614f268184018a613c57565b90508281036020840152614f3a8189613c57565b6bffffffffffffffffffffffff88811660408601528716606085015273ffffffffffffffffffffffffffffffffffffffff861660808501529150614f83905060a0830184613ec3565b979650505050505050565b60008060408385031215614fa157600080fd5b825160078110614fb057600080fd5b6020840151909250613dff81613dac565b600060208284031215614fd357600080fd5b5051919050565b60008351614fec818460208801613c33565b835190830190615000818360208801613c33565b0194935050505056fe307866666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666a164736f6c6343000813000a", } var FunctionsCoordinatorABI = FunctionsCoordinatorMetaData.ABI @@ -92,7 +92,7 @@ func DeployFunctionsCoordinator(auth *bind.TransactOpts, backend bind.ContractBa if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &FunctionsCoordinator{FunctionsCoordinatorCaller: FunctionsCoordinatorCaller{contract: contract}, FunctionsCoordinatorTransactor: FunctionsCoordinatorTransactor{contract: contract}, FunctionsCoordinatorFilterer: FunctionsCoordinatorFilterer{contract: contract}}, nil + return address, tx, &FunctionsCoordinator{address: address, abi: *parsed, FunctionsCoordinatorCaller: FunctionsCoordinatorCaller{contract: contract}, FunctionsCoordinatorTransactor: FunctionsCoordinatorTransactor{contract: contract}, FunctionsCoordinatorFilterer: FunctionsCoordinatorFilterer{contract: contract}}, nil } type FunctionsCoordinator struct { @@ -1528,6 +1528,137 @@ func (_FunctionsCoordinator *FunctionsCoordinatorFilterer) ParseOwnershipTransfe return event, nil } +type FunctionsCoordinatorRequestBilledIterator struct { + Event *FunctionsCoordinatorRequestBilled + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *FunctionsCoordinatorRequestBilledIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(FunctionsCoordinatorRequestBilled) + 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(FunctionsCoordinatorRequestBilled) + 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 *FunctionsCoordinatorRequestBilledIterator) Error() error { + return it.fail +} + +func (it *FunctionsCoordinatorRequestBilledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type FunctionsCoordinatorRequestBilled struct { + RequestId [32]byte + JuelsPerGas *big.Int + L1FeeShareWei *big.Int + CallbackCostJuels *big.Int + TotalCostJuels *big.Int + Raw types.Log +} + +func (_FunctionsCoordinator *FunctionsCoordinatorFilterer) FilterRequestBilled(opts *bind.FilterOpts, requestId [][32]byte) (*FunctionsCoordinatorRequestBilledIterator, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + + logs, sub, err := _FunctionsCoordinator.contract.FilterLogs(opts, "RequestBilled", requestIdRule) + if err != nil { + return nil, err + } + return &FunctionsCoordinatorRequestBilledIterator{contract: _FunctionsCoordinator.contract, event: "RequestBilled", logs: logs, sub: sub}, nil +} + +func (_FunctionsCoordinator *FunctionsCoordinatorFilterer) WatchRequestBilled(opts *bind.WatchOpts, sink chan<- *FunctionsCoordinatorRequestBilled, requestId [][32]byte) (event.Subscription, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + + logs, sub, err := _FunctionsCoordinator.contract.WatchLogs(opts, "RequestBilled", requestIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(FunctionsCoordinatorRequestBilled) + if err := _FunctionsCoordinator.contract.UnpackLog(event, "RequestBilled", 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 (_FunctionsCoordinator *FunctionsCoordinatorFilterer) ParseRequestBilled(log types.Log) (*FunctionsCoordinatorRequestBilled, error) { + event := new(FunctionsCoordinatorRequestBilled) + if err := _FunctionsCoordinator.contract.UnpackLog(event, "RequestBilled", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + type FunctionsCoordinatorTransmittedIterator struct { Event *FunctionsCoordinatorTransmitted @@ -1673,6 +1804,8 @@ func (_FunctionsCoordinator *FunctionsCoordinator) ParseLog(log types.Log) (gene return _FunctionsCoordinator.ParseOwnershipTransferRequested(log) case _FunctionsCoordinator.abi.Events["OwnershipTransferred"].ID: return _FunctionsCoordinator.ParseOwnershipTransferred(log) + case _FunctionsCoordinator.abi.Events["RequestBilled"].ID: + return _FunctionsCoordinator.ParseRequestBilled(log) case _FunctionsCoordinator.abi.Events["Transmitted"].ID: return _FunctionsCoordinator.ParseTransmitted(log) @@ -1709,6 +1842,10 @@ func (FunctionsCoordinatorOwnershipTransferred) Topic() common.Hash { return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") } +func (FunctionsCoordinatorRequestBilled) Topic() common.Hash { + return common.HexToHash("0x90815c2e624694e8010bffad2bcefaf96af282ef1bc2ebc0042d1b89a585e046") +} + func (FunctionsCoordinatorTransmitted) Topic() common.Hash { return common.HexToHash("0xb04e63db38c49950639fa09d29872f21f5d49d614f3a969d8adf3d4b52e41a62") } @@ -1810,6 +1947,12 @@ type FunctionsCoordinatorInterface interface { ParseOwnershipTransferred(log types.Log) (*FunctionsCoordinatorOwnershipTransferred, error) + FilterRequestBilled(opts *bind.FilterOpts, requestId [][32]byte) (*FunctionsCoordinatorRequestBilledIterator, error) + + WatchRequestBilled(opts *bind.WatchOpts, sink chan<- *FunctionsCoordinatorRequestBilled, requestId [][32]byte) (event.Subscription, error) + + ParseRequestBilled(log types.Log) (*FunctionsCoordinatorRequestBilled, error) + FilterTransmitted(opts *bind.FilterOpts) (*FunctionsCoordinatorTransmittedIterator, error) WatchTransmitted(opts *bind.WatchOpts, sink chan<- *FunctionsCoordinatorTransmitted) (event.Subscription, error) diff --git a/core/gethwrappers/functions/generated/functions_load_test_client/functions_load_test_client.go b/core/gethwrappers/functions/generated/functions_load_test_client/functions_load_test_client.go index 37a895fe8cd..a03d6a5b144 100644 --- a/core/gethwrappers/functions/generated/functions_load_test_client/functions_load_test_client.go +++ b/core/gethwrappers/functions/generated/functions_load_test_client/functions_load_test_client.go @@ -52,7 +52,7 @@ func DeployFunctionsLoadTestClient(auth *bind.TransactOpts, backend bind.Contrac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &FunctionsLoadTestClient{FunctionsLoadTestClientCaller: FunctionsLoadTestClientCaller{contract: contract}, FunctionsLoadTestClientTransactor: FunctionsLoadTestClientTransactor{contract: contract}, FunctionsLoadTestClientFilterer: FunctionsLoadTestClientFilterer{contract: contract}}, nil + return address, tx, &FunctionsLoadTestClient{address: address, abi: *parsed, FunctionsLoadTestClientCaller: FunctionsLoadTestClientCaller{contract: contract}, FunctionsLoadTestClientTransactor: FunctionsLoadTestClientTransactor{contract: contract}, FunctionsLoadTestClientFilterer: FunctionsLoadTestClientFilterer{contract: contract}}, nil } type FunctionsLoadTestClient struct { diff --git a/core/gethwrappers/functions/generated/functions_router/functions_router.go b/core/gethwrappers/functions/generated/functions_router/functions_router.go index 2c482048c49..592f95b568f 100644 --- a/core/gethwrappers/functions/generated/functions_router/functions_router.go +++ b/core/gethwrappers/functions/generated/functions_router/functions_router.go @@ -91,7 +91,7 @@ func DeployFunctionsRouter(auth *bind.TransactOpts, backend bind.ContractBackend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &FunctionsRouter{FunctionsRouterCaller: FunctionsRouterCaller{contract: contract}, FunctionsRouterTransactor: FunctionsRouterTransactor{contract: contract}, FunctionsRouterFilterer: FunctionsRouterFilterer{contract: contract}}, nil + return address, tx, &FunctionsRouter{address: address, abi: *parsed, FunctionsRouterCaller: FunctionsRouterCaller{contract: contract}, FunctionsRouterTransactor: FunctionsRouterTransactor{contract: contract}, FunctionsRouterFilterer: FunctionsRouterFilterer{contract: contract}}, nil } type FunctionsRouter struct { diff --git a/core/gethwrappers/functions/generated/functions_v1_events_mock/functions_v1_events_mock.go b/core/gethwrappers/functions/generated/functions_v1_events_mock/functions_v1_events_mock.go index ae1fe20401c..422d2e8b2ae 100644 --- a/core/gethwrappers/functions/generated/functions_v1_events_mock/functions_v1_events_mock.go +++ b/core/gethwrappers/functions/generated/functions_v1_events_mock/functions_v1_events_mock.go @@ -60,7 +60,7 @@ func DeployFunctionsV1EventsMock(auth *bind.TransactOpts, backend bind.ContractB if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &FunctionsV1EventsMock{FunctionsV1EventsMockCaller: FunctionsV1EventsMockCaller{contract: contract}, FunctionsV1EventsMockTransactor: FunctionsV1EventsMockTransactor{contract: contract}, FunctionsV1EventsMockFilterer: FunctionsV1EventsMockFilterer{contract: contract}}, nil + return address, tx, &FunctionsV1EventsMock{address: address, abi: *parsed, FunctionsV1EventsMockCaller: FunctionsV1EventsMockCaller{contract: contract}, FunctionsV1EventsMockTransactor: FunctionsV1EventsMockTransactor{contract: contract}, FunctionsV1EventsMockFilterer: FunctionsV1EventsMockFilterer{contract: contract}}, nil } type FunctionsV1EventsMock struct { diff --git a/core/gethwrappers/functions/generated/ocr2dr/ocr2dr.go b/core/gethwrappers/functions/generated/ocr2dr/ocr2dr.go index 8cc65942168..c2b3c3a5e91 100644 --- a/core/gethwrappers/functions/generated/ocr2dr/ocr2dr.go +++ b/core/gethwrappers/functions/generated/ocr2dr/ocr2dr.go @@ -50,7 +50,7 @@ func DeployOCR2DR(auth *bind.TransactOpts, backend bind.ContractBackend) (common if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &OCR2DR{OCR2DRCaller: OCR2DRCaller{contract: contract}, OCR2DRTransactor: OCR2DRTransactor{contract: contract}, OCR2DRFilterer: OCR2DRFilterer{contract: contract}}, nil + return address, tx, &OCR2DR{address: address, abi: *parsed, OCR2DRCaller: OCR2DRCaller{contract: contract}, OCR2DRTransactor: OCR2DRTransactor{contract: contract}, OCR2DRFilterer: OCR2DRFilterer{contract: contract}}, nil } type OCR2DR struct { diff --git a/core/gethwrappers/functions/generated/ocr2dr_client_example/ocr2dr_client_example.go b/core/gethwrappers/functions/generated/ocr2dr_client_example/ocr2dr_client_example.go index a4b94f7e927..c37ba64b8d9 100644 --- a/core/gethwrappers/functions/generated/ocr2dr_client_example/ocr2dr_client_example.go +++ b/core/gethwrappers/functions/generated/ocr2dr_client_example/ocr2dr_client_example.go @@ -61,7 +61,7 @@ func DeployOCR2DRClientExample(auth *bind.TransactOpts, backend bind.ContractBac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &OCR2DRClientExample{OCR2DRClientExampleCaller: OCR2DRClientExampleCaller{contract: contract}, OCR2DRClientExampleTransactor: OCR2DRClientExampleTransactor{contract: contract}, OCR2DRClientExampleFilterer: OCR2DRClientExampleFilterer{contract: contract}}, nil + return address, tx, &OCR2DRClientExample{address: address, abi: *parsed, OCR2DRClientExampleCaller: OCR2DRClientExampleCaller{contract: contract}, OCR2DRClientExampleTransactor: OCR2DRClientExampleTransactor{contract: contract}, OCR2DRClientExampleFilterer: OCR2DRClientExampleFilterer{contract: contract}}, nil } type OCR2DRClientExample struct { diff --git a/core/gethwrappers/functions/generated/ocr2dr_oracle/ocr2dr_oracle.go b/core/gethwrappers/functions/generated/ocr2dr_oracle/ocr2dr_oracle.go index 509a3c83883..a2f81cfb30c 100644 --- a/core/gethwrappers/functions/generated/ocr2dr_oracle/ocr2dr_oracle.go +++ b/core/gethwrappers/functions/generated/ocr2dr_oracle/ocr2dr_oracle.go @@ -59,7 +59,7 @@ func DeployOCR2DROracle(auth *bind.TransactOpts, backend bind.ContractBackend) ( if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &OCR2DROracle{OCR2DROracleCaller: OCR2DROracleCaller{contract: contract}, OCR2DROracleTransactor: OCR2DROracleTransactor{contract: contract}, OCR2DROracleFilterer: OCR2DROracleFilterer{contract: contract}}, nil + return address, tx, &OCR2DROracle{address: address, abi: *parsed, OCR2DROracleCaller: OCR2DROracleCaller{contract: contract}, OCR2DROracleTransactor: OCR2DROracleTransactor{contract: contract}, OCR2DROracleFilterer: OCR2DROracleFilterer{contract: contract}}, nil } type OCR2DROracle struct { diff --git a/core/gethwrappers/functions/generated/ocr2dr_registry/ocr2dr_registry.go b/core/gethwrappers/functions/generated/ocr2dr_registry/ocr2dr_registry.go index eb825b5eaa6..f81a4316203 100644 --- a/core/gethwrappers/functions/generated/ocr2dr_registry/ocr2dr_registry.go +++ b/core/gethwrappers/functions/generated/ocr2dr_registry/ocr2dr_registry.go @@ -71,7 +71,7 @@ func DeployOCR2DRRegistry(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &OCR2DRRegistry{OCR2DRRegistryCaller: OCR2DRRegistryCaller{contract: contract}, OCR2DRRegistryTransactor: OCR2DRRegistryTransactor{contract: contract}, OCR2DRRegistryFilterer: OCR2DRRegistryFilterer{contract: contract}}, nil + return address, tx, &OCR2DRRegistry{address: address, abi: *parsed, OCR2DRRegistryCaller: OCR2DRRegistryCaller{contract: contract}, OCR2DRRegistryTransactor: OCR2DRRegistryTransactor{contract: contract}, OCR2DRRegistryFilterer: OCR2DRRegistryFilterer{contract: contract}}, nil } type OCR2DRRegistry struct { diff --git a/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt index cff49cd07c2..ac1fc4e83d6 100644 --- a/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -4,7 +4,7 @@ functions_allow_list: ../../../contracts/solc/v0.8.19/functions/v1_X/TermsOfServ functions_billing_registry_events_mock: ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsBillingRegistryEventsMock.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsBillingRegistryEventsMock.bin 50deeb883bd9c3729702be335c0388f9d8553bab4be5e26ecacac496a89e2b77 functions_client: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClient.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClient.bin 2368f537a04489c720a46733f8596c4fc88a31062ecfa966d05f25dd98608aca functions_client_example: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClientExample.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClientExample.bin abf32e69f268f40e8530eb8d8e96bf310b798a4c0049a58022d9d2fb527b601b -functions_coordinator: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.bin 9e11effc1922d258d3fc38564b87f4466c56162f33d553ec6d66edcfa55923af +functions_coordinator: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.bin 97aa7c56d78c703056990eff102279af86b97b11b5855b059e8dd658dc15da8a functions_load_test_client: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsLoadTestClient.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsLoadTestClient.bin c8dbbd5ebb34435800d6674700068837c3a252db60046a14b0e61e829db517de functions_oracle_events_mock: ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsOracleEventsMock.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsOracleEventsMock.bin 3ca70f966f8fe751987f0ccb50bebb6aa5be77e4a9f835d1ae99e0e9bfb7d52c functions_router: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRouter.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRouter.bin 9dedd3a36043605fd9bedf821e7ec5b4281a5c7ae2e4a1955f37aff8ba13519f diff --git a/core/gethwrappers/generated/authorized_forwarder/authorized_forwarder.go b/core/gethwrappers/generated/authorized_forwarder/authorized_forwarder.go index 6417a9c6782..03643c9154b 100644 --- a/core/gethwrappers/generated/authorized_forwarder/authorized_forwarder.go +++ b/core/gethwrappers/generated/authorized_forwarder/authorized_forwarder.go @@ -52,7 +52,7 @@ func DeployAuthorizedForwarder(auth *bind.TransactOpts, backend bind.ContractBac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &AuthorizedForwarder{AuthorizedForwarderCaller: AuthorizedForwarderCaller{contract: contract}, AuthorizedForwarderTransactor: AuthorizedForwarderTransactor{contract: contract}, AuthorizedForwarderFilterer: AuthorizedForwarderFilterer{contract: contract}}, nil + return address, tx, &AuthorizedForwarder{address: address, abi: *parsed, AuthorizedForwarderCaller: AuthorizedForwarderCaller{contract: contract}, AuthorizedForwarderTransactor: AuthorizedForwarderTransactor{contract: contract}, AuthorizedForwarderFilterer: AuthorizedForwarderFilterer{contract: contract}}, nil } type AuthorizedForwarder struct { diff --git a/core/gethwrappers/generated/automation_consumer_benchmark/automation_consumer_benchmark.go b/core/gethwrappers/generated/automation_consumer_benchmark/automation_consumer_benchmark.go index f4d59e7e7de..3dafe68921f 100644 --- a/core/gethwrappers/generated/automation_consumer_benchmark/automation_consumer_benchmark.go +++ b/core/gethwrappers/generated/automation_consumer_benchmark/automation_consumer_benchmark.go @@ -52,7 +52,7 @@ func DeployAutomationConsumerBenchmark(auth *bind.TransactOpts, backend bind.Con if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &AutomationConsumerBenchmark{AutomationConsumerBenchmarkCaller: AutomationConsumerBenchmarkCaller{contract: contract}, AutomationConsumerBenchmarkTransactor: AutomationConsumerBenchmarkTransactor{contract: contract}, AutomationConsumerBenchmarkFilterer: AutomationConsumerBenchmarkFilterer{contract: contract}}, nil + return address, tx, &AutomationConsumerBenchmark{address: address, abi: *parsed, AutomationConsumerBenchmarkCaller: AutomationConsumerBenchmarkCaller{contract: contract}, AutomationConsumerBenchmarkTransactor: AutomationConsumerBenchmarkTransactor{contract: contract}, AutomationConsumerBenchmarkFilterer: AutomationConsumerBenchmarkFilterer{contract: contract}}, nil } type AutomationConsumerBenchmark struct { diff --git a/core/gethwrappers/generated/automation_forwarder_logic/automation_forwarder_logic.go b/core/gethwrappers/generated/automation_forwarder_logic/automation_forwarder_logic.go index b26f5e5692c..8b35a68c826 100644 --- a/core/gethwrappers/generated/automation_forwarder_logic/automation_forwarder_logic.go +++ b/core/gethwrappers/generated/automation_forwarder_logic/automation_forwarder_logic.go @@ -50,7 +50,7 @@ func DeployAutomationForwarderLogic(auth *bind.TransactOpts, backend bind.Contra if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &AutomationForwarderLogic{AutomationForwarderLogicCaller: AutomationForwarderLogicCaller{contract: contract}, AutomationForwarderLogicTransactor: AutomationForwarderLogicTransactor{contract: contract}, AutomationForwarderLogicFilterer: AutomationForwarderLogicFilterer{contract: contract}}, nil + return address, tx, &AutomationForwarderLogic{address: address, abi: *parsed, AutomationForwarderLogicCaller: AutomationForwarderLogicCaller{contract: contract}, AutomationForwarderLogicTransactor: AutomationForwarderLogicTransactor{contract: contract}, AutomationForwarderLogicFilterer: AutomationForwarderLogicFilterer{contract: contract}}, nil } type AutomationForwarderLogic struct { diff --git a/core/gethwrappers/generated/automation_registrar_wrapper2_1/automation_registrar_wrapper2_1.go b/core/gethwrappers/generated/automation_registrar_wrapper2_1/automation_registrar_wrapper2_1.go index d2b8054436a..311ce75c108 100644 --- a/core/gethwrappers/generated/automation_registrar_wrapper2_1/automation_registrar_wrapper2_1.go +++ b/core/gethwrappers/generated/automation_registrar_wrapper2_1/automation_registrar_wrapper2_1.go @@ -77,7 +77,7 @@ func DeployAutomationRegistrar(auth *bind.TransactOpts, backend bind.ContractBac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &AutomationRegistrar{AutomationRegistrarCaller: AutomationRegistrarCaller{contract: contract}, AutomationRegistrarTransactor: AutomationRegistrarTransactor{contract: contract}, AutomationRegistrarFilterer: AutomationRegistrarFilterer{contract: contract}}, nil + return address, tx, &AutomationRegistrar{address: address, abi: *parsed, AutomationRegistrarCaller: AutomationRegistrarCaller{contract: contract}, AutomationRegistrarTransactor: AutomationRegistrarTransactor{contract: contract}, AutomationRegistrarFilterer: AutomationRegistrarFilterer{contract: contract}}, nil } type AutomationRegistrar struct { diff --git a/core/gethwrappers/generated/automation_utils_2_1/automation_utils_2_1.go b/core/gethwrappers/generated/automation_utils_2_1/automation_utils_2_1.go index 1eaa4ea03fe..5d6dc1e41ca 100644 --- a/core/gethwrappers/generated/automation_utils_2_1/automation_utils_2_1.go +++ b/core/gethwrappers/generated/automation_utils_2_1/automation_utils_2_1.go @@ -110,7 +110,7 @@ func DeployAutomationUtils(auth *bind.TransactOpts, backend bind.ContractBackend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &AutomationUtils{AutomationUtilsCaller: AutomationUtilsCaller{contract: contract}, AutomationUtilsTransactor: AutomationUtilsTransactor{contract: contract}, AutomationUtilsFilterer: AutomationUtilsFilterer{contract: contract}}, nil + return address, tx, &AutomationUtils{address: address, abi: *parsed, AutomationUtilsCaller: AutomationUtilsCaller{contract: contract}, AutomationUtilsTransactor: AutomationUtilsTransactor{contract: contract}, AutomationUtilsFilterer: AutomationUtilsFilterer{contract: contract}}, nil } type AutomationUtils struct { diff --git a/core/gethwrappers/generated/batch_blockhash_store/batch_blockhash_store.go b/core/gethwrappers/generated/batch_blockhash_store/batch_blockhash_store.go index 426c5a2c79d..1a43287d748 100644 --- a/core/gethwrappers/generated/batch_blockhash_store/batch_blockhash_store.go +++ b/core/gethwrappers/generated/batch_blockhash_store/batch_blockhash_store.go @@ -50,7 +50,7 @@ func DeployBatchBlockhashStore(auth *bind.TransactOpts, backend bind.ContractBac 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 + return address, tx, &BatchBlockhashStore{address: address, abi: *parsed, BatchBlockhashStoreCaller: BatchBlockhashStoreCaller{contract: contract}, BatchBlockhashStoreTransactor: BatchBlockhashStoreTransactor{contract: contract}, BatchBlockhashStoreFilterer: BatchBlockhashStoreFilterer{contract: contract}}, nil } type BatchBlockhashStore struct { diff --git a/core/gethwrappers/generated/batch_vrf_coordinator_v2/batch_vrf_coordinator_v2.go b/core/gethwrappers/generated/batch_vrf_coordinator_v2/batch_vrf_coordinator_v2.go index 4cb6e76b964..3a1ec957b93 100644 --- a/core/gethwrappers/generated/batch_vrf_coordinator_v2/batch_vrf_coordinator_v2.go +++ b/core/gethwrappers/generated/batch_vrf_coordinator_v2/batch_vrf_coordinator_v2.go @@ -72,7 +72,7 @@ func DeployBatchVRFCoordinatorV2(auth *bind.TransactOpts, backend bind.ContractB if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &BatchVRFCoordinatorV2{BatchVRFCoordinatorV2Caller: BatchVRFCoordinatorV2Caller{contract: contract}, BatchVRFCoordinatorV2Transactor: BatchVRFCoordinatorV2Transactor{contract: contract}, BatchVRFCoordinatorV2Filterer: BatchVRFCoordinatorV2Filterer{contract: contract}}, nil + return address, tx, &BatchVRFCoordinatorV2{address: address, abi: *parsed, BatchVRFCoordinatorV2Caller: BatchVRFCoordinatorV2Caller{contract: contract}, BatchVRFCoordinatorV2Transactor: BatchVRFCoordinatorV2Transactor{contract: contract}, BatchVRFCoordinatorV2Filterer: BatchVRFCoordinatorV2Filterer{contract: contract}}, nil } type BatchVRFCoordinatorV2 struct { diff --git a/core/gethwrappers/generated/batch_vrf_coordinator_v2plus/batch_vrf_coordinator_v2plus.go b/core/gethwrappers/generated/batch_vrf_coordinator_v2plus/batch_vrf_coordinator_v2plus.go index 610f3a27006..58f134fe2a6 100644 --- a/core/gethwrappers/generated/batch_vrf_coordinator_v2plus/batch_vrf_coordinator_v2plus.go +++ b/core/gethwrappers/generated/batch_vrf_coordinator_v2plus/batch_vrf_coordinator_v2plus.go @@ -73,7 +73,7 @@ func DeployBatchVRFCoordinatorV2Plus(auth *bind.TransactOpts, backend bind.Contr if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &BatchVRFCoordinatorV2Plus{BatchVRFCoordinatorV2PlusCaller: BatchVRFCoordinatorV2PlusCaller{contract: contract}, BatchVRFCoordinatorV2PlusTransactor: BatchVRFCoordinatorV2PlusTransactor{contract: contract}, BatchVRFCoordinatorV2PlusFilterer: BatchVRFCoordinatorV2PlusFilterer{contract: contract}}, nil + return address, tx, &BatchVRFCoordinatorV2Plus{address: address, abi: *parsed, BatchVRFCoordinatorV2PlusCaller: BatchVRFCoordinatorV2PlusCaller{contract: contract}, BatchVRFCoordinatorV2PlusTransactor: BatchVRFCoordinatorV2PlusTransactor{contract: contract}, BatchVRFCoordinatorV2PlusFilterer: BatchVRFCoordinatorV2PlusFilterer{contract: contract}}, nil } type BatchVRFCoordinatorV2Plus struct { diff --git a/core/gethwrappers/generated/blockhash_store/blockhash_store.go b/core/gethwrappers/generated/blockhash_store/blockhash_store.go index 8711f13b2d6..e43f9f450eb 100644 --- a/core/gethwrappers/generated/blockhash_store/blockhash_store.go +++ b/core/gethwrappers/generated/blockhash_store/blockhash_store.go @@ -50,7 +50,7 @@ func DeployBlockhashStore(auth *bind.TransactOpts, backend bind.ContractBackend) if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &BlockhashStore{BlockhashStoreCaller: BlockhashStoreCaller{contract: contract}, BlockhashStoreTransactor: BlockhashStoreTransactor{contract: contract}, BlockhashStoreFilterer: BlockhashStoreFilterer{contract: contract}}, nil + return address, tx, &BlockhashStore{address: address, abi: *parsed, BlockhashStoreCaller: BlockhashStoreCaller{contract: contract}, BlockhashStoreTransactor: BlockhashStoreTransactor{contract: contract}, BlockhashStoreFilterer: BlockhashStoreFilterer{contract: contract}}, nil } type BlockhashStore struct { diff --git a/core/gethwrappers/generated/chain_specific_util_helper/chain_specific_util_helper.go b/core/gethwrappers/generated/chain_specific_util_helper/chain_specific_util_helper.go index aa4dc0f88d9..b32c3d18d55 100644 --- a/core/gethwrappers/generated/chain_specific_util_helper/chain_specific_util_helper.go +++ b/core/gethwrappers/generated/chain_specific_util_helper/chain_specific_util_helper.go @@ -50,7 +50,7 @@ func DeployChainSpecificUtilHelper(auth *bind.TransactOpts, backend bind.Contrac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &ChainSpecificUtilHelper{ChainSpecificUtilHelperCaller: ChainSpecificUtilHelperCaller{contract: contract}, ChainSpecificUtilHelperTransactor: ChainSpecificUtilHelperTransactor{contract: contract}, ChainSpecificUtilHelperFilterer: ChainSpecificUtilHelperFilterer{contract: contract}}, nil + return address, tx, &ChainSpecificUtilHelper{address: address, abi: *parsed, ChainSpecificUtilHelperCaller: ChainSpecificUtilHelperCaller{contract: contract}, ChainSpecificUtilHelperTransactor: ChainSpecificUtilHelperTransactor{contract: contract}, ChainSpecificUtilHelperFilterer: ChainSpecificUtilHelperFilterer{contract: contract}}, nil } type ChainSpecificUtilHelper struct { diff --git a/core/gethwrappers/generated/consumer_wrapper/consumer_wrapper.go b/core/gethwrappers/generated/consumer_wrapper/consumer_wrapper.go index 954f66a08ef..0c0386b085f 100644 --- a/core/gethwrappers/generated/consumer_wrapper/consumer_wrapper.go +++ b/core/gethwrappers/generated/consumer_wrapper/consumer_wrapper.go @@ -52,7 +52,7 @@ func DeployConsumer(auth *bind.TransactOpts, backend bind.ContractBackend, _link if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &Consumer{ConsumerCaller: ConsumerCaller{contract: contract}, ConsumerTransactor: ConsumerTransactor{contract: contract}, ConsumerFilterer: ConsumerFilterer{contract: contract}}, nil + return address, tx, &Consumer{address: address, abi: *parsed, ConsumerCaller: ConsumerCaller{contract: contract}, ConsumerTransactor: ConsumerTransactor{contract: contract}, ConsumerFilterer: ConsumerFilterer{contract: contract}}, nil } type Consumer struct { diff --git a/core/gethwrappers/generated/dummy_protocol_wrapper/dummy_protocol_wrapper.go b/core/gethwrappers/generated/dummy_protocol_wrapper/dummy_protocol_wrapper.go index e7a88fdd06e..e2838b3f6fb 100644 --- a/core/gethwrappers/generated/dummy_protocol_wrapper/dummy_protocol_wrapper.go +++ b/core/gethwrappers/generated/dummy_protocol_wrapper/dummy_protocol_wrapper.go @@ -52,7 +52,7 @@ func DeployDummyProtocol(auth *bind.TransactOpts, backend bind.ContractBackend) if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &DummyProtocol{DummyProtocolCaller: DummyProtocolCaller{contract: contract}, DummyProtocolTransactor: DummyProtocolTransactor{contract: contract}, DummyProtocolFilterer: DummyProtocolFilterer{contract: contract}}, nil + return address, tx, &DummyProtocol{address: address, abi: *parsed, DummyProtocolCaller: DummyProtocolCaller{contract: contract}, DummyProtocolTransactor: DummyProtocolTransactor{contract: contract}, DummyProtocolFilterer: DummyProtocolFilterer{contract: contract}}, nil } type DummyProtocol struct { diff --git a/core/gethwrappers/generated/flags_wrapper/flags_wrapper.go b/core/gethwrappers/generated/flags_wrapper/flags_wrapper.go index f8d3c21b54f..590f9e2af96 100644 --- a/core/gethwrappers/generated/flags_wrapper/flags_wrapper.go +++ b/core/gethwrappers/generated/flags_wrapper/flags_wrapper.go @@ -52,7 +52,7 @@ func DeployFlags(auth *bind.TransactOpts, backend bind.ContractBackend, racAddre if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &Flags{FlagsCaller: FlagsCaller{contract: contract}, FlagsTransactor: FlagsTransactor{contract: contract}, FlagsFilterer: FlagsFilterer{contract: contract}}, nil + return address, tx, &Flags{address: address, abi: *parsed, FlagsCaller: FlagsCaller{contract: contract}, FlagsTransactor: FlagsTransactor{contract: contract}, FlagsFilterer: FlagsFilterer{contract: contract}}, nil } type Flags struct { diff --git a/core/gethwrappers/generated/flux_aggregator_wrapper/flux_aggregator_wrapper.go b/core/gethwrappers/generated/flux_aggregator_wrapper/flux_aggregator_wrapper.go index 6c4cb73af7a..876ef73487c 100644 --- a/core/gethwrappers/generated/flux_aggregator_wrapper/flux_aggregator_wrapper.go +++ b/core/gethwrappers/generated/flux_aggregator_wrapper/flux_aggregator_wrapper.go @@ -52,7 +52,7 @@ func DeployFluxAggregator(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &FluxAggregator{FluxAggregatorCaller: FluxAggregatorCaller{contract: contract}, FluxAggregatorTransactor: FluxAggregatorTransactor{contract: contract}, FluxAggregatorFilterer: FluxAggregatorFilterer{contract: contract}}, nil + return address, tx, &FluxAggregator{address: address, abi: *parsed, FluxAggregatorCaller: FluxAggregatorCaller{contract: contract}, FluxAggregatorTransactor: FluxAggregatorTransactor{contract: contract}, FluxAggregatorFilterer: FluxAggregatorFilterer{contract: contract}}, nil } type FluxAggregator struct { diff --git a/core/gethwrappers/generated/functions_billing_registry_events_mock/functions_billing_registry_events_mock.go b/core/gethwrappers/generated/functions_billing_registry_events_mock/functions_billing_registry_events_mock.go index ec87fb9e740..c86c4feaa4b 100644 --- a/core/gethwrappers/generated/functions_billing_registry_events_mock/functions_billing_registry_events_mock.go +++ b/core/gethwrappers/generated/functions_billing_registry_events_mock/functions_billing_registry_events_mock.go @@ -64,7 +64,7 @@ func DeployFunctionsBillingRegistryEventsMock(auth *bind.TransactOpts, backend b if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &FunctionsBillingRegistryEventsMock{FunctionsBillingRegistryEventsMockCaller: FunctionsBillingRegistryEventsMockCaller{contract: contract}, FunctionsBillingRegistryEventsMockTransactor: FunctionsBillingRegistryEventsMockTransactor{contract: contract}, FunctionsBillingRegistryEventsMockFilterer: FunctionsBillingRegistryEventsMockFilterer{contract: contract}}, nil + return address, tx, &FunctionsBillingRegistryEventsMock{address: address, abi: *parsed, FunctionsBillingRegistryEventsMockCaller: FunctionsBillingRegistryEventsMockCaller{contract: contract}, FunctionsBillingRegistryEventsMockTransactor: FunctionsBillingRegistryEventsMockTransactor{contract: contract}, FunctionsBillingRegistryEventsMockFilterer: FunctionsBillingRegistryEventsMockFilterer{contract: contract}}, nil } type FunctionsBillingRegistryEventsMock struct { diff --git a/core/gethwrappers/generated/functions_oracle_events_mock/functions_oracle_events_mock.go b/core/gethwrappers/generated/functions_oracle_events_mock/functions_oracle_events_mock.go index 3b8238c00e0..4e97e082394 100644 --- a/core/gethwrappers/generated/functions_oracle_events_mock/functions_oracle_events_mock.go +++ b/core/gethwrappers/generated/functions_oracle_events_mock/functions_oracle_events_mock.go @@ -52,7 +52,7 @@ func DeployFunctionsOracleEventsMock(auth *bind.TransactOpts, backend bind.Contr if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &FunctionsOracleEventsMock{FunctionsOracleEventsMockCaller: FunctionsOracleEventsMockCaller{contract: contract}, FunctionsOracleEventsMockTransactor: FunctionsOracleEventsMockTransactor{contract: contract}, FunctionsOracleEventsMockFilterer: FunctionsOracleEventsMockFilterer{contract: contract}}, nil + return address, tx, &FunctionsOracleEventsMock{address: address, abi: *parsed, FunctionsOracleEventsMockCaller: FunctionsOracleEventsMockCaller{contract: contract}, FunctionsOracleEventsMockTransactor: FunctionsOracleEventsMockTransactor{contract: contract}, FunctionsOracleEventsMockFilterer: FunctionsOracleEventsMockFilterer{contract: contract}}, nil } type FunctionsOracleEventsMock struct { diff --git a/core/gethwrappers/generated/gas_wrapper/gas_wrapper.go b/core/gethwrappers/generated/gas_wrapper/gas_wrapper.go index f2e25e36a7d..eec0cf855f3 100644 --- a/core/gethwrappers/generated/gas_wrapper/gas_wrapper.go +++ b/core/gethwrappers/generated/gas_wrapper/gas_wrapper.go @@ -52,7 +52,7 @@ func DeployKeeperRegistryCheckUpkeepGasUsageWrapper(auth *bind.TransactOpts, bac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistryCheckUpkeepGasUsageWrapper{KeeperRegistryCheckUpkeepGasUsageWrapperCaller: KeeperRegistryCheckUpkeepGasUsageWrapperCaller{contract: contract}, KeeperRegistryCheckUpkeepGasUsageWrapperTransactor: KeeperRegistryCheckUpkeepGasUsageWrapperTransactor{contract: contract}, KeeperRegistryCheckUpkeepGasUsageWrapperFilterer: KeeperRegistryCheckUpkeepGasUsageWrapperFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistryCheckUpkeepGasUsageWrapper{address: address, abi: *parsed, KeeperRegistryCheckUpkeepGasUsageWrapperCaller: KeeperRegistryCheckUpkeepGasUsageWrapperCaller{contract: contract}, KeeperRegistryCheckUpkeepGasUsageWrapperTransactor: KeeperRegistryCheckUpkeepGasUsageWrapperTransactor{contract: contract}, KeeperRegistryCheckUpkeepGasUsageWrapperFilterer: KeeperRegistryCheckUpkeepGasUsageWrapperFilterer{contract: contract}}, nil } type KeeperRegistryCheckUpkeepGasUsageWrapper struct { diff --git a/core/gethwrappers/generated/gas_wrapper_mock/gas_wrapper_mock.go b/core/gethwrappers/generated/gas_wrapper_mock/gas_wrapper_mock.go index b0420097b43..93803ce9938 100644 --- a/core/gethwrappers/generated/gas_wrapper_mock/gas_wrapper_mock.go +++ b/core/gethwrappers/generated/gas_wrapper_mock/gas_wrapper_mock.go @@ -52,7 +52,7 @@ func DeployKeeperRegistryCheckUpkeepGasUsageWrapperMock(auth *bind.TransactOpts, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistryCheckUpkeepGasUsageWrapperMock{KeeperRegistryCheckUpkeepGasUsageWrapperMockCaller: KeeperRegistryCheckUpkeepGasUsageWrapperMockCaller{contract: contract}, KeeperRegistryCheckUpkeepGasUsageWrapperMockTransactor: KeeperRegistryCheckUpkeepGasUsageWrapperMockTransactor{contract: contract}, KeeperRegistryCheckUpkeepGasUsageWrapperMockFilterer: KeeperRegistryCheckUpkeepGasUsageWrapperMockFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistryCheckUpkeepGasUsageWrapperMock{address: address, abi: *parsed, KeeperRegistryCheckUpkeepGasUsageWrapperMockCaller: KeeperRegistryCheckUpkeepGasUsageWrapperMockCaller{contract: contract}, KeeperRegistryCheckUpkeepGasUsageWrapperMockTransactor: KeeperRegistryCheckUpkeepGasUsageWrapperMockTransactor{contract: contract}, KeeperRegistryCheckUpkeepGasUsageWrapperMockFilterer: KeeperRegistryCheckUpkeepGasUsageWrapperMockFilterer{contract: contract}}, nil } type KeeperRegistryCheckUpkeepGasUsageWrapperMock struct { diff --git a/core/gethwrappers/generated/keeper_consumer_performance_wrapper/keeper_consumer_performance_wrapper.go b/core/gethwrappers/generated/keeper_consumer_performance_wrapper/keeper_consumer_performance_wrapper.go index 0e1876ce0a2..08178c98824 100644 --- a/core/gethwrappers/generated/keeper_consumer_performance_wrapper/keeper_consumer_performance_wrapper.go +++ b/core/gethwrappers/generated/keeper_consumer_performance_wrapper/keeper_consumer_performance_wrapper.go @@ -52,7 +52,7 @@ func DeployKeeperConsumerPerformance(auth *bind.TransactOpts, backend bind.Contr if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperConsumerPerformance{KeeperConsumerPerformanceCaller: KeeperConsumerPerformanceCaller{contract: contract}, KeeperConsumerPerformanceTransactor: KeeperConsumerPerformanceTransactor{contract: contract}, KeeperConsumerPerformanceFilterer: KeeperConsumerPerformanceFilterer{contract: contract}}, nil + return address, tx, &KeeperConsumerPerformance{address: address, abi: *parsed, KeeperConsumerPerformanceCaller: KeeperConsumerPerformanceCaller{contract: contract}, KeeperConsumerPerformanceTransactor: KeeperConsumerPerformanceTransactor{contract: contract}, KeeperConsumerPerformanceFilterer: KeeperConsumerPerformanceFilterer{contract: contract}}, nil } type KeeperConsumerPerformance struct { diff --git a/core/gethwrappers/generated/keeper_consumer_wrapper/keeper_consumer_wrapper.go b/core/gethwrappers/generated/keeper_consumer_wrapper/keeper_consumer_wrapper.go index 896927f9e6d..feb614aa83b 100644 --- a/core/gethwrappers/generated/keeper_consumer_wrapper/keeper_consumer_wrapper.go +++ b/core/gethwrappers/generated/keeper_consumer_wrapper/keeper_consumer_wrapper.go @@ -29,8 +29,8 @@ var ( ) var KeeperConsumerMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"updateInterval\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"checkData\",\"type\":\"bytes\"}],\"name\":\"checkUpkeep\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"upkeepNeeded\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"performData\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"counter\",\"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\":\"lastTimeStamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"performData\",\"type\":\"bytes\"}],\"name\":\"performUpkeep\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x60a060405234801561001057600080fd5b5060405161036c38038061036c83398101604081905261002f9161003f565b6080524260015560008055610058565b60006020828403121561005157600080fd5b5051919050565b6080516102fa610072600039600060cc01526102fa6000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c806361bc221a1161005057806361bc221a1461009d5780636e04ff0d146100a6578063947a36fb146100c757600080fd5b80633f3b3b271461006c5780634585e33b14610088575b600080fd5b61007560015481565b6040519081526020015b60405180910390f35b61009b6100963660046101c5565b6100ee565b005b61007560005481565b6100b96100b43660046101c5565b610103565b60405161007f929190610237565b6100757f000000000000000000000000000000000000000000000000000000000000000081565b6000546100fc9060016102ad565b6000555050565b6000606061010f610157565b6001848481818080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250959a92995091975050505050505050565b32156101c3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f6f6e6c7920666f722073696d756c61746564206261636b656e64000000000000604482015260640160405180910390fd5b565b600080602083850312156101d857600080fd5b823567ffffffffffffffff808211156101f057600080fd5b818501915085601f83011261020457600080fd5b81358181111561021357600080fd5b86602082850101111561022557600080fd5b60209290920196919550909350505050565b821515815260006020604081840152835180604085015260005b8181101561026d57858101830151858201606001528201610251565b5060006060828601015260607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f830116850101925050509392505050565b808201808211156102e7577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9291505056fea164736f6c6343000810000a", + ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"updateInterval\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"OnlySimulatedBackend\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"checkData\",\"type\":\"bytes\"}],\"name\":\"checkUpkeep\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"upkeepNeeded\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"performData\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"counter\",\"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\":\"lastTimeStamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"performData\",\"type\":\"bytes\"}],\"name\":\"performUpkeep\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60a060405234801561001057600080fd5b5060405161033838038061033883398101604081905261002f9161003f565b6080524260015560008055610058565b60006020828403121561005157600080fd5b5051919050565b6080516102c6610072600039600060cc01526102c66000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c806361bc221a1161005057806361bc221a1461009d5780636e04ff0d146100a6578063947a36fb146100c757600080fd5b80633f3b3b271461006c5780634585e33b14610088575b600080fd5b61007560015481565b6040519081526020015b60405180910390f35b61009b610096366004610191565b6100ee565b005b61007560005481565b6100b96100b4366004610191565b610103565b60405161007f929190610203565b6100757f000000000000000000000000000000000000000000000000000000000000000081565b6000546100fc906001610279565b6000555050565b6000606061010f610157565b6001848481818080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250959a92995091975050505050505050565b321561018f576040517fb60ac5db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b600080602083850312156101a457600080fd5b823567ffffffffffffffff808211156101bc57600080fd5b818501915085601f8301126101d057600080fd5b8135818111156101df57600080fd5b8660208285010111156101f157600080fd5b60209290920196919550909350505050565b821515815260006020604081840152835180604085015260005b818110156102395785810183015185820160600152820161021d565b5060006060828601015260607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f830116850101925050509392505050565b808201808211156102b3577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9291505056fea164736f6c6343000810000a", } var KeeperConsumerABI = KeeperConsumerMetaData.ABI @@ -50,7 +50,7 @@ func DeployKeeperConsumer(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperConsumer{KeeperConsumerCaller: KeeperConsumerCaller{contract: contract}, KeeperConsumerTransactor: KeeperConsumerTransactor{contract: contract}, KeeperConsumerFilterer: KeeperConsumerFilterer{contract: contract}}, nil + return address, tx, &KeeperConsumer{address: address, abi: *parsed, KeeperConsumerCaller: KeeperConsumerCaller{contract: contract}, KeeperConsumerTransactor: KeeperConsumerTransactor{contract: contract}, KeeperConsumerFilterer: KeeperConsumerFilterer{contract: contract}}, nil } type KeeperConsumer struct { diff --git a/core/gethwrappers/generated/keeper_registrar_wrapper1_2/keeper_registrar_wrapper1_2.go b/core/gethwrappers/generated/keeper_registrar_wrapper1_2/keeper_registrar_wrapper1_2.go index 3721238f67e..45564b662d0 100644 --- a/core/gethwrappers/generated/keeper_registrar_wrapper1_2/keeper_registrar_wrapper1_2.go +++ b/core/gethwrappers/generated/keeper_registrar_wrapper1_2/keeper_registrar_wrapper1_2.go @@ -52,7 +52,7 @@ func DeployKeeperRegistrar(auth *bind.TransactOpts, backend bind.ContractBackend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistrar{KeeperRegistrarCaller: KeeperRegistrarCaller{contract: contract}, KeeperRegistrarTransactor: KeeperRegistrarTransactor{contract: contract}, KeeperRegistrarFilterer: KeeperRegistrarFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistrar{address: address, abi: *parsed, KeeperRegistrarCaller: KeeperRegistrarCaller{contract: contract}, KeeperRegistrarTransactor: KeeperRegistrarTransactor{contract: contract}, KeeperRegistrarFilterer: KeeperRegistrarFilterer{contract: contract}}, nil } type KeeperRegistrar struct { diff --git a/core/gethwrappers/generated/keeper_registrar_wrapper1_2_mock/keeper_registrar_wrapper1_2_mock.go b/core/gethwrappers/generated/keeper_registrar_wrapper1_2_mock/keeper_registrar_wrapper1_2_mock.go index ea4c49f7c88..d83fd9a4314 100644 --- a/core/gethwrappers/generated/keeper_registrar_wrapper1_2_mock/keeper_registrar_wrapper1_2_mock.go +++ b/core/gethwrappers/generated/keeper_registrar_wrapper1_2_mock/keeper_registrar_wrapper1_2_mock.go @@ -52,7 +52,7 @@ func DeployKeeperRegistrarMock(auth *bind.TransactOpts, backend bind.ContractBac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistrarMock{KeeperRegistrarMockCaller: KeeperRegistrarMockCaller{contract: contract}, KeeperRegistrarMockTransactor: KeeperRegistrarMockTransactor{contract: contract}, KeeperRegistrarMockFilterer: KeeperRegistrarMockFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistrarMock{address: address, abi: *parsed, KeeperRegistrarMockCaller: KeeperRegistrarMockCaller{contract: contract}, KeeperRegistrarMockTransactor: KeeperRegistrarMockTransactor{contract: contract}, KeeperRegistrarMockFilterer: KeeperRegistrarMockFilterer{contract: contract}}, nil } type KeeperRegistrarMock struct { diff --git a/core/gethwrappers/generated/keeper_registrar_wrapper2_0/keeper_registrar_wrapper2_0.go b/core/gethwrappers/generated/keeper_registrar_wrapper2_0/keeper_registrar_wrapper2_0.go index c8a7016df4f..56d78f9f0dd 100644 --- a/core/gethwrappers/generated/keeper_registrar_wrapper2_0/keeper_registrar_wrapper2_0.go +++ b/core/gethwrappers/generated/keeper_registrar_wrapper2_0/keeper_registrar_wrapper2_0.go @@ -63,7 +63,7 @@ func DeployKeeperRegistrar(auth *bind.TransactOpts, backend bind.ContractBackend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistrar{KeeperRegistrarCaller: KeeperRegistrarCaller{contract: contract}, KeeperRegistrarTransactor: KeeperRegistrarTransactor{contract: contract}, KeeperRegistrarFilterer: KeeperRegistrarFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistrar{address: address, abi: *parsed, KeeperRegistrarCaller: KeeperRegistrarCaller{contract: contract}, KeeperRegistrarTransactor: KeeperRegistrarTransactor{contract: contract}, KeeperRegistrarFilterer: KeeperRegistrarFilterer{contract: contract}}, nil } type KeeperRegistrar struct { diff --git a/core/gethwrappers/generated/keeper_registry_logic1_3/keeper_registry_logic1_3.go b/core/gethwrappers/generated/keeper_registry_logic1_3/keeper_registry_logic1_3.go index 6049536df69..11856a0c8ff 100644 --- a/core/gethwrappers/generated/keeper_registry_logic1_3/keeper_registry_logic1_3.go +++ b/core/gethwrappers/generated/keeper_registry_logic1_3/keeper_registry_logic1_3.go @@ -67,7 +67,7 @@ func DeployKeeperRegistryLogic(auth *bind.TransactOpts, backend bind.ContractBac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistryLogic{KeeperRegistryLogicCaller: KeeperRegistryLogicCaller{contract: contract}, KeeperRegistryLogicTransactor: KeeperRegistryLogicTransactor{contract: contract}, KeeperRegistryLogicFilterer: KeeperRegistryLogicFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistryLogic{address: address, abi: *parsed, KeeperRegistryLogicCaller: KeeperRegistryLogicCaller{contract: contract}, KeeperRegistryLogicTransactor: KeeperRegistryLogicTransactor{contract: contract}, KeeperRegistryLogicFilterer: KeeperRegistryLogicFilterer{contract: contract}}, nil } type KeeperRegistryLogic struct { diff --git a/core/gethwrappers/generated/keeper_registry_logic2_0/keeper_registry_logic2_0.go b/core/gethwrappers/generated/keeper_registry_logic2_0/keeper_registry_logic2_0.go index e4e24a0463a..819e7e094b2 100644 --- a/core/gethwrappers/generated/keeper_registry_logic2_0/keeper_registry_logic2_0.go +++ b/core/gethwrappers/generated/keeper_registry_logic2_0/keeper_registry_logic2_0.go @@ -52,7 +52,7 @@ func DeployKeeperRegistryLogic(auth *bind.TransactOpts, backend bind.ContractBac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistryLogic{KeeperRegistryLogicCaller: KeeperRegistryLogicCaller{contract: contract}, KeeperRegistryLogicTransactor: KeeperRegistryLogicTransactor{contract: contract}, KeeperRegistryLogicFilterer: KeeperRegistryLogicFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistryLogic{address: address, abi: *parsed, KeeperRegistryLogicCaller: KeeperRegistryLogicCaller{contract: contract}, KeeperRegistryLogicTransactor: KeeperRegistryLogicTransactor{contract: contract}, KeeperRegistryLogicFilterer: KeeperRegistryLogicFilterer{contract: contract}}, nil } type KeeperRegistryLogic struct { diff --git a/core/gethwrappers/generated/keeper_registry_logic_a_wrapper_2_1/keeper_registry_logic_a_wrapper_2_1.go b/core/gethwrappers/generated/keeper_registry_logic_a_wrapper_2_1/keeper_registry_logic_a_wrapper_2_1.go index 48c4d75e8b3..69d9adbc686 100644 --- a/core/gethwrappers/generated/keeper_registry_logic_a_wrapper_2_1/keeper_registry_logic_a_wrapper_2_1.go +++ b/core/gethwrappers/generated/keeper_registry_logic_a_wrapper_2_1/keeper_registry_logic_a_wrapper_2_1.go @@ -52,7 +52,7 @@ func DeployKeeperRegistryLogicA(auth *bind.TransactOpts, backend bind.ContractBa if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistryLogicA{KeeperRegistryLogicACaller: KeeperRegistryLogicACaller{contract: contract}, KeeperRegistryLogicATransactor: KeeperRegistryLogicATransactor{contract: contract}, KeeperRegistryLogicAFilterer: KeeperRegistryLogicAFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistryLogicA{address: address, abi: *parsed, KeeperRegistryLogicACaller: KeeperRegistryLogicACaller{contract: contract}, KeeperRegistryLogicATransactor: KeeperRegistryLogicATransactor{contract: contract}, KeeperRegistryLogicAFilterer: KeeperRegistryLogicAFilterer{contract: contract}}, nil } type KeeperRegistryLogicA struct { diff --git a/core/gethwrappers/generated/keeper_registry_logic_b_wrapper_2_1/keeper_registry_logic_b_wrapper_2_1.go b/core/gethwrappers/generated/keeper_registry_logic_b_wrapper_2_1/keeper_registry_logic_b_wrapper_2_1.go index 15330fb8219..984d9221eb1 100644 --- a/core/gethwrappers/generated/keeper_registry_logic_b_wrapper_2_1/keeper_registry_logic_b_wrapper_2_1.go +++ b/core/gethwrappers/generated/keeper_registry_logic_b_wrapper_2_1/keeper_registry_logic_b_wrapper_2_1.go @@ -96,7 +96,7 @@ func DeployKeeperRegistryLogicB(auth *bind.TransactOpts, backend bind.ContractBa if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistryLogicB{KeeperRegistryLogicBCaller: KeeperRegistryLogicBCaller{contract: contract}, KeeperRegistryLogicBTransactor: KeeperRegistryLogicBTransactor{contract: contract}, KeeperRegistryLogicBFilterer: KeeperRegistryLogicBFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistryLogicB{address: address, abi: *parsed, KeeperRegistryLogicBCaller: KeeperRegistryLogicBCaller{contract: contract}, KeeperRegistryLogicBTransactor: KeeperRegistryLogicBTransactor{contract: contract}, KeeperRegistryLogicBFilterer: KeeperRegistryLogicBFilterer{contract: contract}}, nil } type KeeperRegistryLogicB struct { diff --git a/core/gethwrappers/generated/keeper_registry_wrapper1_1/keeper_registry_wrapper1_1.go b/core/gethwrappers/generated/keeper_registry_wrapper1_1/keeper_registry_wrapper1_1.go index 896de7f088b..196b2f36f5d 100644 --- a/core/gethwrappers/generated/keeper_registry_wrapper1_1/keeper_registry_wrapper1_1.go +++ b/core/gethwrappers/generated/keeper_registry_wrapper1_1/keeper_registry_wrapper1_1.go @@ -52,7 +52,7 @@ func DeployKeeperRegistry(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistry{KeeperRegistryCaller: KeeperRegistryCaller{contract: contract}, KeeperRegistryTransactor: KeeperRegistryTransactor{contract: contract}, KeeperRegistryFilterer: KeeperRegistryFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistry{address: address, abi: *parsed, KeeperRegistryCaller: KeeperRegistryCaller{contract: contract}, KeeperRegistryTransactor: KeeperRegistryTransactor{contract: contract}, KeeperRegistryFilterer: KeeperRegistryFilterer{contract: contract}}, nil } type KeeperRegistry struct { diff --git a/core/gethwrappers/generated/keeper_registry_wrapper1_1_mock/keeper_registry_wrapper1_1_mock.go b/core/gethwrappers/generated/keeper_registry_wrapper1_1_mock/keeper_registry_wrapper1_1_mock.go index d7bfc132eb5..14457f91efa 100644 --- a/core/gethwrappers/generated/keeper_registry_wrapper1_1_mock/keeper_registry_wrapper1_1_mock.go +++ b/core/gethwrappers/generated/keeper_registry_wrapper1_1_mock/keeper_registry_wrapper1_1_mock.go @@ -52,7 +52,7 @@ func DeployKeeperRegistryMock(auth *bind.TransactOpts, backend bind.ContractBack if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistryMock{KeeperRegistryMockCaller: KeeperRegistryMockCaller{contract: contract}, KeeperRegistryMockTransactor: KeeperRegistryMockTransactor{contract: contract}, KeeperRegistryMockFilterer: KeeperRegistryMockFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistryMock{address: address, abi: *parsed, KeeperRegistryMockCaller: KeeperRegistryMockCaller{contract: contract}, KeeperRegistryMockTransactor: KeeperRegistryMockTransactor{contract: contract}, KeeperRegistryMockFilterer: KeeperRegistryMockFilterer{contract: contract}}, nil } type KeeperRegistryMock struct { diff --git a/core/gethwrappers/generated/keeper_registry_wrapper1_2/keeper_registry_wrapper1_2.go b/core/gethwrappers/generated/keeper_registry_wrapper1_2/keeper_registry_wrapper1_2.go index cbfd589abf4..6fa0d55b78d 100644 --- a/core/gethwrappers/generated/keeper_registry_wrapper1_2/keeper_registry_wrapper1_2.go +++ b/core/gethwrappers/generated/keeper_registry_wrapper1_2/keeper_registry_wrapper1_2.go @@ -74,7 +74,7 @@ func DeployKeeperRegistry(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistry{KeeperRegistryCaller: KeeperRegistryCaller{contract: contract}, KeeperRegistryTransactor: KeeperRegistryTransactor{contract: contract}, KeeperRegistryFilterer: KeeperRegistryFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistry{address: address, abi: *parsed, KeeperRegistryCaller: KeeperRegistryCaller{contract: contract}, KeeperRegistryTransactor: KeeperRegistryTransactor{contract: contract}, KeeperRegistryFilterer: KeeperRegistryFilterer{contract: contract}}, nil } type KeeperRegistry struct { diff --git a/core/gethwrappers/generated/keeper_registry_wrapper1_3/keeper_registry_wrapper1_3.go b/core/gethwrappers/generated/keeper_registry_wrapper1_3/keeper_registry_wrapper1_3.go index 73ff800e12b..bea6e458a00 100644 --- a/core/gethwrappers/generated/keeper_registry_wrapper1_3/keeper_registry_wrapper1_3.go +++ b/core/gethwrappers/generated/keeper_registry_wrapper1_3/keeper_registry_wrapper1_3.go @@ -74,7 +74,7 @@ func DeployKeeperRegistry(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistry{KeeperRegistryCaller: KeeperRegistryCaller{contract: contract}, KeeperRegistryTransactor: KeeperRegistryTransactor{contract: contract}, KeeperRegistryFilterer: KeeperRegistryFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistry{address: address, abi: *parsed, KeeperRegistryCaller: KeeperRegistryCaller{contract: contract}, KeeperRegistryTransactor: KeeperRegistryTransactor{contract: contract}, KeeperRegistryFilterer: KeeperRegistryFilterer{contract: contract}}, nil } type KeeperRegistry struct { diff --git a/core/gethwrappers/generated/keeper_registry_wrapper2_0/keeper_registry_wrapper2_0.go b/core/gethwrappers/generated/keeper_registry_wrapper2_0/keeper_registry_wrapper2_0.go index 2aaf8085aa4..bd000995e33 100644 --- a/core/gethwrappers/generated/keeper_registry_wrapper2_0/keeper_registry_wrapper2_0.go +++ b/core/gethwrappers/generated/keeper_registry_wrapper2_0/keeper_registry_wrapper2_0.go @@ -94,7 +94,7 @@ func DeployKeeperRegistry(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistry{KeeperRegistryCaller: KeeperRegistryCaller{contract: contract}, KeeperRegistryTransactor: KeeperRegistryTransactor{contract: contract}, KeeperRegistryFilterer: KeeperRegistryFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistry{address: address, abi: *parsed, KeeperRegistryCaller: KeeperRegistryCaller{contract: contract}, KeeperRegistryTransactor: KeeperRegistryTransactor{contract: contract}, KeeperRegistryFilterer: KeeperRegistryFilterer{contract: contract}}, nil } type KeeperRegistry struct { diff --git a/core/gethwrappers/generated/keeper_registry_wrapper_2_1/keeper_registry_wrapper_2_1.go b/core/gethwrappers/generated/keeper_registry_wrapper_2_1/keeper_registry_wrapper_2_1.go index 1db34ca3953..fc9d120c5e3 100644 --- a/core/gethwrappers/generated/keeper_registry_wrapper_2_1/keeper_registry_wrapper_2_1.go +++ b/core/gethwrappers/generated/keeper_registry_wrapper_2_1/keeper_registry_wrapper_2_1.go @@ -70,7 +70,7 @@ func DeployKeeperRegistry(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeeperRegistry{KeeperRegistryCaller: KeeperRegistryCaller{contract: contract}, KeeperRegistryTransactor: KeeperRegistryTransactor{contract: contract}, KeeperRegistryFilterer: KeeperRegistryFilterer{contract: contract}}, nil + return address, tx, &KeeperRegistry{address: address, abi: *parsed, KeeperRegistryCaller: KeeperRegistryCaller{contract: contract}, KeeperRegistryTransactor: KeeperRegistryTransactor{contract: contract}, KeeperRegistryFilterer: KeeperRegistryFilterer{contract: contract}}, nil } type KeeperRegistry struct { diff --git a/core/gethwrappers/generated/keepers_vrf_consumer/keepers_vrf_consumer.go b/core/gethwrappers/generated/keepers_vrf_consumer/keepers_vrf_consumer.go index 99e0fece52e..57e0aced8a2 100644 --- a/core/gethwrappers/generated/keepers_vrf_consumer/keepers_vrf_consumer.go +++ b/core/gethwrappers/generated/keepers_vrf_consumer/keepers_vrf_consumer.go @@ -50,7 +50,7 @@ func DeployKeepersVRFConsumer(auth *bind.TransactOpts, backend bind.ContractBack if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &KeepersVRFConsumer{KeepersVRFConsumerCaller: KeepersVRFConsumerCaller{contract: contract}, KeepersVRFConsumerTransactor: KeepersVRFConsumerTransactor{contract: contract}, KeepersVRFConsumerFilterer: KeepersVRFConsumerFilterer{contract: contract}}, nil + return address, tx, &KeepersVRFConsumer{address: address, abi: *parsed, KeepersVRFConsumerCaller: KeepersVRFConsumerCaller{contract: contract}, KeepersVRFConsumerTransactor: KeepersVRFConsumerTransactor{contract: contract}, KeepersVRFConsumerFilterer: KeepersVRFConsumerFilterer{contract: contract}}, nil } type KeepersVRFConsumer struct { diff --git a/core/gethwrappers/generated/link_token_interface/link_token_interface.go b/core/gethwrappers/generated/link_token_interface/link_token_interface.go index d0a6b92c1e4..53fbb56dce7 100644 --- a/core/gethwrappers/generated/link_token_interface/link_token_interface.go +++ b/core/gethwrappers/generated/link_token_interface/link_token_interface.go @@ -52,7 +52,7 @@ func DeployLinkToken(auth *bind.TransactOpts, backend bind.ContractBackend) (com if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &LinkToken{LinkTokenCaller: LinkTokenCaller{contract: contract}, LinkTokenTransactor: LinkTokenTransactor{contract: contract}, LinkTokenFilterer: LinkTokenFilterer{contract: contract}}, nil + return address, tx, &LinkToken{address: address, abi: *parsed, LinkTokenCaller: LinkTokenCaller{contract: contract}, LinkTokenTransactor: LinkTokenTransactor{contract: contract}, LinkTokenFilterer: LinkTokenFilterer{contract: contract}}, nil } type LinkToken struct { diff --git a/core/gethwrappers/generated/log_emitter/log_emitter.go b/core/gethwrappers/generated/log_emitter/log_emitter.go index 4f0189dfdea..3cb11da5125 100644 --- a/core/gethwrappers/generated/log_emitter/log_emitter.go +++ b/core/gethwrappers/generated/log_emitter/log_emitter.go @@ -52,7 +52,7 @@ func DeployLogEmitter(auth *bind.TransactOpts, backend bind.ContractBackend) (co if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &LogEmitter{LogEmitterCaller: LogEmitterCaller{contract: contract}, LogEmitterTransactor: LogEmitterTransactor{contract: contract}, LogEmitterFilterer: LogEmitterFilterer{contract: contract}}, nil + return address, tx, &LogEmitter{address: address, abi: *parsed, LogEmitterCaller: LogEmitterCaller{contract: contract}, LogEmitterTransactor: LogEmitterTransactor{contract: contract}, LogEmitterFilterer: LogEmitterFilterer{contract: contract}}, nil } type LogEmitter struct { diff --git a/core/gethwrappers/generated/log_triggered_streams_lookup_wrapper/log_triggered_streams_lookup_wrapper.go b/core/gethwrappers/generated/log_triggered_streams_lookup_wrapper/log_triggered_streams_lookup_wrapper.go index 18b61baed7f..ccd5aea2c37 100644 --- a/core/gethwrappers/generated/log_triggered_streams_lookup_wrapper/log_triggered_streams_lookup_wrapper.go +++ b/core/gethwrappers/generated/log_triggered_streams_lookup_wrapper/log_triggered_streams_lookup_wrapper.go @@ -63,7 +63,7 @@ func DeployLogTriggeredStreamsLookup(auth *bind.TransactOpts, backend bind.Contr if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &LogTriggeredStreamsLookup{LogTriggeredStreamsLookupCaller: LogTriggeredStreamsLookupCaller{contract: contract}, LogTriggeredStreamsLookupTransactor: LogTriggeredStreamsLookupTransactor{contract: contract}, LogTriggeredStreamsLookupFilterer: LogTriggeredStreamsLookupFilterer{contract: contract}}, nil + return address, tx, &LogTriggeredStreamsLookup{address: address, abi: *parsed, LogTriggeredStreamsLookupCaller: LogTriggeredStreamsLookupCaller{contract: contract}, LogTriggeredStreamsLookupTransactor: LogTriggeredStreamsLookupTransactor{contract: contract}, LogTriggeredStreamsLookupFilterer: LogTriggeredStreamsLookupFilterer{contract: contract}}, nil } type LogTriggeredStreamsLookup struct { diff --git a/core/gethwrappers/generated/log_upkeep_counter_wrapper/log_upkeep_counter_wrapper.go b/core/gethwrappers/generated/log_upkeep_counter_wrapper/log_upkeep_counter_wrapper.go index fd55349b126..51b7b753cc7 100644 --- a/core/gethwrappers/generated/log_upkeep_counter_wrapper/log_upkeep_counter_wrapper.go +++ b/core/gethwrappers/generated/log_upkeep_counter_wrapper/log_upkeep_counter_wrapper.go @@ -63,7 +63,7 @@ func DeployLogUpkeepCounter(auth *bind.TransactOpts, backend bind.ContractBacken if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &LogUpkeepCounter{LogUpkeepCounterCaller: LogUpkeepCounterCaller{contract: contract}, LogUpkeepCounterTransactor: LogUpkeepCounterTransactor{contract: contract}, LogUpkeepCounterFilterer: LogUpkeepCounterFilterer{contract: contract}}, nil + return address, tx, &LogUpkeepCounter{address: address, abi: *parsed, LogUpkeepCounterCaller: LogUpkeepCounterCaller{contract: contract}, LogUpkeepCounterTransactor: LogUpkeepCounterTransactor{contract: contract}, LogUpkeepCounterFilterer: LogUpkeepCounterFilterer{contract: contract}}, nil } type LogUpkeepCounter struct { diff --git a/core/gethwrappers/generated/mock_aggregator_proxy/mock_aggregator_proxy.go b/core/gethwrappers/generated/mock_aggregator_proxy/mock_aggregator_proxy.go index 1694ebd11f1..a9c972e8be9 100644 --- a/core/gethwrappers/generated/mock_aggregator_proxy/mock_aggregator_proxy.go +++ b/core/gethwrappers/generated/mock_aggregator_proxy/mock_aggregator_proxy.go @@ -50,7 +50,7 @@ func DeployMockAggregatorProxy(auth *bind.TransactOpts, backend bind.ContractBac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &MockAggregatorProxy{MockAggregatorProxyCaller: MockAggregatorProxyCaller{contract: contract}, MockAggregatorProxyTransactor: MockAggregatorProxyTransactor{contract: contract}, MockAggregatorProxyFilterer: MockAggregatorProxyFilterer{contract: contract}}, nil + return address, tx, &MockAggregatorProxy{address: address, abi: *parsed, MockAggregatorProxyCaller: MockAggregatorProxyCaller{contract: contract}, MockAggregatorProxyTransactor: MockAggregatorProxyTransactor{contract: contract}, MockAggregatorProxyFilterer: MockAggregatorProxyFilterer{contract: contract}}, nil } type MockAggregatorProxy struct { diff --git a/core/gethwrappers/generated/mock_ethlink_aggregator_wrapper/mock_ethlink_aggregator_wrapper.go b/core/gethwrappers/generated/mock_ethlink_aggregator_wrapper/mock_ethlink_aggregator_wrapper.go index bcf34a0c477..bc92e3f32b9 100644 --- a/core/gethwrappers/generated/mock_ethlink_aggregator_wrapper/mock_ethlink_aggregator_wrapper.go +++ b/core/gethwrappers/generated/mock_ethlink_aggregator_wrapper/mock_ethlink_aggregator_wrapper.go @@ -50,7 +50,7 @@ func DeployMockETHLINKAggregator(auth *bind.TransactOpts, backend bind.ContractB if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &MockETHLINKAggregator{MockETHLINKAggregatorCaller: MockETHLINKAggregatorCaller{contract: contract}, MockETHLINKAggregatorTransactor: MockETHLINKAggregatorTransactor{contract: contract}, MockETHLINKAggregatorFilterer: MockETHLINKAggregatorFilterer{contract: contract}}, nil + return address, tx, &MockETHLINKAggregator{address: address, abi: *parsed, MockETHLINKAggregatorCaller: MockETHLINKAggregatorCaller{contract: contract}, MockETHLINKAggregatorTransactor: MockETHLINKAggregatorTransactor{contract: contract}, MockETHLINKAggregatorFilterer: MockETHLINKAggregatorFilterer{contract: contract}}, nil } type MockETHLINKAggregator struct { diff --git a/core/gethwrappers/generated/mock_gas_aggregator_wrapper/mock_gas_aggregator_wrapper.go b/core/gethwrappers/generated/mock_gas_aggregator_wrapper/mock_gas_aggregator_wrapper.go index edc0adc5b70..148417f3fc4 100644 --- a/core/gethwrappers/generated/mock_gas_aggregator_wrapper/mock_gas_aggregator_wrapper.go +++ b/core/gethwrappers/generated/mock_gas_aggregator_wrapper/mock_gas_aggregator_wrapper.go @@ -50,7 +50,7 @@ func DeployMockGASAggregator(auth *bind.TransactOpts, backend bind.ContractBacke if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &MockGASAggregator{MockGASAggregatorCaller: MockGASAggregatorCaller{contract: contract}, MockGASAggregatorTransactor: MockGASAggregatorTransactor{contract: contract}, MockGASAggregatorFilterer: MockGASAggregatorFilterer{contract: contract}}, nil + return address, tx, &MockGASAggregator{address: address, abi: *parsed, MockGASAggregatorCaller: MockGASAggregatorCaller{contract: contract}, MockGASAggregatorTransactor: MockGASAggregatorTransactor{contract: contract}, MockGASAggregatorFilterer: MockGASAggregatorFilterer{contract: contract}}, nil } type MockGASAggregator struct { diff --git a/core/gethwrappers/generated/multiwordconsumer_wrapper/multiwordconsumer_wrapper.go b/core/gethwrappers/generated/multiwordconsumer_wrapper/multiwordconsumer_wrapper.go index c939418e575..9a1fc55b6c2 100644 --- a/core/gethwrappers/generated/multiwordconsumer_wrapper/multiwordconsumer_wrapper.go +++ b/core/gethwrappers/generated/multiwordconsumer_wrapper/multiwordconsumer_wrapper.go @@ -52,7 +52,7 @@ func DeployMultiWordConsumer(auth *bind.TransactOpts, backend bind.ContractBacke if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &MultiWordConsumer{MultiWordConsumerCaller: MultiWordConsumerCaller{contract: contract}, MultiWordConsumerTransactor: MultiWordConsumerTransactor{contract: contract}, MultiWordConsumerFilterer: MultiWordConsumerFilterer{contract: contract}}, nil + return address, tx, &MultiWordConsumer{address: address, abi: *parsed, MultiWordConsumerCaller: MultiWordConsumerCaller{contract: contract}, MultiWordConsumerTransactor: MultiWordConsumerTransactor{contract: contract}, MultiWordConsumerFilterer: MultiWordConsumerFilterer{contract: contract}}, nil } type MultiWordConsumer struct { diff --git a/core/gethwrappers/generated/operator_factory/operator_factory.go b/core/gethwrappers/generated/operator_factory/operator_factory.go index b062f6b01a3..c9a6c57ce21 100644 --- a/core/gethwrappers/generated/operator_factory/operator_factory.go +++ b/core/gethwrappers/generated/operator_factory/operator_factory.go @@ -52,7 +52,7 @@ func DeployOperatorFactory(auth *bind.TransactOpts, backend bind.ContractBackend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &OperatorFactory{OperatorFactoryCaller: OperatorFactoryCaller{contract: contract}, OperatorFactoryTransactor: OperatorFactoryTransactor{contract: contract}, OperatorFactoryFilterer: OperatorFactoryFilterer{contract: contract}}, nil + return address, tx, &OperatorFactory{address: address, abi: *parsed, OperatorFactoryCaller: OperatorFactoryCaller{contract: contract}, OperatorFactoryTransactor: OperatorFactoryTransactor{contract: contract}, OperatorFactoryFilterer: OperatorFactoryFilterer{contract: contract}}, nil } type OperatorFactory struct { diff --git a/core/gethwrappers/generated/operator_wrapper/operator_wrapper.go b/core/gethwrappers/generated/operator_wrapper/operator_wrapper.go index 69541858f31..4b7f6347639 100644 --- a/core/gethwrappers/generated/operator_wrapper/operator_wrapper.go +++ b/core/gethwrappers/generated/operator_wrapper/operator_wrapper.go @@ -52,7 +52,7 @@ func DeployOperator(auth *bind.TransactOpts, backend bind.ContractBackend, link if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &Operator{OperatorCaller: OperatorCaller{contract: contract}, OperatorTransactor: OperatorTransactor{contract: contract}, OperatorFilterer: OperatorFilterer{contract: contract}}, nil + return address, tx, &Operator{address: address, abi: *parsed, OperatorCaller: OperatorCaller{contract: contract}, OperatorTransactor: OperatorTransactor{contract: contract}, OperatorFilterer: OperatorFilterer{contract: contract}}, nil } type Operator struct { diff --git a/core/gethwrappers/generated/oracle_wrapper/oracle_wrapper.go b/core/gethwrappers/generated/oracle_wrapper/oracle_wrapper.go index 98c7b373382..e22e6cd3034 100644 --- a/core/gethwrappers/generated/oracle_wrapper/oracle_wrapper.go +++ b/core/gethwrappers/generated/oracle_wrapper/oracle_wrapper.go @@ -52,7 +52,7 @@ func DeployOracle(auth *bind.TransactOpts, backend bind.ContractBackend, _link c if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &Oracle{OracleCaller: OracleCaller{contract: contract}, OracleTransactor: OracleTransactor{contract: contract}, OracleFilterer: OracleFilterer{contract: contract}}, nil + return address, tx, &Oracle{address: address, abi: *parsed, OracleCaller: OracleCaller{contract: contract}, OracleTransactor: OracleTransactor{contract: contract}, OracleFilterer: OracleFilterer{contract: contract}}, nil } type Oracle struct { diff --git a/core/gethwrappers/generated/perform_data_checker_wrapper/perform_data_checker_wrapper.go b/core/gethwrappers/generated/perform_data_checker_wrapper/perform_data_checker_wrapper.go index aa639deb9c0..a678ad0a8dd 100644 --- a/core/gethwrappers/generated/perform_data_checker_wrapper/perform_data_checker_wrapper.go +++ b/core/gethwrappers/generated/perform_data_checker_wrapper/perform_data_checker_wrapper.go @@ -50,7 +50,7 @@ func DeployPerformDataChecker(auth *bind.TransactOpts, backend bind.ContractBack if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &PerformDataChecker{PerformDataCheckerCaller: PerformDataCheckerCaller{contract: contract}, PerformDataCheckerTransactor: PerformDataCheckerTransactor{contract: contract}, PerformDataCheckerFilterer: PerformDataCheckerFilterer{contract: contract}}, nil + return address, tx, &PerformDataChecker{address: address, abi: *parsed, PerformDataCheckerCaller: PerformDataCheckerCaller{contract: contract}, PerformDataCheckerTransactor: PerformDataCheckerTransactor{contract: contract}, PerformDataCheckerFilterer: PerformDataCheckerFilterer{contract: contract}}, nil } type PerformDataChecker struct { diff --git a/core/gethwrappers/generated/simple_log_upkeep_counter_wrapper/simple_log_upkeep_counter_wrapper.go b/core/gethwrappers/generated/simple_log_upkeep_counter_wrapper/simple_log_upkeep_counter_wrapper.go new file mode 100644 index 00000000000..bb28c8bd6c4 --- /dev/null +++ b/core/gethwrappers/generated/simple_log_upkeep_counter_wrapper/simple_log_upkeep_counter_wrapper.go @@ -0,0 +1,504 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package simple_log_upkeep_counter_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/v2/core/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +type Log struct { + Index *big.Int + Timestamp *big.Int + TxHash [32]byte + BlockNumber *big.Int + BlockHash [32]byte + Source common.Address + Topics [][32]byte + Data []byte +} + +var SimpleLogUpkeepCounterMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"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\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timeToPerform\",\"type\":\"uint256\"}],\"name\":\"PerformingUpkeep\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"source\",\"type\":\"address\"},{\"internalType\":\"bytes32[]\",\"name\":\"topics\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"structLog\",\"name\":\"log\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"checkLog\",\"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\":\"initialBlock\",\"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\":[],\"name\":\"timeToPerform\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5060006001819055438155600281905560035561088a806100326000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c806361bc221a1161005b57806361bc221a146100d4578063806b984f146100dd578063917d895f146100e6578063c6066f0d146100ef57600080fd5b80632cb158641461008257806340691db41461009e5780634585e33b146100bf575b600080fd5b61008b60025481565b6040519081526020015b60405180910390f35b6100b16100ac366004610384565b6100f8565b60405161009592919061055e565b6100d26100cd366004610312565b61012a565b005b61008b60035481565b61008b60005481565b61008b60015481565b61008b60045481565b6000606060018460405160200161010f91906105db565b604051602081830303815290604052915091505b9250929050565b60025461013657436002555b436000556003546101489060016107f0565b6003556000805460015561015e828401846103f1565b90508060200151426101709190610808565b6004819055600254600054600154600354604080519485526020850193909352918301526060820152608081019190915232907f4874b8dd61a40fe23599b4360a9a824d7081742fca9f555bcee3d389c4f4bd659060a00160405180910390a2505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146101f957600080fd5b919050565b600082601f83011261020f57600080fd5b8135602067ffffffffffffffff82111561022b5761022b61084e565b8160051b61023a8282016106d6565b83815282810190868401838801850189101561025557600080fd5b600093505b8584101561027857803583526001939093019291840191840161025a565b50979650505050505050565b600082601f83011261029557600080fd5b813567ffffffffffffffff8111156102af576102af61084e565b6102e060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016106d6565b8181528460208386010111156102f557600080fd5b816020850160208301376000918101602001919091529392505050565b6000806020838503121561032557600080fd5b823567ffffffffffffffff8082111561033d57600080fd5b818501915085601f83011261035157600080fd5b81358181111561036057600080fd5b86602082850101111561037257600080fd5b60209290920196919550909350505050565b6000806040838503121561039757600080fd5b823567ffffffffffffffff808211156103af57600080fd5b9084019061010082870312156103c457600080fd5b909250602084013590808211156103da57600080fd5b506103e785828601610284565b9150509250929050565b60006020828403121561040357600080fd5b813567ffffffffffffffff8082111561041b57600080fd5b90830190610100828603121561043057600080fd5b6104386106ac565b823581526020830135602082015260408301356040820152606083013560608201526080830135608082015261047060a084016101d5565b60a082015260c08301358281111561048757600080fd5b610493878286016101fe565b60c08301525060e0830135828111156104ab57600080fd5b6104b787828601610284565b60e08301525095945050505050565b81835260007f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8311156104f857600080fd5b8260051b8083602087013760009401602001938452509192915050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b821515815260006020604081840152835180604085015260005b8181101561059457858101830151858201606001528201610578565b818111156105a6576000606083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201606001949350505050565b6020815281356020820152602082013560408201526040820135606082015260608201356080820152608082013560a082015273ffffffffffffffffffffffffffffffffffffffff61062f60a084016101d5565b1660c0820152600061064460c0840184610725565b6101008060e086015261065c610120860183856104c6565b925061066b60e087018761078c565b92507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe086850301828701526106a1848483610515565b979650505050505050565b604051610100810167ffffffffffffffff811182821017156106d0576106d061084e565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561071d5761071d61084e565b604052919050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261075a57600080fd5b830160208101925035905067ffffffffffffffff81111561077a57600080fd5b8060051b360383131561012357600080fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126107c157600080fd5b830160208101925035905067ffffffffffffffff8111156107e157600080fd5b80360383131561012357600080fd5b600082198211156108035761080361081f565b500190565b60008282101561081a5761081a61081f565b500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fdfea164736f6c6343000806000a", +} + +var SimpleLogUpkeepCounterABI = SimpleLogUpkeepCounterMetaData.ABI + +var SimpleLogUpkeepCounterBin = SimpleLogUpkeepCounterMetaData.Bin + +func DeploySimpleLogUpkeepCounter(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *SimpleLogUpkeepCounter, error) { + parsed, err := SimpleLogUpkeepCounterMetaData.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(SimpleLogUpkeepCounterBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &SimpleLogUpkeepCounter{address: address, abi: *parsed, SimpleLogUpkeepCounterCaller: SimpleLogUpkeepCounterCaller{contract: contract}, SimpleLogUpkeepCounterTransactor: SimpleLogUpkeepCounterTransactor{contract: contract}, SimpleLogUpkeepCounterFilterer: SimpleLogUpkeepCounterFilterer{contract: contract}}, nil +} + +type SimpleLogUpkeepCounter struct { + address common.Address + abi abi.ABI + SimpleLogUpkeepCounterCaller + SimpleLogUpkeepCounterTransactor + SimpleLogUpkeepCounterFilterer +} + +type SimpleLogUpkeepCounterCaller struct { + contract *bind.BoundContract +} + +type SimpleLogUpkeepCounterTransactor struct { + contract *bind.BoundContract +} + +type SimpleLogUpkeepCounterFilterer struct { + contract *bind.BoundContract +} + +type SimpleLogUpkeepCounterSession struct { + Contract *SimpleLogUpkeepCounter + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type SimpleLogUpkeepCounterCallerSession struct { + Contract *SimpleLogUpkeepCounterCaller + CallOpts bind.CallOpts +} + +type SimpleLogUpkeepCounterTransactorSession struct { + Contract *SimpleLogUpkeepCounterTransactor + TransactOpts bind.TransactOpts +} + +type SimpleLogUpkeepCounterRaw struct { + Contract *SimpleLogUpkeepCounter +} + +type SimpleLogUpkeepCounterCallerRaw struct { + Contract *SimpleLogUpkeepCounterCaller +} + +type SimpleLogUpkeepCounterTransactorRaw struct { + Contract *SimpleLogUpkeepCounterTransactor +} + +func NewSimpleLogUpkeepCounter(address common.Address, backend bind.ContractBackend) (*SimpleLogUpkeepCounter, error) { + abi, err := abi.JSON(strings.NewReader(SimpleLogUpkeepCounterABI)) + if err != nil { + return nil, err + } + contract, err := bindSimpleLogUpkeepCounter(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &SimpleLogUpkeepCounter{address: address, abi: abi, SimpleLogUpkeepCounterCaller: SimpleLogUpkeepCounterCaller{contract: contract}, SimpleLogUpkeepCounterTransactor: SimpleLogUpkeepCounterTransactor{contract: contract}, SimpleLogUpkeepCounterFilterer: SimpleLogUpkeepCounterFilterer{contract: contract}}, nil +} + +func NewSimpleLogUpkeepCounterCaller(address common.Address, caller bind.ContractCaller) (*SimpleLogUpkeepCounterCaller, error) { + contract, err := bindSimpleLogUpkeepCounter(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &SimpleLogUpkeepCounterCaller{contract: contract}, nil +} + +func NewSimpleLogUpkeepCounterTransactor(address common.Address, transactor bind.ContractTransactor) (*SimpleLogUpkeepCounterTransactor, error) { + contract, err := bindSimpleLogUpkeepCounter(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &SimpleLogUpkeepCounterTransactor{contract: contract}, nil +} + +func NewSimpleLogUpkeepCounterFilterer(address common.Address, filterer bind.ContractFilterer) (*SimpleLogUpkeepCounterFilterer, error) { + contract, err := bindSimpleLogUpkeepCounter(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &SimpleLogUpkeepCounterFilterer{contract: contract}, nil +} + +func bindSimpleLogUpkeepCounter(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := SimpleLogUpkeepCounterMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SimpleLogUpkeepCounter.Contract.SimpleLogUpkeepCounterCaller.contract.Call(opts, result, method, params...) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SimpleLogUpkeepCounter.Contract.SimpleLogUpkeepCounterTransactor.contract.Transfer(opts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SimpleLogUpkeepCounter.Contract.SimpleLogUpkeepCounterTransactor.contract.Transact(opts, method, params...) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SimpleLogUpkeepCounter.Contract.contract.Call(opts, result, method, params...) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SimpleLogUpkeepCounter.Contract.contract.Transfer(opts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SimpleLogUpkeepCounter.Contract.contract.Transact(opts, method, params...) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCaller) CheckLog(opts *bind.CallOpts, log Log, arg1 []byte) (bool, []byte, error) { + var out []interface{} + err := _SimpleLogUpkeepCounter.contract.Call(opts, &out, "checkLog", log, arg1) + + if err != nil { + return *new(bool), *new([]byte), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + out1 := *abi.ConvertType(out[1], new([]byte)).(*[]byte) + + return out0, out1, err + +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterSession) CheckLog(log Log, arg1 []byte) (bool, []byte, error) { + return _SimpleLogUpkeepCounter.Contract.CheckLog(&_SimpleLogUpkeepCounter.CallOpts, log, arg1) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCallerSession) CheckLog(log Log, arg1 []byte) (bool, []byte, error) { + return _SimpleLogUpkeepCounter.Contract.CheckLog(&_SimpleLogUpkeepCounter.CallOpts, log, arg1) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCaller) Counter(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SimpleLogUpkeepCounter.contract.Call(opts, &out, "counter") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterSession) Counter() (*big.Int, error) { + return _SimpleLogUpkeepCounter.Contract.Counter(&_SimpleLogUpkeepCounter.CallOpts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCallerSession) Counter() (*big.Int, error) { + return _SimpleLogUpkeepCounter.Contract.Counter(&_SimpleLogUpkeepCounter.CallOpts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCaller) InitialBlock(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SimpleLogUpkeepCounter.contract.Call(opts, &out, "initialBlock") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterSession) InitialBlock() (*big.Int, error) { + return _SimpleLogUpkeepCounter.Contract.InitialBlock(&_SimpleLogUpkeepCounter.CallOpts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCallerSession) InitialBlock() (*big.Int, error) { + return _SimpleLogUpkeepCounter.Contract.InitialBlock(&_SimpleLogUpkeepCounter.CallOpts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCaller) LastBlock(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SimpleLogUpkeepCounter.contract.Call(opts, &out, "lastBlock") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterSession) LastBlock() (*big.Int, error) { + return _SimpleLogUpkeepCounter.Contract.LastBlock(&_SimpleLogUpkeepCounter.CallOpts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCallerSession) LastBlock() (*big.Int, error) { + return _SimpleLogUpkeepCounter.Contract.LastBlock(&_SimpleLogUpkeepCounter.CallOpts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCaller) PreviousPerformBlock(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SimpleLogUpkeepCounter.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 (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterSession) PreviousPerformBlock() (*big.Int, error) { + return _SimpleLogUpkeepCounter.Contract.PreviousPerformBlock(&_SimpleLogUpkeepCounter.CallOpts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCallerSession) PreviousPerformBlock() (*big.Int, error) { + return _SimpleLogUpkeepCounter.Contract.PreviousPerformBlock(&_SimpleLogUpkeepCounter.CallOpts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCaller) TimeToPerform(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SimpleLogUpkeepCounter.contract.Call(opts, &out, "timeToPerform") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterSession) TimeToPerform() (*big.Int, error) { + return _SimpleLogUpkeepCounter.Contract.TimeToPerform(&_SimpleLogUpkeepCounter.CallOpts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterCallerSession) TimeToPerform() (*big.Int, error) { + return _SimpleLogUpkeepCounter.Contract.TimeToPerform(&_SimpleLogUpkeepCounter.CallOpts) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterTransactor) PerformUpkeep(opts *bind.TransactOpts, performData []byte) (*types.Transaction, error) { + return _SimpleLogUpkeepCounter.contract.Transact(opts, "performUpkeep", performData) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterSession) PerformUpkeep(performData []byte) (*types.Transaction, error) { + return _SimpleLogUpkeepCounter.Contract.PerformUpkeep(&_SimpleLogUpkeepCounter.TransactOpts, performData) +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterTransactorSession) PerformUpkeep(performData []byte) (*types.Transaction, error) { + return _SimpleLogUpkeepCounter.Contract.PerformUpkeep(&_SimpleLogUpkeepCounter.TransactOpts, performData) +} + +type SimpleLogUpkeepCounterPerformingUpkeepIterator struct { + Event *SimpleLogUpkeepCounterPerformingUpkeep + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *SimpleLogUpkeepCounterPerformingUpkeepIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(SimpleLogUpkeepCounterPerformingUpkeep) + 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(SimpleLogUpkeepCounterPerformingUpkeep) + 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 *SimpleLogUpkeepCounterPerformingUpkeepIterator) Error() error { + return it.fail +} + +func (it *SimpleLogUpkeepCounterPerformingUpkeepIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type SimpleLogUpkeepCounterPerformingUpkeep struct { + From common.Address + InitialBlock *big.Int + LastBlock *big.Int + PreviousBlock *big.Int + Counter *big.Int + TimeToPerform *big.Int + Raw types.Log +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterFilterer) FilterPerformingUpkeep(opts *bind.FilterOpts, from []common.Address) (*SimpleLogUpkeepCounterPerformingUpkeepIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _SimpleLogUpkeepCounter.contract.FilterLogs(opts, "PerformingUpkeep", fromRule) + if err != nil { + return nil, err + } + return &SimpleLogUpkeepCounterPerformingUpkeepIterator{contract: _SimpleLogUpkeepCounter.contract, event: "PerformingUpkeep", logs: logs, sub: sub}, nil +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterFilterer) WatchPerformingUpkeep(opts *bind.WatchOpts, sink chan<- *SimpleLogUpkeepCounterPerformingUpkeep, from []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _SimpleLogUpkeepCounter.contract.WatchLogs(opts, "PerformingUpkeep", 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(SimpleLogUpkeepCounterPerformingUpkeep) + if err := _SimpleLogUpkeepCounter.contract.UnpackLog(event, "PerformingUpkeep", 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 (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounterFilterer) ParsePerformingUpkeep(log types.Log) (*SimpleLogUpkeepCounterPerformingUpkeep, error) { + event := new(SimpleLogUpkeepCounterPerformingUpkeep) + if err := _SimpleLogUpkeepCounter.contract.UnpackLog(event, "PerformingUpkeep", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounter) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _SimpleLogUpkeepCounter.abi.Events["PerformingUpkeep"].ID: + return _SimpleLogUpkeepCounter.ParsePerformingUpkeep(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (SimpleLogUpkeepCounterPerformingUpkeep) Topic() common.Hash { + return common.HexToHash("0x4874b8dd61a40fe23599b4360a9a824d7081742fca9f555bcee3d389c4f4bd65") +} + +func (_SimpleLogUpkeepCounter *SimpleLogUpkeepCounter) Address() common.Address { + return _SimpleLogUpkeepCounter.address +} + +type SimpleLogUpkeepCounterInterface interface { + CheckLog(opts *bind.CallOpts, log Log, arg1 []byte) (bool, []byte, error) + + Counter(opts *bind.CallOpts) (*big.Int, error) + + InitialBlock(opts *bind.CallOpts) (*big.Int, error) + + LastBlock(opts *bind.CallOpts) (*big.Int, error) + + PreviousPerformBlock(opts *bind.CallOpts) (*big.Int, error) + + TimeToPerform(opts *bind.CallOpts) (*big.Int, error) + + PerformUpkeep(opts *bind.TransactOpts, performData []byte) (*types.Transaction, error) + + FilterPerformingUpkeep(opts *bind.FilterOpts, from []common.Address) (*SimpleLogUpkeepCounterPerformingUpkeepIterator, error) + + WatchPerformingUpkeep(opts *bind.WatchOpts, sink chan<- *SimpleLogUpkeepCounterPerformingUpkeep, from []common.Address) (event.Subscription, error) + + ParsePerformingUpkeep(log types.Log) (*SimpleLogUpkeepCounterPerformingUpkeep, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/generated/solidity_vrf_consumer_interface/solidity_vrf_consumer_interface.go b/core/gethwrappers/generated/solidity_vrf_consumer_interface/solidity_vrf_consumer_interface.go index de123d5ec7a..8c815be3a52 100644 --- a/core/gethwrappers/generated/solidity_vrf_consumer_interface/solidity_vrf_consumer_interface.go +++ b/core/gethwrappers/generated/solidity_vrf_consumer_interface/solidity_vrf_consumer_interface.go @@ -50,7 +50,7 @@ func DeployVRFConsumer(auth *bind.TransactOpts, backend bind.ContractBackend, _v if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFConsumer{VRFConsumerCaller: VRFConsumerCaller{contract: contract}, VRFConsumerTransactor: VRFConsumerTransactor{contract: contract}, VRFConsumerFilterer: VRFConsumerFilterer{contract: contract}}, nil + return address, tx, &VRFConsumer{address: address, abi: *parsed, VRFConsumerCaller: VRFConsumerCaller{contract: contract}, VRFConsumerTransactor: VRFConsumerTransactor{contract: contract}, VRFConsumerFilterer: VRFConsumerFilterer{contract: contract}}, nil } type VRFConsumer struct { diff --git a/core/gethwrappers/generated/solidity_vrf_consumer_interface_v08/solidity_vrf_consumer_interface_v08.go b/core/gethwrappers/generated/solidity_vrf_consumer_interface_v08/solidity_vrf_consumer_interface_v08.go index e122d3e07fd..b3267a17ce2 100644 --- a/core/gethwrappers/generated/solidity_vrf_consumer_interface_v08/solidity_vrf_consumer_interface_v08.go +++ b/core/gethwrappers/generated/solidity_vrf_consumer_interface_v08/solidity_vrf_consumer_interface_v08.go @@ -50,7 +50,7 @@ func DeployVRFConsumer(auth *bind.TransactOpts, backend bind.ContractBackend, vr if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFConsumer{VRFConsumerCaller: VRFConsumerCaller{contract: contract}, VRFConsumerTransactor: VRFConsumerTransactor{contract: contract}, VRFConsumerFilterer: VRFConsumerFilterer{contract: contract}}, nil + return address, tx, &VRFConsumer{address: address, abi: *parsed, VRFConsumerCaller: VRFConsumerCaller{contract: contract}, VRFConsumerTransactor: VRFConsumerTransactor{contract: contract}, VRFConsumerFilterer: VRFConsumerFilterer{contract: contract}}, nil } type VRFConsumer struct { diff --git a/core/gethwrappers/generated/solidity_vrf_coordinator_interface/solidity_vrf_coordinator_interface.go b/core/gethwrappers/generated/solidity_vrf_coordinator_interface/solidity_vrf_coordinator_interface.go index 36c715a2c08..7ac2210dc7d 100644 --- a/core/gethwrappers/generated/solidity_vrf_coordinator_interface/solidity_vrf_coordinator_interface.go +++ b/core/gethwrappers/generated/solidity_vrf_coordinator_interface/solidity_vrf_coordinator_interface.go @@ -52,7 +52,7 @@ func DeployVRFCoordinator(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFCoordinator{VRFCoordinatorCaller: VRFCoordinatorCaller{contract: contract}, VRFCoordinatorTransactor: VRFCoordinatorTransactor{contract: contract}, VRFCoordinatorFilterer: VRFCoordinatorFilterer{contract: contract}}, nil + return address, tx, &VRFCoordinator{address: address, abi: *parsed, VRFCoordinatorCaller: VRFCoordinatorCaller{contract: contract}, VRFCoordinatorTransactor: VRFCoordinatorTransactor{contract: contract}, VRFCoordinatorFilterer: VRFCoordinatorFilterer{contract: contract}}, nil } type VRFCoordinator struct { diff --git a/core/gethwrappers/generated/solidity_vrf_request_id/solidity_vrf_request_id.go b/core/gethwrappers/generated/solidity_vrf_request_id/solidity_vrf_request_id.go index fd35e245e41..b0d101a96c8 100644 --- a/core/gethwrappers/generated/solidity_vrf_request_id/solidity_vrf_request_id.go +++ b/core/gethwrappers/generated/solidity_vrf_request_id/solidity_vrf_request_id.go @@ -50,7 +50,7 @@ func DeployVRFRequestIDBaseTestHelper(auth *bind.TransactOpts, backend bind.Cont if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFRequestIDBaseTestHelper{VRFRequestIDBaseTestHelperCaller: VRFRequestIDBaseTestHelperCaller{contract: contract}, VRFRequestIDBaseTestHelperTransactor: VRFRequestIDBaseTestHelperTransactor{contract: contract}, VRFRequestIDBaseTestHelperFilterer: VRFRequestIDBaseTestHelperFilterer{contract: contract}}, nil + return address, tx, &VRFRequestIDBaseTestHelper{address: address, abi: *parsed, VRFRequestIDBaseTestHelperCaller: VRFRequestIDBaseTestHelperCaller{contract: contract}, VRFRequestIDBaseTestHelperTransactor: VRFRequestIDBaseTestHelperTransactor{contract: contract}, VRFRequestIDBaseTestHelperFilterer: VRFRequestIDBaseTestHelperFilterer{contract: contract}}, nil } type VRFRequestIDBaseTestHelper struct { diff --git a/core/gethwrappers/generated/solidity_vrf_request_id_v08/solidity_vrf_request_id_v08.go b/core/gethwrappers/generated/solidity_vrf_request_id_v08/solidity_vrf_request_id_v08.go index 181226bfa30..5d931e3d90b 100644 --- a/core/gethwrappers/generated/solidity_vrf_request_id_v08/solidity_vrf_request_id_v08.go +++ b/core/gethwrappers/generated/solidity_vrf_request_id_v08/solidity_vrf_request_id_v08.go @@ -50,7 +50,7 @@ func DeployVRFRequestIDBaseTestHelper(auth *bind.TransactOpts, backend bind.Cont if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFRequestIDBaseTestHelper{VRFRequestIDBaseTestHelperCaller: VRFRequestIDBaseTestHelperCaller{contract: contract}, VRFRequestIDBaseTestHelperTransactor: VRFRequestIDBaseTestHelperTransactor{contract: contract}, VRFRequestIDBaseTestHelperFilterer: VRFRequestIDBaseTestHelperFilterer{contract: contract}}, nil + return address, tx, &VRFRequestIDBaseTestHelper{address: address, abi: *parsed, VRFRequestIDBaseTestHelperCaller: VRFRequestIDBaseTestHelperCaller{contract: contract}, VRFRequestIDBaseTestHelperTransactor: VRFRequestIDBaseTestHelperTransactor{contract: contract}, VRFRequestIDBaseTestHelperFilterer: VRFRequestIDBaseTestHelperFilterer{contract: contract}}, nil } type VRFRequestIDBaseTestHelper struct { diff --git a/core/gethwrappers/generated/solidity_vrf_v08_verifier_wrapper/solidity_vrf_v08_verifier_wrapper.go b/core/gethwrappers/generated/solidity_vrf_v08_verifier_wrapper/solidity_vrf_v08_verifier_wrapper.go index 045c30e9359..6eaf2b996fa 100644 --- a/core/gethwrappers/generated/solidity_vrf_v08_verifier_wrapper/solidity_vrf_v08_verifier_wrapper.go +++ b/core/gethwrappers/generated/solidity_vrf_v08_verifier_wrapper/solidity_vrf_v08_verifier_wrapper.go @@ -62,7 +62,7 @@ func DeployVRFV08TestHelper(auth *bind.TransactOpts, backend bind.ContractBacken if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV08TestHelper{VRFV08TestHelperCaller: VRFV08TestHelperCaller{contract: contract}, VRFV08TestHelperTransactor: VRFV08TestHelperTransactor{contract: contract}, VRFV08TestHelperFilterer: VRFV08TestHelperFilterer{contract: contract}}, nil + return address, tx, &VRFV08TestHelper{address: address, abi: *parsed, VRFV08TestHelperCaller: VRFV08TestHelperCaller{contract: contract}, VRFV08TestHelperTransactor: VRFV08TestHelperTransactor{contract: contract}, VRFV08TestHelperFilterer: VRFV08TestHelperFilterer{contract: contract}}, nil } type VRFV08TestHelper struct { diff --git a/core/gethwrappers/generated/solidity_vrf_verifier_wrapper/solidity_vrf_verifier_wrapper.go b/core/gethwrappers/generated/solidity_vrf_verifier_wrapper/solidity_vrf_verifier_wrapper.go index a00768ee1bb..b46cc3de083 100644 --- a/core/gethwrappers/generated/solidity_vrf_verifier_wrapper/solidity_vrf_verifier_wrapper.go +++ b/core/gethwrappers/generated/solidity_vrf_verifier_wrapper/solidity_vrf_verifier_wrapper.go @@ -50,7 +50,7 @@ func DeployVRFTestHelper(auth *bind.TransactOpts, backend bind.ContractBackend) if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFTestHelper{VRFTestHelperCaller: VRFTestHelperCaller{contract: contract}, VRFTestHelperTransactor: VRFTestHelperTransactor{contract: contract}, VRFTestHelperFilterer: VRFTestHelperFilterer{contract: contract}}, nil + return address, tx, &VRFTestHelper{address: address, abi: *parsed, VRFTestHelperCaller: VRFTestHelperCaller{contract: contract}, VRFTestHelperTransactor: VRFTestHelperTransactor{contract: contract}, VRFTestHelperFilterer: VRFTestHelperFilterer{contract: contract}}, nil } type VRFTestHelper struct { diff --git a/core/gethwrappers/generated/solidity_vrf_wrapper/solidity_vrf_wrapper.go b/core/gethwrappers/generated/solidity_vrf_wrapper/solidity_vrf_wrapper.go index fb16ff3a8bc..82199d45389 100644 --- a/core/gethwrappers/generated/solidity_vrf_wrapper/solidity_vrf_wrapper.go +++ b/core/gethwrappers/generated/solidity_vrf_wrapper/solidity_vrf_wrapper.go @@ -50,7 +50,7 @@ func DeployVRF(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Ad if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRF{VRFCaller: VRFCaller{contract: contract}, VRFTransactor: VRFTransactor{contract: contract}, VRFFilterer: VRFFilterer{contract: contract}}, nil + return address, tx, &VRF{address: address, abi: *parsed, VRFCaller: VRFCaller{contract: contract}, VRFTransactor: VRFTransactor{contract: contract}, VRFFilterer: VRFFilterer{contract: contract}}, nil } type VRF struct { diff --git a/core/gethwrappers/generated/streams_lookup_upkeep_wrapper/streams_lookup_upkeep_wrapper.go b/core/gethwrappers/generated/streams_lookup_upkeep_wrapper/streams_lookup_upkeep_wrapper.go index 3332ac8215e..d54ee36f8ff 100644 --- a/core/gethwrappers/generated/streams_lookup_upkeep_wrapper/streams_lookup_upkeep_wrapper.go +++ b/core/gethwrappers/generated/streams_lookup_upkeep_wrapper/streams_lookup_upkeep_wrapper.go @@ -52,7 +52,7 @@ func DeployStreamsLookupUpkeep(auth *bind.TransactOpts, backend bind.ContractBac if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &StreamsLookupUpkeep{StreamsLookupUpkeepCaller: StreamsLookupUpkeepCaller{contract: contract}, StreamsLookupUpkeepTransactor: StreamsLookupUpkeepTransactor{contract: contract}, StreamsLookupUpkeepFilterer: StreamsLookupUpkeepFilterer{contract: contract}}, nil + return address, tx, &StreamsLookupUpkeep{address: address, abi: *parsed, StreamsLookupUpkeepCaller: StreamsLookupUpkeepCaller{contract: contract}, StreamsLookupUpkeepTransactor: StreamsLookupUpkeepTransactor{contract: contract}, StreamsLookupUpkeepFilterer: StreamsLookupUpkeepFilterer{contract: contract}}, nil } type StreamsLookupUpkeep struct { diff --git a/core/gethwrappers/generated/test_api_consumer_wrapper/test_api_consumer_wrapper.go b/core/gethwrappers/generated/test_api_consumer_wrapper/test_api_consumer_wrapper.go index 8503d6f75e9..71b26af95df 100644 --- a/core/gethwrappers/generated/test_api_consumer_wrapper/test_api_consumer_wrapper.go +++ b/core/gethwrappers/generated/test_api_consumer_wrapper/test_api_consumer_wrapper.go @@ -52,7 +52,7 @@ func DeployTestAPIConsumer(auth *bind.TransactOpts, backend bind.ContractBackend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &TestAPIConsumer{TestAPIConsumerCaller: TestAPIConsumerCaller{contract: contract}, TestAPIConsumerTransactor: TestAPIConsumerTransactor{contract: contract}, TestAPIConsumerFilterer: TestAPIConsumerFilterer{contract: contract}}, nil + return address, tx, &TestAPIConsumer{address: address, abi: *parsed, TestAPIConsumerCaller: TestAPIConsumerCaller{contract: contract}, TestAPIConsumerTransactor: TestAPIConsumerTransactor{contract: contract}, TestAPIConsumerFilterer: TestAPIConsumerFilterer{contract: contract}}, nil } type TestAPIConsumer struct { diff --git a/core/gethwrappers/generated/trusted_blockhash_store/trusted_blockhash_store.go b/core/gethwrappers/generated/trusted_blockhash_store/trusted_blockhash_store.go index 43ae1a3f5b4..c571b0a7002 100644 --- a/core/gethwrappers/generated/trusted_blockhash_store/trusted_blockhash_store.go +++ b/core/gethwrappers/generated/trusted_blockhash_store/trusted_blockhash_store.go @@ -52,7 +52,7 @@ func DeployTrustedBlockhashStore(auth *bind.TransactOpts, backend bind.ContractB if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &TrustedBlockhashStore{TrustedBlockhashStoreCaller: TrustedBlockhashStoreCaller{contract: contract}, TrustedBlockhashStoreTransactor: TrustedBlockhashStoreTransactor{contract: contract}, TrustedBlockhashStoreFilterer: TrustedBlockhashStoreFilterer{contract: contract}}, nil + return address, tx, &TrustedBlockhashStore{address: address, abi: *parsed, TrustedBlockhashStoreCaller: TrustedBlockhashStoreCaller{contract: contract}, TrustedBlockhashStoreTransactor: TrustedBlockhashStoreTransactor{contract: contract}, TrustedBlockhashStoreFilterer: TrustedBlockhashStoreFilterer{contract: contract}}, nil } type TrustedBlockhashStore struct { diff --git a/core/gethwrappers/generated/upkeep_counter_wrapper/upkeep_counter_wrapper.go b/core/gethwrappers/generated/upkeep_counter_wrapper/upkeep_counter_wrapper.go index 4f9fe79d77c..13db591730e 100644 --- a/core/gethwrappers/generated/upkeep_counter_wrapper/upkeep_counter_wrapper.go +++ b/core/gethwrappers/generated/upkeep_counter_wrapper/upkeep_counter_wrapper.go @@ -52,7 +52,7 @@ func DeployUpkeepCounter(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &UpkeepCounter{UpkeepCounterCaller: UpkeepCounterCaller{contract: contract}, UpkeepCounterTransactor: UpkeepCounterTransactor{contract: contract}, UpkeepCounterFilterer: UpkeepCounterFilterer{contract: contract}}, nil + return address, tx, &UpkeepCounter{address: address, abi: *parsed, UpkeepCounterCaller: UpkeepCounterCaller{contract: contract}, UpkeepCounterTransactor: UpkeepCounterTransactor{contract: contract}, UpkeepCounterFilterer: UpkeepCounterFilterer{contract: contract}}, nil } type UpkeepCounter struct { diff --git a/core/gethwrappers/generated/upkeep_perform_counter_restrictive_wrapper/upkeep_perform_counter_restrictive_wrapper.go b/core/gethwrappers/generated/upkeep_perform_counter_restrictive_wrapper/upkeep_perform_counter_restrictive_wrapper.go index e6770fd074f..e004ab61434 100644 --- a/core/gethwrappers/generated/upkeep_perform_counter_restrictive_wrapper/upkeep_perform_counter_restrictive_wrapper.go +++ b/core/gethwrappers/generated/upkeep_perform_counter_restrictive_wrapper/upkeep_perform_counter_restrictive_wrapper.go @@ -52,7 +52,7 @@ func DeployUpkeepPerformCounterRestrictive(auth *bind.TransactOpts, backend bind if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &UpkeepPerformCounterRestrictive{UpkeepPerformCounterRestrictiveCaller: UpkeepPerformCounterRestrictiveCaller{contract: contract}, UpkeepPerformCounterRestrictiveTransactor: UpkeepPerformCounterRestrictiveTransactor{contract: contract}, UpkeepPerformCounterRestrictiveFilterer: UpkeepPerformCounterRestrictiveFilterer{contract: contract}}, nil + return address, tx, &UpkeepPerformCounterRestrictive{address: address, abi: *parsed, UpkeepPerformCounterRestrictiveCaller: UpkeepPerformCounterRestrictiveCaller{contract: contract}, UpkeepPerformCounterRestrictiveTransactor: UpkeepPerformCounterRestrictiveTransactor{contract: contract}, UpkeepPerformCounterRestrictiveFilterer: UpkeepPerformCounterRestrictiveFilterer{contract: contract}}, nil } type UpkeepPerformCounterRestrictive struct { diff --git a/core/gethwrappers/generated/upkeep_transcoder/upkeep_transcoder.go b/core/gethwrappers/generated/upkeep_transcoder/upkeep_transcoder.go index a439dddd40f..53d557d79af 100644 --- a/core/gethwrappers/generated/upkeep_transcoder/upkeep_transcoder.go +++ b/core/gethwrappers/generated/upkeep_transcoder/upkeep_transcoder.go @@ -50,7 +50,7 @@ func DeployUpkeepTranscoder(auth *bind.TransactOpts, backend bind.ContractBacken if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &UpkeepTranscoder{UpkeepTranscoderCaller: UpkeepTranscoderCaller{contract: contract}, UpkeepTranscoderTransactor: UpkeepTranscoderTransactor{contract: contract}, UpkeepTranscoderFilterer: UpkeepTranscoderFilterer{contract: contract}}, nil + return address, tx, &UpkeepTranscoder{address: address, abi: *parsed, UpkeepTranscoderCaller: UpkeepTranscoderCaller{contract: contract}, UpkeepTranscoderTransactor: UpkeepTranscoderTransactor{contract: contract}, UpkeepTranscoderFilterer: UpkeepTranscoderFilterer{contract: contract}}, nil } type UpkeepTranscoder struct { diff --git a/core/gethwrappers/generated/verifiable_load_log_trigger_upkeep_wrapper/verifiable_load_log_trigger_upkeep_wrapper.go b/core/gethwrappers/generated/verifiable_load_log_trigger_upkeep_wrapper/verifiable_load_log_trigger_upkeep_wrapper.go index 52e241965f8..9648c4bd719 100644 --- a/core/gethwrappers/generated/verifiable_load_log_trigger_upkeep_wrapper/verifiable_load_log_trigger_upkeep_wrapper.go +++ b/core/gethwrappers/generated/verifiable_load_log_trigger_upkeep_wrapper/verifiable_load_log_trigger_upkeep_wrapper.go @@ -76,7 +76,7 @@ func DeployVerifiableLoadLogTriggerUpkeep(auth *bind.TransactOpts, backend bind. if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VerifiableLoadLogTriggerUpkeep{VerifiableLoadLogTriggerUpkeepCaller: VerifiableLoadLogTriggerUpkeepCaller{contract: contract}, VerifiableLoadLogTriggerUpkeepTransactor: VerifiableLoadLogTriggerUpkeepTransactor{contract: contract}, VerifiableLoadLogTriggerUpkeepFilterer: VerifiableLoadLogTriggerUpkeepFilterer{contract: contract}}, nil + return address, tx, &VerifiableLoadLogTriggerUpkeep{address: address, abi: *parsed, VerifiableLoadLogTriggerUpkeepCaller: VerifiableLoadLogTriggerUpkeepCaller{contract: contract}, VerifiableLoadLogTriggerUpkeepTransactor: VerifiableLoadLogTriggerUpkeepTransactor{contract: contract}, VerifiableLoadLogTriggerUpkeepFilterer: VerifiableLoadLogTriggerUpkeepFilterer{contract: contract}}, nil } type VerifiableLoadLogTriggerUpkeep struct { diff --git a/core/gethwrappers/generated/verifiable_load_streams_lookup_upkeep_wrapper/verifiable_load_streams_lookup_upkeep_wrapper.go b/core/gethwrappers/generated/verifiable_load_streams_lookup_upkeep_wrapper/verifiable_load_streams_lookup_upkeep_wrapper.go index e8a10d85dd1..fc39ffb366b 100644 --- a/core/gethwrappers/generated/verifiable_load_streams_lookup_upkeep_wrapper/verifiable_load_streams_lookup_upkeep_wrapper.go +++ b/core/gethwrappers/generated/verifiable_load_streams_lookup_upkeep_wrapper/verifiable_load_streams_lookup_upkeep_wrapper.go @@ -65,7 +65,7 @@ func DeployVerifiableLoadStreamsLookupUpkeep(auth *bind.TransactOpts, backend bi if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VerifiableLoadStreamsLookupUpkeep{VerifiableLoadStreamsLookupUpkeepCaller: VerifiableLoadStreamsLookupUpkeepCaller{contract: contract}, VerifiableLoadStreamsLookupUpkeepTransactor: VerifiableLoadStreamsLookupUpkeepTransactor{contract: contract}, VerifiableLoadStreamsLookupUpkeepFilterer: VerifiableLoadStreamsLookupUpkeepFilterer{contract: contract}}, nil + return address, tx, &VerifiableLoadStreamsLookupUpkeep{address: address, abi: *parsed, VerifiableLoadStreamsLookupUpkeepCaller: VerifiableLoadStreamsLookupUpkeepCaller{contract: contract}, VerifiableLoadStreamsLookupUpkeepTransactor: VerifiableLoadStreamsLookupUpkeepTransactor{contract: contract}, VerifiableLoadStreamsLookupUpkeepFilterer: VerifiableLoadStreamsLookupUpkeepFilterer{contract: contract}}, nil } type VerifiableLoadStreamsLookupUpkeep struct { diff --git a/core/gethwrappers/generated/verifiable_load_upkeep_wrapper/verifiable_load_upkeep_wrapper.go b/core/gethwrappers/generated/verifiable_load_upkeep_wrapper/verifiable_load_upkeep_wrapper.go index 6dc8e73f1fd..b95f311f1cc 100644 --- a/core/gethwrappers/generated/verifiable_load_upkeep_wrapper/verifiable_load_upkeep_wrapper.go +++ b/core/gethwrappers/generated/verifiable_load_upkeep_wrapper/verifiable_load_upkeep_wrapper.go @@ -65,7 +65,7 @@ func DeployVerifiableLoadUpkeep(auth *bind.TransactOpts, backend bind.ContractBa if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VerifiableLoadUpkeep{VerifiableLoadUpkeepCaller: VerifiableLoadUpkeepCaller{contract: contract}, VerifiableLoadUpkeepTransactor: VerifiableLoadUpkeepTransactor{contract: contract}, VerifiableLoadUpkeepFilterer: VerifiableLoadUpkeepFilterer{contract: contract}}, nil + return address, tx, &VerifiableLoadUpkeep{address: address, abi: *parsed, VerifiableLoadUpkeepCaller: VerifiableLoadUpkeepCaller{contract: contract}, VerifiableLoadUpkeepTransactor: VerifiableLoadUpkeepTransactor{contract: contract}, VerifiableLoadUpkeepFilterer: VerifiableLoadUpkeepFilterer{contract: contract}}, nil } type VerifiableLoadUpkeep struct { diff --git a/core/gethwrappers/generated/vrf_consumer_v2/vrf_consumer_v2.go b/core/gethwrappers/generated/vrf_consumer_v2/vrf_consumer_v2.go index 8317e4c5b3e..16090150fcc 100644 --- a/core/gethwrappers/generated/vrf_consumer_v2/vrf_consumer_v2.go +++ b/core/gethwrappers/generated/vrf_consumer_v2/vrf_consumer_v2.go @@ -50,7 +50,7 @@ func DeployVRFConsumerV2(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFConsumerV2{VRFConsumerV2Caller: VRFConsumerV2Caller{contract: contract}, VRFConsumerV2Transactor: VRFConsumerV2Transactor{contract: contract}, VRFConsumerV2Filterer: VRFConsumerV2Filterer{contract: contract}}, nil + return address, tx, &VRFConsumerV2{address: address, abi: *parsed, VRFConsumerV2Caller: VRFConsumerV2Caller{contract: contract}, VRFConsumerV2Transactor: VRFConsumerV2Transactor{contract: contract}, VRFConsumerV2Filterer: VRFConsumerV2Filterer{contract: contract}}, nil } type VRFConsumerV2 struct { diff --git a/core/gethwrappers/generated/vrf_consumer_v2_plus_upgradeable_example/vrf_consumer_v2_plus_upgradeable_example.go b/core/gethwrappers/generated/vrf_consumer_v2_plus_upgradeable_example/vrf_consumer_v2_plus_upgradeable_example.go index fce0876ba3b..deb678c4ebb 100644 --- a/core/gethwrappers/generated/vrf_consumer_v2_plus_upgradeable_example/vrf_consumer_v2_plus_upgradeable_example.go +++ b/core/gethwrappers/generated/vrf_consumer_v2_plus_upgradeable_example/vrf_consumer_v2_plus_upgradeable_example.go @@ -52,7 +52,7 @@ func DeployVRFConsumerV2PlusUpgradeableExample(auth *bind.TransactOpts, backend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFConsumerV2PlusUpgradeableExample{VRFConsumerV2PlusUpgradeableExampleCaller: VRFConsumerV2PlusUpgradeableExampleCaller{contract: contract}, VRFConsumerV2PlusUpgradeableExampleTransactor: VRFConsumerV2PlusUpgradeableExampleTransactor{contract: contract}, VRFConsumerV2PlusUpgradeableExampleFilterer: VRFConsumerV2PlusUpgradeableExampleFilterer{contract: contract}}, nil + return address, tx, &VRFConsumerV2PlusUpgradeableExample{address: address, abi: *parsed, VRFConsumerV2PlusUpgradeableExampleCaller: VRFConsumerV2PlusUpgradeableExampleCaller{contract: contract}, VRFConsumerV2PlusUpgradeableExampleTransactor: VRFConsumerV2PlusUpgradeableExampleTransactor{contract: contract}, VRFConsumerV2PlusUpgradeableExampleFilterer: VRFConsumerV2PlusUpgradeableExampleFilterer{contract: contract}}, nil } type VRFConsumerV2PlusUpgradeableExample struct { diff --git a/core/gethwrappers/generated/vrf_consumer_v2_upgradeable_example/vrf_consumer_v2_upgradeable_example.go b/core/gethwrappers/generated/vrf_consumer_v2_upgradeable_example/vrf_consumer_v2_upgradeable_example.go index 72bdbce58a3..5499868e323 100644 --- a/core/gethwrappers/generated/vrf_consumer_v2_upgradeable_example/vrf_consumer_v2_upgradeable_example.go +++ b/core/gethwrappers/generated/vrf_consumer_v2_upgradeable_example/vrf_consumer_v2_upgradeable_example.go @@ -52,7 +52,7 @@ func DeployVRFConsumerV2UpgradeableExample(auth *bind.TransactOpts, backend bind if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFConsumerV2UpgradeableExample{VRFConsumerV2UpgradeableExampleCaller: VRFConsumerV2UpgradeableExampleCaller{contract: contract}, VRFConsumerV2UpgradeableExampleTransactor: VRFConsumerV2UpgradeableExampleTransactor{contract: contract}, VRFConsumerV2UpgradeableExampleFilterer: VRFConsumerV2UpgradeableExampleFilterer{contract: contract}}, nil + return address, tx, &VRFConsumerV2UpgradeableExample{address: address, abi: *parsed, VRFConsumerV2UpgradeableExampleCaller: VRFConsumerV2UpgradeableExampleCaller{contract: contract}, VRFConsumerV2UpgradeableExampleTransactor: VRFConsumerV2UpgradeableExampleTransactor{contract: contract}, VRFConsumerV2UpgradeableExampleFilterer: VRFConsumerV2UpgradeableExampleFilterer{contract: contract}}, nil } type VRFConsumerV2UpgradeableExample struct { diff --git a/core/gethwrappers/generated/vrf_coordinator_mock/vrf_coordinator_mock.go b/core/gethwrappers/generated/vrf_coordinator_mock/vrf_coordinator_mock.go index 35fbabc8e29..961aa7b30ca 100644 --- a/core/gethwrappers/generated/vrf_coordinator_mock/vrf_coordinator_mock.go +++ b/core/gethwrappers/generated/vrf_coordinator_mock/vrf_coordinator_mock.go @@ -52,7 +52,7 @@ func DeployVRFCoordinatorMock(auth *bind.TransactOpts, backend bind.ContractBack if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFCoordinatorMock{VRFCoordinatorMockCaller: VRFCoordinatorMockCaller{contract: contract}, VRFCoordinatorMockTransactor: VRFCoordinatorMockTransactor{contract: contract}, VRFCoordinatorMockFilterer: VRFCoordinatorMockFilterer{contract: contract}}, nil + return address, tx, &VRFCoordinatorMock{address: address, abi: *parsed, VRFCoordinatorMockCaller: VRFCoordinatorMockCaller{contract: contract}, VRFCoordinatorMockTransactor: VRFCoordinatorMockTransactor{contract: contract}, VRFCoordinatorMockFilterer: VRFCoordinatorMockFilterer{contract: contract}}, nil } type VRFCoordinatorMock struct { diff --git a/core/gethwrappers/generated/vrf_coordinator_v2/vrf_coordinator_v2.go b/core/gethwrappers/generated/vrf_coordinator_v2/vrf_coordinator_v2.go index 1f9ef1eda20..ffe32f78101 100644 --- a/core/gethwrappers/generated/vrf_coordinator_v2/vrf_coordinator_v2.go +++ b/core/gethwrappers/generated/vrf_coordinator_v2/vrf_coordinator_v2.go @@ -84,7 +84,7 @@ func DeployVRFCoordinatorV2(auth *bind.TransactOpts, backend bind.ContractBacken if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFCoordinatorV2{VRFCoordinatorV2Caller: VRFCoordinatorV2Caller{contract: contract}, VRFCoordinatorV2Transactor: VRFCoordinatorV2Transactor{contract: contract}, VRFCoordinatorV2Filterer: VRFCoordinatorV2Filterer{contract: contract}}, nil + return address, tx, &VRFCoordinatorV2{address: address, abi: *parsed, VRFCoordinatorV2Caller: VRFCoordinatorV2Caller{contract: contract}, VRFCoordinatorV2Transactor: VRFCoordinatorV2Transactor{contract: contract}, VRFCoordinatorV2Filterer: VRFCoordinatorV2Filterer{contract: contract}}, nil } type VRFCoordinatorV2 struct { diff --git a/core/gethwrappers/generated/vrf_coordinator_v2_5/vrf_coordinator_v2_5.go b/core/gethwrappers/generated/vrf_coordinator_v2_5/vrf_coordinator_v2_5.go index e09c7c46e29..62e8b9f0de3 100644 --- a/core/gethwrappers/generated/vrf_coordinator_v2_5/vrf_coordinator_v2_5.go +++ b/core/gethwrappers/generated/vrf_coordinator_v2_5/vrf_coordinator_v2_5.go @@ -87,7 +87,7 @@ func DeployVRFCoordinatorV25(auth *bind.TransactOpts, backend bind.ContractBacke if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFCoordinatorV25{VRFCoordinatorV25Caller: VRFCoordinatorV25Caller{contract: contract}, VRFCoordinatorV25Transactor: VRFCoordinatorV25Transactor{contract: contract}, VRFCoordinatorV25Filterer: VRFCoordinatorV25Filterer{contract: contract}}, nil + return address, tx, &VRFCoordinatorV25{address: address, abi: *parsed, VRFCoordinatorV25Caller: VRFCoordinatorV25Caller{contract: contract}, VRFCoordinatorV25Transactor: VRFCoordinatorV25Transactor{contract: contract}, VRFCoordinatorV25Filterer: VRFCoordinatorV25Filterer{contract: contract}}, nil } type VRFCoordinatorV25 struct { diff --git a/core/gethwrappers/generated/vrf_coordinator_v2_plus_v2_example/vrf_coordinator_v2_plus_v2_example.go b/core/gethwrappers/generated/vrf_coordinator_v2_plus_v2_example/vrf_coordinator_v2_plus_v2_example.go index dd7865fe8aa..3d9efea9f73 100644 --- a/core/gethwrappers/generated/vrf_coordinator_v2_plus_v2_example/vrf_coordinator_v2_plus_v2_example.go +++ b/core/gethwrappers/generated/vrf_coordinator_v2_plus_v2_example/vrf_coordinator_v2_plus_v2_example.go @@ -59,7 +59,7 @@ func DeployVRFCoordinatorV2PlusV2Example(auth *bind.TransactOpts, backend bind.C if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFCoordinatorV2PlusV2Example{VRFCoordinatorV2PlusV2ExampleCaller: VRFCoordinatorV2PlusV2ExampleCaller{contract: contract}, VRFCoordinatorV2PlusV2ExampleTransactor: VRFCoordinatorV2PlusV2ExampleTransactor{contract: contract}, VRFCoordinatorV2PlusV2ExampleFilterer: VRFCoordinatorV2PlusV2ExampleFilterer{contract: contract}}, nil + return address, tx, &VRFCoordinatorV2PlusV2Example{address: address, abi: *parsed, VRFCoordinatorV2PlusV2ExampleCaller: VRFCoordinatorV2PlusV2ExampleCaller{contract: contract}, VRFCoordinatorV2PlusV2ExampleTransactor: VRFCoordinatorV2PlusV2ExampleTransactor{contract: contract}, VRFCoordinatorV2PlusV2ExampleFilterer: VRFCoordinatorV2PlusV2ExampleFilterer{contract: contract}}, nil } type VRFCoordinatorV2PlusV2Example struct { diff --git a/core/gethwrappers/generated/vrf_external_sub_owner_example/vrf_external_sub_owner_example.go b/core/gethwrappers/generated/vrf_external_sub_owner_example/vrf_external_sub_owner_example.go index 8752e2f39cc..4ab3cdf591d 100644 --- a/core/gethwrappers/generated/vrf_external_sub_owner_example/vrf_external_sub_owner_example.go +++ b/core/gethwrappers/generated/vrf_external_sub_owner_example/vrf_external_sub_owner_example.go @@ -50,7 +50,7 @@ func DeployVRFExternalSubOwnerExample(auth *bind.TransactOpts, backend bind.Cont if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFExternalSubOwnerExample{VRFExternalSubOwnerExampleCaller: VRFExternalSubOwnerExampleCaller{contract: contract}, VRFExternalSubOwnerExampleTransactor: VRFExternalSubOwnerExampleTransactor{contract: contract}, VRFExternalSubOwnerExampleFilterer: VRFExternalSubOwnerExampleFilterer{contract: contract}}, nil + return address, tx, &VRFExternalSubOwnerExample{address: address, abi: *parsed, VRFExternalSubOwnerExampleCaller: VRFExternalSubOwnerExampleCaller{contract: contract}, VRFExternalSubOwnerExampleTransactor: VRFExternalSubOwnerExampleTransactor{contract: contract}, VRFExternalSubOwnerExampleFilterer: VRFExternalSubOwnerExampleFilterer{contract: contract}}, nil } type VRFExternalSubOwnerExample struct { diff --git a/core/gethwrappers/generated/vrf_load_test_external_sub_owner/vrf_load_test_external_sub_owner.go b/core/gethwrappers/generated/vrf_load_test_external_sub_owner/vrf_load_test_external_sub_owner.go index ea3302d2e73..e98239eb4e2 100644 --- a/core/gethwrappers/generated/vrf_load_test_external_sub_owner/vrf_load_test_external_sub_owner.go +++ b/core/gethwrappers/generated/vrf_load_test_external_sub_owner/vrf_load_test_external_sub_owner.go @@ -52,7 +52,7 @@ func DeployVRFLoadTestExternalSubOwner(auth *bind.TransactOpts, backend bind.Con 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 + return address, tx, &VRFLoadTestExternalSubOwner{address: address, abi: *parsed, VRFLoadTestExternalSubOwnerCaller: VRFLoadTestExternalSubOwnerCaller{contract: contract}, VRFLoadTestExternalSubOwnerTransactor: VRFLoadTestExternalSubOwnerTransactor{contract: contract}, VRFLoadTestExternalSubOwnerFilterer: VRFLoadTestExternalSubOwnerFilterer{contract: contract}}, nil } type VRFLoadTestExternalSubOwner struct { diff --git a/core/gethwrappers/generated/vrf_load_test_ownerless_consumer/vrf_load_test_ownerless_consumer.go b/core/gethwrappers/generated/vrf_load_test_ownerless_consumer/vrf_load_test_ownerless_consumer.go index 6beadf5ad52..fea4360e502 100644 --- a/core/gethwrappers/generated/vrf_load_test_ownerless_consumer/vrf_load_test_ownerless_consumer.go +++ b/core/gethwrappers/generated/vrf_load_test_ownerless_consumer/vrf_load_test_ownerless_consumer.go @@ -50,7 +50,7 @@ func DeployVRFLoadTestOwnerlessConsumer(auth *bind.TransactOpts, backend bind.Co 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 + return address, tx, &VRFLoadTestOwnerlessConsumer{address: address, abi: *parsed, VRFLoadTestOwnerlessConsumerCaller: VRFLoadTestOwnerlessConsumerCaller{contract: contract}, VRFLoadTestOwnerlessConsumerTransactor: VRFLoadTestOwnerlessConsumerTransactor{contract: contract}, VRFLoadTestOwnerlessConsumerFilterer: VRFLoadTestOwnerlessConsumerFilterer{contract: contract}}, nil } type VRFLoadTestOwnerlessConsumer struct { diff --git a/core/gethwrappers/generated/vrf_load_test_with_metrics/vrf_load_test_with_metrics.go b/core/gethwrappers/generated/vrf_load_test_with_metrics/vrf_load_test_with_metrics.go index 93d50b72dd5..76b5e267d3b 100644 --- a/core/gethwrappers/generated/vrf_load_test_with_metrics/vrf_load_test_with_metrics.go +++ b/core/gethwrappers/generated/vrf_load_test_with_metrics/vrf_load_test_with_metrics.go @@ -52,7 +52,7 @@ func DeployVRFV2LoadTestWithMetrics(auth *bind.TransactOpts, backend bind.Contra if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2LoadTestWithMetrics{VRFV2LoadTestWithMetricsCaller: VRFV2LoadTestWithMetricsCaller{contract: contract}, VRFV2LoadTestWithMetricsTransactor: VRFV2LoadTestWithMetricsTransactor{contract: contract}, VRFV2LoadTestWithMetricsFilterer: VRFV2LoadTestWithMetricsFilterer{contract: contract}}, nil + return address, tx, &VRFV2LoadTestWithMetrics{address: address, abi: *parsed, VRFV2LoadTestWithMetricsCaller: VRFV2LoadTestWithMetricsCaller{contract: contract}, VRFV2LoadTestWithMetricsTransactor: VRFV2LoadTestWithMetricsTransactor{contract: contract}, VRFV2LoadTestWithMetricsFilterer: VRFV2LoadTestWithMetricsFilterer{contract: contract}}, nil } type VRFV2LoadTestWithMetrics struct { diff --git a/core/gethwrappers/generated/vrf_malicious_consumer_v2/vrf_malicious_consumer_v2.go b/core/gethwrappers/generated/vrf_malicious_consumer_v2/vrf_malicious_consumer_v2.go index 5fb16e00e8b..1fe5e61f3ab 100644 --- a/core/gethwrappers/generated/vrf_malicious_consumer_v2/vrf_malicious_consumer_v2.go +++ b/core/gethwrappers/generated/vrf_malicious_consumer_v2/vrf_malicious_consumer_v2.go @@ -50,7 +50,7 @@ func DeployVRFMaliciousConsumerV2(auth *bind.TransactOpts, backend bind.Contract if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFMaliciousConsumerV2{VRFMaliciousConsumerV2Caller: VRFMaliciousConsumerV2Caller{contract: contract}, VRFMaliciousConsumerV2Transactor: VRFMaliciousConsumerV2Transactor{contract: contract}, VRFMaliciousConsumerV2Filterer: VRFMaliciousConsumerV2Filterer{contract: contract}}, nil + return address, tx, &VRFMaliciousConsumerV2{address: address, abi: *parsed, VRFMaliciousConsumerV2Caller: VRFMaliciousConsumerV2Caller{contract: contract}, VRFMaliciousConsumerV2Transactor: VRFMaliciousConsumerV2Transactor{contract: contract}, VRFMaliciousConsumerV2Filterer: VRFMaliciousConsumerV2Filterer{contract: contract}}, nil } type VRFMaliciousConsumerV2 struct { diff --git a/core/gethwrappers/generated/vrf_malicious_consumer_v2_plus/vrf_malicious_consumer_v2_plus.go b/core/gethwrappers/generated/vrf_malicious_consumer_v2_plus/vrf_malicious_consumer_v2_plus.go index 64a5cace7f5..df5a49a8de4 100644 --- a/core/gethwrappers/generated/vrf_malicious_consumer_v2_plus/vrf_malicious_consumer_v2_plus.go +++ b/core/gethwrappers/generated/vrf_malicious_consumer_v2_plus/vrf_malicious_consumer_v2_plus.go @@ -52,7 +52,7 @@ func DeployVRFMaliciousConsumerV2Plus(auth *bind.TransactOpts, backend bind.Cont if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFMaliciousConsumerV2Plus{VRFMaliciousConsumerV2PlusCaller: VRFMaliciousConsumerV2PlusCaller{contract: contract}, VRFMaliciousConsumerV2PlusTransactor: VRFMaliciousConsumerV2PlusTransactor{contract: contract}, VRFMaliciousConsumerV2PlusFilterer: VRFMaliciousConsumerV2PlusFilterer{contract: contract}}, nil + return address, tx, &VRFMaliciousConsumerV2Plus{address: address, abi: *parsed, VRFMaliciousConsumerV2PlusCaller: VRFMaliciousConsumerV2PlusCaller{contract: contract}, VRFMaliciousConsumerV2PlusTransactor: VRFMaliciousConsumerV2PlusTransactor{contract: contract}, VRFMaliciousConsumerV2PlusFilterer: VRFMaliciousConsumerV2PlusFilterer{contract: contract}}, nil } type VRFMaliciousConsumerV2Plus struct { diff --git a/core/gethwrappers/generated/vrf_owner/vrf_owner.go b/core/gethwrappers/generated/vrf_owner/vrf_owner.go index 8a97fcf5aa6..b029bd393f6 100644 --- a/core/gethwrappers/generated/vrf_owner/vrf_owner.go +++ b/core/gethwrappers/generated/vrf_owner/vrf_owner.go @@ -84,7 +84,7 @@ func DeployVRFOwner(auth *bind.TransactOpts, backend bind.ContractBackend, _vrfC if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFOwner{VRFOwnerCaller: VRFOwnerCaller{contract: contract}, VRFOwnerTransactor: VRFOwnerTransactor{contract: contract}, VRFOwnerFilterer: VRFOwnerFilterer{contract: contract}}, nil + return address, tx, &VRFOwner{address: address, abi: *parsed, VRFOwnerCaller: VRFOwnerCaller{contract: contract}, VRFOwnerTransactor: VRFOwnerTransactor{contract: contract}, VRFOwnerFilterer: VRFOwnerFilterer{contract: contract}}, nil } type VRFOwner struct { diff --git a/core/gethwrappers/generated/vrf_owner_test_consumer/vrf_owner_test_consumer.go b/core/gethwrappers/generated/vrf_owner_test_consumer/vrf_owner_test_consumer.go index ce42ddb7d6c..8a17b64378e 100644 --- a/core/gethwrappers/generated/vrf_owner_test_consumer/vrf_owner_test_consumer.go +++ b/core/gethwrappers/generated/vrf_owner_test_consumer/vrf_owner_test_consumer.go @@ -52,7 +52,7 @@ func DeployVRFV2OwnerTestConsumer(auth *bind.TransactOpts, backend bind.Contract if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2OwnerTestConsumer{VRFV2OwnerTestConsumerCaller: VRFV2OwnerTestConsumerCaller{contract: contract}, VRFV2OwnerTestConsumerTransactor: VRFV2OwnerTestConsumerTransactor{contract: contract}, VRFV2OwnerTestConsumerFilterer: VRFV2OwnerTestConsumerFilterer{contract: contract}}, nil + return address, tx, &VRFV2OwnerTestConsumer{address: address, abi: *parsed, VRFV2OwnerTestConsumerCaller: VRFV2OwnerTestConsumerCaller{contract: contract}, VRFV2OwnerTestConsumerTransactor: VRFV2OwnerTestConsumerTransactor{contract: contract}, VRFV2OwnerTestConsumerFilterer: VRFV2OwnerTestConsumerFilterer{contract: contract}}, nil } type VRFV2OwnerTestConsumer struct { diff --git a/core/gethwrappers/generated/vrf_ownerless_consumer_example/vrf_ownerless_consumer_example.go b/core/gethwrappers/generated/vrf_ownerless_consumer_example/vrf_ownerless_consumer_example.go index c71553ac815..697a862533d 100644 --- a/core/gethwrappers/generated/vrf_ownerless_consumer_example/vrf_ownerless_consumer_example.go +++ b/core/gethwrappers/generated/vrf_ownerless_consumer_example/vrf_ownerless_consumer_example.go @@ -50,7 +50,7 @@ func DeployVRFOwnerlessConsumerExample(auth *bind.TransactOpts, backend bind.Con if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFOwnerlessConsumerExample{VRFOwnerlessConsumerExampleCaller: VRFOwnerlessConsumerExampleCaller{contract: contract}, VRFOwnerlessConsumerExampleTransactor: VRFOwnerlessConsumerExampleTransactor{contract: contract}, VRFOwnerlessConsumerExampleFilterer: VRFOwnerlessConsumerExampleFilterer{contract: contract}}, nil + return address, tx, &VRFOwnerlessConsumerExample{address: address, abi: *parsed, VRFOwnerlessConsumerExampleCaller: VRFOwnerlessConsumerExampleCaller{contract: contract}, VRFOwnerlessConsumerExampleTransactor: VRFOwnerlessConsumerExampleTransactor{contract: contract}, VRFOwnerlessConsumerExampleFilterer: VRFOwnerlessConsumerExampleFilterer{contract: contract}}, nil } type VRFOwnerlessConsumerExample struct { diff --git a/core/gethwrappers/generated/vrf_single_consumer_example/vrf_single_consumer_example.go b/core/gethwrappers/generated/vrf_single_consumer_example/vrf_single_consumer_example.go index affc8c53715..ac5c081ab3a 100644 --- a/core/gethwrappers/generated/vrf_single_consumer_example/vrf_single_consumer_example.go +++ b/core/gethwrappers/generated/vrf_single_consumer_example/vrf_single_consumer_example.go @@ -50,7 +50,7 @@ func DeployVRFSingleConsumerExample(auth *bind.TransactOpts, backend bind.Contra if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFSingleConsumerExample{VRFSingleConsumerExampleCaller: VRFSingleConsumerExampleCaller{contract: contract}, VRFSingleConsumerExampleTransactor: VRFSingleConsumerExampleTransactor{contract: contract}, VRFSingleConsumerExampleFilterer: VRFSingleConsumerExampleFilterer{contract: contract}}, nil + return address, tx, &VRFSingleConsumerExample{address: address, abi: *parsed, VRFSingleConsumerExampleCaller: VRFSingleConsumerExampleCaller{contract: contract}, VRFSingleConsumerExampleTransactor: VRFSingleConsumerExampleTransactor{contract: contract}, VRFSingleConsumerExampleFilterer: VRFSingleConsumerExampleFilterer{contract: contract}}, nil } type VRFSingleConsumerExample struct { diff --git a/core/gethwrappers/generated/vrf_v2_consumer_wrapper/vrf_v2_consumer_wrapper.go b/core/gethwrappers/generated/vrf_v2_consumer_wrapper/vrf_v2_consumer_wrapper.go index a5d57945802..e35efc9ec89 100644 --- a/core/gethwrappers/generated/vrf_v2_consumer_wrapper/vrf_v2_consumer_wrapper.go +++ b/core/gethwrappers/generated/vrf_v2_consumer_wrapper/vrf_v2_consumer_wrapper.go @@ -52,7 +52,7 @@ func DeployVRFv2Consumer(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFv2Consumer{VRFv2ConsumerCaller: VRFv2ConsumerCaller{contract: contract}, VRFv2ConsumerTransactor: VRFv2ConsumerTransactor{contract: contract}, VRFv2ConsumerFilterer: VRFv2ConsumerFilterer{contract: contract}}, nil + return address, tx, &VRFv2Consumer{address: address, abi: *parsed, VRFv2ConsumerCaller: VRFv2ConsumerCaller{contract: contract}, VRFv2ConsumerTransactor: VRFv2ConsumerTransactor{contract: contract}, VRFv2ConsumerFilterer: VRFv2ConsumerFilterer{contract: contract}}, nil } type VRFv2Consumer struct { diff --git a/core/gethwrappers/generated/vrf_v2plus_load_test_with_metrics/vrf_v2plus_load_test_with_metrics.go b/core/gethwrappers/generated/vrf_v2plus_load_test_with_metrics/vrf_v2plus_load_test_with_metrics.go index c8971595c53..017423772f7 100644 --- a/core/gethwrappers/generated/vrf_v2plus_load_test_with_metrics/vrf_v2plus_load_test_with_metrics.go +++ b/core/gethwrappers/generated/vrf_v2plus_load_test_with_metrics/vrf_v2plus_load_test_with_metrics.go @@ -52,7 +52,7 @@ func DeployVRFV2PlusLoadTestWithMetrics(auth *bind.TransactOpts, backend bind.Co if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2PlusLoadTestWithMetrics{VRFV2PlusLoadTestWithMetricsCaller: VRFV2PlusLoadTestWithMetricsCaller{contract: contract}, VRFV2PlusLoadTestWithMetricsTransactor: VRFV2PlusLoadTestWithMetricsTransactor{contract: contract}, VRFV2PlusLoadTestWithMetricsFilterer: VRFV2PlusLoadTestWithMetricsFilterer{contract: contract}}, nil + return address, tx, &VRFV2PlusLoadTestWithMetrics{address: address, abi: *parsed, VRFV2PlusLoadTestWithMetricsCaller: VRFV2PlusLoadTestWithMetricsCaller{contract: contract}, VRFV2PlusLoadTestWithMetricsTransactor: VRFV2PlusLoadTestWithMetricsTransactor{contract: contract}, VRFV2PlusLoadTestWithMetricsFilterer: VRFV2PlusLoadTestWithMetricsFilterer{contract: contract}}, nil } type VRFV2PlusLoadTestWithMetrics struct { diff --git a/core/gethwrappers/generated/vrf_v2plus_single_consumer/vrf_v2plus_single_consumer.go b/core/gethwrappers/generated/vrf_v2plus_single_consumer/vrf_v2plus_single_consumer.go index afa659269db..b9de348b103 100644 --- a/core/gethwrappers/generated/vrf_v2plus_single_consumer/vrf_v2plus_single_consumer.go +++ b/core/gethwrappers/generated/vrf_v2plus_single_consumer/vrf_v2plus_single_consumer.go @@ -52,7 +52,7 @@ func DeployVRFV2PlusSingleConsumerExample(auth *bind.TransactOpts, backend bind. if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2PlusSingleConsumerExample{VRFV2PlusSingleConsumerExampleCaller: VRFV2PlusSingleConsumerExampleCaller{contract: contract}, VRFV2PlusSingleConsumerExampleTransactor: VRFV2PlusSingleConsumerExampleTransactor{contract: contract}, VRFV2PlusSingleConsumerExampleFilterer: VRFV2PlusSingleConsumerExampleFilterer{contract: contract}}, nil + return address, tx, &VRFV2PlusSingleConsumerExample{address: address, abi: *parsed, VRFV2PlusSingleConsumerExampleCaller: VRFV2PlusSingleConsumerExampleCaller{contract: contract}, VRFV2PlusSingleConsumerExampleTransactor: VRFV2PlusSingleConsumerExampleTransactor{contract: contract}, VRFV2PlusSingleConsumerExampleFilterer: VRFV2PlusSingleConsumerExampleFilterer{contract: contract}}, nil } type VRFV2PlusSingleConsumerExample struct { diff --git a/core/gethwrappers/generated/vrf_v2plus_sub_owner/vrf_v2plus_sub_owner.go b/core/gethwrappers/generated/vrf_v2plus_sub_owner/vrf_v2plus_sub_owner.go index da32a6a202c..8cc57fce6c6 100644 --- a/core/gethwrappers/generated/vrf_v2plus_sub_owner/vrf_v2plus_sub_owner.go +++ b/core/gethwrappers/generated/vrf_v2plus_sub_owner/vrf_v2plus_sub_owner.go @@ -52,7 +52,7 @@ func DeployVRFV2PlusExternalSubOwnerExample(auth *bind.TransactOpts, backend bin if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2PlusExternalSubOwnerExample{VRFV2PlusExternalSubOwnerExampleCaller: VRFV2PlusExternalSubOwnerExampleCaller{contract: contract}, VRFV2PlusExternalSubOwnerExampleTransactor: VRFV2PlusExternalSubOwnerExampleTransactor{contract: contract}, VRFV2PlusExternalSubOwnerExampleFilterer: VRFV2PlusExternalSubOwnerExampleFilterer{contract: contract}}, nil + return address, tx, &VRFV2PlusExternalSubOwnerExample{address: address, abi: *parsed, VRFV2PlusExternalSubOwnerExampleCaller: VRFV2PlusExternalSubOwnerExampleCaller{contract: contract}, VRFV2PlusExternalSubOwnerExampleTransactor: VRFV2PlusExternalSubOwnerExampleTransactor{contract: contract}, VRFV2PlusExternalSubOwnerExampleFilterer: VRFV2PlusExternalSubOwnerExampleFilterer{contract: contract}}, nil } type VRFV2PlusExternalSubOwnerExample struct { diff --git a/core/gethwrappers/generated/vrf_v2plus_upgraded_version/vrf_v2plus_upgraded_version.go b/core/gethwrappers/generated/vrf_v2plus_upgraded_version/vrf_v2plus_upgraded_version.go index 852501d09ec..7aae3d37770 100644 --- a/core/gethwrappers/generated/vrf_v2plus_upgraded_version/vrf_v2plus_upgraded_version.go +++ b/core/gethwrappers/generated/vrf_v2plus_upgraded_version/vrf_v2plus_upgraded_version.go @@ -87,7 +87,7 @@ func DeployVRFCoordinatorV2PlusUpgradedVersion(auth *bind.TransactOpts, backend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFCoordinatorV2PlusUpgradedVersion{VRFCoordinatorV2PlusUpgradedVersionCaller: VRFCoordinatorV2PlusUpgradedVersionCaller{contract: contract}, VRFCoordinatorV2PlusUpgradedVersionTransactor: VRFCoordinatorV2PlusUpgradedVersionTransactor{contract: contract}, VRFCoordinatorV2PlusUpgradedVersionFilterer: VRFCoordinatorV2PlusUpgradedVersionFilterer{contract: contract}}, nil + return address, tx, &VRFCoordinatorV2PlusUpgradedVersion{address: address, abi: *parsed, VRFCoordinatorV2PlusUpgradedVersionCaller: VRFCoordinatorV2PlusUpgradedVersionCaller{contract: contract}, VRFCoordinatorV2PlusUpgradedVersionTransactor: VRFCoordinatorV2PlusUpgradedVersionTransactor{contract: contract}, VRFCoordinatorV2PlusUpgradedVersionFilterer: VRFCoordinatorV2PlusUpgradedVersionFilterer{contract: contract}}, nil } type VRFCoordinatorV2PlusUpgradedVersion struct { diff --git a/core/gethwrappers/generated/vrfv2_proxy_admin/vrfv2_proxy_admin.go b/core/gethwrappers/generated/vrfv2_proxy_admin/vrfv2_proxy_admin.go index 2cb4edd2082..d92679f4ad7 100644 --- a/core/gethwrappers/generated/vrfv2_proxy_admin/vrfv2_proxy_admin.go +++ b/core/gethwrappers/generated/vrfv2_proxy_admin/vrfv2_proxy_admin.go @@ -52,7 +52,7 @@ func DeployVRFV2ProxyAdmin(auth *bind.TransactOpts, backend bind.ContractBackend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2ProxyAdmin{VRFV2ProxyAdminCaller: VRFV2ProxyAdminCaller{contract: contract}, VRFV2ProxyAdminTransactor: VRFV2ProxyAdminTransactor{contract: contract}, VRFV2ProxyAdminFilterer: VRFV2ProxyAdminFilterer{contract: contract}}, nil + return address, tx, &VRFV2ProxyAdmin{address: address, abi: *parsed, VRFV2ProxyAdminCaller: VRFV2ProxyAdminCaller{contract: contract}, VRFV2ProxyAdminTransactor: VRFV2ProxyAdminTransactor{contract: contract}, VRFV2ProxyAdminFilterer: VRFV2ProxyAdminFilterer{contract: contract}}, nil } type VRFV2ProxyAdmin struct { diff --git a/core/gethwrappers/generated/vrfv2_reverting_example/vrfv2_reverting_example.go b/core/gethwrappers/generated/vrfv2_reverting_example/vrfv2_reverting_example.go index 9f8a4eb0fac..facfc931c91 100644 --- a/core/gethwrappers/generated/vrfv2_reverting_example/vrfv2_reverting_example.go +++ b/core/gethwrappers/generated/vrfv2_reverting_example/vrfv2_reverting_example.go @@ -50,7 +50,7 @@ func DeployVRFV2RevertingExample(auth *bind.TransactOpts, backend bind.ContractB if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2RevertingExample{VRFV2RevertingExampleCaller: VRFV2RevertingExampleCaller{contract: contract}, VRFV2RevertingExampleTransactor: VRFV2RevertingExampleTransactor{contract: contract}, VRFV2RevertingExampleFilterer: VRFV2RevertingExampleFilterer{contract: contract}}, nil + return address, tx, &VRFV2RevertingExample{address: address, abi: *parsed, VRFV2RevertingExampleCaller: VRFV2RevertingExampleCaller{contract: contract}, VRFV2RevertingExampleTransactor: VRFV2RevertingExampleTransactor{contract: contract}, VRFV2RevertingExampleFilterer: VRFV2RevertingExampleFilterer{contract: contract}}, nil } type VRFV2RevertingExample struct { diff --git a/core/gethwrappers/generated/vrfv2_transparent_upgradeable_proxy/vrfv2_transparent_upgradeable_proxy.go b/core/gethwrappers/generated/vrfv2_transparent_upgradeable_proxy/vrfv2_transparent_upgradeable_proxy.go index 657cc1894f3..c808650a084 100644 --- a/core/gethwrappers/generated/vrfv2_transparent_upgradeable_proxy/vrfv2_transparent_upgradeable_proxy.go +++ b/core/gethwrappers/generated/vrfv2_transparent_upgradeable_proxy/vrfv2_transparent_upgradeable_proxy.go @@ -52,7 +52,7 @@ func DeployVRFV2TransparentUpgradeableProxy(auth *bind.TransactOpts, backend bin if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2TransparentUpgradeableProxy{VRFV2TransparentUpgradeableProxyCaller: VRFV2TransparentUpgradeableProxyCaller{contract: contract}, VRFV2TransparentUpgradeableProxyTransactor: VRFV2TransparentUpgradeableProxyTransactor{contract: contract}, VRFV2TransparentUpgradeableProxyFilterer: VRFV2TransparentUpgradeableProxyFilterer{contract: contract}}, nil + return address, tx, &VRFV2TransparentUpgradeableProxy{address: address, abi: *parsed, VRFV2TransparentUpgradeableProxyCaller: VRFV2TransparentUpgradeableProxyCaller{contract: contract}, VRFV2TransparentUpgradeableProxyTransactor: VRFV2TransparentUpgradeableProxyTransactor{contract: contract}, VRFV2TransparentUpgradeableProxyFilterer: VRFV2TransparentUpgradeableProxyFilterer{contract: contract}}, nil } type VRFV2TransparentUpgradeableProxy struct { diff --git a/core/gethwrappers/generated/vrfv2_wrapper/vrfv2_wrapper.go b/core/gethwrappers/generated/vrfv2_wrapper/vrfv2_wrapper.go index 61cb52e117d..9e7e25229e5 100644 --- a/core/gethwrappers/generated/vrfv2_wrapper/vrfv2_wrapper.go +++ b/core/gethwrappers/generated/vrfv2_wrapper/vrfv2_wrapper.go @@ -52,7 +52,7 @@ func DeployVRFV2Wrapper(auth *bind.TransactOpts, backend bind.ContractBackend, _ if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2Wrapper{VRFV2WrapperCaller: VRFV2WrapperCaller{contract: contract}, VRFV2WrapperTransactor: VRFV2WrapperTransactor{contract: contract}, VRFV2WrapperFilterer: VRFV2WrapperFilterer{contract: contract}}, nil + return address, tx, &VRFV2Wrapper{address: address, abi: *parsed, VRFV2WrapperCaller: VRFV2WrapperCaller{contract: contract}, VRFV2WrapperTransactor: VRFV2WrapperTransactor{contract: contract}, VRFV2WrapperFilterer: VRFV2WrapperFilterer{contract: contract}}, nil } type VRFV2Wrapper struct { diff --git a/core/gethwrappers/generated/vrfv2_wrapper_consumer_example/vrfv2_wrapper_consumer_example.go b/core/gethwrappers/generated/vrfv2_wrapper_consumer_example/vrfv2_wrapper_consumer_example.go index ba38ddf436f..dbf5d97f9cd 100644 --- a/core/gethwrappers/generated/vrfv2_wrapper_consumer_example/vrfv2_wrapper_consumer_example.go +++ b/core/gethwrappers/generated/vrfv2_wrapper_consumer_example/vrfv2_wrapper_consumer_example.go @@ -52,7 +52,7 @@ func DeployVRFV2WrapperConsumerExample(auth *bind.TransactOpts, backend bind.Con if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2WrapperConsumerExample{VRFV2WrapperConsumerExampleCaller: VRFV2WrapperConsumerExampleCaller{contract: contract}, VRFV2WrapperConsumerExampleTransactor: VRFV2WrapperConsumerExampleTransactor{contract: contract}, VRFV2WrapperConsumerExampleFilterer: VRFV2WrapperConsumerExampleFilterer{contract: contract}}, nil + return address, tx, &VRFV2WrapperConsumerExample{address: address, abi: *parsed, VRFV2WrapperConsumerExampleCaller: VRFV2WrapperConsumerExampleCaller{contract: contract}, VRFV2WrapperConsumerExampleTransactor: VRFV2WrapperConsumerExampleTransactor{contract: contract}, VRFV2WrapperConsumerExampleFilterer: VRFV2WrapperConsumerExampleFilterer{contract: contract}}, nil } type VRFV2WrapperConsumerExample struct { diff --git a/core/gethwrappers/generated/vrfv2plus_client/vrfv2plus_client.go b/core/gethwrappers/generated/vrfv2plus_client/vrfv2plus_client.go index 91112ff856f..f6a65a63f27 100644 --- a/core/gethwrappers/generated/vrfv2plus_client/vrfv2plus_client.go +++ b/core/gethwrappers/generated/vrfv2plus_client/vrfv2plus_client.go @@ -50,7 +50,7 @@ func DeployVRFV2PlusClient(auth *bind.TransactOpts, backend bind.ContractBackend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2PlusClient{VRFV2PlusClientCaller: VRFV2PlusClientCaller{contract: contract}, VRFV2PlusClientTransactor: VRFV2PlusClientTransactor{contract: contract}, VRFV2PlusClientFilterer: VRFV2PlusClientFilterer{contract: contract}}, nil + return address, tx, &VRFV2PlusClient{address: address, abi: *parsed, VRFV2PlusClientCaller: VRFV2PlusClientCaller{contract: contract}, VRFV2PlusClientTransactor: VRFV2PlusClientTransactor{contract: contract}, VRFV2PlusClientFilterer: VRFV2PlusClientFilterer{contract: contract}}, nil } type VRFV2PlusClient struct { diff --git a/core/gethwrappers/generated/vrfv2plus_consumer_example/vrfv2plus_consumer_example.go b/core/gethwrappers/generated/vrfv2plus_consumer_example/vrfv2plus_consumer_example.go index 2ad412a1223..20f3d4422b1 100644 --- a/core/gethwrappers/generated/vrfv2plus_consumer_example/vrfv2plus_consumer_example.go +++ b/core/gethwrappers/generated/vrfv2plus_consumer_example/vrfv2plus_consumer_example.go @@ -52,7 +52,7 @@ func DeployVRFV2PlusConsumerExample(auth *bind.TransactOpts, backend bind.Contra if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2PlusConsumerExample{VRFV2PlusConsumerExampleCaller: VRFV2PlusConsumerExampleCaller{contract: contract}, VRFV2PlusConsumerExampleTransactor: VRFV2PlusConsumerExampleTransactor{contract: contract}, VRFV2PlusConsumerExampleFilterer: VRFV2PlusConsumerExampleFilterer{contract: contract}}, nil + return address, tx, &VRFV2PlusConsumerExample{address: address, abi: *parsed, VRFV2PlusConsumerExampleCaller: VRFV2PlusConsumerExampleCaller{contract: contract}, VRFV2PlusConsumerExampleTransactor: VRFV2PlusConsumerExampleTransactor{contract: contract}, VRFV2PlusConsumerExampleFilterer: VRFV2PlusConsumerExampleFilterer{contract: contract}}, nil } type VRFV2PlusConsumerExample struct { diff --git a/core/gethwrappers/generated/vrfv2plus_malicious_migrator/vrfv2plus_malicious_migrator.go b/core/gethwrappers/generated/vrfv2plus_malicious_migrator/vrfv2plus_malicious_migrator.go index c0c19a1134c..03c5ffd8ccc 100644 --- a/core/gethwrappers/generated/vrfv2plus_malicious_migrator/vrfv2plus_malicious_migrator.go +++ b/core/gethwrappers/generated/vrfv2plus_malicious_migrator/vrfv2plus_malicious_migrator.go @@ -50,7 +50,7 @@ func DeployVRFV2PlusMaliciousMigrator(auth *bind.TransactOpts, backend bind.Cont if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2PlusMaliciousMigrator{VRFV2PlusMaliciousMigratorCaller: VRFV2PlusMaliciousMigratorCaller{contract: contract}, VRFV2PlusMaliciousMigratorTransactor: VRFV2PlusMaliciousMigratorTransactor{contract: contract}, VRFV2PlusMaliciousMigratorFilterer: VRFV2PlusMaliciousMigratorFilterer{contract: contract}}, nil + return address, tx, &VRFV2PlusMaliciousMigrator{address: address, abi: *parsed, VRFV2PlusMaliciousMigratorCaller: VRFV2PlusMaliciousMigratorCaller{contract: contract}, VRFV2PlusMaliciousMigratorTransactor: VRFV2PlusMaliciousMigratorTransactor{contract: contract}, VRFV2PlusMaliciousMigratorFilterer: VRFV2PlusMaliciousMigratorFilterer{contract: contract}}, nil } type VRFV2PlusMaliciousMigrator struct { diff --git a/core/gethwrappers/generated/vrfv2plus_reverting_example/vrfv2plus_reverting_example.go b/core/gethwrappers/generated/vrfv2plus_reverting_example/vrfv2plus_reverting_example.go index 72364701395..5e66eb2474d 100644 --- a/core/gethwrappers/generated/vrfv2plus_reverting_example/vrfv2plus_reverting_example.go +++ b/core/gethwrappers/generated/vrfv2plus_reverting_example/vrfv2plus_reverting_example.go @@ -52,7 +52,7 @@ func DeployVRFV2PlusRevertingExample(auth *bind.TransactOpts, backend bind.Contr if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2PlusRevertingExample{VRFV2PlusRevertingExampleCaller: VRFV2PlusRevertingExampleCaller{contract: contract}, VRFV2PlusRevertingExampleTransactor: VRFV2PlusRevertingExampleTransactor{contract: contract}, VRFV2PlusRevertingExampleFilterer: VRFV2PlusRevertingExampleFilterer{contract: contract}}, nil + return address, tx, &VRFV2PlusRevertingExample{address: address, abi: *parsed, VRFV2PlusRevertingExampleCaller: VRFV2PlusRevertingExampleCaller{contract: contract}, VRFV2PlusRevertingExampleTransactor: VRFV2PlusRevertingExampleTransactor{contract: contract}, VRFV2PlusRevertingExampleFilterer: VRFV2PlusRevertingExampleFilterer{contract: contract}}, nil } type VRFV2PlusRevertingExample struct { diff --git a/core/gethwrappers/generated/vrfv2plus_wrapper/vrfv2plus_wrapper.go b/core/gethwrappers/generated/vrfv2plus_wrapper/vrfv2plus_wrapper.go index d7894c576e3..5a5ccb34f1d 100644 --- a/core/gethwrappers/generated/vrfv2plus_wrapper/vrfv2plus_wrapper.go +++ b/core/gethwrappers/generated/vrfv2plus_wrapper/vrfv2plus_wrapper.go @@ -52,7 +52,7 @@ func DeployVRFV2PlusWrapper(auth *bind.TransactOpts, backend bind.ContractBacken if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2PlusWrapper{VRFV2PlusWrapperCaller: VRFV2PlusWrapperCaller{contract: contract}, VRFV2PlusWrapperTransactor: VRFV2PlusWrapperTransactor{contract: contract}, VRFV2PlusWrapperFilterer: VRFV2PlusWrapperFilterer{contract: contract}}, nil + return address, tx, &VRFV2PlusWrapper{address: address, abi: *parsed, VRFV2PlusWrapperCaller: VRFV2PlusWrapperCaller{contract: contract}, VRFV2PlusWrapperTransactor: VRFV2PlusWrapperTransactor{contract: contract}, VRFV2PlusWrapperFilterer: VRFV2PlusWrapperFilterer{contract: contract}}, nil } type VRFV2PlusWrapper struct { diff --git a/core/gethwrappers/generated/vrfv2plus_wrapper_consumer_example/vrfv2plus_wrapper_consumer_example.go b/core/gethwrappers/generated/vrfv2plus_wrapper_consumer_example/vrfv2plus_wrapper_consumer_example.go index 9cbd5ef964b..ee2f1e360b0 100644 --- a/core/gethwrappers/generated/vrfv2plus_wrapper_consumer_example/vrfv2plus_wrapper_consumer_example.go +++ b/core/gethwrappers/generated/vrfv2plus_wrapper_consumer_example/vrfv2plus_wrapper_consumer_example.go @@ -52,7 +52,7 @@ func DeployVRFV2PlusWrapperConsumerExample(auth *bind.TransactOpts, backend bind if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2PlusWrapperConsumerExample{VRFV2PlusWrapperConsumerExampleCaller: VRFV2PlusWrapperConsumerExampleCaller{contract: contract}, VRFV2PlusWrapperConsumerExampleTransactor: VRFV2PlusWrapperConsumerExampleTransactor{contract: contract}, VRFV2PlusWrapperConsumerExampleFilterer: VRFV2PlusWrapperConsumerExampleFilterer{contract: contract}}, nil + return address, tx, &VRFV2PlusWrapperConsumerExample{address: address, abi: *parsed, VRFV2PlusWrapperConsumerExampleCaller: VRFV2PlusWrapperConsumerExampleCaller{contract: contract}, VRFV2PlusWrapperConsumerExampleTransactor: VRFV2PlusWrapperConsumerExampleTransactor{contract: contract}, VRFV2PlusWrapperConsumerExampleFilterer: VRFV2PlusWrapperConsumerExampleFilterer{contract: contract}}, nil } type VRFV2PlusWrapperConsumerExample struct { diff --git a/core/gethwrappers/generated/vrfv2plus_wrapper_load_test_consumer/vrfv2plus_wrapper_load_test_consumer.go b/core/gethwrappers/generated/vrfv2plus_wrapper_load_test_consumer/vrfv2plus_wrapper_load_test_consumer.go index 231945c9b7a..8da1419620b 100644 --- a/core/gethwrappers/generated/vrfv2plus_wrapper_load_test_consumer/vrfv2plus_wrapper_load_test_consumer.go +++ b/core/gethwrappers/generated/vrfv2plus_wrapper_load_test_consumer/vrfv2plus_wrapper_load_test_consumer.go @@ -52,7 +52,7 @@ func DeployVRFV2PlusWrapperLoadTestConsumer(auth *bind.TransactOpts, backend bin if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VRFV2PlusWrapperLoadTestConsumer{VRFV2PlusWrapperLoadTestConsumerCaller: VRFV2PlusWrapperLoadTestConsumerCaller{contract: contract}, VRFV2PlusWrapperLoadTestConsumerTransactor: VRFV2PlusWrapperLoadTestConsumerTransactor{contract: contract}, VRFV2PlusWrapperLoadTestConsumerFilterer: VRFV2PlusWrapperLoadTestConsumerFilterer{contract: contract}}, nil + return address, tx, &VRFV2PlusWrapperLoadTestConsumer{address: address, abi: *parsed, VRFV2PlusWrapperLoadTestConsumerCaller: VRFV2PlusWrapperLoadTestConsumerCaller{contract: contract}, VRFV2PlusWrapperLoadTestConsumerTransactor: VRFV2PlusWrapperLoadTestConsumerTransactor{contract: contract}, VRFV2PlusWrapperLoadTestConsumerFilterer: VRFV2PlusWrapperLoadTestConsumerFilterer{contract: contract}}, nil } type VRFV2PlusWrapperLoadTestConsumer struct { diff --git a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 0d0bb388f2f..6efc75fce98 100644 --- a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,116 +1,108 @@ GETH_VERSION: 1.12.0 -KeeperConsumer: ../../contracts/solc/v0.8.16/KeeperConsumer.abi ../../contracts/solc/v0.8.16/KeeperConsumer.bin 53d7902867ce421641ffa9de63204b89ab9dc157b93f0beb9ac08c6450365a70 -KeeperConsumerPerformance: ../../contracts/solc/v0.8.16/KeeperConsumerPerformance.abi ../../contracts/solc/v0.8.16/KeeperConsumerPerformance.bin eeda39f5d3e1c8ffa0fb6cd1803731b98a4bc262d41833458e3fe8b40933ae90 -PerformDataChecker: ../../contracts/solc/v0.8.16/PerformDataChecker.abi ../../contracts/solc/v0.8.16/PerformDataChecker.bin 48d8309c2117c29a24e1155917ab0b780956b2cd6a8a39ef06ae66a7f6d94f73 -UpkeepCounter: ../../contracts/solc/v0.8.16/UpkeepCounter.abi ../../contracts/solc/v0.8.16/UpkeepCounter.bin 77f000229a501f638dd2dc439859257f632894c728b31e68aea4f6d6c52f1b71 -UpkeepPerformCounterRestrictive: ../../contracts/solc/v0.8.16/UpkeepPerformCounterRestrictive.abi ../../contracts/solc/v0.8.16/UpkeepPerformCounterRestrictive.bin 20955b21acceb58355fa287b29194a73edf5937067ba7140667301017cb2b24c -VRFv2Consumer: ../../contracts/solc/v0.8.6/VRFv2Consumer.abi ../../contracts/solc/v0.8.6/VRFv2Consumer.bin 12368b3b5e06392440143a13b94c0ea2f79c4c897becc3b060982559e10ace40 -aggregator_v2v3_interface: ../../contracts/solc/v0.8.6/AggregatorV2V3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface.bin 95e8814b408bb05bf21742ef580d98698b7db6a9bac6a35c3de12b23aec4ee28 -aggregator_v3_interface: ../../contracts/solc/v0.8.6/AggregatorV3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV3Interface.bin 351b55d3b0f04af67db6dfb5c92f1c64479400ca1fec77afc20bc0ce65cb49ab -authorized_forwarder: ../../contracts/solc/v0.8.19/AuthorizedForwarder.abi ../../contracts/solc/v0.8.19/AuthorizedForwarder.bin 8ea76c883d460f8353a45a493f2aebeb5a2d9a7b4619d1bc4fff5fb590bb3e10 -authorized_receiver: ../../contracts/solc/v0.8.19/AuthorizedReceiver.abi ../../contracts/solc/v0.8.19/AuthorizedReceiver.bin 18e8969ba3234b027e1b16c11a783aca58d0ea5c2361010ec597f134b7bf1c4f -automation_consumer_benchmark: ../../contracts/solc/v0.8.16/AutomationConsumerBenchmark.abi ../../contracts/solc/v0.8.16/AutomationConsumerBenchmark.bin f52c76f1aaed4be541d82d97189d70f5aa027fc9838037dd7a7d21910c8c488e -automation_forwarder_logic: ../../contracts/solc/v0.8.16/AutomationForwarderLogic.abi ../../contracts/solc/v0.8.16/AutomationForwarderLogic.bin 15ae0c367297955fdab4b552dbb10e1f2be80a8fde0efec4a4d398693e9d72b5 -automation_registrar_wrapper2_1: ../../contracts/solc/v0.8.16/AutomationRegistrar2_1.abi ../../contracts/solc/v0.8.16/AutomationRegistrar2_1.bin eb06d853aab39d3196c593b03e555851cbe8386e0fe54a74c2479f62d14b3c42 -automation_utils_2_1: ../../contracts/solc/v0.8.16/AutomationUtils2_1.abi ../../contracts/solc/v0.8.16/AutomationUtils2_1.bin 331bfa79685aee6ddf63b64c0747abee556c454cae3fb8175edff425b615d8aa -batch_blockhash_store: ../../contracts/solc/v0.8.6/BatchBlockhashStore.abi ../../contracts/solc/v0.8.6/BatchBlockhashStore.bin 14356c48ef70f66ef74f22f644450dbf3b2a147c1b68deaa7e7d1eb8ffab15db -batch_vrf_coordinator_v2: ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2.bin d0a54963260d8c1f1bbd984b758285e6027cfb5a7e42701bcb562ab123219332 -batch_vrf_coordinator_v2plus: ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2Plus.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2Plus.bin 7bb76ae241cf1b37b41920830b836cb99f1ad33efd7435ca2398ff6cd2fe5d48 -blockhash_store: ../../contracts/solc/v0.8.6/BlockhashStore.abi ../../contracts/solc/v0.8.6/BlockhashStore.bin 12b0662f1636a341c8863bdec7a20f2ddd97c3a4fd1a7ae353fe316609face4e -chain_specific_util_helper: ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper.abi ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper.bin 5f10664e31abc768f4a37901cae7a3bef90146180f97303e5a1bde5a08d84595 -consumer_wrapper: ../../contracts/solc/v0.7/Consumer.abi ../../contracts/solc/v0.7/Consumer.bin 894d1cbd920dccbd36d92918c1037c6ded34f66f417ccb18ec3f33c64ef83ec5 -cron_upkeep_factory_wrapper: ../../contracts/solc/v0.8.6/CronUpkeepFactory.abi - dacb0f8cdf54ae9d2781c5e720fc314b32ed5e58eddccff512c75d6067292cd7 -cron_upkeep_wrapper: ../../contracts/solc/v0.8.6/CronUpkeep.abi - 362fcfcf30a6ab3acff83095ea4b2b9056dd5e9dcb94bc5411aae58995d22709 -dummy_protocol_wrapper: ../../contracts/solc/v0.8.16/DummyProtocol.abi ../../contracts/solc/v0.8.16/DummyProtocol.bin 583a448170b13abf7ed64e406e8177d78c9e55ab44efd141eee60de23a71ee3b -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 -functions_billing_registry_events_mock: ../../contracts/solc/v0.8.6/FunctionsBillingRegistryEventsMock.abi ../../contracts/solc/v0.8.6/FunctionsBillingRegistryEventsMock.bin 50deeb883bd9c3729702be335c0388f9d8553bab4be5e26ecacac496a89e2b77 -functions_oracle_events_mock: ../../contracts/solc/v0.8.6/FunctionsOracleEventsMock.abi ../../contracts/solc/v0.8.6/FunctionsOracleEventsMock.bin 3ca70f966f8fe751987f0ccb50bebb6aa5be77e4a9f835d1ae99e0e9bfb7d52c -gas_wrapper: ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2.abi ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2.bin 4a5dcdac486d18fcd58e3488c15c1710ae76b977556a3f3191bd269a4bc75723 -gas_wrapper_mock: ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock.abi ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock.bin a9b08f18da59125c6fc305855710241f3d35161b8b9f3e3f635a7b1d5c6da9c8 -i_keeper_registry_master_wrapper_2_1: ../../contracts/solc/v0.8.16/IKeeperRegistryMaster.abi ../../contracts/solc/v0.8.16/IKeeperRegistryMaster.bin 6501bb9bcf5048bab2737b00685c6984a24867e234ddf5b60a65904eee9a4ebc -i_log_automation: ../../contracts/solc/v0.8.16/ILogAutomation.abi ../../contracts/solc/v0.8.16/ILogAutomation.bin 296beccb6af655d6fc3a6e676b244831cce2da6688d3afc4f21f8738ae59e03e -keeper_consumer_performance_wrapper: ../../contracts/solc/v0.8.16/KeeperConsumerPerformance.abi ../../contracts/solc/v0.8.16/KeeperConsumerPerformance.bin eeda39f5d3e1c8ffa0fb6cd1803731b98a4bc262d41833458e3fe8b40933ae90 -keeper_consumer_wrapper: ../../contracts/solc/v0.8.16/KeeperConsumer.abi ../../contracts/solc/v0.8.16/KeeperConsumer.bin 53d7902867ce421641ffa9de63204b89ab9dc157b93f0beb9ac08c6450365a70 -keeper_registrar_wrapper1_2: ../../contracts/solc/v0.8.6/KeeperRegistrar.abi ../../contracts/solc/v0.8.6/KeeperRegistrar.bin e49b2f8b23da17af1ed2209b8ae0968cc04350554d636711e6c24a3ad3118692 -keeper_registrar_wrapper1_2_mock: ../../contracts/solc/v0.8.6/KeeperRegistrar1_2Mock.abi ../../contracts/solc/v0.8.6/KeeperRegistrar1_2Mock.bin 5b155a7cb3def309fd7525de1d7cd364ebf8491bdc3060eac08ea0ff55ab29bc -keeper_registrar_wrapper2_0: ../../contracts/solc/v0.8.6/KeeperRegistrar2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistrar2_0.bin 647f125c2f0dafabcdc545cb77b15dc2ec3ea9429357806813179b1fd555c2d2 -keeper_registry_logic1_3: ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3.bin 903f8b9c8e25425ca6d0b81b89e339d695a83630bfbfa24a6f3b38869676bc5a -keeper_registry_logic2_0: ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0.bin d69d2bc8e4844293dbc2d45abcddc50b84c88554ecccfa4fa77c0ca45ec80871 -keeper_registry_logic_a_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1.bin 77481ab75c9aa86a62a7b2a708599b5ea1a6346ed1c0def6d4826e7ae523f1ee -keeper_registry_logic_b_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1.bin 467d10741a04601b136553a2b1c6ab37f2a65d809366faf03180a22ff26be215 -keeper_registry_wrapper1_1: ../../contracts/solc/v0.7/KeeperRegistry1_1.abi ../../contracts/solc/v0.7/KeeperRegistry1_1.bin 6ce079f2738f015f7374673a2816e8e9787143d00b780ea7652c8aa9ad9e1e20 -keeper_registry_wrapper1_1_mock: ../../contracts/solc/v0.7/KeeperRegistry1_1Mock.abi ../../contracts/solc/v0.7/KeeperRegistry1_1Mock.bin 98ddb3680e86359de3b5d17e648253ba29a84703f087a1b52237824003a8c6df -keeper_registry_wrapper1_2: ../../contracts/solc/v0.8.6/KeeperRegistry1_2.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_2.bin a40ff877dd7c280f984cbbb2b428e160662b0c295e881d5f778f941c0088ca22 -keeper_registry_wrapper1_3: ../../contracts/solc/v0.8.6/KeeperRegistry1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_3.bin d4dc760b767ae274ee25c4a604ea371e1fa603a7b6421b69efb2088ad9e8abb3 -keeper_registry_wrapper2_0: ../../contracts/solc/v0.8.6/KeeperRegistry2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistry2_0.bin c32dea7d5ef66b7c58ddc84ddf69aa44df1b3ae8601fbc271c95be4ff5853056 -keeper_registry_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistry2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistry2_1.bin 604e4a0cd980c713929b523b999462a3aa0ed06f96ff563a4c8566cf59c8445b -keepers_vrf_consumer: ../../contracts/solc/v0.8.6/KeepersVRFConsumer.abi ../../contracts/solc/v0.8.6/KeepersVRFConsumer.bin fa75572e689c9e84705c63e8dbe1b7b8aa1a8fe82d66356c4873d024bb9166e8 -log_emitter: ../../contracts/solc/v0.8.19/LogEmitter.abi ../../contracts/solc/v0.8.19/LogEmitter.bin 244ba13730c036de0b02beef4e3d9c9a96946ce353c27f366baecc7f5be5a6fd -log_triggered_streams_lookup_wrapper: ../../contracts/solc/v0.8.16/LogTriggeredStreamsLookup.abi ../../contracts/solc/v0.8.16/LogTriggeredStreamsLookup.bin f8da43a927c1a66238a9f4fd5d5dd7e280e361daa0444da1f7f79498ace901e1 -log_upkeep_counter_wrapper: ../../contracts/solc/v0.8.6/LogUpkeepCounter.abi ../../contracts/solc/v0.8.6/LogUpkeepCounter.bin 42426bbb83f96dfbe55fc576d6c65020eaeed690e2289cf99b0c4aa810a5f4ec -mock_aggregator_proxy: ../../contracts/solc/v0.8.6/MockAggregatorProxy.abi ../../contracts/solc/v0.8.6/MockAggregatorProxy.bin b16c108f3dd384c342ddff5e94da7c0a8d39d1be5e3d8f2cf61ecc7f0e50ff42 -mock_ethlink_aggregator_wrapper: ../../contracts/solc/v0.6/MockETHLINKAggregator.abi ../../contracts/solc/v0.6/MockETHLINKAggregator.bin 1c52c24f797b8482aa12b8251dcea1c072827bd5b3426b822621261944b99ca0 -mock_gas_aggregator_wrapper: ../../contracts/solc/v0.6/MockGASAggregator.abi ../../contracts/solc/v0.6/MockGASAggregator.bin bacbb1ea4dc6beac0db8a13ca5c75e2fd61b903d70feea9b3b1c8b10fe8df4f3 -multiwordconsumer_wrapper: ../../contracts/solc/v0.7/MultiWordConsumer.abi ../../contracts/solc/v0.7/MultiWordConsumer.bin 6e68abdf614e3ed0f5066c1b5f9d7c1199f1e7c5c5251fe8a471344a59afc6ba +aggregator_v2v3_interface: ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.bin 95e8814b408bb05bf21742ef580d98698b7db6a9bac6a35c3de12b23aec4ee28 +aggregator_v3_interface: ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV3Interface.bin 351b55d3b0f04af67db6dfb5c92f1c64479400ca1fec77afc20bc0ce65cb49ab +authorized_forwarder: ../../contracts/solc/v0.8.19/AuthorizedForwarder/AuthorizedForwarder.abi ../../contracts/solc/v0.8.19/AuthorizedForwarder/AuthorizedForwarder.bin 8ea76c883d460f8353a45a493f2aebeb5a2d9a7b4619d1bc4fff5fb590bb3e10 +authorized_receiver: ../../contracts/solc/v0.8.19/AuthorizedReceiver/AuthorizedReceiver.abi ../../contracts/solc/v0.8.19/AuthorizedReceiver/AuthorizedReceiver.bin 18e8969ba3234b027e1b16c11a783aca58d0ea5c2361010ec597f134b7bf1c4f +automation_consumer_benchmark: ../../contracts/solc/v0.8.16/AutomationConsumerBenchmark/AutomationConsumerBenchmark.abi ../../contracts/solc/v0.8.16/AutomationConsumerBenchmark/AutomationConsumerBenchmark.bin f52c76f1aaed4be541d82d97189d70f5aa027fc9838037dd7a7d21910c8c488e +automation_forwarder_logic: ../../contracts/solc/v0.8.16/AutomationForwarderLogic/AutomationForwarderLogic.abi ../../contracts/solc/v0.8.16/AutomationForwarderLogic/AutomationForwarderLogic.bin 15ae0c367297955fdab4b552dbb10e1f2be80a8fde0efec4a4d398693e9d72b5 +automation_registrar_wrapper2_1: ../../contracts/solc/v0.8.16/AutomationRegistrar2_1/AutomationRegistrar2_1.abi ../../contracts/solc/v0.8.16/AutomationRegistrar2_1/AutomationRegistrar2_1.bin eb06d853aab39d3196c593b03e555851cbe8386e0fe54a74c2479f62d14b3c42 +automation_utils_2_1: ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.abi ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.bin 331bfa79685aee6ddf63b64c0747abee556c454cae3fb8175edff425b615d8aa +batch_blockhash_store: ../../contracts/solc/v0.8.6/BatchBlockhashStore/BatchBlockhashStore.abi ../../contracts/solc/v0.8.6/BatchBlockhashStore/BatchBlockhashStore.bin 14356c48ef70f66ef74f22f644450dbf3b2a147c1b68deaa7e7d1eb8ffab15db +batch_vrf_coordinator_v2: ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2/BatchVRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2/BatchVRFCoordinatorV2.bin d0a54963260d8c1f1bbd984b758285e6027cfb5a7e42701bcb562ab123219332 +batch_vrf_coordinator_v2plus: ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2Plus/BatchVRFCoordinatorV2Plus.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2Plus/BatchVRFCoordinatorV2Plus.bin 7bb76ae241cf1b37b41920830b836cb99f1ad33efd7435ca2398ff6cd2fe5d48 +blockhash_store: ../../contracts/solc/v0.8.6/BlockhashStore/BlockhashStore.abi ../../contracts/solc/v0.8.6/BlockhashStore/BlockhashStore.bin 12b0662f1636a341c8863bdec7a20f2ddd97c3a4fd1a7ae353fe316609face4e +chain_specific_util_helper: ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper/ChainSpecificUtilHelper.abi ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper/ChainSpecificUtilHelper.bin 5f10664e31abc768f4a37901cae7a3bef90146180f97303e5a1bde5a08d84595 +consumer_wrapper: ../../contracts/solc/v0.7/Consumer/Consumer.abi ../../contracts/solc/v0.7/Consumer/Consumer.bin 894d1cbd920dccbd36d92918c1037c6ded34f66f417ccb18ec3f33c64ef83ec5 +cron_upkeep_factory_wrapper: ../../contracts/solc/v0.8.6/CronUpkeepFactory/CronUpkeepFactory.abi - dacb0f8cdf54ae9d2781c5e720fc314b32ed5e58eddccff512c75d6067292cd7 +cron_upkeep_wrapper: ../../contracts/solc/v0.8.6/CronUpkeepFactory/CronUpkeep.abi - 362fcfcf30a6ab3acff83095ea4b2b9056dd5e9dcb94bc5411aae58995d22709 +dummy_protocol_wrapper: ../../contracts/solc/v0.8.16/DummyProtocol/DummyProtocol.abi ../../contracts/solc/v0.8.16/DummyProtocol/DummyProtocol.bin 583a448170b13abf7ed64e406e8177d78c9e55ab44efd141eee60de23a71ee3b +flags_wrapper: ../../contracts/solc/v0.6/Flags/Flags.abi ../../contracts/solc/v0.6/Flags/Flags.bin 2034d1b562ca37a63068851915e3703980276e8d5f7db6db8a3351a49d69fc4a +flux_aggregator_wrapper: ../../contracts/solc/v0.6/FluxAggregator/FluxAggregator.abi ../../contracts/solc/v0.6/FluxAggregator/FluxAggregator.bin a3b0a6396c4aa3b5ee39b3c4bd45efc89789d4859379a8a92caca3a0496c5794 +gas_wrapper: ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2/KeeperRegistryCheckUpkeepGasUsageWrapper1_2.abi ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2/KeeperRegistryCheckUpkeepGasUsageWrapper1_2.bin 4a5dcdac486d18fcd58e3488c15c1710ae76b977556a3f3191bd269a4bc75723 +gas_wrapper_mock: ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock.abi ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock.bin a9b08f18da59125c6fc305855710241f3d35161b8b9f3e3f635a7b1d5c6da9c8 +i_keeper_registry_master_wrapper_2_1: ../../contracts/solc/v0.8.16/IKeeperRegistryMaster/IKeeperRegistryMaster.abi ../../contracts/solc/v0.8.16/IKeeperRegistryMaster/IKeeperRegistryMaster.bin 6501bb9bcf5048bab2737b00685c6984a24867e234ddf5b60a65904eee9a4ebc +i_log_automation: ../../contracts/solc/v0.8.16/ILogAutomation/ILogAutomation.abi ../../contracts/solc/v0.8.16/ILogAutomation/ILogAutomation.bin 296beccb6af655d6fc3a6e676b244831cce2da6688d3afc4f21f8738ae59e03e +keeper_consumer_performance_wrapper: ../../contracts/solc/v0.8.16/KeeperConsumerPerformance/KeeperConsumerPerformance.abi ../../contracts/solc/v0.8.16/KeeperConsumerPerformance/KeeperConsumerPerformance.bin eeda39f5d3e1c8ffa0fb6cd1803731b98a4bc262d41833458e3fe8b40933ae90 +keeper_consumer_wrapper: ../../contracts/solc/v0.8.16/KeeperConsumer/KeeperConsumer.abi ../../contracts/solc/v0.8.16/KeeperConsumer/KeeperConsumer.bin 2c6163b145082fbab74b7343577a9cec8fda8b0da9daccf2a82581b1f5a84b83 +keeper_registrar_wrapper1_2: ../../contracts/solc/v0.8.6/KeeperRegistrar1_2/KeeperRegistrar.abi ../../contracts/solc/v0.8.6/KeeperRegistrar1_2/KeeperRegistrar.bin e49b2f8b23da17af1ed2209b8ae0968cc04350554d636711e6c24a3ad3118692 +keeper_registrar_wrapper1_2_mock: ../../contracts/solc/v0.8.6/KeeperRegistrar1_2Mock/KeeperRegistrar1_2Mock.abi ../../contracts/solc/v0.8.6/KeeperRegistrar1_2Mock/KeeperRegistrar1_2Mock.bin 5b155a7cb3def309fd7525de1d7cd364ebf8491bdc3060eac08ea0ff55ab29bc +keeper_registrar_wrapper2_0: ../../contracts/solc/v0.8.6/KeeperRegistrar2_0/KeeperRegistrar2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistrar2_0/KeeperRegistrar2_0.bin 647f125c2f0dafabcdc545cb77b15dc2ec3ea9429357806813179b1fd555c2d2 +keeper_registry_logic1_3: ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3/KeeperRegistryLogic1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3/KeeperRegistryLogic1_3.bin 903f8b9c8e25425ca6d0b81b89e339d695a83630bfbfa24a6f3b38869676bc5a +keeper_registry_logic2_0: ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0/KeeperRegistryLogic2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0/KeeperRegistryLogic2_0.bin d69d2bc8e4844293dbc2d45abcddc50b84c88554ecccfa4fa77c0ca45ec80871 +keeper_registry_logic_a_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.bin 77481ab75c9aa86a62a7b2a708599b5ea1a6346ed1c0def6d4826e7ae523f1ee +keeper_registry_logic_b_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1/KeeperRegistryLogicB2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1/KeeperRegistryLogicB2_1.bin 467d10741a04601b136553a2b1c6ab37f2a65d809366faf03180a22ff26be215 +keeper_registry_wrapper1_1: ../../contracts/solc/v0.7/KeeperRegistry1_1/KeeperRegistry1_1.abi ../../contracts/solc/v0.7/KeeperRegistry1_1/KeeperRegistry1_1.bin 6ce079f2738f015f7374673a2816e8e9787143d00b780ea7652c8aa9ad9e1e20 +keeper_registry_wrapper1_1_mock: ../../contracts/solc/v0.7/KeeperRegistry1_1Mock/KeeperRegistry1_1Mock.abi ../../contracts/solc/v0.7/KeeperRegistry1_1Mock/KeeperRegistry1_1Mock.bin 98ddb3680e86359de3b5d17e648253ba29a84703f087a1b52237824003a8c6df +keeper_registry_wrapper1_2: ../../contracts/solc/v0.8.6/KeeperRegistry1_2/KeeperRegistry1_2.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_2/KeeperRegistry1_2.bin a40ff877dd7c280f984cbbb2b428e160662b0c295e881d5f778f941c0088ca22 +keeper_registry_wrapper1_3: ../../contracts/solc/v0.8.6/KeeperRegistry1_3/KeeperRegistry1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_3/KeeperRegistry1_3.bin d4dc760b767ae274ee25c4a604ea371e1fa603a7b6421b69efb2088ad9e8abb3 +keeper_registry_wrapper2_0: ../../contracts/solc/v0.8.6/KeeperRegistry2_0/KeeperRegistry2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistry2_0/KeeperRegistry2_0.bin c32dea7d5ef66b7c58ddc84ddf69aa44df1b3ae8601fbc271c95be4ff5853056 +keeper_registry_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistry2_1/KeeperRegistry2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistry2_1/KeeperRegistry2_1.bin 604e4a0cd980c713929b523b999462a3aa0ed06f96ff563a4c8566cf59c8445b +keepers_vrf_consumer: ../../contracts/solc/v0.8.6/KeepersVRFConsumer/KeepersVRFConsumer.abi ../../contracts/solc/v0.8.6/KeepersVRFConsumer/KeepersVRFConsumer.bin fa75572e689c9e84705c63e8dbe1b7b8aa1a8fe82d66356c4873d024bb9166e8 +log_emitter: ../../contracts/solc/v0.8.19/LogEmitter/LogEmitter.abi ../../contracts/solc/v0.8.19/LogEmitter/LogEmitter.bin 244ba13730c036de0b02beef4e3d9c9a96946ce353c27f366baecc7f5be5a6fd +log_triggered_streams_lookup_wrapper: ../../contracts/solc/v0.8.16/LogTriggeredStreamsLookup/LogTriggeredStreamsLookup.abi ../../contracts/solc/v0.8.16/LogTriggeredStreamsLookup/LogTriggeredStreamsLookup.bin f8da43a927c1a66238a9f4fd5d5dd7e280e361daa0444da1f7f79498ace901e1 +log_upkeep_counter_wrapper: ../../contracts/solc/v0.8.6/LogUpkeepCounter/LogUpkeepCounter.abi ../../contracts/solc/v0.8.6/LogUpkeepCounter/LogUpkeepCounter.bin 42426bbb83f96dfbe55fc576d6c65020eaeed690e2289cf99b0c4aa810a5f4ec +mock_aggregator_proxy: ../../contracts/solc/v0.8.6/MockAggregatorProxy/MockAggregatorProxy.abi ../../contracts/solc/v0.8.6/MockAggregatorProxy/MockAggregatorProxy.bin b16c108f3dd384c342ddff5e94da7c0a8d39d1be5e3d8f2cf61ecc7f0e50ff42 +mock_ethlink_aggregator_wrapper: ../../contracts/solc/v0.6/MockETHLINKAggregator/MockETHLINKAggregator.abi ../../contracts/solc/v0.6/MockETHLINKAggregator/MockETHLINKAggregator.bin 1c52c24f797b8482aa12b8251dcea1c072827bd5b3426b822621261944b99ca0 +mock_gas_aggregator_wrapper: ../../contracts/solc/v0.6/MockGASAggregator/MockGASAggregator.abi ../../contracts/solc/v0.6/MockGASAggregator/MockGASAggregator.bin bacbb1ea4dc6beac0db8a13ca5c75e2fd61b903d70feea9b3b1c8b10fe8df4f3 +multiwordconsumer_wrapper: ../../contracts/solc/v0.7/MultiWordConsumer/MultiWordConsumer.abi ../../contracts/solc/v0.7/MultiWordConsumer/MultiWordConsumer.bin 6e68abdf614e3ed0f5066c1b5f9d7c1199f1e7c5c5251fe8a471344a59afc6ba offchain_aggregator_wrapper: OffchainAggregator/OffchainAggregator.abi - 5c8d6562e94166d4790f1ee6e4321d359d9f7262e6c5452a712b1f1c896f45cf -operator_factory: ../../contracts/solc/v0.8.19/OperatorFactory.abi ../../contracts/solc/v0.8.19/OperatorFactory.bin 0fdfacf8879537b854875608dfca41c6221c342174417112acaa67dfcadafddc -operator_wrapper: ../../contracts/solc/v0.8.19/Operator.abi ../../contracts/solc/v0.8.19/Operator.bin d7abd0e67f30a3a4c9c04c896124391306fa364fcf579fa6df04dbf912b48568 -oracle_wrapper: ../../contracts/solc/v0.6/Oracle.abi ../../contracts/solc/v0.6/Oracle.bin 7af2fbac22a6e8c2847e8e685a5400cac5101d72ddf5365213beb79e4dede43a -perform_data_checker_wrapper: ../../contracts/solc/v0.8.16/PerformDataChecker.abi ../../contracts/solc/v0.8.16/PerformDataChecker.bin 48d8309c2117c29a24e1155917ab0b780956b2cd6a8a39ef06ae66a7f6d94f73 -solidity_vrf_consumer_interface: ../../contracts/solc/v0.6/VRFConsumer.abi ../../contracts/solc/v0.6/VRFConsumer.bin ecc99378aa798014de9db42b2eb81320778b0663dbe208008dad75ccdc1d4366 -solidity_vrf_consumer_interface_v08: ../../contracts/solc/v0.8.6/VRFConsumer.abi ../../contracts/solc/v0.8.6/VRFConsumer.bin b14f9136b15e3dc9d6154d5700f3ed4cf88ddc4f70f20c3bb57fc46050904c8f -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.6/VRFRequestIDBaseTestHelper.abi ../../contracts/solc/v0.8.6/VRFRequestIDBaseTestHelper.bin f2559015d6f3e5d285c57b011be9b2300632e93dd6c4524e58202d6200f09edc -solidity_vrf_v08_verifier_wrapper: ../../contracts/solc/v0.8.6/VRFTestHelper.abi ../../contracts/solc/v0.8.6/VRFTestHelper.bin f37f8b21a81c113085c6137835a2246db6ebda07da455c4f2b5c7ec60c725c3b -solidity_vrf_verifier_wrapper: ../../contracts/solc/v0.6/VRFTestHelper.abi ../../contracts/solc/v0.6/VRFTestHelper.bin 44c2b67d8d2990ab580453deb29d63508c6147a3dc49908a1db563bef06e6474 -solidity_vrf_wrapper: ../../contracts/solc/v0.6/VRF.abi ../../contracts/solc/v0.6/VRF.bin 04ede5b83c06ba5b76ef99c081c72928007d8a7aaefcf21449a46a07cbd4bfc2 -streams_lookup_compatible_interface: ../../contracts/solc/v0.8.16/StreamsLookupCompatibleInterface.abi ../../contracts/solc/v0.8.16/StreamsLookupCompatibleInterface.bin feb92cc666df21ea04ab9d7a588a513847b01b2f66fc167d06ab28ef2b17e015 -streams_lookup_upkeep_wrapper: ../../contracts/solc/v0.8.16/StreamsLookupUpkeep.abi ../../contracts/solc/v0.8.16/StreamsLookupUpkeep.bin b1a598963cacac51ed4706538d0f142bdc0d94b9a4b13e2d402131cdf05c9bcf -test_api_consumer_wrapper: ../../contracts/solc/v0.6/TestAPIConsumer.abi ../../contracts/solc/v0.6/TestAPIConsumer.bin ed10893cb18894c18e275302329c955f14ea2de37ee044f84aa1e067ac5ea71e -trusted_blockhash_store: ../../contracts/solc/v0.8.6/TrustedBlockhashStore.abi ../../contracts/solc/v0.8.6/TrustedBlockhashStore.bin 98cb0dc06c15af5dcd3b53bdfc98e7ed2489edc96a42203294ac2fc0efdda02b -type_and_version_interface_wrapper: ../../contracts/solc/v0.8.6/TypeAndVersionInterface.abi ../../contracts/solc/v0.8.6/TypeAndVersionInterface.bin bc9c3a6e73e3ebd5b58754df0deeb3b33f4bb404d5709bb904aed51d32f4b45e -upkeep_counter_wrapper: ../../contracts/solc/v0.8.16/UpkeepCounter.abi ../../contracts/solc/v0.8.16/UpkeepCounter.bin 77f000229a501f638dd2dc439859257f632894c728b31e68aea4f6d6c52f1b71 -upkeep_perform_counter_restrictive_wrapper: ../../contracts/solc/v0.8.16/UpkeepPerformCounterRestrictive.abi ../../contracts/solc/v0.8.16/UpkeepPerformCounterRestrictive.bin 20955b21acceb58355fa287b29194a73edf5937067ba7140667301017cb2b24c -upkeep_transcoder: ../../contracts/solc/v0.8.6/UpkeepTranscoder.abi ../../contracts/solc/v0.8.6/UpkeepTranscoder.bin 336c92a981597be26508455f81a908a0784a817b129a59686c5b2c4afcba730a -verifiable_load_log_trigger_upkeep_wrapper: ../../contracts/solc/v0.8.16/VerifiableLoadLogTriggerUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadLogTriggerUpkeep.bin fb674ba44c0e8f3b385cd10b2f7dea5cd07b5f38df08066747e8b1542e152557 -verifiable_load_streams_lookup_upkeep_wrapper: ../../contracts/solc/v0.8.16/VerifiableLoadStreamsLookupUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadStreamsLookupUpkeep.bin 785f68c44bfff070505eaa65e38a1af94046e5f9afc1189bcf2c8cfcd1102d66 -verifiable_load_upkeep_wrapper: ../../contracts/solc/v0.8.16/VerifiableLoadUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadUpkeep.bin a3e02c43756ea91e7ce4b81e48c11648f1d12f6663c236780147e41dfa36ebee -vrf_consumer_v2: ../../contracts/solc/v0.8.6/VRFConsumerV2.abi ../../contracts/solc/v0.8.6/VRFConsumerV2.bin 9ef258bf8e9f8d880fd229ceb145593d91e24fc89366baa0bf19169c5787d15f -vrf_consumer_v2_plus_upgradeable_example: ../../contracts/solc/v0.8.6/VRFConsumerV2PlusUpgradeableExample.abi ../../contracts/solc/v0.8.6/VRFConsumerV2PlusUpgradeableExample.bin 3155c611e4d6882e9324b6e975033b31356776ea8b031ca63d63da37589d583b -vrf_consumer_v2_upgradeable_example: ../../contracts/solc/v0.8.6/VRFConsumerV2UpgradeableExample.abi ../../contracts/solc/v0.8.6/VRFConsumerV2UpgradeableExample.bin f1790a9a2f2a04c730593e483459709cb89e897f8a19d7a3ac0cfe6a97265e6e -vrf_coordinator_mock: ../../contracts/solc/v0.8.6/VRFCoordinatorMock.abi ../../contracts/solc/v0.8.6/VRFCoordinatorMock.bin 5c495cf8df1f46d8736b9150cdf174cce358cb8352f60f0d5bb9581e23920501 -vrf_coordinator_v2: ../../contracts/solc/v0.8.6/VRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2.bin 295f35ce282060317dfd01f45959f5a2b05ba26913e422fbd4fb6bf90b107006 -vrf_coordinator_v2_5: ../../contracts/solc/v0.8.6/VRFCoordinatorV2_5.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2_5.bin b0e7c42a30b36d9d31fa9a3f26bad7937152e3dddee5bd8dd3d121390c879ab6 -vrf_coordinator_v2_plus_v2_example: ../../contracts/solc/v0.8.6/VRFCoordinatorV2Plus_V2Example.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2Plus_V2Example.bin 4a5b86701983b1b65f0a8dfa116b3f6d75f8f706fa274004b57bdf5992e4cec3 -vrf_coordinator_v2plus: ../../contracts/solc/v0.8.6/VRFCoordinatorV2Plus.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2Plus.bin e4409bbe361258273458a5c99408b3d7f0cc57a2560dee91c0596cc6d6f738be -vrf_coordinator_v2plus_interface: ../../contracts/solc/v0.8.6/IVRFCoordinatorV2PlusInternal.abi ../../contracts/solc/v0.8.6/IVRFCoordinatorV2PlusInternal.bin 834a2ce0e83276372a0e1446593fd89798f4cf6dc95d4be0113e99fadf61558b -vrf_external_sub_owner_example: ../../contracts/solc/v0.8.6/VRFExternalSubOwnerExample.abi ../../contracts/solc/v0.8.6/VRFExternalSubOwnerExample.bin 14f888eb313930b50233a6f01ea31eba0206b7f41a41f6311670da8bb8a26963 -vrf_load_test_external_sub_owner: ../../contracts/solc/v0.8.6/VRFLoadTestExternalSubOwner.abi ../../contracts/solc/v0.8.6/VRFLoadTestExternalSubOwner.bin 2097faa70265e420036cc8a3efb1f1e0836ad2d7323b295b9a26a125dbbe6c7d -vrf_load_test_ownerless_consumer: ../../contracts/solc/v0.8.6/VRFLoadTestOwnerlessConsumer.abi ../../contracts/solc/v0.8.6/VRFLoadTestOwnerlessConsumer.bin 74f914843cbc70b9c3079c3e1c709382ce415225e8bb40113e7ac018bfcb0f5c -vrf_load_test_with_metrics: ../../contracts/solc/v0.8.6/VRFV2LoadTestWithMetrics.abi ../../contracts/solc/v0.8.6/VRFV2LoadTestWithMetrics.bin 8ab9de5816fbdf93a2865e2711b85a39a6fc9c413a4b336578c485be1158d430 -vrf_malicious_consumer_v2: ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2.abi ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2.bin 9755fa8ffc7f5f0b337d5d413d77b0c9f6cd6f68c31727d49acdf9d4a51bc522 -vrf_malicious_consumer_v2_plus: ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2Plus.abi ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2Plus.bin e2a72638e11da807b6533d037e7e5aaeed695efd5035777b8e20d2f8973a574c -vrf_owner: ../../contracts/solc/v0.8.6/VRFOwner.abi ../../contracts/solc/v0.8.6/VRFOwner.bin eccfae5ee295b5850e22f61240c469f79752b8d9a3bac5d64aec7ac8def2f6cb -vrf_owner_test_consumer: ../../contracts/solc/v0.8.6/VRFV2OwnerTestConsumer.abi ../../contracts/solc/v0.8.6/VRFV2OwnerTestConsumer.bin 0537bbe96c5a8bbd44d0a65fbb7e51f6a9f9e75f4673225845ac1ba33f4e7974 -vrf_ownerless_consumer_example: ../../contracts/solc/v0.8.6/VRFOwnerlessConsumerExample.abi ../../contracts/solc/v0.8.6/VRFOwnerlessConsumerExample.bin 9893b3805863273917fb282eed32274e32aa3d5c2a67a911510133e1218132be -vrf_single_consumer_example: ../../contracts/solc/v0.8.6/VRFSingleConsumerExample.abi ../../contracts/solc/v0.8.6/VRFSingleConsumerExample.bin 892a5ed35da2e933f7fd7835cd6f7f70ef3aa63a9c03a22c5b1fd026711b0ece -vrf_v2_consumer_wrapper: ../../contracts/solc/v0.8.6/VRFv2Consumer.abi ../../contracts/solc/v0.8.6/VRFv2Consumer.bin 12368b3b5e06392440143a13b94c0ea2f79c4c897becc3b060982559e10ace40 -vrf_v2plus_load_test_with_metrics: ../../contracts/solc/v0.8.6/VRFV2PlusLoadTestWithMetrics.abi ../../contracts/solc/v0.8.6/VRFV2PlusLoadTestWithMetrics.bin 0a89cb7ed9dfb42f91e559b03dc351ccdbe14d281a7ab71c63bd3f47eeed7711 -vrf_v2plus_single_consumer: ../../contracts/solc/v0.8.6/VRFV2PlusSingleConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusSingleConsumerExample.bin 6226d05afa1664033b182bfbdde11d5dfb1d4c8e3eb0bd0448c8bfb76f5b96e4 -vrf_v2plus_sub_owner: ../../contracts/solc/v0.8.6/VRFV2PlusExternalSubOwnerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusExternalSubOwnerExample.bin 7541f986571b8a5671a256edc27ae9b8df9bcdff45ac3b96e5609bbfcc320e4e -vrf_v2plus_upgraded_version: ../../contracts/solc/v0.8.6/VRFCoordinatorV2PlusUpgradedVersion.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2PlusUpgradedVersion.bin c0793d86fb6e45342c4424184fe241c16da960c0b4de76816364b933344d0756 -vrfv2_proxy_admin: ../../contracts/solc/v0.8.6/VRFV2ProxyAdmin.abi ../../contracts/solc/v0.8.6/VRFV2ProxyAdmin.bin 402b1103087ffe1aa598854a8f8b38f8cd3de2e3aaa86369e28017a9157f4980 -vrfv2_reverting_example: ../../contracts/solc/v0.8.6/VRFV2RevertingExample.abi ../../contracts/solc/v0.8.6/VRFV2RevertingExample.bin 1ae46f80351d428bd85ba58b9041b2a608a1845300d79a8fed83edf96606de87 -vrfv2_transparent_upgradeable_proxy: ../../contracts/solc/v0.8.6/VRFV2TransparentUpgradeableProxy.abi ../../contracts/solc/v0.8.6/VRFV2TransparentUpgradeableProxy.bin fe1a8e6852fbd06d91f64315c5cede86d340891f5b5cc981fb5b86563f7eac3f -vrfv2_wrapper: ../../contracts/solc/v0.8.6/VRFV2Wrapper.abi ../../contracts/solc/v0.8.6/VRFV2Wrapper.bin d5e9a982325d2d4f517c4f2bc818795f61555408ef4b38fb59b923d144970e38 -vrfv2_wrapper_consumer_example: ../../contracts/solc/v0.8.6/VRFV2WrapperConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2WrapperConsumerExample.bin 3c5c9f1c501e697a7e77e959b48767e2a0bb1372393fd7686f7aaef3eb794231 -vrfv2_wrapper_interface: ../../contracts/solc/v0.8.6/VRFV2WrapperInterface.abi ../../contracts/solc/v0.8.6/VRFV2WrapperInterface.bin ff8560169de171a68b360b7438d13863682d07040d984fd0fb096b2379421003 -vrfv2plus_client: ../../contracts/solc/v0.8.6/VRFV2PlusClient.abi ../../contracts/solc/v0.8.6/VRFV2PlusClient.bin 3ffbfa4971a7e5f46051a26b1722613f265d89ea1867547ecec58500953a9501 -vrfv2plus_consumer_example: ../../contracts/solc/v0.8.6/VRFV2PlusConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusConsumerExample.bin 2c480a6d7955d33a00690fdd943486d95802e48a03f3cc243df314448e4ddb2c -vrfv2plus_malicious_migrator: ../../contracts/solc/v0.8.6/VRFV2PlusMaliciousMigrator.abi ../../contracts/solc/v0.8.6/VRFV2PlusMaliciousMigrator.bin 80dbc98be5e42246960c889d29488f978d3db0127e95e9b295352c481d8c9b07 -vrfv2plus_reverting_example: ../../contracts/solc/v0.8.6/VRFV2PlusRevertingExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusRevertingExample.bin 6c9053a94f90b8151964d3311310478b57744fbbd153e8ee742ed570e1e49798 -vrfv2plus_wrapper: ../../contracts/solc/v0.8.6/VRFV2PlusWrapper.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapper.bin 934bafba386b934f491827e535306726069f4cafef9125079ea88abf0d808877 -vrfv2plus_wrapper_consumer_example: ../../contracts/solc/v0.8.6/VRFV2PlusWrapperConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapperConsumerExample.bin a14c4c6e2299cd963a8f0ed069e61dd135af5aad4c13a94f6ea7e086eced7191 -vrfv2plus_wrapper_load_test_consumer: ../../contracts/solc/v0.8.6/VRFV2PlusWrapperLoadTestConsumer.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapperLoadTestConsumer.bin 55e3bd534045125fb6579a201ab766185e9b0fac5737b4f37897bb69c9f599fa +operator_factory: ../../contracts/solc/v0.8.19/OperatorFactory/OperatorFactory.abi ../../contracts/solc/v0.8.19/OperatorFactory/OperatorFactory.bin 0fdfacf8879537b854875608dfca41c6221c342174417112acaa67dfcadafddc +operator_wrapper: ../../contracts/solc/v0.8.19/Operator/Operator.abi ../../contracts/solc/v0.8.19/Operator/Operator.bin d7abd0e67f30a3a4c9c04c896124391306fa364fcf579fa6df04dbf912b48568 +oracle_wrapper: ../../contracts/solc/v0.6/Oracle/Oracle.abi ../../contracts/solc/v0.6/Oracle/Oracle.bin 7af2fbac22a6e8c2847e8e685a5400cac5101d72ddf5365213beb79e4dede43a +perform_data_checker_wrapper: ../../contracts/solc/v0.8.16/PerformDataChecker/PerformDataChecker.abi ../../contracts/solc/v0.8.16/PerformDataChecker/PerformDataChecker.bin 48d8309c2117c29a24e1155917ab0b780956b2cd6a8a39ef06ae66a7f6d94f73 +simple_log_upkeep_counter_wrapper: ../../contracts/solc/v0.8.6/SimpleLogUpkeepCounter/SimpleLogUpkeepCounter.abi ../../contracts/solc/v0.8.6/SimpleLogUpkeepCounter/SimpleLogUpkeepCounter.bin 0a7a0cc4da7dc2a3d0a0c36c746b1adc044af5cad1838367356a0604f3255a01 +solidity_vrf_consumer_interface: ../../contracts/solc/v0.6/VRFConsumer/VRFConsumer.abi ../../contracts/solc/v0.6/VRFConsumer/VRFConsumer.bin ecc99378aa798014de9db42b2eb81320778b0663dbe208008dad75ccdc1d4366 +solidity_vrf_consumer_interface_v08: ../../contracts/solc/v0.8.6/VRFConsumer/VRFConsumer.abi ../../contracts/solc/v0.8.6/VRFConsumer/VRFConsumer.bin b14f9136b15e3dc9d6154d5700f3ed4cf88ddc4f70f20c3bb57fc46050904c8f +solidity_vrf_coordinator_interface: ../../contracts/solc/v0.6/VRFCoordinator/VRFCoordinator.abi ../../contracts/solc/v0.6/VRFCoordinator/VRFCoordinator.bin a23d3c395156804788c7f6fbda2994e8f7184304c0f0c9f2c4ddeaf073d346d2 +solidity_vrf_request_id: ../../contracts/solc/v0.6/VRFRequestIDBaseTestHelper/VRFRequestIDBaseTestHelper.abi ../../contracts/solc/v0.6/VRFRequestIDBaseTestHelper/VRFRequestIDBaseTestHelper.bin 383b59e861732c1911ddb7b002c6158608496ce889979296527215fd0366b318 +solidity_vrf_request_id_v08: ../../contracts/solc/v0.8.6/VRFRequestIDBaseTestHelper/VRFRequestIDBaseTestHelper.abi ../../contracts/solc/v0.8.6/VRFRequestIDBaseTestHelper/VRFRequestIDBaseTestHelper.bin f2559015d6f3e5d285c57b011be9b2300632e93dd6c4524e58202d6200f09edc +solidity_vrf_v08_verifier_wrapper: ../../contracts/solc/v0.8.6/VRFTestHelper/VRFTestHelper.abi ../../contracts/solc/v0.8.6/VRFTestHelper/VRFTestHelper.bin f37f8b21a81c113085c6137835a2246db6ebda07da455c4f2b5c7ec60c725c3b +solidity_vrf_verifier_wrapper: ../../contracts/solc/v0.6/VRFTestHelper/VRFTestHelper.abi ../../contracts/solc/v0.6/VRFTestHelper/VRFTestHelper.bin 44c2b67d8d2990ab580453deb29d63508c6147a3dc49908a1db563bef06e6474 +solidity_vrf_wrapper: ../../contracts/solc/v0.6/VRF/VRF.abi ../../contracts/solc/v0.6/VRF/VRF.bin 04ede5b83c06ba5b76ef99c081c72928007d8a7aaefcf21449a46a07cbd4bfc2 +streams_lookup_compatible_interface: ../../contracts/solc/v0.8.16/StreamsLookupCompatibleInterface/StreamsLookupCompatibleInterface.abi ../../contracts/solc/v0.8.16/StreamsLookupCompatibleInterface/StreamsLookupCompatibleInterface.bin feb92cc666df21ea04ab9d7a588a513847b01b2f66fc167d06ab28ef2b17e015 +streams_lookup_upkeep_wrapper: ../../contracts/solc/v0.8.16/StreamsLookupUpkeep/StreamsLookupUpkeep.abi ../../contracts/solc/v0.8.16/StreamsLookupUpkeep/StreamsLookupUpkeep.bin b1a598963cacac51ed4706538d0f142bdc0d94b9a4b13e2d402131cdf05c9bcf +test_api_consumer_wrapper: ../../contracts/solc/v0.6/TestAPIConsumer/TestAPIConsumer.abi ../../contracts/solc/v0.6/TestAPIConsumer/TestAPIConsumer.bin ed10893cb18894c18e275302329c955f14ea2de37ee044f84aa1e067ac5ea71e +trusted_blockhash_store: ../../contracts/solc/v0.8.6/TrustedBlockhashStore/TrustedBlockhashStore.abi ../../contracts/solc/v0.8.6/TrustedBlockhashStore/TrustedBlockhashStore.bin 98cb0dc06c15af5dcd3b53bdfc98e7ed2489edc96a42203294ac2fc0efdda02b +type_and_version_interface_wrapper: ../../contracts/solc/v0.8.6/KeeperRegistry1_2/TypeAndVersionInterface.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_2/TypeAndVersionInterface.bin bc9c3a6e73e3ebd5b58754df0deeb3b33f4bb404d5709bb904aed51d32f4b45e +upkeep_counter_wrapper: ../../contracts/solc/v0.8.16/UpkeepCounter/UpkeepCounter.abi ../../contracts/solc/v0.8.16/UpkeepCounter/UpkeepCounter.bin 77f000229a501f638dd2dc439859257f632894c728b31e68aea4f6d6c52f1b71 +upkeep_perform_counter_restrictive_wrapper: ../../contracts/solc/v0.8.16/UpkeepPerformCounterRestrictive/UpkeepPerformCounterRestrictive.abi ../../contracts/solc/v0.8.16/UpkeepPerformCounterRestrictive/UpkeepPerformCounterRestrictive.bin 20955b21acceb58355fa287b29194a73edf5937067ba7140667301017cb2b24c +upkeep_transcoder: ../../contracts/solc/v0.8.6/UpkeepTranscoder/UpkeepTranscoder.abi ../../contracts/solc/v0.8.6/UpkeepTranscoder/UpkeepTranscoder.bin 336c92a981597be26508455f81a908a0784a817b129a59686c5b2c4afcba730a +verifiable_load_log_trigger_upkeep_wrapper: ../../contracts/solc/v0.8.16/VerifiableLoadLogTriggerUpkeep/VerifiableLoadLogTriggerUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadLogTriggerUpkeep/VerifiableLoadLogTriggerUpkeep.bin fb674ba44c0e8f3b385cd10b2f7dea5cd07b5f38df08066747e8b1542e152557 +verifiable_load_streams_lookup_upkeep_wrapper: ../../contracts/solc/v0.8.16/VerifiableLoadStreamsLookupUpkeep/VerifiableLoadStreamsLookupUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadStreamsLookupUpkeep/VerifiableLoadStreamsLookupUpkeep.bin 785f68c44bfff070505eaa65e38a1af94046e5f9afc1189bcf2c8cfcd1102d66 +verifiable_load_upkeep_wrapper: ../../contracts/solc/v0.8.16/VerifiableLoadUpkeep/VerifiableLoadUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadUpkeep/VerifiableLoadUpkeep.bin a3e02c43756ea91e7ce4b81e48c11648f1d12f6663c236780147e41dfa36ebee +vrf_consumer_v2: ../../contracts/solc/v0.8.6/VRFConsumerV2/VRFConsumerV2.abi ../../contracts/solc/v0.8.6/VRFConsumerV2/VRFConsumerV2.bin 9ef258bf8e9f8d880fd229ceb145593d91e24fc89366baa0bf19169c5787d15f +vrf_consumer_v2_plus_upgradeable_example: ../../contracts/solc/v0.8.6/VRFConsumerV2PlusUpgradeableExample/VRFConsumerV2PlusUpgradeableExample.abi ../../contracts/solc/v0.8.6/VRFConsumerV2PlusUpgradeableExample/VRFConsumerV2PlusUpgradeableExample.bin 3155c611e4d6882e9324b6e975033b31356776ea8b031ca63d63da37589d583b +vrf_consumer_v2_upgradeable_example: ../../contracts/solc/v0.8.6/VRFConsumerV2UpgradeableExample/VRFConsumerV2UpgradeableExample.abi ../../contracts/solc/v0.8.6/VRFConsumerV2UpgradeableExample/VRFConsumerV2UpgradeableExample.bin f1790a9a2f2a04c730593e483459709cb89e897f8a19d7a3ac0cfe6a97265e6e +vrf_coordinator_mock: ../../contracts/solc/v0.8.6/VRFCoordinatorMock/VRFCoordinatorMock.abi ../../contracts/solc/v0.8.6/VRFCoordinatorMock/VRFCoordinatorMock.bin 5c495cf8df1f46d8736b9150cdf174cce358cb8352f60f0d5bb9581e23920501 +vrf_coordinator_v2: ../../contracts/solc/v0.8.6/VRFCoordinatorV2/VRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2/VRFCoordinatorV2.bin 295f35ce282060317dfd01f45959f5a2b05ba26913e422fbd4fb6bf90b107006 +vrf_coordinator_v2_5: ../../contracts/solc/v0.8.6/VRFCoordinatorV2_5/VRFCoordinatorV2_5.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2_5/VRFCoordinatorV2_5.bin b0e7c42a30b36d9d31fa9a3f26bad7937152e3dddee5bd8dd3d121390c879ab6 +vrf_coordinator_v2_plus_v2_example: ../../contracts/solc/v0.8.6/VRFCoordinatorV2Plus_V2Example/VRFCoordinatorV2Plus_V2Example.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2Plus_V2Example/VRFCoordinatorV2Plus_V2Example.bin 4a5b86701983b1b65f0a8dfa116b3f6d75f8f706fa274004b57bdf5992e4cec3 +vrf_coordinator_v2plus_interface: ../../contracts/solc/v0.8.6/IVRFCoordinatorV2PlusInternal/IVRFCoordinatorV2PlusInternal.abi ../../contracts/solc/v0.8.6/IVRFCoordinatorV2PlusInternal/IVRFCoordinatorV2PlusInternal.bin 834a2ce0e83276372a0e1446593fd89798f4cf6dc95d4be0113e99fadf61558b +vrf_external_sub_owner_example: ../../contracts/solc/v0.8.6/VRFExternalSubOwnerExample/VRFExternalSubOwnerExample.abi ../../contracts/solc/v0.8.6/VRFExternalSubOwnerExample/VRFExternalSubOwnerExample.bin 14f888eb313930b50233a6f01ea31eba0206b7f41a41f6311670da8bb8a26963 +vrf_load_test_external_sub_owner: ../../contracts/solc/v0.8.6/VRFLoadTestExternalSubOwner/VRFLoadTestExternalSubOwner.abi ../../contracts/solc/v0.8.6/VRFLoadTestExternalSubOwner/VRFLoadTestExternalSubOwner.bin 2097faa70265e420036cc8a3efb1f1e0836ad2d7323b295b9a26a125dbbe6c7d +vrf_load_test_ownerless_consumer: ../../contracts/solc/v0.8.6/VRFLoadTestOwnerlessConsumer/VRFLoadTestOwnerlessConsumer.abi ../../contracts/solc/v0.8.6/VRFLoadTestOwnerlessConsumer/VRFLoadTestOwnerlessConsumer.bin 74f914843cbc70b9c3079c3e1c709382ce415225e8bb40113e7ac018bfcb0f5c +vrf_load_test_with_metrics: ../../contracts/solc/v0.8.6/VRFV2LoadTestWithMetrics/VRFV2LoadTestWithMetrics.abi ../../contracts/solc/v0.8.6/VRFV2LoadTestWithMetrics/VRFV2LoadTestWithMetrics.bin 8ab9de5816fbdf93a2865e2711b85a39a6fc9c413a4b336578c485be1158d430 +vrf_malicious_consumer_v2: ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2/VRFMaliciousConsumerV2.abi ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2/VRFMaliciousConsumerV2.bin 9755fa8ffc7f5f0b337d5d413d77b0c9f6cd6f68c31727d49acdf9d4a51bc522 +vrf_malicious_consumer_v2_plus: ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2Plus/VRFMaliciousConsumerV2Plus.abi ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2Plus/VRFMaliciousConsumerV2Plus.bin e2a72638e11da807b6533d037e7e5aaeed695efd5035777b8e20d2f8973a574c +vrf_owner: ../../contracts/solc/v0.8.6/VRFOwner/VRFOwner.abi ../../contracts/solc/v0.8.6/VRFOwner/VRFOwner.bin eccfae5ee295b5850e22f61240c469f79752b8d9a3bac5d64aec7ac8def2f6cb +vrf_owner_test_consumer: ../../contracts/solc/v0.8.6/VRFV2OwnerTestConsumer/VRFV2OwnerTestConsumer.abi ../../contracts/solc/v0.8.6/VRFV2OwnerTestConsumer/VRFV2OwnerTestConsumer.bin 0537bbe96c5a8bbd44d0a65fbb7e51f6a9f9e75f4673225845ac1ba33f4e7974 +vrf_ownerless_consumer_example: ../../contracts/solc/v0.8.6/VRFOwnerlessConsumerExample/VRFOwnerlessConsumerExample.abi ../../contracts/solc/v0.8.6/VRFOwnerlessConsumerExample/VRFOwnerlessConsumerExample.bin 9893b3805863273917fb282eed32274e32aa3d5c2a67a911510133e1218132be +vrf_single_consumer_example: ../../contracts/solc/v0.8.6/VRFSingleConsumerExample/VRFSingleConsumerExample.abi ../../contracts/solc/v0.8.6/VRFSingleConsumerExample/VRFSingleConsumerExample.bin 892a5ed35da2e933f7fd7835cd6f7f70ef3aa63a9c03a22c5b1fd026711b0ece +vrf_v2_consumer_wrapper: ../../contracts/solc/v0.8.6/VRFv2Consumer/VRFv2Consumer.abi ../../contracts/solc/v0.8.6/VRFv2Consumer/VRFv2Consumer.bin 12368b3b5e06392440143a13b94c0ea2f79c4c897becc3b060982559e10ace40 +vrf_v2plus_load_test_with_metrics: ../../contracts/solc/v0.8.6/VRFV2PlusLoadTestWithMetrics/VRFV2PlusLoadTestWithMetrics.abi ../../contracts/solc/v0.8.6/VRFV2PlusLoadTestWithMetrics/VRFV2PlusLoadTestWithMetrics.bin 0a89cb7ed9dfb42f91e559b03dc351ccdbe14d281a7ab71c63bd3f47eeed7711 +vrf_v2plus_single_consumer: ../../contracts/solc/v0.8.6/VRFV2PlusSingleConsumerExample/VRFV2PlusSingleConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusSingleConsumerExample/VRFV2PlusSingleConsumerExample.bin 6226d05afa1664033b182bfbdde11d5dfb1d4c8e3eb0bd0448c8bfb76f5b96e4 +vrf_v2plus_sub_owner: ../../contracts/solc/v0.8.6/VRFV2PlusExternalSubOwnerExample/VRFV2PlusExternalSubOwnerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusExternalSubOwnerExample/VRFV2PlusExternalSubOwnerExample.bin 7541f986571b8a5671a256edc27ae9b8df9bcdff45ac3b96e5609bbfcc320e4e +vrf_v2plus_upgraded_version: ../../contracts/solc/v0.8.6/VRFCoordinatorV2PlusUpgradedVersion/VRFCoordinatorV2PlusUpgradedVersion.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2PlusUpgradedVersion/VRFCoordinatorV2PlusUpgradedVersion.bin c0793d86fb6e45342c4424184fe241c16da960c0b4de76816364b933344d0756 +vrfv2_proxy_admin: ../../contracts/solc/v0.8.6/VRFV2ProxyAdmin/VRFV2ProxyAdmin.abi ../../contracts/solc/v0.8.6/VRFV2ProxyAdmin/VRFV2ProxyAdmin.bin 402b1103087ffe1aa598854a8f8b38f8cd3de2e3aaa86369e28017a9157f4980 +vrfv2_reverting_example: ../../contracts/solc/v0.8.6/VRFV2RevertingExample/VRFV2RevertingExample.abi ../../contracts/solc/v0.8.6/VRFV2RevertingExample/VRFV2RevertingExample.bin 1ae46f80351d428bd85ba58b9041b2a608a1845300d79a8fed83edf96606de87 +vrfv2_transparent_upgradeable_proxy: ../../contracts/solc/v0.8.6/VRFV2TransparentUpgradeableProxy/VRFV2TransparentUpgradeableProxy.abi ../../contracts/solc/v0.8.6/VRFV2TransparentUpgradeableProxy/VRFV2TransparentUpgradeableProxy.bin fe1a8e6852fbd06d91f64315c5cede86d340891f5b5cc981fb5b86563f7eac3f +vrfv2_wrapper: ../../contracts/solc/v0.8.6/VRFV2Wrapper/VRFV2Wrapper.abi ../../contracts/solc/v0.8.6/VRFV2Wrapper/VRFV2Wrapper.bin d5e9a982325d2d4f517c4f2bc818795f61555408ef4b38fb59b923d144970e38 +vrfv2_wrapper_consumer_example: ../../contracts/solc/v0.8.6/VRFV2WrapperConsumerExample/VRFV2WrapperConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2WrapperConsumerExample/VRFV2WrapperConsumerExample.bin 3c5c9f1c501e697a7e77e959b48767e2a0bb1372393fd7686f7aaef3eb794231 +vrfv2_wrapper_interface: ../../contracts/solc/v0.8.6/VRFV2WrapperInterface/VRFV2WrapperInterface.abi ../../contracts/solc/v0.8.6/VRFV2WrapperInterface/VRFV2WrapperInterface.bin ff8560169de171a68b360b7438d13863682d07040d984fd0fb096b2379421003 +vrfv2plus_client: ../../contracts/solc/v0.8.6/VRFV2PlusClient/VRFV2PlusClient.abi ../../contracts/solc/v0.8.6/VRFV2PlusClient/VRFV2PlusClient.bin 3ffbfa4971a7e5f46051a26b1722613f265d89ea1867547ecec58500953a9501 +vrfv2plus_consumer_example: ../../contracts/solc/v0.8.6/VRFV2PlusConsumerExample/VRFV2PlusConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusConsumerExample/VRFV2PlusConsumerExample.bin 2c480a6d7955d33a00690fdd943486d95802e48a03f3cc243df314448e4ddb2c +vrfv2plus_malicious_migrator: ../../contracts/solc/v0.8.6/VRFV2PlusMaliciousMigrator/VRFV2PlusMaliciousMigrator.abi ../../contracts/solc/v0.8.6/VRFV2PlusMaliciousMigrator/VRFV2PlusMaliciousMigrator.bin 80dbc98be5e42246960c889d29488f978d3db0127e95e9b295352c481d8c9b07 +vrfv2plus_reverting_example: ../../contracts/solc/v0.8.6/VRFV2PlusRevertingExample/VRFV2PlusRevertingExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusRevertingExample/VRFV2PlusRevertingExample.bin 6c9053a94f90b8151964d3311310478b57744fbbd153e8ee742ed570e1e49798 +vrfv2plus_wrapper: ../../contracts/solc/v0.8.6/VRFV2PlusWrapper/VRFV2PlusWrapper.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapper/VRFV2PlusWrapper.bin 934bafba386b934f491827e535306726069f4cafef9125079ea88abf0d808877 +vrfv2plus_wrapper_consumer_example: ../../contracts/solc/v0.8.6/VRFV2PlusWrapperConsumerExample/VRFV2PlusWrapperConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapperConsumerExample/VRFV2PlusWrapperConsumerExample.bin a14c4c6e2299cd963a8f0ed069e61dd135af5aad4c13a94f6ea7e086eced7191 +vrfv2plus_wrapper_load_test_consumer: ../../contracts/solc/v0.8.6/VRFV2PlusWrapperLoadTestConsumer/VRFV2PlusWrapperLoadTestConsumer.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapperLoadTestConsumer/VRFV2PlusWrapperLoadTestConsumer.bin 55e3bd534045125fb6579a201ab766185e9b0fac5737b4f37897bb69c9f599fa diff --git a/core/gethwrappers/go_generate.go b/core/gethwrappers/go_generate.go index ce553dc8186..07e4fa9b8f3 100644 --- a/core/gethwrappers/go_generate.go +++ b/core/gethwrappers/go_generate.go @@ -2,142 +2,143 @@ // golang packages, using abigen. package gethwrappers -// Make sure solidity compiler artifacts are up to date. Only output stdout on failure. +// Make sure solidity compiler artifacts are up-to-date. Only output stdout on failure. //go:generate ./generation/compile_contracts.sh -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/FluxAggregator.abi ../../contracts/solc/v0.6/FluxAggregator.bin FluxAggregator flux_aggregator_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/VRF.abi ../../contracts/solc/v0.6/VRF.bin VRF solidity_vrf_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/VRFTestHelper.abi ../../contracts/solc/v0.6/VRFTestHelper.bin VRFTestHelper solidity_vrf_verifier_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/VRFCoordinator.abi ../../contracts/solc/v0.6/VRFCoordinator.bin VRFCoordinator solidity_vrf_coordinator_interface -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/VRFConsumer.abi ../../contracts/solc/v0.6/VRFConsumer.bin VRFConsumer solidity_vrf_consumer_interface -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/VRFRequestIDBaseTestHelper.abi ../../contracts/solc/v0.6/VRFRequestIDBaseTestHelper.bin VRFRequestIDBaseTestHelper solidity_vrf_request_id -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/Flags.abi ../../contracts/solc/v0.6/Flags.bin Flags flags_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/Oracle.abi ../../contracts/solc/v0.6/Oracle.bin Oracle oracle_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/TestAPIConsumer.abi ../../contracts/solc/v0.6/TestAPIConsumer.bin TestAPIConsumer test_api_consumer_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/MockETHLINKAggregator.abi ../../contracts/solc/v0.6/MockETHLINKAggregator.bin MockETHLINKAggregator mock_ethlink_aggregator_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/MockGASAggregator.abi ../../contracts/solc/v0.6/MockGASAggregator.bin MockGASAggregator mock_gas_aggregator_wrapper - -//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.8.19/Operator.abi ../../contracts/solc/v0.8.19/Operator.bin Operator operator_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/OperatorFactory.abi ../../contracts/solc/v0.8.19/OperatorFactory.bin OperatorFactory operator_factory -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/AuthorizedForwarder.abi ../../contracts/solc/v0.8.19/AuthorizedForwarder.bin AuthorizedForwarder authorized_forwarder -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/AuthorizedReceiver.abi ../../contracts/solc/v0.8.19/AuthorizedReceiver.bin AuthorizedReceiver authorized_receiver +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/FluxAggregator/FluxAggregator.abi ../../contracts/solc/v0.6/FluxAggregator/FluxAggregator.bin FluxAggregator flux_aggregator_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/VRF/VRF.abi ../../contracts/solc/v0.6/VRF/VRF.bin VRF solidity_vrf_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/VRFTestHelper/VRFTestHelper.abi ../../contracts/solc/v0.6/VRFTestHelper/VRFTestHelper.bin VRFTestHelper solidity_vrf_verifier_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/VRFCoordinator/VRFCoordinator.abi ../../contracts/solc/v0.6/VRFCoordinator/VRFCoordinator.bin VRFCoordinator solidity_vrf_coordinator_interface +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/VRFConsumer/VRFConsumer.abi ../../contracts/solc/v0.6/VRFConsumer/VRFConsumer.bin VRFConsumer solidity_vrf_consumer_interface +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/VRFRequestIDBaseTestHelper/VRFRequestIDBaseTestHelper.abi ../../contracts/solc/v0.6/VRFRequestIDBaseTestHelper/VRFRequestIDBaseTestHelper.bin VRFRequestIDBaseTestHelper solidity_vrf_request_id +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/Flags/Flags.abi ../../contracts/solc/v0.6/Flags/Flags.bin Flags flags_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/Oracle/Oracle.abi ../../contracts/solc/v0.6/Oracle/Oracle.bin Oracle oracle_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/TestAPIConsumer/TestAPIConsumer.abi ../../contracts/solc/v0.6/TestAPIConsumer/TestAPIConsumer.bin TestAPIConsumer test_api_consumer_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/MockETHLINKAggregator/MockETHLINKAggregator.abi ../../contracts/solc/v0.6/MockETHLINKAggregator/MockETHLINKAggregator.bin MockETHLINKAggregator mock_ethlink_aggregator_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.6/MockGASAggregator/MockGASAggregator.abi ../../contracts/solc/v0.6/MockGASAggregator/MockGASAggregator.bin MockGASAggregator mock_gas_aggregator_wrapper + +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.7/Consumer/Consumer.abi ../../contracts/solc/v0.7/Consumer/Consumer.bin Consumer consumer_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.7/MultiWordConsumer/MultiWordConsumer.abi ../../contracts/solc/v0.7/MultiWordConsumer/MultiWordConsumer.bin MultiWordConsumer multiwordconsumer_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/Operator/Operator.abi ../../contracts/solc/v0.8.19/Operator/Operator.bin Operator operator_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/OperatorFactory/OperatorFactory.abi ../../contracts/solc/v0.8.19/OperatorFactory/OperatorFactory.bin OperatorFactory operator_factory +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/AuthorizedForwarder/AuthorizedForwarder.abi ../../contracts/solc/v0.8.19/AuthorizedForwarder/AuthorizedForwarder.bin AuthorizedForwarder authorized_forwarder +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/AuthorizedReceiver/AuthorizedReceiver.abi ../../contracts/solc/v0.8.19/AuthorizedReceiver/AuthorizedReceiver.bin AuthorizedReceiver authorized_receiver //go:generate go run ./generation/generate/wrap.go OffchainAggregator/OffchainAggregator.abi - OffchainAggregator offchain_aggregator_wrapper // Automation -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.7/KeeperRegistry1_1.abi ../../contracts/solc/v0.7/KeeperRegistry1_1.bin KeeperRegistry keeper_registry_wrapper1_1 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.7/KeeperRegistry1_1Mock.abi ../../contracts/solc/v0.7/KeeperRegistry1_1Mock.bin KeeperRegistryMock keeper_registry_wrapper1_1_mock -//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.8.6/CronUpkeepFactory.abi - CronUpkeepFactory cron_upkeep_factory_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/CronUpkeep.abi - CronUpkeep cron_upkeep_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistrar.abi ../../contracts/solc/v0.8.6/KeeperRegistrar.bin KeeperRegistrar keeper_registrar_wrapper1_2 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistrar1_2Mock.abi ../../contracts/solc/v0.8.6/KeeperRegistrar1_2Mock.bin KeeperRegistrarMock keeper_registrar_wrapper1_2_mock -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistry1_2.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_2.bin KeeperRegistry keeper_registry_wrapper1_2 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/TypeAndVersionInterface.abi ../../contracts/solc/v0.8.6/TypeAndVersionInterface.bin TypeAndVersionInterface type_and_version_interface_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2.abi ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2.bin KeeperRegistryCheckUpkeepGasUsageWrapper gas_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock.abi ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock.bin KeeperRegistryCheckUpkeepGasUsageWrapperMock gas_wrapper_mock -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistry1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_3.bin KeeperRegistry keeper_registry_wrapper1_3 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3.bin KeeperRegistryLogic keeper_registry_logic1_3 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistrar2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistrar2_0.bin KeeperRegistrar keeper_registrar_wrapper2_0 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistry2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistry2_0.bin KeeperRegistry keeper_registry_wrapper2_0 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0.bin KeeperRegistryLogic keeper_registry_logic2_0 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/UpkeepTranscoder.abi ../../contracts/solc/v0.8.6/UpkeepTranscoder.bin UpkeepTranscoder upkeep_transcoder -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/VerifiableLoadUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadUpkeep.bin VerifiableLoadUpkeep verifiable_load_upkeep_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/VerifiableLoadStreamsLookupUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadStreamsLookupUpkeep.bin VerifiableLoadStreamsLookupUpkeep verifiable_load_streams_lookup_upkeep_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/VerifiableLoadLogTriggerUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadLogTriggerUpkeep.bin VerifiableLoadLogTriggerUpkeep verifiable_load_log_trigger_upkeep_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/StreamsLookupUpkeep.abi ../../contracts/solc/v0.8.16/StreamsLookupUpkeep.bin StreamsLookupUpkeep streams_lookup_upkeep_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/StreamsLookupCompatibleInterface.abi ../../contracts/solc/v0.8.16/StreamsLookupCompatibleInterface.bin StreamsLookupCompatibleInterface streams_lookup_compatible_interface -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/AutomationConsumerBenchmark.abi ../../contracts/solc/v0.8.16/AutomationConsumerBenchmark.bin AutomationConsumerBenchmark automation_consumer_benchmark -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/AutomationRegistrar2_1.abi ../../contracts/solc/v0.8.16/AutomationRegistrar2_1.bin AutomationRegistrar automation_registrar_wrapper2_1 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/KeeperRegistry2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistry2_1.bin KeeperRegistry keeper_registry_wrapper_2_1 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1.bin KeeperRegistryLogicA keeper_registry_logic_a_wrapper_2_1 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1.bin KeeperRegistryLogicB keeper_registry_logic_b_wrapper_2_1 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/IKeeperRegistryMaster.abi ../../contracts/solc/v0.8.16/IKeeperRegistryMaster.bin IKeeperRegistryMaster i_keeper_registry_master_wrapper_2_1 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/ILogAutomation.abi ../../contracts/solc/v0.8.16/ILogAutomation.bin ILogAutomation i_log_automation -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/AutomationUtils2_1.abi ../../contracts/solc/v0.8.16/AutomationUtils2_1.bin AutomationUtils automation_utils_2_1 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/AutomationForwarderLogic.abi ../../contracts/solc/v0.8.16/AutomationForwarderLogic.bin AutomationForwarderLogic automation_forwarder_logic -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/LogUpkeepCounter.abi ../../contracts/solc/v0.8.6/LogUpkeepCounter.bin LogUpkeepCounter log_upkeep_counter_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/LogTriggeredStreamsLookup.abi ../../contracts/solc/v0.8.16/LogTriggeredStreamsLookup.bin LogTriggeredStreamsLookup log_triggered_streams_lookup_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/DummyProtocol.abi ../../contracts/solc/v0.8.16/DummyProtocol.bin DummyProtocol dummy_protocol_wrapper - -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/KeeperConsumer.abi ../../contracts/solc/v0.8.16/KeeperConsumer.bin KeeperConsumer keeper_consumer_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/KeeperConsumerPerformance.abi ../../contracts/solc/v0.8.16/KeeperConsumerPerformance.bin KeeperConsumerPerformance keeper_consumer_performance_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/PerformDataChecker.abi ../../contracts/solc/v0.8.16/PerformDataChecker.bin PerformDataChecker perform_data_checker_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/UpkeepCounter.abi ../../contracts/solc/v0.8.16/UpkeepCounter.bin UpkeepCounter upkeep_counter_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/UpkeepPerformCounterRestrictive.abi ../../contracts/solc/v0.8.16/UpkeepPerformCounterRestrictive.bin UpkeepPerformCounterRestrictive upkeep_perform_counter_restrictive_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.7/KeeperRegistry1_1/KeeperRegistry1_1.abi ../../contracts/solc/v0.7/KeeperRegistry1_1/KeeperRegistry1_1.bin KeeperRegistry keeper_registry_wrapper1_1 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.7/KeeperRegistry1_1Mock/KeeperRegistry1_1Mock.abi ../../contracts/solc/v0.7/KeeperRegistry1_1Mock/KeeperRegistry1_1Mock.bin KeeperRegistryMock keeper_registry_wrapper1_1_mock +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.7/UpkeepPerformCounterRestrictive/UpkeepPerformCounterRestrictive.abi ../../contracts/solc/v0.7/UpkeepPerformCounterRestrictive/UpkeepPerformCounterRestrictive.bin UpkeepPerformCounterRestrictive upkeep_perform_counter_restrictive_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.7/UpkeepCounter/UpkeepCounter.abi ../../contracts/solc/v0.7/UpkeepCounter/UpkeepCounter.bin UpkeepCounter upkeep_counter_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/CronUpkeepFactory/CronUpkeepFactory.abi - CronUpkeepFactory cron_upkeep_factory_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/CronUpkeepFactory/CronUpkeep.abi - CronUpkeep cron_upkeep_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistrar1_2/KeeperRegistrar.abi ../../contracts/solc/v0.8.6/KeeperRegistrar1_2/KeeperRegistrar.bin KeeperRegistrar keeper_registrar_wrapper1_2 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistrar1_2Mock/KeeperRegistrar1_2Mock.abi ../../contracts/solc/v0.8.6/KeeperRegistrar1_2Mock/KeeperRegistrar1_2Mock.bin KeeperRegistrarMock keeper_registrar_wrapper1_2_mock +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistry1_2/KeeperRegistry1_2.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_2/KeeperRegistry1_2.bin KeeperRegistry keeper_registry_wrapper1_2 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistry1_2/TypeAndVersionInterface.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_2/TypeAndVersionInterface.bin TypeAndVersionInterface type_and_version_interface_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2/KeeperRegistryCheckUpkeepGasUsageWrapper1_2.abi ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2/KeeperRegistryCheckUpkeepGasUsageWrapper1_2.bin KeeperRegistryCheckUpkeepGasUsageWrapper gas_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock.abi ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock/KeeperRegistryCheckUpkeepGasUsageWrapper1_2Mock.bin KeeperRegistryCheckUpkeepGasUsageWrapperMock gas_wrapper_mock +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistry1_3/KeeperRegistry1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_3/KeeperRegistry1_3.bin KeeperRegistry keeper_registry_wrapper1_3 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3/KeeperRegistryLogic1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3/KeeperRegistryLogic1_3.bin KeeperRegistryLogic keeper_registry_logic1_3 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistrar2_0/KeeperRegistrar2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistrar2_0/KeeperRegistrar2_0.bin KeeperRegistrar keeper_registrar_wrapper2_0 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistry2_0/KeeperRegistry2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistry2_0/KeeperRegistry2_0.bin KeeperRegistry keeper_registry_wrapper2_0 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0/KeeperRegistryLogic2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0/KeeperRegistryLogic2_0.bin KeeperRegistryLogic keeper_registry_logic2_0 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/UpkeepTranscoder/UpkeepTranscoder.abi ../../contracts/solc/v0.8.6/UpkeepTranscoder/UpkeepTranscoder.bin UpkeepTranscoder upkeep_transcoder +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/VerifiableLoadUpkeep/VerifiableLoadUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadUpkeep/VerifiableLoadUpkeep.bin VerifiableLoadUpkeep verifiable_load_upkeep_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/VerifiableLoadStreamsLookupUpkeep/VerifiableLoadStreamsLookupUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadStreamsLookupUpkeep/VerifiableLoadStreamsLookupUpkeep.bin VerifiableLoadStreamsLookupUpkeep verifiable_load_streams_lookup_upkeep_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/VerifiableLoadLogTriggerUpkeep/VerifiableLoadLogTriggerUpkeep.abi ../../contracts/solc/v0.8.16/VerifiableLoadLogTriggerUpkeep/VerifiableLoadLogTriggerUpkeep.bin VerifiableLoadLogTriggerUpkeep verifiable_load_log_trigger_upkeep_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/StreamsLookupUpkeep/StreamsLookupUpkeep.abi ../../contracts/solc/v0.8.16/StreamsLookupUpkeep/StreamsLookupUpkeep.bin StreamsLookupUpkeep streams_lookup_upkeep_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/StreamsLookupCompatibleInterface/StreamsLookupCompatibleInterface.abi ../../contracts/solc/v0.8.16/StreamsLookupCompatibleInterface/StreamsLookupCompatibleInterface.bin StreamsLookupCompatibleInterface streams_lookup_compatible_interface +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/AutomationConsumerBenchmark/AutomationConsumerBenchmark.abi ../../contracts/solc/v0.8.16/AutomationConsumerBenchmark/AutomationConsumerBenchmark.bin AutomationConsumerBenchmark automation_consumer_benchmark +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/AutomationRegistrar2_1/AutomationRegistrar2_1.abi ../../contracts/solc/v0.8.16/AutomationRegistrar2_1/AutomationRegistrar2_1.bin AutomationRegistrar automation_registrar_wrapper2_1 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/KeeperRegistry2_1/KeeperRegistry2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistry2_1/KeeperRegistry2_1.bin KeeperRegistry keeper_registry_wrapper_2_1 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.bin KeeperRegistryLogicA keeper_registry_logic_a_wrapper_2_1 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1/KeeperRegistryLogicB2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1/KeeperRegistryLogicB2_1.bin KeeperRegistryLogicB keeper_registry_logic_b_wrapper_2_1 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/IKeeperRegistryMaster/IKeeperRegistryMaster.abi ../../contracts/solc/v0.8.16/IKeeperRegistryMaster/IKeeperRegistryMaster.bin IKeeperRegistryMaster i_keeper_registry_master_wrapper_2_1 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/ILogAutomation/ILogAutomation.abi ../../contracts/solc/v0.8.16/ILogAutomation/ILogAutomation.bin ILogAutomation i_log_automation +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.abi ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.bin AutomationUtils automation_utils_2_1 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/AutomationForwarderLogic/AutomationForwarderLogic.abi ../../contracts/solc/v0.8.16/AutomationForwarderLogic/AutomationForwarderLogic.bin AutomationForwarderLogic automation_forwarder_logic +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/LogUpkeepCounter/LogUpkeepCounter.abi ../../contracts/solc/v0.8.6/LogUpkeepCounter/LogUpkeepCounter.bin LogUpkeepCounter log_upkeep_counter_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/SimpleLogUpkeepCounter/SimpleLogUpkeepCounter.abi ../../contracts/solc/v0.8.6/SimpleLogUpkeepCounter/SimpleLogUpkeepCounter.bin SimpleLogUpkeepCounter simple_log_upkeep_counter_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/LogTriggeredStreamsLookup/LogTriggeredStreamsLookup.abi ../../contracts/solc/v0.8.16/LogTriggeredStreamsLookup/LogTriggeredStreamsLookup.bin LogTriggeredStreamsLookup log_triggered_streams_lookup_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/DummyProtocol/DummyProtocol.abi ../../contracts/solc/v0.8.16/DummyProtocol/DummyProtocol.bin DummyProtocol dummy_protocol_wrapper + +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/KeeperConsumer/KeeperConsumer.abi ../../contracts/solc/v0.8.16/KeeperConsumer/KeeperConsumer.bin KeeperConsumer keeper_consumer_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/KeeperConsumerPerformance/KeeperConsumerPerformance.abi ../../contracts/solc/v0.8.16/KeeperConsumerPerformance/KeeperConsumerPerformance.bin KeeperConsumerPerformance keeper_consumer_performance_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/PerformDataChecker/PerformDataChecker.abi ../../contracts/solc/v0.8.16/PerformDataChecker/PerformDataChecker.bin PerformDataChecker perform_data_checker_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/UpkeepCounter/UpkeepCounter.abi ../../contracts/solc/v0.8.16/UpkeepCounter/UpkeepCounter.bin UpkeepCounter upkeep_counter_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.16/UpkeepPerformCounterRestrictive/UpkeepPerformCounterRestrictive.abi ../../contracts/solc/v0.8.16/UpkeepPerformCounterRestrictive/UpkeepPerformCounterRestrictive.bin UpkeepPerformCounterRestrictive upkeep_perform_counter_restrictive_wrapper // v0.8.6 VRFConsumer -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFCoordinatorMock.abi ../../contracts/solc/v0.8.6/VRFCoordinatorMock.bin VRFCoordinatorMock vrf_coordinator_mock -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFConsumer.abi ../../contracts/solc/v0.8.6/VRFConsumer.bin VRFConsumer solidity_vrf_consumer_interface_v08 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFRequestIDBaseTestHelper.abi ../../contracts/solc/v0.8.6/VRFRequestIDBaseTestHelper.bin VRFRequestIDBaseTestHelper solidity_vrf_request_id_v08 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFOwnerlessConsumerExample.abi ../../contracts/solc/v0.8.6/VRFOwnerlessConsumerExample.bin VRFOwnerlessConsumerExample vrf_ownerless_consumer_example -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFLoadTestOwnerlessConsumer.abi ../../contracts/solc/v0.8.6/VRFLoadTestOwnerlessConsumer.bin VRFLoadTestOwnerlessConsumer vrf_load_test_ownerless_consumer -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFLoadTestExternalSubOwner.abi ../../contracts/solc/v0.8.6/VRFLoadTestExternalSubOwner.bin VRFLoadTestExternalSubOwner vrf_load_test_external_sub_owner -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2LoadTestWithMetrics.abi ../../contracts/solc/v0.8.6/VRFV2LoadTestWithMetrics.bin VRFV2LoadTestWithMetrics vrf_load_test_with_metrics -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2OwnerTestConsumer.abi ../../contracts/solc/v0.8.6/VRFV2OwnerTestConsumer.bin VRFV2OwnerTestConsumer vrf_owner_test_consumer -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFv2Consumer.abi ../../contracts/solc/v0.8.6/VRFv2Consumer.bin VRFv2Consumer vrf_v2_consumer_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFCoordinatorMock/VRFCoordinatorMock.abi ../../contracts/solc/v0.8.6/VRFCoordinatorMock/VRFCoordinatorMock.bin VRFCoordinatorMock vrf_coordinator_mock +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFConsumer/VRFConsumer.abi ../../contracts/solc/v0.8.6/VRFConsumer/VRFConsumer.bin VRFConsumer solidity_vrf_consumer_interface_v08 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFRequestIDBaseTestHelper/VRFRequestIDBaseTestHelper.abi ../../contracts/solc/v0.8.6/VRFRequestIDBaseTestHelper/VRFRequestIDBaseTestHelper.bin VRFRequestIDBaseTestHelper solidity_vrf_request_id_v08 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFOwnerlessConsumerExample/VRFOwnerlessConsumerExample.abi ../../contracts/solc/v0.8.6/VRFOwnerlessConsumerExample/VRFOwnerlessConsumerExample.bin VRFOwnerlessConsumerExample vrf_ownerless_consumer_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFLoadTestOwnerlessConsumer/VRFLoadTestOwnerlessConsumer.abi ../../contracts/solc/v0.8.6/VRFLoadTestOwnerlessConsumer/VRFLoadTestOwnerlessConsumer.bin VRFLoadTestOwnerlessConsumer vrf_load_test_ownerless_consumer +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFLoadTestExternalSubOwner/VRFLoadTestExternalSubOwner.abi ../../contracts/solc/v0.8.6/VRFLoadTestExternalSubOwner/VRFLoadTestExternalSubOwner.bin VRFLoadTestExternalSubOwner vrf_load_test_external_sub_owner +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2LoadTestWithMetrics/VRFV2LoadTestWithMetrics.abi ../../contracts/solc/v0.8.6/VRFV2LoadTestWithMetrics/VRFV2LoadTestWithMetrics.bin VRFV2LoadTestWithMetrics vrf_load_test_with_metrics +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2OwnerTestConsumer/VRFV2OwnerTestConsumer.abi ../../contracts/solc/v0.8.6/VRFV2OwnerTestConsumer/VRFV2OwnerTestConsumer.bin VRFV2OwnerTestConsumer vrf_owner_test_consumer +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFv2Consumer/VRFv2Consumer.abi ../../contracts/solc/v0.8.6/VRFv2Consumer/VRFv2Consumer.bin VRFv2Consumer vrf_v2_consumer_wrapper //go:generate go run ./generation/generate_link/wrap_link.go // VRF V2 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/BlockhashStore.abi ../../contracts/solc/v0.8.6/BlockhashStore.bin BlockhashStore blockhash_store -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/BatchBlockhashStore.abi ../../contracts/solc/v0.8.6/BatchBlockhashStore.bin BatchBlockhashStore batch_blockhash_store -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2.bin BatchVRFCoordinatorV2 batch_vrf_coordinator_v2 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFOwner.abi ../../contracts/solc/v0.8.6/VRFOwner.bin VRFOwner vrf_owner -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2.bin VRFCoordinatorV2 vrf_coordinator_v2 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFConsumerV2.abi ../../contracts/solc/v0.8.6/VRFConsumerV2.bin VRFConsumerV2 vrf_consumer_v2 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2.abi ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2.bin VRFMaliciousConsumerV2 vrf_malicious_consumer_v2 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/BlockhashStore/BlockhashStore.abi ../../contracts/solc/v0.8.6/BlockhashStore/BlockhashStore.bin BlockhashStore blockhash_store +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/BatchBlockhashStore/BatchBlockhashStore.abi ../../contracts/solc/v0.8.6/BatchBlockhashStore/BatchBlockhashStore.bin BatchBlockhashStore batch_blockhash_store +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2/BatchVRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2/BatchVRFCoordinatorV2.bin BatchVRFCoordinatorV2 batch_vrf_coordinator_v2 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFOwner/VRFOwner.abi ../../contracts/solc/v0.8.6/VRFOwner/VRFOwner.bin VRFOwner vrf_owner +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFCoordinatorV2/VRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2/VRFCoordinatorV2.bin VRFCoordinatorV2 vrf_coordinator_v2 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFConsumerV2/VRFConsumerV2.abi ../../contracts/solc/v0.8.6/VRFConsumerV2/VRFConsumerV2.bin VRFConsumerV2 vrf_consumer_v2 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2/VRFMaliciousConsumerV2.abi ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2/VRFMaliciousConsumerV2.bin VRFMaliciousConsumerV2 vrf_malicious_consumer_v2 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFTestHelper.abi ../../contracts/solc/v0.8.6/VRFTestHelper.bin VRFV08TestHelper solidity_vrf_v08_verifier_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFSingleConsumerExample.abi ../../contracts/solc/v0.8.6/VRFSingleConsumerExample.bin VRFSingleConsumerExample vrf_single_consumer_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFTestHelper/VRFTestHelper.abi ../../contracts/solc/v0.8.6/VRFTestHelper/VRFTestHelper.bin VRFV08TestHelper solidity_vrf_v08_verifier_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFSingleConsumerExample/VRFSingleConsumerExample.abi ../../contracts/solc/v0.8.6/VRFSingleConsumerExample/VRFSingleConsumerExample.bin VRFSingleConsumerExample vrf_single_consumer_example -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFExternalSubOwnerExample.abi ../../contracts/solc/v0.8.6/VRFExternalSubOwnerExample.bin VRFExternalSubOwnerExample vrf_external_sub_owner_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFExternalSubOwnerExample/VRFExternalSubOwnerExample.abi ../../contracts/solc/v0.8.6/VRFExternalSubOwnerExample/VRFExternalSubOwnerExample.bin VRFExternalSubOwnerExample vrf_external_sub_owner_example -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2RevertingExample.abi ../../contracts/solc/v0.8.6/VRFV2RevertingExample.bin VRFV2RevertingExample vrfv2_reverting_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2RevertingExample/VRFV2RevertingExample.abi ../../contracts/solc/v0.8.6/VRFV2RevertingExample/VRFV2RevertingExample.bin VRFV2RevertingExample vrfv2_reverting_example -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFConsumerV2UpgradeableExample.abi ../../contracts/solc/v0.8.6/VRFConsumerV2UpgradeableExample.bin VRFConsumerV2UpgradeableExample vrf_consumer_v2_upgradeable_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFConsumerV2UpgradeableExample/VRFConsumerV2UpgradeableExample.abi ../../contracts/solc/v0.8.6/VRFConsumerV2UpgradeableExample/VRFConsumerV2UpgradeableExample.bin VRFConsumerV2UpgradeableExample vrf_consumer_v2_upgradeable_example -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2TransparentUpgradeableProxy.abi ../../contracts/solc/v0.8.6/VRFV2TransparentUpgradeableProxy.bin VRFV2TransparentUpgradeableProxy vrfv2_transparent_upgradeable_proxy -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2ProxyAdmin.abi ../../contracts/solc/v0.8.6/VRFV2ProxyAdmin.bin VRFV2ProxyAdmin vrfv2_proxy_admin -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper.abi ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper.bin ChainSpecificUtilHelper chain_specific_util_helper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2TransparentUpgradeableProxy/VRFV2TransparentUpgradeableProxy.abi ../../contracts/solc/v0.8.6/VRFV2TransparentUpgradeableProxy/VRFV2TransparentUpgradeableProxy.bin VRFV2TransparentUpgradeableProxy vrfv2_transparent_upgradeable_proxy +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2ProxyAdmin/VRFV2ProxyAdmin.abi ../../contracts/solc/v0.8.6/VRFV2ProxyAdmin/VRFV2ProxyAdmin.bin VRFV2ProxyAdmin vrfv2_proxy_admin +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper/ChainSpecificUtilHelper.abi ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper/ChainSpecificUtilHelper.bin ChainSpecificUtilHelper chain_specific_util_helper // VRF V2 Wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2Wrapper.abi ../../contracts/solc/v0.8.6/VRFV2Wrapper.bin VRFV2Wrapper vrfv2_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2WrapperInterface.abi ../../contracts/solc/v0.8.6/VRFV2WrapperInterface.bin VRFV2WrapperInterface vrfv2_wrapper_interface -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2WrapperConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2WrapperConsumerExample.bin VRFV2WrapperConsumerExample vrfv2_wrapper_consumer_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2Wrapper/VRFV2Wrapper.abi ../../contracts/solc/v0.8.6/VRFV2Wrapper/VRFV2Wrapper.bin VRFV2Wrapper vrfv2_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2WrapperInterface/VRFV2WrapperInterface.abi ../../contracts/solc/v0.8.6/VRFV2WrapperInterface/VRFV2WrapperInterface.bin VRFV2WrapperInterface vrfv2_wrapper_interface +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2WrapperConsumerExample/VRFV2WrapperConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2WrapperConsumerExample/VRFV2WrapperConsumerExample.bin VRFV2WrapperConsumerExample vrfv2_wrapper_consumer_example // Keepers X VRF v2 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeepersVRFConsumer.abi ../../contracts/solc/v0.8.6/KeepersVRFConsumer.bin KeepersVRFConsumer keepers_vrf_consumer +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/KeepersVRFConsumer/KeepersVRFConsumer.abi ../../contracts/solc/v0.8.6/KeepersVRFConsumer/KeepersVRFConsumer.bin KeepersVRFConsumer keepers_vrf_consumer // VRF V2Plus -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/IVRFCoordinatorV2PlusInternal.abi ../../contracts/solc/v0.8.6/IVRFCoordinatorV2PlusInternal.bin IVRFCoordinatorV2PlusInternal vrf_coordinator_v2plus_interface -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2Plus.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2Plus.bin BatchVRFCoordinatorV2Plus batch_vrf_coordinator_v2plus -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/TrustedBlockhashStore.abi ../../contracts/solc/v0.8.6/TrustedBlockhashStore.bin TrustedBlockhashStore trusted_blockhash_store -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusConsumerExample.bin VRFV2PlusConsumerExample vrfv2plus_consumer_example -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFCoordinatorV2_5.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2_5.bin VRFCoordinatorV2_5 vrf_coordinator_v2_5 -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusWrapper.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapper.bin VRFV2PlusWrapper vrfv2plus_wrapper -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusWrapperConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapperConsumerExample.bin VRFV2PlusWrapperConsumerExample vrfv2plus_wrapper_consumer_example -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2Plus.abi ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2Plus.bin VRFMaliciousConsumerV2Plus vrf_malicious_consumer_v2_plus -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusSingleConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusSingleConsumerExample.bin VRFV2PlusSingleConsumerExample vrf_v2plus_single_consumer -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusExternalSubOwnerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusExternalSubOwnerExample.bin VRFV2PlusExternalSubOwnerExample vrf_v2plus_sub_owner -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusRevertingExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusRevertingExample.bin VRFV2PlusRevertingExample vrfv2plus_reverting_example -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFConsumerV2PlusUpgradeableExample.abi ../../contracts/solc/v0.8.6/VRFConsumerV2PlusUpgradeableExample.bin VRFConsumerV2PlusUpgradeableExample vrf_consumer_v2_plus_upgradeable_example -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusClient.abi ../../contracts/solc/v0.8.6/VRFV2PlusClient.bin VRFV2PlusClient vrfv2plus_client -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFCoordinatorV2Plus_V2Example.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2Plus_V2Example.bin VRFCoordinatorV2Plus_V2Example vrf_coordinator_v2_plus_v2_example -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusMaliciousMigrator.abi ../../contracts/solc/v0.8.6/VRFV2PlusMaliciousMigrator.bin VRFV2PlusMaliciousMigrator vrfv2plus_malicious_migrator -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusLoadTestWithMetrics.abi ../../contracts/solc/v0.8.6/VRFV2PlusLoadTestWithMetrics.bin VRFV2PlusLoadTestWithMetrics vrf_v2plus_load_test_with_metrics -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFCoordinatorV2PlusUpgradedVersion.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2PlusUpgradedVersion.bin VRFCoordinatorV2PlusUpgradedVersion vrf_v2plus_upgraded_version -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusWrapperLoadTestConsumer.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapperLoadTestConsumer.bin VRFV2PlusWrapperLoadTestConsumer vrfv2plus_wrapper_load_test_consumer +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/IVRFCoordinatorV2PlusInternal/IVRFCoordinatorV2PlusInternal.abi ../../contracts/solc/v0.8.6/IVRFCoordinatorV2PlusInternal/IVRFCoordinatorV2PlusInternal.bin IVRFCoordinatorV2PlusInternal vrf_coordinator_v2plus_interface +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2Plus/BatchVRFCoordinatorV2Plus.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2Plus/BatchVRFCoordinatorV2Plus.bin BatchVRFCoordinatorV2Plus batch_vrf_coordinator_v2plus +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/TrustedBlockhashStore/TrustedBlockhashStore.abi ../../contracts/solc/v0.8.6/TrustedBlockhashStore/TrustedBlockhashStore.bin TrustedBlockhashStore trusted_blockhash_store +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusConsumerExample/VRFV2PlusConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusConsumerExample/VRFV2PlusConsumerExample.bin VRFV2PlusConsumerExample vrfv2plus_consumer_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFCoordinatorV2_5/VRFCoordinatorV2_5.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2_5/VRFCoordinatorV2_5.bin VRFCoordinatorV2_5 vrf_coordinator_v2_5 +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusWrapper/VRFV2PlusWrapper.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapper/VRFV2PlusWrapper.bin VRFV2PlusWrapper vrfv2plus_wrapper +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusWrapperConsumerExample/VRFV2PlusWrapperConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapperConsumerExample/VRFV2PlusWrapperConsumerExample.bin VRFV2PlusWrapperConsumerExample vrfv2plus_wrapper_consumer_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2Plus/VRFMaliciousConsumerV2Plus.abi ../../contracts/solc/v0.8.6/VRFMaliciousConsumerV2Plus/VRFMaliciousConsumerV2Plus.bin VRFMaliciousConsumerV2Plus vrf_malicious_consumer_v2_plus +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusSingleConsumerExample/VRFV2PlusSingleConsumerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusSingleConsumerExample/VRFV2PlusSingleConsumerExample.bin VRFV2PlusSingleConsumerExample vrf_v2plus_single_consumer +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusExternalSubOwnerExample/VRFV2PlusExternalSubOwnerExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusExternalSubOwnerExample/VRFV2PlusExternalSubOwnerExample.bin VRFV2PlusExternalSubOwnerExample vrf_v2plus_sub_owner +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusRevertingExample/VRFV2PlusRevertingExample.abi ../../contracts/solc/v0.8.6/VRFV2PlusRevertingExample/VRFV2PlusRevertingExample.bin VRFV2PlusRevertingExample vrfv2plus_reverting_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFConsumerV2PlusUpgradeableExample/VRFConsumerV2PlusUpgradeableExample.abi ../../contracts/solc/v0.8.6/VRFConsumerV2PlusUpgradeableExample/VRFConsumerV2PlusUpgradeableExample.bin VRFConsumerV2PlusUpgradeableExample vrf_consumer_v2_plus_upgradeable_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusClient/VRFV2PlusClient.abi ../../contracts/solc/v0.8.6/VRFV2PlusClient/VRFV2PlusClient.bin VRFV2PlusClient vrfv2plus_client +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFCoordinatorV2Plus_V2Example/VRFCoordinatorV2Plus_V2Example.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2Plus_V2Example/VRFCoordinatorV2Plus_V2Example.bin VRFCoordinatorV2Plus_V2Example vrf_coordinator_v2_plus_v2_example +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusMaliciousMigrator/VRFV2PlusMaliciousMigrator.abi ../../contracts/solc/v0.8.6/VRFV2PlusMaliciousMigrator/VRFV2PlusMaliciousMigrator.bin VRFV2PlusMaliciousMigrator vrfv2plus_malicious_migrator +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusLoadTestWithMetrics/VRFV2PlusLoadTestWithMetrics.abi ../../contracts/solc/v0.8.6/VRFV2PlusLoadTestWithMetrics/VRFV2PlusLoadTestWithMetrics.bin VRFV2PlusLoadTestWithMetrics vrf_v2plus_load_test_with_metrics +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFCoordinatorV2PlusUpgradedVersion/VRFCoordinatorV2PlusUpgradedVersion.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2PlusUpgradedVersion/VRFCoordinatorV2PlusUpgradedVersion.bin VRFCoordinatorV2PlusUpgradedVersion vrf_v2plus_upgraded_version +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/VRFV2PlusWrapperLoadTestConsumer/VRFV2PlusWrapperLoadTestConsumer.abi ../../contracts/solc/v0.8.6/VRFV2PlusWrapperLoadTestConsumer/VRFV2PlusWrapperLoadTestConsumer.bin VRFV2PlusWrapperLoadTestConsumer vrfv2plus_wrapper_load_test_consumer // Aggregators -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/AggregatorV2V3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface.bin AggregatorV2V3Interface aggregator_v2v3_interface -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/AggregatorV3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV3Interface.bin AggregatorV3Interface aggregator_v3_interface -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/MockAggregatorProxy.abi ../../contracts/solc/v0.8.6/MockAggregatorProxy.bin MockAggregatorProxy mock_aggregator_proxy +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.bin AggregatorV2V3Interface aggregator_v2v3_interface +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV3Interface.bin AggregatorV3Interface aggregator_v3_interface +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/MockAggregatorProxy/MockAggregatorProxy.abi ../../contracts/solc/v0.8.6/MockAggregatorProxy/MockAggregatorProxy.bin MockAggregatorProxy mock_aggregator_proxy // Log tester -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/LogEmitter.abi ../../contracts/solc/v0.8.19/LogEmitter.bin LogEmitter log_emitter +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/LogEmitter/LogEmitter.abi ../../contracts/solc/v0.8.19/LogEmitter/LogEmitter.bin LogEmitter log_emitter // Chainlink Functions //go:generate go generate ./functions diff --git a/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go b/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go index 846ebb197b8..ad0ff294789 100644 --- a/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go +++ b/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go @@ -55,7 +55,7 @@ func DeployErroredVerifier(auth *bind.TransactOpts, backend bind.ContractBackend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &ErroredVerifier{ErroredVerifierCaller: ErroredVerifierCaller{contract: contract}, ErroredVerifierTransactor: ErroredVerifierTransactor{contract: contract}, ErroredVerifierFilterer: ErroredVerifierFilterer{contract: contract}}, nil + return address, tx, &ErroredVerifier{address: address, abi: *parsed, ErroredVerifierCaller: ErroredVerifierCaller{contract: contract}, ErroredVerifierTransactor: ErroredVerifierTransactor{contract: contract}, ErroredVerifierFilterer: ErroredVerifierFilterer{contract: contract}}, nil } type ErroredVerifier struct { diff --git a/core/gethwrappers/llo-feeds/generated/exposed_verifier/exposed_verifier.go b/core/gethwrappers/llo-feeds/generated/exposed_verifier/exposed_verifier.go index 2ca74b7cf3d..e27cb58d157 100644 --- a/core/gethwrappers/llo-feeds/generated/exposed_verifier/exposed_verifier.go +++ b/core/gethwrappers/llo-feeds/generated/exposed_verifier/exposed_verifier.go @@ -50,7 +50,7 @@ func DeployExposedVerifier(auth *bind.TransactOpts, backend bind.ContractBackend if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &ExposedVerifier{ExposedVerifierCaller: ExposedVerifierCaller{contract: contract}, ExposedVerifierTransactor: ExposedVerifierTransactor{contract: contract}, ExposedVerifierFilterer: ExposedVerifierFilterer{contract: contract}}, nil + return address, tx, &ExposedVerifier{address: address, abi: *parsed, ExposedVerifierCaller: ExposedVerifierCaller{contract: contract}, ExposedVerifierTransactor: ExposedVerifierTransactor{contract: contract}, ExposedVerifierFilterer: ExposedVerifierFilterer{contract: contract}}, nil } type ExposedVerifier struct { diff --git a/core/gethwrappers/llo-feeds/generated/fee_manager/fee_manager.go b/core/gethwrappers/llo-feeds/generated/fee_manager/fee_manager.go index 666edd3385d..742ec91bf97 100644 --- a/core/gethwrappers/llo-feeds/generated/fee_manager/fee_manager.go +++ b/core/gethwrappers/llo-feeds/generated/fee_manager/fee_manager.go @@ -67,7 +67,7 @@ func DeployFeeManager(auth *bind.TransactOpts, backend bind.ContractBackend, _li if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &FeeManager{FeeManagerCaller: FeeManagerCaller{contract: contract}, FeeManagerTransactor: FeeManagerTransactor{contract: contract}, FeeManagerFilterer: FeeManagerFilterer{contract: contract}}, nil + return address, tx, &FeeManager{address: address, abi: *parsed, FeeManagerCaller: FeeManagerCaller{contract: contract}, FeeManagerTransactor: FeeManagerTransactor{contract: contract}, FeeManagerFilterer: FeeManagerFilterer{contract: contract}}, nil } type FeeManager struct { diff --git a/core/gethwrappers/llo-feeds/generated/reward_manager/reward_manager.go b/core/gethwrappers/llo-feeds/generated/reward_manager/reward_manager.go index 224fb37ef63..c870a403016 100644 --- a/core/gethwrappers/llo-feeds/generated/reward_manager/reward_manager.go +++ b/core/gethwrappers/llo-feeds/generated/reward_manager/reward_manager.go @@ -62,7 +62,7 @@ func DeployRewardManager(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &RewardManager{RewardManagerCaller: RewardManagerCaller{contract: contract}, RewardManagerTransactor: RewardManagerTransactor{contract: contract}, RewardManagerFilterer: RewardManagerFilterer{contract: contract}}, nil + return address, tx, &RewardManager{address: address, abi: *parsed, RewardManagerCaller: RewardManagerCaller{contract: contract}, RewardManagerTransactor: RewardManagerTransactor{contract: contract}, RewardManagerFilterer: RewardManagerFilterer{contract: contract}}, nil } type RewardManager struct { diff --git a/core/gethwrappers/llo-feeds/generated/verifier/verifier.go b/core/gethwrappers/llo-feeds/generated/verifier/verifier.go index 993de18eb57..09bf78b23b5 100644 --- a/core/gethwrappers/llo-feeds/generated/verifier/verifier.go +++ b/core/gethwrappers/llo-feeds/generated/verifier/verifier.go @@ -57,7 +57,7 @@ func DeployVerifier(auth *bind.TransactOpts, backend bind.ContractBackend, verif if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &Verifier{VerifierCaller: VerifierCaller{contract: contract}, VerifierTransactor: VerifierTransactor{contract: contract}, VerifierFilterer: VerifierFilterer{contract: contract}}, nil + return address, tx, &Verifier{address: address, abi: *parsed, VerifierCaller: VerifierCaller{contract: contract}, VerifierTransactor: VerifierTransactor{contract: contract}, VerifierFilterer: VerifierFilterer{contract: contract}}, nil } type Verifier struct { diff --git a/core/gethwrappers/llo-feeds/generated/verifier_proxy/verifier_proxy.go b/core/gethwrappers/llo-feeds/generated/verifier_proxy/verifier_proxy.go index 5ed70fef20a..fc7f10b6415 100644 --- a/core/gethwrappers/llo-feeds/generated/verifier_proxy/verifier_proxy.go +++ b/core/gethwrappers/llo-feeds/generated/verifier_proxy/verifier_proxy.go @@ -57,7 +57,7 @@ func DeployVerifierProxy(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &VerifierProxy{VerifierProxyCaller: VerifierProxyCaller{contract: contract}, VerifierProxyTransactor: VerifierProxyTransactor{contract: contract}, VerifierProxyFilterer: VerifierProxyFilterer{contract: contract}}, nil + return address, tx, &VerifierProxy{address: address, abi: *parsed, VerifierProxyCaller: VerifierProxyCaller{contract: contract}, VerifierProxyTransactor: VerifierProxyTransactor{contract: contract}, VerifierProxyFilterer: VerifierProxyFilterer{contract: contract}}, nil } type VerifierProxy struct { diff --git a/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt index abc3b47db2c..293defcfbe0 100644 --- a/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,10 +1,10 @@ GETH_VERSION: 1.12.0 -errored_verifier: ../../../contracts/solc/v0.8.16/ErroredVerifier.abi ../../../contracts/solc/v0.8.16/ErroredVerifier.bin 510d18a58bfda646be35e46491baf73041eb333a349615465b20e2b5b41c5f73 -exposed_verifier: ../../../contracts/solc/v0.8.16/ExposedVerifier.abi ../../../contracts/solc/v0.8.16/ExposedVerifier.bin 6932cea8f2738e874d3ec9e1a4231d2421704030c071d9e15dd2f7f08482c246 -fee_manager: ../../../contracts/solc/v0.8.16/FeeManager.abi ../../../contracts/solc/v0.8.16/FeeManager.bin 1b852df75bfabcc2b57539e84309cd57f9e693a2bb6b25a50e4a6101ccf32c49 +errored_verifier: ../../../contracts/solc/v0.8.16/ErroredVerifier/ErroredVerifier.abi ../../../contracts/solc/v0.8.16/ErroredVerifier/ErroredVerifier.bin 510d18a58bfda646be35e46491baf73041eb333a349615465b20e2b5b41c5f73 +exposed_verifier: ../../../contracts/solc/v0.8.16/ExposedVerifier/ExposedVerifier.abi ../../../contracts/solc/v0.8.16/ExposedVerifier/ExposedVerifier.bin 6932cea8f2738e874d3ec9e1a4231d2421704030c071d9e15dd2f7f08482c246 +fee_manager: ../../../contracts/solc/v0.8.16/FeeManager/FeeManager.abi ../../../contracts/solc/v0.8.16/FeeManager/FeeManager.bin 1b852df75bfabcc2b57539e84309cd57f9e693a2bb6b25a50e4a6101ccf32c49 llo_feeds: ../../../contracts/solc/v0.8.16/FeeManager.abi ../../../contracts/solc/v0.8.16/FeeManager.bin cb71e018f67e49d7bc0e194c822204dfd59f79ff42e4fc8fd8ab63f3acd71361 llo_feeds_test: ../../../contracts/solc/v0.8.16/ExposedVerifier.abi ../../../contracts/solc/v0.8.16/ExposedVerifier.bin 6932cea8f2738e874d3ec9e1a4231d2421704030c071d9e15dd2f7f08482c246 -reward_manager: ../../../contracts/solc/v0.8.16/RewardManager.abi ../../../contracts/solc/v0.8.16/RewardManager.bin db73e9062b17a1d5aa14c06881fe2be49bd95b00b7f1a8943910c5e4ded5b221 -verifier: ../../../contracts/solc/v0.8.16/Verifier.abi ../../../contracts/solc/v0.8.16/Verifier.bin df12786bbeccf3a8f3389479cf93c055b4efd5904b9f99a4835f81af43fe62bf -verifier_proxy: ../../../contracts/solc/v0.8.16/VerifierProxy.abi ../../../contracts/solc/v0.8.16/VerifierProxy.bin 6393443d0a323f2dbe9687dc30fd77f8dfa918944b61c651759746ff2d76e4e5 +reward_manager: ../../../contracts/solc/v0.8.16/RewardManager/RewardManager.abi ../../../contracts/solc/v0.8.16/RewardManager/RewardManager.bin db73e9062b17a1d5aa14c06881fe2be49bd95b00b7f1a8943910c5e4ded5b221 +verifier: ../../../contracts/solc/v0.8.16/Verifier/Verifier.abi ../../../contracts/solc/v0.8.16/Verifier/Verifier.bin df12786bbeccf3a8f3389479cf93c055b4efd5904b9f99a4835f81af43fe62bf +verifier_proxy: ../../../contracts/solc/v0.8.16/VerifierProxy/VerifierProxy.abi ../../../contracts/solc/v0.8.16/VerifierProxy/VerifierProxy.bin 6393443d0a323f2dbe9687dc30fd77f8dfa918944b61c651759746ff2d76e4e5 werc20_mock: ../../../contracts/solc/v0.8.19/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock.bin ff2ca3928b2aa9c412c892cb8226c4d754c73eeb291bb7481c32c48791b2aa94 diff --git a/core/gethwrappers/llo-feeds/go_generate.go b/core/gethwrappers/llo-feeds/go_generate.go index 8d9e3be0493..5b2088f43a0 100644 --- a/core/gethwrappers/llo-feeds/go_generate.go +++ b/core/gethwrappers/llo-feeds/go_generate.go @@ -3,9 +3,9 @@ package gethwrappers // Chainlink LLO -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/Verifier.abi ../../../contracts/solc/v0.8.16/Verifier.bin Verifier verifier -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/VerifierProxy.abi ../../../contracts/solc/v0.8.16/VerifierProxy.bin VerifierProxy verifier_proxy -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/ErroredVerifier.abi ../../../contracts/solc/v0.8.16/ErroredVerifier.bin ErroredVerifier errored_verifier -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/ExposedVerifier.abi ../../../contracts/solc/v0.8.16/ExposedVerifier.bin ExposedVerifier exposed_verifier -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/RewardManager.abi ../../../contracts/solc/v0.8.16/RewardManager.bin RewardManager reward_manager -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/FeeManager.abi ../../../contracts/solc/v0.8.16/FeeManager.bin FeeManager fee_manager +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/Verifier/Verifier.abi ../../../contracts/solc/v0.8.16/Verifier/Verifier.bin Verifier verifier +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/VerifierProxy/VerifierProxy.abi ../../../contracts/solc/v0.8.16/VerifierProxy/VerifierProxy.bin VerifierProxy verifier_proxy +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/ErroredVerifier/ErroredVerifier.abi ../../../contracts/solc/v0.8.16/ErroredVerifier/ErroredVerifier.bin ErroredVerifier errored_verifier +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/ExposedVerifier/ExposedVerifier.abi ../../../contracts/solc/v0.8.16/ExposedVerifier/ExposedVerifier.bin ExposedVerifier exposed_verifier +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/RewardManager/RewardManager.abi ../../../contracts/solc/v0.8.16/RewardManager/RewardManager.bin RewardManager reward_manager +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.16/FeeManager/FeeManager.abi ../../../contracts/solc/v0.8.16/FeeManager/FeeManager.bin FeeManager fee_manager diff --git a/core/gethwrappers/shared/generated/burn_mint_erc677/burn_mint_erc677.go b/core/gethwrappers/shared/generated/burn_mint_erc677/burn_mint_erc677.go index f138b3b1f00..1d5b1c4ab17 100644 --- a/core/gethwrappers/shared/generated/burn_mint_erc677/burn_mint_erc677.go +++ b/core/gethwrappers/shared/generated/burn_mint_erc677/burn_mint_erc677.go @@ -52,7 +52,7 @@ func DeployBurnMintERC677(auth *bind.TransactOpts, backend bind.ContractBackend, if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &BurnMintERC677{BurnMintERC677Caller: BurnMintERC677Caller{contract: contract}, BurnMintERC677Transactor: BurnMintERC677Transactor{contract: contract}, BurnMintERC677Filterer: BurnMintERC677Filterer{contract: contract}}, nil + return address, tx, &BurnMintERC677{address: address, abi: *parsed, BurnMintERC677Caller: BurnMintERC677Caller{contract: contract}, BurnMintERC677Transactor: BurnMintERC677Transactor{contract: contract}, BurnMintERC677Filterer: BurnMintERC677Filterer{contract: contract}}, nil } type BurnMintERC677 struct { diff --git a/core/gethwrappers/shared/generated/erc20/erc20.go b/core/gethwrappers/shared/generated/erc20/erc20.go index f5b1d9b7bf2..9fd43134b6d 100644 --- a/core/gethwrappers/shared/generated/erc20/erc20.go +++ b/core/gethwrappers/shared/generated/erc20/erc20.go @@ -52,7 +52,7 @@ func DeployERC20(auth *bind.TransactOpts, backend bind.ContractBackend, name_ st if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &ERC20{ERC20Caller: ERC20Caller{contract: contract}, ERC20Transactor: ERC20Transactor{contract: contract}, ERC20Filterer: ERC20Filterer{contract: contract}}, nil + return address, tx, &ERC20{address: address, abi: *parsed, ERC20Caller: ERC20Caller{contract: contract}, ERC20Transactor: ERC20Transactor{contract: contract}, ERC20Filterer: ERC20Filterer{contract: contract}}, nil } type ERC20 struct { diff --git a/core/gethwrappers/shared/generated/link_token/link_token.go b/core/gethwrappers/shared/generated/link_token/link_token.go index 98de7de66a0..14676806266 100644 --- a/core/gethwrappers/shared/generated/link_token/link_token.go +++ b/core/gethwrappers/shared/generated/link_token/link_token.go @@ -52,7 +52,7 @@ func DeployLinkToken(auth *bind.TransactOpts, backend bind.ContractBackend) (com if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &LinkToken{LinkTokenCaller: LinkTokenCaller{contract: contract}, LinkTokenTransactor: LinkTokenTransactor{contract: contract}, LinkTokenFilterer: LinkTokenFilterer{contract: contract}}, nil + return address, tx, &LinkToken{address: address, abi: *parsed, LinkTokenCaller: LinkTokenCaller{contract: contract}, LinkTokenTransactor: LinkTokenTransactor{contract: contract}, LinkTokenFilterer: LinkTokenFilterer{contract: contract}}, nil } type LinkToken struct { diff --git a/core/gethwrappers/shared/generated/werc20_mock/werc20_mock.go b/core/gethwrappers/shared/generated/werc20_mock/werc20_mock.go index 3d8660c701d..c8ff3722755 100644 --- a/core/gethwrappers/shared/generated/werc20_mock/werc20_mock.go +++ b/core/gethwrappers/shared/generated/werc20_mock/werc20_mock.go @@ -52,7 +52,7 @@ func DeployWERC20Mock(auth *bind.TransactOpts, backend bind.ContractBackend) (co if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &WERC20Mock{WERC20MockCaller: WERC20MockCaller{contract: contract}, WERC20MockTransactor: WERC20MockTransactor{contract: contract}, WERC20MockFilterer: WERC20MockFilterer{contract: contract}}, nil + return address, tx, &WERC20Mock{address: address, abi: *parsed, WERC20MockCaller: WERC20MockCaller{contract: contract}, WERC20MockTransactor: WERC20MockTransactor{contract: contract}, WERC20MockFilterer: WERC20MockFilterer{contract: contract}}, nil } type WERC20Mock struct { diff --git a/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 7ac7e77a4b8..af907ce85eb 100644 --- a/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,5 +1,5 @@ GETH_VERSION: 1.12.0 -burn_mint_erc677: ../../../contracts/solc/v0.8.19/BurnMintERC677.abi ../../../contracts/solc/v0.8.19/BurnMintERC677.bin 405c9016171e614b17e10588653ef8d33dcea21dd569c3fddc596a46fcff68a3 -erc20: ../../../contracts/solc/v0.8.19/ERC20.abi ../../../contracts/solc/v0.8.19/ERC20.bin 5b1a93d9b24f250e49a730c96335a8113c3f7010365cba578f313b483001d4fc -link_token: ../../../contracts/solc/v0.8.19/LinkToken.abi ../../../contracts/solc/v0.8.19/LinkToken.bin c0ef9b507103aae541ebc31d87d051c2764ba9d843076b30ec505d37cdfffaba -werc20_mock: ../../../contracts/solc/v0.8.19/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock.bin ff2ca3928b2aa9c412c892cb8226c4d754c73eeb291bb7481c32c48791b2aa94 +burn_mint_erc677: ../../../contracts/solc/v0.8.19/BurnMintERC677/BurnMintERC677.abi ../../../contracts/solc/v0.8.19/BurnMintERC677/BurnMintERC677.bin 405c9016171e614b17e10588653ef8d33dcea21dd569c3fddc596a46fcff68a3 +erc20: ../../../contracts/solc/v0.8.19/ERC20/ERC20.abi ../../../contracts/solc/v0.8.19/ERC20/ERC20.bin 5b1a93d9b24f250e49a730c96335a8113c3f7010365cba578f313b483001d4fc +link_token: ../../../contracts/solc/v0.8.19/LinkToken/LinkToken.abi ../../../contracts/solc/v0.8.19/LinkToken/LinkToken.bin c0ef9b507103aae541ebc31d87d051c2764ba9d843076b30ec505d37cdfffaba +werc20_mock: ../../../contracts/solc/v0.8.19/WERC20Mock/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock/WERC20Mock.bin ff2ca3928b2aa9c412c892cb8226c4d754c73eeb291bb7481c32c48791b2aa94 diff --git a/core/gethwrappers/shared/go_generate.go b/core/gethwrappers/shared/go_generate.go index 85a01670c9a..6f3bead7d6b 100644 --- a/core/gethwrappers/shared/go_generate.go +++ b/core/gethwrappers/shared/go_generate.go @@ -2,7 +2,7 @@ // golang packages, using abigen. package gethwrappers -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/BurnMintERC677.abi ../../../contracts/solc/v0.8.19/BurnMintERC677.bin BurnMintERC677 burn_mint_erc677 -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/LinkToken.abi ../../../contracts/solc/v0.8.19/LinkToken.bin LinkToken link_token -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ERC20.abi ../../../contracts/solc/v0.8.19/ERC20.bin ERC20 erc20 -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock.bin WERC20Mock werc20_mock +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/BurnMintERC677/BurnMintERC677.abi ../../../contracts/solc/v0.8.19/BurnMintERC677/BurnMintERC677.bin BurnMintERC677 burn_mint_erc677 +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/LinkToken/LinkToken.abi ../../../contracts/solc/v0.8.19/LinkToken/LinkToken.bin LinkToken link_token +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ERC20/ERC20.abi ../../../contracts/solc/v0.8.19/ERC20/ERC20.bin ERC20 erc20 +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/WERC20Mock/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock/WERC20Mock.bin WERC20Mock werc20_mock diff --git a/core/gethwrappers/transmission/generated/entry_point/entry_point.go b/core/gethwrappers/transmission/generated/entry_point/entry_point.go index 9177186f4d6..09a3a94a7f4 100644 --- a/core/gethwrappers/transmission/generated/entry_point/entry_point.go +++ b/core/gethwrappers/transmission/generated/entry_point/entry_point.go @@ -99,7 +99,7 @@ func DeployEntryPoint(auth *bind.TransactOpts, backend bind.ContractBackend) (co if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &EntryPoint{EntryPointCaller: EntryPointCaller{contract: contract}, EntryPointTransactor: EntryPointTransactor{contract: contract}, EntryPointFilterer: EntryPointFilterer{contract: contract}}, nil + return address, tx, &EntryPoint{address: address, abi: *parsed, EntryPointCaller: EntryPointCaller{contract: contract}, EntryPointTransactor: EntryPointTransactor{contract: contract}, EntryPointFilterer: EntryPointFilterer{contract: contract}}, nil } type EntryPoint struct { diff --git a/core/gethwrappers/transmission/generated/greeter_wrapper/greeter_wrapper.go b/core/gethwrappers/transmission/generated/greeter_wrapper/greeter_wrapper.go index 857472422f7..9814c6a12c0 100644 --- a/core/gethwrappers/transmission/generated/greeter_wrapper/greeter_wrapper.go +++ b/core/gethwrappers/transmission/generated/greeter_wrapper/greeter_wrapper.go @@ -50,7 +50,7 @@ func DeployGreeter(auth *bind.TransactOpts, backend bind.ContractBackend) (commo if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &Greeter{GreeterCaller: GreeterCaller{contract: contract}, GreeterTransactor: GreeterTransactor{contract: contract}, GreeterFilterer: GreeterFilterer{contract: contract}}, nil + return address, tx, &Greeter{address: address, abi: *parsed, GreeterCaller: GreeterCaller{contract: contract}, GreeterTransactor: GreeterTransactor{contract: contract}, GreeterFilterer: GreeterFilterer{contract: contract}}, nil } type Greeter struct { diff --git a/core/gethwrappers/transmission/generated/paymaster_wrapper/paymaster_wrapper.go b/core/gethwrappers/transmission/generated/paymaster_wrapper/paymaster_wrapper.go index 84283b28bb8..4910d2b4bb9 100644 --- a/core/gethwrappers/transmission/generated/paymaster_wrapper/paymaster_wrapper.go +++ b/core/gethwrappers/transmission/generated/paymaster_wrapper/paymaster_wrapper.go @@ -66,7 +66,7 @@ func DeployPaymaster(auth *bind.TransactOpts, backend bind.ContractBackend, link if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &Paymaster{PaymasterCaller: PaymasterCaller{contract: contract}, PaymasterTransactor: PaymasterTransactor{contract: contract}, PaymasterFilterer: PaymasterFilterer{contract: contract}}, nil + return address, tx, &Paymaster{address: address, abi: *parsed, PaymasterCaller: PaymasterCaller{contract: contract}, PaymasterTransactor: PaymasterTransactor{contract: contract}, PaymasterFilterer: PaymasterFilterer{contract: contract}}, nil } type Paymaster struct { diff --git a/core/gethwrappers/transmission/generated/sca_wrapper/sca_wrapper.go b/core/gethwrappers/transmission/generated/sca_wrapper/sca_wrapper.go index 36838fc1b05..55a3107710a 100644 --- a/core/gethwrappers/transmission/generated/sca_wrapper/sca_wrapper.go +++ b/core/gethwrappers/transmission/generated/sca_wrapper/sca_wrapper.go @@ -64,7 +64,7 @@ func DeploySCA(auth *bind.TransactOpts, backend bind.ContractBackend, owner comm if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &SCA{SCACaller: SCACaller{contract: contract}, SCATransactor: SCATransactor{contract: contract}, SCAFilterer: SCAFilterer{contract: contract}}, nil + return address, tx, &SCA{address: address, abi: *parsed, SCACaller: SCACaller{contract: contract}, SCATransactor: SCATransactor{contract: contract}, SCAFilterer: SCAFilterer{contract: contract}}, nil } type SCA struct { diff --git a/core/gethwrappers/transmission/generated/smart_contract_account_factory/smart_contract_account_factory.go b/core/gethwrappers/transmission/generated/smart_contract_account_factory/smart_contract_account_factory.go index 1e7761bffe3..0b4daf3fa89 100644 --- a/core/gethwrappers/transmission/generated/smart_contract_account_factory/smart_contract_account_factory.go +++ b/core/gethwrappers/transmission/generated/smart_contract_account_factory/smart_contract_account_factory.go @@ -52,7 +52,7 @@ func DeploySmartContractAccountFactory(auth *bind.TransactOpts, backend bind.Con if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &SmartContractAccountFactory{SmartContractAccountFactoryCaller: SmartContractAccountFactoryCaller{contract: contract}, SmartContractAccountFactoryTransactor: SmartContractAccountFactoryTransactor{contract: contract}, SmartContractAccountFactoryFilterer: SmartContractAccountFactoryFilterer{contract: contract}}, nil + return address, tx, &SmartContractAccountFactory{address: address, abi: *parsed, SmartContractAccountFactoryCaller: SmartContractAccountFactoryCaller{contract: contract}, SmartContractAccountFactoryTransactor: SmartContractAccountFactoryTransactor{contract: contract}, SmartContractAccountFactoryFilterer: SmartContractAccountFactoryFilterer{contract: contract}}, nil } type SmartContractAccountFactory struct { diff --git a/core/gethwrappers/transmission/generated/smart_contract_account_helper/smart_contract_account_helper.go b/core/gethwrappers/transmission/generated/smart_contract_account_helper/smart_contract_account_helper.go index add1d2ce51b..d951227c3a5 100644 --- a/core/gethwrappers/transmission/generated/smart_contract_account_helper/smart_contract_account_helper.go +++ b/core/gethwrappers/transmission/generated/smart_contract_account_helper/smart_contract_account_helper.go @@ -50,7 +50,7 @@ func DeploySmartContractAccountHelper(auth *bind.TransactOpts, backend bind.Cont if err != nil { return common.Address{}, nil, nil, err } - return address, tx, &SmartContractAccountHelper{SmartContractAccountHelperCaller: SmartContractAccountHelperCaller{contract: contract}, SmartContractAccountHelperTransactor: SmartContractAccountHelperTransactor{contract: contract}, SmartContractAccountHelperFilterer: SmartContractAccountHelperFilterer{contract: contract}}, nil + return address, tx, &SmartContractAccountHelper{address: address, abi: *parsed, SmartContractAccountHelperCaller: SmartContractAccountHelperCaller{contract: contract}, SmartContractAccountHelperTransactor: SmartContractAccountHelperTransactor{contract: contract}, SmartContractAccountHelperFilterer: SmartContractAccountHelperFilterer{contract: contract}}, nil } type SmartContractAccountHelper struct { diff --git a/core/gethwrappers/transmission/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/transmission/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 8ea0492fa40..a6d32bf0a85 100644 --- a/core/gethwrappers/transmission/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/transmission/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,9 +1,9 @@ GETH_VERSION: 1.12.0 -entry_point: ../../../contracts/solc/v0.8.15/EntryPoint.abi ../../../contracts/solc/v0.8.15/EntryPoint.bin 2cb4bb2ba3efa8df3dfb0a57eb3727d17b68fe202682024fa7cfb4faf026833e +entry_point: ../../../contracts/solc/v0.8.15/EntryPoint/EntryPoint.abi ../../../contracts/solc/v0.8.15/EntryPoint/EntryPoint.bin 2cb4bb2ba3efa8df3dfb0a57eb3727d17b68fe202682024fa7cfb4faf026833e greeter: ../../../contracts/solc/v0.8.15/Greeter.abi ../../../contracts/solc/v0.8.15/Greeter.bin 653dcba5c33a46292073939ce1e639372cf521c0ec2814d4c9f20c72f796f18c -greeter_wrapper: ../../../contracts/solc/v0.8.15/Greeter.abi ../../../contracts/solc/v0.8.15/Greeter.bin 653dcba5c33a46292073939ce1e639372cf521c0ec2814d4c9f20c72f796f18c -paymaster_wrapper: ../../../contracts/solc/v0.8.15/Paymaster.abi ../../../contracts/solc/v0.8.15/Paymaster.bin 189ef817a5b7a6ff53ddf35b1988465b8aec479c47b77236fe20bf7e67d48100 +greeter_wrapper: ../../../contracts/solc/v0.8.15/Greeter/Greeter.abi ../../../contracts/solc/v0.8.15/Greeter/Greeter.bin 653dcba5c33a46292073939ce1e639372cf521c0ec2814d4c9f20c72f796f18c +paymaster_wrapper: ../../../contracts/solc/v0.8.15/Paymaster/Paymaster.abi ../../../contracts/solc/v0.8.15/Paymaster/Paymaster.bin 189ef817a5b7a6ff53ddf35b1988465b8aec479c47b77236fe20bf7e67d48100 sca: ../../../contracts/solc/v0.8.15/SCA.abi ../../../contracts/solc/v0.8.15/SCA.bin ae0f860cdac87d4ac505edbd228bd3ea1108550453aba67aebcb61f09cf70d0b -sca_wrapper: ../../../contracts/solc/v0.8.15/SCA.abi ../../../contracts/solc/v0.8.15/SCA.bin 2a8100fbdb41e6ce917ed333a624eaa4a8984b07e2d8d8ca6bba9bc9f74b05d7 -smart_contract_account_factory: ../../../contracts/solc/v0.8.15/SmartContractAccountFactory.abi ../../../contracts/solc/v0.8.15/SmartContractAccountFactory.bin a44d6fa2dbf9cb3441d6d637d89e1cd656f28b6bf4146f58d508067474bf845b -smart_contract_account_helper: ../../../contracts/solc/v0.8.15/SmartContractAccountHelper.abi ../../../contracts/solc/v0.8.15/SmartContractAccountHelper.bin 22f960a74bd1581a12aa4f8f438a3f265f32f43682f5c1897ca50707b9982d56 +sca_wrapper: ../../../contracts/solc/v0.8.15/SCA/SCA.abi ../../../contracts/solc/v0.8.15/SCA/SCA.bin 2a8100fbdb41e6ce917ed333a624eaa4a8984b07e2d8d8ca6bba9bc9f74b05d7 +smart_contract_account_factory: ../../../contracts/solc/v0.8.15/SmartContractAccountFactory/SmartContractAccountFactory.abi ../../../contracts/solc/v0.8.15/SmartContractAccountFactory/SmartContractAccountFactory.bin a44d6fa2dbf9cb3441d6d637d89e1cd656f28b6bf4146f58d508067474bf845b +smart_contract_account_helper: ../../../contracts/solc/v0.8.15/SmartContractAccountHelper/SmartContractAccountHelper.abi ../../../contracts/solc/v0.8.15/SmartContractAccountHelper/SmartContractAccountHelper.bin 22f960a74bd1581a12aa4f8f438a3f265f32f43682f5c1897ca50707b9982d56 diff --git a/core/gethwrappers/transmission/go_generate.go b/core/gethwrappers/transmission/go_generate.go index 52182a11504..54c6ecf94ed 100644 --- a/core/gethwrappers/transmission/go_generate.go +++ b/core/gethwrappers/transmission/go_generate.go @@ -3,9 +3,9 @@ package gethwrappers // Transmission -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/Greeter.abi ../../../contracts/solc/v0.8.15/Greeter.bin Greeter greeter_wrapper -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/SmartContractAccountFactory.abi ../../../contracts/solc/v0.8.15/SmartContractAccountFactory.bin SmartContractAccountFactory smart_contract_account_factory -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/EntryPoint.abi ../../../contracts/solc/v0.8.15/EntryPoint.bin EntryPoint entry_point -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/SmartContractAccountHelper.abi ../../../contracts/solc/v0.8.15/SmartContractAccountHelper.bin SmartContractAccountHelper smart_contract_account_helper -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/SCA.abi ../../../contracts/solc/v0.8.15/SCA.bin SCA sca_wrapper -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/Paymaster.abi ../../../contracts/solc/v0.8.15/Paymaster.bin Paymaster paymaster_wrapper +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/Greeter/Greeter.abi ../../../contracts/solc/v0.8.15/Greeter/Greeter.bin Greeter greeter_wrapper +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/SmartContractAccountFactory/SmartContractAccountFactory.abi ../../../contracts/solc/v0.8.15/SmartContractAccountFactory/SmartContractAccountFactory.bin SmartContractAccountFactory smart_contract_account_factory +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/EntryPoint/EntryPoint.abi ../../../contracts/solc/v0.8.15/EntryPoint/EntryPoint.bin EntryPoint entry_point +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/SmartContractAccountHelper/SmartContractAccountHelper.abi ../../../contracts/solc/v0.8.15/SmartContractAccountHelper/SmartContractAccountHelper.bin SmartContractAccountHelper smart_contract_account_helper +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/SCA/SCA.abi ../../../contracts/solc/v0.8.15/SCA/SCA.bin SCA sca_wrapper +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.15/Paymaster/Paymaster.abi ../../../contracts/solc/v0.8.15/Paymaster/Paymaster.bin Paymaster paymaster_wrapper diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index a9ff8144b3c..8802d9c4b0d 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -6,7 +6,6 @@ import ( crand "crypto/rand" "encoding/json" "errors" - "flag" "fmt" "io" "math/big" @@ -16,8 +15,6 @@ import ( "net/url" "os" "reflect" - "runtime" - "strings" "testing" "time" @@ -29,7 +26,6 @@ import ( "github.com/google/uuid" "github.com/gorilla/securecookie" "github.com/gorilla/sessions" - cryptop2p "github.com/libp2p/go-libp2p-core/crypto" p2ppeer "github.com/libp2p/go-libp2p-core/peer" "github.com/manyminds/api2go/jsonapi" "github.com/onsi/gomega" @@ -37,31 +33,28 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" - "github.com/urfave/cli" + + "github.com/jmoiron/sqlx" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" - "github.com/smartcontractkit/sqlx" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop" - clienttypes "github.com/smartcontractkit/chainlink/v2/common/chains/client" - txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" + "github.com/smartcontractkit/chainlink/v2/common/client" commonmocks "github.com/smartcontractkit/chainlink/v2/common/types/mocks" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" clhttptest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/httptest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/keystest" @@ -83,6 +76,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/cache" "github.com/smartcontractkit/chainlink/v2/core/services/webhook" clsessions "github.com/smartcontractkit/chainlink/v2/core/sessions" "github.com/smartcontractkit/chainlink/v2/core/static" @@ -108,7 +103,7 @@ const ( // SessionSecret is the hardcoded secret solely used for test SessionSecret = "clsession_test_secret" // DefaultPeerID is the peer ID of the default p2p key - DefaultPeerID = "12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X" + DefaultPeerID = configtest.DefaultPeerID // DefaultOCRKeyBundleID is the ID of the default ocr key bundle DefaultOCRKeyBundleID = "f5bf259689b26f1374efb3c9a9868796953a0f814bb2d39b968d0e61b58620a5" // DefaultOCR2KeyBundleID is the ID of the fixture ocr2 key bundle @@ -120,7 +115,6 @@ const ( var ( DefaultP2PPeerID p2pkey.PeerID FixtureChainID = *testutils.FixtureChainID - source rand.Source DefaultCosmosKey = cosmoskey.MustNewInsecure(keystest.NewRandReaderFromSeed(KeyBigIntSeed)) DefaultCSAKey = csakey.MustNewV2XXXTestingOnly(big.NewInt(KeyBigIntSeed)) @@ -147,13 +141,6 @@ func init() { fmt.Printf("[gin] %-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers) } - // Seed the random number generator, otherwise separate modules will take - // the same advisory locks when tested with `go test -p N` for N > 1 - seed := time.Now().UTC().UnixNano() - fmt.Printf("cltest random seed: %v\n", seed) - - // Also seed the local source - source = rand.NewSource(seed) defaultP2PPeerID, err := p2ppeer.Decode(configtest.DefaultPeerID) if err != nil { panic(err) @@ -188,11 +175,11 @@ type JobPipelineConfig interface { MaxSuccessfulRuns() uint64 } -func NewJobPipelineV2(t testing.TB, cfg pipeline.BridgeConfig, jpcfg JobPipelineConfig, dbCfg pg.QConfig, legacyChains evm.LegacyChainContainer, db *sqlx.DB, keyStore keystore.Master, restrictedHTTPClient, unrestrictedHTTPClient *http.Client) JobPipelineV2TestHelper { +func NewJobPipelineV2(t testing.TB, cfg pipeline.BridgeConfig, jpcfg JobPipelineConfig, dbCfg pg.QConfig, legacyChains legacyevm.LegacyChainContainer, db *sqlx.DB, keyStore keystore.Master, restrictedHTTPClient, unrestrictedHTTPClient *http.Client) JobPipelineV2TestHelper { lggr := logger.TestLogger(t) prm := pipeline.NewORM(db, lggr, dbCfg, jpcfg.MaxSuccessfulRuns()) btORM := bridges.NewORM(db, lggr, dbCfg) - jrm := job.NewORM(db, legacyChains, prm, btORM, keyStore, lggr, dbCfg) + jrm := job.NewORM(db, prm, btORM, keyStore, lggr, dbCfg) pr := pipeline.NewRunner(prm, btORM, jpcfg, cfg, legacyChains, keyStore.Eth(), keyStore.VRF(), lggr, restrictedHTTPClient, unrestrictedHTTPClient) return JobPipelineV2TestHelper{ prm, @@ -201,22 +188,6 @@ func NewJobPipelineV2(t testing.TB, cfg pipeline.BridgeConfig, jpcfg JobPipeline } } -func NewEventBroadcaster(t testing.TB, dbURL url.URL) pg.EventBroadcaster { - lggr := logger.TestLogger(t) - return pg.NewEventBroadcaster(dbURL, 0, 0, lggr, uuid.New()) -} - -func NewEthConfirmer(t testing.TB, txStore txmgr.EvmTxStore, ethClient evmclient.Client, config evmconfig.ChainScopedConfig, ks keystore.Eth, fn txmgrcommon.ResumeCallback) *txmgr.Confirmer { - lggr := logger.TestLogger(t) - ge := config.EVM().GasEstimator() - estimator := gas.NewWrappedEvmEstimator(gas.NewFixedPriceEstimator(ge, ge.BlockHistory(), lggr), ge.EIP1559DynamicFees(), nil) - txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ks, estimator) - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), config.Database(), ks, txBuilder, lggr) - ec.SetResumeCallback(fn) - require.NoError(t, ec.Start(testutils.Context(t))) - return ec -} - // TestApplication holds the test application and test servers type TestApplication struct { t testing.TB @@ -228,19 +199,12 @@ type TestApplication struct { Keys []ethkey.KeyV2 } -// 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 testutils.JSONRPCHandler) string { - server := testutils.NewWSServer(t, chainID, callback) - return server.WSURL().String() -} - // NewApplicationEVMDisabled creates a new application with default config but EVM disabled // Useful for testing controllers func NewApplicationEVMDisabled(t *testing.T) *TestApplication { t.Helper() - c := configtest2.NewGeneralConfig(t, nil) + c := configtest.NewGeneralConfig(t, nil) return NewApplicationWithConfig(t, c) } @@ -250,7 +214,7 @@ func NewApplicationEVMDisabled(t *testing.T) *TestApplication { func NewApplication(t testing.TB, flagsAndDeps ...interface{}) *TestApplication { t.Helper() - c := configtest2.NewGeneralConfig(t, nil) + c := configtest.NewGeneralConfig(t, nil) return NewApplicationWithConfig(t, c, flagsAndDeps...) } @@ -260,7 +224,7 @@ func NewApplication(t testing.TB, flagsAndDeps ...interface{}) *TestApplication func NewApplicationWithKey(t *testing.T, flagsAndDeps ...interface{}) *TestApplication { t.Helper() - config := configtest2.NewGeneralConfig(t, nil) + config := configtest.NewGeneralConfig(t, nil) return NewApplicationWithConfigAndKey(t, config, flagsAndDeps...) } @@ -380,14 +344,21 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn mailMon := utils.NewMailboxMonitor(cfg.AppID().String()) loopRegistry := plugins.NewLoopRegistry(lggr, nil) + mercuryPool := wsrpc.NewPool(lggr, cache.Config{ + LatestReportTTL: cfg.Mercury().Cache().LatestReportTTL(), + MaxStaleAge: cfg.Mercury().Cache().MaxStaleAge(), + LatestReportDeadline: cfg.Mercury().Cache().LatestReportDeadline(), + }) + relayerFactory := chainlink.RelayerFactory{ Logger: lggr, LoopRegistry: loopRegistry, GRPCOpts: loop.GRPCOpts{}, + MercuryPool: mercuryPool, } evmOpts := chainlink.EVMFactoryConfig{ - ChainOpts: evm.ChainOpts{ + ChainOpts: legacyevm.ChainOpts{ AppConfig: cfg, EventBroadcaster: eventBroadcaster, MailMon: mailMon, @@ -415,11 +386,10 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn if cfg.CosmosEnabled() { cosmosCfg := chainlink.CosmosFactoryConfig{ - Keystore: keyStore.Cosmos(), - CosmosConfigs: cfg.CosmosConfigs(), - EventBroadcaster: eventBroadcaster, - DB: db, - QConfig: cfg.Database(), + Keystore: keyStore.Cosmos(), + TOMLConfigs: cfg.CosmosConfigs(), + DB: db, + QConfig: cfg.Database(), } initOps = append(initOps, chainlink.InitCosmos(testCtx, relayerFactory, cosmosCfg)) } @@ -458,6 +428,7 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn UnrestrictedHTTPClient: c, SecretGenerator: MockSecretGenerator{}, LoopRegistry: plugins.NewLoopRegistry(lggr, nil), + MercuryPool: mercuryPool, }) require.NoError(t, err) app := appInstance.(*chainlink.ChainlinkApplication) @@ -520,7 +491,7 @@ func NewEthMocksWithTransactionsOnBlocksAssertions(t testing.TB) *evmclimocks.Cl 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) - c.On("SendTransactionReturnCode", mock.Anything, mock.Anything, mock.Anything).Maybe().Return(clienttypes.Successful, nil) + c.On("SendTransactionReturnCode", mock.Anything, mock.Anything, mock.Anything).Maybe().Return(client.Successful, nil) // Construct chain h2 := Head(2) h1 := HeadWithHash(1, h2.ParentHash) @@ -630,7 +601,7 @@ func (ta *TestApplication) NewHTTPClient(user *User) HTTPClientCleaner { u, err := clsessions.NewUser(user.Email, Password, user.Role) require.NoError(ta.t, err) - err = ta.SessionORM().CreateUser(&u) + err = ta.BasicAdminUsersORM().CreateUser(&u) require.NoError(ta.t, err) sessionID := ta.MustSeedNewSession(user.Email) @@ -1077,7 +1048,7 @@ type TransactionReceipter interface { func RequireTxSuccessful(t testing.TB, client TransactionReceipter, txHash common.Hash) *types.Receipt { t.Helper() - r, err := client.TransactionReceipt(context.Background(), txHash) + r, err := client.TransactionReceipt(testutils.Context(t), txHash) require.NoError(t, err) require.NotNil(t, r) require.Equal(t, uint64(1), r.Status) @@ -1579,58 +1550,17 @@ func AssertRecordEventually(t *testing.T, db *sqlx.DB, model interface{}, stmt s }, testutils.WaitTimeout(t), DBPollingInterval).Should(gomega.BeTrue()) } -func MustSendingKeyStates(t *testing.T, ethKeyStore keystore.Eth, chainID *big.Int) []ethkey.State { - keys, err := ethKeyStore.EnabledKeysForChain(chainID) - require.NoError(t, err) - states, err := ethKeyStore.GetStatesForKeys(keys) - require.NoError(t, err) - return states -} - -func MustRandomP2PPeerID(t *testing.T) p2ppeer.ID { - reader := rand.New(source) - p2pPrivkey, _, err := cryptop2p.GenerateEd25519Key(reader) - require.NoError(t, err) - id, err := p2ppeer.IDFromPrivateKey(p2pPrivkey) - require.NoError(t, err) - return id -} - func MustWebURL(t *testing.T, s string) *models.WebURL { uri, err := url.Parse(s) require.NoError(t, err) return (*models.WebURL)(uri) } -func AssertPipelineTaskRunsSuccessful(t testing.TB, runs []pipeline.TaskRun) { - t.Helper() - for i, run := range runs { - require.True(t, run.Error.IsZero(), fmt.Sprintf("pipeline.Task run failed (idx: %v, dotID: %v, error: '%v')", i, run.GetDotID(), run.Error.ValueOrZero())) - } -} - -func AssertPipelineTaskRunsErrored(t testing.TB, runs []pipeline.TaskRun) { - t.Helper() - for i, run := range runs { - require.False(t, run.Error.IsZero(), fmt.Sprintf("expected pipeline.Task run to have failed, but it succeeded (idx: %v, dotID: %v, output: '%v')", i, run.GetDotID(), run.Output)) - } -} - func NewTestChainScopedConfig(t testing.TB) evmconfig.ChainScopedConfig { - cfg := configtest2.NewGeneralConfig(t, nil) + cfg := configtest.NewGeneralConfig(t, nil) return evmtest.NewChainScopedConfig(t, cfg) } -func MustGetStateForKey(t testing.TB, kst keystore.Eth, key ethkey.KeyV2) ethkey.State { - state, err := kst.GetStateForKey(key) - require.NoError(t, err) - return state -} - -func NewTxStore(t *testing.T, db *sqlx.DB, cfg pg.QConfig) txmgr.EvmTxStore { - return txmgr.NewTxStore(db, logger.TestLogger(t), cfg) -} - func NewTestTxStore(t *testing.T, db *sqlx.DB, cfg pg.QConfig) txmgr.TestEvmTxStore { return txmgr.NewTxStore(db, logger.TestLogger(t), cfg) } @@ -1648,47 +1578,3 @@ func ClearDBTables(t *testing.T, db *sqlx.DB, tables ...string) { err = tx.Commit() require.NoError(t, err) } - -// FlagSetApplyFromAction applies the flags from action to the flagSet. -// `parentCommand` will filter the app commands and only applies the flags if the command/subcommand has a parent with that name, if left empty no filtering is done -func FlagSetApplyFromAction(action interface{}, flagSet *flag.FlagSet, parentCommand string) { - cliApp := cmd.Shell{} - app := cmd.NewApp(&cliApp) - - foundName := parentCommand == "" - actionFuncName := getFuncName(action) - - for _, command := range app.Commands { - flags := recursiveFindFlagsWithName(actionFuncName, command, parentCommand, foundName) - - for _, flag := range flags { - flag.Apply(flagSet) - } - } - -} - -func recursiveFindFlagsWithName(actionFuncName string, command cli.Command, parent string, foundName bool) []cli.Flag { - - if command.Action != nil { - if actionFuncName == getFuncName(command.Action) && foundName { - return command.Flags - } - } - - for _, subcommand := range command.Subcommands { - if !foundName { - foundName = strings.EqualFold(subcommand.Name, parent) - } - - found := recursiveFindFlagsWithName(actionFuncName, subcommand, parent, foundName) - if found != nil { - return found - } - } - return nil -} - -func getFuncName(i interface{}) string { - return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() -} diff --git a/core/internal/cltest/event_websocket_server.go b/core/internal/cltest/event_websocket_server.go deleted file mode 100644 index 64b3e90cd81..00000000000 --- a/core/internal/cltest/event_websocket_server.go +++ /dev/null @@ -1,154 +0,0 @@ -package cltest - -import ( - "net/http" - "net/http/httptest" - "net/url" - "sync" - "testing" - - "github.com/pkg/errors" - - "github.com/gorilla/websocket" -) - -// EventWebSocketServer is a web socket server designed specifically for testing -type EventWebSocketServer struct { - *httptest.Server - mutex *sync.RWMutex // shared mutex for safe access to arrays/maps. - t *testing.T - connections []*websocket.Conn - Connected chan struct{} - Disconnected chan struct{} - ReceivedText chan string - ReceivedBinary chan []byte - URL *url.URL -} - -// NewEventWebSocketServer returns a new EventWebSocketServer -func NewEventWebSocketServer(t *testing.T) (*EventWebSocketServer, func()) { - server := &EventWebSocketServer{ - mutex: &sync.RWMutex{}, - t: t, - Connected: make(chan struct{}, 1), // have buffer of one for easier assertions after the event - Disconnected: make(chan struct{}, 1), // have buffer of one for easier assertions after the event - ReceivedText: make(chan string, 100), - ReceivedBinary: make(chan []byte, 100), - } - - server.Server = httptest.NewServer(http.HandlerFunc(server.handler)) - u, err := url.Parse(server.Server.URL) - if err != nil { - t.Fatal("EventWebSocketServer: ", err) - } - u.Scheme = "ws" - server.URL = u - return server, func() { - server.Close() - } -} - -func (wss EventWebSocketServer) ConnectionsCount() int { - wss.mutex.RLock() - defer wss.mutex.RUnlock() - return len(wss.connections) -} - -// Broadcast sends a message to every web socket client connected to the EventWebSocketServer -func (wss *EventWebSocketServer) Broadcast(message string) error { - wss.mutex.RLock() - defer wss.mutex.RUnlock() - for _, connection := range wss.connections { - err := connection.WriteMessage(websocket.TextMessage, []byte(message)) - if err != nil { - return errors.Wrap(err, "error writing message to connection") - } - } - - return nil -} - -// WriteCloseMessage tells connected clients to disconnect. -// Useful to emulate that the websocket server is shutting down without -// actually shutting down. -// This overcomes httptest.Server's inability to restart on the same URL:port. -func (wss *EventWebSocketServer) WriteCloseMessage() { - wss.mutex.RLock() - for _, connection := range wss.connections { - err := connection.WriteMessage( - websocket.CloseMessage, - websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - if err != nil { - wss.t.Error(err) - } - } - wss.mutex.RUnlock() -} - -var ( - upgrader = websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { return true }, - } - - closeCodes = []int{websocket.CloseNormalClosure, websocket.CloseAbnormalClosure} -) - -func (wss *EventWebSocketServer) handler(w http.ResponseWriter, r *http.Request) { - var err error - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - wss.t.Fatal("EventWebSocketServer Upgrade: ", err) - } - - wss.addConnection(conn) - for { - messageType, payload, err := conn.ReadMessage() // we only read - if websocket.IsCloseError(err, closeCodes...) { - wss.removeConnection(conn) - return - } - if err != nil { - wss.t.Fatal("EventWebSocketServer ReadMessage: ", err) - } - - if messageType == websocket.TextMessage { - select { - case wss.ReceivedText <- string(payload): - default: - } - } else if messageType == websocket.BinaryMessage { - select { - case wss.ReceivedBinary <- payload: - default: - } - } else { - wss.t.Fatal("EventWebSocketServer UnsupportedMessageType: ", messageType) - } - } -} - -func (wss *EventWebSocketServer) addConnection(conn *websocket.Conn) { - wss.mutex.Lock() - wss.connections = append(wss.connections, conn) - wss.mutex.Unlock() - select { // broadcast connected event - case wss.Connected <- struct{}{}: - default: - } -} - -func (wss *EventWebSocketServer) removeConnection(conn *websocket.Conn) { - newc := []*websocket.Conn{} - wss.mutex.Lock() - for _, connection := range wss.connections { - if connection != conn { - newc = append(newc, connection) - } - } - wss.connections = newc - wss.mutex.Unlock() - select { // broadcast disconnected event - case wss.Disconnected <- struct{}{}: - default: - } -} diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index a1a5d9db6be..f0ce8c4ff66 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -21,20 +21,20 @@ import ( "github.com/urfave/cli" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/flux_aggregator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keeper" @@ -134,7 +134,7 @@ func EmptyCLIContext() *cli.Context { return cli.NewContext(nil, set, nil) } -func NewEthTx(t *testing.T, fromAddress common.Address) txmgr.Tx { +func NewEthTx(fromAddress common.Address) txmgr.Tx { return txmgr.Tx{ FromAddress: fromAddress, ToAddress: testutils.NewAddress(), @@ -156,7 +156,7 @@ func MustInsertUnconfirmedEthTx(t *testing.T, txStore txmgr.TestEvmTxStore, nonc chainID = v } } - etx := NewEthTx(t, fromAddress) + etx := NewEthTx(fromAddress) etx.BroadcastAt = &broadcastAt etx.InitialBroadcastAt = &broadcastAt @@ -184,101 +184,16 @@ func MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t *testing.T, txStore return etx } -func MustInsertUnconfirmedEthTxWithAttemptState(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address, txAttemptState txmgrtypes.TxAttemptState, opts ...interface{}) txmgr.Tx { - etx := MustInsertUnconfirmedEthTx(t, txStore, nonce, fromAddress, opts...) - 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 = txAttemptState - require.NoError(t, txStore.InsertTxAttempt(&attempt)) - etx, err := txStore.FindTxWithAttempts(etx.ID) - require.NoError(t, err) - return etx -} - -func MustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address, opts ...interface{}) txmgr.Tx { - etx := MustInsertUnconfirmedEthTx(t, txStore, nonce, fromAddress, opts...) - attempt := NewDynamicFeeEthTxAttempt(t, etx.ID) - - addr := testutils.NewAddress() - dtx := types.DynamicFeeTx{ - ChainID: big.NewInt(0), - Nonce: uint64(nonce), - GasTipCap: big.NewInt(1), - GasFeeCap: big.NewInt(1), - Gas: 242, - To: &addr, - Value: big.NewInt(342), - Data: []byte{2, 3, 4}, - } - tx := types.NewTx(&dtx) - rlp := new(bytes.Buffer) - require.NoError(t, tx.EncodeRLP(rlp)) - attempt.SignedRawTx = rlp.Bytes() - - attempt.State = txmgrtypes.TxAttemptBroadcast - require.NoError(t, txStore.InsertTxAttempt(&attempt)) - etx, err := txStore.FindTxWithAttempts(etx.ID) - require.NoError(t, err) - return etx -} - -func MustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address) txmgr.Tx { - timeNow := time.Now() - etx := NewEthTx(t, fromAddress) - - etx.BroadcastAt = &timeNow - etx.InitialBroadcastAt = &timeNow - n := evmtypes.Nonce(nonce) - etx.Sequence = &n - etx.State = txmgrcommon.TxUnconfirmed - require.NoError(t, txStore.InsertTx(&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 = txmgrtypes.TxAttemptInsufficientFunds - require.NoError(t, txStore.InsertTxAttempt(&attempt)) - etx, err := txStore.FindTxWithAttempts(etx.ID) - require.NoError(t, err) - return etx -} - -func MustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( - t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, broadcastBeforeBlockNum int64, - broadcastAt time.Time, fromAddress common.Address) txmgr.Tx { - etx := NewEthTx(t, fromAddress) - - etx.BroadcastAt = &broadcastAt - etx.InitialBroadcastAt = &broadcastAt - n := evmtypes.Nonce(nonce) - etx.Sequence = &n - etx.State = txmgrcommon.TxConfirmedMissingReceipt - require.NoError(t, txStore.InsertTx(&etx)) - attempt := NewLegacyEthTxAttempt(t, etx.ID) - attempt.BroadcastBeforeBlockNum = &broadcastBeforeBlockNum - attempt.State = txmgrtypes.TxAttemptBroadcast - require.NoError(t, txStore.InsertTxAttempt(&attempt)) - etx.TxAttempts = append(etx.TxAttempts, attempt) - return etx -} - func MustInsertConfirmedEthTxWithLegacyAttempt(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, broadcastBeforeBlockNum int64, fromAddress common.Address) txmgr.Tx { timeNow := time.Now() - etx := NewEthTx(t, fromAddress) + etx := NewEthTx(fromAddress) etx.BroadcastAt = &timeNow etx.InitialBroadcastAt = &timeNow n := evmtypes.Nonce(nonce) etx.Sequence = &n etx.State = txmgrcommon.TxConfirmed + etx.MinConfirmations.SetValid(6) require.NoError(t, txStore.InsertTx(&etx)) attempt := NewLegacyEthTxAttempt(t, etx.ID) attempt.BroadcastBeforeBlockNum = &broadcastBeforeBlockNum @@ -288,91 +203,6 @@ func MustInsertConfirmedEthTxWithLegacyAttempt(t *testing.T, txStore txmgr.TestE return etx } -func MustInsertInProgressEthTxWithAttempt(t *testing.T, txStore txmgr.TestEvmTxStore, nonce evmtypes.Nonce, fromAddress common.Address) txmgr.Tx { - etx := NewEthTx(t, fromAddress) - - etx.Sequence = &nonce - etx.State = txmgrcommon.TxInProgress - require.NoError(t, txStore.InsertTx(&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 = txmgrtypes.TxAttemptInProgress - require.NoError(t, txStore.InsertTxAttempt(&attempt)) - etx, err := txStore.FindTxWithAttempts(etx.ID) - require.NoError(t, err) - return etx -} - -func MustCreateUnstartedGeneratedTx(t testing.TB, txStore txmgr.EvmTxStore, fromAddress common.Address, chainID *big.Int, opts ...func(*txmgr.TxRequest)) (tx txmgr.Tx) { - txRequest := txmgr.TxRequest{ - FromAddress: fromAddress, - } - - // Apply the default options - WithDefaults()(&txRequest) - // Apply the optional parameters - for _, opt := range opts { - opt(&txRequest) - } - return MustCreateUnstartedTxFromEvmTxRequest(t, txStore, txRequest, chainID) -} - -func WithDefaults() func(*txmgr.TxRequest) { - return func(tx *txmgr.TxRequest) { - tx.ToAddress = testutils.NewAddress() - tx.EncodedPayload = []byte{1, 2, 3} - tx.Value = big.Int(assets.NewEthValue(142)) - tx.FeeLimit = uint32(1000000000) - tx.Strategy = txmgrcommon.NewSendEveryStrategy() - // Set default values for other fields if needed - } -} - -func EvmTxRequestWithStrategy(strategy txmgrtypes.TxStrategy) func(*txmgr.TxRequest) { - return func(tx *txmgr.TxRequest) { - tx.Strategy = strategy - } -} - -func EvmTxRequestWithChecker(checker txmgr.TransmitCheckerSpec) func(*txmgr.TxRequest) { - return func(tx *txmgr.TxRequest) { - tx.Checker = checker - } -} -func EvmTxRequestWithValue(value big.Int) func(*txmgr.TxRequest) { - return func(tx *txmgr.TxRequest) { - tx.Value = value - } -} - -func EvmTxRequestWithIdempotencyKey(idempotencyKey string) func(*txmgr.TxRequest) { - return func(tx *txmgr.TxRequest) { - tx.IdempotencyKey = &idempotencyKey - } -} - -func MustCreateUnstartedTx(t testing.TB, txStore txmgr.EvmTxStore, fromAddress common.Address, toAddress common.Address, encodedPayload []byte, gasLimit uint32, value big.Int, chainID *big.Int, opts ...interface{}) (tx txmgr.Tx) { - txRequest := txmgr.TxRequest{ - FromAddress: fromAddress, - ToAddress: toAddress, - EncodedPayload: encodedPayload, - Value: value, - FeeLimit: gasLimit, - Strategy: txmgrcommon.NewSendEveryStrategy(), - } - - return MustCreateUnstartedTxFromEvmTxRequest(t, txStore, txRequest, chainID) -} - -func MustCreateUnstartedTxFromEvmTxRequest(t testing.TB, txStore txmgr.EvmTxStore, txRequest txmgr.TxRequest, chainID *big.Int) (tx txmgr.Tx) { - tx, err := txStore.CreateTransaction(testutils.Context(t), txRequest, chainID) - require.NoError(t, err) - return tx -} - func NewLegacyEthTxAttempt(t *testing.T, etxID int64) txmgr.TxAttempt { gasPrice := assets.NewWeiI(1) return txmgr.TxAttempt{ @@ -406,72 +236,6 @@ func NewDynamicFeeEthTxAttempt(t *testing.T, etxID int64) txmgr.TxAttempt { } } -func NewEthReceipt(t *testing.T, blockNumber int64, blockHash common.Hash, txHash common.Hash, status uint64) txmgr.Receipt { - transactionIndex := uint(NewRandomPositiveInt64()) - - receipt := evmtypes.Receipt{ - BlockNumber: big.NewInt(blockNumber), - BlockHash: blockHash, - TxHash: txHash, - TransactionIndex: transactionIndex, - Status: status, - } - - r := txmgr.Receipt{ - BlockNumber: blockNumber, - BlockHash: blockHash, - TxHash: txHash, - TransactionIndex: transactionIndex, - Receipt: receipt, - } - return r -} - -func MustInsertEthReceipt(t *testing.T, txStore txmgr.TestEvmTxStore, blockNumber int64, blockHash common.Hash, txHash common.Hash) txmgr.Receipt { - r := NewEthReceipt(t, blockNumber, blockHash, txHash, 0x1) - id, err := txStore.InsertReceipt(&r.Receipt) - require.NoError(t, err) - r.ID = id - return r -} - -func MustInsertRevertedEthReceipt(t *testing.T, txStore txmgr.TestEvmTxStore, blockNumber int64, blockHash common.Hash, txHash common.Hash) txmgr.Receipt { - r := NewEthReceipt(t, blockNumber, blockHash, txHash, 0x0) - id, err := txStore.InsertReceipt(&r.Receipt) - require.NoError(t, err) - r.ID = id - return r -} - -// Inserts into evm.receipts but does not update evm.txes or evm.tx_attempts -func MustInsertConfirmedEthTxWithReceipt(t *testing.T, txStore txmgr.TestEvmTxStore, fromAddress common.Address, nonce, blockNum int64) (etx txmgr.Tx) { - etx = MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, nonce, blockNum, fromAddress) - MustInsertEthReceipt(t, txStore, blockNum, utils.NewHash(), etx.TxAttempts[0].Hash) - return etx -} - -func MustInsertConfirmedEthTxBySaveFetchedReceipts(t *testing.T, txStore txmgr.TestEvmTxStore, fromAddress common.Address, nonce int64, blockNum int64, chainID big.Int) (etx txmgr.Tx) { - etx = MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, nonce, blockNum, fromAddress) - receipt := evmtypes.Receipt{ - TxHash: etx.TxAttempts[0].Hash, - BlockHash: utils.NewHash(), - BlockNumber: big.NewInt(nonce), - TransactionIndex: uint(1), - } - err := txStore.SaveFetchedReceipts(testutils.Context(t), []*evmtypes.Receipt{&receipt}, &chainID) - require.NoError(t, err) - return etx -} - -func MustInsertFatalErrorEthTx(t *testing.T, txStore txmgr.TestEvmTxStore, fromAddress common.Address) txmgr.Tx { - etx := NewEthTx(t, fromAddress) - etx.Error = null.StringFrom("something exploded") - etx.State = txmgrcommon.TxFatalError - - require.NoError(t, txStore.InsertTx(&etx)) - return etx -} - type RandomKey struct { Nonce int64 Disabled bool @@ -500,7 +264,8 @@ func (r RandomKey) MustInsert(t testing.TB, keystore keystore.Eth) (ethkey.KeyV2 func (r RandomKey) MustInsertWithState(t testing.TB, keystore keystore.Eth) (ethkey.State, common.Address) { k, address := r.MustInsert(t, keystore) - state := MustGetStateForKey(t, keystore, k) + state, err := keystore.GetStateForKey(k) + require.NoError(t, err) return state, address } @@ -563,7 +328,7 @@ func MustInsertV2JobSpec(t *testing.T, db *sqlx.DB, transmitterAddress common.Ad PipelineSpecID: pipelineSpec.ID, } - jorm := job.NewORM(db, nil, nil, nil, nil, logger.TestLogger(t), configtest.NewTestGeneralConfig(t).Database()) + jorm := job.NewORM(db, nil, nil, nil, logger.TestLogger(t), configtest.NewTestGeneralConfig(t).Database()) err = jorm.InsertJob(&jb) require.NoError(t, err) return jb @@ -619,7 +384,7 @@ func MustInsertKeeperJob(t *testing.T, db *sqlx.DB, korm keeper.ORM, from ethkey tlg := logger.TestLogger(t) prm := pipeline.NewORM(db, tlg, cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) btORM := bridges.NewORM(db, tlg, cfg.Database()) - jrm := job.NewORM(db, nil, prm, btORM, nil, tlg, cfg.Database()) + jrm := job.NewORM(db, prm, btORM, nil, tlg, cfg.Database()) err = jrm.InsertJob(&jb) require.NoError(t, err) return jb diff --git a/core/internal/cltest/heavyweight/orm.go b/core/internal/cltest/heavyweight/orm.go index 841901c25aa..5df28a49778 100644 --- a/core/internal/cltest/heavyweight/orm.go +++ b/core/internal/cltest/heavyweight/orm.go @@ -7,20 +7,22 @@ import ( "database/sql" "errors" "fmt" - "math/rand" "net/url" "os" "path" "runtime" + "strings" "testing" - "github.com/smartcontractkit/sqlx" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/store/dialects" @@ -29,28 +31,32 @@ import ( // FullTestDBV2 creates a pristine 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 FullTestDBV2(t testing.TB, name string, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) { - return prepareFullTestDBV2(t, name, false, true, overrideFn) +func FullTestDBV2(t testing.TB, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) { + return prepareFullTestDBV2(t, false, true, overrideFn) } // FullTestDBNoFixturesV2 is the same as FullTestDB, but it does not load fixtures. -func FullTestDBNoFixturesV2(t testing.TB, name string, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) { - return prepareFullTestDBV2(t, name, false, false, overrideFn) +func FullTestDBNoFixturesV2(t testing.TB, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) { + return prepareFullTestDBV2(t, false, false, overrideFn) } // FullTestDBEmptyV2 creates an empty DB (without migrations). -func FullTestDBEmptyV2(t testing.TB, name string, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) { - return prepareFullTestDBV2(t, name, true, false, overrideFn) +func FullTestDBEmptyV2(t testing.TB, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) { + return prepareFullTestDBV2(t, true, false, overrideFn) +} + +func generateName() string { + return strings.ReplaceAll(uuid.New().String(), "-", "") } -func prepareFullTestDBV2(t testing.TB, name string, empty bool, loadFixtures bool, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) { +func prepareFullTestDBV2(t testing.TB, empty bool, loadFixtures bool, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) { testutils.SkipShort(t, "FullTestDB") if empty && loadFixtures { t.Fatal("could not load fixtures into an empty DB") } - gcfg := configtest2.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { + gcfg := configtest.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Database.Dialect = dialects.Postgres if overrideFn != nil { overrideFn(c, s) @@ -58,8 +64,7 @@ func prepareFullTestDBV2(t testing.TB, name string, empty bool, loadFixtures boo }) require.NoError(t, os.MkdirAll(gcfg.RootDir(), 0700)) - name = fmt.Sprintf("%s_%x", name, rand.Intn(0xFFF)) // to avoid name collisions - migrationTestDBURL, err := dropAndCreateThrowawayTestDB(gcfg.Database().URL(), name, empty) + migrationTestDBURL, err := dropAndCreateThrowawayTestDB(gcfg.Database().URL(), generateName(), empty) require.NoError(t, err) db, err := pg.NewConnection(migrationTestDBURL, dialects.Postgres, gcfg.Database()) require.NoError(t, err) @@ -68,7 +73,7 @@ func prepareFullTestDBV2(t testing.TB, name string, empty bool, loadFixtures boo os.RemoveAll(gcfg.RootDir()) }) - gcfg = configtest2.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { + gcfg = configtest.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Database.Dialect = dialects.Postgres s.Database.URL = models.MustSecretURL(migrationTestDBURL) if overrideFn != nil { diff --git a/core/internal/cltest/job_factories.go b/core/internal/cltest/job_factories.go index 910ffe79e38..a9e403fb608 100644 --- a/core/internal/cltest/job_factories.go +++ b/core/internal/cltest/job_factories.go @@ -5,18 +5,17 @@ import ( "testing" "github.com/google/uuid" - "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/require" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink/v2/core/bridges" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" ) const ( @@ -65,9 +64,7 @@ func getORMs(t *testing.T, db *sqlx.DB) (jobORM job.ORM, pipelineORM pipeline.OR lggr := logger.TestLogger(t) pipelineORM = pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgeORM := bridges.NewORM(db, lggr, config.Database()) - cc := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(cc) - jobORM = job.NewORM(db, legacyChains, pipelineORM, bridgeORM, keyStore, lggr, config.Database()) + jobORM = job.NewORM(db, pipelineORM, bridgeORM, keyStore, lggr, config.Database()) t.Cleanup(func() { jobORM.Close() }) return } diff --git a/core/internal/cltest/mocks.go b/core/internal/cltest/mocks.go index 9fdbcbb373d..073b3ba246c 100644 --- a/core/internal/cltest/mocks.go +++ b/core/internal/cltest/mocks.go @@ -11,12 +11,14 @@ import ( "testing" "time" - "github.com/smartcontractkit/sqlx" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - evmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/mocks" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" + evmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm/mocks" "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -309,7 +311,7 @@ func MustRandomUser(t testing.TB) sessions.User { return r } -func NewUserWithSession(t testing.TB, orm sessions.ORM) sessions.User { +func NewUserWithSession(t testing.TB, orm sessions.AuthenticationProvider) sessions.User { u := MustRandomUser(t) require.NoError(t, orm.CreateUser(&u)) @@ -330,7 +332,7 @@ func NewMockAPIInitializer(t testing.TB) *MockAPIInitializer { return &MockAPIInitializer{t: t} } -func (m *MockAPIInitializer) Initialize(orm sessions.ORM, lggr logger.Logger) (sessions.User, error) { +func (m *MockAPIInitializer) Initialize(orm sessions.BasicAdminUsersORM, lggr logger.Logger) (sessions.User, error) { if user, err := orm.FindUser(APIEmailAdmin); err == nil { return user, err } @@ -397,7 +399,7 @@ func (m MockPasswordPrompter) Prompt() string { return m.Password } -func NewLegacyChainsWithMockChain(t testing.TB, ethClient evmclient.Client, cfg evm.AppConfig) evm.LegacyChainContainer { +func NewLegacyChainsWithMockChain(t testing.TB, ethClient evmclient.Client, cfg legacyevm.AppConfig) legacyevm.LegacyChainContainer { ch := new(evmmocks.Chain) ch.On("Client").Return(ethClient) ch.On("Logger").Return(logger.TestLogger(t)) @@ -409,7 +411,19 @@ func NewLegacyChainsWithMockChain(t testing.TB, ethClient evmclient.Client, cfg } -func NewLegacyChainsWithChain(ch evm.Chain, cfg evm.AppConfig) evm.LegacyChainContainer { - m := map[string]evm.Chain{ch.ID().String(): ch} - return evm.NewLegacyChains(m, cfg.EVMConfigs()) +func NewLegacyChainsWithMockChainAndTxManager(t testing.TB, ethClient evmclient.Client, cfg legacyevm.AppConfig, txm txmgr.TxManager) legacyevm.LegacyChainContainer { + ch := new(evmmocks.Chain) + ch.On("Client").Return(ethClient) + ch.On("Logger").Return(logger.TestLogger(t)) + scopedCfg := evmtest.NewChainScopedConfig(t, cfg) + ch.On("ID").Return(scopedCfg.EVM().ChainID()) + ch.On("Config").Return(scopedCfg) + ch.On("TxManager").Return(txm) + + return NewLegacyChainsWithChain(ch, cfg) +} + +func NewLegacyChainsWithChain(ch legacyevm.Chain, cfg legacyevm.AppConfig) legacyevm.LegacyChainContainer { + m := map[string]legacyevm.Chain{ch.ID().String(): ch} + return legacyevm.NewLegacyChains(m, cfg.EVMConfigs()) } diff --git a/core/internal/features/features_test.go b/core/internal/features/features_test.go index c5d16590d52..35fd3a31ff8 100644 --- a/core/internal/features/features_test.go +++ b/core/internal/features/features_test.go @@ -41,9 +41,9 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting/confighelper" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -56,7 +56,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -83,7 +83,7 @@ func TestIntegration_ExternalInitiatorV2(t *testing.T) { ethClient := cltest.NewEthMocksWithStartupAssertions(t) - cfg := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.JobPipeline.ExternalInitiatorsEnabled = ptr(true) c.Database.Listener.FallbackPollInterval = models.MustNewDuration(10 * time.Millisecond) }) @@ -237,8 +237,7 @@ observationSource = """ pipelineORM := pipeline.NewORM(app.GetSqlxDB(), logger.TestLogger(t), cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) bridgeORM := bridges.NewORM(app.GetSqlxDB(), logger.TestLogger(t), cfg.Database()) - legacyChains := app.GetRelayers().LegacyEVMChains() - jobORM := job.NewORM(app.GetSqlxDB(), legacyChains, pipelineORM, bridgeORM, app.KeyStore, logger.TestLogger(t), cfg.Database()) + jobORM := job.NewORM(app.GetSqlxDB(), pipelineORM, bridgeORM, app.KeyStore, logger.TestLogger(t), cfg.Database()) runs := cltest.WaitForPipelineComplete(t, 0, jobID, 1, 2, jobORM, 5*time.Second, 300*time.Millisecond) require.Len(t, runs, 1) @@ -267,7 +266,7 @@ func TestIntegration_AuthToken(t *testing.T) { mockUser := cltest.MustRandomUser(t) key, secret := uuid.New().String(), uuid.New().String() apiToken := auth.Token{AccessKey: key, Secret: secret} - orm := app.SessionORM() + orm := app.AuthenticationProvider() require.NoError(t, orm.CreateUser(&mockUser)) require.NoError(t, orm.SetAuthToken(&mockUser, &apiToken)) @@ -362,7 +361,7 @@ func TestIntegration_DirectRequest(t *testing.T) { t.Run(test.name, func(t *testing.T) { // Simulate a consumer contract calling to obtain ETH quotes in 3 different currencies // in a single callback. - config := configtest2.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { + config := configtest.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Database.Listener.FallbackPollInterval = models.MustNewDuration(100 * time.Millisecond) c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true) }) @@ -430,7 +429,7 @@ func TestIntegration_DirectRequest(t *testing.T) { pipelineRuns := cltest.WaitForPipelineComplete(t, 0, j.ID, 1, 14, app.JobORM(), testutils.WaitTimeout(t)/2, time.Second) pipelineRun := pipelineRuns[0] - cltest.AssertPipelineTaskRunsSuccessful(t, pipelineRun.PipelineTaskRuns) + assertPipelineTaskRunsSuccessful(t, pipelineRun.PipelineTaskRuns) assertPricesUint256(t, big.NewInt(61464), big.NewInt(50707), big.NewInt(6381886), operatorContracts.multiWord) nameAndExternalJobID = uuid.New() @@ -455,7 +454,7 @@ func TestIntegration_DirectRequest(t *testing.T) { pipelineRuns = cltest.WaitForPipelineComplete(t, 0, jobSingleWord.ID, 1, 8, app.JobORM(), testutils.WaitTimeout(t), time.Second) pipelineRun = pipelineRuns[0] - cltest.AssertPipelineTaskRunsSuccessful(t, pipelineRun.PipelineTaskRuns) + assertPipelineTaskRunsSuccessful(t, pipelineRun.PipelineTaskRuns) v, err := operatorContracts.singleWord.CurrentPriceInt(nil) require.NoError(t, err) assert.Equal(t, big.NewInt(61464), v) @@ -467,7 +466,7 @@ func setupAppForEthTx(t *testing.T, operatorContracts OperatorContracts) (app *c b := operatorContracts.sim lggr, o := logger.TestLoggerObserved(t, zapcore.DebugLevel) - cfg := configtest2.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { + cfg := configtest.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Database.Listener.FallbackPollInterval = models.MustNewDuration(100 * time.Millisecond) }) app = cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, cfg, b, lggr) @@ -536,7 +535,7 @@ observationSource = """ // The run should have succeeded but with the receipt detailing the reverted transaction pipelineRun := pipelineRuns[0] - cltest.AssertPipelineTaskRunsSuccessful(t, pipelineRun.PipelineTaskRuns) + assertPipelineTaskRunsSuccessful(t, pipelineRun.PipelineTaskRuns) outputs := pipelineRun.Outputs.Val.([]interface{}) require.Len(t, outputs, 1) @@ -582,7 +581,7 @@ observationSource = """ // The run should have failed as a revert pipelineRun := pipelineRuns[0] - cltest.AssertPipelineTaskRunsErrored(t, pipelineRun.PipelineTaskRuns) + assertPipelineTaskRunsErrored(t, pipelineRun.PipelineTaskRuns) }) t.Run("with FailOnRevert disabled, run succeeds with output being reverted receipt", func(t *testing.T) { @@ -620,7 +619,7 @@ observationSource = """ // The run should have succeeded but with the receipt detailing the reverted transaction pipelineRun := pipelineRuns[0] - cltest.AssertPipelineTaskRunsSuccessful(t, pipelineRun.PipelineTaskRuns) + assertPipelineTaskRunsSuccessful(t, pipelineRun.PipelineTaskRuns) outputs := pipelineRun.Outputs.Val.([]interface{}) require.Len(t, outputs, 1) @@ -676,11 +675,11 @@ func setupOCRContracts(t *testing.T) (*bind.TransactOpts, *backends.SimulatedBac return owner, b, ocrContractAddress, ocrContract, flagsContract, flagsContractAddress } -func setupNode(t *testing.T, owner *bind.TransactOpts, portV1, portV2 int, dbName string, +func setupNode(t *testing.T, owner *bind.TransactOpts, portV1, portV2 int, b *backends.SimulatedBackend, ns ocrnetworking.NetworkingStack, overrides func(c *chainlink.Config, s *chainlink.Secrets), ) (*cltest.TestApplication, string, common.Address, ocrkey.KeyV2) { p2pKey := keystest.NewP2PKeyV2(t) - config, _ := heavyweight.FullTestDBV2(t, dbName, func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Insecure.OCRDevelopmentMode = ptr(true) // Disables ocr spec validation so we can have fast polling for the test. c.OCR.Enabled = ptr(true) @@ -749,7 +748,6 @@ func setupForwarderEnabledNode( owner *bind.TransactOpts, portV1, portV2 int, - dbName string, b *backends.SimulatedBackend, ns ocrnetworking.NetworkingStack, overrides func(c *chainlink.Config, s *chainlink.Secrets), @@ -761,7 +759,7 @@ func setupForwarderEnabledNode( ocrkey.KeyV2, ) { p2pKey := keystest.NewP2PKeyV2(t) - config, _ := heavyweight.FullTestDBV2(t, dbName, func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Insecure.OCRDevelopmentMode = ptr(true) // Disables ocr spec validation so we can have fast polling for the test. c.OCR.Enabled = ptr(true) @@ -868,7 +866,7 @@ func TestIntegration_OCR(t *testing.T) { // Note it's plausible these ports could be occupied on a CI machine. // May need a port randomize + retry approach if we observe collisions. - appBootstrap, bootstrapPeerID, _, _ := setupNode(t, owner, bootstrapNodePortV1, bootstrapNodePortV2, fmt.Sprintf("b_%d", test.id), b, test.ns, nil) + appBootstrap, bootstrapPeerID, _, _ := setupNode(t, owner, bootstrapNodePortV1, bootstrapNodePortV2, b, test.ns, nil) var ( oracles []confighelper.OracleIdentityExtra transmitters []common.Address @@ -879,7 +877,7 @@ func TestIntegration_OCR(t *testing.T) { for i := 0; i < numOracles; i++ { portV1 := ports[2*i] portV2 := ports[2*i+1] - app, peerID, transmitter, key := setupNode(t, owner, portV1, portV2, fmt.Sprintf("o%d_%d", i, test.id), b, test.ns, func(c *chainlink.Config, s *chainlink.Secrets) { + app, peerID, transmitter, key := setupNode(t, owner, portV1, portV2, b, test.ns, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].FlagsContractAddress = ptr(ethkey.EIP55AddressFromAddress(flagsContractAddress)) c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(test.eip1559) if test.ns != ocrnetworking.NetworkingStackV1 { @@ -1093,7 +1091,7 @@ func TestIntegration_OCR_ForwarderFlow(t *testing.T) { // Note it's plausible these ports could be occupied on a CI machine. // May need a port randomize + retry approach if we observe collisions. - appBootstrap, bootstrapPeerID, _, _ := setupNode(t, owner, bootstrapNodePortV1, bootstrapNodePortV2, fmt.Sprintf("b_%d", 1), b, ocrnetworking.NetworkingStackV2, nil) + appBootstrap, bootstrapPeerID, _, _ := setupNode(t, owner, bootstrapNodePortV1, bootstrapNodePortV2, b, ocrnetworking.NetworkingStackV2, nil) var ( oracles []confighelper.OracleIdentityExtra @@ -1106,7 +1104,7 @@ func TestIntegration_OCR_ForwarderFlow(t *testing.T) { for i := 0; i < numOracles; i++ { portV1 := ports[2*i] portV2 := ports[2*i+1] - app, peerID, transmitter, forwarder, key := setupForwarderEnabledNode(t, owner, portV1, portV2, fmt.Sprintf("o%d_%d", i, 1), b, ocrnetworking.NetworkingStackV2, func(c *chainlink.Config, s *chainlink.Secrets) { + app, peerID, transmitter, forwarder, key := setupForwarderEnabledNode(t, owner, portV1, portV2, b, ocrnetworking.NetworkingStackV2, func(c *chainlink.Config, s *chainlink.Secrets) { c.Feature.LogPoller = ptr(true) c.EVM[0].FlagsContractAddress = ptr(ethkey.EIP55AddressFromAddress(flagsContractAddress)) c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true) @@ -1320,7 +1318,7 @@ func TestIntegration_BlockHistoryEstimator(t *testing.T) { var initialDefaultGasPrice int64 = 5_000_000_000 maxGasPrice := assets.NewWeiI(10 * initialDefaultGasPrice) - cfg := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].BalanceMonitor.Enabled = ptr(false) c.EVM[0].GasEstimator.BlockHistory.CheckInclusionBlocks = ptr[uint16](0) c.EVM[0].GasEstimator.PriceDefault = assets.NewWeiI(initialDefaultGasPrice) @@ -1456,3 +1454,17 @@ func assertPricesUint256(t *testing.T, usd, eur, jpy *big.Int, consumer *multiwo } func ptr[T any](v T) *T { return &v } + +func assertPipelineTaskRunsSuccessful(t testing.TB, runs []pipeline.TaskRun) { + t.Helper() + for i, run := range runs { + require.True(t, run.Error.IsZero(), fmt.Sprintf("pipeline.Task run failed (idx: %v, dotID: %v, error: '%v')", i, run.GetDotID(), run.Error.ValueOrZero())) + } +} + +func assertPipelineTaskRunsErrored(t testing.TB, runs []pipeline.TaskRun) { + t.Helper() + for i, run := range runs { + require.False(t, run.Error.IsZero(), fmt.Sprintf("expected pipeline.Task run to have failed, but it succeeded (idx: %v, dotID: %v, output: '%v')", i, run.GetDotID(), run.Output)) + } +} diff --git a/core/internal/features/ocr2/features_ocr2_test.go b/core/internal/features/ocr2/features_ocr2_test.go index 25b6781c4e9..387b15f76c8 100644 --- a/core/internal/features/ocr2/features_ocr2_test.go +++ b/core/internal/features/ocr2/features_ocr2_test.go @@ -34,8 +34,8 @@ import ( confighelper2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" ocrtypes2 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_forwarder" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" @@ -105,13 +105,12 @@ func setupNodeOCR2( t *testing.T, owner *bind.TransactOpts, port int, - dbName string, useForwarder bool, b *backends.SimulatedBackend, p2pV2Bootstrappers []commontypes.BootstrapperLocator, ) *ocr2Node { p2pKey := keystest.NewP2PKeyV2(t) - config, _ := heavyweight.FullTestDBV2(t, fmt.Sprintf("%s%d", dbName, port), func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Insecure.OCRDevelopmentMode = ptr(true) // Disables ocr spec validation so we can have fast polling for the test. c.Feature.LogPoller = ptr(true) @@ -193,7 +192,7 @@ func TestIntegration_OCR2(t *testing.T) { lggr := logger.TestLogger(t) bootstrapNodePort := freeport.GetOne(t) - bootstrapNode := setupNodeOCR2(t, owner, bootstrapNodePort, "bootstrap", false /* useForwarders */, b, nil) + bootstrapNode := setupNodeOCR2(t, owner, bootstrapNodePort, false /* useForwarders */, b, nil) var ( oracles []confighelper2.OracleIdentityExtra @@ -203,7 +202,7 @@ func TestIntegration_OCR2(t *testing.T) { ) ports := freeport.GetN(t, 4) for i := 0; i < 4; i++ { - node := setupNodeOCR2(t, owner, ports[i], fmt.Sprintf("oracle%d", i), false /* useForwarders */, b, []commontypes.BootstrapperLocator{ + node := setupNodeOCR2(t, owner, ports[i], false /* useForwarders */, b, []commontypes.BootstrapperLocator{ // Supply the bootstrap IP and port as a V2 peer address {PeerID: bootstrapNode.peerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, }) @@ -477,7 +476,7 @@ func TestIntegration_OCR2_ForwarderFlow(t *testing.T) { lggr := logger.TestLogger(t) bootstrapNodePort := freeport.GetOne(t) - bootstrapNode := setupNodeOCR2(t, owner, bootstrapNodePort, "bootstrap", true /* useForwarders */, b, nil) + bootstrapNode := setupNodeOCR2(t, owner, bootstrapNodePort, true /* useForwarders */, b, nil) var ( oracles []confighelper2.OracleIdentityExtra @@ -488,7 +487,7 @@ func TestIntegration_OCR2_ForwarderFlow(t *testing.T) { ) ports := freeport.GetN(t, 4) for i := uint16(0); i < 4; i++ { - node := setupNodeOCR2(t, owner, ports[i], fmt.Sprintf("oracle%d", i), true /* useForwarders */, b, []commontypes.BootstrapperLocator{ + node := setupNodeOCR2(t, owner, ports[i], true /* useForwarders */, b, []commontypes.BootstrapperLocator{ // Supply the bootstrap IP and port as a V2 peer address {PeerID: bootstrapNode.peerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, }) diff --git a/core/internal/mocks/application.go b/core/internal/mocks/application.go index ec656509afd..48f8e12dac3 100644 --- a/core/internal/mocks/application.go +++ b/core/internal/mocks/application.go @@ -31,7 +31,7 @@ import ( sessions "github.com/smartcontractkit/chainlink/v2/core/sessions" - sqlx "github.com/smartcontractkit/sqlx" + sqlx "github.com/jmoiron/sqlx" txmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" @@ -63,6 +63,38 @@ func (_m *Application) AddJobV2(ctx context.Context, _a1 *job.Job) error { return r0 } +// AuthenticationProvider provides a mock function with given fields: +func (_m *Application) AuthenticationProvider() sessions.AuthenticationProvider { + ret := _m.Called() + + var r0 sessions.AuthenticationProvider + if rf, ok := ret.Get(0).(func() sessions.AuthenticationProvider); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(sessions.AuthenticationProvider) + } + } + + return r0 +} + +// BasicAdminUsersORM provides a mock function with given fields: +func (_m *Application) BasicAdminUsersORM() sessions.BasicAdminUsersORM { + ret := _m.Called() + + var r0 sessions.BasicAdminUsersORM + if rf, ok := ret.Get(0).(func() sessions.BasicAdminUsersORM); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(sessions.BasicAdminUsersORM) + } + } + + return r0 +} + // BridgeORM provides a mock function with given fields: func (_m *Application) BridgeORM() bridges.ORM { ret := _m.Called() @@ -439,22 +471,6 @@ func (_m *Application) SecretGenerator() chainlink.SecretGenerator { return r0 } -// SessionORM provides a mock function with given fields: -func (_m *Application) SessionORM() sessions.ORM { - ret := _m.Called() - - var r0 sessions.ORM - if rf, ok := ret.Get(0).(func() sessions.ORM); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(sessions.ORM) - } - } - - return r0 -} - // SetLogLevel provides a mock function with given fields: lvl func (_m *Application) SetLogLevel(lvl zapcore.Level) error { ret := _m.Called(lvl) diff --git a/core/internal/testutils/configtest/general_config.go b/core/internal/testutils/configtest/general_config.go index 1b652eeecdd..93d388b2d30 100644 --- a/core/internal/testutils/configtest/general_config.go +++ b/core/internal/testutils/configtest/general_config.go @@ -1,6 +1,122 @@ package configtest -// TODO move? -const ( - DefaultPeerID = "12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X" +import ( + "net" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/store/dialects" + "github.com/smartcontractkit/chainlink/v2/core/store/models" + "github.com/smartcontractkit/chainlink/v2/core/utils" ) + +const DefaultPeerID = "12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X" + +// NewTestGeneralConfig returns a new chainlink.GeneralConfig with default test overrides and one chain with evmclient.NullClientChainID. +func NewTestGeneralConfig(t testing.TB) chainlink.GeneralConfig { return NewGeneralConfig(t, nil) } + +// NewGeneralConfig returns a new chainlink.GeneralConfig with overrides. +// The default test overrides are applied before overrideFn, and include one chain with evmclient.NullClientChainID. +func NewGeneralConfig(t testing.TB, overrideFn func(*chainlink.Config, *chainlink.Secrets)) chainlink.GeneralConfig { + tempDir := t.TempDir() + g, err := chainlink.GeneralConfigOpts{ + OverrideFn: func(c *chainlink.Config, s *chainlink.Secrets) { + overrides(c, s) + c.RootDir = &tempDir + if fn := overrideFn; fn != nil { + fn(c, s) + } + }, + }.New() + require.NoError(t, err) + return g +} + +// overrides applies some test config settings and adds a default chain with evmclient.NullClientChainID. +func overrides(c *chainlink.Config, s *chainlink.Secrets) { + s.Password.Keystore = models.NewSecret("dummy-to-pass-validation") + + c.Insecure.OCRDevelopmentMode = ptr(true) + c.InsecureFastScrypt = ptr(true) + c.ShutdownGracePeriod = models.MustNewDuration(testutils.DefaultWaitTimeout) + + c.Database.Dialect = dialects.TransactionWrappedPostgres + c.Database.Lock.Enabled = ptr(false) + c.Database.MaxIdleConns = ptr[int64](20) + c.Database.MaxOpenConns = ptr[int64](20) + c.Database.MigrateOnStartup = ptr(false) + c.Database.DefaultLockTimeout = models.MustNewDuration(1 * time.Minute) + + c.JobPipeline.ReaperInterval = models.MustNewDuration(0) + + c.P2P.V1.Enabled = ptr(false) + c.P2P.V2.Enabled = ptr(false) + + c.WebServer.SessionTimeout = models.MustNewDuration(2 * time.Minute) + c.WebServer.BridgeResponseURL = models.MustParseURL("http://localhost:6688") + testIP := net.ParseIP("127.0.0.1") + c.WebServer.ListenIP = &testIP + c.WebServer.TLS.ListenIP = &testIP + + chainID := utils.NewBigI(evmclient.NullClientChainID) + c.EVM = append(c.EVM, &evmcfg.EVMConfig{ + ChainID: chainID, + Chain: evmcfg.Defaults(chainID), + Nodes: evmcfg.EVMNodes{ + &evmcfg.Node{ + Name: ptr("test"), + WSURL: &models.URL{}, + HTTPURL: &models.URL{}, + SendOnly: new(bool), + Order: ptr[int32](100), + }, + }, + }) +} + +// NewGeneralConfigSimulated returns a new chainlink.GeneralConfig with overrides, including the simulated EVM chain. +// The default test overrides are applied before overrideFn. +// The simulated chain (testutils.SimulatedChainID) replaces the null chain (evmclient.NullClientChainID). +func NewGeneralConfigSimulated(t testing.TB, overrideFn func(*chainlink.Config, *chainlink.Secrets)) chainlink.GeneralConfig { + return NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + simulated(c, s) + if fn := overrideFn; fn != nil { + fn(c, s) + } + }) +} + +// simulated is a config override func that appends the simulated EVM chain (testutils.SimulatedChainID), +// or replaces the null chain (client.NullClientChainID) if that is the only entry. +func simulated(c *chainlink.Config, s *chainlink.Secrets) { + chainID := utils.NewBig(testutils.SimulatedChainID) + enabled := true + cfg := evmcfg.EVMConfig{ + ChainID: chainID, + Chain: evmcfg.Defaults(chainID), + Enabled: &enabled, + Nodes: evmcfg.EVMNodes{&validTestNode}, + } + if len(c.EVM) == 1 && c.EVM[0].ChainID.Cmp(utils.NewBigI(client.NullClientChainID)) == 0 { + c.EVM[0] = &cfg // replace null, if only entry + } else { + c.EVM = append(c.EVM, &cfg) + } +} + +var validTestNode = evmcfg.Node{ + Name: ptr("simulated-node"), + WSURL: models.MustParseURL("WSS://simulated-wss.com/ws"), + HTTPURL: models.MustParseURL("http://simulated.com"), + SendOnly: nil, + Order: ptr(int32(1)), +} + +func ptr[T any](v T) *T { return &v } diff --git a/core/internal/testutils/configtest/v2/toml/toml.go b/core/internal/testutils/configtest/toml.go similarity index 95% rename from core/internal/testutils/configtest/v2/toml/toml.go rename to core/internal/testutils/configtest/toml.go index 0cb89010f25..78db05f9c3c 100644 --- a/core/internal/testutils/configtest/v2/toml/toml.go +++ b/core/internal/testutils/configtest/toml.go @@ -1,4 +1,4 @@ -package testtomlutils +package configtest import ( "os" diff --git a/core/internal/testutils/configtest/v2/general_config.go b/core/internal/testutils/configtest/v2/general_config.go deleted file mode 100644 index febbb367bd5..00000000000 --- a/core/internal/testutils/configtest/v2/general_config.go +++ /dev/null @@ -1,120 +0,0 @@ -package v2 - -import ( - "net" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" - "github.com/smartcontractkit/chainlink/v2/core/store/models" - "github.com/smartcontractkit/chainlink/v2/core/utils" -) - -// NewTestGeneralConfig returns a new chainlink.GeneralConfig with default test overrides and one chain with evmclient.NullClientChainID. -func NewTestGeneralConfig(t testing.TB) chainlink.GeneralConfig { return NewGeneralConfig(t, nil) } - -// NewGeneralConfig returns a new chainlink.GeneralConfig with overrides. -// The default test overrides are applied before overrideFn, and include one chain with evmclient.NullClientChainID. -func NewGeneralConfig(t testing.TB, overrideFn func(*chainlink.Config, *chainlink.Secrets)) chainlink.GeneralConfig { - tempDir := t.TempDir() - g, err := chainlink.GeneralConfigOpts{ - OverrideFn: func(c *chainlink.Config, s *chainlink.Secrets) { - overrides(c, s) - c.RootDir = &tempDir - if fn := overrideFn; fn != nil { - fn(c, s) - } - }, - }.New() - require.NoError(t, err) - return g -} - -// overrides applies some test config settings and adds a default chain with evmclient.NullClientChainID. -func overrides(c *chainlink.Config, s *chainlink.Secrets) { - s.Password.Keystore = models.NewSecret("dummy-to-pass-validation") - - c.Insecure.OCRDevelopmentMode = ptr(true) - c.InsecureFastScrypt = ptr(true) - c.ShutdownGracePeriod = models.MustNewDuration(testutils.DefaultWaitTimeout) - - c.Database.Dialect = dialects.TransactionWrappedPostgres - c.Database.Lock.Enabled = ptr(false) - c.Database.MaxIdleConns = ptr[int64](20) - c.Database.MaxOpenConns = ptr[int64](20) - c.Database.MigrateOnStartup = ptr(false) - c.Database.DefaultLockTimeout = models.MustNewDuration(1 * time.Minute) - - c.JobPipeline.ReaperInterval = models.MustNewDuration(0) - - c.P2P.V1.Enabled = ptr(false) - c.P2P.V2.Enabled = ptr(false) - - c.WebServer.SessionTimeout = models.MustNewDuration(2 * time.Minute) - c.WebServer.BridgeResponseURL = models.MustParseURL("http://localhost:6688") - testIP := net.ParseIP("127.0.0.1") - c.WebServer.ListenIP = &testIP - c.WebServer.TLS.ListenIP = &testIP - - chainID := utils.NewBigI(evmclient.NullClientChainID) - c.EVM = append(c.EVM, &evmcfg.EVMConfig{ - ChainID: chainID, - Chain: evmcfg.Defaults(chainID), - Nodes: evmcfg.EVMNodes{ - &evmcfg.Node{ - Name: ptr("test"), - WSURL: &models.URL{}, - HTTPURL: &models.URL{}, - SendOnly: new(bool), - Order: ptr[int32](100), - }, - }, - }) -} - -// NewGeneralConfigSimulated returns a new chainlink.GeneralConfig with overrides, including the simulated EVM chain. -// The default test overrides are applied before overrideFn. -// The simulated chain (testutils.SimulatedChainID) replaces the null chain (evmclient.NullClientChainID). -func NewGeneralConfigSimulated(t testing.TB, overrideFn func(*chainlink.Config, *chainlink.Secrets)) chainlink.GeneralConfig { - return NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - simulated(c, s) - if fn := overrideFn; fn != nil { - fn(c, s) - } - }) -} - -// simulated is a config override func that appends the simulated EVM chain (testutils.SimulatedChainID), -// or replaces the null chain (client.NullClientChainID) if that is the only entry. -func simulated(c *chainlink.Config, s *chainlink.Secrets) { - chainID := utils.NewBig(testutils.SimulatedChainID) - enabled := true - cfg := evmcfg.EVMConfig{ - ChainID: chainID, - Chain: evmcfg.Defaults(chainID), - Enabled: &enabled, - Nodes: evmcfg.EVMNodes{&validTestNode}, - } - if len(c.EVM) == 1 && c.EVM[0].ChainID.Cmp(utils.NewBigI(client.NullClientChainID)) == 0 { - c.EVM[0] = &cfg // replace null, if only entry - } else { - c.EVM = append(c.EVM, &cfg) - } -} - -var validTestNode = evmcfg.Node{ - Name: ptr("simulated-node"), - WSURL: models.MustParseURL("WSS://simulated-wss.com/ws"), - HTTPURL: models.MustParseURL("http://simulated.com"), - SendOnly: nil, - Order: ptr(int32(1)), -} - -func ptr[T any](v T) *T { return &v } diff --git a/core/internal/testutils/evmtest/evmtest.go b/core/internal/testutils/evmtest/evmtest.go index 3a08c815166..423615145e5 100644 --- a/core/internal/testutils/evmtest/evmtest.go +++ b/core/internal/testutils/evmtest/evmtest.go @@ -9,17 +9,16 @@ import ( "testing" "github.com/ethereum/go-ethereum" + "github.com/jmoiron/sqlx" "github.com/pelletier/go-toml/v2" - "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" commonmocks "github.com/smartcontractkit/chainlink/v2/common/types/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" @@ -30,6 +29,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" @@ -40,7 +40,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/utils" ) -func NewChainScopedConfig(t testing.TB, cfg evm.AppConfig) evmconfig.ChainScopedConfig { +func NewChainScopedConfig(t testing.TB, cfg legacyevm.AppConfig) evmconfig.ChainScopedConfig { var evmCfg *evmtoml.EVMConfig if len(cfg.EVMConfigs()) > 0 { evmCfg = cfg.EVMConfigs()[0] @@ -60,7 +60,7 @@ type TestChainOpts struct { Client evmclient.Client LogBroadcaster log.Broadcaster LogPoller logpoller.LogPoller - GeneralConfig evm.AppConfig + GeneralConfig legacyevm.AppConfig HeadTracker httypes.HeadTracker DB *sqlx.DB TxManager txmgr.TxManager @@ -78,12 +78,12 @@ func NewChainRelayExtenders(t testing.TB, testopts TestChainOpts) *evmrelay.Chai return cc } -func NewChainRelayExtOpts(t testing.TB, testopts TestChainOpts) evm.ChainRelayExtenderConfig { +func NewChainRelayExtOpts(t testing.TB, testopts TestChainOpts) legacyevm.ChainRelayExtenderConfig { require.NotNil(t, testopts.KeyStore) - opts := evm.ChainRelayExtenderConfig{ + opts := legacyevm.ChainRelayExtenderConfig{ Logger: logger.TestLogger(t), KeyStore: testopts.KeyStore, - ChainOpts: evm.ChainOpts{ + ChainOpts: legacyevm.ChainOpts{ AppConfig: testopts.GeneralConfig, EventBroadcaster: pg.NewNullEventBroadcaster(), MailMon: testopts.MailMon, @@ -138,7 +138,7 @@ func MustGetDefaultChainID(t testing.TB, evmCfgs evmtoml.EVMConfigs) *big.Int { } // Deprecated, this is a replacement function for tests for now removed default chain logic -func MustGetDefaultChain(t testing.TB, cc evm.LegacyChainContainer) evm.Chain { +func MustGetDefaultChain(t testing.TB, cc legacyevm.LegacyChainContainer) legacyevm.Chain { if len(cc.Slice()) == 0 { t.Fatalf("at least one evm chain container must be defined") } diff --git a/core/internal/testutils/evmtest/v2/evmtest.go b/core/internal/testutils/evmtest/v2/evmtest.go index 400690480d7..fa22588c8fb 100644 --- a/core/internal/testutils/evmtest/v2/evmtest.go +++ b/core/internal/testutils/evmtest/v2/evmtest.go @@ -5,7 +5,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/internal/testutils/pgtest/pgtest.go b/core/internal/testutils/pgtest/pgtest.go index 283326de85f..01024b39c37 100644 --- a/core/internal/testutils/pgtest/pgtest.go +++ b/core/internal/testutils/pgtest/pgtest.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/google/uuid" + "github.com/jmoiron/sqlx" "github.com/scylladb/go-reflectx" - "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -34,7 +34,6 @@ func NewSqlxDB(t testing.TB) *sqlx.DB { db, err := sqlx.Open(string(dialects.TransactionWrappedPostgres), uuid.New().String()) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, db.Close()) }) - db.MapperFunc(reflectx.CamelToSnakeASCII) return db diff --git a/core/internal/testutils/pgtest/txdb.go b/core/internal/testutils/pgtest/txdb.go index 598a5dddc55..da9fd6cb2d0 100644 --- a/core/internal/testutils/pgtest/txdb.go +++ b/core/internal/testutils/pgtest/txdb.go @@ -12,7 +12,7 @@ import ( "sync" "testing" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "go.uber.org/multierr" "github.com/smartcontractkit/chainlink/v2/core/config/env" diff --git a/core/internal/testutils/pgtest/txdb_test.go b/core/internal/testutils/pgtest/txdb_test.go index 71960c6150a..37339bf28be 100644 --- a/core/internal/testutils/pgtest/txdb_test.go +++ b/core/internal/testutils/pgtest/txdb_test.go @@ -6,8 +6,10 @@ import ( "time" "github.com/google/uuid" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" ) func TestTxDBDriver(t *testing.T) { @@ -35,7 +37,7 @@ func TestTxDBDriver(t *testing.T) { ensureValuesPresent(t, db) t.Run("Cancel of tx's context does not trigger rollback of driver's tx", func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(testutils.Context(t)) _, err := db.BeginTx(ctx, nil) assert.NoError(t, err) cancel() diff --git a/core/internal/testutils/testutils.go b/core/internal/testutils/testutils.go index 938d814b9eb..6ffd873d092 100644 --- a/core/internal/testutils/testutils.go +++ b/core/internal/testutils/testutils.go @@ -27,7 +27,7 @@ import ( "github.com/tidwall/gjson" "go.uber.org/zap/zaptest/observer" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -451,3 +451,7 @@ func MustDecodeBase64(s string) (b []byte) { } return } + +func SkipFlakey(t *testing.T, ticketURL string) { + t.Skip("Flakey", ticketURL) +} diff --git a/core/logger/audit/audit_logger.go b/core/logger/audit/audit_logger.go index 38afd77a284..ef66a063a55 100644 --- a/core/logger/audit/audit_logger.go +++ b/core/logger/audit/audit_logger.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "encoding/json" + "errors" + "fmt" "io" "net" "net/http" @@ -11,13 +13,11 @@ import ( "os" "time" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/store/models" - "github.com/smartcontractkit/chainlink/v2/core/utils" - - "github.com/pkg/errors" ) const bufferCapacity = 2048 @@ -26,7 +26,7 @@ const webRequestTimeout = 10 type Data = map[string]any type AuditLogger interface { - services.ServiceCtx + services.Service Audit(eventID EventID, data Data) } @@ -47,7 +47,7 @@ type AuditLoggerService struct { loggingClient HTTPAuditLoggerInterface // Abstract type for sending logs onward loggingChannel chan wrappedAuditLog - chStop utils.StopChan + chStop services.StopChan chDone chan struct{} } @@ -72,7 +72,7 @@ func NewAuditLogger(logger logger.Logger, config config.AuditLogger) (AuditLogge hostname, err := os.Hostname() if err != nil { - return nil, errors.Errorf("initialization error - unable to get hostname: %s", err) + return nil, fmt.Errorf("initialization error - unable to get hostname: %w", err) } forwardToUrl, err := config.ForwardToUrl() diff --git a/core/logger/internal/colortest/prettyconsole_test.go b/core/logger/internal/colortest/prettyconsole_test.go index fd2ea3f0b17..125f5a2ea50 100644 --- a/core/logger/internal/colortest/prettyconsole_test.go +++ b/core/logger/internal/colortest/prettyconsole_test.go @@ -61,6 +61,12 @@ func TestPrettyConsole_Write(t *testing.T) { "2018-04-12T12:55:28Z \x1b[91m[FATAL] \x1b[0mtop level \x1b[34m\x1b[0m \n", false, }, + { + "control", + `{"ts":1523537728, "level":"fatal", "msg":"\u0008\t\n\r\u000b\u000c\ufffd\ufffd", "hash":"nuances"}`, + "2018-04-12T12:55:28Z \x1b[91m[FATAL] \x1b[0m\\b\t\n\r\\v\\f�� \x1b[34m\x1b[0m \n", + false, + }, {"broken", `{"broken":}`, `{}`, true}, } diff --git a/core/logger/logger.go b/core/logger/logger.go index 8e847a99ac0..1feaf0f5d6d 100644 --- a/core/logger/logger.go +++ b/core/logger/logger.go @@ -12,7 +12,7 @@ import ( "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" - relaylogger "github.com/smartcontractkit/chainlink-relay/pkg/logger" + common "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/static" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -49,7 +49,7 @@ func init() { } } -var _ relaylogger.Logger = (Logger)(nil) +var _ common.Logger = (Logger)(nil) //go:generate mockery --quiet --name Logger --output . --filename logger_mock_test.go --inpackage --case=underscore //go:generate mockery --quiet --name Logger --output ./mocks/ --case=underscore diff --git a/core/logger/prettyconsole.go b/core/logger/prettyconsole.go index 69427f74715..5150f1f3d69 100644 --- a/core/logger/prettyconsole.go +++ b/core/logger/prettyconsole.go @@ -6,8 +6,10 @@ import ( "net/url" "os" "sort" + "strconv" "strings" "time" + "unicode" "github.com/fatih/color" "github.com/tidwall/gjson" @@ -72,7 +74,7 @@ func generateHeadline(js gjson.Result) string { tsStr, " ", coloredLevel(js.Get("level")), - fmt.Sprintf("%-50s", js.Get("msg")), + fmt.Sprintf("%-50s", sanitized(js.Get("msg").String())), " ", fmt.Sprintf("%-32s", blue(js.Get("caller"))), } @@ -105,7 +107,7 @@ func generateDetails(js gjson.Result) string { var details strings.Builder for _, v := range keys { - details.WriteString(fmt.Sprintf("%s=%v ", green(v), data[v])) + details.WriteString(fmt.Sprintf("%s=%v ", green(sanitized(v)), sanitized(data[v].String()))) } return details.String() @@ -129,3 +131,26 @@ func prettyConsoleSink(s zap.Sink) func(*url.URL) (zap.Sink, error) { return PrettyConsole{s}, nil } } + +type sanitized string + +// String replaces control characters with Go escape sequences, except for newlines and tabs. +// See strconv.QuoteRune. +func (s sanitized) String() string { + var out string + for _, r := range s { + switch r { + case '\n', '\r', '\t': + // allowed + default: + // escape others + if unicode.IsControl(r) { + q := strconv.QuoteRune(r) + out += q[1 : len(q)-1] // trim quotes + continue + } + } + out += string(r) + } + return out +} diff --git a/core/scripts/chaincli/command/keeper/verifiable_load.go b/core/scripts/chaincli/command/keeper/verifiable_load.go index 7d77f0d3a37..33acf9bf3b2 100644 --- a/core/scripts/chaincli/command/keeper/verifiable_load.go +++ b/core/scripts/chaincli/command/keeper/verifiable_load.go @@ -1,6 +1,8 @@ package keeper import ( + "log" + "github.com/spf13/cobra" "github.com/smartcontractkit/chainlink/core/scripts/chaincli/config" @@ -15,6 +17,14 @@ var verifiableLoad = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { cfg := config.New() hdlr := handler.NewKeeper(cfg) - hdlr.GetVerifiableLoadStats(cmd.Context()) + csv, err := cmd.Flags().GetBool("csv") + if err != nil { + log.Fatal("failed to get verify flag: ", err) + } + hdlr.GetVerifiableLoadStats(cmd.Context(), csv) }, } + +func init() { + verifiableLoad.Flags().BoolP("csv", "c", false, "Specify if stats should be output as CSV") +} diff --git a/core/scripts/chaincli/handler/debug.go b/core/scripts/chaincli/handler/debug.go index bdbce799264..fec8c6cd414 100644 --- a/core/scripts/chaincli/handler/debug.go +++ b/core/scripts/chaincli/handler/debug.go @@ -18,16 +18,19 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/smartcontractkit/chainlink/core/scripts/chaincli/config" "github.com/smartcontractkit/chainlink/core/scripts/common" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" - evm "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/encoding" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams" "github.com/smartcontractkit/chainlink/v2/core/utils" bigmath "github.com/smartcontractkit/chainlink/v2/core/utils/big_math" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" ) const ( @@ -70,7 +73,7 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { // verify contract is correct typeAndVersion, err := keeperRegistry21.TypeAndVersion(latestCallOpts) if err != nil { - failCheckConfig("failed to get typeAndVersion: are you sure you have the correct contract address?", err) + failCheckConfig("failed to get typeAndVersion: make sure your registry contract address and archive node are valid", err) } if typeAndVersion != expectedTypeAndVersion { failCheckConfig(fmt.Sprintf("invalid registry contract: this command can only debug %s, got: %s", expectedTypeAndVersion, typeAndVersion), nil) @@ -148,10 +151,10 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { if err != nil { failCheckArgs("unable to parse log index", err) } - // find transaciton receipt + // find transaction receipt _, isPending, err := k.client.TransactionByHash(ctx, txHash) if err != nil { - log.Fatal("failed to fetch tx receipt", err) + log.Fatal("failed to get tx by hash", err) } if isPending { resolveIneligible(fmt.Sprintf("tx %s is still pending confirmation", txHash)) @@ -227,6 +230,13 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { message(fmt.Sprintf("checkUpkeep failed with UpkeepFailureReason %d", checkResult.UpkeepFailureReason)) } if checkResult.UpkeepFailureReason == uint8(encoding.UpkeepFailureReasonTargetCheckReverted) { + // TODO use the new streams lookup lib + //mc := &models.MercuryCredentials{k.cfg.MercuryLegacyURL, k.cfg.MercuryURL, k.cfg.MercuryID, k.cfg.MercuryKey} + //mercuryConfig := evm.NewMercuryConfig(mc, core.StreamsCompatibleABI) + //lggr, _ := logger.NewLogger() + //blockSub := &blockSubscriber{k.client} + //_ = streams.NewStreamsLookup(packer, mercuryConfig, blockSub, keeperRegistry21, k.rpcClient, lggr) + streamsLookupErr, err := packer.DecodeStreamsLookupRequest(checkResult.PerformData) if err == nil { message("upkeep reverted with StreamsLookup") @@ -240,7 +250,7 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { } allowed := false if len(cfg) > 0 { - var privilegeConfig evm.UpkeepPrivilegeConfig + var privilegeConfig streams.UpkeepPrivilegeConfig if err := json.Unmarshal(cfg, &privilegeConfig); err != nil { failUnknown("failed to unmarshal privilege config ", err) } @@ -256,6 +266,10 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { message("using mercury lookup v0.3") } streamsLookup := &StreamsLookup{streamsLookupErr.FeedParamKey, streamsLookupErr.Feeds, streamsLookupErr.TimeParamKey, streamsLookupErr.Time, streamsLookupErr.ExtraData, upkeepID, blockNum} + + if k.cfg.MercuryLegacyURL == "" || k.cfg.MercuryURL == "" || k.cfg.MercuryID == "" || k.cfg.MercuryKey == "" { + failCheckConfig("Mercury configs not set properly, check your MERCURY_LEGACY_URL, MERCURY_URL, MERCURY_ID and MERCURY_KEY", nil) + } handler := NewMercuryLookupHandler(&MercuryCredentials{k.cfg.MercuryLegacyURL, k.cfg.MercuryURL, k.cfg.MercuryID, k.cfg.MercuryKey}, k.rpcClient) state, failureReason, values, _, err := handler.doMercuryRequest(ctx, streamsLookup) if failureReason == UpkeepFailureReasonInvalidRevertDataInput { @@ -271,13 +285,21 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { if err != nil { failUnknown("failed to execute mercury callback ", err) } + if callbackResult.UpkeepFailureReason != 0 { + message(fmt.Sprintf("checkCallback failed with UpkeepFailureReason %d", checkResult.UpkeepFailureReason)) + } upkeepNeeded, performData = callbackResult.UpkeepNeeded, callbackResult.PerformData - // do tenderly simulation + // do tenderly simulations rawCall, err := core.RegistryABI.Pack("checkCallback", upkeepID, values, streamsLookup.extraData) if err != nil { - failUnknown("failed to pack raw checkUpkeep call", err) + failUnknown("failed to pack raw checkCallback call", err) } addLink("checkCallback simulation", tenderlySimLink(k.cfg, chainID, blockNum, rawCall, registryAddress)) + rawCall, err = core.StreamsCompatibleABI.Pack("checkCallback", values, streamsLookup.extraData) + if err != nil { + failUnknown("failed to pack raw checkCallback (direct) call", err) + } + addLink("checkCallback (direct) simulation", tenderlySimLink(k.cfg, chainID, blockNum, rawCall, upkeepInfo.Target)) } else { message("did not revert with StreamsLookup error") } @@ -295,6 +317,22 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { } } +type blockSubscriber struct { + ethClient *ethclient.Client +} + +func (bs *blockSubscriber) LatestBlock() *ocr2keepers.BlockKey { + header, err := bs.ethClient.HeaderByNumber(context.Background(), nil) + if err != nil { + return nil + } + + return &ocr2keepers.BlockKey{ + Number: ocr2keepers.BlockNumber(header.Number.Uint64()), + Hash: header.Hash(), + } +} + func logMatchesTriggerConfig(log *types.Log, config automation_utils_2_1.LogTriggerConfig) bool { if log.Topics[0] != config.Topic0 { return false diff --git a/core/scripts/chaincli/handler/keeper.go b/core/scripts/chaincli/handler/keeper.go index ba8276efb56..439532430ab 100644 --- a/core/scripts/chaincli/handler/keeper.go +++ b/core/scripts/chaincli/handler/keeper.go @@ -14,6 +14,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/umbracle/ethgo/abi" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams" + "github.com/smartcontractkit/chainlink/core/scripts/chaincli/config" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" "github.com/smartcontractkit/chainlink/v2/core/cmd" @@ -34,7 +36,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/verifiable_load_upkeep_wrapper" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keeper" - evm "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21" ) // Keeper is the keepers commands handler @@ -719,7 +720,7 @@ func (k *Keeper) deployUpkeeps(ctx context.Context, registryAddr common.Address, log.Printf("registry version is %s", v) log.Printf("active upkeep ids: %v", activeUpkeepIds) - adminBytes, err := json.Marshal(evm.UpkeepPrivilegeConfig{ + adminBytes, err := json.Marshal(streams.UpkeepPrivilegeConfig{ MercuryEnabled: true, }) if err != nil { diff --git a/core/scripts/chaincli/handler/keeper_deployer.go b/core/scripts/chaincli/handler/keeper_deployer.go index eae8c68d332..7c532753426 100644 --- a/core/scripts/chaincli/handler/keeper_deployer.go +++ b/core/scripts/chaincli/handler/keeper_deployer.go @@ -13,14 +13,15 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/umbracle/ethgo/abi" + ocr2config "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" - ocr3confighelper "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/umbracle/ethgo/abi" "github.com/smartcontractkit/chainlink/core/scripts/chaincli/config" - offchain20config "github.com/smartcontractkit/ocr2keepers/pkg/v2/config" + offchain20config "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" "github.com/smartcontractkit/chainlink/v2/core/cmd" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" diff --git a/core/scripts/chaincli/handler/keeper_verifiable_load.go b/core/scripts/chaincli/handler/keeper_verifiable_load.go index 429a7620079..b71a9af3387 100644 --- a/core/scripts/chaincli/handler/keeper_verifiable_load.go +++ b/core/scripts/chaincli/handler/keeper_verifiable_load.go @@ -2,6 +2,7 @@ package handler import ( "context" + "fmt" "log" "math/big" "sort" @@ -57,7 +58,7 @@ type upkeepStats struct { SortedAllDelays []float64 } -func (k *Keeper) GetVerifiableLoadStats(ctx context.Context) { +func (k *Keeper) GetVerifiableLoadStats(ctx context.Context, csv bool) { var v verifiableLoad var err error addr := common.HexToAddress(k.cfg.VerifiableLoadContractAddress) @@ -84,6 +85,10 @@ func (k *Keeper) GetVerifiableLoadStats(ctx context.Context) { log.Fatalf("failed to get active upkeep IDs from %s: %v", k.cfg.VerifiableLoadContractAddress, err) } + if csv { + fmt.Println("upkeep ID,total performs,p50,p90,p95,p99,max delay,total delay blocks,average perform delay") + } + us := &upkeepStats{BlockNumber: blockNum} resultsChan := make(chan *upkeepInfo, maxUpkeepNum) @@ -94,7 +99,7 @@ func (k *Keeper) GetVerifiableLoadStats(ctx context.Context) { // create a number of workers to process the upkeep ids in batch for i := 0; i < workerNum; i++ { wg.Add(1) - go k.getUpkeepInfo(idChan, resultsChan, v, opts, &wg) + go k.getUpkeepInfo(idChan, resultsChan, v, opts, &wg, csv) } for _, id := range upkeepIds { @@ -120,12 +125,16 @@ func (k *Keeper) GetVerifiableLoadStats(ctx context.Context) { p90, _ := stats.Percentile(us.SortedAllDelays, 90) p95, _ := stats.Percentile(us.SortedAllDelays, 95) p99, _ := stats.Percentile(us.SortedAllDelays, 99) - maxDelay := us.SortedAllDelays[len(us.SortedAllDelays)-1] + + maxDelay := float64(0) + if len(us.SortedAllDelays) > 0 { + maxDelay = us.SortedAllDelays[len(us.SortedAllDelays)-1] + } log.Printf("For total %d upkeeps: total performs: %d, p50: %f, p90: %f, p95: %f, p99: %f, max delay: %f, total delay blocks: %f, average perform delay: %f\n", len(upkeepIds), us.TotalPerforms, p50, p90, p95, p99, maxDelay, us.TotalDelayBlock, us.TotalDelayBlock/float64(us.TotalPerforms)) log.Printf("All STATS ABOVE ARE CALCULATED AT BLOCK %d", blockNum) } -func (k *Keeper) getUpkeepInfo(idChan chan *big.Int, resultsChan chan *upkeepInfo, v verifiableLoad, opts *bind.CallOpts, wg *sync.WaitGroup) { +func (k *Keeper) getUpkeepInfo(idChan chan *big.Int, resultsChan chan *upkeepInfo, v verifiableLoad, opts *bind.CallOpts, wg *sync.WaitGroup, csv bool) { defer wg.Done() for id := range idChan { @@ -171,9 +180,18 @@ func (k *Keeper) getUpkeepInfo(idChan chan *big.Int, resultsChan chan *upkeepInf p90, _ := stats.Percentile(info.SortedAllDelays, 90) p95, _ := stats.Percentile(info.SortedAllDelays, 95) p99, _ := stats.Percentile(info.SortedAllDelays, 99) - maxDelay := info.SortedAllDelays[len(info.SortedAllDelays)-1] - log.Printf("upkeep ID %s has %d performs in total. p50: %f, p90: %f, p95: %f, p99: %f, max delay: %f, total delay blocks: %d, average perform delay: %f\n", id, info.TotalPerforms, p50, p90, p95, p99, maxDelay, uint64(info.TotalDelayBlock), info.TotalDelayBlock/float64(info.TotalPerforms)) + maxDelay := float64(0) + + if len(info.SortedAllDelays) > 0 { + maxDelay = info.SortedAllDelays[len(info.SortedAllDelays)-1] + } + + if csv { + fmt.Printf("%s,%d,%f,%f,%f,%f,%f,%d,%f\n", id, info.TotalPerforms, p50, p90, p95, p99, maxDelay, uint64(info.TotalDelayBlock), info.TotalDelayBlock/float64(info.TotalPerforms)) + } else { + log.Printf("upkeep ID %s has %d performs in total. p50: %f, p90: %f, p95: %f, p99: %f, max delay: %f, total delay blocks: %d, average perform delay: %f\n", id, info.TotalPerforms, p50, p90, p95, p99, maxDelay, uint64(info.TotalDelayBlock), info.TotalDelayBlock/float64(info.TotalPerforms)) + } resultsChan <- info } } diff --git a/core/scripts/chaincli/handler/mercury_lookup_handler.go b/core/scripts/chaincli/handler/mercury_lookup_handler.go index 0db142df9f9..1bd4b2e183c 100644 --- a/core/scripts/chaincli/handler/mercury_lookup_handler.go +++ b/core/scripts/chaincli/handler/mercury_lookup_handler.go @@ -3,7 +3,9 @@ package handler import ( "context" "crypto/hmac" + "crypto/sha256" "encoding/hex" + "encoding/json" "fmt" "io" "math/big" @@ -13,10 +15,6 @@ import ( "strings" "time" - "crypto/sha256" - - "encoding/json" - "github.com/avast/retry-go" ethabi "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" diff --git a/core/scripts/chaincli/handler/ocr2_config.go b/core/scripts/chaincli/handler/ocr2_config.go index 7cf25364058..21b2ad414a3 100644 --- a/core/scripts/chaincli/handler/ocr2_config.go +++ b/core/scripts/chaincli/handler/ocr2_config.go @@ -11,8 +11,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/olekukonko/tablewriter" + "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" - ocr2keepers20config "github.com/smartcontractkit/ocr2keepers/pkg/v2/config" + + ocr2keepers20config "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper2_0" ) diff --git a/core/scripts/chaincli/handler/report.go b/core/scripts/chaincli/handler/report.go index 90f2be96548..622963f1ac0 100644 --- a/core/scripts/chaincli/handler/report.go +++ b/core/scripts/chaincli/handler/report.go @@ -16,11 +16,13 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" "github.com/olekukonko/tablewriter" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - ocr2keepers20 "github.com/smartcontractkit/ocr2keepers/pkg/v2" + + ocr2keepers20 "github.com/smartcontractkit/chainlink-automation/pkg/v2" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper2_0" - evm "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm20" + evm "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20" ) type OCR2ReportDataElem struct { diff --git a/core/scripts/common/arbitrum.go b/core/scripts/common/arbitrum.go index a3a2a24fcd8..251f9b7609c 100644 --- a/core/scripts/common/arbitrum.go +++ b/core/scripts/common/arbitrum.go @@ -1,12 +1,13 @@ package common const ( - ArbitrumGoerliChainID int64 = 421613 - ArbitrumOneChainID int64 = 42161 + ArbitrumGoerliChainID int64 = 421613 + ArbitrumOneChainID int64 = 42161 + ArbitrumSepoliaChainID int64 = 421614 ) // IsArbitrumChainID returns true if and only if the given chain ID corresponds // to an Arbitrum chain (testnet or mainnet). func IsArbitrumChainID(chainID int64) bool { - return chainID == ArbitrumGoerliChainID || chainID == ArbitrumOneChainID + return chainID == ArbitrumGoerliChainID || chainID == ArbitrumOneChainID || chainID == ArbitrumSepoliaChainID } diff --git a/core/scripts/common/helpers.go b/core/scripts/common/helpers.go index d03dcec097f..c141e8a29c4 100644 --- a/core/scripts/common/helpers.go +++ b/core/scripts/common/helpers.go @@ -219,6 +219,11 @@ func explorerLinkPrefix(chainID int64) (prefix string) { case 8453: prefix = "https://basescan.org" + case 280: // zkSync Goerli testnet + prefix = "https://goerli.explorer.zksync.io" + case 324: // zkSync mainnet + prefix = "https://explorer.zksync.io" + default: // Unknown chain, return prefix as-is prefix = "" } diff --git a/core/scripts/common/vrf/model/model.go b/core/scripts/common/vrf/model/model.go index bd0e3bbe364..0972c47e618 100644 --- a/core/scripts/common/vrf/model/model.go +++ b/core/scripts/common/vrf/model/model.go @@ -1,8 +1,9 @@ package model import ( - "github.com/ethereum/go-ethereum/common" "math/big" + + "github.com/ethereum/go-ethereum/common" ) var ( @@ -44,3 +45,8 @@ type ContractAddresses struct { CoordinatorAddress common.Address BatchCoordinatorAddress common.Address } + +type VRFKeyRegistrationConfig struct { + VRFKeyUncompressedPubKey string + RegisterAgainstAddress string +} diff --git a/core/scripts/common/vrf/setup-envs/README.md b/core/scripts/common/vrf/setup-envs/README.md index 33515338a24..9aa76ffbbb7 100644 --- a/core/scripts/common/vrf/setup-envs/README.md +++ b/core/scripts/common/vrf/setup-envs/README.md @@ -15,6 +15,7 @@ export ETH_CHAIN_ID= export ACCOUNT_KEY= ``` 5. execute from `core/scripts/common/vrf/setup-envs` folder + * `--vrf-version` - "v2" or "v2plus" ``` go run . \ --vrf-version="v2plus" \ @@ -28,22 +29,96 @@ go run . \ --bhs-bk-creds-file \ --bhf-node-url=http://localhost:6614 \ --bhf-creds-file \ +--num-eth-keys=1 \ +--num-vrf-keys=1 \ +--sending-key-funding-amount="1e17" \ --deploy-contracts-and-create-jobs="true" \ --subscription-balance="1e19" \ --subscription-balance-native="1e18" \ --batch-fulfillment-enabled="true" \ --min-confs=3 \ ---num-eth-keys=1 \ ---num-vrf-keys=1 \ ---sending-key-funding-amount="1e17" +--register-vrf-key-against-address= ``` -Optional parameters - will not be deployed if specified (NOT WORKING YET) +Optional parameters - will not be deployed if specified ``` --link-address
\ --link-eth-feed
\ +``` + +WIP - Not working yet: +``` --bhs-address
\ --batch-bhs-address
\ --coordinator-address
\ --batch-coordinator-address
-``` \ No newline at end of file +``` + + +## Process Example + +1. If the CL nodes do not have needed amount of ETH and VRF keys, you need to create them first: +``` +go run . \ +--vrf-version="v2" \ +--vrf-primary-node-url= \ +--vrf-primary-creds-file \ +--bhs-node-url= \ +--bhs-creds-file \ +--num-eth-keys=3 \ +--num-vrf-keys=1 \ +--sending-key-funding-amount="1e17" \ +--deploy-contracts-and-create-jobs="false" +``` +Then update corresponding deployment scripts with the new ETH addresses, specifying max gas price for each key + +e.g.: +``` +[[EVM.KeySpecific]] +Key = '' +GasEstimator.PriceMax = '30 gwei' +``` + +2. If the CL nodes already have needed amount of ETH and VRF keys, you can deploy contracts and create jobs with the following command: +NOTE - nodes will be funded at least to the amount specified in `--sending-key-funding-amount` parameter. +``` +go run . \ +--vrf-version="v2" \ +--vrf-primary-node-url= \ +--vrf-primary-creds-file \ +--bhs-node-url= \ +--bhs-creds-file \ +--num-eth-keys=3 \ +--num-vrf-keys=1 \ +--sending-key-funding-amount="1e17" \ +--deploy-contracts-and-create-jobs="true" \ +--subscription-balance="1e19" \ +--subscription-balance-native="1e18" \ +--batch-fulfillment-enabled="true" \ +--min-confs=3 \ +--register-vrf-key-against-address="" \ +--link-address "" \ +--link-eth-feed "" +``` + + +3. We can run sample rand request to see if the setup works. + After previous script was done, we should see the command to run in the console: + + e.g. to trigger rand request: + 1. navigate to `core/scripts/vrfv2plus/testnet` or `core/scripts/vrfv2/testnet` folder + 2. set needed env variables + ``` + export ETH_URL= + export ETH_CHAIN_ID= + export ACCOUNT_KEY= + ``` + 3. Trigger rand request (get this command from the console after running `setup-envs` script ) + ```bash + go run . eoa-load-test-request-with-metrics --consumer-address= --sub-id=1 --key-hash= --request-confirmations <> --requests 1 --runs 1 --cb-gas-limit 1_000_000 + ``` + 4. Then to check that rand request was fulfilled (get this command from the console after running `setup-envs` script ) + ```bash + go run . eoa-load-test-read-metrics --consumer-address= + ``` \ No newline at end of file diff --git a/core/scripts/common/vrf/setup-envs/main.go b/core/scripts/common/vrf/setup-envs/main.go index 6748408f476..94662aa1831 100644 --- a/core/scripts/common/vrf/setup-envs/main.go +++ b/core/scripts/common/vrf/setup-envs/main.go @@ -85,6 +85,8 @@ func main() { batchBHSAddressString := flag.String("batch-bhs-address", "", "address of Batch BHS contract") coordinatorAddressString := flag.String("coordinator-address", "", "address of VRF Coordinator contract") batchCoordinatorAddressString := flag.String("batch-coordinator-address", "", "address Batch VRF Coordinator contract") + registerVRFKeyAgainstAddress := flag.String("register-vrf-key-against-address", "", "VRF Key registration against address - "+ + "from this address you can perform `coordinator.oracleWithdraw` to withdraw earned funds from rand request fulfilments") e := helpers.SetupEnv(false) flag.Parse() @@ -171,6 +173,11 @@ func main() { BatchCoordinatorAddress: common.HexToAddress(*batchCoordinatorAddressString), } + vrfKeyRegistrationConfig := model.VRFKeyRegistrationConfig{ + VRFKeyUncompressedPubKey: nodesMap[model.VRFPrimaryNodeName].VrfKeys[0], + RegisterAgainstAddress: *registerVRFKeyAgainstAddress, + } + var jobSpecs model.JobSpecs switch *vrfVersion { @@ -188,10 +195,10 @@ func main() { } coordinatorConfigV2 := v2scripts.CoordinatorConfigV2{ - MinConfs: minConfs, - MaxGasLimit: &constants.MaxGasLimit, - StalenessSeconds: &constants.StalenessSeconds, - GasAfterPayment: &constants.GasAfterPayment, + MinConfs: *minConfs, + MaxGasLimit: constants.MaxGasLimit, + StalenessSeconds: constants.StalenessSeconds, + GasAfterPayment: constants.GasAfterPayment, FallbackWeiPerUnitLink: constants.FallbackWeiPerUnitLink, FeeConfig: feeConfigV2, } @@ -199,7 +206,7 @@ func main() { jobSpecs = v2scripts.VRFV2DeployUniverse( e, subscriptionBalanceJuels, - &nodesMap[model.VRFPrimaryNodeName].VrfKeys[0], + vrfKeyRegistrationConfig, contractAddresses, coordinatorConfigV2, *batchFulfillmentEnabled, @@ -211,10 +218,10 @@ func main() { FulfillmentFlatFeeNativePPM: uint32(constants.FlatFeeNativePPM), } coordinatorConfigV2Plus := v2plusscripts.CoordinatorConfigV2Plus{ - MinConfs: minConfs, - MaxGasLimit: &constants.MaxGasLimit, - StalenessSeconds: &constants.StalenessSeconds, - GasAfterPayment: &constants.GasAfterPayment, + MinConfs: *minConfs, + MaxGasLimit: constants.MaxGasLimit, + StalenessSeconds: constants.StalenessSeconds, + GasAfterPayment: constants.GasAfterPayment, FallbackWeiPerUnitLink: constants.FallbackWeiPerUnitLink, FeeConfig: feeConfigV2Plus, } @@ -223,7 +230,7 @@ func main() { e, subscriptionBalanceJuels, subscriptionBalanceNativeWei, - &nodesMap[model.VRFPrimaryNodeName].VrfKeys[0], + vrfKeyRegistrationConfig, contractAddresses, coordinatorConfigV2Plus, *batchFulfillmentEnabled, @@ -499,6 +506,8 @@ func createETHKeysIfNeeded(client *clcmd.Shell, app *cli.App, output *bytes.Buff if *maxGasPriceGwei > 0 { helpers.PanicErr(flagSet.Set("max-gas-price-gwei", fmt.Sprintf("%d", *maxGasPriceGwei))) } + err := flagSet.Parse([]string{"-evm-chain-id", os.Getenv("ETH_CHAIN_ID")}) + helpers.PanicErr(err) err = client.CreateETHKey(cli.NewContext(app, flagSet, nil)) helpers.PanicErr(err) helpers.PanicErr(json.Unmarshal(output.Bytes(), &newKey)) diff --git a/core/scripts/functions/templates/oracle.toml b/core/scripts/functions/templates/oracle.toml index 4739252d68e..d21fe4a5e87 100644 --- a/core/scripts/functions/templates/oracle.toml +++ b/core/scripts/functions/templates/oracle.toml @@ -36,6 +36,7 @@ requestTimeoutSec = 300 maxRequestSizesList = [30_720, 51_200, 102_400, 204_800, 512_000, 1_048_576, 2_097_152, 3_145_728, 5_242_880, 10_485_760] maxSecretsSizesList = [10_240, 20_480, 51_200, 102_400, 307_200, 512_000, 1_048_576, 2_097_152] minimumSubscriptionBalance = "2 link" +pastBlocksToPoll = 25 [pluginConfig.OnchainAllowlist] diff --git a/core/scripts/gateway/client/README.md b/core/scripts/gateway/client/README.md new file mode 100644 index 00000000000..8c257753e6a --- /dev/null +++ b/core/scripts/gateway/client/README.md @@ -0,0 +1,20 @@ +# A gateway client script + +This script is used to connect to a gateway server and send commands to it. + +## Usage + +All requests have to be signed on behalf of a user, you need to provide your private key in .env file, e.g. + +``` +PRIVATE_KEY=1a2b3c... +``` + +The script will automatically sign the message using the provided private key. +Run the script without arguments to get the list of available commands. + +## Example + +``` +go run . -gateway_url https://01.functions-gateway.chain.link -don_id fun-avalanche-mainnet-2 -method secrets_list -message_id 123 +``` diff --git a/core/scripts/gateway/client/send_request.go b/core/scripts/gateway/client/send_request.go index 94ff6fb17da..8ab4e8bce79 100644 --- a/core/scripts/gateway/client/send_request.go +++ b/core/scripts/gateway/client/send_request.go @@ -28,7 +28,7 @@ func main() { s4SetSlotId := flag.Uint("s4_set_slot_id", 0, "S4 set slot ID") s4SetVersion := flag.Uint64("s4_set_version", 0, "S4 set version") s4SetExpirationPeriod := flag.Int64("s4_set_expiration_period", 60*60*1000, "S4 how long until the entry expires from now (in milliseconds)") - s4SetPayload := flag.String("s4_set_payload", "", "S4 set payload") + s4SetPayloadFile := flag.String("s4_set_payload_file", "", "S4 payload file to set secret") repeat := flag.Bool("repeat", false, "Repeat sending the request every 10 seconds") flag.Parse() @@ -50,6 +50,16 @@ func main() { } address := crypto.PubkeyToAddress(key.PublicKey) + var s4SetPayload []byte + if *methodName == functions.MethodSecretsSet { + var err error + s4SetPayload, err = os.ReadFile(*s4SetPayloadFile) + if err != nil { + fmt.Println("error reading S4 payload file", err) + return + } + } + // build payload (if relevant) var payloadJSON []byte if *methodName == functions.MethodSecretsSet { @@ -57,7 +67,7 @@ func main() { Address: address.Bytes(), SlotID: *s4SetSlotId, Version: *s4SetVersion, - Payload: []byte(*s4SetPayload), + Payload: s4SetPayload, Expiration: time.Now().UnixMilli() + *s4SetExpirationPeriod, } signature, err := envelope.Sign(key) @@ -70,7 +80,7 @@ func main() { SlotID: envelope.SlotID, Version: envelope.Version, Expiration: envelope.Expiration, - Payload: []byte(*s4SetPayload), + Payload: s4SetPayload, Signature: signature, } @@ -131,7 +141,12 @@ func main() { return } - fmt.Println(string(body)) + var prettyJSON bytes.Buffer + if err := json.Indent(&prettyJSON, body, "", " "); err != nil { + fmt.Println(string(body)) + } else { + fmt.Println(prettyJSON.String()) + } } sendRequest() diff --git a/core/scripts/go.mod b/core/scripts/go.mod index c8b616a4b6e..c7af0541c12 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -1,6 +1,6 @@ module github.com/smartcontractkit/chainlink/core/scripts -go 1.21 +go 1.21.3 // Make sure we're working with the latest chainlink libs replace github.com/smartcontractkit/chainlink/v2 => ../../ @@ -8,11 +8,12 @@ replace github.com/smartcontractkit/chainlink/v2 => ../../ require ( github.com/ava-labs/coreth v0.12.1 github.com/avast/retry-go v3.0.0+incompatible - github.com/docker/docker v24.0.6+incompatible + github.com/docker/docker v24.0.7+incompatible github.com/docker/go-connections v0.4.0 github.com/ethereum/go-ethereum v1.12.0 - github.com/google/go-cmp v0.5.9 - github.com/google/uuid v1.3.1 + github.com/google/go-cmp v0.6.0 + github.com/google/uuid v1.4.0 + github.com/jmoiron/sqlx v1.3.5 github.com/joho/godotenv v1.4.0 github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f github.com/montanaflynn/stats v0.7.1 @@ -20,11 +21,10 @@ require ( github.com/pelletier/go-toml/v2 v2.1.0 github.com/pkg/errors v0.9.1 github.com/shopspring/decimal v1.3.1 + github.com/smartcontractkit/chainlink-automation v1.0.1 + github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 - github.com/smartcontractkit/libocr v0.0.0-20231020123319-d255366a6545 - github.com/smartcontractkit/ocr2keepers v0.7.27 - github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687 - github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb + github.com/smartcontractkit/libocr v0.0.0-20231107151413-13e0202ae8d7 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.4 @@ -44,6 +44,7 @@ require ( filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect github.com/CosmWasm/wasmd v0.40.1 // indirect github.com/CosmWasm/wasmvm v1.2.4 // indirect @@ -55,7 +56,7 @@ require ( github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/ava-labs/avalanchego v1.10.1 // indirect - github.com/avast/retry-go/v4 v4.5.0 // indirect + github.com/avast/retry-go/v4 v4.5.1 // 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.1-0.20220910012023-760eaf8b6816 // indirect @@ -80,7 +81,7 @@ require ( github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect github.com/cosmos/cosmos-sdk v0.47.4 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect - github.com/cosmos/gogoproto v1.4.10 // indirect + github.com/cosmos/gogoproto v1.4.11 // indirect github.com/cosmos/iavl v0.20.0 // indirect github.com/cosmos/ibc-go/v7 v7.0.1 // indirect github.com/cosmos/ics23/go v0.9.1-0.20221207100636-b1abd8678aab // indirect @@ -102,10 +103,10 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/esote/minmaxheap v1.0.0 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/fxamacker/cbor/v2 v2.4.0 // indirect + github.com/fxamacker/cbor/v2 v2.5.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gagliardetto/binary v0.7.1 // indirect github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27 // indirect @@ -119,38 +120,40 @@ require ( github.com/gin-contrib/size v0.0.0-20230212012657-e14a14094dc4 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect + github.com/go-ldap/ldap/v3 v3.4.5 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/go-webauthn/revoke v0.1.9 // indirect - github.com/go-webauthn/webauthn v0.8.2 // indirect + github.com/go-webauthn/webauthn v0.9.1 // indirect + github.com/go-webauthn/x v0.1.4 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.3 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/glog v1.1.0 // indirect + github.com/golang-jwt/jwt/v5 v5.1.0 // indirect + github.com/golang/glog v1.1.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/go-tpm v0.3.3 // indirect + github.com/google/go-tpm v0.9.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect + github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect github.com/gorilla/context v1.1.1 // indirect - github.com/gorilla/securecookie v1.1.1 // indirect - github.com/gorilla/sessions v1.2.1 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.2.2 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/grafana/pyroscope-go v1.0.4 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.4 // indirect github.com/graph-gophers/dataloader v5.0.0+incompatible // indirect @@ -196,10 +199,9 @@ require ( 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.5 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.0 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/koron/go-ssdp v0.0.2 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -249,9 +251,9 @@ require ( github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect github.com/minio/sha256-simd v0.1.1 // indirect @@ -283,12 +285,12 @@ require ( github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/pressly/goose/v3 v3.15.1 // indirect + github.com/pressly/goose/v3 v3.16.0 // indirect github.com/prometheus/client_golang v1.17.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/prometheus/prometheus v0.46.0 // indirect + github.com/prometheus/prometheus v0.48.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rjeczalik/notify v0.9.3 // indirect @@ -297,14 +299,16 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/scylladb/go-reflectx v1.0.1 // indirect + github.com/sethvargo/go-retry v0.2.4 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/shirou/gopsutil/v3 v3.23.9 // indirect + github.com/shirou/gopsutil/v3 v3.23.10 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 // indirect - github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20230913032705-f924d753cc47 // indirect - github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231020230319-2ede955d1dc9 // indirect - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231023133638-72f4e799ab05 // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231024133459-1ef3a11319eb // indirect + github.com/smartcontractkit/chainlink-common v0.1.7-0.20231201152724-d550085eb3c4 // indirect + github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231117191236-12eab01a4542 // indirect + github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d // indirect + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231129183458-faee879168b3 // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231117204155-b253a2f56664 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/wsrpc v0.7.2 // indirect @@ -322,7 +326,7 @@ require ( github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 // indirect github.com/theodesp/go-heaps v0.0.0-20190520121037-88e35354fe0a // indirect github.com/tidwall/btree v1.6.0 // indirect - github.com/tidwall/gjson v1.16.0 // indirect + github.com/tidwall/gjson v1.17.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect @@ -342,33 +346,33 @@ require ( go.dedis.ch/fixbuf v1.0.3 // indirect go.etcd.io/bbolt v1.3.7 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/sdk v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect + go.opentelemetry.io/otel v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect + go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/sdk v1.21.0 // indirect + go.opentelemetry.io/otel/trace v1.21.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/arch v0.4.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect - golang.org/x/mod v0.13.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sync v0.4.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.14.0 // indirect - gonum.org/v1/gonum v0.13.0 // indirect - google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 // indirect - google.golang.org/grpc v1.58.3 // indirect + golang.org/x/arch v0.6.0 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.16.0 // indirect + gonum.org/v1/gonum v0.14.0 // indirect + google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/guregu/null.v2 v2.1.2 // indirect gopkg.in/guregu/null.v4 v4.0.0 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index c4d0575bb20..7cea79eb76e 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -18,23 +18,23 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb 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.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= -cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.9 h1:e7ITSqGFFk4rbz/JFIqZh3G4VEHguhAL4BQcFlWtU68= +cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM= 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/compute v1.22.0 h1:cB8R6FtUtT1TYGl5R3xuxnW6OUIc/DrT2aiR16TTG7Y= -cloud.google.com/go/compute v1.22.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 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/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.4 h1:K6n/GZHFTtEoKT5aUG3l9diPi0VduZNQ1PfdnpkkIFk= +cloud.google.com/go/iam v1.1.4/go.mod h1:l/rg8l1AaA+VFMho/HYx2Vv6xinPSLMF8qfhRPIZ0L8= 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= @@ -79,12 +79,18 @@ github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOv github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 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/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= +github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= +github.com/ClickHouse/clickhouse-go/v2 v2.15.0 h1:G0hTKyO8fXXR1bGnZ0DY3vTG01xYfOGW76zgjg5tmC4= +github.com/ClickHouse/clickhouse-go/v2 v2.15.0/go.mod h1:kXt1SRq0PIRa6aKZD7TnFnY9PQKmc2b13sHtOYcK6cQ= 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/wasmd v0.40.1 h1:LxbO78t/6S8TkeQlUrJ0m5O87HtAwLx4RGHq3rdrOEU= @@ -124,11 +130,15 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy 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/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4= github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= @@ -144,12 +154,12 @@ github.com/ava-labs/coreth v0.12.1 h1:EWSkFGHGVUxmu1pnSK/2pdcxaAVHbGspHqO3Ag+i7s github.com/ava-labs/coreth v0.12.1/go.mod h1:/5x54QlIKjlPebkdzTA5ic9wXdejbWOnQosztkv9jxo= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= -github.com/avast/retry-go/v4 v4.5.0 h1:QoRAZZ90cj5oni2Lsgl2GW8mNTnUCnmpx/iKpwVisHg= -github.com/avast/retry-go/v4 v4.5.0/go.mod h1:7hLEXp0oku2Nir2xBAsg0PTphp9z71bN5Aq1fboC3+I= +github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o= +github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc= 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.44.302 h1:ST3ko6GrJKn3Xi+nAvxjG3uk/V1pW8KC52WLeIxqqNk= -github.com/aws/aws-sdk-go v1.44.302/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= +github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= 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= @@ -242,8 +252,8 @@ github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0 github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4= github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= -github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= -github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= +github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= 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= @@ -265,8 +275,8 @@ github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= -github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI= -github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= +github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= +github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= github.com/cosmos/iavl v0.20.0 h1:fTVznVlepH0KK8NyKq8w+U7c2L6jofa27aFX6YGlm38= github.com/cosmos/iavl v0.20.0/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= github.com/cosmos/ibc-go/v7 v7.0.1 h1:NIBNRWjlOoFvFQu1ZlgwkaSeHO5avf4C1YQiWegt8jw= @@ -331,10 +341,12 @@ 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/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= +github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= -github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 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-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -345,6 +357,10 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/elastic/go-sysinfo v1.11.1 h1:g9mwl05njS4r69TisC+vwHWTSKywZFYYUu3so3T/Lao= +github.com/elastic/go-sysinfo v1.11.1/go.mod h1:6KQb31j0QeWBDF88jIdWSxE8cwoOB9tO4Y4osN7Q70E= +github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0= +github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss= 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= @@ -369,8 +385,8 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod 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.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -387,8 +403,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= -github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gagliardetto/binary v0.6.1/go.mod h1:aOfYkc20U0deHaHn/LVZXiqlkDbFAX0FpTlDhsXa0S0= @@ -424,10 +440,16 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= +github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= +github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= 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= @@ -438,14 +460,16 @@ github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEai github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= +github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= 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-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= @@ -470,10 +494,10 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me 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-webauthn/revoke v0.1.9 h1:gSJ1ckA9VaKA2GN4Ukp+kiGTk1/EXtaDb1YE8RknbS0= -github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w= -github.com/go-webauthn/webauthn v0.8.2 h1:8KLIbpldjz9KVGHfqEgJNbkhd7bbRXhNw4QWFJE15oA= -github.com/go-webauthn/webauthn v0.8.2/go.mod h1:d+ezx/jMCNDiqSMzOchuynKb9CVU1NM9BumOnokfcVQ= +github.com/go-webauthn/webauthn v0.9.1 h1:KuZjvUX9JTuFjB2n7kZhM6n76BClLUFbFM8SLKnrXpo= +github.com/go-webauthn/webauthn v0.9.1/go.mod h1:m315kRGbUljOytw8b9FGWG9QzErjI5v02pNFCF3lwpI= +github.com/go-webauthn/x v0.1.4 h1:sGmIFhcY70l6k7JIDfnjVBiAAFEssga5lXIUXe0GtAs= +github.com/go-webauthn/x v0.1.4/go.mod h1:75Ug0oK6KYpANh5hDOanfDI+dvPWHk788naJVG/37H8= 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= @@ -491,12 +515,15 @@ github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q8 github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= 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= @@ -554,17 +581,14 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI= -github.com/google/go-tpm v0.3.0/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw= -github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo= -github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51BeghL4= -github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0= -github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4= +github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -589,18 +613,20 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +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.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.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= -github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= 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.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= @@ -614,15 +640,14 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grafana/pyroscope-go v1.0.4 h1:oyQX0BOkL+iARXzHuCdIF5TQ7/sRSel1YFViMHC7Bm0= github.com/grafana/pyroscope-go v1.0.4/go.mod h1:0d7ftwSMBV/Awm7CCiYmHQEG8Y44Ma3YSjt+nWcWztY= github.com/grafana/pyroscope-go/godeltaprof v0.1.4 h1:mDsJ3ngul7UfrHibGQpV66PbZ3q1T8glz/tK3bQKKEk= @@ -726,6 +751,8 @@ github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3 github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= 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.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= @@ -826,10 +853,15 @@ github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQ github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw= +github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= 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.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 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 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -854,9 +886,13 @@ github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= 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= @@ -885,8 +921,8 @@ github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= @@ -1162,8 +1198,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -1172,8 +1208,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 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.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= @@ -1320,25 +1356,29 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 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.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM= -github.com/opencontainers/runc v1.1.9/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/opencontainers/runc v1.1.10 h1:EaL5WeO9lv9wmS6SASjszOeQdSctvpbu0DdBQBizE40= +github.com/opencontainers/runc v1.1.10/go.mod h1:+/R6+KmDlh+hOO8NkjmgkG9Qzvypzk0yXxAPYYR65+M= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= +github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -1348,6 +1388,8 @@ github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdU github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -1362,8 +1404,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 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/pressly/goose/v3 v3.15.1 h1:dKaJ1SdLvS/+HtS8PzFT0KBEtICC1jewLXM+b3emlv8= -github.com/pressly/goose/v3 v3.15.1/go.mod h1:0E3Yg/+EwYzO6Rz2P98MlClFgIcoujbVRs575yi3iIM= +github.com/pressly/goose/v3 v3.16.0 h1:xMJUsZdHLqSnCqESyKSqEfcYVYsUuup1nrOhaEFftQg= +github.com/pressly/goose/v3 v3.16.0/go.mod h1:JwdKVnmCRhnF6XLQs2mHEQtucFD49cQBdRM4UiwkxsM= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 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= @@ -1380,16 +1422,16 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q 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.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/prometheus/prometheus v0.46.0 h1:9JSdXnsuT6YsbODEhSQMwxNkGwPExfmzqG73vCMk/Kw= -github.com/prometheus/prometheus v0.46.0/go.mod h1:10L5IJE5CEsjee1FnOcVswYXlPIscDWWt3IJ2UDYrz4= +github.com/prometheus/prometheus v0.48.0 h1:yrBloImGQ7je4h8M10ujGh4R6oxYQJQKlMuETwNskGk= +github.com/prometheus/prometheus v0.48.0/go.mod h1:SRw624aMAxTfryAcP8rOjg4S/sHHaetx2lyJJ2nM83g= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= @@ -1437,11 +1479,15 @@ github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtm 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= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= +github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= 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.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= -github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= +github.com/shirou/gopsutil/v3 v3.23.10 h1:/N42opWlYzegYaVkWejXWJpbzKv2JDy3mrgGzKsh9hM= +github.com/shirou/gopsutil/v3 v3.23.10/go.mod h1:JIE26kpucQi+innVlAUnIEOSBhBUkirr5b44yr55+WE= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -1456,26 +1502,26 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 h1:T3lFWumvbfM1u/etVq42Afwq/jtNSBSOA8n5jntnNPo= github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704/go.mod h1:2QuJdEouTWjh5BDy5o/vgGXQtR4Gz8yH1IYB5eT7u4M= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20230913032705-f924d753cc47 h1:vdieOW3CZGdD2R5zvCSMS+0vksyExPN3/Fa1uVfld/A= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20230913032705-f924d753cc47/go.mod h1:xMwqRdj5vqYhCJXgKVqvyAwdcqM6ZAEhnwEQ4Khsop8= -github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231020230319-2ede955d1dc9 h1:fFD5SgSJtnXvkGLK3CExNKpUIz4sGrNNkKv3Ljw63Hk= -github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231020230319-2ede955d1dc9/go.mod h1:M9U1JV7IQi8Sfj4JR1qSi1tIh6omgW78W/8SHN/8BUQ= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231023133638-72f4e799ab05 h1:DaPSVnxe7oz1QJ+AVIhQWs1W3ubQvwvGo9NbHpMs1OQ= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231023133638-72f4e799ab05/go.mod h1:o0Pn1pbaUluboaK6/yhf8xf7TiFCkyFl6WUOdwqamuU= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231024133459-1ef3a11319eb h1:HiluOfEVGOQTM6BTDImOqYdMZZ7qq7fkZ3TJdmItNr8= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231024133459-1ef3a11319eb/go.mod h1:/30flFG4L/iCYAFeA3DUzR0xuHSxAMONiWTzyzvsNwo= +github.com/smartcontractkit/chainlink-automation v1.0.1 h1:vVjBFq2Zsz21kPy1Pb0wpjF9zrbJX+zjXphDeeR4XZk= +github.com/smartcontractkit/chainlink-automation v1.0.1/go.mod h1:INSchkV3ntyDdlZKGWA030MPDpp6pbeuiRkRKYFCm2k= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20231201152724-d550085eb3c4 h1:qau0/AHvPwMR3p6gWsFWC4qVfEtSEALtBetTOpHA2IU= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20231201152724-d550085eb3c4/go.mod h1:Hrru9i7n+WEYyW2aIt3/YGPhxLX+HEGWnhk3yVXeDF8= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231117191236-12eab01a4542 h1:oewYJtdRkJKUHCNDCj5C2LQe6Oq6qy975g931nfG0cc= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231117191236-12eab01a4542/go.mod h1:EpvRoycRD+kniYlz+pCpRT5e+fmPm0mSD/vmND+0oMg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d h1:w4MsbOtNk6nD/mcXLstHWk9hB6g7QLtcAfhPjhwvOaQ= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d/go.mod h1:YPAfLNowdBwiKiYOwgwtbJHi8AJWbcxkbOY0ItAvkfc= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231129183458-faee879168b3 h1:DudPr8ZNMEVgDwHBvnrpvK96JrGcGCG+LLBnlqaPGnE= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231129183458-faee879168b3/go.mod h1:UfW7/PZKon+iDEHtrHOfvMnS5GfYOW/SdMZ6P97rPEk= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231117204155-b253a2f56664 h1:yxaHuDTtj2xxtsR8b+LJw8xDvyr6v/F6GP3InsP4wPI= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231117204155-b253a2f56664/go.mod h1:3Fa+HQTZ3R3fPC0hUqugvoo+NEeo8Y4J2WOnQfi7O34= +github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 h1:FFdvEzlYwcuVHkdZ8YnZR/XomeMGbz5E2F2HZI3I3w8= +github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868/go.mod h1:Kn1Hape05UzFZ7bOUnm3GVsHzP0TNrVmpfXYNHdqGGs= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 h1:ko88+ZznniNJZbZPWAvHQU8SwKAdHngdDZ+pvVgB5ss= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJif132UCdjo8u43i7iPN1/MFnu49hv7lFGFftCHKU= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f/go.mod h1:MvMXoufZAtqExNexqi4cjrNYE9MefKddKylxjS+//n0= -github.com/smartcontractkit/libocr v0.0.0-20231020123319-d255366a6545 h1:qOsw2ETQD/Sb/W2xuYn2KPWjvvsWA0C+l19rWFq8iNg= -github.com/smartcontractkit/libocr v0.0.0-20231020123319-d255366a6545/go.mod h1:2lyRkw/qLQgUWlrWWmq5nj0y90rWeO6Y+v+fCakRgb0= -github.com/smartcontractkit/ocr2keepers v0.7.27 h1:kwqMrzmEdq6gH4yqNuLQCbdlED0KaIjwZzu3FF+Gves= -github.com/smartcontractkit/ocr2keepers v0.7.27/go.mod h1:1QGzJURnoWpysguPowOe2bshV0hNp1YX10HHlhDEsas= -github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687 h1:NwC3SOc25noBTe1KUQjt45fyTIuInhoE2UfgcHAdihM= -github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687/go.mod h1:YYZq52t4wcHoMQeITksYsorD+tZcOyuVU5+lvot3VFM= -github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb h1:OMaBUb4X9IFPLbGbCHsMU+kw/BPCrewaVwWGIBc0I4A= -github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb/go.mod h1:HNUu4cJekUdsJbwRBCiOybtkPJEfGRELQPe2tkoDEyk= +github.com/smartcontractkit/libocr v0.0.0-20231107151413-13e0202ae8d7 h1:21V61XOYSxpFmFqlhr5IaEh1uQ1F6CewJ30D/U/P34c= +github.com/smartcontractkit/libocr v0.0.0-20231107151413-13e0202ae8d7/go.mod h1:2lyRkw/qLQgUWlrWWmq5nj0y90rWeO6Y+v+fCakRgb0= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 h1:yiKnypAqP8l0OX0P3klzZ7SCcBUxy5KqTAKZmQOvSQE= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:q6f4fe39oZPdsh1i57WznEZgxd8siidMaSFq3wdPmVg= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 h1:Dai1bn+Q5cpeGMQwRdjOdVjG8mmFFROVkSKuUgBErRQ= @@ -1499,7 +1545,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -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.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= @@ -1510,7 +1555,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn 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.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= @@ -1557,8 +1601,8 @@ github.com/theodesp/go-heaps v0.0.0-20190520121037-88e35354fe0a/go.mod h1:/sfW47 github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg= -github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= @@ -1603,6 +1647,8 @@ github.com/valyala/fastjson v1.4.1/go.mod h1:nV6MsjxL2IMJQUoHDIrjEI7oLyeqK6aBD7E 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/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw= +github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= 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= @@ -1615,13 +1661,21 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7V github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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 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/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd h1:dzWP1Lu+A40W883dK/Mr3xyDSM/2MggS8GtHT0qgAnE= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= +github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2 h1:E0yUuuX7UmPxXm92+yQCjMveLFO3zfvYFIJVuAqsVRA= +github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2/go.mod h1:fjBLQ2TdQNl4bMjuWl9adoTGBypwUTPoGC+EqYqiIcU= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= @@ -1660,20 +1714,20 @@ 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.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 h1:RsQi0qJ2imFfCvZabqzM9cNXBG8k6gXMv1A0cXRmH6A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0/go.mod h1:G17FHPDLt74bCI7tJ4CMitEk4BXTYG4FW6XUpkPBXa4= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1685,8 +1739,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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= @@ -1707,8 +1761,8 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= -golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= +golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1745,8 +1799,9 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/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= @@ -1757,8 +1812,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 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-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= +golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1785,8 +1840,9 @@ 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -1804,7 +1860,6 @@ golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn 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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1846,8 +1901,9 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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= @@ -1858,8 +1914,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ 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-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= 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= @@ -1872,8 +1928,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -1945,7 +2002,6 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w 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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/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-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1967,16 +2023,17 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 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= @@ -1987,14 +2044,15 @@ 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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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= @@ -2063,8 +2121,9 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 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= @@ -2072,10 +2131,10 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= -gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= +gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= 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= @@ -2096,8 +2155,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 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.132.0 h1:8t2/+qZ26kAOGSmOiHwVycqVaDg7q3JDILrNi/Z6rvc= -google.golang.org/api v0.132.0/go.mod h1:AeTBC6GpJnJSRJjktDcPX0QwtS8pGYZOV6MSuSCusw0= +google.golang.org/api v0.147.0 h1:Can3FaQo9LlVqxJCodNmeZW/ib3/qKAY3rFeXiHo5gc= +google.golang.org/api v0.147.0/go.mod h1:pQ/9j83DcmPd/5C9e2nFOdjjNkDZ1G+zkbK2uvdkJMs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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= @@ -2105,8 +2164,9 @@ 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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 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= @@ -2152,16 +2212,15 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753 h1:+VoAg+OKmWaommL56xmZSE2sUK8A7m6SUO7X89F2tbw= -google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753/go.mod h1:iqkVr8IRpZ53gx1dEnWlCUIEwDWqWARWrbzpasaTNYM= -google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753 h1:lCbbUxUDD+DiXx9Q6F/ttL0aAu7N2pz8XnmMm8ZW4NE= -google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 h1:XUODHrpzJEUeWmVo/jfNTLj0YyVveOo28oE6vkFbkO4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U= +google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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= @@ -2183,8 +2242,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG 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.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc/examples v0.0.0-20210424002626-9572fd6faeae/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= 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= @@ -2259,22 +2318,24 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt 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.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= -modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= -modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= +modernc.org/libc v1.32.0 h1:yXatHTrACp3WaKNRCoZwUK7qj5V8ep1XyY0ka4oYcNc= +modernc.org/libc v1.32.0/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw= -modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8= +modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/core/scripts/ocr2vrf/main.go b/core/scripts/ocr2vrf/main.go index 60260c54d9d..532fbd3f22c 100644 --- a/core/scripts/ocr2vrf/main.go +++ b/core/scripts/ocr2vrf/main.go @@ -12,10 +12,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/shopspring/decimal" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" + + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" ) diff --git a/core/scripts/ocr2vrf/util.go b/core/scripts/ocr2vrf/util.go index a2ff55524d3..4ec78472e50 100644 --- a/core/scripts/ocr2vrf/util.go +++ b/core/scripts/ocr2vrf/util.go @@ -21,10 +21,11 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/ocr2vrf/altbn_128" - "github.com/smartcontractkit/ocr2vrf/dkg" - "github.com/smartcontractkit/ocr2vrf/ocr2vrf" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" + + "github.com/smartcontractkit/chainlink-vrf/altbn_128" + "github.com/smartcontractkit/chainlink-vrf/dkg" + "github.com/smartcontractkit/chainlink-vrf/ocr2vrf" + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_forwarder" diff --git a/core/scripts/ocr2vrf/verify.go b/core/scripts/ocr2vrf/verify.go index 4b91148a8c2..7d7fb94496a 100644 --- a/core/scripts/ocr2vrf/verify.go +++ b/core/scripts/ocr2vrf/verify.go @@ -9,12 +9,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" bn256 "github.com/ethereum/go-ethereum/crypto/bn256/google" - "github.com/smartcontractkit/ocr2vrf/altbn_128" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/group/mod" "go.dedis.ch/kyber/v3/pairing" + "github.com/smartcontractkit/chainlink-vrf/altbn_128" + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" + helpers "github.com/smartcontractkit/chainlink/core/scripts/common" dkgContract "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ocr2vrf/generated/dkg" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ocr2vrf/generated/vrf_beacon" diff --git a/core/scripts/vrfv2/testnet/README.md b/core/scripts/vrfv2/testnet/README.md index 1b2d986f554..b527c576fd0 100644 --- a/core/scripts/vrfv2/testnet/README.md +++ b/core/scripts/vrfv2/testnet/README.md @@ -57,11 +57,19 @@ To deploy a full VRF environment on-chain, run: ```shell go run . deploy-universe \ ---sending-key-funding-amount 100000000000000000 \ ---subscription-balance=10000000000000000000 \ +--subscription-balance=5000000000000000000 \ #5 LINK --uncompressed-pub-key= \ ---vrf-primary-node-sending-keys="" \ ---batch-fulfillment-enabled false +--vrf-primary-node-sending-keys="" \ #used to fund the keys and for sample VRF Job Spec generation +--sending-key-funding-amount 100000000000000000 \ #0.1 ETH, fund addresses specified in vrf-primary-node-sending-keys +--batch-fulfillment-enabled false \ #only used for sample VRF Job Spec generation +--register-vrf-key-against-address=<"from this address you can perform `coordinator.oracleWithdraw` to withdraw earned funds from rand request fulfilments> +``` +```shell +go run . deploy-universe \ +--subscription-balance=5000000000000000000 \ +--uncompressed-pub-key="0xf3706e247a7b205c8a8bd25a6e8c4650474da496151371085d45beeead27e568c1a5e8330c7fa718f8a31226efbff6632ed6f8ed470b637aa9be2b948e9dcef6" \ +--batch-fulfillment-enabled false \ +--register-vrf-key-against-address="0x23b5613fc04949F4A53d1cc8d6BCCD21ffc38C11" ``` ## Deploying the Consumer Contract diff --git a/core/scripts/vrfv2/testnet/main.go b/core/scripts/vrfv2/testnet/main.go index 5b216776bd9..5856256504b 100644 --- a/core/scripts/vrfv2/testnet/main.go +++ b/core/scripts/vrfv2/testnet/main.go @@ -21,10 +21,10 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/shopspring/decimal" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_blockhash_store" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_vrf_coordinator_v2" diff --git a/core/scripts/vrfv2/testnet/v2scripts/super_scripts.go b/core/scripts/vrfv2/testnet/v2scripts/super_scripts.go index f5e37005690..4a1c1fcec41 100644 --- a/core/scripts/vrfv2/testnet/v2scripts/super_scripts.go +++ b/core/scripts/vrfv2/testnet/v2scripts/super_scripts.go @@ -5,10 +5,6 @@ import ( "encoding/hex" "flag" "fmt" - "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/constants" - "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/jobs" - "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/model" - "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/util" "math/big" "os" "strings" @@ -18,17 +14,24 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/shopspring/decimal" + "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/constants" + "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/jobs" + "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/model" + "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/util" + + evmtypes "github.com/ethereum/go-ethereum/core/types" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_owner" "github.com/smartcontractkit/chainlink/v2/core/services/signatures/secp256k1" ) type CoordinatorConfigV2 struct { - MinConfs *int - MaxGasLimit *int64 - StalenessSeconds *int64 - GasAfterPayment *int64 + MinConfs int + MaxGasLimit int64 + StalenessSeconds int64 + GasAfterPayment int64 FallbackWeiPerUnitLink *big.Int FeeConfig vrf_coordinator_v2.VRFCoordinatorV2FeeConfig } @@ -37,21 +40,26 @@ func DeployUniverseViaCLI(e helpers.Environment) { deployCmd := flag.NewFlagSet("deploy-universe", flag.ExitOnError) // required flags - linkAddress := *deployCmd.String("link-address", "", "address of link token") - linkEthAddress := *deployCmd.String("link-eth-feed", "", "address of link eth feed") - bhsContractAddressString := *deployCmd.String("bhs-address", "", "address of BHS contract") - batchBHSAddressString := *deployCmd.String("batch-bhs-address", "", "address of Batch BHS contract") - coordinatorAddressString := *deployCmd.String("coordinator-address", "", "address of VRF Coordinator contract") - batchCoordinatorAddressString := *deployCmd.String("batch-coordinator-address", "", "address Batch VRF Coordinator contract") + linkAddress := deployCmd.String("link-address", "", "address of link token") + linkEthAddress := deployCmd.String("link-eth-feed", "", "address of link eth feed") + bhsContractAddressString := deployCmd.String("bhs-address", "", "address of BHS contract") + batchBHSAddressString := deployCmd.String("batch-bhs-address", "", "address of Batch BHS contract") + coordinatorAddressString := deployCmd.String("coordinator-address", "", "address of VRF Coordinator contract") + batchCoordinatorAddressString := deployCmd.String("batch-coordinator-address", "", "address Batch VRF Coordinator contract") subscriptionBalanceJuelsString := deployCmd.String("subscription-balance", constants.SubscriptionBalanceJuels, "amount to fund subscription") nodeSendingKeyFundingAmount := deployCmd.String("sending-key-funding-amount", constants.NodeSendingKeyFundingAmount, "CL node sending key funding amount") batchFulfillmentEnabled := deployCmd.Bool("batch-fulfillment-enabled", constants.BatchFulfillmentEnabled, "whether send randomness fulfillments in batches inside one tx from CL node") + deployVRFOwner := deployCmd.Bool("deploy-vrf-owner", false, "whether to deploy VRF owner contracts") + // optional flags fallbackWeiPerUnitLinkString := deployCmd.String("fallback-wei-per-unit-link", constants.FallbackWeiPerUnitLink.String(), "fallback wei/link ratio") - registerKeyUncompressedPubKey := deployCmd.String("uncompressed-pub-key", "", "uncompressed public key") + registerVRFKeyUncompressedPubKey := deployCmd.String("uncompressed-pub-key", "", "uncompressed public key") + registerVRFKeyAgainstAddress := deployCmd.String("register-vrf-key-against-address", "", "VRF Key registration against address - "+ + "from this address you can perform `coordinator.oracleWithdraw` to withdraw earned funds from rand request fulfilments") + vrfPrimaryNodeSendingKeysString := deployCmd.String("vrf-primary-node-sending-keys", "", "VRF Primary Node sending keys") minConfs := deployCmd.Int("min-confs", constants.MinConfs, "min confs") @@ -87,27 +95,27 @@ func DeployUniverseViaCLI(e helpers.Environment) { ReqsForTier5: big.NewInt(*reqsForTier5), } - vrfPrimaryNodeSendingKeys := strings.Split(*vrfPrimaryNodeSendingKeysString, ",") + var vrfPrimaryNodeSendingKeys []string + if len(*vrfPrimaryNodeSendingKeysString) > 0 { + vrfPrimaryNodeSendingKeys = strings.Split(*vrfPrimaryNodeSendingKeysString, ",") + } nodesMap := make(map[string]model.Node) - fundingAmount, ok := new(big.Int).SetString(*nodeSendingKeyFundingAmount, 10) - if !ok { - panic(fmt.Sprintf("failed to parse node sending key funding amount '%s'", *nodeSendingKeyFundingAmount)) - } + fundingAmount := decimal.RequireFromString(*nodeSendingKeyFundingAmount).BigInt() nodesMap[model.VRFPrimaryNodeName] = model.Node{ SendingKeys: util.MapToSendingKeyArr(vrfPrimaryNodeSendingKeys), SendingKeyFundingAmount: fundingAmount, } - bhsContractAddress := common.HexToAddress(bhsContractAddressString) - batchBHSAddress := common.HexToAddress(batchBHSAddressString) - coordinatorAddress := common.HexToAddress(coordinatorAddressString) - batchCoordinatorAddress := common.HexToAddress(batchCoordinatorAddressString) + bhsContractAddress := common.HexToAddress(*bhsContractAddressString) + batchBHSAddress := common.HexToAddress(*batchBHSAddressString) + coordinatorAddress := common.HexToAddress(*coordinatorAddressString) + batchCoordinatorAddress := common.HexToAddress(*batchCoordinatorAddressString) contractAddresses := model.ContractAddresses{ - LinkAddress: linkAddress, - LinkEthAddress: linkEthAddress, + LinkAddress: *linkAddress, + LinkEthAddress: *linkEthAddress, BhsContractAddress: bhsContractAddress, BatchBHSAddress: batchBHSAddress, CoordinatorAddress: coordinatorAddress, @@ -115,22 +123,28 @@ func DeployUniverseViaCLI(e helpers.Environment) { } coordinatorConfig := CoordinatorConfigV2{ - MinConfs: minConfs, - MaxGasLimit: maxGasLimit, - StalenessSeconds: stalenessSeconds, - GasAfterPayment: gasAfterPayment, + MinConfs: *minConfs, + MaxGasLimit: *maxGasLimit, + StalenessSeconds: *stalenessSeconds, + GasAfterPayment: *gasAfterPayment, FallbackWeiPerUnitLink: fallbackWeiPerUnitLink, FeeConfig: feeConfig, } + vrfKeyRegistrationConfig := model.VRFKeyRegistrationConfig{ + VRFKeyUncompressedPubKey: *registerVRFKeyUncompressedPubKey, + RegisterAgainstAddress: *registerVRFKeyAgainstAddress, + } + VRFV2DeployUniverse( e, subscriptionBalanceJuels, - registerKeyUncompressedPubKey, + vrfKeyRegistrationConfig, contractAddresses, coordinatorConfig, *batchFulfillmentEnabled, nodesMap, + *deployVRFOwner, ) vrfPrimaryNode := nodesMap[model.VRFPrimaryNodeName] @@ -143,36 +157,40 @@ func DeployUniverseViaCLI(e helpers.Environment) { func VRFV2DeployUniverse( e helpers.Environment, subscriptionBalanceJuels *big.Int, - registerKeyUncompressedPubKey *string, + vrfKeyRegistrationConfig model.VRFKeyRegistrationConfig, contractAddresses model.ContractAddresses, coordinatorConfig CoordinatorConfigV2, batchFulfillmentEnabled bool, nodesMap map[string]model.Node, + deployVRFOwner bool, ) model.JobSpecs { - - // Put key in ECDSA format - if strings.HasPrefix(*registerKeyUncompressedPubKey, "0x") { - *registerKeyUncompressedPubKey = strings.Replace(*registerKeyUncompressedPubKey, "0x", "04", 1) + var compressedPkHex string + var keyHash common.Hash + if len(vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey) > 0 { + // Put key in ECDSA format + if strings.HasPrefix(vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey, "0x") { + vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey = strings.Replace(vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey, "0x", "04", 1) + } + + // Generate compressed public key and key hash + pubBytes, err := hex.DecodeString(vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey) + helpers.PanicErr(err) + pk, err := crypto.UnmarshalPubkey(pubBytes) + helpers.PanicErr(err) + var pkBytes []byte + if big.NewInt(0).Mod(pk.Y, big.NewInt(2)).Uint64() != 0 { + pkBytes = append(pk.X.Bytes(), 1) + } else { + pkBytes = append(pk.X.Bytes(), 0) + } + var newPK secp256k1.PublicKey + copy(newPK[:], pkBytes) + + compressedPkHex = hexutil.Encode(pkBytes) + keyHash, err = newPK.Hash() + helpers.PanicErr(err) } - // Generate compressed public key and key hash - pubBytes, err := hex.DecodeString(*registerKeyUncompressedPubKey) - helpers.PanicErr(err) - pk, err := crypto.UnmarshalPubkey(pubBytes) - helpers.PanicErr(err) - var pkBytes []byte - if big.NewInt(0).Mod(pk.Y, big.NewInt(2)).Uint64() != 0 { - pkBytes = append(pk.X.Bytes(), 1) - } else { - pkBytes = append(pk.X.Bytes(), 0) - } - var newPK secp256k1.PublicKey - copy(newPK[:], pkBytes) - - compressedPkHex := hexutil.Encode(pkBytes) - keyHash, err := newPK.Hash() - helpers.PanicErr(err) - if len(contractAddresses.LinkAddress) == 0 { fmt.Println("\nDeploying LINK Token...") contractAddresses.LinkAddress = helpers.DeployLinkToken(e).String() @@ -201,6 +219,15 @@ func VRFV2DeployUniverse( coordinator, err := vrf_coordinator_v2.NewVRFCoordinatorV2(contractAddresses.CoordinatorAddress, e.Ec) helpers.PanicErr(err) + var vrfOwnerAddress common.Address + if deployVRFOwner { + var tx *evmtypes.Transaction + fmt.Printf("\nDeploying VRF Owner for coordinator %v\n", contractAddresses.CoordinatorAddress) + vrfOwnerAddress, tx, _, err = vrf_owner.DeployVRFOwner(e.Owner, e.Ec, contractAddresses.CoordinatorAddress) + helpers.PanicErr(err) + helpers.ConfirmContractDeployed(context.Background(), e.Ec, tx, e.ChainID) + } + if contractAddresses.BatchCoordinatorAddress.String() == "0x0000000000000000000000000000000000000000" { fmt.Println("\nDeploying Batch Coordinator...") contractAddresses.BatchCoordinatorAddress = DeployBatchCoordinatorV2(e, contractAddresses.CoordinatorAddress) @@ -210,10 +237,10 @@ func VRFV2DeployUniverse( SetCoordinatorConfig( e, *coordinator, - uint16(*coordinatorConfig.MinConfs), - uint32(*coordinatorConfig.MaxGasLimit), - uint32(*coordinatorConfig.StalenessSeconds), - uint32(*coordinatorConfig.GasAfterPayment), + uint16(coordinatorConfig.MinConfs), + uint32(coordinatorConfig.MaxGasLimit), + uint32(coordinatorConfig.StalenessSeconds), + uint32(coordinatorConfig.GasAfterPayment), coordinatorConfig.FallbackWeiPerUnitLink, coordinatorConfig.FeeConfig, ) @@ -221,12 +248,12 @@ func VRFV2DeployUniverse( fmt.Println("\nConfig set, getting current config from deployed contract...") PrintCoordinatorConfig(coordinator) - if len(*registerKeyUncompressedPubKey) > 0 { + if len(vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey) > 0 { fmt.Println("\nRegistering proving key...") //NOTE - register proving key against EOA account, and not against Oracle's sending address in other to be able // easily withdraw funds from Coordinator contract back to EOA account - RegisterCoordinatorProvingKey(e, *coordinator, *registerKeyUncompressedPubKey, e.Owner.From.String()) + RegisterCoordinatorProvingKey(e, *coordinator, vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey, vrfKeyRegistrationConfig.RegisterAgainstAddress) fmt.Println("\nProving key registered, getting proving key hashes from deployed contract...") _, _, provingKeyHashes, configErr := coordinator.GetRequestConfig(nil) @@ -258,20 +285,54 @@ func VRFV2DeployUniverse( helpers.PanicErr(err) fmt.Printf("Subscription %+v\n", s) + if deployVRFOwner { + // VRF Owner + vrfOwner, err := vrf_owner.NewVRFOwner(vrfOwnerAddress, e.Ec) + helpers.PanicErr(err) + var authorizedSendersSlice []common.Address + for _, s := range nodesMap[model.VRFPrimaryNodeName].SendingKeys { + authorizedSendersSlice = append(authorizedSendersSlice, common.HexToAddress(s.Address)) + } + fmt.Printf("\nSetting authorised senders for VRF Owner: %v, Authorised senders %v\n", vrfOwnerAddress.String(), authorizedSendersSlice) + tx, err := vrfOwner.SetAuthorizedSenders(e.Owner, authorizedSendersSlice) + helpers.PanicErr(err) + helpers.ConfirmTXMined(context.Background(), e.Ec, tx, e.ChainID, "vrf owner set authorized senders") + fmt.Printf("\nTransfering ownership of coordinator: %v, VRF Owner %v\n", contractAddresses.CoordinatorAddress, vrfOwnerAddress.String()) + tx, err = coordinator.TransferOwnership(e.Owner, vrfOwnerAddress) + helpers.PanicErr(err) + helpers.ConfirmTXMined(context.Background(), e.Ec, tx, e.ChainID, "transfer ownership to", vrfOwnerAddress.String()) + fmt.Printf("\nAccepting ownership of coordinator: %v, VRF Owner %v\n", contractAddresses.CoordinatorAddress, vrfOwnerAddress.String()) + tx, err = vrfOwner.AcceptVRFOwnership(e.Owner) + helpers.PanicErr(err) + helpers.ConfirmTXMined(context.Background(), e.Ec, tx, e.ChainID, "vrf owner accepting vrf ownership") + } + formattedVrfPrimaryJobSpec := fmt.Sprintf( jobs.VRFV2JobFormatted, contractAddresses.CoordinatorAddress, //coordinatorAddress contractAddresses.BatchCoordinatorAddress, //batchCoordinatorAddress batchFulfillmentEnabled, //batchFulfillmentEnabled compressedPkHex, //publicKey - *coordinatorConfig.MinConfs, //minIncomingConfirmations + coordinatorConfig.MinConfs, //minIncomingConfirmations e.ChainID, //evmChainID strings.Join(util.MapToAddressArr(nodesMap[model.VRFPrimaryNodeName].SendingKeys), "\",\""), //fromAddresses contractAddresses.CoordinatorAddress, - nodesMap[model.VRFPrimaryNodeName].SendingKeys[0].Address, + func() string { + if keys := nodesMap[model.VRFPrimaryNodeName].SendingKeys; len(keys) > 0 { + return keys[0].Address + } else { + return common.HexToAddress("0x0").String() + } + }(), contractAddresses.CoordinatorAddress, contractAddresses.CoordinatorAddress, ) + if deployVRFOwner { + formattedVrfPrimaryJobSpec = strings.Replace(formattedVrfPrimaryJobSpec, + "minIncomingConfirmations", + fmt.Sprintf("vrfOwnerAddress = \"%s\"\nminIncomingConfirmations", vrfOwnerAddress.Hex()), + 1) + } formattedVrfBackupJobSpec := fmt.Sprintf( jobs.VRFV2JobFormatted, @@ -283,10 +344,22 @@ func VRFV2DeployUniverse( e.ChainID, //evmChainID strings.Join(util.MapToAddressArr(nodesMap[model.VRFBackupNodeName].SendingKeys), "\",\""), //fromAddresses contractAddresses.CoordinatorAddress, - nodesMap[model.VRFPrimaryNodeName].SendingKeys[0], + func() string { + if keys := nodesMap[model.VRFPrimaryNodeName].SendingKeys; len(keys) > 0 { + return keys[0].Address + } else { + return common.HexToAddress("0x0").String() + } + }(), contractAddresses.CoordinatorAddress, contractAddresses.CoordinatorAddress, ) + if deployVRFOwner { + formattedVrfBackupJobSpec = strings.Replace(formattedVrfBackupJobSpec, + "minIncomingConfirmations", + fmt.Sprintf("vrfOwnerAddress = \"%s\"\nminIncomingConfirmations", vrfOwnerAddress.Hex()), + 1) + } formattedBHSJobSpec := fmt.Sprintf( jobs.BHSJobFormatted, @@ -326,10 +399,11 @@ func VRFV2DeployUniverse( "\nVRF Coordinator Address:", contractAddresses.CoordinatorAddress, "\nBatch VRF Coordinator Address:", contractAddresses.BatchCoordinatorAddress, "\nVRF Consumer Address:", consumerAddress, + "\nVRF Owner Address:", vrfOwnerAddress, "\nVRF Subscription Id:", subID, "\nVRF Subscription Balance:", *subscriptionBalanceJuels, "\nPossible VRF Request command: ", - fmt.Sprintf("go run . eoa-load-test-request-with-metrics --consumer-address=%s --sub-id=%d --key-hash=%s --request-confirmations %d --requests 1 --runs 1 --cb-gas-limit 1_000_000", consumerAddress, subID, keyHash, *coordinatorConfig.MinConfs), + fmt.Sprintf("go run . eoa-load-test-request-with-metrics --consumer-address=%s --sub-id=%d --key-hash=%s --request-confirmations %d --requests 1 --runs 1 --cb-gas-limit 1_000_000", consumerAddress, subID, keyHash, coordinatorConfig.MinConfs), "\nRetrieve Request Status: ", fmt.Sprintf("go run . eoa-load-test-read-metrics --consumer-address=%s", consumerAddress), "\nA node can now be configured to run a VRF job with the below job spec :\n", diff --git a/core/scripts/vrfv2/testnet/v2scripts/util.go b/core/scripts/vrfv2/testnet/v2scripts/util.go index 0e348e9c01c..94e381378bb 100644 --- a/core/scripts/vrfv2/testnet/v2scripts/util.go +++ b/core/scripts/vrfv2/testnet/v2scripts/util.go @@ -89,7 +89,6 @@ func EoaFundSubscription(e helpers.Environment, coordinator vrf_coordinator_v2.V fmt.Println("Initial account balance:", bal, e.Owner.From.String(), "Funding amount:", amount.String()) b, err := utils.ABIEncode(`[{"type":"uint64"}]`, subID) helpers.PanicErr(err) - e.Owner.GasLimit = 500000 tx, err := linkToken.TransferAndCall(e.Owner, coordinator.Address(), amount, b) helpers.PanicErr(err) helpers.ConfirmTXMined(context.Background(), e.Ec, tx, e.ChainID, fmt.Sprintf("sub ID: %d", subID)) diff --git a/core/scripts/vrfv2plus/testnet/README.md b/core/scripts/vrfv2plus/testnet/README.md index b95ec99d5fb..6402569c560 100644 --- a/core/scripts/vrfv2plus/testnet/README.md +++ b/core/scripts/vrfv2plus/testnet/README.md @@ -58,7 +58,16 @@ cd /core/scripts/vrfv2/testnet - Not specifying `--link-eth-feed` would make the super script deploy a new LINK-ETH feed contract and use it for funding VRF V2+ subscription ```shell -go run . deploy-universe --link-address=$LINK --link-eth-feed=$LINK_ETH_FEED --subscription-balance= --uncompressed-pub-key=$PUB_KEY --oracle-address=$ORACLE_ADDRESS +go run . deploy-universe \ +--link-address=$LINK \ +--link-eth-feed=$LINK_ETH_FEED \ +--subscription-balance=5000000000000000000 \ #5 LINK +--subscription-balance-native=1000000000000000000 \ #1 ETH +--uncompressed-pub-key= \ +--vrf-primary-node-sending-keys="" \ #used to fund the keys and for sample VRF Job Spec generation +--sending-key-funding-amount 100000000000000000 \ #0.1 ETH, fund addresses specified in vrf-primary-node-sending-keys +--batch-fulfillment-enabled false \ #only used for sample VRF Job Spec generation +--register-vrf-key-against-address="" # from this address you can perform `coordinator.oracleWithdraw` to withdraw earned funds from rand request fulfilments ``` ## Deploying the Consumer Contract diff --git a/core/scripts/vrfv2plus/testnet/main.go b/core/scripts/vrfv2plus/testnet/main.go index 0d1bf9a9481..6b1ef585a5a 100644 --- a/core/scripts/vrfv2plus/testnet/main.go +++ b/core/scripts/vrfv2plus/testnet/main.go @@ -6,12 +6,13 @@ import ( "encoding/hex" "flag" "fmt" - "github.com/smartcontractkit/chainlink/core/scripts/vrfv2plus/testnet/v2plusscripts" "log" "math/big" "os" "strings" + "github.com/smartcontractkit/chainlink/core/scripts/vrfv2plus/testnet/v2plusscripts" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/chain_specific_util_helper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2_5" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2plus_interface" @@ -25,10 +26,10 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/shopspring/decimal" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_blockhash_store" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_vrf_coordinator_v2plus" diff --git a/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go b/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go index f805e7b74f0..50584d885a2 100644 --- a/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go +++ b/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go @@ -6,15 +6,16 @@ import ( "encoding/hex" "flag" "fmt" + "math/big" + "os" + "strings" + "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/constants" "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/jobs" "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/model" "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/util" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2plus_interface" - "math/big" - "os" - "strings" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -38,10 +39,10 @@ import ( var coordinatorV2PlusABI = evmtypes.MustGetABI(vrf_coordinator_v2plus_interface.IVRFCoordinatorV2PlusInternalABI) type CoordinatorConfigV2Plus struct { - MinConfs *int - MaxGasLimit *int64 - StalenessSeconds *int64 - GasAfterPayment *int64 + MinConfs int + MaxGasLimit int64 + StalenessSeconds int64 + GasAfterPayment int64 FallbackWeiPerUnitLink *big.Int FeeConfig vrf_coordinator_v2_5.VRFCoordinatorV25FeeConfig } @@ -467,12 +468,12 @@ func DeployUniverseViaCLI(e helpers.Environment) { deployCmd := flag.NewFlagSet("deploy-universe", flag.ExitOnError) // required flags - linkAddress := *deployCmd.String("link-address", "", "address of link token") - linkEthAddress := *deployCmd.String("link-eth-feed", "", "address of link eth feed") - bhsContractAddressString := *deployCmd.String("bhs-address", "", "address of BHS contract") - batchBHSAddressString := *deployCmd.String("batch-bhs-address", "", "address of Batch BHS contract") - coordinatorAddressString := *deployCmd.String("coordinator-address", "", "address of VRF Coordinator contract") - batchCoordinatorAddressString := *deployCmd.String("batch-coordinator-address", "", "address Batch VRF Coordinator contract") + linkAddress := deployCmd.String("link-address", "", "address of link token") + linkEthAddress := deployCmd.String("link-eth-feed", "", "address of link eth feed") + bhsContractAddressString := deployCmd.String("bhs-address", "", "address of BHS contract") + batchBHSAddressString := deployCmd.String("batch-bhs-address", "", "address of Batch BHS contract") + coordinatorAddressString := deployCmd.String("coordinator-address", "", "address of VRF Coordinator contract") + batchCoordinatorAddressString := deployCmd.String("batch-coordinator-address", "", "address Batch VRF Coordinator contract") subscriptionBalanceJuelsString := deployCmd.String("subscription-balance", "1e19", "amount to fund subscription with Link token (Juels)") subscriptionBalanceNativeWeiString := deployCmd.String("subscription-balance-native", "1e18", "amount to fund subscription with native token (Wei)") @@ -480,7 +481,10 @@ func DeployUniverseViaCLI(e helpers.Environment) { // optional flags fallbackWeiPerUnitLinkString := deployCmd.String("fallback-wei-per-unit-link", "6e16", "fallback wei/link ratio") - registerKeyUncompressedPubKey := deployCmd.String("uncompressed-pub-key", "", "uncompressed public key") + registerVRFKeyUncompressedPubKey := deployCmd.String("uncompressed-pub-key", "", "uncompressed public key") + registerVRFKeyAgainstAddress := deployCmd.String("register-vrf-key-against-address", "", "VRF Key registration against address - "+ + "from this address you can perform `coordinator.oracleWithdraw` to withdraw earned funds from rand request fulfilments") + vrfPrimaryNodeSendingKeysString := deployCmd.String("vrf-primary-node-sending-keys", "", "VRF Primary Node sending keys") minConfs := deployCmd.Int("min-confs", constants.MinConfs, "min confs") nodeSendingKeyFundingAmount := deployCmd.String("sending-key-funding-amount", constants.NodeSendingKeyFundingAmount, "CL node sending key funding amount") @@ -504,23 +508,30 @@ func DeployUniverseViaCLI(e helpers.Environment) { FulfillmentFlatFeeNativePPM: uint32(*flatFeeEthPPM), } - vrfPrimaryNodeSendingKeys := strings.Split(*vrfPrimaryNodeSendingKeysString, ",") + var vrfPrimaryNodeSendingKeys []string + if len(*vrfPrimaryNodeSendingKeysString) > 0 { + vrfPrimaryNodeSendingKeys = strings.Split(*vrfPrimaryNodeSendingKeysString, ",") + } nodesMap := make(map[string]model.Node) + fundingAmount, ok := new(big.Int).SetString(*nodeSendingKeyFundingAmount, 10) + if !ok { + panic(fmt.Sprintf("failed to parse node sending key funding amount '%s'", *nodeSendingKeyFundingAmount)) + } nodesMap[model.VRFPrimaryNodeName] = model.Node{ SendingKeys: util.MapToSendingKeyArr(vrfPrimaryNodeSendingKeys), SendingKeyFundingAmount: fundingAmount, } - bhsContractAddress := common.HexToAddress(bhsContractAddressString) - batchBHSAddress := common.HexToAddress(batchBHSAddressString) - coordinatorAddress := common.HexToAddress(coordinatorAddressString) - batchCoordinatorAddress := common.HexToAddress(batchCoordinatorAddressString) + bhsContractAddress := common.HexToAddress(*bhsContractAddressString) + batchBHSAddress := common.HexToAddress(*batchBHSAddressString) + coordinatorAddress := common.HexToAddress(*coordinatorAddressString) + batchCoordinatorAddress := common.HexToAddress(*batchCoordinatorAddressString) contractAddresses := model.ContractAddresses{ - LinkAddress: linkAddress, - LinkEthAddress: linkEthAddress, + LinkAddress: *linkAddress, + LinkEthAddress: *linkEthAddress, BhsContractAddress: bhsContractAddress, BatchBHSAddress: batchBHSAddress, CoordinatorAddress: coordinatorAddress, @@ -528,19 +539,24 @@ func DeployUniverseViaCLI(e helpers.Environment) { } coordinatorConfig := CoordinatorConfigV2Plus{ - MinConfs: minConfs, - MaxGasLimit: maxGasLimit, - StalenessSeconds: stalenessSeconds, - GasAfterPayment: gasAfterPayment, + MinConfs: *minConfs, + MaxGasLimit: *maxGasLimit, + StalenessSeconds: *stalenessSeconds, + GasAfterPayment: *gasAfterPayment, FallbackWeiPerUnitLink: fallbackWeiPerUnitLink, FeeConfig: feeConfig, } + vrfKeyRegistrationConfig := model.VRFKeyRegistrationConfig{ + VRFKeyUncompressedPubKey: *registerVRFKeyUncompressedPubKey, + RegisterAgainstAddress: *registerVRFKeyAgainstAddress, + } + VRFV2PlusDeployUniverse( e, subscriptionBalanceJuels, subscriptionBalanceNativeWei, - registerKeyUncompressedPubKey, + vrfKeyRegistrationConfig, contractAddresses, coordinatorConfig, *batchFulfillmentEnabled, @@ -557,34 +573,38 @@ func DeployUniverseViaCLI(e helpers.Environment) { func VRFV2PlusDeployUniverse(e helpers.Environment, subscriptionBalanceJuels *big.Int, subscriptionBalanceNativeWei *big.Int, - registerKeyUncompressedPubKey *string, + vrfKeyRegistrationConfig model.VRFKeyRegistrationConfig, contractAddresses model.ContractAddresses, coordinatorConfig CoordinatorConfigV2Plus, batchFulfillmentEnabled bool, nodesMap map[string]model.Node, ) model.JobSpecs { - // Put key in ECDSA format - if strings.HasPrefix(*registerKeyUncompressedPubKey, "0x") { - *registerKeyUncompressedPubKey = strings.Replace(*registerKeyUncompressedPubKey, "0x", "04", 1) - } + var compressedPkHex string + var keyHash common.Hash + if len(vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey) > 0 { + // Put key in ECDSA format + if strings.HasPrefix(vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey, "0x") { + vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey = strings.Replace(vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey, "0x", "04", 1) + } - // Generate compressed public key and key hash - pubBytes, err := hex.DecodeString(*registerKeyUncompressedPubKey) - helpers.PanicErr(err) - pk, err := crypto.UnmarshalPubkey(pubBytes) - helpers.PanicErr(err) - var pkBytes []byte - if big.NewInt(0).Mod(pk.Y, big.NewInt(2)).Uint64() != 0 { - pkBytes = append(pk.X.Bytes(), 1) - } else { - pkBytes = append(pk.X.Bytes(), 0) - } - var newPK secp256k1.PublicKey - copy(newPK[:], pkBytes) + // Generate compressed public key and key hash + pubBytes, err := hex.DecodeString(vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey) + helpers.PanicErr(err) + pk, err := crypto.UnmarshalPubkey(pubBytes) + helpers.PanicErr(err) + var pkBytes []byte + if big.NewInt(0).Mod(pk.Y, big.NewInt(2)).Uint64() != 0 { + pkBytes = append(pk.X.Bytes(), 1) + } else { + pkBytes = append(pk.X.Bytes(), 0) + } + var newPK secp256k1.PublicKey + copy(newPK[:], pkBytes) - compressedPkHex := hexutil.Encode(pkBytes) - keyHash, err := newPK.Hash() - helpers.PanicErr(err) + compressedPkHex = hexutil.Encode(pkBytes) + keyHash, err = newPK.Hash() + helpers.PanicErr(err) + } if len(contractAddresses.LinkAddress) == 0 { fmt.Println("\nDeploying LINK Token...") @@ -623,10 +643,10 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, SetCoordinatorConfig( e, *coordinator, - uint16(*coordinatorConfig.MinConfs), - uint32(*coordinatorConfig.MaxGasLimit), - uint32(*coordinatorConfig.StalenessSeconds), - uint32(*coordinatorConfig.GasAfterPayment), + uint16(coordinatorConfig.MinConfs), + uint32(coordinatorConfig.MaxGasLimit), + uint32(coordinatorConfig.StalenessSeconds), + uint32(coordinatorConfig.GasAfterPayment), coordinatorConfig.FallbackWeiPerUnitLink, coordinatorConfig.FeeConfig, ) @@ -634,12 +654,12 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, fmt.Println("\nConfig set, getting current config from deployed contract...") PrintCoordinatorConfig(coordinator) - if len(*registerKeyUncompressedPubKey) > 0 { + if len(vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey) > 0 { fmt.Println("\nRegistering proving key...") //NOTE - register proving key against EOA account, and not against Oracle's sending address in other to be able // easily withdraw funds from Coordinator contract back to EOA account - RegisterCoordinatorProvingKey(e, *coordinator, *registerKeyUncompressedPubKey, e.Owner.From.String()) + RegisterCoordinatorProvingKey(e, *coordinator, vrfKeyRegistrationConfig.VRFKeyUncompressedPubKey, vrfKeyRegistrationConfig.RegisterAgainstAddress) fmt.Println("\nProving key registered, getting proving key hashes from deployed contract...") _, _, provingKeyHashes, configErr := coordinator.GetRequestConfig(nil) @@ -685,11 +705,17 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, contractAddresses.BatchCoordinatorAddress, //batchCoordinatorAddress batchFulfillmentEnabled, //batchFulfillmentEnabled compressedPkHex, //publicKey - *coordinatorConfig.MinConfs, //minIncomingConfirmations + coordinatorConfig.MinConfs, //minIncomingConfirmations e.ChainID, //evmChainID strings.Join(util.MapToAddressArr(nodesMap[model.VRFPrimaryNodeName].SendingKeys), "\",\""), //fromAddresses contractAddresses.CoordinatorAddress, - nodesMap[model.VRFPrimaryNodeName].SendingKeys[0].Address, + func() string { + if keys := nodesMap[model.VRFPrimaryNodeName].SendingKeys; len(keys) > 0 { + return keys[0].Address + } else { + return common.HexToAddress("0x0").String() + } + }(), contractAddresses.CoordinatorAddress, contractAddresses.CoordinatorAddress, ) @@ -704,7 +730,13 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, e.ChainID, //evmChainID strings.Join(util.MapToAddressArr(nodesMap[model.VRFBackupNodeName].SendingKeys), "\",\""), //fromAddresses contractAddresses.CoordinatorAddress, - nodesMap[model.VRFPrimaryNodeName].SendingKeys[0], + func() string { + if keys := nodesMap[model.VRFPrimaryNodeName].SendingKeys; len(keys) > 0 { + return keys[0].Address + } else { + return common.HexToAddress("0x0").String() + } + }(), contractAddresses.CoordinatorAddress, contractAddresses.CoordinatorAddress, ) @@ -751,7 +783,7 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, "\nVRF Subscription LINK Balance:", *subscriptionBalanceJuels, "\nVRF Subscription Native Balance:", *subscriptionBalanceNativeWei, "\nPossible VRF Request command: ", - fmt.Sprintf("go run . eoa-load-test-request-with-metrics --consumer-address=%s --sub-id=%d --key-hash=%s --request-confirmations %d --requests 1 --runs 1 --cb-gas-limit 1_000_000", consumerAddress, subID, keyHash, *coordinatorConfig.MinConfs), + fmt.Sprintf("go run . eoa-load-test-request-with-metrics --consumer-address=%s --sub-id=%d --key-hash=%s --request-confirmations %d --requests 1 --runs 1 --cb-gas-limit 1_000_000", consumerAddress, subID, keyHash, coordinatorConfig.MinConfs), "\nRetrieve Request Status: ", fmt.Sprintf("go run . eoa-load-test-read-metrics --consumer-address=%s", consumerAddress), "\nA node can now be configured to run a VRF job with the below job spec :\n", diff --git a/core/services/blockhashstore/bhs_test.go b/core/services/blockhashstore/bhs_test.go index ed32b3ab49f..44205ec7b86 100644 --- a/core/services/blockhashstore/bhs_test.go +++ b/core/services/blockhashstore/bhs_test.go @@ -1,7 +1,6 @@ package blockhashstore_test import ( - "context" "testing" "github.com/ethereum/go-ethereum/common" @@ -12,7 +11,8 @@ import ( txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/blockhash_store" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -66,9 +66,11 @@ func TestStoreRotatesFromAddresses(t *testing.T) { return tx.FromAddress.String() == k2.Address.String() })).Once().Return(txmgr.Tx{}, nil) + ctx := testutils.Context(t) + // store 2 blocks - err = bhs.Store(context.Background(), 1) + err = bhs.Store(ctx, 1) require.NoError(t, err) - err = bhs.Store(context.Background(), 2) + err = bhs.Store(ctx, 2) require.NoError(t, err) } diff --git a/core/services/blockhashstore/coordinators.go b/core/services/blockhashstore/coordinators.go index ff5aff1f5e5..4cb58bab6fd 100644 --- a/core/services/blockhashstore/coordinators.go +++ b/core/services/blockhashstore/coordinators.go @@ -128,7 +128,7 @@ func (v *V1Coordinator) Fulfillments(ctx context.Context, fromBlock uint64) ([]E logs, err := v.lp.LogsWithSigs( int64(fromBlock), - int64(toBlock), + toBlock.BlockNumber, []common.Hash{ v1.VRFCoordinatorRandomnessRequestFulfilled{}.Topic(), }, @@ -219,7 +219,7 @@ func (v *V2Coordinator) Fulfillments(ctx context.Context, fromBlock uint64) ([]E logs, err := v.lp.LogsWithSigs( int64(fromBlock), - int64(toBlock), + toBlock.BlockNumber, []common.Hash{ v2.VRFCoordinatorV2RandomWordsFulfilled{}.Topic(), }, @@ -310,7 +310,7 @@ func (v *V2PlusCoordinator) Fulfillments(ctx context.Context, fromBlock uint64) logs, err := v.lp.LogsWithSigs( int64(fromBlock), - int64(toBlock), + toBlock.BlockNumber, []common.Hash{ v2plus.IVRFCoordinatorV2PlusInternalRandomWordsFulfilled{}.Topic(), }, diff --git a/core/services/blockhashstore/delegate.go b/core/services/blockhashstore/delegate.go index 90819f27a1d..d6c27acd0b5 100644 --- a/core/services/blockhashstore/delegate.go +++ b/core/services/blockhashstore/delegate.go @@ -8,7 +8,8 @@ import ( "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/blockhash_store" v1 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/trusted_blockhash_store" @@ -27,14 +28,14 @@ var _ job.ServiceCtx = &service{} // Delegate creates BlockhashStore feeder jobs. type Delegate struct { logger logger.Logger - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer ks keystore.Eth } // NewDelegate creates a new Delegate. func NewDelegate( logger logger.Logger, - legacyChains evm.LegacyChainContainer, + legacyChains legacyevm.LegacyChainContainer, ks keystore.Eth, ) *Delegate { return &Delegate{ @@ -172,7 +173,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { if err != nil { return 0, errors.Wrap(err, "getting chain head") } - return uint64(head), nil + return uint64(head.BlockNumber), nil }) return []job.ServiceCtx{&service{ @@ -197,7 +198,7 @@ func (d *Delegate) OnDeleteJob(spec job.Job, q pg.Queryer) error { return nil } // service is a job.Service that runs the BHS feeder every pollPeriod. type service struct { - utils.StartStopOnce + services.StateMachine feeder *Feeder wg sync.WaitGroup pollPeriod time.Duration diff --git a/core/services/blockhashstore/delegate_test.go b/core/services/blockhashstore/delegate_test.go index 582492105e0..0096ac5ca9e 100644 --- a/core/services/blockhashstore/delegate_test.go +++ b/core/services/blockhashstore/delegate_test.go @@ -10,12 +10,13 @@ import ( "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" mocklp "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -40,7 +41,7 @@ func TestDelegate_JobType(t *testing.T) { type testData struct { ethClient *mocks.Client ethKeyStore keystore.Eth - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer sendingKey ethkey.KeyV2 logs *observer.ObservedLogs } @@ -58,7 +59,7 @@ func createTestDelegate(t *testing.T) (*blockhashstore.Delegate, *testData) { sendingKey, _ := cltest.MustInsertRandomKey(t, kst) lp := &mocklp.LogPoller{} lp.On("RegisterFilter", mock.Anything).Return(nil) - lp.On("LatestBlock", mock.Anything, mock.Anything).Return(int64(0), nil) + lp.On("LatestBlock", mock.Anything, mock.Anything).Return(logpoller.LogPollerBlock{}, nil) relayExtenders := evmtest.NewChainRelayExtenders( t, diff --git a/core/services/blockhashstore/feeder_test.go b/core/services/blockhashstore/feeder_test.go index 3145a9fd76d..8d9ed48c4bf 100644 --- a/core/services/blockhashstore/feeder_test.go +++ b/core/services/blockhashstore/feeder_test.go @@ -445,7 +445,7 @@ func (test testCase) testFeederWithLogPollerVRFv1(t *testing.T) { // Mock log poller. lp.On("LatestBlock", mock.Anything). - Return(latest, nil) + Return(logpoller.LogPollerBlock{BlockNumber: latest}, nil) lp.On( "LogsWithSigs", fromBlock, @@ -543,7 +543,7 @@ func (test testCase) testFeederWithLogPollerVRFv2(t *testing.T) { // Mock log poller. lp.On("LatestBlock", mock.Anything). - Return(latest, nil) + Return(logpoller.LogPollerBlock{BlockNumber: latest}, nil) lp.On( "LogsWithSigs", fromBlock, @@ -641,7 +641,7 @@ func (test testCase) testFeederWithLogPollerVRFv2Plus(t *testing.T) { // Mock log poller. lp.On("LatestBlock", mock.Anything). - Return(latest, nil) + Return(logpoller.LogPollerBlock{BlockNumber: latest}, nil) lp.On( "LogsWithSigs", fromBlock, diff --git a/core/services/blockheaderfeeder/delegate.go b/core/services/blockheaderfeeder/delegate.go index 02ab34821f6..53f514cee27 100644 --- a/core/services/blockheaderfeeder/delegate.go +++ b/core/services/blockheaderfeeder/delegate.go @@ -8,7 +8,8 @@ import ( "github.com/pkg/errors" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_blockhash_store" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/blockhash_store" v1 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_coordinator_interface" @@ -26,13 +27,13 @@ var _ job.ServiceCtx = &service{} type Delegate struct { logger logger.Logger - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer ks keystore.Eth } func NewDelegate( logger logger.Logger, - legacyChains evm.LegacyChainContainer, + legacyChains legacyevm.LegacyChainContainer, ks keystore.Eth, ) *Delegate { return &Delegate{ @@ -211,7 +212,7 @@ func (d *Delegate) OnDeleteJob(spec job.Job, q pg.Queryer) error { return nil } // service is a job.Service that runs the BHS feeder every pollPeriod. type service struct { - utils.StartStopOnce + services.StateMachine feeder *BlockHeaderFeeder done chan struct{} pollPeriod time.Duration diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 9829036e13f..5c204d693e9 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -16,10 +16,11 @@ import ( "go.uber.org/multierr" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - relayservices "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + commonservices "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/static" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/build" @@ -48,10 +49,13 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/promreporter" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" "github.com/smartcontractkit/chainlink/v2/core/services/vrf" "github.com/smartcontractkit/chainlink/v2/core/services/webhook" "github.com/smartcontractkit/chainlink/v2/core/sessions" + "github.com/smartcontractkit/chainlink/v2/core/sessions/ldapauth" + "github.com/smartcontractkit/chainlink/v2/core/sessions/localauth" "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/v2/plugins" ) @@ -82,7 +86,8 @@ type Application interface { EVMORM() evmtypes.Configs PipelineORM() pipeline.ORM BridgeORM() bridges.ORM - SessionORM() sessions.ORM + BasicAdminUsersORM() sessions.BasicAdminUsersORM + AuthenticationProvider() sessions.AuthenticationProvider TxmStorageService() txmgr.EvmTxStore AddJobV2(ctx context.Context, job *job.Job) error DeleteJob(ctx context.Context, jobID int32) error @@ -115,7 +120,8 @@ type ChainlinkApplication struct { pipelineORM pipeline.ORM pipelineRunner pipeline.Runner bridgeORM bridges.ORM - sessionORM sessions.ORM + localAdminUsersORM sessions.BasicAdminUsersORM + authenticationProvider sessions.AuthenticationProvider txmStorageService txmgr.EvmTxStore FeedsService feeds.Service webhookJobRunner webhook.JobRunner @@ -156,6 +162,7 @@ type ApplicationOpts struct { SecretGenerator SecretGenerator LoopRegistry *plugins.LoopRegistry GRPCOpts loop.GRPCOpts + MercuryPool wsrpc.Pool } // NewApplication initializes a new store if one is not already @@ -232,10 +239,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") } - srvcs = append(srvcs, eventBroadcaster, mailMon) - srvcs = append(srvcs, relayerChainInterops.Services()...) - promReporter := promreporter.NewPromReporter(db.DB, globalLogger) - srvcs = append(srvcs, promReporter) + // pool must be started before all relayers and stopped after them + srvcs = append(srvcs, opts.MercuryPool) // EVM chains are used all over the place. This will need to change for fully EVM extraction // TODO: BCF-2510, BCF-2511 @@ -245,13 +250,44 @@ func NewApplication(opts ApplicationOpts) (Application, error) { return nil, fmt.Errorf("no evm chains found") } + srvcs = append(srvcs, eventBroadcaster, mailMon) + srvcs = append(srvcs, relayerChainInterops.Services()...) + promReporter := promreporter.NewPromReporter(db.DB, legacyEVMChains, globalLogger) + srvcs = append(srvcs, promReporter) + + // Initialize Local Users ORM and Authentication Provider specified in config + // BasicAdminUsersORM is initialized and required regardless of separate Authentication Provider + localAdminUsersORM := localauth.NewORM(db, cfg.WebServer().SessionTimeout().Duration(), globalLogger, cfg.Database(), auditLogger) + + // Initialize Sessions ORM based on environment configured authenticator + // localDB auth or remote LDAP auth + authMethod := cfg.WebServer().AuthenticationMethod() + var authenticationProvider sessions.AuthenticationProvider + var sessionReaper utils.SleeperTask + + switch sessions.AuthenticationProviderName(authMethod) { + case sessions.LDAPAuth: + var err error + authenticationProvider, err = ldapauth.NewLDAPAuthenticator( + db, cfg.Database(), cfg.WebServer().LDAP(), cfg.Insecure().DevWebServer(), globalLogger, auditLogger, + ) + if err != nil { + return nil, errors.Wrap(err, "NewApplication: failed to initialize LDAP Authentication module") + } + sessionReaper = ldapauth.NewLDAPServerStateSync(db, cfg.Database(), cfg.WebServer().LDAP(), globalLogger) + case sessions.LocalAuth: + authenticationProvider = localauth.NewORM(db, cfg.WebServer().SessionTimeout().Duration(), globalLogger, cfg.Database(), auditLogger) + sessionReaper = localauth.NewSessionReaper(db.DB, cfg.WebServer(), globalLogger) + default: + return nil, errors.Errorf("NewApplication: Unexpected 'AuthenticationMethod': %s supported values: %s, %s", authMethod, sessions.LocalAuth, sessions.LDAPAuth) + } + var ( pipelineORM = pipeline.NewORM(db, globalLogger, cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) bridgeORM = bridges.NewORM(db, globalLogger, cfg.Database()) - sessionORM = sessions.NewORM(db, cfg.WebServer().SessionTimeout().Duration(), globalLogger, cfg.Database(), auditLogger) mercuryORM = mercury.NewORM(db, globalLogger, cfg.Database()) pipelineRunner = pipeline.NewRunner(pipelineORM, bridgeORM, cfg.JobPipeline(), cfg.WebServer(), legacyEVMChains, keyStore.Eth(), keyStore.VRF(), globalLogger, restrictedHTTPClient, unrestrictedHTTPClient) - jobORM = job.NewORM(db, legacyEVMChains, pipelineORM, bridgeORM, keyStore, globalLogger, cfg.Database()) + jobORM = job.NewORM(db, pipelineORM, bridgeORM, keyStore, globalLogger, cfg.Database()) txmORM = txmgr.NewTxStore(db, globalLogger, cfg.Database()) ) @@ -389,7 +425,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { globalLogger.Debug("Off-chain reporting v2 disabled") } - healthChecker := relayservices.NewChecker() + healthChecker := commonservices.NewChecker(static.Version, static.Sha) var lbs []utils.DependentAwaiter for _, c := range legacyEVMChains.Slice() { @@ -429,6 +465,9 @@ func NewApplication(opts ApplicationOpts) (Application, error) { } for _, s := range srvcs { + if s == nil { + panic("service unexpectedly nil") + } if err := healthChecker.Register(s); err != nil { return nil, err } @@ -442,13 +481,14 @@ func NewApplication(opts ApplicationOpts) (Application, error) { pipelineRunner: pipelineRunner, pipelineORM: pipelineORM, bridgeORM: bridgeORM, - sessionORM: sessionORM, + localAdminUsersORM: localAdminUsersORM, + authenticationProvider: authenticationProvider, txmStorageService: txmORM, FeedsService: feedsService, Config: cfg, webhookJobRunner: webhookJobRunner, KeyStore: keyStore, - SessionReaper: sessions.NewSessionReaper(db.DB, cfg.WebServer(), globalLogger), + SessionReaper: sessionReaper, ExternalInitiatorManager: externalInitiatorManager, HealthChecker: healthChecker, Nurse: nurse, @@ -614,8 +654,12 @@ func (app *ChainlinkApplication) BridgeORM() bridges.ORM { return app.bridgeORM } -func (app *ChainlinkApplication) SessionORM() sessions.ORM { - return app.sessionORM +func (app *ChainlinkApplication) BasicAdminUsersORM() sessions.BasicAdminUsersORM { + return app.localAdminUsersORM +} + +func (app *ChainlinkApplication) AuthenticationProvider() sessions.AuthenticationProvider { + return app.authenticationProvider } // TODO BCF-2516 remove this all together remove EVM specifics diff --git a/core/services/chainlink/config.go b/core/services/chainlink/config.go index 26e2d539bac..5d8b1019e8c 100644 --- a/core/services/chainlink/config.go +++ b/core/services/chainlink/config.go @@ -8,10 +8,10 @@ import ( gotoml "github.com/pelletier/go-toml/v2" + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana" stkcfg "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/config/docs" "github.com/smartcontractkit/chainlink/v2/core/config/env" @@ -36,7 +36,7 @@ type Config struct { EVM evmcfg.EVMConfigs `toml:",omitempty"` - Cosmos cosmos.CosmosConfigs `toml:",omitempty"` + Cosmos coscfg.TOMLConfigs `toml:",omitempty"` Solana solana.TOMLConfigs `toml:",omitempty"` @@ -52,6 +52,27 @@ func (c *Config) TOMLString() (string, error) { return string(b), nil } +// warnings aggregates warnings from valueWarnings and deprecationWarnings +func (c *Config) warnings() (err error) { + deprecationErr := c.deprecationWarnings() + warningErr := c.valueWarnings() + err = multierr.Append(deprecationErr, warningErr) + _, list := utils.MultiErrorList(err) + return list +} + +// valueWarnings returns an error if the Config contains values that hint at misconfiguration before defaults are applied. +func (c *Config) valueWarnings() (err error) { + if c.Tracing.Enabled != nil && *c.Tracing.Enabled { + if c.Tracing.Mode != nil && *c.Tracing.Mode == "unencrypted" { + if c.Tracing.TLSCertPath != nil { + err = multierr.Append(err, config.ErrInvalid{Name: "Tracing.TLSCertPath", Value: *c.Tracing.TLSCertPath, Msg: "must be empty when Tracing.Mode is 'unencrypted'"}) + } + } + } + return +} + // deprecationWarnings returns an error if the Config contains deprecated fields. // This is typically used before defaults have been applied, with input from the user. func (c *Config) deprecationWarnings() (err error) { @@ -119,7 +140,7 @@ func (c *Config) setDefaults() { for i := range c.Cosmos { if c.Cosmos[i] == nil { - c.Cosmos[i] = new(cosmos.CosmosConfig) + c.Cosmos[i] = new(coscfg.TOMLConfig) } c.Cosmos[i].Chain.SetDefaults() } @@ -168,28 +189,32 @@ type Secrets struct { } func (s *Secrets) SetFrom(f *Secrets) (err error) { - if err1 := s.Database.SetFrom(&f.Database); err1 != nil { - err = multierr.Append(err, config.NamedMultiErrorList(err1, "Database")) + if err2 := s.Database.SetFrom(&f.Database); err2 != nil { + err = multierr.Append(err, config.NamedMultiErrorList(err2, "Database")) } if err2 := s.Password.SetFrom(&f.Password); err2 != nil { err = multierr.Append(err, config.NamedMultiErrorList(err2, "Password")) } - if err3 := s.Pyroscope.SetFrom(&f.Pyroscope); err3 != nil { - err = multierr.Append(err, config.NamedMultiErrorList(err3, "Pyroscope")) + if err2 := s.WebServer.SetFrom(&f.WebServer); err2 != nil { + err = multierr.Append(err, config.NamedMultiErrorList(err2, "WebServer")) + } + + if err2 := s.Pyroscope.SetFrom(&f.Pyroscope); err2 != nil { + err = multierr.Append(err, config.NamedMultiErrorList(err2, "Pyroscope")) } - if err4 := s.Prometheus.SetFrom(&f.Prometheus); err4 != nil { - err = multierr.Append(err, config.NamedMultiErrorList(err4, "Prometheus")) + if err2 := s.Prometheus.SetFrom(&f.Prometheus); err2 != nil { + err = multierr.Append(err, config.NamedMultiErrorList(err2, "Prometheus")) } - if err5 := s.Mercury.SetFrom(&f.Mercury); err5 != nil { - err = multierr.Append(err, config.NamedMultiErrorList(err5, "Mercury")) + if err2 := s.Mercury.SetFrom(&f.Mercury); err2 != nil { + err = multierr.Append(err, config.NamedMultiErrorList(err2, "Mercury")) } - if err6 := s.Threshold.SetFrom(&f.Threshold); err6 != nil { - err = multierr.Append(err, config.NamedMultiErrorList(err6, "Threshold")) + if err2 := s.Threshold.SetFrom(&f.Threshold); err2 != nil { + err = multierr.Append(err, config.NamedMultiErrorList(err2, "Threshold")) } _, err = utils.MultiErrorList(err) diff --git a/core/services/chainlink/config_general.go b/core/services/chainlink/config_general.go index 6243146e91e..c7d9cc6ce5d 100644 --- a/core/services/chainlink/config_general.go +++ b/core/services/chainlink/config_general.go @@ -15,10 +15,10 @@ import ( ocrnetworking "github.com/smartcontractkit/libocr/networking" + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana" starknet "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/config" coreconfig "github.com/smartcontractkit/chainlink/v2/core/config" @@ -137,7 +137,7 @@ func (o GeneralConfigOpts) New() (GeneralConfig, error) { return nil, err } - _, warning := utils.MultiErrorList(o.Config.deprecationWarnings()) + _, warning := utils.MultiErrorList(o.Config.warnings()) o.Config.setDefaults() if !o.SkipEnv { @@ -199,7 +199,7 @@ func (g *generalConfig) EVMConfigs() evmcfg.EVMConfigs { return g.c.EVM } -func (g *generalConfig) CosmosConfigs() cosmos.CosmosConfigs { +func (g *generalConfig) CosmosConfigs() coscfg.TOMLConfigs { return g.c.Cosmos } @@ -348,7 +348,7 @@ func (g *generalConfig) StarkNetEnabled() bool { } func (g *generalConfig) WebServer() config.WebServer { - return &webServerConfig{c: g.c.WebServer, rootDir: g.RootDir} + return &webServerConfig{c: g.c.WebServer, s: g.secrets.WebServer, rootDir: g.RootDir} } func (g *generalConfig) AutoPprofBlockProfileRate() int { @@ -507,7 +507,7 @@ func (g *generalConfig) Prometheus() coreconfig.Prometheus { } func (g *generalConfig) Mercury() coreconfig.Mercury { - return &mercuryConfig{s: g.secrets.Mercury} + return &mercuryConfig{c: g.c.Mercury, s: g.secrets.Mercury} } func (g *generalConfig) Threshold() coreconfig.Threshold { diff --git a/core/services/chainlink/config_general_test.go b/core/services/chainlink/config_general_test.go index 46931e53e2b..c122f8f968c 100644 --- a/core/services/chainlink/config_general_test.go +++ b/core/services/chainlink/config_general_test.go @@ -149,6 +149,9 @@ var mercurySecretsTOMLSplitTwo string //go:embed testdata/mergingsecretsdata/secrets-threshold.toml var thresholdSecretsTOML string +//go:embed testdata/mergingsecretsdata/secrets-webserver-ldap.toml +var WebServerLDAPSecretsTOML string + func TestConfig_SecretsMerging(t *testing.T) { t.Run("verify secrets merging in GeneralConfigOpts.New()", func(t *testing.T) { databaseSecrets, err := parseSecrets(databaseSecretsTOML) @@ -165,6 +168,8 @@ func TestConfig_SecretsMerging(t *testing.T) { require.NoErrorf(t, err6, "error: %s", err6) thresholdSecrets, err7 := parseSecrets(thresholdSecretsTOML) require.NoErrorf(t, err7, "error: %s", err7) + webserverLDAPSecrets, err8 := parseSecrets(WebServerLDAPSecretsTOML) + require.NoErrorf(t, err8, "error: %s", err8) opts := new(GeneralConfigOpts) configFiles := []string{ @@ -178,6 +183,7 @@ func TestConfig_SecretsMerging(t *testing.T) { "testdata/mergingsecretsdata/secrets-mercury-split-one.toml", "testdata/mergingsecretsdata/secrets-mercury-split-two.toml", "testdata/mergingsecretsdata/secrets-threshold.toml", + "testdata/mergingsecretsdata/secrets-webserver-ldap.toml", } err = opts.Setup(configFiles, secretsFiles) require.NoErrorf(t, err, "error: %s", err) @@ -194,6 +200,10 @@ func TestConfig_SecretsMerging(t *testing.T) { assert.Equal(t, (string)(*prometheusSecrets.Prometheus.AuthToken), (string)(*opts.Secrets.Prometheus.AuthToken)) assert.Equal(t, (string)(*thresholdSecrets.Threshold.ThresholdKeyShare), (string)(*opts.Secrets.Threshold.ThresholdKeyShare)) + assert.Equal(t, webserverLDAPSecrets.WebServer.LDAP.ServerAddress.URL().String(), opts.Secrets.WebServer.LDAP.ServerAddress.URL().String()) + assert.Equal(t, webserverLDAPSecrets.WebServer.LDAP.ReadOnlyUserLogin, opts.Secrets.WebServer.LDAP.ReadOnlyUserLogin) + assert.Equal(t, webserverLDAPSecrets.WebServer.LDAP.ReadOnlyUserPass, opts.Secrets.WebServer.LDAP.ReadOnlyUserPass) + err = assertDeepEqualityMercurySecrets(*merge(mercurySecrets_a.Mercury, mercurySecrets_b.Mercury), opts.Secrets.Mercury) require.NoErrorf(t, err, "merged mercury secrets unequal") }) diff --git a/core/services/chainlink/config_mercury.go b/core/services/chainlink/config_mercury.go index 1a20dd069d8..61e48d340e4 100644 --- a/core/services/chainlink/config_mercury.go +++ b/core/services/chainlink/config_mercury.go @@ -1,11 +1,31 @@ package chainlink import ( + "time" + + "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/config/toml" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" ) +var _ config.MercuryCache = (*mercuryCacheConfig)(nil) + +type mercuryCacheConfig struct { + c toml.MercuryCache +} + +func (m *mercuryCacheConfig) LatestReportTTL() time.Duration { + return m.c.LatestReportTTL.Duration() +} +func (m *mercuryCacheConfig) MaxStaleAge() time.Duration { + return m.c.MaxStaleAge.Duration() +} +func (m *mercuryCacheConfig) LatestReportDeadline() time.Duration { + return m.c.LatestReportDeadline.Duration() +} + type mercuryConfig struct { + c toml.Mercury s toml.MercurySecrets } @@ -23,3 +43,7 @@ func (m *mercuryConfig) Credentials(credName string) *models.MercuryCredentials } return nil } + +func (m *mercuryConfig) Cache() config.MercuryCache { + return &mercuryCacheConfig{c: m.c.Cache} +} diff --git a/core/services/chainlink/config_ocr2_test.go b/core/services/chainlink/config_ocr2_test.go index 66427489241..5bf84934d13 100644 --- a/core/services/chainlink/config_ocr2_test.go +++ b/core/services/chainlink/config_ocr2_test.go @@ -38,7 +38,7 @@ func TestOCR2Config(t *testing.T) { require.Equal(t, false, ocr2Cfg.TraceLogging()) require.Equal(t, uint32(1), ocr2Cfg.DefaultTransactionQueueDepth()) require.Equal(t, false, ocr2Cfg.CaptureEATelemetry()) - require.Equal(t, false, ocr2Cfg.CaptureAutomationCustomTelemetry()) + require.Equal(t, true, ocr2Cfg.CaptureAutomationCustomTelemetry()) keyBundleID, err := ocr2Cfg.KeyBundleID() require.NoError(t, err) diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 597dab6ba1c..2966a896902 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -16,14 +16,14 @@ import ( ocrcommontypes "github.com/smartcontractkit/libocr/commontypes" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + commoncfg "github.com/smartcontractkit/chainlink-common/pkg/config" coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - relayutils "github.com/smartcontractkit/chainlink-relay/pkg/utils" "github.com/smartcontractkit/chainlink-solana/pkg/solana" solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" stkcfg "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" - "github.com/smartcontractkit/chainlink/v2/core/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" legacy "github.com/smartcontractkit/chainlink/v2/core/config" @@ -138,14 +138,14 @@ var ( }, }}, }, - Cosmos: []*cosmos.CosmosConfig{ + Cosmos: []*coscfg.TOMLConfig{ { ChainID: ptr("Ibiza-808"), Chain: coscfg.Chain{ MaxMsgsPerBatch: ptr[int64](13), }, Nodes: []*coscfg.Node{ - {Name: ptr("primary"), TendermintURL: relayutils.MustParseURL("http://columbus.cosmos.com")}, + {Name: ptr("primary"), TendermintURL: commoncfg.MustParseURL("http://columbus.cosmos.com")}, }}, { ChainID: ptr("Malaga-420"), @@ -153,7 +153,7 @@ var ( BlocksUntilTxTimeout: ptr[int64](20), }, Nodes: []*coscfg.Node{ - {Name: ptr("secondary"), TendermintURL: relayutils.MustParseURL("http://bombay.cosmos.com")}, + {Name: ptr("secondary"), TendermintURL: commoncfg.MustParseURL("http://bombay.cosmos.com")}, }}, }, Solana: []*solana.TOMLConfig{ @@ -163,16 +163,16 @@ var ( MaxRetries: ptr[int64](12), }, Nodes: []*solcfg.Node{ - {Name: ptr("primary"), URL: relayutils.MustParseURL("http://mainnet.solana.com")}, + {Name: ptr("primary"), URL: commoncfg.MustParseURL("http://mainnet.solana.com")}, }, }, { ChainID: ptr("testnet"), Chain: solcfg.Chain{ - OCR2CachePollPeriod: relayutils.MustNewDuration(time.Minute), + OCR2CachePollPeriod: commoncfg.MustNewDuration(time.Minute), }, Nodes: []*solcfg.Node{ - {Name: ptr("secondary"), URL: relayutils.MustParseURL("http://testnet.solana.com")}, + {Name: ptr("secondary"), URL: commoncfg.MustParseURL("http://testnet.solana.com")}, }, }, }, @@ -180,10 +180,10 @@ var ( { ChainID: ptr("foobar"), Chain: stkcfg.Chain{ - ConfirmationPoll: relayutils.MustNewDuration(time.Hour), + ConfirmationPoll: commoncfg.MustNewDuration(time.Hour), }, Nodes: []*stkcfg.Node{ - {Name: ptr("primary"), URL: relayutils.MustParseURL("http://stark.node")}, + {Name: ptr("primary"), URL: commoncfg.MustParseURL("http://stark.node")}, }, }, }, @@ -227,11 +227,13 @@ func TestConfig_Marshal(t *testing.T) { Enabled: ptr(true), CollectorTarget: ptr("localhost:4317"), NodeID: ptr("clc-ocr-sol-devnet-node-1"), + SamplingRatio: ptr(1.0), + Mode: ptr("tls"), + TLSCertPath: ptr("/path/to/cert.pem"), Attributes: map[string]string{ "test": "load", "env": "dev", }, - SamplingRatio: ptr(1.0), }, }, } @@ -309,6 +311,7 @@ func TestConfig_Marshal(t *testing.T) { }, } full.WebServer = toml.WebServer{ + AuthenticationMethod: ptr("local"), AllowOrigins: ptr("*"), BridgeResponseURL: mustURL("https://bridge.response"), BridgeCacheTTL: models.MustNewDuration(10 * time.Second), @@ -324,6 +327,25 @@ func TestConfig_Marshal(t *testing.T) { RPID: ptr("test-rpid"), RPOrigin: ptr("test-rp-origin"), }, + LDAP: toml.WebServerLDAP{ + ServerTLS: ptr(true), + SessionTimeout: models.MustNewDuration(15 * time.Minute), + QueryTimeout: models.MustNewDuration(2 * time.Minute), + BaseUserAttr: ptr("uid"), + BaseDN: ptr("dc=custom,dc=example,dc=com"), + UsersDN: ptr("ou=users"), + GroupsDN: ptr("ou=groups"), + ActiveAttribute: ptr("organizationalStatus"), + ActiveAttributeAllowedValue: ptr("ACTIVE"), + AdminUserGroupCN: ptr("NodeAdmins"), + EditUserGroupCN: ptr("NodeEditors"), + RunUserGroupCN: ptr("NodeRunners"), + ReadUserGroupCN: ptr("NodeReadOnly"), + UserApiTokenEnabled: ptr(false), + UserAPITokenDuration: models.MustNewDuration(240 * time.Hour), + UpstreamSyncInterval: models.MustNewDuration(0 * time.Second), + UpstreamSyncRateLimit: models.MustNewDuration(2 * time.Minute), + }, RateLimit: toml.WebServerRateLimit{ Authenticated: ptr[int64](42), AuthenticatedPeriod: models.MustNewDuration(time.Second), @@ -365,7 +387,7 @@ func TestConfig_Marshal(t *testing.T) { DatabaseTimeout: models.MustNewDuration(8 * time.Second), KeyBundleID: ptr(models.MustSha256HashFromHex("7a5f66bbe6594259325bf2b4f5b1a9c9")), CaptureEATelemetry: ptr(false), - CaptureAutomationCustomTelemetry: ptr(false), + CaptureAutomationCustomTelemetry: ptr(true), DefaultTransactionQueueDepth: ptr[uint32](1), SimulateTransactions: ptr(false), TraceLogging: ptr(false), @@ -469,7 +491,7 @@ func TestConfig_Marshal(t *testing.T) { FlagsContractAddress: mustAddress("0xae4E781a6218A8031764928E88d457937A954fC3"), GasEstimator: evmcfg.GasEstimator{ - Mode: ptr("L2Suggested"), + Mode: ptr("SuggestedPrice"), EIP1559DynamicFees: ptr(true), BumpPercent: ptr[uint16](10), BumpThreshold: ptr[uint32](6), @@ -518,7 +540,7 @@ func TestConfig_Marshal(t *testing.T) { LogBackfillBatchSize: ptr[uint32](17), LogPollInterval: &minute, LogKeepBlocksDepth: ptr[uint32](100000), - MinContractPayment: assets.NewLinkFromJuels(math.MaxInt64), + MinContractPayment: commonassets.NewLinkFromJuels(math.MaxInt64), MinIncomingConfirmations: ptr[uint32](13), NonceAutoSync: ptr(true), NoNewHeadsThreshold: &minute, @@ -552,6 +574,8 @@ func TestConfig_Marshal(t *testing.T) { ContractConfirmations: ptr[uint16](11), ContractTransmitterTransmitTimeout: &minute, DatabaseTimeout: &second, + DeltaCOverride: models.MustNewDuration(time.Hour), + DeltaCJitterOverride: models.MustNewDuration(time.Second), ObservationGracePeriod: &second, }, OCR2: evmcfg.OCR2{ @@ -583,13 +607,13 @@ func TestConfig_Marshal(t *testing.T) { ChainID: ptr("mainnet"), Enabled: ptr(false), Chain: solcfg.Chain{ - BalancePollPeriod: relayutils.MustNewDuration(time.Minute), - ConfirmPollPeriod: relayutils.MustNewDuration(time.Second), - OCR2CachePollPeriod: relayutils.MustNewDuration(time.Minute), - OCR2CacheTTL: relayutils.MustNewDuration(time.Hour), - TxTimeout: relayutils.MustNewDuration(time.Hour), - TxRetryTimeout: relayutils.MustNewDuration(time.Minute), - TxConfirmTimeout: relayutils.MustNewDuration(time.Second), + BalancePollPeriod: commoncfg.MustNewDuration(time.Minute), + ConfirmPollPeriod: commoncfg.MustNewDuration(time.Second), + OCR2CachePollPeriod: commoncfg.MustNewDuration(time.Minute), + OCR2CacheTTL: commoncfg.MustNewDuration(time.Hour), + TxTimeout: commoncfg.MustNewDuration(time.Hour), + TxRetryTimeout: commoncfg.MustNewDuration(time.Minute), + TxConfirmTimeout: commoncfg.MustNewDuration(time.Second), SkipPreflight: ptr(true), Commitment: ptr("banana"), MaxRetries: ptr[int64](7), @@ -597,12 +621,12 @@ func TestConfig_Marshal(t *testing.T) { ComputeUnitPriceMax: ptr[uint64](1000), ComputeUnitPriceMin: ptr[uint64](10), ComputeUnitPriceDefault: ptr[uint64](100), - FeeBumpPeriod: relayutils.MustNewDuration(time.Minute), + FeeBumpPeriod: commoncfg.MustNewDuration(time.Minute), }, Nodes: []*solcfg.Node{ - {Name: ptr("primary"), URL: relayutils.MustParseURL("http://solana.web")}, - {Name: ptr("foo"), URL: relayutils.MustParseURL("http://solana.foo")}, - {Name: ptr("bar"), URL: relayutils.MustParseURL("http://solana.bar")}, + {Name: ptr("primary"), URL: commoncfg.MustParseURL("http://solana.web")}, + {Name: ptr("foo"), URL: commoncfg.MustParseURL("http://solana.foo")}, + {Name: ptr("bar"), URL: commoncfg.MustParseURL("http://solana.bar")}, }, }, } @@ -611,41 +635,48 @@ func TestConfig_Marshal(t *testing.T) { ChainID: ptr("foobar"), Enabled: ptr(true), Chain: stkcfg.Chain{ - OCR2CachePollPeriod: relayutils.MustNewDuration(6 * time.Hour), - OCR2CacheTTL: relayutils.MustNewDuration(3 * time.Minute), - RequestTimeout: relayutils.MustNewDuration(time.Minute + 3*time.Second), - TxTimeout: relayutils.MustNewDuration(13 * time.Second), - ConfirmationPoll: relayutils.MustNewDuration(42 * time.Second), + OCR2CachePollPeriod: commoncfg.MustNewDuration(6 * time.Hour), + OCR2CacheTTL: commoncfg.MustNewDuration(3 * time.Minute), + RequestTimeout: commoncfg.MustNewDuration(time.Minute + 3*time.Second), + TxTimeout: commoncfg.MustNewDuration(13 * time.Second), + ConfirmationPoll: commoncfg.MustNewDuration(42 * time.Second), }, Nodes: []*stkcfg.Node{ - {Name: ptr("primary"), URL: relayutils.MustParseURL("http://stark.node")}, + {Name: ptr("primary"), URL: commoncfg.MustParseURL("http://stark.node")}, }, }, } - full.Cosmos = []*cosmos.CosmosConfig{ + full.Cosmos = []*coscfg.TOMLConfig{ { ChainID: ptr("Malaga-420"), Enabled: ptr(true), Chain: coscfg.Chain{ Bech32Prefix: ptr("wasm"), - BlockRate: relayutils.MustNewDuration(time.Minute), + BlockRate: commoncfg.MustNewDuration(time.Minute), BlocksUntilTxTimeout: ptr[int64](12), - ConfirmPollPeriod: relayutils.MustNewDuration(time.Second), + ConfirmPollPeriod: commoncfg.MustNewDuration(time.Second), FallbackGasPrice: mustDecimal("0.001"), GasToken: ptr("ucosm"), GasLimitMultiplier: mustDecimal("1.2"), MaxMsgsPerBatch: ptr[int64](17), - OCR2CachePollPeriod: relayutils.MustNewDuration(time.Minute), - OCR2CacheTTL: relayutils.MustNewDuration(time.Hour), - TxMsgTimeout: relayutils.MustNewDuration(time.Second), + OCR2CachePollPeriod: commoncfg.MustNewDuration(time.Minute), + OCR2CacheTTL: commoncfg.MustNewDuration(time.Hour), + TxMsgTimeout: commoncfg.MustNewDuration(time.Second), }, Nodes: []*coscfg.Node{ - {Name: ptr("primary"), TendermintURL: relayutils.MustParseURL("http://tender.mint")}, - {Name: ptr("foo"), TendermintURL: relayutils.MustParseURL("http://foo.url")}, - {Name: ptr("bar"), TendermintURL: relayutils.MustParseURL("http://bar.web")}, + {Name: ptr("primary"), TendermintURL: commoncfg.MustParseURL("http://tender.mint")}, + {Name: ptr("foo"), TendermintURL: commoncfg.MustParseURL("http://foo.url")}, + {Name: ptr("bar"), TendermintURL: commoncfg.MustParseURL("http://bar.web")}, }, }, } + full.Mercury = toml.Mercury{ + Cache: toml.MercuryCache{ + LatestReportTTL: models.MustNewDuration(100 * time.Second), + MaxStaleAge: models.MustNewDuration(101 * time.Second), + LatestReportDeadline: models.MustNewDuration(102 * time.Second), + }, + } for _, tt := range []struct { name string @@ -668,6 +699,8 @@ Enabled = true CollectorTarget = 'localhost:4317' NodeID = 'clc-ocr-sol-devnet-node-1' SamplingRatio = 1.0 +Mode = 'tls' +TLSCertPath = '/path/to/cert.pem' [Tracing.Attributes] env = 'dev' @@ -739,6 +772,7 @@ MaxAgeDays = 17 MaxBackups = 9 `}, {"WebServer", Config{Core: toml.Core{WebServer: full.WebServer}}, `[WebServer] +AuthenticationMethod = 'local' AllowOrigins = '*' BridgeResponseURL = 'https://bridge.response' BridgeCacheTTL = '10s' @@ -751,6 +785,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '192.158.1.37' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = 'dc=custom,dc=example,dc=com' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = 'organizationalStatus' +ActiveAttributeAllowedValue = 'ACTIVE' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = 'test-rpid' RPOrigin = 'test-rp-origin' @@ -808,7 +861,7 @@ ContractTransmitterTransmitTimeout = '1m0s' DatabaseTimeout = '8s' KeyBundleID = '7a5f66bbe6594259325bf2b4f5b1a9c900000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -913,7 +966,7 @@ ResendAfterThreshold = '1h0m0s' Enabled = true [EVM.GasEstimator] -Mode = 'L2Suggested' +Mode = 'SuggestedPrice' PriceDefault = '9.223372036854775807 ether' PriceMax = '281.474976710655 micro' PriceMin = '13 wei' @@ -968,6 +1021,8 @@ LeaseDuration = '0s' ContractConfirmations = 11 ContractTransmitterTransmitTimeout = '1m0s' DatabaseTimeout = '1s' +DeltaCOverride = '1h0m0s' +DeltaCJitterOverride = '1s' ObservationGracePeriod = '1s' [EVM.OCR2] @@ -1059,6 +1114,12 @@ ConfirmationPoll = '42s' [[Starknet.Nodes]] Name = 'primary' URL = 'http://stark.node' +`}, + {"Mercury", Config{Core: toml.Core{Mercury: full.Mercury}}, `[Mercury] +[Mercury.Cache] +LatestReportTTL = '1m40s' +MaxStaleAge = '1m41s' +LatestReportDeadline = '1m42s' `}, {"full", full, fullTOML}, {"multi-chain", multiChain, multiChainTOML}, @@ -1119,8 +1180,17 @@ func TestConfig_Validate(t *testing.T) { toml string exp string }{ - {name: "invalid", toml: invalidTOML, exp: `invalid configuration: 5 errors: + {name: "invalid", toml: invalidTOML, exp: `invalid configuration: 6 errors: - Database.Lock.LeaseRefreshInterval: invalid value (6s): must be less than or equal to half of LeaseDuration (10s) + - WebServer: 8 errors: + - LDAP.BaseDN: invalid value (): LDAP BaseDN can not be empty + - LDAP.BaseUserAttr: invalid value (): LDAP BaseUserAttr can not be empty + - LDAP.UsersDN: invalid value (): LDAP UsersDN can not be empty + - LDAP.GroupsDN: invalid value (): LDAP GroupsDN can not be empty + - LDAP.AdminUserGroupCN: invalid value (): LDAP AdminUserGroupCN can not be empty + - LDAP.RunUserGroupCN: invalid value (): LDAP ReadUserGroupCN can not be empty + - LDAP.RunUserGroupCN: invalid value (): LDAP RunUserGroupCN can not be empty + - LDAP.ReadUserGroupCN: invalid value (): LDAP ReadUserGroupCN can not be empty - EVM: 8 errors: - 1.ChainID: invalid value (1): duplicate - must be unique - 0.Nodes.1.Name: invalid value (foo): duplicate - must be unique @@ -1142,7 +1212,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 6 errors: - ChainType: invalid value (Foo): must not be set with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Foo): must be one of arbitrum, metis, xdai, optimismBedrock, celo or omitted + - ChainType: invalid value (Foo): must be one of arbitrum, metis, xdai, optimismBedrock, celo, kroma, wemix, zksync or omitted - HeadTracker.HistoryDepth: invalid value (30): must be equal to or greater than FinalityDepth - GasEstimator: 2 errors: - FeeCapDefault: invalid value (101 wei): must be equal to PriceMax (99 wei) since you are using FixedPrice estimation with gas bumping disabled in EIP1559 mode - PriceMax will be used as the FeeCap for transactions instead of FeeCapDefault @@ -1151,7 +1221,7 @@ func TestConfig_Validate(t *testing.T) { - 2: 5 errors: - ChainType: invalid value (Arbitrum): only "optimismBedrock" can be used with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Arbitrum): must be one of arbitrum, metis, xdai, optimismBedrock, celo or omitted + - ChainType: invalid value (Arbitrum): must be one of arbitrum, metis, xdai, optimismBedrock, celo, kroma, wemix, zksync or omitted - FinalityDepth: invalid value (0): must be greater than or equal to 1 - MinIncomingConfirmations: invalid value (0): must be greater than or equal to 1 - 3.Nodes: 5 errors: @@ -1435,7 +1505,7 @@ func assertValidationError(t *testing.T, invalid interface{ Validate() error }, func TestConfig_setDefaults(t *testing.T) { var c Config c.EVM = evmcfg.EVMConfigs{{ChainID: utils.NewBigI(99999133712345)}} - c.Cosmos = cosmos.CosmosConfigs{{ChainID: ptr("unknown cosmos chain")}} + c.Cosmos = coscfg.TOMLConfigs{{ChainID: ptr("unknown cosmos chain")}} c.Solana = solana.TOMLConfigs{{ChainID: ptr("unknown solana chain")}} c.Starknet = stkcfg.TOMLConfigs{{ChainID: ptr("unknown starknet chain")}} c.setDefaults() @@ -1488,4 +1558,80 @@ func TestConfig_SetFrom(t *testing.T) { } } +func TestConfig_Warnings(t *testing.T) { + tests := []struct { + name string + config Config + expectedErrors []string + }{ + { + name: "No warnings", + config: Config{}, + expectedErrors: nil, + }, + { + name: "Value warning - unencrypted mode with TLS path set", + config: Config{ + Core: toml.Core{ + Tracing: toml.Tracing{ + Enabled: ptr(true), + Mode: ptr("unencrypted"), + TLSCertPath: ptr("/path/to/cert.pem"), + }, + }, + }, + expectedErrors: []string{"Tracing.TLSCertPath: invalid value (/path/to/cert.pem): must be empty when Tracing.Mode is 'unencrypted'"}, + }, + { + name: "Deprecation warning - P2P.V1 fields set", + config: Config{ + Core: toml.Core{ + P2P: toml.P2P{ + V1: toml.P2PV1{ + Enabled: ptr(true), + }, + }, + }, + }, + expectedErrors: []string{ + "P2P.V1: is deprecated and will be removed in a future version", + }, + }, + { + name: "Value warning and deprecation warning", + config: Config{ + Core: toml.Core{ + P2P: toml.P2P{ + V1: toml.P2PV1{ + Enabled: ptr(true), + }, + }, + Tracing: toml.Tracing{ + Enabled: ptr(true), + Mode: ptr("unencrypted"), + TLSCertPath: ptr("/path/to/cert.pem"), + }, + }, + }, + expectedErrors: []string{ + "Tracing.TLSCertPath: invalid value (/path/to/cert.pem): must be empty when Tracing.Mode is 'unencrypted'", + "P2P.V1: is deprecated and will be removed in a future version", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.config.warnings() + if len(tt.expectedErrors) == 0 { + assert.NoError(t, err) + } else { + for _, expectedErr := range tt.expectedErrors { + assert.Contains(t, err.Error(), expectedErr) + } + } + }) + } +} + func ptr[T any](t T) *T { return &t } diff --git a/core/services/chainlink/config_tracing.go b/core/services/chainlink/config_tracing.go index 390645974f1..36d09ae0564 100644 --- a/core/services/chainlink/config_tracing.go +++ b/core/services/chainlink/config_tracing.go @@ -18,10 +18,18 @@ func (t tracingConfig) NodeID() string { return *t.s.NodeID } -func (t tracingConfig) Attributes() map[string]string { - return t.s.Attributes -} - func (t tracingConfig) SamplingRatio() float64 { return *t.s.SamplingRatio } + +func (t tracingConfig) Mode() string { + return *t.s.Mode +} + +func (t tracingConfig) TLSCertPath() string { + return *t.s.TLSCertPath +} + +func (t tracingConfig) Attributes() map[string]string { + return t.s.Attributes +} diff --git a/core/services/chainlink/config_tracing_test.go b/core/services/chainlink/config_tracing_test.go index 5968bc306a2..37653729cf3 100644 --- a/core/services/chainlink/config_tracing_test.go +++ b/core/services/chainlink/config_tracing_test.go @@ -14,12 +14,16 @@ func TestTracing_Config(t *testing.T) { collectorTarget := "http://localhost:9000" nodeID := "Node1" samplingRatio := 0.5 + mode := "tls" + tlsCertPath := "/path/to/cert.pem" attributes := map[string]string{"key": "value"} tracing := toml.Tracing{ Enabled: &enabled, CollectorTarget: &collectorTarget, NodeID: &nodeID, SamplingRatio: &samplingRatio, + Mode: &mode, + TLSCertPath: &tlsCertPath, Attributes: attributes, } tConfig := tracingConfig{s: tracing} @@ -28,6 +32,8 @@ func TestTracing_Config(t *testing.T) { assert.Equal(t, "http://localhost:9000", tConfig.CollectorTarget()) assert.Equal(t, "Node1", tConfig.NodeID()) assert.Equal(t, 0.5, tConfig.SamplingRatio()) + assert.Equal(t, "tls", tConfig.Mode()) + assert.Equal(t, "/path/to/cert.pem", tConfig.TLSCertPath()) assert.Equal(t, map[string]string{"key": "value"}, tConfig.Attributes()) // Test when all fields are nil @@ -38,5 +44,7 @@ func TestTracing_Config(t *testing.T) { assert.Panics(t, func() { nilConfig.CollectorTarget() }) assert.Panics(t, func() { nilConfig.NodeID() }) assert.Panics(t, func() { nilConfig.SamplingRatio() }) + assert.Panics(t, func() { nilConfig.Mode() }) + assert.Panics(t, func() { nilConfig.TLSCertPath() }) assert.Nil(t, nilConfig.Attributes()) } diff --git a/core/services/chainlink/config_web_server.go b/core/services/chainlink/config_web_server.go index a931d67f386..06db398e2ea 100644 --- a/core/services/chainlink/config_web_server.go +++ b/core/services/chainlink/config_web_server.go @@ -98,6 +98,7 @@ func (m *mfaConfig) RPOrigin() string { type webServerConfig struct { c toml.WebServer + s toml.WebServerSecrets rootDir func() string } @@ -113,6 +114,14 @@ func (w *webServerConfig) MFA() config.MFA { return &mfaConfig{c: w.c.MFA} } +func (w *webServerConfig) LDAP() config.LDAP { + return &ldapConfig{c: w.c.LDAP, s: w.s.LDAP} +} + +func (w *webServerConfig) AuthenticationMethod() string { + return *w.c.AuthenticationMethod +} + func (w *webServerConfig) AllowOrigins() string { return *w.c.AllowOrigins } @@ -168,3 +177,139 @@ func (w *webServerConfig) SessionTimeout() models.Duration { func (w *webServerConfig) ListenIP() net.IP { return *w.c.ListenIP } + +type ldapConfig struct { + c toml.WebServerLDAP + s toml.WebServerLDAPSecrets +} + +func (l *ldapConfig) ServerAddress() string { + if l.s.ServerAddress == nil { + return "" + } + return l.s.ServerAddress.URL().String() +} + +func (l *ldapConfig) ReadOnlyUserLogin() string { + if l.s.ReadOnlyUserLogin == nil { + return "" + } + return string(*l.s.ReadOnlyUserLogin) +} + +func (l *ldapConfig) ReadOnlyUserPass() string { + if l.s.ReadOnlyUserPass == nil { + return "" + } + return string(*l.s.ReadOnlyUserPass) +} + +func (l *ldapConfig) ServerTLS() bool { + if l.c.ServerTLS == nil { + return false + } + return *l.c.ServerTLS +} + +func (l *ldapConfig) SessionTimeout() models.Duration { + return *l.c.SessionTimeout +} + +func (l *ldapConfig) QueryTimeout() time.Duration { + return l.c.QueryTimeout.Duration() +} + +func (l *ldapConfig) UserAPITokenDuration() models.Duration { + return *l.c.UserAPITokenDuration +} + +func (l *ldapConfig) BaseUserAttr() string { + if l.c.BaseUserAttr == nil { + return "" + } + return *l.c.BaseUserAttr +} + +func (l *ldapConfig) BaseDN() string { + if l.c.BaseDN == nil { + return "" + } + return *l.c.BaseDN +} + +func (l *ldapConfig) UsersDN() string { + if l.c.UsersDN == nil { + return "" + } + return *l.c.UsersDN +} + +func (l *ldapConfig) GroupsDN() string { + if l.c.GroupsDN == nil { + return "" + } + return *l.c.GroupsDN +} + +func (l *ldapConfig) ActiveAttribute() string { + if l.c.ActiveAttribute == nil { + return "" + } + return *l.c.ActiveAttribute +} + +func (l *ldapConfig) ActiveAttributeAllowedValue() string { + if l.c.ActiveAttributeAllowedValue == nil { + return "" + } + return *l.c.ActiveAttributeAllowedValue +} + +func (l *ldapConfig) AdminUserGroupCN() string { + if l.c.AdminUserGroupCN == nil { + return "" + } + return *l.c.AdminUserGroupCN +} + +func (l *ldapConfig) EditUserGroupCN() string { + if l.c.EditUserGroupCN == nil { + return "" + } + return *l.c.EditUserGroupCN +} + +func (l *ldapConfig) RunUserGroupCN() string { + if l.c.RunUserGroupCN == nil { + return "" + } + return *l.c.RunUserGroupCN +} + +func (l *ldapConfig) ReadUserGroupCN() string { + if l.c.ReadUserGroupCN == nil { + return "" + } + return *l.c.ReadUserGroupCN +} + +func (l *ldapConfig) UserApiTokenEnabled() bool { + if l.c.UserApiTokenEnabled == nil { + return false + } + return *l.c.UserApiTokenEnabled +} + +func (l *ldapConfig) UpstreamSyncInterval() models.Duration { + if l.c.UpstreamSyncInterval == nil { + return models.Duration{} + } + return *l.c.UpstreamSyncInterval +} + +func (l *ldapConfig) UpstreamSyncRateLimit() models.Duration { + if l.c.UpstreamSyncRateLimit == nil { + return models.Duration{} + } + return *l.c.UpstreamSyncRateLimit +} diff --git a/core/services/chainlink/mocks/general_config.go b/core/services/chainlink/mocks/general_config.go index 0bc51ea4310..98796e90053 100644 --- a/core/services/chainlink/mocks/general_config.go +++ b/core/services/chainlink/mocks/general_config.go @@ -6,7 +6,7 @@ import ( chainlinkconfig "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" config "github.com/smartcontractkit/chainlink/v2/core/config" - cosmos "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" + cosmosconfig "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" mock "github.com/stretchr/testify/mock" @@ -99,15 +99,15 @@ func (_m *GeneralConfig) ConfigTOML() (string, string) { } // CosmosConfigs provides a mock function with given fields: -func (_m *GeneralConfig) CosmosConfigs() cosmos.CosmosConfigs { +func (_m *GeneralConfig) CosmosConfigs() cosmosconfig.TOMLConfigs { ret := _m.Called() - var r0 cosmos.CosmosConfigs - if rf, ok := ret.Get(0).(func() cosmos.CosmosConfigs); ok { + var r0 cosmosconfig.TOMLConfigs + if rf, ok := ret.Get(0).(func() cosmosconfig.TOMLConfigs); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(cosmos.CosmosConfigs) + r0 = ret.Get(0).(cosmosconfig.TOMLConfigs) } } diff --git a/core/services/chainlink/mocks/relayer_chain_interoperators.go b/core/services/chainlink/mocks/relayer_chain_interoperators.go index 0a8758f6d4b..74dc9dc1d45 100644 --- a/core/services/chainlink/mocks/relayer_chain_interoperators.go +++ b/core/services/chainlink/mocks/relayer_chain_interoperators.go @@ -1,248 +1,61 @@ -// Code generated by mockery v2.28.1. DO NOT EDIT. - package mocks import ( - context "context" - - chainlink "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - - cosmos "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" + "context" + "slices" - evm "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + services2 "github.com/smartcontractkit/chainlink/v2/core/services" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - // Manually edited. mockery generates the wrong dependency. edited to use `loop` rather than `loop/internal` - // seems to caused by incorrect alias resolution of the relayer dep - internal "github.com/smartcontractkit/chainlink-relay/pkg/loop" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" - mock "github.com/stretchr/testify/mock" + "github.com/smartcontractkit/chainlink-common/pkg/loop" - relay "github.com/smartcontractkit/chainlink/v2/core/services/relay" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" - services "github.com/smartcontractkit/chainlink/v2/core/services" - - types "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" ) -// RelayerChainInteroperators is an autogenerated mock type for the RelayerChainInteroperators type -type RelayerChainInteroperators struct { - mock.Mock -} - -// ChainStatus provides a mock function with given fields: ctx, id -func (_m *RelayerChainInteroperators) ChainStatus(ctx context.Context, id relay.ID) (types.ChainStatus, error) { - ret := _m.Called(ctx, id) - - var r0 types.ChainStatus - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, relay.ID) (types.ChainStatus, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, relay.ID) types.ChainStatus); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Get(0).(types.ChainStatus) - } - - if rf, ok := ret.Get(1).(func(context.Context, relay.ID) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ChainStatuses provides a mock function with given fields: ctx, offset, limit -func (_m *RelayerChainInteroperators) ChainStatuses(ctx context.Context, offset int, limit int) ([]types.ChainStatus, int, error) { - ret := _m.Called(ctx, offset, limit) - - var r0 []types.ChainStatus - var r1 int - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, int, int) ([]types.ChainStatus, int, error)); ok { - return rf(ctx, offset, limit) - } - if rf, ok := ret.Get(0).(func(context.Context, int, int) []types.ChainStatus); ok { - r0 = rf(ctx, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.ChainStatus) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, int, int) int); ok { - r1 = rf(ctx, offset, limit) - } else { - r1 = ret.Get(1).(int) - } - - 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 +// FakeRelayerChainInteroperators is a fake chainlink.RelayerChainInteroperators. +// This exists because mockery generation doesn't understand how to produce an alias instead of the underlying type (which is not exported in this case). +type FakeRelayerChainInteroperators struct { + EVMChains legacyevm.LegacyChainContainer + Nodes []types.NodeStatus + NodesErr error } -// Get provides a mock function with given fields: id -func (_m *RelayerChainInteroperators) Get(id relay.ID) (internal.Relayer, error) { - ret := _m.Called(id) - - var r0 internal.Relayer - var r1 error - if rf, ok := ret.Get(0).(func(relay.ID) (internal.Relayer, error)); ok { - return rf(id) - } - if rf, ok := ret.Get(0).(func(relay.ID) internal.Relayer); ok { - r0 = rf(id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(internal.Relayer) - } - } - - if rf, ok := ret.Get(1).(func(relay.ID) error); ok { - r1 = rf(id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 +func (f *FakeRelayerChainInteroperators) LegacyEVMChains() legacyevm.LegacyChainContainer { + return f.EVMChains } -// LegacyCosmosChains provides a mock function with given fields: -func (_m *RelayerChainInteroperators) LegacyCosmosChains() cosmos.LegacyChainContainer { - ret := _m.Called() - - var r0 cosmos.LegacyChainContainer - if rf, ok := ret.Get(0).(func() cosmos.LegacyChainContainer); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(cosmos.LegacyChainContainer) - } - } - - return r0 +func (f *FakeRelayerChainInteroperators) NodeStatuses(ctx context.Context, offset, limit int, relayIDs ...relay.ID) (nodes []types.NodeStatus, count int, err error) { + return slices.Clone(f.Nodes), len(f.Nodes), f.NodesErr } -// LegacyEVMChains provides a mock function with given fields: -func (_m *RelayerChainInteroperators) LegacyEVMChains() evm.LegacyChainContainer { - ret := _m.Called() - - var r0 evm.LegacyChainContainer - if rf, ok := ret.Get(0).(func() evm.LegacyChainContainer); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(evm.LegacyChainContainer) - } - } - - return r0 +func (f *FakeRelayerChainInteroperators) Services() []services2.ServiceCtx { + panic("unimplemented") } -// List provides a mock function with given fields: filter -func (_m *RelayerChainInteroperators) List(filter chainlink.FilterFn) chainlink.RelayerChainInteroperators { - ret := _m.Called(filter) - - var r0 chainlink.RelayerChainInteroperators - if rf, ok := ret.Get(0).(func(chainlink.FilterFn) chainlink.RelayerChainInteroperators); ok { - r0 = rf(filter) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(chainlink.RelayerChainInteroperators) - } - } - - return r0 +func (f *FakeRelayerChainInteroperators) List(filter chainlink.FilterFn) chainlink.RelayerChainInteroperators { + panic("unimplemented") } -// NodeStatuses provides a mock function with given fields: ctx, offset, limit, relayIDs -func (_m *RelayerChainInteroperators) NodeStatuses(ctx context.Context, offset int, limit int, relayIDs ...relay.ID) ([]types.NodeStatus, int, error) { - _va := make([]interface{}, len(relayIDs)) - for _i := range relayIDs { - _va[_i] = relayIDs[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, offset, limit) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - var r0 []types.NodeStatus - var r1 int - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, int, int, ...relay.ID) ([]types.NodeStatus, int, error)); ok { - return rf(ctx, offset, limit, relayIDs...) - } - if rf, ok := ret.Get(0).(func(context.Context, int, int, ...relay.ID) []types.NodeStatus); ok { - r0 = rf(ctx, offset, limit, relayIDs...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.NodeStatus) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, int, int, ...relay.ID) int); ok { - r1 = rf(ctx, offset, limit, relayIDs...) - } else { - r1 = ret.Get(1).(int) - } - - if rf, ok := ret.Get(2).(func(context.Context, int, int, ...relay.ID) error); ok { - r2 = rf(ctx, offset, limit, relayIDs...) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 +func (f *FakeRelayerChainInteroperators) Get(id relay.ID) (loop.Relayer, error) { + panic("unimplemented") } -// Services provides a mock function with given fields: -func (_m *RelayerChainInteroperators) Services() []services.ServiceCtx { - ret := _m.Called() - - var r0 []services.ServiceCtx - if rf, ok := ret.Get(0).(func() []services.ServiceCtx); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]services.ServiceCtx) - } - } - - return r0 +func (f *FakeRelayerChainInteroperators) Slice() []loop.Relayer { + panic("unimplemented") } -// Slice provides a mock function with given fields: -func (_m *RelayerChainInteroperators) Slice() []internal.Relayer { - ret := _m.Called() - - var r0 []internal.Relayer - if rf, ok := ret.Get(0).(func() []internal.Relayer); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]internal.Relayer) - } - } - - return r0 +func (f *FakeRelayerChainInteroperators) LegacyCosmosChains() chainlink.LegacyCosmosContainer { + panic("unimplemented") } -type mockConstructorTestingTNewRelayerChainInteroperators interface { - mock.TestingT - Cleanup(func()) +func (f *FakeRelayerChainInteroperators) ChainStatus(ctx context.Context, id relay.ID) (types.ChainStatus, error) { + panic("unimplemented") } -// NewRelayerChainInteroperators creates a new instance of RelayerChainInteroperators. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewRelayerChainInteroperators(t mockConstructorTestingTNewRelayerChainInteroperators) *RelayerChainInteroperators { - mock := &RelayerChainInteroperators{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock +func (f *FakeRelayerChainInteroperators) ChainStatuses(ctx context.Context, offset, limit int) ([]types.ChainStatus, int, error) { + panic("unimplemented") } diff --git a/core/services/chainlink/relayer_chain_interoperators.go b/core/services/chainlink/relayer_chain_interoperators.go index e039afbfc91..673f71e3c65 100644 --- a/core/services/chainlink/relayer_chain_interoperators.go +++ b/core/services/chainlink/relayer_chain_interoperators.go @@ -7,12 +7,13 @@ import ( "sort" "sync" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos" + "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/adapters" "github.com/smartcontractkit/chainlink/v2/core/chains" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2" "github.com/smartcontractkit/chainlink/v2/core/services/relay" @@ -24,10 +25,6 @@ var ErrNoSuchRelayer = errors.New("relayer does not exist") // encapsulates relayers and chains and is the primary entry point for // the node to access relayers, get legacy chains associated to a relayer // and get status about the chains and nodes -// -// note the generated mockery code incorrectly resolves dependencies and needs to be manually edited -// therefore this interface is not auto-generated. for reference use and edit the result: -// `go:generate mockery --quiet --name RelayerChainInteroperators --output ./mocks/ --case=underscore“` type RelayerChainInteroperators interface { Services() []services.ServiceCtx @@ -49,8 +46,8 @@ type LoopRelayerStorer interface { // This will be deprecated/removed when products depend only // on the relayer interface. type LegacyChainer interface { - LegacyEVMChains() evm.LegacyChainContainer - LegacyCosmosChains() cosmos.LegacyChainContainer + LegacyEVMChains() legacyevm.LegacyChainContainer + LegacyCosmosChains() LegacyCosmosContainer } type ChainStatuser interface { @@ -109,14 +106,14 @@ func InitEVM(ctx context.Context, factory RelayerFactory, config EVMFactoryConfi return fmt.Errorf("failed to setup EVM relayer: %w", err2) } - legacyMap := make(map[string]evm.Chain) + legacyMap := make(map[string]legacyevm.Chain) for id, a := range adapters { // adapter is a service op.srvs = append(op.srvs, a) op.loopRelayers[id] = a legacyMap[id.ChainID] = a.Chain() } - op.legacyChains.EVMChains = evm.NewLegacyChains(legacyMap, config.AppConfig.EVMConfigs()) + op.legacyChains.EVMChains = legacyevm.NewLegacyChains(legacyMap, config.AppConfig.EVMConfigs()) return nil } } @@ -124,7 +121,7 @@ func InitEVM(ctx context.Context, factory RelayerFactory, config EVMFactoryConfi // InitCosmos is a option for instantiating Cosmos relayers func InitCosmos(ctx context.Context, factory RelayerFactory, config CosmosFactoryConfig) CoreRelayerChainInitFunc { return func(op *CoreRelayerChainInteroperators) (err error) { - adapters, err2 := factory.NewCosmos(ctx, config) + adapters, err2 := factory.NewCosmos(config) if err2 != nil { return fmt.Errorf("failed to setup Cosmos relayer: %w", err2) } @@ -135,7 +132,7 @@ func InitCosmos(ctx context.Context, factory RelayerFactory, config CosmosFactor op.loopRelayers[id] = a legacyMap[id.ChainID] = a.Chain() } - op.legacyChains.CosmosChains = cosmos.NewLegacyChains(legacyMap) + op.legacyChains.CosmosChains = NewLegacyCosmos(legacyMap) return nil } @@ -188,7 +185,7 @@ func (rs *CoreRelayerChainInteroperators) Get(id relay.ID) (loop.Relayer, error) // LegacyEVMChains returns a container with all the evm chains // TODO BCF-2511 -func (rs *CoreRelayerChainInteroperators) LegacyEVMChains() evm.LegacyChainContainer { +func (rs *CoreRelayerChainInteroperators) LegacyEVMChains() legacyevm.LegacyChainContainer { rs.mu.Lock() defer rs.mu.Unlock() return rs.legacyChains.EVMChains @@ -196,7 +193,7 @@ func (rs *CoreRelayerChainInteroperators) LegacyEVMChains() evm.LegacyChainConta // LegacyCosmosChains returns a container with all the cosmos chains // TODO BCF-2511 -func (rs *CoreRelayerChainInteroperators) LegacyCosmosChains() cosmos.LegacyChainContainer { +func (rs *CoreRelayerChainInteroperators) LegacyCosmosChains() LegacyCosmosContainer { rs.mu.Lock() defer rs.mu.Unlock() return rs.legacyChains.CosmosChains @@ -354,6 +351,45 @@ func (rs *CoreRelayerChainInteroperators) Services() (s []services.ServiceCtx) { // legacyChains encapsulates the chain-specific dependencies. Will be // deprecated when chain-specific logic is removed from products. type legacyChains struct { - EVMChains evm.LegacyChainContainer - CosmosChains cosmos.LegacyChainContainer + EVMChains legacyevm.LegacyChainContainer + CosmosChains LegacyCosmosContainer } + +// LegacyCosmosContainer is container interface for Cosmos chains +type LegacyCosmosContainer interface { + Get(id string) (adapters.Chain, error) + Len() int + List(ids ...string) ([]adapters.Chain, error) + Slice() []adapters.Chain +} + +type LegacyCosmos = chains.ChainsKV[adapters.Chain] + +var _ LegacyCosmosContainer = &LegacyCosmos{} + +func NewLegacyCosmos(m map[string]adapters.Chain) *LegacyCosmos { + return chains.NewChainsKV[adapters.Chain](m) +} + +type CosmosLoopRelayerChainer interface { + loop.Relayer + Chain() adapters.Chain +} + +type CosmosLoopRelayerChain struct { + loop.Relayer + chain adapters.Chain +} + +func NewCosmosLoopRelayerChain(r *cosmos.Relayer, s adapters.Chain) *CosmosLoopRelayerChain { + ra := relay.NewServerAdapter(r, s) + return &CosmosLoopRelayerChain{ + Relayer: ra, + chain: s, + } +} +func (r *CosmosLoopRelayerChain) Chain() adapters.Chain { + return r.chain +} + +var _ CosmosLoopRelayerChainer = &CosmosLoopRelayerChain{} diff --git a/core/services/chainlink/relayer_chain_interoperators_test.go b/core/services/chainlink/relayer_chain_interoperators_test.go index 5b17e2e4232..6a5445d9f21 100644 --- a/core/services/chainlink/relayer_chain_interoperators_test.go +++ b/core/services/chainlink/relayer_chain_interoperators_test.go @@ -9,15 +9,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + commoncfg "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/loop" coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - relayutils "github.com/smartcontractkit/chainlink-relay/pkg/utils" solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" stkcfg "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" @@ -29,7 +28,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/plugins" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -85,7 +84,7 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { Chain: solcfg.Chain{}, Nodes: []*solcfg.Node{{ Name: ptr("solana chain 1 node 1"), - URL: ((*relayutils.URL)(models.MustParseURL("http://localhost:8547").URL())), + URL: ((*commoncfg.URL)(models.MustParseURL("http://localhost:8547").URL())), }}, }, &solana.TOMLConfig{ @@ -94,7 +93,7 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { Chain: solcfg.Chain{}, Nodes: []*solcfg.Node{{ Name: ptr("solana chain 2 node 1"), - URL: ((*relayutils.URL)(models.MustParseURL("http://localhost:8527").URL())), + URL: ((*commoncfg.URL)(models.MustParseURL("http://localhost:8527").URL())), }}, }, } @@ -107,15 +106,15 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { Nodes: []*stkcfg.Node{ { Name: ptr("starknet chain 1 node 1"), - URL: ((*relayutils.URL)(models.MustParseURL("http://localhost:8547").URL())), + URL: ((*commoncfg.URL)(models.MustParseURL("http://localhost:8547").URL())), }, { Name: ptr("starknet chain 1 node 2"), - URL: ((*relayutils.URL)(models.MustParseURL("http://localhost:8548").URL())), + URL: ((*commoncfg.URL)(models.MustParseURL("http://localhost:8548").URL())), }, { Name: ptr("starknet chain 1 node 3"), - URL: ((*relayutils.URL)(models.MustParseURL("http://localhost:8549").URL())), + URL: ((*commoncfg.URL)(models.MustParseURL("http://localhost:8549").URL())), }, }, }, @@ -126,14 +125,14 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { Nodes: []*stkcfg.Node{ { Name: ptr("starknet chain 2 node 1"), - URL: ((*relayutils.URL)(models.MustParseURL("http://localhost:3547").URL())), + URL: ((*commoncfg.URL)(models.MustParseURL("http://localhost:3547").URL())), }, }, }, } - c.Cosmos = cosmos.CosmosConfigs{ - &cosmos.CosmosConfig{ + c.Cosmos = coscfg.TOMLConfigs{ + &coscfg.TOMLConfig{ ChainID: &cosmosChainID1, Enabled: ptr(true), Chain: coscfg.Chain{ @@ -141,14 +140,14 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { Bech32Prefix: ptr("wasm"), GasToken: ptr("cosm"), }, - Nodes: cosmos.CosmosNodes{ + Nodes: coscfg.Nodes{ &coscfg.Node{ Name: ptr("cosmos chain 1 node 1"), - TendermintURL: (*relayutils.URL)(models.MustParseURL("http://localhost:9548").URL()), + TendermintURL: (*commoncfg.URL)(models.MustParseURL("http://localhost:9548").URL()), }, }, }, - &cosmos.CosmosConfig{ + &coscfg.TOMLConfig{ ChainID: &cosmosChainID2, Enabled: ptr(true), Chain: coscfg.Chain{ @@ -156,10 +155,10 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { Bech32Prefix: ptr("wasm"), GasToken: ptr("cosm"), }, - Nodes: cosmos.CosmosNodes{ + Nodes: coscfg.Nodes{ &coscfg.Node{ Name: ptr("cosmos chain 2 node 1"), - TendermintURL: (*relayutils.URL)(models.MustParseURL("http://localhost:9598").URL()), + TendermintURL: (*commoncfg.URL)(models.MustParseURL("http://localhost:9598").URL()), }, }, }, @@ -204,7 +203,7 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { {name: "2 evm chains with 3 nodes", initFuncs: []chainlink.CoreRelayerChainInitFunc{ chainlink.InitEVM(testctx, factory, chainlink.EVMFactoryConfig{ - ChainOpts: evm.ChainOpts{ + ChainOpts: legacyevm.ChainOpts{ AppConfig: cfg, EventBroadcaster: pg.NewNullEventBroadcaster(), MailMon: &utils.MailboxMonitor{}, @@ -258,11 +257,10 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { name: "2 cosmos chains with 2 nodes", initFuncs: []chainlink.CoreRelayerChainInitFunc{ chainlink.InitCosmos(testctx, factory, chainlink.CosmosFactoryConfig{ - Keystore: keyStore.Cosmos(), - CosmosConfigs: cfg.CosmosConfigs(), - EventBroadcaster: pg.NewNullEventBroadcaster(), - DB: db, - QConfig: cfg.Database()}), + Keystore: keyStore.Cosmos(), + TOMLConfigs: cfg.CosmosConfigs(), + DB: db, + QConfig: cfg.Database()}), }, expectedCosmosChainCnt: 2, expectedCosmosNodeCnt: 2, @@ -279,7 +277,7 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { Keystore: keyStore.Solana(), TOMLConfigs: cfg.SolanaConfigs()}), chainlink.InitEVM(testctx, factory, chainlink.EVMFactoryConfig{ - ChainOpts: evm.ChainOpts{ + ChainOpts: legacyevm.ChainOpts{ AppConfig: cfg, EventBroadcaster: pg.NewNullEventBroadcaster(), MailMon: &utils.MailboxMonitor{}, @@ -291,11 +289,10 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { Keystore: keyStore.StarkNet(), TOMLConfigs: cfg.StarknetConfigs()}), chainlink.InitCosmos(testctx, factory, chainlink.CosmosFactoryConfig{ - Keystore: keyStore.Cosmos(), - CosmosConfigs: cfg.CosmosConfigs(), - EventBroadcaster: pg.NewNullEventBroadcaster(), - DB: db, - QConfig: cfg.Database(), + Keystore: keyStore.Cosmos(), + TOMLConfigs: cfg.CosmosConfigs(), + DB: db, + QConfig: cfg.Database(), }), }, expectedEVMChainCnt: 2, diff --git a/core/services/chainlink/relayer_factory.go b/core/services/chainlink/relayer_factory.go index 31251069df5..8b8749013fc 100644 --- a/core/services/chainlink/relayer_factory.go +++ b/core/services/chainlink/relayer_factory.go @@ -7,23 +7,25 @@ import ( "github.com/pelletier/go-toml/v2" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - pkgcosmos "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos" + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana" pkgsolana "github.com/smartcontractkit/chainlink-solana/pkg/solana" pkgstarknet "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink" starkchain "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/chain" "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/relay" evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" "github.com/smartcontractkit/chainlink/v2/plugins" ) @@ -31,10 +33,11 @@ type RelayerFactory struct { logger.Logger *plugins.LoopRegistry loop.GRPCOpts + MercuryPool wsrpc.Pool } type EVMFactoryConfig struct { - evm.ChainOpts + legacyevm.ChainOpts evmrelay.CSAETHKeystore } @@ -44,7 +47,7 @@ func (r *RelayerFactory) NewEVM(ctx context.Context, config EVMFactoryConfig) (m relayers := make(map[relay.ID]evmrelay.LoopRelayAdapter) // override some common opts with the factory values. this seems weird... maybe other signatures should change, or this should take a different type... - ccOpts := evm.ChainRelayExtenderConfig{ + ccOpts := legacyevm.ChainRelayExtenderConfig{ Logger: r.Logger.Named("EVM"), KeyStore: config.CSAETHKeystore.Eth(), ChainOpts: config.ChainOpts, @@ -67,8 +70,9 @@ func (r *RelayerFactory) NewEVM(ctx context.Context, config EVMFactoryConfig) (m QConfig: ccOpts.AppConfig.Database(), CSAETHKeystore: config.CSAETHKeystore, EventBroadcaster: ccOpts.EventBroadcaster, + MercuryPool: r.MercuryPool, } - relayer, err2 := evmrelay.NewRelayer(ccOpts.Logger.Named(relayID.ChainID), chain, relayerOpts) + relayer, err2 := evmrelay.NewRelayer(r.Logger.Named("EVM").Named(relayID.ChainID), chain, relayerOpts) if err2 != nil { err = errors.Join(err, err2) continue @@ -223,8 +227,7 @@ func (r *RelayerFactory) NewStarkNet(ks keystore.StarkNet, chainCfgs config.TOML type CosmosFactoryConfig struct { Keystore keystore.Cosmos - cosmos.CosmosConfigs - EventBroadcaster pg.EventBroadcaster + coscfg.TOMLConfigs *sqlx.DB pg.QConfig } @@ -234,12 +237,9 @@ func (c CosmosFactoryConfig) Validate() error { if c.Keystore == nil { err = errors.Join(err, fmt.Errorf("nil Keystore")) } - if len(c.CosmosConfigs) == 0 { + if len(c.TOMLConfigs) == 0 { err = errors.Join(err, fmt.Errorf("no CosmosConfigs provided")) } - if c.EventBroadcaster == nil { - err = errors.Join(err, fmt.Errorf("nil EventBroadcaster")) - } if c.DB == nil { err = errors.Join(err, fmt.Errorf("nil DB")) } @@ -253,12 +253,12 @@ func (c CosmosFactoryConfig) Validate() error { return err } -func (r *RelayerFactory) NewCosmos(ctx context.Context, config CosmosFactoryConfig) (map[relay.ID]cosmos.LoopRelayerChainer, error) { +func (r *RelayerFactory) NewCosmos(config CosmosFactoryConfig) (map[relay.ID]CosmosLoopRelayerChainer, error) { err := config.Validate() if err != nil { return nil, fmt.Errorf("cannot create Cosmos relayer: %w", err) } - relayers := make(map[relay.ID]cosmos.LoopRelayerChainer) + relayers := make(map[relay.ID]CosmosLoopRelayerChainer) var ( cosmosLggr = r.Logger.Named("Cosmos") @@ -266,17 +266,15 @@ func (r *RelayerFactory) NewCosmos(ctx context.Context, config CosmosFactoryConf ) // create one relayer per chain id - for _, chainCfg := range config.CosmosConfigs { + for _, chainCfg := range config.TOMLConfigs { relayID := relay.ID{Network: relay.Cosmos, ChainID: *chainCfg.ChainID} lggr := cosmosLggr.Named(relayID.ChainID) opts := cosmos.ChainOpts{ - QueryConfig: config.QConfig, - Logger: lggr, - DB: config.DB, - KeyStore: loopKs, - EventBroadcaster: config.EventBroadcaster, + Logger: lggr, + DB: config.DB, + KeyStore: loopKs, } chain, err := cosmos.NewChain(chainCfg, opts) @@ -284,7 +282,7 @@ func (r *RelayerFactory) NewCosmos(ctx context.Context, config CosmosFactoryConf return nil, fmt.Errorf("failed to load Cosmos chain %q: %w", relayID, err) } - relayers[relayID] = cosmos.NewLoopRelayerChain(pkgcosmos.NewRelayer(lggr, chain), chain) + relayers[relayID] = NewCosmosLoopRelayerChain(cosmos.NewRelayer(lggr, chain), chain) } return relayers, nil diff --git a/core/services/chainlink/testdata/config-empty-effective.toml b/core/services/chainlink/testdata/config-empty-effective.toml index 48d432138a8..2531e7c281d 100644 --- a/core/services/chainlink/testdata/config-empty-effective.toml +++ b/core/services/chainlink/testdata/config-empty-effective.toml @@ -61,6 +61,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -73,6 +74,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -117,7 +137,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -212,3 +232,11 @@ Enabled = false CollectorTarget = '' NodeID = '' SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index 1534a411dc1..46d9dc2c239 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -67,6 +67,7 @@ MaxAgeDays = 17 MaxBackups = 9 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = '*' BridgeResponseURL = 'https://bridge.response' BridgeCacheTTL = '10s' @@ -79,6 +80,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '192.158.1.37' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = 'dc=custom,dc=example,dc=com' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = 'organizationalStatus' +ActiveAttributeAllowedValue = 'ACTIVE' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = 'test-rpid' RPOrigin = 'test-rp-origin' @@ -123,7 +143,7 @@ ContractTransmitterTransmitTimeout = '1m0s' DatabaseTimeout = '8s' KeyBundleID = '7a5f66bbe6594259325bf2b4f5b1a9c900000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -218,11 +238,19 @@ Enabled = true CollectorTarget = 'localhost:4317' NodeID = 'clc-ocr-sol-devnet-node-1' SamplingRatio = 1.0 +Mode = 'tls' +TLSCertPath = '/path/to/cert.pem' [Tracing.Attributes] env = 'dev' test = 'load' +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1m40s' +MaxStaleAge = '1m41s' +LatestReportDeadline = '1m42s' + [[EVM]] ChainID = '1' Enabled = false @@ -257,7 +285,7 @@ ResendAfterThreshold = '1h0m0s' Enabled = true [EVM.GasEstimator] -Mode = 'L2Suggested' +Mode = 'SuggestedPrice' PriceDefault = '9.223372036854775807 ether' PriceMax = '281.474976710655 micro' PriceMin = '13 wei' @@ -312,6 +340,8 @@ LeaseDuration = '0s' ContractConfirmations = 11 ContractTransmitterTransmitTimeout = '1m0s' DatabaseTimeout = '1s' +DeltaCOverride = '1h0m0s' +DeltaCJitterOverride = '1s' ObservationGracePeriod = '1s' [EVM.OCR2] diff --git a/core/services/chainlink/testdata/config-invalid.toml b/core/services/chainlink/testdata/config-invalid.toml index 3b7e89299f6..4d8c9bc29a9 100644 --- a/core/services/chainlink/testdata/config-invalid.toml +++ b/core/services/chainlink/testdata/config-invalid.toml @@ -2,6 +2,28 @@ LeaseRefreshInterval='6s' LeaseDuration='10s' +[WebServer] +AuthenticationMethod = 'ldap' + +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = '' +BaseDN = '' +UsersDN = '' +GroupsDN = '' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = '' +EditUserGroupCN = '' +RunUserGroupCN = '' +ReadUserGroupCN = '' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [[EVM]] ChainID = '1' Transactions.MaxInFlight= 10 diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index 1dcbfe3a830..74d83035cd5 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -61,6 +61,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -73,6 +74,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -117,7 +137,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '20s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -212,6 +232,14 @@ Enabled = false CollectorTarget = '' NodeID = '' SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' [[EVM]] ChainID = '1' @@ -283,6 +311,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] @@ -368,6 +398,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] @@ -447,6 +479,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] diff --git a/core/services/chainlink/testdata/mergingsecretsdata/secrets-webserver-ldap.toml b/core/services/chainlink/testdata/mergingsecretsdata/secrets-webserver-ldap.toml new file mode 100644 index 00000000000..f73efcff0cc --- /dev/null +++ b/core/services/chainlink/testdata/mergingsecretsdata/secrets-webserver-ldap.toml @@ -0,0 +1,4 @@ +[WebServer.LDAP] +ServerAddress = 'ldaps://127.0.0.1' +ReadOnlyUserLogin = 'viewer@example.com' +ReadOnlyUserPass = 'password' \ No newline at end of file diff --git a/core/services/chainlink/testdata/secrets-full-redacted.toml b/core/services/chainlink/testdata/secrets-full-redacted.toml index 740c3250edb..9d91d79cb51 100644 --- a/core/services/chainlink/testdata/secrets-full-redacted.toml +++ b/core/services/chainlink/testdata/secrets-full-redacted.toml @@ -7,6 +7,12 @@ AllowSimplePasswords = false Keystore = 'xxxxx' VRF = 'xxxxx' +[WebServer] +[WebServer.LDAP] +ServerAddress = 'xxxxx' +ReadOnlyUserLogin = 'xxxxx' +ReadOnlyUserPass = 'xxxxx' + [Pyroscope] AuthToken = 'xxxxx' diff --git a/core/services/chainlink/testdata/secrets-full.toml b/core/services/chainlink/testdata/secrets-full.toml index 37e5dafc7d7..37a3e2e7dc2 100644 --- a/core/services/chainlink/testdata/secrets-full.toml +++ b/core/services/chainlink/testdata/secrets-full.toml @@ -6,6 +6,12 @@ BackupURL = "postgresql://user:pass@localhost:5432/backupdbname?sslmode=disable" Keystore = "keystore_pass" VRF = "VRF_pass" +[WebServer] +[WebServer.LDAP] +ServerAddress = 'ldaps://127.0.0.1' +ReadOnlyUserLogin = 'viewer@example.com' +ReadOnlyUserPass = 'password' + [Pyroscope] AuthToken = "pyroscope-token" diff --git a/core/services/chainlink/types.go b/core/services/chainlink/types.go index 1233a179617..72cad694167 100644 --- a/core/services/chainlink/types.go +++ b/core/services/chainlink/types.go @@ -1,10 +1,10 @@ package chainlink import ( + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana" stkcfg "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/config" ) @@ -14,7 +14,7 @@ import ( type GeneralConfig interface { config.AppConfig toml.HasEVMConfigs - CosmosConfigs() cosmos.CosmosConfigs + CosmosConfigs() coscfg.TOMLConfigs SolanaConfigs() solana.TOMLConfigs StarknetConfigs() stkcfg.TOMLConfigs // ConfigTOML returns both the user provided and effective configuration as TOML. diff --git a/core/services/cron/cron.go b/core/services/cron/cron.go index e89dd1ceabd..500192554fb 100644 --- a/core/services/cron/cron.go +++ b/core/services/cron/cron.go @@ -6,10 +6,11 @@ import ( "github.com/robfig/cron/v3" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) // Cron runs a cron jobSpec from a CronSpec @@ -18,7 +19,7 @@ type Cron struct { logger logger.Logger jobSpec job.Job pipelineRunner pipeline.Runner - chStop utils.StopChan + chStop services.StopChan } // NewCronFromJobSpec instantiates a job that executes on a predefined schedule. diff --git a/core/services/cron/cron_test.go b/core/services/cron/cron_test.go index 174f80586ae..b561248eddb 100644 --- a/core/services/cron/cron_test.go +++ b/core/services/cron/cron_test.go @@ -11,15 +11,13 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/cron" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" pipelinemocks "github.com/smartcontractkit/chainlink/v2/core/services/pipeline/mocks" - evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" ) func TestCronV2Pipeline(t *testing.T) { @@ -28,12 +26,10 @@ func TestCronV2Pipeline(t *testing.T) { db := pgtest.NewSqlxDB(t) keyStore := cltest.NewKeyStore(t, db, cfg.Database()) - relayerExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: cfg, Client: evmtest.NewEthClientMockWithDefaultChain(t), KeyStore: keyStore.Eth()}) lggr := logger.TestLogger(t) orm := pipeline.NewORM(db, lggr, cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) btORM := bridges.NewORM(db, lggr, cfg.Database()) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayerExtenders) - jobORM := job.NewORM(db, legacyChains, orm, btORM, keyStore, lggr, cfg.Database()) + jobORM := job.NewORM(db, orm, btORM, keyStore, lggr, cfg.Database()) jb := &job.Job{ Type: job.Cron, diff --git a/core/services/directrequest/delegate.go b/core/services/directrequest/delegate.go index 75cebb7a74a..a21029ea177 100644 --- a/core/services/directrequest/delegate.go +++ b/core/services/directrequest/delegate.go @@ -9,13 +9,14 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/operator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" @@ -29,7 +30,7 @@ type ( pipelineRunner pipeline.Runner pipelineORM pipeline.ORM chHeads chan *evmtypes.Head - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer mailMon *utils.MailboxMonitor } @@ -45,7 +46,7 @@ func NewDelegate( logger logger.Logger, pipelineRunner pipeline.Runner, pipelineORM pipeline.ORM, - legacyChains evm.LegacyChainContainer, + legacyChains legacyevm.LegacyChainContainer, mailMon *utils.MailboxMonitor, ) *Delegate { return &Delegate{ @@ -76,7 +77,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { if err != nil { return nil, err } - concreteSpec := job.LoadEnvConfigVarsDR(chain.Config().EVM(), *jb.DirectRequestSpec) + concreteSpec := job.SetDRMinIncomingConfirmations(chain.Config().EVM().MinIncomingConfirmations(), *jb.DirectRequestSpec) oracle, err := operator_wrapper.NewOperator(concreteSpec.ContractAddress.Address(), chain.Client()) if err != nil { @@ -119,6 +120,7 @@ var ( ) type listener struct { + services.StateMachine logger logger.Logger config Config logBroadcaster log.Broadcaster @@ -127,7 +129,7 @@ type listener struct { pipelineORM pipeline.ORM mailMon *utils.MailboxMonitor job job.Job - runs sync.Map // map[string]utils.StopChan + runs sync.Map // map[string]services.StopChan shutdownWaitGroup sync.WaitGroup mbOracleRequests *utils.Mailbox[log.Broadcast] mbOracleCancelRequests *utils.Mailbox[log.Broadcast] @@ -135,7 +137,6 @@ type listener struct { requesters models.AddressCollection minContractPayment *assets.Link chStop chan struct{} - utils.StartStopOnce } func (l *listener) HealthReport() map[string]error { @@ -177,7 +178,7 @@ func (l *listener) Start(context.Context) error { func (l *listener) Close() error { return l.StopOnce("DirectRequestListener", func() error { l.runs.Range(func(key, runCloserChannelIf interface{}) bool { - runCloserChannel := runCloserChannelIf.(utils.StopChan) + runCloserChannel := runCloserChannelIf.(services.StopChan) close(runCloserChannel) return true }) @@ -337,10 +338,10 @@ func (l *listener) handleOracleRequest(request *operator_wrapper.OperatorOracleR meta := make(map[string]interface{}) meta["oracleRequest"] = oracleRequestToMap(request) - runCloserChannel := make(utils.StopChan) + runCloserChannel := make(services.StopChan) runCloserChannelIf, loaded := l.runs.LoadOrStore(formatRequestId(request.RequestId), runCloserChannel) if loaded { - runCloserChannel = runCloserChannelIf.(utils.StopChan) + runCloserChannel = runCloserChannelIf.(services.StopChan) } ctx, cancel := runCloserChannel.NewCtx() defer cancel() @@ -397,7 +398,7 @@ func (l *listener) allowRequester(requester common.Address) bool { func (l *listener) handleCancelOracleRequest(request *operator_wrapper.OperatorCancelOracleRequest, lb log.Broadcast) { runCloserChannelIf, loaded := l.runs.LoadAndDelete(formatRequestId(request.RequestId)) if loaded { - close(runCloserChannelIf.(utils.StopChan)) + close(runCloserChannelIf.(services.StopChan)) } l.markLogConsumed(lb) } diff --git a/core/services/directrequest/delegate_test.go b/core/services/directrequest/delegate_test.go index ffd78443cc2..56c28e57458 100644 --- a/core/services/directrequest/delegate_test.go +++ b/core/services/directrequest/delegate_test.go @@ -13,14 +13,14 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" log_mocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log/mocks" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/operator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -88,8 +88,8 @@ func NewDirectRequestUniverseWithConfig(t *testing.T, cfg chainlink.GeneralConfi lggr := logger.TestLogger(t) orm := pipeline.NewORM(db, lggr, cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) btORM := bridges.NewORM(db, lggr, cfg.Database()) + jobORM := job.NewORM(db, orm, btORM, keyStore, lggr, cfg.Database()) legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jobORM := job.NewORM(db, legacyChains, orm, btORM, keyStore, lggr, cfg.Database()) delegate := directrequest.NewDelegate(lggr, runner, orm, legacyChains, mailMon) jb := cltest.MakeDirectRequestJobSpec(t) diff --git a/core/services/directrequest/validate.go b/core/services/directrequest/validate.go index 95ea9b60232..bc31f09b685 100644 --- a/core/services/directrequest/validate.go +++ b/core/services/directrequest/validate.go @@ -4,7 +4,7 @@ import ( "github.com/pelletier/go-toml" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" diff --git a/core/services/feeds/orm.go b/core/services/feeds/orm.go index 30b6ad632a6..24ed7b8b369 100644 --- a/core/services/feeds/orm.go +++ b/core/services/feeds/orm.go @@ -9,7 +9,7 @@ import ( "github.com/lib/pq" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" diff --git a/core/services/feeds/orm_test.go b/core/services/feeds/orm_test.go index fab9d39a265..02b9e24739c 100644 --- a/core/services/feeds/orm_test.go +++ b/core/services/feeds/orm_test.go @@ -10,11 +10,11 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -1656,8 +1656,7 @@ func createJob(t *testing.T, db *sqlx.DB, externalJobID uuid.UUID) *job.Job { bridgeORM = bridges.NewORM(db, lggr, config.Database()) relayExtenders = evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) ) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := job.NewORM(db, legacyChains, pipelineORM, bridgeORM, keyStore, lggr, config.Database()) + orm := job.NewORM(db, pipelineORM, bridgeORM, keyStore, lggr, config.Database()) require.NoError(t, keyStore.OCR().Add(cltest.DefaultOCRKey)) require.NoError(t, keyStore.P2P().Add(cltest.DefaultP2PKey)) @@ -1667,6 +1666,7 @@ func createJob(t *testing.T, db *sqlx.DB, externalJobID uuid.UUID) *job.Job { _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) + legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) jb, err := ocr.ValidatedOracleSpecToml(legacyChains, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: externalJobID.String(), diff --git a/core/services/feeds/service.go b/core/services/feeds/service.go index 4c3647fde49..ea6e6cae5ab 100644 --- a/core/services/feeds/service.go +++ b/core/services/feeds/service.go @@ -15,9 +15,11 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" pb "github.com/smartcontractkit/chainlink/v2/core/services/feeds/proto" "github.com/smartcontractkit/chainlink/v2/core/services/fluxmonitorv2" @@ -98,7 +100,7 @@ type Service interface { } type service struct { - utils.StartStopOnce + services.StateMachine orm ORM jobORM job.ORM @@ -113,7 +115,7 @@ type service struct { ocrCfg OCRConfig ocr2cfg OCR2Config connMgr ConnectionsManager - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer lggr logger.Logger version string } @@ -130,7 +132,7 @@ func NewService( ocrCfg OCRConfig, ocr2Cfg OCR2Config, dbCfg pg.QConfig, - legacyChains evm.LegacyChainContainer, + legacyChains legacyevm.LegacyChainContainer, lggr logger.Logger, version string, ) *service { @@ -184,7 +186,7 @@ func (s *service) RegisterManager(ctx context.Context, params RegisterManagerPar } var id int64 - q := s.q.WithOpts(pg.WithParentCtx(context.Background())) + q := s.q.WithOpts(pg.WithParentCtx(ctx)) err = q.Transaction(func(tx pg.Queryer) error { var txerr error diff --git a/core/services/feeds/service_test.go b/core/services/feeds/service_test.go index 744c5d14702..d811a4461fd 100644 --- a/core/services/feeds/service_test.go +++ b/core/services/feeds/service_test.go @@ -11,11 +11,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -149,7 +149,7 @@ type TestService struct { p2pKeystore *ksmocks.P2P ocr1Keystore *ksmocks.OCR ocr2Keystore *ksmocks.OCR2 - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer } func setupTestService(t *testing.T) *TestService { diff --git a/core/services/fluxmonitorv2/config.go b/core/services/fluxmonitorv2/config.go index 5e5e56e768b..2680f30a777 100644 --- a/core/services/fluxmonitorv2/config.go +++ b/core/services/fluxmonitorv2/config.go @@ -3,7 +3,7 @@ package fluxmonitorv2 import ( "time" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/store/models" ) diff --git a/core/services/fluxmonitorv2/delegate.go b/core/services/fluxmonitorv2/delegate.go index d380122f715..99e2b688f5d 100644 --- a/core/services/fluxmonitorv2/delegate.go +++ b/core/services/fluxmonitorv2/delegate.go @@ -3,11 +3,11 @@ package fluxmonitorv2 import ( "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" @@ -22,7 +22,7 @@ type Delegate struct { jobORM job.ORM pipelineORM pipeline.ORM pipelineRunner pipeline.Runner - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer lggr logger.Logger } @@ -35,7 +35,7 @@ func NewDelegate( pipelineORM pipeline.ORM, pipelineRunner pipeline.Runner, db *sqlx.DB, - legacyChains evm.LegacyChainContainer, + legacyChains legacyevm.LegacyChainContainer, lggr logger.Logger, ) *Delegate { return &Delegate{ diff --git a/core/services/fluxmonitorv2/flux_monitor.go b/core/services/fluxmonitorv2/flux_monitor.go index fe8a22d4177..79dd44c8014 100644 --- a/core/services/fluxmonitorv2/flux_monitor.go +++ b/core/services/fluxmonitorv2/flux_monitor.go @@ -13,7 +13,9 @@ import ( "github.com/pkg/errors" "github.com/shopspring/decimal" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/bridges" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" @@ -55,6 +57,7 @@ const DefaultHibernationPollPeriod = 24 * time.Hour // FluxMonitor polls external price adapters via HTTP to check for price swings. type FluxMonitor struct { + services.StateMachine contractAddress common.Address oracleAddress common.Address jobSpec job.Job @@ -80,8 +83,7 @@ type FluxMonitor struct { backlog *utils.BoundedPriorityQueue[log.Broadcast] chProcessLogs chan struct{} - utils.StartStopOnce - chStop chan struct{} + chStop services.StopChan waitOnStop chan struct{} } @@ -134,9 +136,8 @@ func NewFluxMonitor( PriorityAnswerUpdatedLog: 1, PriorityFlagChangedLog: 2, }), - StartStopOnce: utils.StartStopOnce{}, chProcessLogs: make(chan struct{}, 1), - chStop: make(chan struct{}), + chStop: make(services.StopChan), waitOnStop: make(chan struct{}), } @@ -587,7 +588,7 @@ func (fm *FluxMonitor) respondToAnswerUpdatedLog(log flux_aggregator_wrapper.Flu // need to poll and submit an answer to the contract regardless of the deviation. func (fm *FluxMonitor) respondToNewRoundLog(log flux_aggregator_wrapper.FluxAggregatorNewRound, lb log.Broadcast) { started := time.Now() - ctx, cancel := utils.StopChan(fm.chStop).NewCtx() + ctx, cancel := fm.chStop.NewCtx() defer cancel() newRoundLogger := fm.logger.With( @@ -813,7 +814,7 @@ func (fm *FluxMonitor) checkEligibilityAndAggregatorFunding(roundState flux_aggr func (fm *FluxMonitor) pollIfEligible(pollReq PollRequestType, deviationChecker *DeviationChecker, broadcast log.Broadcast) { started := time.Now() - ctx, cancel := utils.StopChan(fm.chStop).NewCtx() + ctx, cancel := fm.chStop.NewCtx() defer cancel() l := fm.logger.With( diff --git a/core/services/fluxmonitorv2/flux_monitor_test.go b/core/services/fluxmonitorv2/flux_monitor_test.go index 83ffee8ac57..1a14fb8bd0a 100644 --- a/core/services/fluxmonitorv2/flux_monitor_test.go +++ b/core/services/fluxmonitorv2/flux_monitor_test.go @@ -3,7 +3,6 @@ package fluxmonitorv2_test import ( "fmt" "math/big" - "strings" "testing" "time" @@ -19,20 +18,19 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink-common/pkg/assets" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" logmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/flux_aggregator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" corenull "github.com/smartcontractkit/chainlink/v2/core/null" @@ -284,8 +282,8 @@ func setupStoreWithKey(t *testing.T) (*sqlx.DB, common.Address) { } // setupStoreWithKey setups a new store and adds a key to the keystore -func setupFullDBWithKey(t *testing.T, name string) (*sqlx.DB, common.Address) { - cfg, db := heavyweight.FullTestDBV2(t, name, nil) +func setupFullDBWithKey(t *testing.T) (*sqlx.DB, common.Address) { + cfg, db := heavyweight.FullTestDBV2(t, nil) ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() _, nodeAddr := cltest.MustInsertRandomKey(t, ethKeyStore) @@ -906,18 +904,8 @@ func TestFluxMonitor_HibernationTickerFiresMultipleTimes(t *testing.T) { g.Eventually(func() int { return len(pollOccured) }, testutils.WaitTimeout(t)).Should(gomega.Equal(3)) } -// chainlink_test_TestFluxMonitor_HibernationIsEnteredAndRetryTickerStopped -// 63 bytes is max and chainlink_test_ takes up 15, plus 4 for a random hex suffix. -func dbName(s string) string { - diff := len(cmd.TestDBNamePrefix) + len("_FFF") - if len(s) <= diff { - return strings.ReplaceAll(strings.ToLower(s), "/", "") - } - return strings.ReplaceAll(strings.ToLower(s[len(s)-diff:]), "/", "") -} - func TestFluxMonitor_HibernationIsEnteredAndRetryTickerStopped(t *testing.T) { - db, nodeAddr := setupFullDBWithKey(t, "hibernation") + db, nodeAddr := setupFullDBWithKey(t) oracles := []common.Address{nodeAddr, testutils.NewAddress()} const ( diff --git a/core/services/fluxmonitorv2/integrations_test.go b/core/services/fluxmonitorv2/integrations_test.go index 2c45ed5ad89..729bcd76eba 100644 --- a/core/services/fluxmonitorv2/integrations_test.go +++ b/core/services/fluxmonitorv2/integrations_test.go @@ -24,10 +24,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/flags_wrapper" faw "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/flux_aggregator_wrapper" @@ -206,7 +206,7 @@ func startApplication( fa fluxAggregatorUniverse, overrides func(c *chainlink.Config, s *chainlink.Secrets), ) *cltest.TestApplication { - config, _ := heavyweight.FullTestDBV2(t, dbName(t.Name()), overrides) + config, _ := heavyweight.FullTestDBV2(t, overrides) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, fa.backend, fa.key) 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 61395e8708a..f85ab146c7e 100644 --- a/core/services/fluxmonitorv2/orm.go +++ b/core/services/fluxmonitorv2/orm.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" diff --git a/core/services/fluxmonitorv2/orm_test.go b/core/services/fluxmonitorv2/orm_test.go index 3bebc150c82..6e06a1e65b8 100644 --- a/core/services/fluxmonitorv2/orm_test.go +++ b/core/services/fluxmonitorv2/orm_test.go @@ -16,14 +16,12 @@ import ( txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/fluxmonitorv2" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -96,11 +94,9 @@ func TestORM_UpdateFluxMonitorRoundStats(t *testing.T) { pipelineORM := pipeline.NewORM(db, lggr, cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) bridgeORM := bridges.NewORM(db, lggr, cfg.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{GeneralConfig: cfg, DB: db, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) // Instantiate a real job ORM because we need to create a job to satisfy // a check in pipeline.CreateRun - jobORM := job.NewORM(db, legacyChains, pipelineORM, bridgeORM, keyStore, lggr, cfg.Database()) + jobORM := job.NewORM(db, pipelineORM, bridgeORM, keyStore, lggr, cfg.Database()) orm := newORM(t, db, cfg.Database(), nil) address := testutils.NewAddress() diff --git a/core/services/fluxmonitorv2/payment_checker.go b/core/services/fluxmonitorv2/payment_checker.go index 6b11ec13433..e4cc40c96a3 100644 --- a/core/services/fluxmonitorv2/payment_checker.go +++ b/core/services/fluxmonitorv2/payment_checker.go @@ -3,7 +3,7 @@ package fluxmonitorv2 import ( "math/big" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" ) // MinFundedRounds defines the minimum number of rounds that needs to be paid diff --git a/core/services/fluxmonitorv2/payment_checker_test.go b/core/services/fluxmonitorv2/payment_checker_test.go index c987fdf3604..baec5642339 100644 --- a/core/services/fluxmonitorv2/payment_checker_test.go +++ b/core/services/fluxmonitorv2/payment_checker_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/services/fluxmonitorv2" ) diff --git a/core/services/fluxmonitorv2/validate_test.go b/core/services/fluxmonitorv2/validate_test.go index 78dda2e3d31..94dc8b6b709 100644 --- a/core/services/fluxmonitorv2/validate_test.go +++ b/core/services/fluxmonitorv2/validate_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils/tomlutils" diff --git a/core/services/functions/connector_handler.go b/core/services/functions/connector_handler.go index c32bf56f0cd..5496bbdefc1 100644 --- a/core/services/functions/connector_handler.go +++ b/core/services/functions/connector_handler.go @@ -1,14 +1,22 @@ package functions import ( + "bytes" "context" "crypto/ecdsa" "encoding/json" "fmt" + "sync" + "time" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/v2/core/assets" + ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/common" @@ -16,43 +24,61 @@ import ( hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" "github.com/smartcontractkit/chainlink/v2/core/services/s4" - "github.com/smartcontractkit/chainlink/v2/core/utils" - - ethCommon "github.com/ethereum/go-ethereum/common" ) type functionsConnectorHandler struct { - utils.StartStopOnce + services.StateMachine - connector connector.GatewayConnector - signerKey *ecdsa.PrivateKey - nodeAddress string - storage s4.Storage - allowlist functions.OnchainAllowlist - rateLimiter *hc.RateLimiter - subscriptions functions.OnchainSubscriptions - minimumBalance assets.Link - lggr logger.Logger + connector connector.GatewayConnector + signerKey *ecdsa.PrivateKey + nodeAddress string + storage s4.Storage + allowlist functions.OnchainAllowlist + rateLimiter *hc.RateLimiter + subscriptions functions.OnchainSubscriptions + minimumBalance assets.Link + listener FunctionsListener + offchainTransmitter OffchainTransmitter + heartbeatRequests map[RequestID]*HeartbeatResponse + orderedRequests []RequestID + mu sync.Mutex + chStop services.StopChan + shutdownWaitGroup sync.WaitGroup + lggr logger.Logger } +const ( + HeartbeatRequestTimeoutSec = 240 + HeartbeatCacheSize = 1000 +) + var ( _ connector.Signer = &functionsConnectorHandler{} _ connector.GatewayConnectorHandler = &functionsConnectorHandler{} ) -func NewFunctionsConnectorHandler(nodeAddress string, signerKey *ecdsa.PrivateKey, storage s4.Storage, allowlist functions.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions functions.OnchainSubscriptions, minimumBalance assets.Link, lggr logger.Logger) (*functionsConnectorHandler, error) { - if signerKey == nil || storage == nil || allowlist == nil || rateLimiter == nil || subscriptions == nil { - return nil, fmt.Errorf("signerKey, storage, allowlist, rateLimiter and subscriptions must be non-nil") +// internal request ID is a hash of (sender, requestID) +func InternalId(sender []byte, requestId []byte) RequestID { + return RequestID(crypto.Keccak256Hash(append(sender, requestId...)).Bytes()) +} + +func NewFunctionsConnectorHandler(nodeAddress string, signerKey *ecdsa.PrivateKey, storage s4.Storage, allowlist functions.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions functions.OnchainSubscriptions, listener FunctionsListener, offchainTransmitter OffchainTransmitter, minimumBalance assets.Link, lggr logger.Logger) (*functionsConnectorHandler, error) { + if signerKey == nil || storage == nil || allowlist == nil || rateLimiter == nil || subscriptions == nil || listener == nil || offchainTransmitter == nil { + return nil, fmt.Errorf("all dependencies must be non-nil") } return &functionsConnectorHandler{ - nodeAddress: nodeAddress, - signerKey: signerKey, - storage: storage, - allowlist: allowlist, - rateLimiter: rateLimiter, - subscriptions: subscriptions, - minimumBalance: minimumBalance, - lggr: lggr.Named("FunctionsConnectorHandler"), + nodeAddress: nodeAddress, + signerKey: signerKey, + storage: storage, + allowlist: allowlist, + rateLimiter: rateLimiter, + subscriptions: subscriptions, + minimumBalance: minimumBalance, + listener: listener, + offchainTransmitter: offchainTransmitter, + heartbeatRequests: make(map[RequestID]*HeartbeatResponse), + chStop: make(services.StopChan), + lggr: lggr.Named("FunctionsConnectorHandler"), }, nil } @@ -75,18 +101,24 @@ func (h *functionsConnectorHandler) HandleGatewayMessage(ctx context.Context, ga h.lggr.Errorw("request rate-limited", "id", gatewayId, "address", fromAddr) return } - if balance, err := h.subscriptions.GetMaxUserBalance(fromAddr); err != nil || balance.Cmp(h.minimumBalance.ToInt()) < 0 { - h.lggr.Errorw("user subscription has insufficient balance", "id", gatewayId, "address", fromAddr, "balance", balance, "minBalance", h.minimumBalance) - return - } - h.lggr.Debugw("handling gateway request", "id", gatewayId, "method", body.Method) switch body.Method { case functions.MethodSecretsList: h.handleSecretsList(ctx, gatewayId, body, fromAddr) case functions.MethodSecretsSet: + if balance, err := h.subscriptions.GetMaxUserBalance(fromAddr); err != nil || balance.Cmp(h.minimumBalance.ToInt()) < 0 { + h.lggr.Errorw("user subscription has insufficient balance", "id", gatewayId, "address", fromAddr, "balance", balance, "minBalance", h.minimumBalance) + response := functions.ResponseBase{ + Success: false, + ErrorMessage: "user subscription has insufficient balance", + } + h.sendResponseAndLog(ctx, gatewayId, body, response) + return + } h.handleSecretsSet(ctx, gatewayId, body, fromAddr) + case functions.MethodHeartbeat: + h.handleHeartbeat(ctx, gatewayId, body, fromAddr) default: h.lggr.Errorw("unsupported method", "id", gatewayId, "method", body.Method) } @@ -97,14 +129,21 @@ func (h *functionsConnectorHandler) Start(ctx context.Context) error { if err := h.allowlist.Start(ctx); err != nil { return err } - return h.subscriptions.Start(ctx) + if err := h.subscriptions.Start(ctx); err != nil { + return err + } + h.shutdownWaitGroup.Add(1) + go h.reportLoop() + return nil }) } func (h *functionsConnectorHandler) Close() error { return h.StopOnce("FunctionsConnectorHandler", func() (err error) { + close(h.chStop) err = multierr.Combine(err, h.allowlist.Close()) err = multierr.Combine(err, h.subscriptions.Close()) + h.shutdownWaitGroup.Wait() return }) } @@ -125,10 +164,7 @@ func (h *functionsConnectorHandler) handleSecretsList(ctx context.Context, gatew } else { response.ErrorMessage = fmt.Sprintf("Failed to list secrets: %v", err) } - - if err := h.sendResponse(ctx, gatewayId, body, response); err != nil { - h.lggr.Errorw("failed to send response to gateway", "id", gatewayId, "error", err) - } + h.sendResponseAndLog(ctx, gatewayId, body, response) } func (h *functionsConnectorHandler) handleSecretsSet(ctx context.Context, gatewayId string, body *api.MessageBody, fromAddr ethCommon.Address) { @@ -155,9 +191,121 @@ func (h *functionsConnectorHandler) handleSecretsSet(ctx context.Context, gatewa } else { response.ErrorMessage = fmt.Sprintf("Bad request to set secret: %v", err) } + h.sendResponseAndLog(ctx, gatewayId, body, response) +} + +func (h *functionsConnectorHandler) handleHeartbeat(ctx context.Context, gatewayId string, requestBody *api.MessageBody, fromAddr ethCommon.Address) { + var request *OffchainRequest + err := json.Unmarshal(requestBody.Payload, &request) + if err != nil { + h.sendResponseAndLog(ctx, gatewayId, requestBody, internalErrorResponse(fmt.Sprintf("failed to unmarshal request: %v", err))) + return + } + if !bytes.Equal(request.RequestInitiator, fromAddr.Bytes()) { + h.sendResponseAndLog(ctx, gatewayId, requestBody, internalErrorResponse("RequestInitiator doesn't match sender")) + return + } + if !bytes.Equal(request.SubscriptionOwner, fromAddr.Bytes()) { + h.sendResponseAndLog(ctx, gatewayId, requestBody, internalErrorResponse("SubscriptionOwner doesn't match sender")) + return + } - if err := h.sendResponse(ctx, gatewayId, body, response); err != nil { + internalId := InternalId(fromAddr.Bytes(), request.RequestId) + request.RequestId = internalId[:] + h.lggr.Infow("handling offchain heartbeat", "messageId", requestBody.MessageId, "internalId", internalId, "sender", requestBody.Sender) + h.mu.Lock() + response, ok := h.heartbeatRequests[internalId] + if !ok { // new request + response = &HeartbeatResponse{ + Status: RequestStatePending, + ReceivedTs: uint64(time.Now().Unix()), + } + h.cacheNewRequestLocked(internalId, response) + h.shutdownWaitGroup.Add(1) + go h.handleOffchainRequest(request) + } + responseToSend := *response + h.mu.Unlock() + requestBody.Receiver = requestBody.Sender + h.sendResponseAndLog(ctx, gatewayId, requestBody, responseToSend) +} + +func internalErrorResponse(internalError string) HeartbeatResponse { + return HeartbeatResponse{ + Status: RequestStateInternalError, + InternalError: internalError, + } +} + +func (h *functionsConnectorHandler) handleOffchainRequest(request *OffchainRequest) { + defer h.shutdownWaitGroup.Done() + stopCtx, _ := h.chStop.NewCtx() + ctx, cancel := context.WithTimeout(stopCtx, time.Duration(HeartbeatRequestTimeoutSec)*time.Second) + defer cancel() + err := h.listener.HandleOffchainRequest(ctx, request) + if err != nil { + h.lggr.Errorw("internal error while processing", "id", request.RequestId, "error", err) + h.mu.Lock() + defer h.mu.Unlock() + state, ok := h.heartbeatRequests[RequestID(request.RequestId)] + if !ok { + h.lggr.Errorw("request unexpectedly disappeared from local cache", "id", request.RequestId) + return + } + state.CompletedTs = uint64(time.Now().Unix()) + state.Status = RequestStateInternalError + state.InternalError = err.Error() + } else { + // no error - results will be sent to OCR aggregation and returned via reportLoop() + h.lggr.Infow("request processed successfully, waiting for aggregation ...", "id", request.RequestId) + } +} + +// Listen to OCR reports passed from the plugin and process them against a local cache of requests. +func (h *functionsConnectorHandler) reportLoop() { + defer h.shutdownWaitGroup.Done() + for { + select { + case report := <-h.offchainTransmitter.ReportChannel(): + h.lggr.Infow("received report", "requestId", report.RequestId, "resultLen", len(report.Result), "errorLen", len(report.Error)) + if len(report.RequestId) != RequestIDLength { + h.lggr.Errorw("report has invalid requestId", "requestId", report.RequestId) + continue + } + h.mu.Lock() + cachedResponse, ok := h.heartbeatRequests[RequestID(report.RequestId)] + if !ok { + h.lggr.Infow("received report for unknown request, caching it", "id", report.RequestId) + cachedResponse = &HeartbeatResponse{} + h.cacheNewRequestLocked(RequestID(report.RequestId), cachedResponse) + } + cachedResponse.CompletedTs = uint64(time.Now().Unix()) + cachedResponse.Status = RequestStateComplete + cachedResponse.Response = report + h.mu.Unlock() + case <-h.chStop: + h.lggr.Info("exiting reportLoop") + return + } + } +} + +func (h *functionsConnectorHandler) cacheNewRequestLocked(requestId RequestID, response *HeartbeatResponse) { + // remove oldest requests + for len(h.orderedRequests) >= HeartbeatCacheSize { + delete(h.heartbeatRequests, h.orderedRequests[0]) + h.orderedRequests = h.orderedRequests[1:] + } + h.heartbeatRequests[requestId] = response + h.orderedRequests = append(h.orderedRequests, requestId) +} + +func (h *functionsConnectorHandler) sendResponseAndLog(ctx context.Context, gatewayId string, requestBody *api.MessageBody, payload any) { + err := h.sendResponse(ctx, gatewayId, requestBody, payload) + if err != nil { h.lggr.Errorw("failed to send response to gateway", "id", gatewayId, "error", err) + } else { + h.lggr.Debugw("sent to gateway", "id", gatewayId, "messageId", requestBody.MessageId, "donId", requestBody.DonId, "method", requestBody.Method) } } @@ -179,10 +327,5 @@ func (h *functionsConnectorHandler) sendResponse(ctx context.Context, gatewayId if err = msg.Sign(h.signerKey); err != nil { return err } - - err = h.connector.SendToGateway(ctx, gatewayId, msg) - if err == nil { - h.lggr.Debugw("sent to gateway", "id", gatewayId, "messageId", requestBody.MessageId, "donId", requestBody.DonId, "method", requestBody.Method) - } - return err + return h.connector.SendToGateway(ctx, gatewayId, msg) } diff --git a/core/services/functions/connector_handler_test.go b/core/services/functions/connector_handler_test.go index bb3e2acbabd..409f9cdcc56 100644 --- a/core/services/functions/connector_handler_test.go +++ b/core/services/functions/connector_handler_test.go @@ -1,16 +1,22 @@ package functions_test import ( + "crypto/rand" "encoding/base64" "encoding/json" "errors" "math/big" "testing" + "time" - "github.com/smartcontractkit/chainlink/v2/core/assets" + geth_common "github.com/ethereum/go-ethereum/common" + "github.com/onsi/gomega" + + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/functions" + sfmocks "github.com/smartcontractkit/chainlink/v2/core/services/functions/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/common" gcmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector/mocks" @@ -24,6 +30,31 @@ import ( "github.com/stretchr/testify/require" ) +func newOffchainRequest(t *testing.T, sender []byte) (*api.Message, functions.RequestID) { + requestId := make([]byte, 32) + _, err := rand.Read(requestId) + require.NoError(t, err) + request := &functions.OffchainRequest{ + RequestId: requestId, + RequestInitiator: sender, + SubscriptionId: 1, + SubscriptionOwner: sender, + } + + internalId := functions.InternalId(request.RequestInitiator, request.RequestId) + req, err := json.Marshal(request) + require.NoError(t, err) + msg := &api.Message{ + Body: api.MessageBody{ + DonId: "fun4", + MessageId: "1", + Method: "heartbeat", + Payload: req, + }, + } + return msg, internalId +} + func TestFunctionsConnectorHandler(t *testing.T) { t.Parallel() @@ -34,12 +65,16 @@ func TestFunctionsConnectorHandler(t *testing.T) { allowlist := gfmocks.NewOnchainAllowlist(t) rateLimiter, err := hc.NewRateLimiter(hc.RateLimiterConfig{GlobalRPS: 100.0, GlobalBurst: 100, PerSenderRPS: 100.0, PerSenderBurst: 100}) subscriptions := gfmocks.NewOnchainSubscriptions(t) + reportCh := make(chan *functions.OffchainResponse) + offchainTransmitter := sfmocks.NewOffchainTransmitter(t) + offchainTransmitter.On("ReportChannel", mock.Anything).Return(reportCh) + listener := sfmocks.NewFunctionsListener(t) require.NoError(t, err) allowlist.On("Start", mock.Anything).Return(nil) allowlist.On("Close", mock.Anything).Return(nil) subscriptions.On("Start", mock.Anything).Return(nil) subscriptions.On("Close", mock.Anything).Return(nil) - handler, err := functions.NewFunctionsConnectorHandler(addr.Hex(), privateKey, storage, allowlist, rateLimiter, subscriptions, *assets.NewLinkFromJuels(0), logger) + handler, err := functions.NewFunctionsConnectorHandler(addr.Hex(), privateKey, storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, *assets.NewLinkFromJuels(100), logger) require.NoError(t, err) handler.SetConnector(connector) @@ -78,7 +113,6 @@ func TestFunctionsConnectorHandler(t *testing.T) { } storage.On("List", ctx, addr).Return(snapshot, nil).Once() allowlist.On("Allow", addr).Return(true).Once() - subscriptions.On("GetMaxUserBalance", mock.Anything).Return(big.NewInt(100), nil) connector.On("SendToGateway", ctx, "gw1", mock.Anything).Run(func(args mock.Arguments) { msg, ok := args[2].(*api.Message) require.True(t, ok) @@ -135,7 +169,7 @@ func TestFunctionsConnectorHandler(t *testing.T) { storage.On("Put", ctx, &key, &record, signature).Return(nil).Once() allowlist.On("Allow", addr).Return(true).Once() - subscriptions.On("GetMaxUserBalance", mock.Anything).Return(big.NewInt(100), nil) + subscriptions.On("GetMaxUserBalance", mock.Anything).Return(big.NewInt(100), nil).Once() connector.On("SendToGateway", ctx, "gw1", mock.Anything).Run(func(args mock.Arguments) { msg, ok := args[2].(*api.Message) require.True(t, ok) @@ -148,6 +182,7 @@ func TestFunctionsConnectorHandler(t *testing.T) { t.Run("orm error", func(t *testing.T) { storage.On("Put", ctx, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("boom")).Once() allowlist.On("Allow", addr).Return(true).Once() + subscriptions.On("GetMaxUserBalance", mock.Anything).Return(big.NewInt(100), nil).Once() connector.On("SendToGateway", ctx, "gw1", mock.Anything).Run(func(args mock.Arguments) { msg, ok := args[2].(*api.Message) require.True(t, ok) @@ -163,6 +198,7 @@ func TestFunctionsConnectorHandler(t *testing.T) { require.NoError(t, msg.Sign(privateKey)) storage.On("Put", ctx, mock.Anything, mock.Anything, mock.Anything).Return(s4.ErrWrongSignature).Once() allowlist.On("Allow", addr).Return(true).Once() + subscriptions.On("GetMaxUserBalance", mock.Anything).Return(big.NewInt(100), nil).Once() connector.On("SendToGateway", ctx, "gw1", mock.Anything).Run(func(args mock.Arguments) { msg, ok := args[2].(*api.Message) require.True(t, ok) @@ -177,6 +213,7 @@ func TestFunctionsConnectorHandler(t *testing.T) { msg.Body.Payload = json.RawMessage(`{sdfgdfgoscsicosd:sdf:::sdf ::; xx}`) require.NoError(t, msg.Sign(privateKey)) allowlist.On("Allow", addr).Return(true).Once() + subscriptions.On("GetMaxUserBalance", mock.Anything).Return(big.NewInt(100), nil).Once() connector.On("SendToGateway", ctx, "gw1", mock.Anything).Run(func(args mock.Arguments) { msg, ok := args[2].(*api.Message) require.True(t, ok) @@ -186,6 +223,19 @@ func TestFunctionsConnectorHandler(t *testing.T) { handler.HandleGatewayMessage(ctx, "gw1", &msg) }) + + t.Run("insufficient balance", func(t *testing.T) { + allowlist.On("Allow", addr).Return(true).Once() + subscriptions.On("GetMaxUserBalance", mock.Anything).Return(big.NewInt(0), nil).Once() + connector.On("SendToGateway", ctx, "gw1", mock.Anything).Run(func(args mock.Arguments) { + msg, ok := args[2].(*api.Message) + require.True(t, ok) + require.Equal(t, `{"success":false,"error_message":"user subscription has insufficient balance"}`, string(msg.Body.Payload)) + + }).Return(nil).Once() + + handler.HandleGatewayMessage(ctx, "gw1", &msg) + }) }) t.Run("unsupported method", func(t *testing.T) { @@ -204,4 +254,82 @@ func TestFunctionsConnectorHandler(t *testing.T) { handler.HandleGatewayMessage(testutils.Context(t), "gw1", &msg) }) }) + + t.Run("heartbeat success", func(t *testing.T) { + ctx := testutils.Context(t) + msg, internalId := newOffchainRequest(t, addr.Bytes()) + require.NoError(t, msg.Sign(privateKey)) + + // first call to trigger the request + var response functions.HeartbeatResponse + allowlist.On("Allow", addr).Return(true).Once() + listener.On("HandleOffchainRequest", mock.Anything, mock.Anything).Return(nil).Once() + connector.On("SendToGateway", mock.Anything, "gw1", mock.Anything).Run(func(args mock.Arguments) { + respMsg, ok := args[2].(*api.Message) + require.True(t, ok) + require.NoError(t, json.Unmarshal(respMsg.Body.Payload, &response)) + require.Equal(t, functions.RequestStatePending, response.Status) + }).Return(nil).Once() + handler.HandleGatewayMessage(ctx, "gw1", msg) + + // async response computation + reportCh <- &functions.OffchainResponse{ + RequestId: internalId[:], + Result: []byte("ok!"), + } + reportCh <- &functions.OffchainResponse{} // sending second item to make sure the first one got processed + + // second call to collect the response + allowlist.On("Allow", addr).Return(true).Once() + connector.On("SendToGateway", mock.Anything, "gw1", mock.Anything).Run(func(args mock.Arguments) { + respMsg, ok := args[2].(*api.Message) + require.True(t, ok) + require.NoError(t, json.Unmarshal(respMsg.Body.Payload, &response)) + require.Equal(t, functions.RequestStateComplete, response.Status) + }).Return(nil).Once() + handler.HandleGatewayMessage(ctx, "gw1", msg) + }) + + t.Run("heartbeat internal error", func(t *testing.T) { + ctx := testutils.Context(t) + msg, _ := newOffchainRequest(t, addr.Bytes()) + require.NoError(t, msg.Sign(privateKey)) + + // first call to trigger the request + var response functions.HeartbeatResponse + allowlist.On("Allow", addr).Return(true).Once() + listener.On("HandleOffchainRequest", mock.Anything, mock.Anything).Return(errors.New("boom")).Once() + connector.On("SendToGateway", mock.Anything, "gw1", mock.Anything).Return(nil).Once() + handler.HandleGatewayMessage(ctx, "gw1", msg) + + // collect the response - should eventually result in an internal error + gomega.NewGomegaWithT(t).Eventually(func() bool { + returnedState := 0 + allowlist.On("Allow", addr).Return(true).Once() + connector.On("SendToGateway", mock.Anything, "gw1", mock.Anything).Run(func(args mock.Arguments) { + respMsg, ok := args[2].(*api.Message) + require.True(t, ok) + require.NoError(t, json.Unmarshal(respMsg.Body.Payload, &response)) + returnedState = response.Status + }).Return(nil).Once() + handler.HandleGatewayMessage(ctx, "gw1", msg) + return returnedState == functions.RequestStateInternalError + }, testutils.WaitTimeout(t), 50*time.Millisecond).Should(gomega.BeTrue()) + }) + + t.Run("heartbeat sender address doesn't match", func(t *testing.T) { + ctx := testutils.Context(t) + msg, _ := newOffchainRequest(t, geth_common.BytesToAddress([]byte("0x1234")).Bytes()) + require.NoError(t, msg.Sign(privateKey)) + + var response functions.HeartbeatResponse + allowlist.On("Allow", addr).Return(true).Once() + connector.On("SendToGateway", mock.Anything, "gw1", mock.Anything).Run(func(args mock.Arguments) { + respMsg, ok := args[2].(*api.Message) + require.True(t, ok) + require.NoError(t, json.Unmarshal(respMsg.Body.Payload, &response)) + require.Equal(t, functions.RequestStateInternalError, response.Status) + }).Return(nil).Once() + handler.HandleGatewayMessage(ctx, "gw1", msg) + }) } diff --git a/core/services/functions/listener.go b/core/services/functions/listener.go index b76eb0a1c05..65c364adb7c 100644 --- a/core/services/functions/listener.go +++ b/core/services/functions/listener.go @@ -13,6 +13,9 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "google.golang.org/protobuf/proto" + "github.com/smartcontractkit/libocr/commontypes" + + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/cbor" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -23,14 +26,9 @@ import ( evmrelayTypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" "github.com/smartcontractkit/chainlink/v2/core/services/s4" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" - "github.com/smartcontractkit/chainlink/v2/core/utils" - - "github.com/smartcontractkit/libocr/commontypes" ) var ( - _ job.ServiceCtx = &FunctionsListener{} - sizeBuckets = []float64{ 1024, 1024 * 4, @@ -111,12 +109,22 @@ const ( DefaultPruneCheckFrequencySec uint32 = 60 * 10 DefaultPruneBatchSize uint32 = 500 + // Used in place of OnchainMetadata for all offchain requests. + OffchainRequestMarker string = "OFFCHAIN_REQUEST" + FlagCBORMaxSize uint32 = 1 FlagSecretsMaxSize uint32 = 2 ) -type FunctionsListener struct { - utils.StartStopOnce +//go:generate mockery --quiet --name FunctionsListener --output ./mocks/ --case=underscore +type FunctionsListener interface { + job.ServiceCtx + + HandleOffchainRequest(ctx context.Context, request *OffchainRequest) error +} + +type functionsListener struct { + services.StateMachine client client.Client contractAddressHex string job job.Job @@ -134,11 +142,13 @@ type FunctionsListener struct { logPollerWrapper evmrelayTypes.LogPollerWrapper } -func (l *FunctionsListener) HealthReport() map[string]error { +var _ FunctionsListener = &functionsListener{} + +func (l *functionsListener) HealthReport() map[string]error { return map[string]error{l.Name(): l.Healthy()} } -func (l *FunctionsListener) Name() string { return l.logger.Name() } +func (l *functionsListener) Name() string { return l.logger.Name() } func formatRequestId(requestId [32]byte) string { return fmt.Sprintf("0x%x", requestId) @@ -156,8 +166,8 @@ func NewFunctionsListener( urlsMonEndpoint commontypes.MonitoringEndpoint, decryptor threshold.Decryptor, logPollerWrapper evmrelayTypes.LogPollerWrapper, -) *FunctionsListener { - return &FunctionsListener{ +) *functionsListener { + return &functionsListener{ client: client, contractAddressHex: contractAddressHex, job: job, @@ -174,7 +184,7 @@ func NewFunctionsListener( } // Start complies with job.Service -func (l *FunctionsListener) Start(context.Context) error { +func (l *functionsListener) Start(context.Context) error { return l.StartOnce("FunctionsListener", func() error { l.serviceContext, l.serviceCancel = context.WithCancel(context.Background()) @@ -201,7 +211,7 @@ func (l *FunctionsListener) Start(context.Context) error { } // Close complies with job.Service -func (l *FunctionsListener) Close() error { +func (l *functionsListener) Close() error { return l.StopOnce("FunctionsListener", func() error { l.serviceCancel() close(l.chStop) @@ -210,7 +220,7 @@ func (l *FunctionsListener) Close() error { }) } -func (l *FunctionsListener) processOracleEventsV1() { +func (l *functionsListener) processOracleEventsV1() { defer l.shutdownWaitGroup.Done() freqMillis := l.pluginConfig.ListenerEventsCheckFrequencyMillis if freqMillis == 0 { @@ -244,7 +254,7 @@ func (l *FunctionsListener) processOracleEventsV1() { } } -func (l *FunctionsListener) getNewHandlerContext() (context.Context, context.CancelFunc) { +func (l *functionsListener) getNewHandlerContext() (context.Context, context.CancelFunc) { timeoutSec := l.pluginConfig.ListenerEventHandlerTimeoutSec if timeoutSec == 0 { return context.WithCancel(l.serviceContext) @@ -252,7 +262,7 @@ func (l *FunctionsListener) getNewHandlerContext() (context.Context, context.Can return context.WithTimeout(l.serviceContext, time.Duration(timeoutSec)*time.Second) } -func (l *FunctionsListener) setError(ctx context.Context, requestId RequestID, errType ErrType, errBytes []byte) { +func (l *functionsListener) setError(ctx context.Context, requestId RequestID, errType ErrType, errBytes []byte) { if errType == INTERNAL_ERROR { promRequestInternalError.WithLabelValues(l.contractAddressHex).Inc() } else { @@ -264,7 +274,7 @@ func (l *FunctionsListener) setError(ctx context.Context, requestId RequestID, e } } -func (l *FunctionsListener) getMaxCBORsize(flags RequestFlags) uint32 { +func (l *functionsListener) getMaxCBORsize(flags RequestFlags) uint32 { idx := flags[FlagCBORMaxSize] if int(idx) >= len(l.pluginConfig.MaxRequestSizesList) { return l.pluginConfig.MaxRequestSizeBytes // deprecated @@ -272,7 +282,7 @@ func (l *FunctionsListener) getMaxCBORsize(flags RequestFlags) uint32 { return l.pluginConfig.MaxRequestSizesList[idx] } -func (l *FunctionsListener) getMaxSecretsSize(flags RequestFlags) uint32 { +func (l *functionsListener) getMaxSecretsSize(flags RequestFlags) uint32 { idx := flags[FlagSecretsMaxSize] if int(idx) >= len(l.pluginConfig.MaxSecretsSizesList) { return math.MaxUint32 // not enforced if not configured @@ -280,7 +290,48 @@ func (l *FunctionsListener) getMaxSecretsSize(flags RequestFlags) uint32 { return l.pluginConfig.MaxSecretsSizesList[idx] } -func (l *FunctionsListener) handleOracleRequestV1(request *evmrelayTypes.OracleRequest) { +func (l *functionsListener) HandleOffchainRequest(ctx context.Context, request *OffchainRequest) error { + if request == nil { + return errors.New("HandleOffchainRequest: received nil request") + } + if len(request.RequestId) != RequestIDLength { + return fmt.Errorf("HandleOffchainRequest: invalid request ID length %d", len(request.RequestId)) + } + if len(request.SubscriptionOwner) != common.AddressLength || len(request.RequestInitiator) != common.AddressLength { + return fmt.Errorf("HandleOffchainRequest: SubscriptionOwner and RequestInitiator must be set to valid addresses") + } + if request.Timestamp < uint64(time.Now().Unix()-int64(l.pluginConfig.RequestTimeoutSec)) { + return fmt.Errorf("HandleOffchainRequest: request timestamp is too old") + } + + var requestId RequestID + copy(requestId[:], request.RequestId[:32]) + subscriptionOwner := common.BytesToAddress(request.SubscriptionOwner) + senderAddr := common.BytesToAddress(request.RequestInitiator) + emptyTxHash := common.Hash{} + zeroCallbackGasLimit := uint32(0) + newReq := &Request{ + RequestID: requestId, + RequestTxHash: &emptyTxHash, + ReceivedAt: time.Now(), + Flags: []byte{}, + CallbackGasLimit: &zeroCallbackGasLimit, + // use sender address in place of coordinator contract to keep batches uniform + CoordinatorContractAddress: &senderAddr, + OnchainMetadata: []byte(OffchainRequestMarker), + } + if err := l.pluginORM.CreateRequest(newReq, pg.WithParentCtx(ctx)); err != nil { + if errors.Is(err, ErrDuplicateRequestID) { + l.logger.Warnw("HandleOffchainRequest: received duplicate request ID", "requestID", formatRequestId(requestId), "err", err) + } else { + l.logger.Errorw("HandleOffchainRequest: failed to create a DB entry for new request", "requestID", formatRequestId(requestId), "err", err) + } + return err + } + return l.handleRequest(ctx, requestId, request.SubscriptionId, subscriptionOwner, RequestFlags{}, &request.Data) +} + +func (l *functionsListener) handleOracleRequestV1(request *evmrelayTypes.OracleRequest) { defer l.shutdownWaitGroup.Done() l.logger.Infow("handleOracleRequestV1: oracle request v1 received", "requestID", formatRequestId(request.RequestId)) ctx, cancel := l.getNewHandlerContext() @@ -312,10 +363,13 @@ func (l *FunctionsListener) handleOracleRequestV1(request *evmrelayTypes.OracleR l.setError(ctx, request.RequestId, USER_ERROR, []byte(err.Error())) return } - l.handleRequest(ctx, request.RequestId, request.SubscriptionId, request.SubscriptionOwner, request.Flags, requestData) + err = l.handleRequest(ctx, request.RequestId, request.SubscriptionId, request.SubscriptionOwner, request.Flags, requestData) + if err != nil { + l.logger.Errorw("handleOracleRequestV1: error in handleRequest()", "requestID", formatRequestId(request.RequestId), "err", err) + } } -func (l *FunctionsListener) parseCBOR(requestId RequestID, cborData []byte, maxSizeBytes uint32) (*RequestData, error) { +func (l *functionsListener) parseCBOR(requestId RequestID, cborData []byte, maxSizeBytes uint32) (*RequestData, error) { if maxSizeBytes > 0 && uint32(len(cborData)) > maxSizeBytes { l.logger.Errorw("request too big", "requestID", formatRequestId(requestId), "requestSize", len(cborData), "maxRequestSize", maxSizeBytes) return nil, fmt.Errorf("request too big (max %d bytes)", maxSizeBytes) @@ -330,7 +384,8 @@ func (l *FunctionsListener) parseCBOR(requestId RequestID, cborData []byte, maxS return &requestData, nil } -func (l *FunctionsListener) handleRequest(ctx context.Context, requestID RequestID, subscriptionId uint64, subscriptionOwner common.Address, flags RequestFlags, requestData *RequestData) { +// Handle secret fetching/decryption and functions computation. Return error only for internal errors. +func (l *functionsListener) handleRequest(ctx context.Context, requestID RequestID, subscriptionId uint64, subscriptionOwner common.Address, flags RequestFlags, requestData *RequestData) error { startTime := time.Now() defer func() { duration := time.Since(startTime) @@ -343,26 +398,26 @@ func (l *FunctionsListener) handleRequest(ctx context.Context, requestID Request if err != nil { l.logger.Errorw("failed to create ExternalAdapterClient", "requestID", requestIDStr, "err", err) l.setError(ctx, requestID, INTERNAL_ERROR, []byte(err.Error())) - return + return err } nodeProvidedSecrets, userErr, internalErr := l.getSecrets(ctx, eaClient, requestID, subscriptionOwner, requestData) if internalErr != nil { l.logger.Errorw("internal error during getSecrets", "requestID", requestIDStr, "err", internalErr) l.setError(ctx, requestID, INTERNAL_ERROR, []byte(internalErr.Error())) - return + return internalErr } if userErr != nil { l.logger.Debugw("user error during getSecrets", "requestID", requestIDStr, "err", userErr) l.setError(ctx, requestID, USER_ERROR, []byte(userErr.Error())) - return + return nil // user error } maxSecretsSize := l.getMaxSecretsSize(flags) if uint32(len(nodeProvidedSecrets)) > maxSecretsSize { l.logger.Errorw("secrets size too big", "requestID", requestIDStr, "secretsSize", len(nodeProvidedSecrets), "maxSecretsSize", maxSecretsSize) l.setError(ctx, requestID, USER_ERROR, []byte("secrets size too big")) - return + return nil // user error } computationResult, computationError, domains, err := eaClient.RunComputation(ctx, requestIDStr, l.job.Name.ValueOrZero(), subscriptionOwner.Hex(), subscriptionId, flags, nodeProvidedSecrets, requestData) @@ -370,7 +425,7 @@ func (l *FunctionsListener) handleRequest(ctx context.Context, requestID Request if err != nil { l.logger.Errorw("internal adapter error", "requestID", requestIDStr, "err", err) l.setError(ctx, requestID, INTERNAL_ERROR, []byte(err.Error())) - return + return err } if len(computationError) == 0 && len(computationResult) == 0 { @@ -396,11 +451,13 @@ func (l *FunctionsListener) handleRequest(ctx context.Context, requestID Request l.logger.Debugw("saving computation result", "requestID", requestIDStr) if err2 := l.pluginORM.SetResult(requestID, computationResult, time.Now(), pg.WithParentCtx(ctx)); err2 != nil { l.logger.Errorw("call to SetResult failed", "requestID", requestIDStr, "err", err2) + return err2 } } + return nil } -func (l *FunctionsListener) handleOracleResponseV1(response *evmrelayTypes.OracleResponse) { +func (l *functionsListener) handleOracleResponseV1(response *evmrelayTypes.OracleResponse) { defer l.shutdownWaitGroup.Done() l.logger.Infow("oracle response v1 received", "requestID", formatRequestId(response.RequestId)) @@ -412,7 +469,7 @@ func (l *FunctionsListener) handleOracleResponseV1(response *evmrelayTypes.Oracl promRequestConfirmed.WithLabelValues(l.contractAddressHex).Inc() } -func (l *FunctionsListener) timeoutRequests() { +func (l *functionsListener) timeoutRequests() { defer l.shutdownWaitGroup.Done() timeoutSec, freqSec, batchSize := l.pluginConfig.RequestTimeoutSec, l.pluginConfig.RequestTimeoutCheckFrequencySec, l.pluginConfig.RequestTimeoutBatchLookupSize if timeoutSec == 0 || freqSec == 0 || batchSize == 0 { @@ -448,7 +505,7 @@ func (l *FunctionsListener) timeoutRequests() { } } -func (l *FunctionsListener) pruneRequests() { +func (l *functionsListener) pruneRequests() { defer l.shutdownWaitGroup.Done() maxStoredRequests, freqSec, batchSize := l.pluginConfig.PruneMaxStoredRequests, l.pluginConfig.PruneCheckFrequencySec, l.pluginConfig.PruneBatchSize if maxStoredRequests == 0 { @@ -490,7 +547,7 @@ func (l *FunctionsListener) pruneRequests() { } } -func (l *FunctionsListener) reportSourceCodeDomains(requestId RequestID, domains []string) { +func (l *functionsListener) reportSourceCodeDomains(requestId RequestID, domains []string) { r := &telem.FunctionsRequest{ RequestId: formatRequestId(requestId), NodeAddress: l.job.OCR2OracleSpec.TransmitterID.ValueOrZero(), @@ -505,7 +562,7 @@ func (l *FunctionsListener) reportSourceCodeDomains(requestId RequestID, domains } } -func (l *FunctionsListener) getSecrets(ctx context.Context, eaClient ExternalAdapterClient, requestID RequestID, subscriptionOwner common.Address, requestData *RequestData) (decryptedSecrets string, userError, internalError error) { +func (l *functionsListener) getSecrets(ctx context.Context, eaClient ExternalAdapterClient, requestID RequestID, subscriptionOwner common.Address, requestData *RequestData) (decryptedSecrets string, userError, internalError error) { if l.decryptor == nil { l.logger.Warn("Decryptor not configured") return "", nil, nil diff --git a/core/services/functions/listener_test.go b/core/services/functions/listener_test.go index 6f0badd6e11..0fcc9c65599 100644 --- a/core/services/functions/listener_test.go +++ b/core/services/functions/listener_test.go @@ -7,6 +7,7 @@ import ( "math/big" "sync" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/fxamacker/cbor/v2" @@ -21,7 +22,7 @@ import ( log_mocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -46,7 +47,7 @@ import ( ) type FunctionsListenerUniverse struct { - service *functions_service.FunctionsListener + service functions_service.FunctionsListener bridgeAccessor *functions_mocks.BridgeAccessor eaClient *functions_mocks.ExternalAdapterClient pluginORM *functions_mocks.ORM @@ -125,7 +126,7 @@ func NewFunctionsListenerUniverse(t *testing.T, timeoutSec int, pruneFrequencySe ingressClient := sync_mocks.NewTelemetryService(t) ingressAgent := telemetry.NewIngressAgentWrapper(ingressClient) - monEndpoint := ingressAgent.GenMonitoringEndpoint(contractAddress, synchronization.FunctionsRequests, "test-network", "test-chainID") + monEndpoint := ingressAgent.GenMonitoringEndpoint("test-network", "test-chainID", contractAddress, synchronization.FunctionsRequests) s4Storage := s4_mocks.NewStorage(t) client := chain.Client() @@ -179,6 +180,72 @@ func TestFunctionsListener_HandleOracleRequestV1_Success(t *testing.T) { uni.service.Close() } +func TestFunctionsListener_HandleOffchainRequest_Success(t *testing.T) { + testutils.SkipShortDB(t) + t.Parallel() + + uni := NewFunctionsListenerUniverse(t, 0, 1_000_000) + + uni.pluginORM.On("CreateRequest", mock.Anything, mock.Anything).Return(nil) + uni.bridgeAccessor.On("NewExternalAdapterClient").Return(uni.eaClient, nil) + uni.eaClient.On("RunComputation", mock.Anything, RequestIDStr, mock.Anything, SubscriptionOwner.Hex(), SubscriptionID, mock.Anything, mock.Anything, mock.Anything).Return(ResultBytes, nil, nil, nil) + uni.pluginORM.On("SetResult", RequestID, ResultBytes, mock.Anything, mock.Anything).Return(nil) + + request := &functions_service.OffchainRequest{ + RequestId: RequestID[:], + RequestInitiator: SubscriptionOwner.Bytes(), + SubscriptionId: uint64(SubscriptionID), + SubscriptionOwner: SubscriptionOwner.Bytes(), + Timestamp: uint64(time.Now().Unix()), + Data: functions_service.RequestData{}, + } + require.NoError(t, uni.service.HandleOffchainRequest(testutils.Context(t), request)) +} + +func TestFunctionsListener_HandleOffchainRequest_Invalid(t *testing.T) { + testutils.SkipShortDB(t) + t.Parallel() + uni := NewFunctionsListenerUniverse(t, 0, 1_000_000) + + request := &functions_service.OffchainRequest{ + RequestId: RequestID[:], + RequestInitiator: []byte("invalid_address"), + SubscriptionId: uint64(SubscriptionID), + SubscriptionOwner: SubscriptionOwner.Bytes(), + Timestamp: uint64(time.Now().Unix()), + Data: functions_service.RequestData{}, + } + require.Error(t, uni.service.HandleOffchainRequest(testutils.Context(t), request)) + + request.RequestInitiator = SubscriptionOwner.Bytes() + request.SubscriptionOwner = []byte("invalid_address") + require.Error(t, uni.service.HandleOffchainRequest(testutils.Context(t), request)) + + request.SubscriptionOwner = SubscriptionOwner.Bytes() + request.Timestamp = 1 + require.Error(t, uni.service.HandleOffchainRequest(testutils.Context(t), request)) +} + +func TestFunctionsListener_HandleOffchainRequest_InternalError(t *testing.T) { + testutils.SkipShortDB(t) + t.Parallel() + uni := NewFunctionsListenerUniverse(t, 0, 1_000_000) + uni.pluginORM.On("CreateRequest", mock.Anything, mock.Anything).Return(nil) + uni.bridgeAccessor.On("NewExternalAdapterClient").Return(uni.eaClient, nil) + uni.eaClient.On("RunComputation", mock.Anything, RequestIDStr, mock.Anything, SubscriptionOwner.Hex(), SubscriptionID, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil, nil, errors.New("error")) + uni.pluginORM.On("SetError", RequestID, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + + request := &functions_service.OffchainRequest{ + RequestId: RequestID[:], + RequestInitiator: SubscriptionOwner.Bytes(), + SubscriptionId: uint64(SubscriptionID), + SubscriptionOwner: SubscriptionOwner.Bytes(), + Timestamp: uint64(time.Now().Unix()), + Data: functions_service.RequestData{}, + } + require.Error(t, uni.service.HandleOffchainRequest(testutils.Context(t), request)) +} + func TestFunctionsListener_HandleOracleRequestV1_ComputationError(t *testing.T) { testutils.SkipShortDB(t) t.Parallel() diff --git a/core/services/functions/mocks/functions_listener.go b/core/services/functions/mocks/functions_listener.go new file mode 100644 index 00000000000..d2aeb2ddab8 --- /dev/null +++ b/core/services/functions/mocks/functions_listener.go @@ -0,0 +1,71 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package mocks + +import ( + context "context" + + functions "github.com/smartcontractkit/chainlink/v2/core/services/functions" + mock "github.com/stretchr/testify/mock" +) + +// FunctionsListener is an autogenerated mock type for the FunctionsListener type +type FunctionsListener struct { + mock.Mock +} + +// Close provides a mock function with given fields: +func (_m *FunctionsListener) 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 +} + +// HandleOffchainRequest provides a mock function with given fields: ctx, request +func (_m *FunctionsListener) HandleOffchainRequest(ctx context.Context, request *functions.OffchainRequest) error { + ret := _m.Called(ctx, request) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *functions.OffchainRequest) error); ok { + r0 = rf(ctx, request) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Start provides a mock function with given fields: _a0 +func (_m *FunctionsListener) 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 +} + +// NewFunctionsListener creates a new instance of FunctionsListener. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewFunctionsListener(t interface { + mock.TestingT + Cleanup(func()) +}) *FunctionsListener { + mock := &FunctionsListener{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/functions/mocks/offchain_transmitter.go b/core/services/functions/mocks/offchain_transmitter.go new file mode 100644 index 00000000000..d9a7be04dd4 --- /dev/null +++ b/core/services/functions/mocks/offchain_transmitter.go @@ -0,0 +1,59 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package mocks + +import ( + context "context" + + functions "github.com/smartcontractkit/chainlink/v2/core/services/functions" + mock "github.com/stretchr/testify/mock" +) + +// OffchainTransmitter is an autogenerated mock type for the OffchainTransmitter type +type OffchainTransmitter struct { + mock.Mock +} + +// ReportChannel provides a mock function with given fields: +func (_m *OffchainTransmitter) ReportChannel() chan *functions.OffchainResponse { + ret := _m.Called() + + var r0 chan *functions.OffchainResponse + if rf, ok := ret.Get(0).(func() chan *functions.OffchainResponse); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(chan *functions.OffchainResponse) + } + } + + return r0 +} + +// TransmitReport provides a mock function with given fields: ctx, report +func (_m *OffchainTransmitter) TransmitReport(ctx context.Context, report *functions.OffchainResponse) error { + ret := _m.Called(ctx, report) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *functions.OffchainResponse) error); ok { + r0 = rf(ctx, report) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewOffchainTransmitter creates a new instance of OffchainTransmitter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOffchainTransmitter(t interface { + mock.TestingT + Cleanup(func()) +}) *OffchainTransmitter { + mock := &OffchainTransmitter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/functions/offchain_transmitter.go b/core/services/functions/offchain_transmitter.go new file mode 100644 index 00000000000..63527937f92 --- /dev/null +++ b/core/services/functions/offchain_transmitter.go @@ -0,0 +1,39 @@ +package functions + +import ( + "context" + + "github.com/pkg/errors" +) + +// Simple wrapper around a channel to transmit offchain reports between +// OCR plugin and Gateway connector +// +//go:generate mockery --quiet --name OffchainTransmitter --output ./mocks/ --case=underscore +type OffchainTransmitter interface { + TransmitReport(ctx context.Context, report *OffchainResponse) error + ReportChannel() chan *OffchainResponse +} + +type offchainTransmitter struct { + reportCh chan *OffchainResponse +} + +func NewOffchainTransmitter(chanSize uint32) OffchainTransmitter { + return &offchainTransmitter{ + reportCh: make(chan *OffchainResponse, chanSize), + } +} + +func (t *offchainTransmitter) TransmitReport(ctx context.Context, report *OffchainResponse) error { + select { + case t.reportCh <- report: + return nil + case <-ctx.Done(): + return errors.New("context cancelled") + } +} + +func (t *offchainTransmitter) ReportChannel() chan *OffchainResponse { + return t.reportCh +} diff --git a/core/services/functions/offchain_transmitter_test.go b/core/services/functions/offchain_transmitter_test.go new file mode 100644 index 00000000000..bec639bf27d --- /dev/null +++ b/core/services/functions/offchain_transmitter_test.go @@ -0,0 +1,31 @@ +package functions_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/services/functions" +) + +func TestOffchainTransmitter(t *testing.T) { + t.Parallel() + + transmitter := functions.NewOffchainTransmitter(1) + ch := transmitter.ReportChannel() + report := &functions.OffchainResponse{RequestId: []byte("testID")} + ctx := testutils.Context(t) + + require.NoError(t, transmitter.TransmitReport(ctx, report)) + require.Equal(t, report, <-ch) + + require.NoError(t, transmitter.TransmitReport(ctx, report)) + + ctxTimeout, cancel := context.WithTimeout(ctx, time.Millisecond*20) + defer cancel() + // should not freeze + require.Error(t, transmitter.TransmitReport(ctxTimeout, report)) +} diff --git a/core/services/functions/orm.go b/core/services/functions/orm.go index b6f692019a1..7838c700858 100644 --- a/core/services/functions/orm.go +++ b/core/services/functions/orm.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" diff --git a/core/services/functions/request.go b/core/services/functions/request.go index 181058b83d0..eaa92fc8088 100644 --- a/core/services/functions/request.go +++ b/core/services/functions/request.go @@ -5,10 +5,23 @@ const ( LocationRemote = 1 LocationDONHosted = 2 LanguageJavaScript = 0 + + RequestStatePending = 1 + RequestStateComplete = 2 + RequestStateInternalError = 3 ) type RequestFlags [32]byte +type OffchainRequest struct { + RequestId []byte `json:"requestId"` + RequestInitiator []byte `json:"requestInitiator"` + SubscriptionId uint64 `json:"subscriptionId"` + SubscriptionOwner []byte `json:"subscriptionOwner"` + Timestamp uint64 `json:"timestamp"` + Data RequestData `json:"data"` +} + type RequestData struct { Source string `json:"source" cbor:"source"` Language int `json:"language" cbor:"language"` @@ -19,6 +32,21 @@ type RequestData struct { BytesArgs [][]byte `json:"bytesArgs,omitempty" cbor:"bytesArgs"` } +// NOTE: to be extended with raw report and signatures when needed +type OffchainResponse struct { + RequestId []byte `json:"requestId"` + Result []byte `json:"result,omitempty"` + Error []byte `json:"error,omitempty"` +} + +type HeartbeatResponse struct { + Status int `json:"status"` + InternalError string `json:"internalError,omitempty"` + ReceivedTs uint64 `json:"receivedTs"` + CompletedTs uint64 `json:"completedTs"` + Response *OffchainResponse `json:"response,omitempty"` +} + type DONHostedSecrets struct { SlotID uint `json:"slotId" cbor:"slotId"` Version uint64 `json:"version" cbor:"version"` diff --git a/core/services/gateway/connectionmanager.go b/core/services/gateway/connectionmanager.go index ae2ade6511e..9f88b51e7b5 100644 --- a/core/services/gateway/connectionmanager.go +++ b/core/services/gateway/connectionmanager.go @@ -15,7 +15,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" @@ -42,7 +42,7 @@ type ConnectionManager interface { } type connectionManager struct { - utils.StartStopOnce + services.StateMachine config *config.ConnectionManagerConfig dons map[string]*donConnectionManager @@ -72,7 +72,7 @@ type donConnectionManager struct { handler handlers.Handler codec api.Codec closeWait sync.WaitGroup - shutdownCh chan struct{} + shutdownCh services.StopChan lggr logger.Logger } @@ -271,7 +271,7 @@ func (m *donConnectionManager) SendToNode(ctx context.Context, nodeAddress strin } func (m *donConnectionManager) readLoop(nodeAddress string, nodeState *nodeState) { - ctx, _ := utils.StopChan(m.shutdownCh).NewCtx() + ctx, _ := m.shutdownCh.NewCtx() for { select { case <-m.shutdownCh: @@ -296,7 +296,7 @@ func (m *donConnectionManager) readLoop(nodeAddress string, nodeState *nodeState } func (m *donConnectionManager) heartbeatLoop(intervalSec uint32) { - ctx, _ := utils.StopChan(m.shutdownCh).NewCtx() + ctx, _ := m.shutdownCh.NewCtx() defer m.closeWait.Done() if intervalSec == 0 { diff --git a/core/services/gateway/connector/connector.go b/core/services/gateway/connector/connector.go index dd8dce473b5..27db8fd44b6 100644 --- a/core/services/gateway/connector/connector.go +++ b/core/services/gateway/connector/connector.go @@ -10,7 +10,7 @@ import ( "github.com/gorilla/websocket" - "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" @@ -46,7 +46,7 @@ type GatewayConnectorHandler interface { } type gatewayConnector struct { - utils.StartStopOnce + services.StateMachine config *ConnectorConfig codec api.Codec @@ -57,7 +57,7 @@ type gatewayConnector struct { gateways map[string]*gatewayState urlToId map[string]string closeWait sync.WaitGroup - shutdownCh chan struct{} + shutdownCh services.StopChan lggr logger.Logger } @@ -143,7 +143,7 @@ func (c *gatewayConnector) SendToGateway(ctx context.Context, gatewayId string, } func (c *gatewayConnector) readLoop(gatewayState *gatewayState) { - ctx, cancel := utils.StopChan(c.shutdownCh).NewCtx() + ctx, cancel := c.shutdownCh.NewCtx() defer cancel() for { @@ -168,7 +168,7 @@ func (c *gatewayConnector) readLoop(gatewayState *gatewayState) { func (c *gatewayConnector) reconnectLoop(gatewayState *gatewayState) { redialBackoff := utils.NewRedialBackoff() - ctx, cancel := utils.StopChan(c.shutdownCh).NewCtx() + ctx, cancel := c.shutdownCh.NewCtx() defer cancel() for { diff --git a/core/services/gateway/delegate.go b/core/services/gateway/delegate.go index 909ca21ad73..d4180184aca 100644 --- a/core/services/gateway/delegate.go +++ b/core/services/gateway/delegate.go @@ -7,7 +7,7 @@ import ( "github.com/pelletier/go-toml" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -16,14 +16,14 @@ import ( ) type Delegate struct { - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer ks keystore.Eth lggr logger.Logger } var _ job.Delegate = (*Delegate)(nil) -func NewDelegate(legacyChains evm.LegacyChainContainer, ks keystore.Eth, lggr logger.Logger) *Delegate { +func NewDelegate(legacyChains legacyevm.LegacyChainContainer, ks keystore.Eth, lggr logger.Logger) *Delegate { return &Delegate{legacyChains: legacyChains, ks: ks, lggr: lggr} } diff --git a/core/services/gateway/gateway.go b/core/services/gateway/gateway.go index fb38ae10de9..79ddf0a5c69 100644 --- a/core/services/gateway/gateway.go +++ b/core/services/gateway/gateway.go @@ -13,6 +13,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" @@ -42,7 +43,7 @@ type HandlerFactory interface { } type gateway struct { - utils.StartStopOnce + services.StateMachine codec api.Codec httpServer gw_net.HttpServer diff --git a/core/services/gateway/handler_factory.go b/core/services/gateway/handler_factory.go index 519de608a0f..368bc64c872 100644 --- a/core/services/gateway/handler_factory.go +++ b/core/services/gateway/handler_factory.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers" @@ -17,13 +17,13 @@ const ( ) type handlerFactory struct { - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer lggr logger.Logger } var _ HandlerFactory = (*handlerFactory)(nil) -func NewHandlerFactory(legacyChains evm.LegacyChainContainer, lggr logger.Logger) HandlerFactory { +func NewHandlerFactory(legacyChains legacyevm.LegacyChainContainer, lggr logger.Logger) HandlerFactory { return &handlerFactory{legacyChains, lggr} } diff --git a/core/services/gateway/handlers/functions/allowlist.go b/core/services/gateway/handlers/functions/allowlist.go index d3619d9071e..0a18d6e6d87 100644 --- a/core/services/gateway/handlers/functions/allowlist.go +++ b/core/services/gateway/handlers/functions/allowlist.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/services" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_allow_list" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_router" @@ -45,7 +46,7 @@ type OnchainAllowlist interface { } type onchainAllowlist struct { - utils.StartStopOnce + services.StateMachine config OnchainAllowlistConfig allowlist atomic.Pointer[map[common.Address]struct{}] @@ -54,7 +55,7 @@ type onchainAllowlist struct { blockConfirmations *big.Int lggr logger.Logger closeWait sync.WaitGroup - stopCh utils.StopChan + stopCh services.StopChan } func NewOnchainAllowlist(client evmclient.Client, config OnchainAllowlistConfig, lggr logger.Logger) (OnchainAllowlist, error) { @@ -77,7 +78,7 @@ func NewOnchainAllowlist(client evmclient.Client, config OnchainAllowlistConfig, contractV1: contractV1, blockConfirmations: big.NewInt(int64(config.BlockConfirmations)), lggr: lggr.Named("OnchainAllowlist"), - stopCh: make(utils.StopChan), + stopCh: make(services.StopChan), } emptyMap := make(map[common.Address]struct{}) allowlist.allowlist.Store(&emptyMap) diff --git a/core/services/gateway/handlers/functions/api.go b/core/services/gateway/handlers/functions/api.go index 979cdb939b6..36db1943931 100644 --- a/core/services/gateway/handlers/functions/api.go +++ b/core/services/gateway/handlers/functions/api.go @@ -5,6 +5,7 @@ import "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" const ( MethodSecretsSet = "secrets_set" MethodSecretsList = "secrets_list" + MethodHeartbeat = "heartbeat" ) type SecretsSetRequest struct { @@ -17,17 +18,17 @@ type SecretsSetRequest struct { // SecretsListRequest has empty payload -type SecretsResponseBase struct { +type ResponseBase struct { Success bool `json:"success"` ErrorMessage string `json:"error_message,omitempty"` } type SecretsSetResponse struct { - SecretsResponseBase + ResponseBase } type SecretsListResponse struct { - SecretsResponseBase + ResponseBase Rows []SecretsListRow `json:"rows,omitempty"` } @@ -38,7 +39,7 @@ type SecretsListRow struct { } // Gateway -> User response, which combines responses from several nodes -type CombinedSecretsResponse struct { - SecretsResponseBase +type CombinedResponse struct { + ResponseBase NodeResponses []*api.Message `json:"node_responses"` } diff --git a/core/services/gateway/handlers/functions/handler.functions.go b/core/services/gateway/handlers/functions/handler.functions.go index 7eb15ef6ffd..5277f9789d6 100644 --- a/core/services/gateway/handlers/functions/handler.functions.go +++ b/core/services/gateway/handlers/functions/handler.functions.go @@ -5,6 +5,8 @@ import ( "encoding/json" "errors" "fmt" + "math/big" + "strings" "time" "github.com/ethereum/go-ethereum/common" @@ -12,14 +14,14 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/v2/core/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) var ( @@ -61,29 +63,31 @@ type FunctionsHandlerConfig struct { OnchainSubscriptions *OnchainSubscriptionsConfig `json:"onchainSubscriptions"` MinimumSubscriptionBalance *assets.Link `json:"minimumSubscriptionBalance"` // Not specifying RateLimiter config disables rate limiting - UserRateLimiter *hc.RateLimiterConfig `json:"userRateLimiter"` - NodeRateLimiter *hc.RateLimiterConfig `json:"nodeRateLimiter"` - MaxPendingRequests uint32 `json:"maxPendingRequests"` - RequestTimeoutMillis int64 `json:"requestTimeoutMillis"` + UserRateLimiter *hc.RateLimiterConfig `json:"userRateLimiter"` + NodeRateLimiter *hc.RateLimiterConfig `json:"nodeRateLimiter"` + MaxPendingRequests uint32 `json:"maxPendingRequests"` + RequestTimeoutMillis int64 `json:"requestTimeoutMillis"` + AllowedHeartbeatInitiators []string `json:"allowedHeartbeatInitiators"` } type functionsHandler struct { - utils.StartStopOnce + services.StateMachine - handlerConfig FunctionsHandlerConfig - donConfig *config.DONConfig - don handlers.DON - pendingRequests hc.RequestCache[PendingSecretsRequest] - allowlist OnchainAllowlist - subscriptions OnchainSubscriptions - minimumBalance *assets.Link - userRateLimiter *hc.RateLimiter - nodeRateLimiter *hc.RateLimiter - chStop utils.StopChan - lggr logger.Logger + handlerConfig FunctionsHandlerConfig + donConfig *config.DONConfig + don handlers.DON + pendingRequests hc.RequestCache[PendingRequest] + allowlist OnchainAllowlist + subscriptions OnchainSubscriptions + minimumBalance *assets.Link + userRateLimiter *hc.RateLimiter + nodeRateLimiter *hc.RateLimiter + allowedHeartbeatInitiators map[string]struct{} + chStop services.StopChan + lggr logger.Logger } -type PendingSecretsRequest struct { +type PendingRequest struct { request *api.Message responses map[string]*api.Message successful []*api.Message @@ -92,7 +96,7 @@ type PendingSecretsRequest struct { var _ handlers.Handler = (*functionsHandler)(nil) -func NewFunctionsHandlerFromConfig(handlerConfig json.RawMessage, donConfig *config.DONConfig, don handlers.DON, legacyChains evm.LegacyChainContainer, lggr logger.Logger) (handlers.Handler, error) { +func NewFunctionsHandlerFromConfig(handlerConfig json.RawMessage, donConfig *config.DONConfig, don handlers.DON, legacyChains legacyevm.LegacyChainContainer, lggr logger.Logger) (handlers.Handler, error) { var cfg FunctionsHandlerConfig err := json.Unmarshal(handlerConfig, &cfg) if err != nil { @@ -134,33 +138,39 @@ func NewFunctionsHandlerFromConfig(handlerConfig json.RawMessage, donConfig *con return nil, err2 } } - pendingRequestsCache := hc.NewRequestCache[PendingSecretsRequest](time.Millisecond*time.Duration(cfg.RequestTimeoutMillis), cfg.MaxPendingRequests) - return NewFunctionsHandler(cfg, donConfig, don, pendingRequestsCache, allowlist, subscriptions, cfg.MinimumSubscriptionBalance, userRateLimiter, nodeRateLimiter, lggr), nil + allowedHeartbeatInitiators := make(map[string]struct{}) + for _, initiator := range cfg.AllowedHeartbeatInitiators { + allowedHeartbeatInitiators[strings.ToLower(initiator)] = struct{}{} + } + pendingRequestsCache := hc.NewRequestCache[PendingRequest](time.Millisecond*time.Duration(cfg.RequestTimeoutMillis), cfg.MaxPendingRequests) + return NewFunctionsHandler(cfg, donConfig, don, pendingRequestsCache, allowlist, subscriptions, cfg.MinimumSubscriptionBalance, userRateLimiter, nodeRateLimiter, allowedHeartbeatInitiators, lggr), nil } func NewFunctionsHandler( cfg FunctionsHandlerConfig, donConfig *config.DONConfig, don handlers.DON, - pendingRequestsCache hc.RequestCache[PendingSecretsRequest], + pendingRequestsCache hc.RequestCache[PendingRequest], allowlist OnchainAllowlist, subscriptions OnchainSubscriptions, minimumBalance *assets.Link, userRateLimiter *hc.RateLimiter, nodeRateLimiter *hc.RateLimiter, + allowedHeartbeatInitiators map[string]struct{}, lggr logger.Logger) handlers.Handler { return &functionsHandler{ - handlerConfig: cfg, - donConfig: donConfig, - don: don, - pendingRequests: pendingRequestsCache, - allowlist: allowlist, - subscriptions: subscriptions, - minimumBalance: minimumBalance, - userRateLimiter: userRateLimiter, - nodeRateLimiter: nodeRateLimiter, - chStop: make(utils.StopChan), - lggr: lggr, + handlerConfig: cfg, + donConfig: donConfig, + don: don, + pendingRequests: pendingRequestsCache, + allowlist: allowlist, + subscriptions: subscriptions, + minimumBalance: minimumBalance, + userRateLimiter: userRateLimiter, + nodeRateLimiter: nodeRateLimiter, + allowedHeartbeatInitiators: allowedHeartbeatInitiators, + chStop: make(services.StopChan), + lggr: lggr, } } @@ -176,15 +186,29 @@ func (h *functionsHandler) HandleUserMessage(ctx context.Context, msg *api.Messa promHandlerError.WithLabelValues(h.donConfig.DonId, ErrRateLimited.Error()).Inc() return ErrRateLimited } - if h.subscriptions != nil && h.minimumBalance != nil { - if balance, err := h.subscriptions.GetMaxUserBalance(sender); err != nil || balance.Cmp(h.minimumBalance.ToInt()) < 0 { - h.lggr.Debug("received a message from a user having insufficient balance", "sender", msg.Body.Sender, "balance", balance.String()) + if msg.Body.Method == MethodSecretsSet && h.subscriptions != nil && h.minimumBalance != nil { + balance, err := h.subscriptions.GetMaxUserBalance(sender) + if err != nil { + h.lggr.Debugw("error getting max user balance", "sender", msg.Body.Sender, "err", err) + } + if balance == nil { + balance = big.NewInt(0) + } + if err != nil || balance.Cmp(h.minimumBalance.ToInt()) < 0 { + h.lggr.Debugw("received a message from a user having insufficient balance", "sender", msg.Body.Sender, "balance", balance.String()) return fmt.Errorf("sender has insufficient balance: %v juels", balance.String()) } } switch msg.Body.Method { case MethodSecretsSet, MethodSecretsList: - return h.handleSecretsRequest(ctx, msg, callbackCh) + return h.handleRequest(ctx, msg, callbackCh) + case MethodHeartbeat: + if _, ok := h.allowedHeartbeatInitiators[msg.Body.Sender]; !ok { + h.lggr.Debugw("received heartbeat request from a non-allowed sender", "sender", msg.Body.Sender) + promHandlerError.WithLabelValues(h.donConfig.DonId, ErrNotAllowlisted.Error()).Inc() + return ErrUnsupportedMethod + } + return h.handleRequest(ctx, msg, callbackCh) default: h.lggr.Debugw("unsupported method", "method", msg.Body.Method) promHandlerError.WithLabelValues(h.donConfig.DonId, ErrUnsupportedMethod.Error()).Inc() @@ -192,11 +216,11 @@ func (h *functionsHandler) HandleUserMessage(ctx context.Context, msg *api.Messa } } -func (h *functionsHandler) handleSecretsRequest(ctx context.Context, msg *api.Message, callbackCh chan<- handlers.UserCallbackPayload) error { - h.lggr.Debugw("handleSecretsRequest: processing message", "sender", msg.Body.Sender, "messageId", msg.Body.MessageId) - err := h.pendingRequests.NewRequest(msg, callbackCh, &PendingSecretsRequest{request: msg, responses: make(map[string]*api.Message)}) +func (h *functionsHandler) handleRequest(ctx context.Context, msg *api.Message, callbackCh chan<- handlers.UserCallbackPayload) error { + h.lggr.Debugw("handleRequest: processing message", "sender", msg.Body.Sender, "messageId", msg.Body.MessageId) + err := h.pendingRequests.NewRequest(msg, callbackCh, &PendingRequest{request: msg, responses: make(map[string]*api.Message)}) if err != nil { - h.lggr.Warnw("handleSecretsRequest: error adding new request", "sender", msg.Body.Sender, "err", err) + h.lggr.Warnw("handleRequest: error adding new request", "sender", msg.Body.Sender, "err", err) promHandlerError.WithLabelValues(h.donConfig.DonId, err.Error()).Inc() return err } @@ -204,7 +228,7 @@ func (h *functionsHandler) handleSecretsRequest(ctx context.Context, msg *api.Me for _, member := range h.donConfig.Members { err := h.don.SendToNode(ctx, member.Address, msg) if err != nil { - h.lggr.Debugw("handleSecretsRequest: failed to send to a node", "node", member.Address, "err", err) + h.lggr.Debugw("handleRequest: failed to send to a node", "node", member.Address, "err", err) } } return nil @@ -219,14 +243,16 @@ func (h *functionsHandler) HandleNodeMessage(ctx context.Context, msg *api.Messa switch msg.Body.Method { case MethodSecretsSet, MethodSecretsList: return h.pendingRequests.ProcessResponse(msg, h.processSecretsResponse) + case MethodHeartbeat: + return h.pendingRequests.ProcessResponse(msg, h.processHeartbeatResponse) default: h.lggr.Debugw("unsupported method", "method", msg.Body.Method) return ErrUnsupportedMethod } } -// Conforms to ResponseProcessor[*PendingSecretsRequest] -func (h *functionsHandler) processSecretsResponse(response *api.Message, responseData *PendingSecretsRequest) (*handlers.UserCallbackPayload, *PendingSecretsRequest, error) { +// Conforms to ResponseProcessor[*PendingRequest] +func (h *functionsHandler) processSecretsResponse(response *api.Message, responseData *PendingRequest) (*handlers.UserCallbackPayload, *PendingRequest, error) { if _, exists := responseData.responses[response.Body.Sender]; exists { return nil, nil, errors.New("duplicate response") } @@ -234,7 +260,7 @@ func (h *functionsHandler) processSecretsResponse(response *api.Message, respons return nil, responseData, errors.New("invalid method") } responseData.responses[response.Body.Sender] = response - var responsePayload SecretsResponseBase + var responsePayload ResponseBase err := json.Unmarshal(response.Body.Payload, &responsePayload) if err != nil { responseData.errors = append(responseData.errors, response) @@ -261,7 +287,7 @@ func (h *functionsHandler) processSecretsResponse(response *api.Message, respons } func newSecretsResponse(request *api.Message, success bool, responses []*api.Message) (*handlers.UserCallbackPayload, error) { - payload := CombinedSecretsResponse{SecretsResponseBase: SecretsResponseBase{Success: success}, NodeResponses: responses} + payload := CombinedResponse{ResponseBase: ResponseBase{Success: success}, NodeResponses: responses} payloadJson, err := json.Marshal(payload) if err != nil { return nil, err @@ -287,6 +313,38 @@ func newSecretsResponse(request *api.Message, success bool, responses []*api.Mes return &handlers.UserCallbackPayload{Msg: &userResponse, ErrCode: api.NoError, ErrMsg: ""}, nil } +// Conforms to ResponseProcessor[*PendingRequest] +func (h *functionsHandler) processHeartbeatResponse(response *api.Message, responseData *PendingRequest) (*handlers.UserCallbackPayload, *PendingRequest, error) { + if _, exists := responseData.responses[response.Body.Sender]; exists { + return nil, nil, errors.New("duplicate response") + } + if response.Body.Method != responseData.request.Body.Method { + return nil, responseData, errors.New("invalid method") + } + responseData.responses[response.Body.Sender] = response + + // user response is ready with F+1 node responses + if len(responseData.responses) >= h.donConfig.F+1 { + var responseList []*api.Message + for _, response := range responseData.responses { + responseList = append(responseList, response) + } + userResponse := *responseData.request + userResponse.Body.Receiver = responseData.request.Body.Sender + // success = true only means that we got F+1 responses + // it's up to the heartbeat sender to validate computation results + payload := CombinedResponse{ResponseBase: ResponseBase{Success: true}, NodeResponses: responseList} + payloadJson, err := json.Marshal(payload) + if err != nil { + return &handlers.UserCallbackPayload{Msg: &userResponse, ErrCode: api.NodeReponseEncodingError, ErrMsg: ""}, nil, nil + } + userResponse.Body.Payload = payloadJson + return &handlers.UserCallbackPayload{Msg: &userResponse, ErrCode: api.NoError, ErrMsg: ""}, nil, nil + } + // not ready to be processed yet + return nil, responseData, nil +} + func (h *functionsHandler) Start(ctx context.Context) error { return h.StartOnce("FunctionsHandler", func() error { h.lggr.Info("starting FunctionsHandler") diff --git a/core/services/gateway/handlers/functions/handler.functions_test.go b/core/services/gateway/handlers/functions/handler.functions_test.go index 1446bc84571..f36b64709a2 100644 --- a/core/services/gateway/handlers/functions/handler.functions_test.go +++ b/core/services/gateway/handlers/functions/handler.functions_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" @@ -25,7 +25,7 @@ import ( handlers_mocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/mocks" ) -func newFunctionsHandlerForATestDON(t *testing.T, nodes []gc.TestNode, requestTimeout time.Duration) (handlers.Handler, *handlers_mocks.DON, *functions_mocks.OnchainAllowlist, *functions_mocks.OnchainSubscriptions) { +func newFunctionsHandlerForATestDON(t *testing.T, nodes []gc.TestNode, requestTimeout time.Duration, heartbeatSender string) (handlers.Handler, *handlers_mocks.DON, *functions_mocks.OnchainAllowlist, *functions_mocks.OnchainSubscriptions) { cfg := functions.FunctionsHandlerConfig{} donConfig := &config.DONConfig{ Members: []config.NodeConfig{}, @@ -47,8 +47,9 @@ func newFunctionsHandlerForATestDON(t *testing.T, nodes []gc.TestNode, requestTi require.NoError(t, err) nodeRateLimiter, err := hc.NewRateLimiter(hc.RateLimiterConfig{GlobalRPS: 100.0, GlobalBurst: 100, PerSenderRPS: 100.0, PerSenderBurst: 100}) require.NoError(t, err) - pendingRequestsCache := hc.NewRequestCache[functions.PendingSecretsRequest](requestTimeout, 1000) - handler := functions.NewFunctionsHandler(cfg, donConfig, don, pendingRequestsCache, allowlist, subscriptions, minBalance, userRateLimiter, nodeRateLimiter, logger.TestLogger(t)) + pendingRequestsCache := hc.NewRequestCache[functions.PendingRequest](requestTimeout, 1000) + allowedHeartbeatInititors := map[string]struct{}{heartbeatSender: {}} + handler := functions.NewFunctionsHandler(cfg, donConfig, don, pendingRequestsCache, allowlist, subscriptions, minBalance, userRateLimiter, nodeRateLimiter, allowedHeartbeatInititors, logger.TestLogger(t)) return handler, don, allowlist, subscriptions } @@ -117,7 +118,7 @@ func TestFunctionsHandler_HandleUserMessage_SecretsSet(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { nodes, user := gc.NewTestNodes(t, 4), gc.NewTestNodes(t, 1)[0] - handler, don, allowlist, subscriptions := newFunctionsHandlerForATestDON(t, nodes, time.Hour*24) + handler, don, allowlist, subscriptions := newFunctionsHandlerForATestDON(t, nodes, time.Hour*24, user.Address) userRequestMsg := newSignedMessage(t, "1234", "secrets_set", "don_id", user.PrivateKey) callbachCh := make(chan handlers.UserCallbackPayload) @@ -128,7 +129,7 @@ func TestFunctionsHandler_HandleUserMessage_SecretsSet(t *testing.T) { response := <-callbachCh require.Equal(t, api.NoError, response.ErrCode) require.Equal(t, userRequestMsg.Body.MessageId, response.Msg.Body.MessageId) - var payload functions.CombinedSecretsResponse + var payload functions.CombinedResponse require.NoError(t, json.Unmarshal(response.Msg.Body.Payload, &payload)) require.Equal(t, test.expectedGatewayResult, payload.Success) require.Equal(t, test.expectedNodeMessageCount, len(payload.NodeResponses)) @@ -144,15 +145,57 @@ func TestFunctionsHandler_HandleUserMessage_SecretsSet(t *testing.T) { } } +func TestFunctionsHandler_HandleUserMessage_Heartbeat(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + nodeResults []bool + expectedGatewayResult bool + expectedNodeMessageCount int + }{ + {"three successful", []bool{true, true, true, false}, true, 2}, + {"two successful", []bool{false, true, false, true}, true, 2}, + {"one successful", []bool{false, true, false, false}, true, 2}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + nodes, user := gc.NewTestNodes(t, 4), gc.NewTestNodes(t, 1)[0] + handler, don, allowlist, _ := newFunctionsHandlerForATestDON(t, nodes, time.Hour*24, user.Address) + userRequestMsg := newSignedMessage(t, "1234", "heartbeat", "don_id", user.PrivateKey) + + callbachCh := make(chan handlers.UserCallbackPayload) + done := make(chan struct{}) + go func() { + defer close(done) + // wait on a response from Gateway to the user + response := <-callbachCh + require.Equal(t, api.NoError, response.ErrCode) + require.Equal(t, userRequestMsg.Body.MessageId, response.Msg.Body.MessageId) + var payload functions.CombinedResponse + require.NoError(t, json.Unmarshal(response.Msg.Body.Payload, &payload)) + require.Equal(t, test.expectedGatewayResult, payload.Success) + require.Equal(t, test.expectedNodeMessageCount, len(payload.NodeResponses)) + }() + + allowlist.On("Allow", common.HexToAddress(user.Address)).Return(true, nil) + don.On("SendToNode", mock.Anything, mock.Anything, mock.Anything).Return(nil) + require.NoError(t, handler.HandleUserMessage(testutils.Context(t), &userRequestMsg, callbachCh)) + sendNodeReponses(t, handler, userRequestMsg, nodes, test.nodeResults) + <-done + }) + } +} + func TestFunctionsHandler_HandleUserMessage_InvalidMethod(t *testing.T) { t.Parallel() nodes, user := gc.NewTestNodes(t, 4), gc.NewTestNodes(t, 1)[0] - handler, _, allowlist, subscriptions := newFunctionsHandlerForATestDON(t, nodes, time.Hour*24) + handler, _, allowlist, _ := newFunctionsHandlerForATestDON(t, nodes, time.Hour*24, user.Address) userRequestMsg := newSignedMessage(t, "1234", "secrets_reveal_all_please", "don_id", user.PrivateKey) allowlist.On("Allow", common.HexToAddress(user.Address)).Return(true, nil) - subscriptions.On("GetMaxUserBalance", common.HexToAddress(user.Address)).Return(big.NewInt(1000), nil) err := handler.HandleUserMessage(testutils.Context(t), &userRequestMsg, make(chan handlers.UserCallbackPayload)) require.Error(t, err) } @@ -161,7 +204,7 @@ func TestFunctionsHandler_HandleUserMessage_Timeout(t *testing.T) { t.Parallel() nodes, user := gc.NewTestNodes(t, 4), gc.NewTestNodes(t, 1)[0] - handler, don, allowlist, subscriptions := newFunctionsHandlerForATestDON(t, nodes, time.Millisecond*10) + handler, don, allowlist, subscriptions := newFunctionsHandlerForATestDON(t, nodes, time.Millisecond*10, user.Address) userRequestMsg := newSignedMessage(t, "1234", "secrets_set", "don_id", user.PrivateKey) callbachCh := make(chan handlers.UserCallbackPayload) diff --git a/core/services/gateway/handlers/functions/subscriptions.go b/core/services/gateway/handlers/functions/subscriptions.go index 181a98009f1..ebffbbdd206 100644 --- a/core/services/gateway/handlers/functions/subscriptions.go +++ b/core/services/gateway/handlers/functions/subscriptions.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/services" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_router" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -38,7 +39,7 @@ type OnchainSubscriptions interface { } type onchainSubscriptions struct { - utils.StartStopOnce + services.StateMachine config OnchainSubscriptionsConfig subscriptions UserSubscriptions @@ -48,7 +49,7 @@ type onchainSubscriptions struct { lggr logger.Logger closeWait sync.WaitGroup rwMutex sync.RWMutex - stopCh utils.StopChan + stopCh services.StopChan } func NewOnchainSubscriptions(client evmclient.Client, config OnchainSubscriptionsConfig, lggr logger.Logger) (OnchainSubscriptions, error) { @@ -69,7 +70,7 @@ func NewOnchainSubscriptions(client evmclient.Client, config OnchainSubscription router: router, blockConfirmations: big.NewInt(int64(config.BlockConfirmations)), lggr: lggr.Named("OnchainSubscriptions"), - stopCh: make(utils.StopChan), + stopCh: make(services.StopChan), }, nil } @@ -129,19 +130,16 @@ func (s *onchainSubscriptions) queryLoop() { blockNumber := big.NewInt(0).Sub(latestBlockHeight, s.blockConfirmations) - updateLastKnownCount := func() { + if lastKnownCount == 0 || start > lastKnownCount { count, err := s.getSubscriptionsCount(ctx, blockNumber) if err != nil { - s.lggr.Errorw("Error getting subscriptions count", "err", err) - return + s.lggr.Errorw("Error getting new subscriptions count", "err", err) + } else { + s.lggr.Infow("Updated subscriptions count", "count", count, "blockNumber", blockNumber.Int64()) + lastKnownCount = count } - s.lggr.Infow("Updated subscriptions count", "err", err, "count", count, "blockNumber", blockNumber.Int64()) - lastKnownCount = count } - if lastKnownCount == 0 { - updateLastKnownCount() - } if lastKnownCount == 0 { s.lggr.Info("Router has no subscriptions yet") return @@ -151,12 +149,9 @@ func (s *onchainSubscriptions) queryLoop() { start = 1 } - end := start + uint64(s.config.UpdateRangeSize) + end := start + uint64(s.config.UpdateRangeSize) - 1 if end > lastKnownCount { - updateLastKnownCount() - if end > lastKnownCount { - end = lastKnownCount - } + end = lastKnownCount } if err := s.querySubscriptionsRange(ctx, blockNumber, start, end); err != nil { s.lggr.Errorw("Error querying subscriptions", "err", err, "start", start, "end", end) @@ -179,6 +174,8 @@ func (s *onchainSubscriptions) queryLoop() { } func (s *onchainSubscriptions) querySubscriptionsRange(ctx context.Context, blockNumber *big.Int, start, end uint64) error { + s.lggr.Debugw("Querying subscriptions", "blockNumber", blockNumber, "start", start, "end", end) + subscriptions, err := s.router.GetSubscriptionsInRange(&bind.CallOpts{ Pending: false, BlockNumber: blockNumber, diff --git a/core/services/gateway/handlers/functions/subscriptions_test.go b/core/services/gateway/handlers/functions/subscriptions_test.go index 1e46bff5c0f..adbf637ad73 100644 --- a/core/services/gateway/handlers/functions/subscriptions_test.go +++ b/core/services/gateway/handlers/functions/subscriptions_test.go @@ -2,6 +2,7 @@ package functions_test import ( "math/big" + "sync/atomic" "testing" "time" @@ -24,9 +25,7 @@ const ( invalidUser = "0x6E2dc0F9DB014aE19888F539E59285D2Ea04244C" ) -func TestSubscriptions(t *testing.T) { - t.Parallel() - +func TestSubscriptions_OnePass(t *testing.T) { getSubscriptionCount := hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000003") getSubscriptionsInRange := hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000109e6e1b12098cc8f3a1e9719a817ec53ab9b35c000000000000000000000000000000000000000000000000000034e23f515cb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f5340f0968ee8b7dfd97e3327a6139273cc2c4fa000000000000000000000000000000000000000000000001158e460913d000000000000000000000000000009ed925d8206a4f88a2f643b28b3035b315753cd60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bc14b92364c75e20000000000000000000000009ed925d8206a4f88a2f643b28b3035b315753cd60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005439e5881a529f3ccbffc0e82d49f9db3950aefe") @@ -46,7 +45,7 @@ func TestSubscriptions(t *testing.T) { BlockConfirmations: 1, UpdateFrequencySec: 1, UpdateTimeoutSec: 1, - UpdateRangeSize: 10, + UpdateRangeSize: 3, } subscriptions, err := functions.NewOnchainSubscriptions(client, config, logger.TestLogger(t)) require.NoError(t, err) @@ -57,6 +56,7 @@ func TestSubscriptions(t *testing.T) { assert.NoError(t, subscriptions.Close()) }) + // initially we have 3 subs and range is 3, which needs one pass gomega.NewGomegaWithT(t).Eventually(func() bool { expectedBalance := big.NewInt(0).SetBytes(hexutil.MustDecode("0x01158e460913d00000")) balance, err1 := subscriptions.GetMaxUserBalance(common.HexToAddress(validUser)) @@ -64,3 +64,47 @@ func TestSubscriptions(t *testing.T) { return err1 == nil && err2 != nil && balance.Cmp(expectedBalance) == 0 }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) } + +func TestSubscriptions_MultiPass(t *testing.T) { + const ncycles int32 = 5 + var currentCycle atomic.Int32 + getSubscriptionCount := hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000006") + getSubscriptionsInRange := hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000109e6e1b12098cc8f3a1e9719a817ec53ab9b35c000000000000000000000000000000000000000000000000000034e23f515cb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f5340f0968ee8b7dfd97e3327a6139273cc2c4fa000000000000000000000000000000000000000000000001158e460913d000000000000000000000000000009ed925d8206a4f88a2f643b28b3035b315753cd60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bc14b92364c75e20000000000000000000000009ed925d8206a4f88a2f643b28b3035b315753cd60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005439e5881a529f3ccbffc0e82d49f9db3950aefe") + + ctx := testutils.Context(t) + client := mocks.NewClient(t) + client.On("LatestBlockHeight", mock.Anything).Return(big.NewInt(42), nil) + client.On("CallContract", mock.Anything, ethereum.CallMsg{ // getSubscriptionCount + To: &common.Address{}, + Data: hexutil.MustDecode("0x66419970"), + }, mock.Anything).Run(func(args mock.Arguments) { + currentCycle.Add(1) + }).Return(getSubscriptionCount, nil) + client.On("CallContract", mock.Anything, ethereum.CallMsg{ // GetSubscriptionsInRange(1,3) + To: &common.Address{}, + Data: hexutil.MustDecode("0xec2454e500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003"), + }, mock.Anything).Return(getSubscriptionsInRange, nil) + client.On("CallContract", mock.Anything, ethereum.CallMsg{ // GetSubscriptionsInRange(4,6) + To: &common.Address{}, + Data: hexutil.MustDecode("0xec2454e500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006"), + }, mock.Anything).Return(getSubscriptionsInRange, nil) + config := functions.OnchainSubscriptionsConfig{ + ContractAddress: common.Address{}, + BlockConfirmations: 1, + UpdateFrequencySec: 1, + UpdateTimeoutSec: 1, + UpdateRangeSize: 3, + } + subscriptions, err := functions.NewOnchainSubscriptions(client, config, logger.TestLogger(t)) + require.NoError(t, err) + + err = subscriptions.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, subscriptions.Close()) + }) + + gomega.NewGomegaWithT(t).Eventually(func() bool { + return currentCycle.Load() == ncycles + }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) +} diff --git a/core/services/gateway/handlers/handler.dummy_test.go b/core/services/gateway/handlers/handler.dummy_test.go index c2c7a7a12a3..c3f23935e67 100644 --- a/core/services/gateway/handlers/handler.dummy_test.go +++ b/core/services/gateway/handlers/handler.dummy_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" @@ -41,15 +42,17 @@ func TestDummyHandler_BasicFlow(t *testing.T) { require.NoError(t, err) connMgr.SetHandler(handler) + ctx := testutils.Context(t) + // User request msg := api.Message{Body: api.MessageBody{MessageId: "1234"}} callbackCh := make(chan handlers.UserCallbackPayload, 1) - require.NoError(t, handler.HandleUserMessage(context.Background(), &msg, callbackCh)) + require.NoError(t, handler.HandleUserMessage(ctx, &msg, callbackCh)) require.Equal(t, 2, connMgr.sendCounter) // Responses from both nodes - require.NoError(t, handler.HandleNodeMessage(context.Background(), &msg, "addr_1")) - require.NoError(t, handler.HandleNodeMessage(context.Background(), &msg, "addr_2")) + require.NoError(t, handler.HandleNodeMessage(ctx, &msg, "addr_1")) + require.NoError(t, handler.HandleNodeMessage(ctx, &msg, "addr_2")) response := <-callbackCh require.Equal(t, "1234", response.Msg.Body.MessageId) } diff --git a/core/services/gateway/network/httpserver.go b/core/services/gateway/network/httpserver.go index d9d3e5ee7e4..d4340a92e98 100644 --- a/core/services/gateway/network/httpserver.go +++ b/core/services/gateway/network/httpserver.go @@ -8,9 +8,9 @@ import ( "net/http" "time" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) //go:generate mockery --quiet --name HttpServer --output ./mocks/ --case=underscore @@ -44,7 +44,7 @@ type HTTPServerConfig struct { } type httpServer struct { - utils.StartStopOnce + services.StateMachine config *HTTPServerConfig listener net.Listener server *http.Server diff --git a/core/services/gateway/network/wsconnection.go b/core/services/gateway/network/wsconnection.go index 345d951ed4a..813b644282f 100644 --- a/core/services/gateway/network/wsconnection.go +++ b/core/services/gateway/network/wsconnection.go @@ -5,13 +5,12 @@ import ( "errors" "sync/atomic" - "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/gorilla/websocket" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) // WSConnectionWrapper is a websocket connection abstraction that supports re-connects. @@ -42,7 +41,7 @@ type WSConnectionWrapper interface { } type wsConnectionWrapper struct { - utils.StartStopOnce + services.StateMachine lggr logger.Logger conn atomic.Pointer[websocket.Conn] diff --git a/core/services/gateway/network/wsserver.go b/core/services/gateway/network/wsserver.go index 2fed4a9df4f..58b2dfe663c 100644 --- a/core/services/gateway/network/wsserver.go +++ b/core/services/gateway/network/wsserver.go @@ -10,9 +10,9 @@ import ( "github.com/gorilla/websocket" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) //go:generate mockery --quiet --name WebSocketServer --output ./mocks/ --case=underscore @@ -29,7 +29,7 @@ type WebSocketServerConfig struct { } type webSocketServer struct { - utils.StartStopOnce + services.StateMachine config *WebSocketServerConfig listener net.Listener server *http.Server diff --git a/core/services/health.go b/core/services/health.go index 568823fa324..32e97fd8db3 100644 --- a/core/services/health.go +++ b/core/services/health.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" ) diff --git a/core/services/job/helpers_test.go b/core/services/job/helpers_test.go index 167ed5297cc..4151ed401c8 100644 --- a/core/services/job/helpers_test.go +++ b/core/services/job/helpers_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" diff --git a/core/services/job/job_orm_test.go b/core/services/job/job_orm_test.go index 20f569a893a..21035140f54 100644 --- a/core/services/job/job_orm_test.go +++ b/core/services/job/job_orm_test.go @@ -14,16 +14,14 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -38,6 +36,7 @@ import ( ocr2validate "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" "github.com/smartcontractkit/chainlink/v2/core/services/ocrbootstrap" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" "github.com/smartcontractkit/chainlink/v2/core/services/webhook" @@ -83,9 +82,7 @@ func TestORM(t *testing.T) { pipelineORM := pipeline.NewORM(db, logger.TestLogger(t), config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: ethKeyStore}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) borm := bridges.NewORM(db, logger.TestLogger(t), config.Database()) _, bridge := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) @@ -331,9 +328,7 @@ func TestORM_DeleteJob_DeletesAssociatedRecords(t *testing.T) { lggr := logger.TestLogger(t) pipelineORM := pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, lggr, config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jobORM := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + jobORM := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) scopedConfig := evmtest.NewChainScopedConfig(t, config) korm := keeper.NewORM(db, logger.TestLogger(t), scopedConfig.Database()) @@ -342,6 +337,8 @@ func TestORM_DeleteJob_DeletesAssociatedRecords(t *testing.T) { _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) + relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) + legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) jb, err := ocr.ValidatedOracleSpecToml(legacyChains, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ TransmitterAddress: address.Hex(), DS1BridgeName: bridge.Name.String(), @@ -431,10 +428,8 @@ func TestORM_CreateJob_VRFV2(t *testing.T) { lggr := logger.TestLogger(t) pipelineORM := pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, lggr, config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jobORM := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + jobORM := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) fromAddresses := []string{cltest.NewEIP55Address().String(), cltest.NewEIP55Address().String()} jb, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec( @@ -514,9 +509,7 @@ func TestORM_CreateJob_VRFV2Plus(t *testing.T) { lggr := logger.TestLogger(t) pipelineORM := pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, lggr, config.Database()) - cc := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(cc) - jobORM := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + jobORM := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) fromAddresses := []string{cltest.NewEIP55Address().String(), cltest.NewEIP55Address().String()} jb, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec( @@ -599,9 +592,7 @@ func TestORM_CreateJob_OCRBootstrap(t *testing.T) { lggr := logger.TestLogger(t) pipelineORM := pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, lggr, config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jobORM := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + jobORM := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) jb, err := ocrbootstrap.ValidatedBootstrapSpecToml(testspecs.GetOCRBootstrapSpec()) require.NoError(t, err) @@ -628,9 +619,7 @@ func TestORM_CreateJob_EVMChainID_Validation(t *testing.T) { pipelineORM := pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, lggr, config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jobORM := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + jobORM := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) t.Run("evm chain id validation for ocr works", func(t *testing.T) { jb := job.Job{ @@ -725,9 +714,7 @@ func TestORM_CreateJob_OCR_DuplicatedContractAddress(t *testing.T) { pipelineORM := pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, lggr, config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jobORM := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + jobORM := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) // defaultChainID is deprecated defaultChainID := customChainID @@ -745,6 +732,8 @@ func TestORM_CreateJob_OCR_DuplicatedContractAddress(t *testing.T) { TransmitterAddress: address.Hex(), JobID: externalJobID.UUID.String(), }) + relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) + legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) jb, err := ocr.ValidatedOracleSpecToml(legacyChains, spec.Toml()) require.NoError(t, err) @@ -794,9 +783,7 @@ func TestORM_CreateJob_OCR2_DuplicatedContractAddress(t *testing.T) { pipelineORM := pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, lggr, config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jobORM := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + jobORM := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) @@ -857,10 +844,7 @@ func TestORM_CreateJob_OCR2_Sending_Keys_Transmitter_Keys_Validations(t *testing pipelineORM := pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, lggr, config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - require.True(t, relayExtenders.Len() > 0) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jobORM := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + jobORM := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) jb, err := ocr2validate.ValidatedOracleSpecToml(config.OCR2(), config.Insecure(), testspecs.GetOCR2EVMSpecMinimal()) require.NoError(t, err) @@ -974,14 +958,15 @@ func Test_FindJobs(t *testing.T) { pipelineORM := pipeline.NewORM(db, logger.TestLogger(t), config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) _, bridge := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) + relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) + legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) jb1, err := ocr.ValidatedOracleSpecToml(legacyChains, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: uuid.New().String(), @@ -1054,9 +1039,8 @@ func Test_FindJob(t *testing.T) { pipelineORM := pipeline.NewORM(db, logger.TestLogger(t), config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) _, bridge := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) @@ -1065,6 +1049,8 @@ func Test_FindJob(t *testing.T) { // Must uniquely name the OCR Specs to properly insert a new job in the job table. externalJobID := uuid.New() _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) + relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) + legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) job, err := ocr.ValidatedOracleSpecToml(legacyChains, testspecs.GenerateOCRSpec(testspecs.OCRSpecParams{ JobID: externalJobID.String(), @@ -1232,9 +1218,7 @@ func Test_FindJobsByPipelineSpecIDs(t *testing.T) { pipelineORM := pipeline.NewORM(db, logger.TestLogger(t), config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) jb, err := directrequest.ValidatedDirectRequestSpec(testspecs.GetDirectRequestSpec()) require.NoError(t, err) @@ -1263,20 +1247,7 @@ func Test_FindJobsByPipelineSpecIDs(t *testing.T) { }) t.Run("with chainID disabled", func(t *testing.T) { - newCfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM[0] = &evmcfg.EVMConfig{ - ChainID: utils.NewBigI(0), - Enabled: ptr(false), - } - c.EVM = append(c.EVM, &evmcfg.EVMConfig{ - ChainID: utils.NewBigI(123123123), - Enabled: ptr(true), - }) - }) - relayExtenders2 := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: newCfg, KeyStore: keyStore.Eth()}) - legacyChains2 := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders2) - - orm2 := NewTestORM(t, db, legacyChains2, pipelineORM, bridgesORM, keyStore, config.Database()) + orm2 := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) jbs, err2 := orm2.FindJobsByPipelineSpecIDs([]int32{jb.PipelineSpecID}) require.NoError(t, err2) @@ -1297,7 +1268,7 @@ func Test_FindPipelineRuns(t *testing.T) { bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) _, bridge := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) @@ -1358,7 +1329,7 @@ func Test_PipelineRunsByJobID(t *testing.T) { bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) _, bridge := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) @@ -1419,7 +1390,7 @@ func Test_FindPipelineRunIDsByJobID(t *testing.T) { bridgesORM := bridges.NewORM(db, lggr, config.Database()) relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) @@ -1527,7 +1498,7 @@ func Test_FindPipelineRunsByIDs(t *testing.T) { bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) _, bridge := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) @@ -1583,9 +1554,7 @@ func Test_FindPipelineRunByID(t *testing.T) { pipelineORM := pipeline.NewORM(db, logger.TestLogger(t), config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) jb, err := directrequest.ValidatedDirectRequestSpec(testspecs.GetDirectRequestSpec()) require.NoError(t, err) @@ -1628,9 +1597,7 @@ func Test_FindJobWithoutSpecErrors(t *testing.T) { pipelineORM := pipeline.NewORM(db, logger.TestLogger(t), config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) jb, err := directrequest.ValidatedDirectRequestSpec(testspecs.GetDirectRequestSpec()) require.NoError(t, err) @@ -1648,7 +1615,7 @@ func Test_FindJobWithoutSpecErrors(t *testing.T) { jb, err = orm.FindJobWithoutSpecErrors(jobSpec.ID) require.NoError(t, err) - jbWithErrors, err := orm.FindJobTx(jobSpec.ID) + jbWithErrors, err := orm.FindJobTx(testutils.Context(t), jobSpec.ID) require.NoError(t, err) assert.Equal(t, len(jb.JobSpecErrors), 0) @@ -1667,9 +1634,7 @@ func Test_FindSpecErrorsByJobIDs(t *testing.T) { pipelineORM := pipeline.NewORM(db, logger.TestLogger(t), config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) jb, err := directrequest.ValidatedDirectRequestSpec(testspecs.GetDirectRequestSpec()) require.NoError(t, err) @@ -1705,7 +1670,7 @@ func Test_CountPipelineRunsByJobID(t *testing.T) { bridgesORM := bridges.NewORM(db, logger.TestLogger(t), config.Database()) relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - orm := NewTestORM(t, db, legacyChains, pipelineORM, bridgesORM, keyStore, config.Database()) + orm := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore, config.Database()) _, bridge := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) _, bridge2 := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{}, config.Database()) diff --git a/core/services/job/job_pipeline_orm_integration_test.go b/core/services/job/job_pipeline_orm_integration_test.go index a5d3ee984da..f1307753d29 100644 --- a/core/services/job/job_pipeline_orm_integration_test.go +++ b/core/services/job/job_pipeline_orm_integration_test.go @@ -4,14 +4,15 @@ import ( "testing" "time" - "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -44,7 +45,7 @@ func TestPipelineORM_Integration(t *testing.T) { answer2 [type=bridge name=election_winner index=1]; ` - config := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.JobPipeline.HTTPRequest.DefaultTimeout = models.MustNewDuration(30 * time.Millisecond) }) db := pgtest.NewSqlxDB(t) @@ -147,7 +148,7 @@ func TestPipelineORM_Integration(t *testing.T) { t.Run("creates runs", func(t *testing.T) { lggr := logger.TestLogger(t) - cfg := configtest2.NewTestGeneralConfig(t) + cfg := configtest.NewTestGeneralConfig(t) clearJobsDb(t, db) orm := pipeline.NewORM(db, logger.TestLogger(t), cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) btORM := bridges.NewORM(db, logger.TestLogger(t), cfg.Database()) @@ -155,7 +156,7 @@ func TestPipelineORM_Integration(t *testing.T) { legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) runner := pipeline.NewRunner(orm, btORM, config.JobPipeline(), cfg.WebServer(), legacyChains, nil, nil, lggr, nil, nil) - jobORM := NewTestORM(t, db, legacyChains, orm, btORM, keyStore, cfg.Database()) + jobORM := NewTestORM(t, db, orm, btORM, keyStore, cfg.Database()) dbSpec := makeVoterTurnoutOCRJobSpec(t, transmitterAddress, bridge.Name.String(), bridge2.Name.String()) diff --git a/core/services/job/mocks/orm.go b/core/services/job/mocks/orm.go index 3d858f81320..9e18573f4e5 100644 --- a/core/services/job/mocks/orm.go +++ b/core/services/job/mocks/orm.go @@ -247,23 +247,23 @@ func (_m *ORM) FindJobIDsWithBridge(name string) ([]int32, error) { return r0, r1 } -// FindJobTx provides a mock function with given fields: id -func (_m *ORM) FindJobTx(id int32) (job.Job, error) { - ret := _m.Called(id) +// FindJobTx provides a mock function with given fields: ctx, id +func (_m *ORM) FindJobTx(ctx context.Context, id int32) (job.Job, error) { + ret := _m.Called(ctx, id) var r0 job.Job var r1 error - if rf, ok := ret.Get(0).(func(int32) (job.Job, error)); ok { - return rf(id) + if rf, ok := ret.Get(0).(func(context.Context, int32) (job.Job, error)); ok { + return rf(ctx, id) } - if rf, ok := ret.Get(0).(func(int32) job.Job); ok { - r0 = rf(id) + if rf, ok := ret.Get(0).(func(context.Context, int32) job.Job); ok { + r0 = rf(ctx, id) } else { r0 = ret.Get(0).(job.Job) } - if rf, ok := ret.Get(1).(func(int32) error); ok { - r1 = rf(id) + if rf, ok := ret.Get(1).(func(context.Context, int32) error); ok { + r1 = rf(ctx, id) } else { r1 = ret.Error(1) } diff --git a/core/services/job/models.go b/core/services/job/models.go index a3dfce59996..05dcab831f1 100644 --- a/core/services/job/models.go +++ b/core/services/job/models.go @@ -14,9 +14,11 @@ import ( "github.com/pkg/errors" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-relay/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" clnull "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" @@ -233,35 +235,25 @@ func (pr *PipelineRun) SetID(value string) error { // 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"` - P2PV2Bootstrappers pq.StringArray `toml:"p2pv2Bootstrappers" db:"p2pv2_bootstrappers"` - IsBootstrapPeer bool `toml:"isBootstrapPeer"` - EncryptedOCRKeyBundleID *models.Sha256Hash `toml:"keyBundleID"` - EncryptedOCRKeyBundleIDEnv bool - TransmitterAddress *ethkey.EIP55Address `toml:"transmitterAddress"` - TransmitterAddressEnv bool - ObservationTimeout models.Interval `toml:"observationTimeout"` - ObservationTimeoutEnv bool - BlockchainTimeout models.Interval `toml:"blockchainTimeout"` - BlockchainTimeoutEnv bool - ContractConfigTrackerSubscribeInterval models.Interval `toml:"contractConfigTrackerSubscribeInterval"` - ContractConfigTrackerSubscribeIntervalEnv bool - ContractConfigTrackerPollInterval models.Interval `toml:"contractConfigTrackerPollInterval"` - ContractConfigTrackerPollIntervalEnv bool - ContractConfigConfirmations uint16 `toml:"contractConfigConfirmations"` - ContractConfigConfirmationsEnv bool - EVMChainID *utils.Big `toml:"evmChainID" db:"evm_chain_id"` - DatabaseTimeout *models.Interval `toml:"databaseTimeout"` - DatabaseTimeoutEnv bool - ObservationGracePeriod *models.Interval `toml:"observationGracePeriod"` - ObservationGracePeriodEnv bool - ContractTransmitterTransmitTimeout *models.Interval `toml:"contractTransmitterTransmitTimeout"` - ContractTransmitterTransmitTimeoutEnv bool - CaptureEATelemetry bool `toml:"captureEATelemetry"` - CreatedAt time.Time `toml:"-"` - UpdatedAt time.Time `toml:"-"` + ID int32 `toml:"-"` + ContractAddress ethkey.EIP55Address `toml:"contractAddress"` + P2PBootstrapPeers pq.StringArray `toml:"p2pBootstrapPeers" db:"p2p_bootstrap_peers"` + P2PV2Bootstrappers pq.StringArray `toml:"p2pv2Bootstrappers" db:"p2pv2_bootstrappers"` + IsBootstrapPeer bool `toml:"isBootstrapPeer"` + EncryptedOCRKeyBundleID *models.Sha256Hash `toml:"keyBundleID"` + TransmitterAddress *ethkey.EIP55Address `toml:"transmitterAddress"` + ObservationTimeout models.Interval `toml:"observationTimeout"` + BlockchainTimeout models.Interval `toml:"blockchainTimeout"` + ContractConfigTrackerSubscribeInterval models.Interval `toml:"contractConfigTrackerSubscribeInterval"` + ContractConfigTrackerPollInterval models.Interval `toml:"contractConfigTrackerPollInterval"` + ContractConfigConfirmations uint16 `toml:"contractConfigConfirmations"` + EVMChainID *utils.Big `toml:"evmChainID" db:"evm_chain_id"` + DatabaseTimeout *models.Interval `toml:"databaseTimeout"` + ObservationGracePeriod *models.Interval `toml:"observationGracePeriod"` + ContractTransmitterTransmitTimeout *models.Interval `toml:"contractTransmitterTransmitTimeout"` + CaptureEATelemetry bool `toml:"captureEATelemetry"` + CreatedAt time.Time `toml:"-"` + UpdatedAt time.Time `toml:"-"` } // GetID is a getter function that returns the ID of the spec. @@ -438,15 +430,14 @@ func (w *WebhookSpec) SetID(value string) error { } type DirectRequestSpec struct { - ID int32 `toml:"-"` - ContractAddress ethkey.EIP55Address `toml:"contractAddress"` - MinIncomingConfirmations clnull.Uint32 `toml:"minIncomingConfirmations"` - MinIncomingConfirmationsEnv bool `toml:"minIncomingConfirmationsEnv"` - Requesters models.AddressCollection `toml:"requesters"` - MinContractPayment *assets.Link `toml:"minContractPaymentLinkJuels"` - EVMChainID *utils.Big `toml:"evmChainID"` - CreatedAt time.Time `toml:"-"` - UpdatedAt time.Time `toml:"-"` + ID int32 `toml:"-"` + ContractAddress ethkey.EIP55Address `toml:"contractAddress"` + MinIncomingConfirmations clnull.Uint32 `toml:"minIncomingConfirmations"` + Requesters models.AddressCollection `toml:"requesters"` + MinContractPayment *commonassets.Link `toml:"minContractPaymentLinkJuels"` + EVMChainID *utils.Big `toml:"evmChainID"` + CreatedAt time.Time `toml:"-"` + UpdatedAt time.Time `toml:"-"` } type CronSpec struct { @@ -484,7 +475,7 @@ type FluxMonitorSpec struct { DrumbeatSchedule string DrumbeatRandomDelay time.Duration DrumbeatEnabled bool - MinPayment *assets.Link + MinPayment *commonassets.Link EVMChainID *utils.Big `toml:"evmChainID"` CreatedAt time.Time `toml:"-"` UpdatedAt time.Time `toml:"-"` @@ -522,13 +513,11 @@ type VRFSpec struct { 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"` // Optional, defaults to 24hr if not provided. + PollPeriod time.Duration `toml:"pollPeriod"` // For v2 jobs + RequestedConfsDelay int64 `toml:"requestedConfsDelay"` // For v2 jobs. Optional, defaults to 0 if not provided. + RequestTimeout time.Duration `toml:"requestTimeout"` // Optional, defaults to 24hr if not provided. // GasLanePrice specifies the gas lane price for this VRF job. // If the specified keys in FromAddresses do not have the provided gas price the job diff --git a/core/services/job/orm.go b/core/services/job/orm.go index cbdd7ebfae6..c5f533c3d20 100644 --- a/core/services/job/orm.go +++ b/core/services/job/orm.go @@ -16,13 +16,11 @@ import ( "github.com/pkg/errors" "go.uber.org/multierr" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/bridges" - "github.com/smartcontractkit/chainlink/v2/core/chains" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -51,7 +49,7 @@ type ORM interface { InsertJob(job *Job, qopts ...pg.QOpt) error CreateJob(jb *Job, qopts ...pg.QOpt) error FindJobs(offset, limit int) ([]Job, int, error) - FindJobTx(id int32) (Job, error) + FindJobTx(ctx context.Context, id int32) (Job, error) FindJob(ctx context.Context, id int32) (Job, error) FindJobByExternalJobID(uuid uuid.UUID, qopts ...pg.QOpt) (Job, error) FindJobIDByAddress(address ethkey.EIP55Address, evmChainID *utils.Big, qopts ...pg.QOpt) (int32, error) @@ -85,35 +83,25 @@ type ORMConfig interface { } type orm struct { - q pg.Q - legacyChains evm.LegacyChainContainer - keyStore keystore.Master - pipelineORM pipeline.ORM - lggr logger.SugaredLogger - cfg pg.QConfig - bridgeORM bridges.ORM + q pg.Q + keyStore keystore.Master + pipelineORM pipeline.ORM + lggr logger.SugaredLogger + cfg pg.QConfig + bridgeORM bridges.ORM } var _ ORM = (*orm)(nil) -func NewORM( - db *sqlx.DB, - legacyChains evm.LegacyChainContainer, - pipelineORM pipeline.ORM, - bridgeORM bridges.ORM, - keyStore keystore.Master, // needed to validation key properties on new job creation - lggr logger.Logger, - cfg pg.QConfig, -) *orm { +func NewORM(db *sqlx.DB, pipelineORM pipeline.ORM, bridgeORM bridges.ORM, keyStore keystore.Master, lggr logger.Logger, cfg pg.QConfig) *orm { namedLogger := logger.Sugared(lggr.Named("JobORM")) return &orm{ - q: pg.NewQ(db, namedLogger, cfg), - legacyChains: legacyChains, - keyStore: keyStore, - pipelineORM: pipelineORM, - bridgeORM: bridgeORM, - lggr: namedLogger, - cfg: cfg, + q: pg.NewQ(db, namedLogger, cfg), + keyStore: keyStore, + pipelineORM: pipelineORM, + bridgeORM: bridgeORM, + lggr: namedLogger, + cfg: cfg, } } func (o *orm) Close() error { @@ -704,62 +692,25 @@ func (o *orm) FindJobs(offset, limit int) (jobs []Job, count int, err error) { if err != nil { return err } - for i := range jobs { - err = multierr.Combine(err, o.LoadEnvConfigVars(&jobs[i])) - } + return nil }) return jobs, int(count), err } -func (o *orm) LoadEnvConfigVars(jb *Job) error { - if jb.OCROracleSpec != nil { - ch, err := o.legacyChains.Get(jb.OCROracleSpec.EVMChainID.String()) - if err != nil { - return err - } - newSpec, err := LoadEnvConfigVarsOCR(ch.Config().EVM().OCR(), ch.Config().OCR(), *jb.OCROracleSpec) - if err != nil { - return err - } - jb.OCROracleSpec = newSpec - } else if jb.VRFSpec != nil { - ch, err := o.legacyChains.Get(jb.VRFSpec.EVMChainID.String()) - if err != nil { - return err - } - jb.VRFSpec = LoadEnvConfigVarsVRF(ch.Config().EVM(), *jb.VRFSpec) - } else if jb.DirectRequestSpec != nil { - ch, err := o.legacyChains.Get(jb.DirectRequestSpec.EVMChainID.String()) - if err != nil { - return err - } - jb.DirectRequestSpec = LoadEnvConfigVarsDR(ch.Config().EVM(), *jb.DirectRequestSpec) - } - return nil -} - -type DRSpecConfig interface { - MinIncomingConfirmations() uint32 -} - -func LoadEnvConfigVarsVRF(cfg DRSpecConfig, vrfs VRFSpec) *VRFSpec { +func LoadDefaultVRFPollPeriod(vrfs VRFSpec) *VRFSpec { if vrfs.PollPeriod == 0 { - vrfs.PollPeriodEnv = true vrfs.PollPeriod = 5 * time.Second } return &vrfs } -func LoadEnvConfigVarsDR(cfg DRSpecConfig, drs DirectRequestSpec) *DirectRequestSpec { - // Take the largest of the global vs specific. - minIncomingConfirmations := cfg.MinIncomingConfirmations() - if !drs.MinIncomingConfirmations.Valid || drs.MinIncomingConfirmations.Uint32 < minIncomingConfirmations { - drs.MinIncomingConfirmationsEnv = true - drs.MinIncomingConfirmations = null.Uint32From(minIncomingConfirmations) +// SetDRMinIncomingConfirmations takes the largest of the global vs specific. +func SetDRMinIncomingConfirmations(defaultMinIncomingConfirmations uint32, drs DirectRequestSpec) *DirectRequestSpec { + if !drs.MinIncomingConfirmations.Valid || drs.MinIncomingConfirmations.Uint32 < defaultMinIncomingConfirmations { + drs.MinIncomingConfirmations = null.Uint32From(defaultMinIncomingConfirmations) } - return &drs } @@ -773,38 +724,30 @@ type OCRConfig interface { TransmitterAddress() (ethkey.EIP55Address, error) } -// LoadEnvConfigVarsLocalOCR loads local OCR env vars into the OCROracleSpec. -func LoadEnvConfigVarsLocalOCR(evmOcrCfg evmconfig.OCR, os OCROracleSpec, ocrCfg OCRConfig) *OCROracleSpec { +// LoadConfigVarsLocalOCR loads local OCR vars into the OCROracleSpec. +func LoadConfigVarsLocalOCR(evmOcrCfg evmconfig.OCR, os OCROracleSpec, ocrCfg OCRConfig) *OCROracleSpec { if os.ObservationTimeout == 0 { - os.ObservationTimeoutEnv = true os.ObservationTimeout = models.Interval(ocrCfg.ObservationTimeout()) } if os.BlockchainTimeout == 0 { - os.BlockchainTimeoutEnv = true os.BlockchainTimeout = models.Interval(ocrCfg.BlockchainTimeout()) } if os.ContractConfigTrackerSubscribeInterval == 0 { - os.ContractConfigTrackerSubscribeIntervalEnv = true os.ContractConfigTrackerSubscribeInterval = models.Interval(ocrCfg.ContractSubscribeInterval()) } if os.ContractConfigTrackerPollInterval == 0 { - os.ContractConfigTrackerPollIntervalEnv = true os.ContractConfigTrackerPollInterval = models.Interval(ocrCfg.ContractPollInterval()) } if os.ContractConfigConfirmations == 0 { - os.ContractConfigConfirmationsEnv = true os.ContractConfigConfirmations = evmOcrCfg.ContractConfirmations() } if os.DatabaseTimeout == nil { - os.DatabaseTimeoutEnv = true os.DatabaseTimeout = models.NewInterval(evmOcrCfg.DatabaseTimeout()) } if os.ObservationGracePeriod == nil { - os.ObservationGracePeriodEnv = true os.ObservationGracePeriod = models.NewInterval(evmOcrCfg.ObservationGracePeriod()) } if os.ContractTransmitterTransmitTimeout == nil { - os.ContractTransmitterTransmitTimeoutEnv = true os.ContractTransmitterTransmitTimeout = models.NewInterval(evmOcrCfg.ContractTransmitterTransmitTimeout()) } os.CaptureEATelemetry = ocrCfg.CaptureEATelemetry() @@ -812,15 +755,14 @@ func LoadEnvConfigVarsLocalOCR(evmOcrCfg evmconfig.OCR, os OCROracleSpec, ocrCfg return &os } -// LoadEnvConfigVarsOCR loads OCR env vars into the OCROracleSpec. -func LoadEnvConfigVarsOCR(evmOcrCfg evmconfig.OCR, ocrCfg OCRConfig, os OCROracleSpec) (*OCROracleSpec, error) { +// LoadConfigVarsOCR loads OCR config vars into the OCROracleSpec. +func LoadConfigVarsOCR(evmOcrCfg evmconfig.OCR, ocrCfg OCRConfig, os OCROracleSpec) (*OCROracleSpec, error) { if os.TransmitterAddress == nil { ta, err := ocrCfg.TransmitterAddress() if !errors.Is(errors.Cause(err), config.ErrEnvUnset) { if err != nil { return nil, err } - os.TransmitterAddressEnv = true os.TransmitterAddress = &ta } } @@ -834,15 +776,14 @@ func LoadEnvConfigVarsOCR(evmOcrCfg evmconfig.OCR, ocrCfg OCRConfig, os OCROracl if err != nil { return nil, err } - os.EncryptedOCRKeyBundleIDEnv = true os.EncryptedOCRKeyBundleID = &encryptedOCRKeyBundleID } - return LoadEnvConfigVarsLocalOCR(evmOcrCfg, os, ocrCfg), nil + return LoadConfigVarsLocalOCR(evmOcrCfg, os, ocrCfg), nil } -func (o *orm) FindJobTx(id int32) (Job, error) { - ctx, cancel := context.WithTimeout(context.Background(), o.cfg.DefaultQueryTimeout()) +func (o *orm) FindJobTx(ctx context.Context, id int32) (Job, error) { + ctx, cancel := context.WithTimeout(ctx, o.cfg.DefaultQueryTimeout()) defer cancel() return o.FindJob(ctx, id) } @@ -872,7 +813,7 @@ func (o *orm) FindJobWithoutSpecErrors(id int32) (jb Job, err error) { return jb, errors.Wrap(err, "FindJobWithoutSpecErrors failed") } - return jb, o.LoadEnvConfigVars(&jb) + return jb, nil } // FindSpecErrorsByJobIDs returns all jobs spec errors by jobs IDs @@ -961,7 +902,7 @@ func (o *orm) findJob(jb *Job, col string, arg interface{}, qopts ...pg.QOpt) er if err != nil { return errors.Wrap(err, "findJob failed") } - return o.LoadEnvConfigVars(jb) + return nil } func (o *orm) FindJobIDsWithBridge(name string) (jids []int32, err error) { @@ -1051,11 +992,13 @@ func (o *orm) loadPipelineRunIDs(jobID *int32, offset, limit int, tx pg.Queryer) // range minID <-> maxID. for n := int64(1000); maxID > 0 && len(ids) < limit; n *= 2 { + var batch []int64 minID := maxID - n - if err = tx.Select(&ids, stmt, offset, limit-len(ids), minID, maxID); err != nil { + if err = tx.Select(&batch, stmt, offset, limit-len(ids), minID, maxID); err != nil { err = errors.Wrap(err, "error loading runs") return } + ids = append(ids, batch...) if offset > 0 { if len(ids) > 0 { // If we're already receiving rows back, then we no longer need an offset @@ -1204,13 +1147,6 @@ func (o *orm) FindJobsByPipelineSpecIDs(ids []int32) ([]Job, error) { if err != nil { return err } - for i := range jbs { - err = o.LoadEnvConfigVars(&jbs[i]) - //We must return the jobs even if the chainID is disabled - if err != nil && !errors.Is(err, chains.ErrNoSuchChainID) { - return err - } - } return nil }) diff --git a/core/services/job/orm_test.go b/core/services/job/orm_test.go index 15a2432f9e9..2e19669417a 100644 --- a/core/services/job/orm_test.go +++ b/core/services/job/orm_test.go @@ -3,13 +3,13 @@ package job_test import ( "testing" - "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink/v2/core/bridges" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/logger" clnull "github.com/smartcontractkit/chainlink/v2/core/null" @@ -18,34 +18,37 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/store/models" ) -func NewTestORM(t *testing.T, db *sqlx.DB, legacyChains evm.LegacyChainContainer, pipelineORM pipeline.ORM, bridgeORM bridges.ORM, keyStore keystore.Master, cfg pg.QConfig) job.ORM { - o := job.NewORM(db, legacyChains, pipelineORM, bridgeORM, keyStore, logger.TestLogger(t), cfg) - t.Cleanup(func() { o.Close() }) +func NewTestORM(t *testing.T, db *sqlx.DB, pipelineORM pipeline.ORM, bridgeORM bridges.ORM, keyStore keystore.Master, cfg pg.QConfig) job.ORM { + o := job.NewORM(db, pipelineORM, bridgeORM, keyStore, logger.TestLogger(t), cfg) + t.Cleanup(func() { assert.NoError(t, o.Close()) }) return o } -func TestLoadEnvConfigVarsLocalOCR(t *testing.T) { +func TestLoadConfigVarsLocalOCR(t *testing.T) { t.Parallel() config := configtest.NewTestGeneralConfig(t) chainConfig := evmtest.NewChainScopedConfig(t, config) jobSpec := &job.OCROracleSpec{} - jobSpec = job.LoadEnvConfigVarsLocalOCR(chainConfig.EVM().OCR(), *jobSpec, chainConfig.OCR()) + jobSpec = job.LoadConfigVarsLocalOCR(chainConfig.EVM().OCR(), *jobSpec, chainConfig.OCR()) + + require.Equal(t, models.Interval(chainConfig.OCR().ObservationTimeout()), jobSpec.ObservationTimeout) + require.Equal(t, models.Interval(chainConfig.OCR().BlockchainTimeout()), jobSpec.BlockchainTimeout) + require.Equal(t, models.Interval(chainConfig.OCR().ContractSubscribeInterval()), jobSpec.ContractConfigTrackerSubscribeInterval) + require.Equal(t, models.Interval(chainConfig.OCR().ContractPollInterval()), jobSpec.ContractConfigTrackerPollInterval) + require.Equal(t, chainConfig.OCR().CaptureEATelemetry(), jobSpec.CaptureEATelemetry) - require.True(t, jobSpec.ObservationTimeoutEnv) - require.True(t, jobSpec.BlockchainTimeoutEnv) - require.True(t, jobSpec.ContractConfigTrackerSubscribeIntervalEnv) - require.True(t, jobSpec.ContractConfigTrackerPollIntervalEnv) - require.True(t, jobSpec.ContractConfigConfirmationsEnv) - require.True(t, jobSpec.DatabaseTimeoutEnv) - require.True(t, jobSpec.ObservationGracePeriodEnv) - require.True(t, jobSpec.ContractTransmitterTransmitTimeoutEnv) + require.Equal(t, chainConfig.EVM().OCR().ContractConfirmations(), jobSpec.ContractConfigConfirmations) + require.Equal(t, models.Interval(chainConfig.EVM().OCR().DatabaseTimeout()), *jobSpec.DatabaseTimeout) + require.Equal(t, models.Interval(chainConfig.EVM().OCR().ObservationGracePeriod()), *jobSpec.ObservationGracePeriod) + require.Equal(t, models.Interval(chainConfig.EVM().OCR().ContractTransmitterTransmitTimeout()), *jobSpec.ContractTransmitterTransmitTimeout) } -func TestLoadEnvConfigVarsDR(t *testing.T) { +func TestSetDRMinIncomingConfirmations(t *testing.T) { t.Parallel() config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { @@ -58,15 +61,14 @@ func TestLoadEnvConfigVarsDR(t *testing.T) { MinIncomingConfirmations: clnull.Uint32From(10), } - drs10 := job.LoadEnvConfigVarsDR(chainConfig.EVM(), jobSpec10) - assert.True(t, drs10.MinIncomingConfirmationsEnv) + drs10 := job.SetDRMinIncomingConfirmations(chainConfig.EVM().MinIncomingConfirmations(), jobSpec10) + assert.Equal(t, uint32(100), drs10.MinIncomingConfirmations.Uint32) jobSpec200 := job.DirectRequestSpec{ MinIncomingConfirmations: clnull.Uint32From(200), } - drs200 := job.LoadEnvConfigVarsDR(chainConfig.EVM(), jobSpec200) - assert.False(t, drs200.MinIncomingConfirmationsEnv) + drs200 := job.SetDRMinIncomingConfirmations(chainConfig.EVM().MinIncomingConfirmations(), jobSpec200) assert.True(t, drs200.MinIncomingConfirmations.Valid) assert.Equal(t, uint32(200), drs200.MinIncomingConfirmations.Uint32) } diff --git a/core/services/job/runner_integration_test.go b/core/services/job/runner_integration_test.go index 05782f95ce8..eb6af3607f3 100644 --- a/core/services/job/runner_integration_test.go +++ b/core/services/job/runner_integration_test.go @@ -13,39 +13,38 @@ import ( "testing" "time" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" - evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" - "github.com/smartcontractkit/chainlink/v2/core/services/srvctest" - "github.com/smartcontractkit/chainlink/v2/core/utils" + "github.com/google/uuid" + "github.com/pelletier/go-toml" + "github.com/pkg/errors" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" clhttptest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/httptest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/ocr" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/srvctest" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" "github.com/smartcontractkit/chainlink/v2/core/services/webhook" "github.com/smartcontractkit/chainlink/v2/core/store/models" + "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/v2/core/web" - - "github.com/google/uuid" - "github.com/pelletier/go-toml" - "github.com/pkg/errors" - "github.com/shopspring/decimal" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v4" ) var monitoringEndpoint = telemetry.MonitoringEndpointGenerator(&telemetry.NoopAgent{}) @@ -58,7 +57,7 @@ func TestRunner(t *testing.T) { _, transmitterAddress := cltest.MustInsertRandomKey(t, ethKeyStore) require.NoError(t, keyStore.OCR().Add(cltest.DefaultOCRKey)) - config := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.P2P.V1.Enabled = ptr(true) c.P2P.V1.DefaultBootstrapPeers = &[]string{ "/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju", @@ -86,7 +85,7 @@ func TestRunner(t *testing.T) { c := clhttptest.NewTestLocalOnlyHTTPClient() runner := pipeline.NewRunner(pipelineORM, btORM, config.JobPipeline(), config.WebServer(), legacyChains, nil, nil, logger.TestLogger(t), c, c) - jobORM := NewTestORM(t, db, legacyChains, pipelineORM, btORM, keyStore, config.Database()) + jobORM := NewTestORM(t, db, pipelineORM, btORM, keyStore, config.Database()) _, placeHolderAddress := cltest.MustInsertRandomKey(t, keyStore.Eth()) @@ -429,45 +428,7 @@ answer1 [type=median index=0]; } }) - t.Run("missing required env vars", func(t *testing.T) { - s := ` - type = "offchainreporting" - schemaVersion = 1 - contractAddress = "%s" - isBootstrapPeer = false - evmChainID = "0" - observationSource = """ -ds1 [type=http method=GET url="%s" allowunrestrictednetworkaccess="true" %s]; -ds1_parse [type=jsonparse path="USD" lax=true]; -ds1 -> ds1_parse; -""" -` - s = fmt.Sprintf(s, cltest.NewEIP55Address(), "http://blah.com", "") - jb, err := ocr.ValidatedOracleSpecToml(legacyChains, s) - require.NoError(t, err) - err = toml.Unmarshal([]byte(s), &jb) - require.NoError(t, err) - jb.MaxTaskDuration = models.Interval(cltest.MustParseDuration(t, "1s")) - err = jobORM.CreateJob(&jb) - require.NoError(t, err) - sd := ocr.NewDelegate( - db, - jobORM, - keyStore, - nil, - nil, - nil, - legacyChains, - logger.TestLogger(t), - config.Database(), - srvctest.Start(t, utils.NewMailboxMonitor(t.Name())), - ) - _, err = sd.ServicesForSpec(jb) - // We expect this to fail as neither the required vars are not set either via the env nor the job itself. - require.Error(t, err) - }) - - t.Run("use env for minimal bootstrap", func(t *testing.T) { + t.Run("minimal bootstrap", func(t *testing.T) { s := ` type = "offchainreporting" schemaVersion = 1 @@ -489,53 +450,7 @@ ds1 -> ds1_parse; assert.NoError(t, err) pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config.P2P(), config.OCR(), config.Database(), db, lggr) require.NoError(t, pw.Start(testutils.Context(t))) - sd := ocr.NewDelegate( - db, - jobORM, - keyStore, - nil, - pw, - monitoringEndpoint, - legacyChains, - lggr, - config.Database(), - srvctest.Start(t, utils.NewMailboxMonitor(t.Name())), - ) - _, err = sd.ServicesForSpec(jb) - require.NoError(t, err) - }) - - t.Run("use env for minimal non-bootstrap", func(t *testing.T) { - s := ` - type = "offchainreporting" - schemaVersion = 1 - contractAddress = "%s" - isBootstrapPeer = false - observationTimeout = "15s" - evmChainID = "0" - observationSource = """ -ds1 [type=http method=GET url="%s" allowunrestrictednetworkaccess="true" %s]; -ds1_parse [type=jsonparse path="USD" lax=true]; -ds1 -> ds1_parse; -""" -` - s = fmt.Sprintf(s, cltest.NewEIP55Address(), "http://blah.com", "") - jb, err := ocr.ValidatedOracleSpecToml(legacyChains, s) - require.NoError(t, err) - err = toml.Unmarshal([]byte(s), &jb) - require.NoError(t, err) - jb.MaxTaskDuration = models.Interval(cltest.MustParseDuration(t, "1s")) - err = jobORM.CreateJob(&jb) - require.NoError(t, err) - // Assert the override - assert.Equal(t, jb.OCROracleSpec.ObservationTimeout, models.Interval(cltest.MustParseDuration(t, "15s"))) - // Assert that this is default - assert.Equal(t, models.Interval(20000000000), jb.OCROracleSpec.BlockchainTimeout) - assert.Equal(t, models.Interval(cltest.MustParseDuration(t, "1s")), jb.MaxTaskDuration) - - lggr := logger.TestLogger(t) - pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config.P2P(), config.OCR(), config.Database(), db, lggr) - require.NoError(t, pw.Start(testutils.Context(t))) + t.Cleanup(func() { assert.NoError(t, pw.Close()) }) sd := ocr.NewDelegate( db, jobORM, @@ -570,6 +485,7 @@ ds1 -> ds1_parse; lggr := logger.TestLogger(t) pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config.P2P(), config.OCR(), config.Database(), db, lggr) require.NoError(t, pw.Start(testutils.Context(t))) + t.Cleanup(func() { assert.NoError(t, pw.Close()) }) sd := ocr.NewDelegate( db, jobORM, @@ -598,6 +514,7 @@ ds1 -> ds1_parse; lggr := logger.TestLogger(t) pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config.P2P(), config.OCR(), config.Database(), db, lggr) require.NoError(t, pw.Start(testutils.Context(t))) + t.Cleanup(func() { assert.NoError(t, pw.Close()) }) sd := ocr.NewDelegate( db, jobORM, @@ -614,6 +531,75 @@ ds1 -> ds1_parse; require.NoError(t, err) }) + t.Run("test enhanced telemetry service creation", func(t *testing.T) { + testCases := []struct { + jbCaptureEATelemetry bool + specCaptureEATelemetry bool + expected bool + }{{false, false, false}, + {true, false, false}, + {false, true, true}, + {true, true, true}, + } + + for _, tc := range testCases { + + config = configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.P2P.V1.Enabled = ptr(true) + c.OCR.CaptureEATelemetry = ptr(tc.specCaptureEATelemetry) + }) + + relayExtenders = evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, Client: ethClient, GeneralConfig: config, KeyStore: ethKeyStore}) + legacyChains = evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) + + kb, err := keyStore.OCR().Create() + require.NoError(t, err) + + s := fmt.Sprintf(minimalNonBootstrapTemplate, cltest.NewEIP55Address(), transmitterAddress.Hex(), kb.ID(), "http://blah.com", "") + jb, err := ocr.ValidatedOracleSpecToml(legacyChains, s) + require.NoError(t, err) + err = toml.Unmarshal([]byte(s), &jb) + require.NoError(t, err) + + jb.MaxTaskDuration = models.Interval(cltest.MustParseDuration(t, "1s")) + err = jobORM.CreateJob(&jb) + require.NoError(t, err) + assert.Equal(t, jb.MaxTaskDuration, models.Interval(cltest.MustParseDuration(t, "1s"))) + + lggr := logger.TestLogger(t) + pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config.P2P(), config.OCR(), config.Database(), db, lggr) + require.NoError(t, pw.Start(testutils.Context(t))) + t.Cleanup(func() { assert.NoError(t, pw.Close()) }) + sd := ocr.NewDelegate( + db, + jobORM, + keyStore, + nil, + pw, + monitoringEndpoint, + legacyChains, + lggr, + config.Database(), + srvctest.Start(t, utils.NewMailboxMonitor(t.Name())), + ) + + jb.OCROracleSpec.CaptureEATelemetry = tc.jbCaptureEATelemetry + services, err := sd.ServicesForSpec(jb) + require.NoError(t, err) + + enhancedTelemetryServiceCreated := false + for _, service := range services { + _, ok := service.(*ocrcommon.EnhancedTelemetryService[ocrcommon.EnhancedTelemetryData]) + enhancedTelemetryServiceCreated = ok + if enhancedTelemetryServiceCreated { + break + } + } + + require.Equal(t, tc.expected, enhancedTelemetryServiceCreated) + } + }) + t.Run("test job spec error is created", func(t *testing.T) { // Create a keystore with an ocr key bundle and p2p key. kb, err := keyStore.OCR().Create() @@ -628,7 +614,7 @@ ds1 -> ds1_parse; lggr := logger.TestLogger(t) pw := ocrcommon.NewSingletonPeerWrapper(keyStore, config.P2P(), config.OCR(), config.Database(), db, lggr) require.NoError(t, pw.Start(testutils.Context(t))) - + t.Cleanup(func() { assert.NoError(t, pw.Close()) }) sd := ocr.NewDelegate( db, jobORM, @@ -759,16 +745,13 @@ func TestRunner_Success_Callback_AsyncJob(t *testing.T) { ethClient := cltest.NewEthMocksWithStartupAssertions(t) - cfg := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { t := true c.JobPipeline.ExternalInitiatorsEnabled = &t c.Database.Listener.FallbackPollInterval = models.MustNewDuration(10 * time.Millisecond) }) app := cltest.NewApplicationWithConfig(t, cfg, ethClient, cltest.UseRealExternalInitiatorManager) - keyStore := cltest.NewKeyStore(t, app.GetSqlxDB(), pgtest.NewQConfig(true)) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: app.GetSqlxDB(), Client: ethClient, GeneralConfig: cfg, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) require.NoError(t, app.Start(testutils.Context(t))) var ( @@ -899,7 +882,7 @@ func TestRunner_Success_Callback_AsyncJob(t *testing.T) { pipelineORM := pipeline.NewORM(app.GetSqlxDB(), logger.TestLogger(t), cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(app.GetSqlxDB(), logger.TestLogger(t), cfg.Database()) - jobORM := NewTestORM(t, app.GetSqlxDB(), legacyChains, pipelineORM, bridgesORM, app.KeyStore, cfg.Database()) + jobORM := NewTestORM(t, app.GetSqlxDB(), pipelineORM, bridgesORM, app.KeyStore, cfg.Database()) // Trigger v2/resume select { @@ -941,17 +924,13 @@ func TestRunner_Error_Callback_AsyncJob(t *testing.T) { ethClient := cltest.NewEthMocksWithStartupAssertions(t) - cfg := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { t := true c.JobPipeline.ExternalInitiatorsEnabled = &t c.Database.Listener.FallbackPollInterval = models.MustNewDuration(10 * time.Millisecond) }) app := cltest.NewApplicationWithConfig(t, cfg, ethClient, cltest.UseRealExternalInitiatorManager) - keyStore := cltest.NewKeyStore(t, app.GetSqlxDB(), pgtest.NewQConfig(true)) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: app.GetSqlxDB(), Client: ethClient, GeneralConfig: cfg, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - require.NoError(t, app.Start(testutils.Context(t))) var ( @@ -1080,7 +1059,7 @@ func TestRunner_Error_Callback_AsyncJob(t *testing.T) { pipelineORM := pipeline.NewORM(app.GetSqlxDB(), logger.TestLogger(t), cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) bridgesORM := bridges.NewORM(app.GetSqlxDB(), logger.TestLogger(t), cfg.Database()) - jobORM := NewTestORM(t, app.GetSqlxDB(), legacyChains, pipelineORM, bridgesORM, app.KeyStore, cfg.Database()) + jobORM := NewTestORM(t, app.GetSqlxDB(), pipelineORM, bridgesORM, app.KeyStore, cfg.Database()) // Trigger v2/resume select { diff --git a/core/services/job/spawner.go b/core/services/job/spawner.go index 200c25deb37..5ed017b8743 100644 --- a/core/services/job/spawner.go +++ b/core/services/job/spawner.go @@ -9,12 +9,11 @@ import ( pkgerrors "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - relayservices "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -26,7 +25,7 @@ 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.ServiceCtx + services.Service // CreateJob creates a new job and starts services. // All services must start without errors for the job to be active. @@ -42,18 +41,23 @@ type ( StartService(ctx context.Context, spec Job, qopts ...pg.QOpt) error } + Checker interface { + Register(service services.HealthReporter) error + Unregister(name string) error + } + spawner struct { + services.StateMachine orm ORM config Config - checker services.Checker + checker Checker jobTypeDelegates map[Type]Delegate activeJobs map[int32]activeJob activeJobsMu sync.RWMutex q pg.Q lggr logger.Logger - utils.StartStopOnce - chStop utils.StopChan + chStop services.StopChan lbDependentAwaiters []utils.DependentAwaiter } @@ -86,7 +90,7 @@ type ( var _ Spawner = (*spawner)(nil) -func NewSpawner(orm ORM, config Config, checker services.Checker, jobTypeDelegates map[Type]Delegate, db *sqlx.DB, lggr logger.Logger, lbDependentAwaiters []utils.DependentAwaiter) *spawner { +func NewSpawner(orm ORM, config Config, checker Checker, jobTypeDelegates map[Type]Delegate, db *sqlx.DB, lggr logger.Logger, lbDependentAwaiters []utils.DependentAwaiter) *spawner { namedLogger := lggr.Named("JobSpawner") s := &spawner{ orm: orm, @@ -96,7 +100,7 @@ func NewSpawner(orm ORM, config Config, checker services.Checker, jobTypeDelegat q: pg.NewQ(db, namedLogger, config), lggr: namedLogger, activeJobs: make(map[int32]activeJob), - chStop: make(chan struct{}), + chStop: make(services.StopChan), lbDependentAwaiters: lbDependentAwaiters, } return s @@ -170,7 +174,7 @@ func (js *spawner) stopService(jobID int32) { for i := len(aj.services) - 1; i >= 0; i-- { service := aj.services[i] sLggr := lggr.With("subservice", i, "serviceType", reflect.TypeOf(service)) - if c, ok := service.(relayservices.HealthReporter); ok { + if c, ok := service.(services.HealthReporter); ok { if err := js.checker.Unregister(c.Name()); err != nil { sLggr.Warnw("Failed to unregister service from health checker", "err", err) } @@ -230,7 +234,7 @@ func (js *spawner) StartService(ctx context.Context, jb Job, qopts ...pg.QOpt) e lggr.Criticalw("Error starting service for job", "err", err) return err } - if c, ok := srv.(relayservices.HealthReporter); ok { + if c, ok := srv.(services.HealthReporter); ok { err = js.checker.Register(c) if err != nil { lggr.Errorw("Error registering service with health checker", "err", err) diff --git a/core/services/job/spawner_test.go b/core/services/job/spawner_test.go index 4e28dcdf062..0ad76491438 100644 --- a/core/services/job/spawner_test.go +++ b/core/services/job/spawner_test.go @@ -9,16 +9,17 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - "github.com/smartcontractkit/chainlink-relay/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/bridges" mocklp "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -73,7 +74,7 @@ func (g *relayGetter) Get(id relay.ID) (loop.Relayer, error) { func TestSpawner_CreateJobDeleteJob(t *testing.T) { t.Parallel() - config := configtest2.NewTestGeneralConfig(t) + config := configtest.NewTestGeneralConfig(t) db := pgtest.NewSqlxDB(t) keyStore := cltest.NewKeyStore(t, db, config.Database()) ethKeyStore := keyStore.Eth() @@ -97,7 +98,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) t.Run("should respect its dependents", func(t *testing.T) { lggr := logger.TestLogger(t) - orm := NewTestORM(t, db, legacyChains, pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()), bridges.NewORM(db, lggr, config.Database()), keyStore, config.Database()) + orm := NewTestORM(t, db, pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()), bridges.NewORM(db, lggr, config.Database()), keyStore, config.Database()) a := utils.NewDependentAwaiter() a.AddDependents(1) spawner := job.NewSpawner(orm, config.Database(), noopChecker{}, map[job.Type]job.Delegate{}, db, lggr, []utils.DependentAwaiter{a}) @@ -120,7 +121,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { jobB := makeOCRJobSpec(t, address, bridge.Name.String(), bridge2.Name.String()) lggr := logger.TestLogger(t) - orm := NewTestORM(t, db, legacyChains, pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()), bridges.NewORM(db, lggr, config.Database()), keyStore, config.Database()) + orm := NewTestORM(t, db, pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()), bridges.NewORM(db, lggr, config.Database()), keyStore, config.Database()) eventuallyA := cltest.NewAwaiter() serviceA1 := mocks.NewServiceCtx(t) @@ -185,7 +186,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { serviceA2.On("Start", mock.Anything).Return(nil).Once().Run(func(mock.Arguments) { eventually.ItHappened() }) lggr := logger.TestLogger(t) - orm := NewTestORM(t, db, legacyChains, pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()), bridges.NewORM(db, lggr, config.Database()), keyStore, config.Database()) + orm := NewTestORM(t, db, pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()), bridges.NewORM(db, lggr, config.Database()), keyStore, config.Database()) mailMon := srvctest.Start(t, utils.NewMailboxMonitor(t.Name())) d := ocr.NewDelegate(nil, orm, nil, nil, nil, monitoringEndpoint, legacyChains, logger.TestLogger(t), config.Database(), mailMon) delegateA := &delegate{jobA.Type, []job.ServiceCtx{serviceA1, serviceA2}, 0, nil, d} @@ -219,7 +220,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { serviceA2.On("Start", mock.Anything).Return(nil).Once().Run(func(mock.Arguments) { eventuallyStart.ItHappened() }) lggr := logger.TestLogger(t) - orm := NewTestORM(t, db, legacyChains, pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()), bridges.NewORM(db, lggr, config.Database()), keyStore, config.Database()) + orm := NewTestORM(t, db, pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()), bridges.NewORM(db, lggr, config.Database()), keyStore, config.Database()) mailMon := srvctest.Start(t, utils.NewMailboxMonitor(t.Name())) d := ocr.NewDelegate(nil, orm, nil, nil, nil, monitoringEndpoint, legacyChains, logger.TestLogger(t), config.Database(), mailMon) delegateA := &delegate{jobA.Type, []job.ServiceCtx{serviceA1, serviceA2}, 0, nil, d} @@ -264,7 +265,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { }) t.Run("Unregisters filters on 'DeleteJob()'", func(t *testing.T) { - config = configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + config = configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Feature.LogPoller = func(b bool) *bool { return &b }(true) }) lp := &mocklp.LogPoller{} @@ -297,7 +298,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { jobOCR2VRF := makeOCR2VRFJobSpec(t, keyStore, config, address, chain.ID(), 2) - orm := NewTestORM(t, db, legacyChains, pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()), bridges.NewORM(db, lggr, config.Database()), keyStore, config.Database()) + orm := NewTestORM(t, db, pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()), bridges.NewORM(db, lggr, config.Database()), keyStore, config.Database()) mailMon := srvctest.Start(t, utils.NewMailboxMonitor(t.Name())) processConfig := plugins.NewRegistrarConfig(loop.GRPCOpts{}, func(name string) (*plugins.RegisteredLoop, error) { return nil, nil }) diff --git a/core/services/keeper/delegate.go b/core/services/keeper/delegate.go index 6c68203f843..0dbf584c56f 100644 --- a/core/services/keeper/delegate.go +++ b/core/services/keeper/delegate.go @@ -3,9 +3,9 @@ package keeper import ( "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pg" @@ -21,7 +21,7 @@ type Delegate struct { db *sqlx.DB jrm job.ORM pr pipeline.Runner - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer mailMon *utils.MailboxMonitor } @@ -31,7 +31,7 @@ func NewDelegate( jrm job.ORM, pr pipeline.Runner, logger logger.Logger, - legacyChains evm.LegacyChainContainer, + legacyChains legacyevm.LegacyChainContainer, mailMon *utils.MailboxMonitor, ) *Delegate { return &Delegate{ diff --git a/core/services/keeper/integration_test.go b/core/services/keeper/integration_test.go index 39431063bcd..29a0b68702d 100644 --- a/core/services/keeper/integration_test.go +++ b/core/services/keeper/integration_test.go @@ -16,7 +16,7 @@ import ( "github.com/smartcontractkit/libocr/gethwrappers/link_token_interface" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_forwarder" @@ -236,7 +236,7 @@ func TestKeeperEthIntegration(t *testing.T) { backend.Commit() // setup app - config, db := heavyweight.FullTestDBV2(t, fmt.Sprintf("keeper_eth_integration_%s", test.name), func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].GasEstimator.EIP1559DynamicFees = &test.eip1559 c.Keeper.MaxGracePeriod = ptr[int64](0) // avoid waiting to re-submit for upkeeps c.Keeper.Registry.SyncInterval = models.MustNewDuration(24 * time.Hour) // disable full sync ticker for test @@ -393,7 +393,7 @@ func TestKeeperForwarderEthIntegration(t *testing.T) { backend.Commit() // setup app - config, db := heavyweight.FullTestDBV2(t, "keeper_forwarder_flow", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Feature.LogPoller = ptr(true) c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true) c.Keeper.MaxGracePeriod = ptr[int64](0) // avoid waiting to re-submit for upkeeps @@ -540,7 +540,7 @@ func TestMaxPerformDataSize(t *testing.T) { backend.Commit() // setup app - config, db := heavyweight.FullTestDBV2(t, "keeper_max_perform_data_test", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Keeper.MaxGracePeriod = ptr[int64](0) // avoid waiting to re-submit for upkeeps c.Keeper.Registry.SyncInterval = models.MustNewDuration(24 * time.Hour) // disable full sync ticker for test c.Keeper.Registry.MaxPerformDataSize = ptr(uint32(maxPerformDataSize)) // set the max perform data size diff --git a/core/services/keeper/orm.go b/core/services/keeper/orm.go index e281d610644..91883f8056c 100644 --- a/core/services/keeper/orm.go +++ b/core/services/keeper/orm.go @@ -3,9 +3,9 @@ package keeper import ( "math/rand" + "github.com/jmoiron/sqlx" "github.com/lib/pq" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" diff --git a/core/services/keeper/orm_test.go b/core/services/keeper/orm_test.go index 972437eda1d..d67baa09a06 100644 --- a/core/services/keeper/orm_test.go +++ b/core/services/keeper/orm_test.go @@ -9,13 +9,14 @@ 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/jmoiron/sqlx" + evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/keeper/registry1_1_synchronizer_test.go b/core/services/keeper/registry1_1_synchronizer_test.go index 8444aa50dd4..031b7a59074 100644 --- a/core/services/keeper/registry1_1_synchronizer_test.go +++ b/core/services/keeper/registry1_1_synchronizer_test.go @@ -18,7 +18,7 @@ import ( registry1_1 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper1_1" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/keeper/registry1_2_synchronizer_test.go b/core/services/keeper/registry1_2_synchronizer_test.go index f593a638f99..e7d8d6a48a2 100644 --- a/core/services/keeper/registry1_2_synchronizer_test.go +++ b/core/services/keeper/registry1_2_synchronizer_test.go @@ -18,7 +18,7 @@ import ( registry1_2 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper1_2" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/keeper/registry1_3_synchronizer_test.go b/core/services/keeper/registry1_3_synchronizer_test.go index 53b5cbf983f..a0522fd717e 100644 --- a/core/services/keeper/registry1_3_synchronizer_test.go +++ b/core/services/keeper/registry1_3_synchronizer_test.go @@ -14,7 +14,7 @@ import ( evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" registry1_3 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper1_3" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/utils" logmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log/mocks" diff --git a/core/services/keeper/registry_synchronizer_core.go b/core/services/keeper/registry_synchronizer_core.go index dea24f1a3bb..db7cca1763f 100644 --- a/core/services/keeper/registry_synchronizer_core.go +++ b/core/services/keeper/registry_synchronizer_core.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -36,7 +37,7 @@ type RegistrySynchronizerOptions struct { } type RegistrySynchronizer struct { - utils.StartStopOnce + services.StateMachine chStop chan struct{} registryWrapper RegistryWrapper interval time.Duration diff --git a/core/services/keeper/registry_synchronizer_helper_test.go b/core/services/keeper/registry_synchronizer_helper_test.go index 99fb54eba4d..966366b1069 100644 --- a/core/services/keeper/registry_synchronizer_helper_test.go +++ b/core/services/keeper/registry_synchronizer_helper_test.go @@ -8,13 +8,13 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" logmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/keeper/upkeep_executer.go b/core/services/keeper/upkeep_executer.go index 435b245792c..84349ba2dca 100644 --- a/core/services/keeper/upkeep_executer.go +++ b/core/services/keeper/upkeep_executer.go @@ -12,7 +12,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" @@ -53,7 +54,8 @@ type UpkeepExecuterConfig interface { // UpkeepExecuter implements the logic to communicate with KeeperRegistry type UpkeepExecuter struct { - chStop utils.StopChan + services.StateMachine + chStop services.StopChan ethClient evmclient.Client config UpkeepExecuterConfig executionQueue chan struct{} @@ -66,7 +68,6 @@ type UpkeepExecuter struct { logger logger.Logger wgDone sync.WaitGroup effectiveKeeperAddress common.Address - utils.StartStopOnce } // NewUpkeepExecuter is the constructor of UpkeepExecuter @@ -82,7 +83,7 @@ func NewUpkeepExecuter( effectiveKeeperAddress common.Address, ) *UpkeepExecuter { return &UpkeepExecuter{ - chStop: make(chan struct{}), + chStop: make(services.StopChan), ethClient: ethClient, executionQueue: make(chan struct{}, executionQueueSize), headBroadcaster: headBroadcaster, diff --git a/core/services/keeper/upkeep_executer_test.go b/core/services/keeper/upkeep_executer_test.go index 39f85aedcd9..7bbecafa22d 100644 --- a/core/services/keeper/upkeep_executer_test.go +++ b/core/services/keeper/upkeep_executer_test.go @@ -9,22 +9,23 @@ 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" - "github.com/smartcontractkit/chainlink/v2/core/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -61,7 +62,7 @@ func setup(t *testing.T, estimator gas.EvmFeeEstimator, overrideFn func(c *chain cltest.JobPipelineV2TestHelper, *txmmocks.MockEvmTxManager, keystore.Master, - evm.Chain, + legacyevm.Chain, keeper.ORM, ) { cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { diff --git a/core/services/keeper/upkeep_executer_unit_test.go b/core/services/keeper/upkeep_executer_unit_test.go index f4e99341ca0..a8fc46319cd 100644 --- a/core/services/keeper/upkeep_executer_unit_test.go +++ b/core/services/keeper/upkeep_executer_unit_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" diff --git a/core/services/keystore/cosmos.go b/core/services/keystore/cosmos.go index c06dfcdbcea..e3549fdb932 100644 --- a/core/services/keystore/cosmos.go +++ b/core/services/keystore/cosmos.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/cosmoskey" ) @@ -147,7 +147,7 @@ func (ks *cosmos) getByID(id string) (cosmoskey.Key, error) { return key, nil } -// CosmosLoopKeystore implements the [github.com/smartcontractkit/chainlink-relay/pkg/loop.Keystore] interface and +// CosmosLoopKeystore implements the [github.com/smartcontractkit/chainlink-common/pkg/loop.Keystore] interface and // handles signing for Cosmos messages. type CosmosLoopKeystore struct { Cosmos diff --git a/core/services/keystore/cosmos_test.go b/core/services/keystore/cosmos_test.go index ada7c25a2c4..3c33f16282d 100644 --- a/core/services/keystore/cosmos_test.go +++ b/core/services/keystore/cosmos_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/cosmoskey" diff --git a/core/services/keystore/csa_test.go b/core/services/keystore/csa_test.go index ef2dd1f6893..b6dfb009593 100644 --- a/core/services/keystore/csa_test.go +++ b/core/services/keystore/csa_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey" diff --git a/core/services/keystore/dkgencrypt_test.go b/core/services/keystore/dkgencrypt_test.go index 1edbc2120fa..36f48f9c2b4 100644 --- a/core/services/keystore/dkgencrypt_test.go +++ b/core/services/keystore/dkgencrypt_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/dkgencryptkey" diff --git a/core/services/keystore/dkgsign_test.go b/core/services/keystore/dkgsign_test.go index d699800b509..5ea23a516be 100644 --- a/core/services/keystore/dkgsign_test.go +++ b/core/services/keystore/dkgsign_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/dkgsignkey" diff --git a/core/services/keystore/eth.go b/core/services/keystore/eth.go index a2b207044c0..0e86cccacae 100644 --- a/core/services/keystore/eth.go +++ b/core/services/keystore/eth.go @@ -221,13 +221,18 @@ func (ks *eth) Enable(address common.Address, chainID *big.Int, qopts ...pg.QOpt func (ks *eth) enable(address common.Address, chainID *big.Int, qopts ...pg.QOpt) error { state := new(ethkey.State) q := ks.q.WithOpts(qopts...) - sql := `UPDATE evm.key_states SET disabled = false, updated_at = NOW() WHERE address = $1 AND evm_chain_id = $2 - RETURNING *;` + sql := `INSERT INTO evm.key_states as key_states ("address", "evm_chain_id", "disabled", "created_at", "updated_at") VALUES ($1, $2, false, NOW(), NOW()) + ON CONFLICT ("address", "evm_chain_id") DO UPDATE SET "disabled" = false, "updated_at" = NOW() WHERE key_states."address" = $1 AND key_states."evm_chain_id" = $2 + RETURNING *;` if err := q.Get(state, sql, address, chainID.String()); err != nil { return errors.Wrap(err, "failed to enable state") } - ks.keyStates.enable(address, chainID, state.UpdatedAt) + if state.CreatedAt.Equal(state.UpdatedAt) { + ks.keyStates.add(state) + } else { + ks.keyStates.enable(address, chainID, state.UpdatedAt) + } ks.notify() return nil } @@ -245,13 +250,18 @@ func (ks *eth) Disable(address common.Address, chainID *big.Int, qopts ...pg.QOp func (ks *eth) disable(address common.Address, chainID *big.Int, qopts ...pg.QOpt) error { state := new(ethkey.State) q := ks.q.WithOpts(qopts...) - sql := `UPDATE evm.key_states SET disabled = false, updated_at = NOW() WHERE address = $1 AND evm_chain_id = $2 - RETURNING id, address, evm_chain_id, disabled, created_at, updated_at;` + sql := `INSERT INTO evm.key_states as key_states ("address", "evm_chain_id", "disabled", "created_at", "updated_at") VALUES ($1, $2, true, NOW(), NOW()) + ON CONFLICT ("address", "evm_chain_id") DO UPDATE SET "disabled" = true, "updated_at" = NOW() WHERE key_states."address" = $1 AND key_states."evm_chain_id" = $2 + RETURNING *;` if err := q.Get(state, sql, address, chainID.String()); err != nil { - return errors.Wrap(err, "failed to enable state") + return errors.Wrap(err, "failed to disable state") } - ks.keyStates.disable(address, chainID, state.UpdatedAt) + if state.CreatedAt.Equal(state.UpdatedAt) { + ks.keyStates.add(state) + } else { + ks.keyStates.disable(address, chainID, state.UpdatedAt) + } ks.notify() return nil } diff --git a/core/services/keystore/eth_test.go b/core/services/keystore/eth_test.go index 0fec509a325..42d6c575376 100644 --- a/core/services/keystore/eth_test.go +++ b/core/services/keystore/eth_test.go @@ -16,7 +16,7 @@ import ( evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" @@ -541,6 +541,51 @@ func Test_EthKeyStore_SubscribeToKeyChanges(t *testing.T) { assertCountAtLeast(1) } +func Test_EthKeyStore_Enable(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := configtest.NewTestGeneralConfig(t) + keyStore := cltest.NewKeyStore(t, db, cfg.Database()) + ks := keyStore.Eth() + + t.Run("already existing disabled key gets enabled", func(t *testing.T) { + k, _ := cltest.MustInsertRandomKeyNoChains(t, ks) + require.NoError(t, ks.Add(k.Address, testutils.SimulatedChainID)) + require.NoError(t, ks.Disable(k.Address, testutils.SimulatedChainID)) + require.NoError(t, ks.Enable(k.Address, testutils.SimulatedChainID)) + key, err := ks.GetState(k.Address.Hex(), testutils.SimulatedChainID) + require.NoError(t, err) + require.Equal(t, key.Disabled, false) + }) + + t.Run("creates key, deletes it unsafely and then enable creates it again", func(t *testing.T) { + k, _ := cltest.MustInsertRandomKeyNoChains(t, ks) + require.NoError(t, ks.Add(k.Address, testutils.SimulatedChainID)) + _, err := db.Exec("DELETE FROM evm.key_states WHERE address = $1", k.Address) + require.NoError(t, err) + require.NoError(t, ks.Enable(k.Address, testutils.SimulatedChainID)) + key, err := ks.GetState(k.Address.Hex(), testutils.SimulatedChainID) + require.NoError(t, err) + require.Equal(t, key.Disabled, false) + }) + + t.Run("creates key and enables it if it exists in the keystore, but is missing from key states db table", func(t *testing.T) { + k, _ := cltest.MustInsertRandomKeyNoChains(t, ks) + require.NoError(t, ks.Enable(k.Address, testutils.SimulatedChainID)) + key, err := ks.GetState(k.Address.Hex(), testutils.SimulatedChainID) + require.NoError(t, err) + require.Equal(t, key.Disabled, false) + }) + + t.Run("errors if key is not present in keystore", func(t *testing.T) { + addrNotInKs := testutils.NewAddress() + require.Error(t, ks.Enable(addrNotInKs, testutils.SimulatedChainID)) + _, err := ks.GetState(addrNotInKs.Hex(), testutils.SimulatedChainID) + require.Error(t, err) + }) +} + func Test_EthKeyStore_EnsureKeys(t *testing.T) { t.Parallel() @@ -743,3 +788,38 @@ func Test_EthKeyStore_CheckEnabled(t *testing.T) { require.Contains(t, err.Error(), fmt.Sprintf("eth key with address %s exists but is disabled for chain 1337 (enabled only for chain IDs: 0)", addr2.Hex())) }) } + +func Test_EthKeyStore_Disable(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := configtest.NewTestGeneralConfig(t) + keyStore := cltest.NewKeyStore(t, db, cfg.Database()) + ks := keyStore.Eth() + + t.Run("creates key, deletes it unsafely and then enable creates it again", func(t *testing.T) { + k, _ := cltest.MustInsertRandomKeyNoChains(t, ks) + require.NoError(t, ks.Add(k.Address, testutils.SimulatedChainID)) + _, err := db.Exec("DELETE FROM evm.key_states WHERE address = $1", k.Address) + require.NoError(t, err) + require.NoError(t, ks.Disable(k.Address, testutils.SimulatedChainID)) + key, err := ks.GetState(k.Address.Hex(), testutils.SimulatedChainID) + require.NoError(t, err) + require.Equal(t, key.Disabled, true) + }) + + t.Run("creates key and enables it if it exists in the keystore, but is missing from key states db table", func(t *testing.T) { + k, _ := cltest.MustInsertRandomKeyNoChains(t, ks) + require.NoError(t, ks.Disable(k.Address, testutils.SimulatedChainID)) + key, err := ks.GetState(k.Address.Hex(), testutils.SimulatedChainID) + require.NoError(t, err) + require.Equal(t, key.Disabled, true) + }) + + t.Run("errors if key is not present in keystore", func(t *testing.T) { + addrNotInKs := testutils.NewAddress() + require.Error(t, ks.Disable(addrNotInKs, testutils.SimulatedChainID)) + _, err := ks.GetState(addrNotInKs.Hex(), testutils.SimulatedChainID) + require.Error(t, err) + }) +} diff --git a/core/services/keystore/helpers_test.go b/core/services/keystore/helpers_test.go index 13627dd0231..d0b2a21ab38 100644 --- a/core/services/keystore/helpers_test.go +++ b/core/services/keystore/helpers_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" diff --git a/core/services/keystore/keys/dkgencryptkey/key.go b/core/services/keystore/keys/dkgencryptkey/key.go index 461dc4e2ade..e94f2a6bdf4 100644 --- a/core/services/keystore/keys/dkgencryptkey/key.go +++ b/core/services/keystore/keys/dkgencryptkey/key.go @@ -6,9 +6,10 @@ import ( "math/big" "github.com/pkg/errors" - "github.com/smartcontractkit/ocr2vrf/altbn_128" "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/pairing" + + "github.com/smartcontractkit/chainlink-vrf/altbn_128" ) var suite pairing.Suite = &altbn_128.PairingSuite{} diff --git a/core/services/keystore/keys/ethkey/address.go b/core/services/keystore/keys/ethkey/address.go index 4a161045ea0..1b26413f634 100644 --- a/core/services/keystore/keys/ethkey/address.go +++ b/core/services/keystore/keys/ethkey/address.go @@ -9,7 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/v2/core/utils" + "github.com/smartcontractkit/chainlink-common/pkg/utils/bytes" ) // EIP55Address is a new type for string which persists an ethereum address in @@ -85,7 +85,7 @@ func (a *EIP55Address) UnmarshalText(input []byte) error { // UnmarshalJSON parses a hash from a JSON string func (a *EIP55Address) UnmarshalJSON(input []byte) error { - input = utils.RemoveQuotes(input) + input = bytes.TrimQuotes(input) return a.UnmarshalText(input) } diff --git a/core/services/keystore/keys/ethkey/key.go b/core/services/keystore/keys/ethkey/key.go index 4201251e34f..6335ed55adc 100644 --- a/core/services/keystore/keys/ethkey/key.go +++ b/core/services/keystore/keys/ethkey/key.go @@ -3,7 +3,7 @@ package ethkey import ( "time" - "github.com/smartcontractkit/chainlink/v2/core/services/pg/datatypes" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" ) // NOTE: This model refers to the OLD key and is only used for migrations @@ -15,10 +15,10 @@ import ( type Key struct { ID int32 Address EIP55Address - JSON datatypes.JSON `json:"-"` - CreatedAt time.Time `json:"-"` - UpdatedAt time.Time `json:"-"` - DeletedAt *time.Time `json:"-"` + JSON sqlutil.JSON `json:"-"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt *time.Time `json:"-"` // IsFunding marks the address as being used for rescuing the node and the pending transactions // Only one key can be IsFunding=true at a time. IsFunding bool diff --git a/core/services/keystore/keystoretest.go b/core/services/keystore/keystoretest.go index 0b5ce4e0057..6efc8e76bcf 100644 --- a/core/services/keystore/keystoretest.go +++ b/core/services/keystore/keystoretest.go @@ -4,7 +4,7 @@ import ( "errors" "sync" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" diff --git a/core/services/keystore/master.go b/core/services/keystore/master.go index fb28202b527..05f19495f9d 100644 --- a/core/services/keystore/master.go +++ b/core/services/keystore/master.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/cosmoskey" diff --git a/core/services/keystore/master_test.go b/core/services/keystore/master_test.go index 7a280cc6756..73f636c6625 100644 --- a/core/services/keystore/master_test.go +++ b/core/services/keystore/master_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" ) diff --git a/core/services/keystore/ocr2_test.go b/core/services/keystore/ocr2_test.go index b4feb33b5f5..9223538a766 100644 --- a/core/services/keystore/ocr2_test.go +++ b/core/services/keystore/ocr2_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" diff --git a/core/services/keystore/ocr_test.go b/core/services/keystore/ocr_test.go index 5698352ec30..200d62415eb 100644 --- a/core/services/keystore/ocr_test.go +++ b/core/services/keystore/ocr_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocrkey" diff --git a/core/services/keystore/orm.go b/core/services/keystore/orm.go index 6f612105ea9..3d75d6f2369 100644 --- a/core/services/keystore/orm.go +++ b/core/services/keystore/orm.go @@ -7,8 +7,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" ) func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig) ksORM { diff --git a/core/services/keystore/p2p_test.go b/core/services/keystore/p2p_test.go index 63654786fd1..89cab3e1621 100644 --- a/core/services/keystore/p2p_test.go +++ b/core/services/keystore/p2p_test.go @@ -9,7 +9,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" diff --git a/core/services/keystore/solana_test.go b/core/services/keystore/solana_test.go index 8d7d90a9da8..6e895a56117 100644 --- a/core/services/keystore/solana_test.go +++ b/core/services/keystore/solana_test.go @@ -8,7 +8,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/solkey" diff --git a/core/services/keystore/starknet.go b/core/services/keystore/starknet.go index 7bf454004d0..251c74d0e00 100644 --- a/core/services/keystore/starknet.go +++ b/core/services/keystore/starknet.go @@ -9,8 +9,8 @@ import ( "github.com/smartcontractkit/caigo" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - adapters "github.com/smartcontractkit/chainlink-relay/pkg/loop/adapters/starknet" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + adapters "github.com/smartcontractkit/chainlink-common/pkg/loop/adapters/starknet" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/starkkey" ) @@ -155,7 +155,7 @@ func (ks *starknet) getByID(id string) (starkkey.Key, error) { return key, nil } -// StarknetLooppSigner implements [github.com/smartcontractkit/chainlink-relay/pkg/loop.Keystore] interface and the requirements +// StarknetLooppSigner implements [github.com/smartcontractkit/chainlink-common/pkg/loop.Keystore] interface and the requirements // of signature d/encoding of the [github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/txm.NewKeystoreAdapter] type StarknetLooppSigner struct { StarkNet @@ -165,7 +165,7 @@ var _ loop.Keystore = &StarknetLooppSigner{} // Sign implements [loop.Keystore] // hash is expected to be the byte representation of big.Int -// the returned []byte is an encoded [github.com/smartcontractkit/chainlink-relay/pkg/loop/adapters/starknet.Signature]. +// the returned []byte is an encoded [github.com/smartcontractkit/chainlink-common/pkg/loop/adapters/starknet.Signature]. // this enables compatibility with [github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/txm.NewKeystoreAdapter] func (lk *StarknetLooppSigner) Sign(ctx context.Context, id string, hash []byte) ([]byte, error) { diff --git a/core/services/keystore/starknet_test.go b/core/services/keystore/starknet_test.go index 571a809a55a..7fc5718bac0 100644 --- a/core/services/keystore/starknet_test.go +++ b/core/services/keystore/starknet_test.go @@ -1,7 +1,6 @@ package keystore_test import ( - "context" "fmt" "math/big" "testing" @@ -13,7 +12,8 @@ import ( "github.com/smartcontractkit/caigo" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/starkkey" @@ -121,13 +121,13 @@ func TestStarknetSigner(t *testing.T) { // on existing sender id t.Run("key exists", func(t *testing.T) { baseKs.On("Get", starknetSenderAddr).Return(starkKey, nil) - signed, err := lk.Sign(context.Background(), starknetSenderAddr, nil) + signed, err := lk.Sign(testutils.Context(t), starknetSenderAddr, nil) require.Nil(t, signed) require.NoError(t, err) }) t.Run("key doesn't exists", func(t *testing.T) { baseKs.On("Get", mock.Anything).Return(starkkey.Key{}, fmt.Errorf("key doesn't exist")) - signed, err := lk.Sign(context.Background(), "not an address", nil) + signed, err := lk.Sign(testutils.Context(t), "not an address", nil) require.Nil(t, signed) require.Error(t, err) }) @@ -140,7 +140,7 @@ func TestStarknetSigner(t *testing.T) { baseKs.On("Get", starknetSenderAddr).Return(starkKey, nil) hash, err := caigo.Curve.PedersenHash([]*big.Int{big.NewInt(42)}) require.NoError(t, err) - r, s, err := adapter.Sign(context.Background(), starknetSenderAddr, hash) + r, s, err := adapter.Sign(testutils.Context(t), starknetSenderAddr, hash) require.NoError(t, err) require.NotNil(t, r) require.NotNil(t, s) diff --git a/core/services/keystore/vrf_test.go b/core/services/keystore/vrf_test.go index f0c6949bbea..7a2e91ffec3 100644 --- a/core/services/keystore/vrf_test.go +++ b/core/services/keystore/vrf_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" diff --git a/core/services/mocks/checker.go b/core/services/mocks/checker.go index 354812d0212..e0c209d8afb 100644 --- a/core/services/mocks/checker.go +++ b/core/services/mocks/checker.go @@ -3,7 +3,7 @@ package mocks import ( - pkgservices "github.com/smartcontractkit/chainlink-relay/pkg/services" + pkgservices "github.com/smartcontractkit/chainlink-common/pkg/services" mock "github.com/stretchr/testify/mock" ) diff --git a/core/services/multi.go b/core/services/multi.go index 4ea263f5a30..1e465d5e724 100644 --- a/core/services/multi.go +++ b/core/services/multi.go @@ -3,7 +3,7 @@ package services import ( "io" - "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/services" ) // StartClose is a subset of the ServiceCtx interface. diff --git a/core/services/nurse.go b/core/services/nurse.go index 33230ddae02..3d896a80ff3 100644 --- a/core/services/nurse.go +++ b/core/services/nurse.go @@ -17,13 +17,14 @@ import ( "github.com/google/pprof/profile" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" ) type Nurse struct { - utils.StartStopOnce + services.StateMachine cfg Config log logger.Logger diff --git a/core/services/ocr/config.go b/core/services/ocr/config.go index e1bc997f269..53ec9f9cea9 100644 --- a/core/services/ocr/config.go +++ b/core/services/ocr/config.go @@ -14,7 +14,7 @@ type Config interface { } func toLocalConfig(cfg ValidationConfig, evmOcrConfig evmconfig.OCR, insecureCfg insecureConfig, spec job.OCROracleSpec, ocrConfig job.OCRConfig) ocrtypes.LocalConfig { - concreteSpec := job.LoadEnvConfigVarsLocalOCR(evmOcrConfig, spec, ocrConfig) + concreteSpec := job.LoadConfigVarsLocalOCR(evmOcrConfig, spec, ocrConfig) lc := ocrtypes.LocalConfig{ BlockchainTimeout: concreteSpec.BlockchainTimeout.Duration(), ContractConfigConfirmations: concreteSpec.ContractConfigConfirmations, diff --git a/core/services/ocr/config_overrider.go b/core/services/ocr/config_overrider.go index 35d8e2a4c89..ac87d0e3924 100644 --- a/core/services/ocr/config_overrider.go +++ b/core/services/ocr/config_overrider.go @@ -9,15 +9,18 @@ import ( "time" "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/utils" ) type ConfigOverriderImpl struct { - utils.StartStopOnce + services.StateMachine logger logger.Logger flags *ContractFlags contractAddress ethkey.EIP55Address @@ -38,8 +41,14 @@ type ConfigOverriderImpl struct { // InitialHibernationStatus - hibernation state set until the first successful update from the chain const InitialHibernationStatus = false +type DeltaCConfig interface { + DeltaCOverride() time.Duration + DeltaCJitterOverride() time.Duration +} + func NewConfigOverriderImpl( logger logger.Logger, + cfg DeltaCConfig, contractAddress ethkey.EIP55Address, flags *ContractFlags, pollTicker utils.TickerBase, @@ -51,12 +60,13 @@ func NewConfigOverriderImpl( } addressBig := contractAddress.Big() - addressSeconds := addressBig.Mod(addressBig, big.NewInt(3600)).Uint64() - deltaC := 23*time.Hour + time.Duration(addressSeconds)*time.Second + jitterSeconds := int64(cfg.DeltaCJitterOverride() / time.Second) + addressSeconds := addressBig.Mod(addressBig, big.NewInt(jitterSeconds)).Uint64() + deltaC := cfg.DeltaCOverride() + time.Duration(addressSeconds)*time.Second ctx, cancel := context.WithCancel(context.Background()) co := ConfigOverriderImpl{ - utils.StartStopOnce{}, + services.StateMachine{}, logger, flags, contractAddress, diff --git a/core/services/ocr/config_overrider_test.go b/core/services/ocr/config_overrider_test.go index bb680189b10..245d6348765 100644 --- a/core/services/ocr/config_overrider_test.go +++ b/core/services/ocr/config_overrider_test.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -27,6 +28,12 @@ type configOverriderUni struct { contractAddress ethkey.EIP55Address } +type deltaCConfig struct{} + +func (d deltaCConfig) DeltaCOverride() time.Duration { return time.Hour * 24 * 7 } + +func (d deltaCConfig) DeltaCJitterOverride() time.Duration { return time.Hour } + func newConfigOverriderUni(t *testing.T, pollITicker utils.TickerBase, flagsContract *mocks.Flags) (uni configOverriderUni) { var testLogger = logger.TestLogger(t) contractAddress := cltest.NewEIP55Address() @@ -35,6 +42,7 @@ func newConfigOverriderUni(t *testing.T, pollITicker utils.TickerBase, flagsCont var err error uni.overrider, err = ocr.NewConfigOverriderImpl( testLogger, + deltaCConfig{}, contractAddress, flags, pollITicker, @@ -141,6 +149,7 @@ func Test_OCRConfigOverrider(t *testing.T) { flags := &ocr.ContractFlags{FlagsInterface: nil} _, err := ocr.NewConfigOverriderImpl( testLogger, + deltaCConfig{}, contractAddress, flags, nil, @@ -160,18 +169,18 @@ func Test_OCRConfigOverrider(t *testing.T) { address2, err := ethkey.NewEIP55Address(common.BigToAddress(big.NewInt(1234567890)).Hex()) require.NoError(t, err) - overrider1a, err := ocr.NewConfigOverriderImpl(testLogger, address1, flags, nil) + overrider1a, err := ocr.NewConfigOverriderImpl(testLogger, deltaCConfig{}, address1, flags, nil) require.NoError(t, err) - overrider1b, err := ocr.NewConfigOverriderImpl(testLogger, address1, flags, nil) + overrider1b, err := ocr.NewConfigOverriderImpl(testLogger, deltaCConfig{}, address1, flags, nil) require.NoError(t, err) - overrider2, err := ocr.NewConfigOverriderImpl(testLogger, address2, flags, nil) + overrider2, err := ocr.NewConfigOverriderImpl(testLogger, deltaCConfig{}, address2, flags, nil) require.NoError(t, err) - require.Equal(t, overrider1a.DeltaCFromAddress, time.Duration(85600000000000)) - require.Equal(t, overrider1b.DeltaCFromAddress, time.Duration(85600000000000)) - require.Equal(t, overrider2.DeltaCFromAddress, time.Duration(84690000000000)) + assert.Equal(t, cltest.MustParseDuration(t, "168h46m40s"), overrider1a.DeltaCFromAddress) + assert.Equal(t, cltest.MustParseDuration(t, "168h46m40s"), overrider1b.DeltaCFromAddress) + assert.Equal(t, cltest.MustParseDuration(t, "168h31m30s"), overrider2.DeltaCFromAddress) }) } diff --git a/core/services/ocr/contract_tracker.go b/core/services/ocr/contract_tracker.go index a7df28e1c76..4f79bcfc31a 100644 --- a/core/services/ocr/contract_tracker.go +++ b/core/services/ocr/contract_tracker.go @@ -14,17 +14,19 @@ import ( gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" "github.com/smartcontractkit/libocr/offchainreporting/confighelper" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/common/config" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/offchain_aggregator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" @@ -52,7 +54,7 @@ type ( // OCRContractTracker complies with ContractConfigTracker interface and // handles log events related to the contract more generally OCRContractTracker struct { - utils.StartStopOnce + services.StateMachine ethClient evmclient.Client contract *offchain_aggregator_wrapper.OffchainAggregator @@ -72,7 +74,7 @@ type ( unsubscribeHeads func() // Start/Stop lifecycle - chStop utils.StopChan + chStop services.StopChan wg sync.WaitGroup unsubscribeLogs func() @@ -132,7 +134,7 @@ func NewOCRContractTracker( cfg: cfg, mailMon: mailMon, headBroadcaster: headBroadcaster, - chStop: make(chan struct{}), + chStop: make(services.StopChan), latestRoundRequested: offchainaggregator.OffchainAggregatorRoundRequested{}, configsMB: utils.NewMailbox[ocrtypes.ContractConfig](configMailboxSanityLimit), chConfigs: make(chan ocrtypes.ContractConfig), @@ -400,7 +402,7 @@ func (t *OCRContractTracker) LatestBlockHeight(ctx context.Context) (blockheight // care about the block height; we have no way of getting the L1 block // height anyway return 0, nil - case "", config.ChainArbitrum, config.ChainCelo, config.ChainOptimismBedrock, config.ChainXDai: + case "", config.ChainArbitrum, config.ChainCelo, config.ChainOptimismBedrock, config.ChainXDai, config.ChainKroma, config.ChainWeMix, config.ChainZkSync: // continue } latestBlockHeight := t.getLatestBlockHeight() diff --git a/core/services/ocr/contract_tracker_test.go b/core/services/ocr/contract_tracker_test.go index d8fe45e8b33..5684219cf16 100644 --- a/core/services/ocr/contract_tracker_test.go +++ b/core/services/ocr/contract_tracker_test.go @@ -23,7 +23,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/offchain_aggregator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/ocr/database.go b/core/services/ocr/database.go index cd8e584e39a..524dfa0e7bb 100644 --- a/core/services/ocr/database.go +++ b/core/services/ocr/database.go @@ -11,9 +11,9 @@ import ( "github.com/pkg/errors" "go.uber.org/multierr" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" diff --git a/core/services/ocr/database_test.go b/core/services/ocr/database_test.go index 8b345df8597..6a72c27aa65 100644 --- a/core/services/ocr/database_test.go +++ b/core/services/ocr/database_test.go @@ -7,14 +7,15 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/ocr" "github.com/smartcontractkit/chainlink/v2/core/utils" diff --git a/core/services/ocr/delegate.go b/core/services/ocr/delegate.go index dee349a9a0d..f473c93b1f0 100644 --- a/core/services/ocr/delegate.go +++ b/core/services/ocr/delegate.go @@ -9,9 +9,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - relaylogger "github.com/smartcontractkit/chainlink-relay/pkg/logger" + commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" ocrnetworking "github.com/smartcontractkit/libocr/networking" @@ -19,8 +19,8 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/offchain_aggregator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -41,7 +41,7 @@ type Delegate struct { pipelineRunner pipeline.Runner peerWrapper *ocrcommon.SingletonPeerWrapper monitoringEndpointGen telemetry.MonitoringEndpointGenerator - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer lggr logger.Logger cfg Config mailMon *utils.MailboxMonitor @@ -58,7 +58,7 @@ func NewDelegate( pipelineRunner pipeline.Runner, peerWrapper *ocrcommon.SingletonPeerWrapper, monitoringEndpointGen telemetry.MonitoringEndpointGenerator, - legacyChains evm.LegacyChainContainer, + legacyChains legacyevm.LegacyChainContainer, lggr logger.Logger, cfg Config, mailMon *utils.MailboxMonitor, @@ -95,7 +95,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.ServiceCtx, err e if err != nil { return nil, err } - concreteSpec, err := job.LoadEnvConfigVarsOCR(chain.Config().EVM().OCR(), chain.Config().OCR(), *jb.OCROracleSpec) + concreteSpec, err := job.LoadConfigVarsOCR(chain.Config().EVM().OCR(), chain.Config().OCR(), *jb.OCROracleSpec) if err != nil { return nil, err } @@ -167,7 +167,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.ServiceCtx, err e v2Bootstrappers = peerWrapper.P2PConfig().V2().DefaultBootstrappers() } - ocrLogger := relaylogger.NewOCRWrapper(lggr, chain.Config().OCR().TraceLogging(), func(msg string) { + ocrLogger := commonlogger.NewOCRWrapper(lggr, chain.Config().OCR().TraceLogging(), func(msg string) { d.jobORM.TryRecordError(jb.ID, msg) }) @@ -274,7 +274,12 @@ func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.ServiceCtx, err e effectiveTransmitterAddress, ) - runResults := make(chan *pipeline.Run, chain.Config().JobPipeline().ResultWriteQueueDepth()) + saver := ocrcommon.NewResultRunSaver( + d.pipelineRunner, + lggr, + cfg.JobPipeline().MaxSuccessfulRuns(), + cfg.JobPipeline().ResultWriteQueueDepth(), + ) var configOverrider ocrtypes.ConfigOverrider configOverriderService, err := d.maybeCreateConfigOverrider(lggr, chain, concreteSpec.ContractAddress) @@ -295,10 +300,13 @@ func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.ServiceCtx, err e configOverrider = configOverriderService } + jb.OCROracleSpec.CaptureEATelemetry = chain.Config().OCR().CaptureEATelemetry() enhancedTelemChan := make(chan ocrcommon.EnhancedTelemetryData, 100) if ocrcommon.ShouldCollectEnhancedTelemetry(&jb) { - enhancedTelemService := ocrcommon.NewEnhancedTelemetryService(&jb, enhancedTelemChan, make(chan struct{}), d.monitoringEndpointGen.GenMonitoringEndpoint(concreteSpec.ContractAddress.String(), synchronization.EnhancedEA, "EVM", chain.ID().String()), lggr.Named("EnhancedTelemetry")) + enhancedTelemService := ocrcommon.NewEnhancedTelemetryService(&jb, enhancedTelemChan, make(chan struct{}), d.monitoringEndpointGen.GenMonitoringEndpoint("EVM", chain.ID().String(), concreteSpec.ContractAddress.String(), synchronization.EnhancedEA), lggr.Named("EnhancedTelemetry")) services = append(services, enhancedTelemService) + } else { + lggr.Infow("Enhanced telemetry is disabled for job", "job", jb.Name) } oracle, err := ocr.NewOracle(ocr.OracleArgs{ @@ -308,7 +316,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.ServiceCtx, err e jb, *jb.PipelineSpec, lggr, - runResults, + saver, enhancedTelemChan, ), LocalConfig: lc, @@ -319,7 +327,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.ServiceCtx, err e Logger: ocrLogger, V1Bootstrappers: v1BootstrapPeers, V2Bootstrappers: v2Bootstrappers, - MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(concreteSpec.ContractAddress.String(), synchronization.OCR, "EVM", chain.ID().String()), + MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint("EVM", chain.ID().String(), concreteSpec.ContractAddress.String(), synchronization.OCR), ConfigOverrider: configOverrider, }) if err != nil { @@ -331,19 +339,13 @@ func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.ServiceCtx, err e // 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.ServiceCtx{ocrcommon.NewResultRunSaver( - runResults, - d.pipelineRunner, - make(chan struct{}), - lggr, - cfg.JobPipeline().MaxSuccessfulRuns(), - )}, services...) + services = append([]job.ServiceCtx{saver}, services...) } return services, nil } -func (d *Delegate) maybeCreateConfigOverrider(logger logger.Logger, chain evm.Chain, contractAddress ethkey.EIP55Address) (*ConfigOverriderImpl, error) { +func (d *Delegate) maybeCreateConfigOverrider(logger logger.Logger, chain legacyevm.Chain, contractAddress ethkey.EIP55Address) (*ConfigOverriderImpl, error) { flagsContractAddress := chain.Config().EVM().FlagsContractAddress() if flagsContractAddress != "" { flags, err := NewFlags(flagsContractAddress, chain.Client()) @@ -355,7 +357,7 @@ func (d *Delegate) maybeCreateConfigOverrider(logger logger.Logger, chain evm.Ch } ticker := utils.NewPausableTicker(ConfigOverriderPollInterval) - return NewConfigOverriderImpl(logger, contractAddress, flags, &ticker) + return NewConfigOverriderImpl(logger, chain.Config().EVM().OCR(), contractAddress, flags, &ticker) } return nil, nil } diff --git a/core/services/ocr/helpers_internal_test.go b/core/services/ocr/helpers_internal_test.go index 9a1f887986e..57b669ef401 100644 --- a/core/services/ocr/helpers_internal_test.go +++ b/core/services/ocr/helpers_internal_test.go @@ -3,7 +3,7 @@ package ocr import ( "testing" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/ocr/validate.go b/core/services/ocr/validate.go index 07fdc15912e..a780ebb0821 100644 --- a/core/services/ocr/validate.go +++ b/core/services/ocr/validate.go @@ -10,9 +10,9 @@ import ( "github.com/pkg/errors" "github.com/smartcontractkit/libocr/offchainreporting" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/common/config" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" - "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" @@ -37,7 +37,7 @@ type insecureConfig interface { } // ValidatedOracleSpecToml validates an oracle spec that came from TOML -func ValidatedOracleSpecToml(legacyChains evm.LegacyChainContainer, tomlString string) (job.Job, error) { +func ValidatedOracleSpecToml(legacyChains legacyevm.LegacyChainContainer, tomlString string) (job.Job, error) { return ValidatedOracleSpecTomlCfg(func(id *big.Int) (evmconfig.ChainScopedConfig, error) { c, err := legacyChains.Get(id.String()) if err != nil { diff --git a/core/services/ocr/validate_test.go b/core/services/ocr/validate_test.go index 3efd2d8381e..0164fd82c54 100644 --- a/core/services/ocr/validate_test.go +++ b/core/services/ocr/validate_test.go @@ -12,7 +12,7 @@ import ( "gopkg.in/guregu/null.v4" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -399,7 +399,7 @@ answer1 [type=median index=0]; for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - c := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Insecure.OCRDevelopmentMode = null.BoolFrom(false).Ptr() if tc.overrides != nil { tc.overrides(c, s) diff --git a/core/services/ocr2/database.go b/core/services/ocr2/database.go index 7061ad0452f..5591f33fd40 100644 --- a/core/services/ocr2/database.go +++ b/core/services/ocr2/database.go @@ -6,11 +6,11 @@ import ( "encoding/binary" "time" + "github.com/jmoiron/sqlx" "github.com/lib/pq" "github.com/pkg/errors" ocrcommon "github.com/smartcontractkit/libocr/commontypes" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" diff --git a/core/services/ocr2/database_test.go b/core/services/ocr2/database_test.go index 18c0ec85194..b70ac629da1 100644 --- a/core/services/ocr2/database_test.go +++ b/core/services/ocr2/database_test.go @@ -7,14 +7,15 @@ import ( medianconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/median/config" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/jmoiron/sqlx" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" diff --git a/core/services/ocr2/delegate.go b/core/services/ocr2/delegate.go index ef1ae7c5888..16f02282afb 100644 --- a/core/services/ocr2/delegate.go +++ b/core/services/ocr2/delegate.go @@ -8,32 +8,35 @@ import ( "log" "time" - "gopkg.in/guregu/null.v4" - "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" + "google.golang.org/grpc" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/libocr/commontypes" libocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - ocr2keepers20 "github.com/smartcontractkit/ocr2keepers/pkg/v2" - ocr2keepers20config "github.com/smartcontractkit/ocr2keepers/pkg/v2/config" - ocr2keepers20coordinator "github.com/smartcontractkit/ocr2keepers/pkg/v2/coordinator" - ocr2keepers20polling "github.com/smartcontractkit/ocr2keepers/pkg/v2/observer/polling" - ocr2keepers20runner "github.com/smartcontractkit/ocr2keepers/pkg/v2/runner" - ocr2keepers21config "github.com/smartcontractkit/ocr2keepers/pkg/v3/config" - ocr2keepers21 "github.com/smartcontractkit/ocr2keepers/pkg/v3/plugin" - "github.com/smartcontractkit/ocr2vrf/altbn_128" - dkgpkg "github.com/smartcontractkit/ocr2vrf/dkg" - "github.com/smartcontractkit/ocr2vrf/ocr2vrf" - "github.com/smartcontractkit/sqlx" - - relaylogger "github.com/smartcontractkit/chainlink-relay/pkg/logger" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + + ocr2keepers20 "github.com/smartcontractkit/chainlink-automation/pkg/v2" + ocr2keepers20config "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" + ocr2keepers20coordinator "github.com/smartcontractkit/chainlink-automation/pkg/v2/coordinator" + ocr2keepers20polling "github.com/smartcontractkit/chainlink-automation/pkg/v2/observer/polling" + ocr2keepers20runner "github.com/smartcontractkit/chainlink-automation/pkg/v2/runner" + ocr2keepers21config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" + ocr2keepers21 "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" + + "github.com/smartcontractkit/chainlink-vrf/altbn_128" + dkgpkg "github.com/smartcontractkit/chainlink-vrf/dkg" + "github.com/smartcontractkit/chainlink-vrf/ocr2vrf" + + commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop/reportingplugins" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/bridges" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" coreconfig "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -43,10 +46,12 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/dkg" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/dkg/persistence" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/generic" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/median" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper" - ocr2keeper21core "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21" + ocr2keeper21core "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ocr2vrfconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2vrf/config" ocr2coordinator "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2vrf/coordinator" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2vrf/juelsfeecoin" @@ -62,7 +67,6 @@ import ( functionsRelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/functions" evmmercury "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" - evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" @@ -70,7 +74,28 @@ import ( "github.com/smartcontractkit/chainlink/v2/plugins" ) -var ErrJobSpecNoRelayer = errors.New("OCR2 job spec could not get relayer id") +type ErrJobSpecNoRelayer struct { + PluginName string + Err error +} + +func (e ErrJobSpecNoRelayer) Unwrap() error { return e.Err } + +func (e ErrJobSpecNoRelayer) Error() string { + return fmt.Sprintf("%s services: OCR2 job spec could not get relayer ID: %s", e.PluginName, e.Err) +} + +type ErrRelayNotEnabled struct { + PluginName string + Relay string + Err error +} + +func (e ErrRelayNotEnabled) Unwrap() error { return e.Err } + +func (e ErrRelayNotEnabled) Error() string { + return fmt.Sprintf("%s services: failed to get relay %s, is it enabled? %s", e.PluginName, e.Relay, e.Err) +} type RelayGetter interface { Get(id relay.ID) (loop.Relayer, error) @@ -93,7 +118,7 @@ type Delegate struct { isNewlyCreatedJob bool // Set to true if this is a new job freshly added, false if job was present already on node boot. mailMon *utils.MailboxMonitor - legacyChains evm.LegacyChainContainer // legacy: use relayers instead + legacyChains legacyevm.LegacyChainContainer // legacy: use relayers instead } type DelegateConfig interface { @@ -150,6 +175,7 @@ type ocr2Config interface { DatabaseTimeout() time.Duration KeyBundleID() (string, error) TraceLogging() bool + CaptureAutomationCustomTelemetry() bool } type insecureConfig interface { @@ -163,6 +189,7 @@ type jobPipelineConfig interface { type mercuryConfig interface { Credentials(credName string) *models.MercuryCredentials + Cache() coreconfig.MercuryCache } type thresholdConfig interface { @@ -191,7 +218,7 @@ func NewDelegate( pipelineRunner pipeline.Runner, peerWrapper *ocrcommon.SingletonPeerWrapper, monitoringEndpointGen telemetry.MonitoringEndpointGenerator, - legacyChains evm.LegacyChainContainer, + legacyChains legacyevm.LegacyChainContainer, lggr logger.Logger, cfg DelegateConfig, ks keystore.OCR2, @@ -245,7 +272,7 @@ func (d *Delegate) OnDeleteJob(jb job.Job, q pg.Queryer) error { rid, err := spec.RelayID() if err != nil { - d.lggr.Errorw("DeleteJob: "+ErrJobSpecNoRelayer.Error(), "err", err) + d.lggr.Errorw("DeleteJob", "err", ErrJobSpecNoRelayer{Err: err, PluginName: string(spec.PluginType)}) return nil } // we only have clean to do for the EVM @@ -337,7 +364,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { rid, err := spec.RelayID() if err != nil { - return nil, fmt.Errorf("ServicesForSpec: %w: %w", ErrJobSpecNoRelayer, err) + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: string(spec.PluginType)} } if rid.Network == relay.EVM { @@ -361,7 +388,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { return nil, errors.New("peerWrapper is not started. OCR2 jobs require a started and running p2p v2 peer") } - ocrLogger := relaylogger.NewOCRWrapper(lggr, d.cfg.OCR2().TraceLogging(), func(msg string) { + ocrLogger := commonlogger.NewOCRWrapper(lggr, d.cfg.OCR2().TraceLogging(), func(msg string) { lggr.ErrorIf(d.jobORM.RecordError(jb.ID, msg), "unable to record error") }) @@ -399,24 +426,22 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { spec.CaptureEATelemetry = d.cfg.OCR2().CaptureEATelemetry() - runResults := make(chan *pipeline.Run, d.cfg.JobPipeline().ResultWriteQueueDepth()) - ctx := lggrCtx.ContextWithValues(context.Background()) switch spec.PluginType { case types.Mercury: - return d.newServicesMercury(ctx, lggr, jb, runResults, bootstrapPeers, kb, ocrDB, lc, ocrLogger) + return d.newServicesMercury(ctx, lggr, jb, bootstrapPeers, kb, ocrDB, lc, ocrLogger) case types.Median: - return d.newServicesMedian(ctx, lggr, jb, runResults, bootstrapPeers, kb, ocrDB, lc, ocrLogger) + return d.newServicesMedian(ctx, lggr, jb, bootstrapPeers, kb, ocrDB, lc, ocrLogger) case types.DKG: return d.newServicesDKG(lggr, jb, bootstrapPeers, kb, ocrDB, lc, ocrLogger) case types.OCR2VRF: - return d.newServicesOCR2VRF(lggr, jb, runResults, bootstrapPeers, kb, ocrDB, lc) + return d.newServicesOCR2VRF(lggr, jb, bootstrapPeers, kb, ocrDB, lc) case types.OCR2Keeper: - return d.newServicesOCR2Keepers(lggr, jb, runResults, bootstrapPeers, kb, ocrDB, lc, ocrLogger) + return d.newServicesOCR2Keepers(lggr, jb, bootstrapPeers, kb, ocrDB, lc, ocrLogger) case types.Functions: const ( @@ -426,14 +451,17 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { ) thresholdPluginDB := NewDB(d.db, spec.ID, thresholdPluginId, lggr, d.cfg.Database()) s4PluginDB := NewDB(d.db, spec.ID, s4PluginId, lggr, d.cfg.Database()) - return d.newServicesOCR2Functions(lggr, jb, runResults, bootstrapPeers, kb, ocrDB, thresholdPluginDB, s4PluginDB, lc, ocrLogger) + return d.newServicesOCR2Functions(lggr, jb, bootstrapPeers, kb, ocrDB, thresholdPluginDB, s4PluginDB, lc, ocrLogger) + + case types.GenericPlugin: + return d.newServicesGenericPlugin(ctx, lggr, jb, bootstrapPeers, kb, ocrDB, lc, ocrLogger) default: return nil, errors.Errorf("plugin type %s not supported", spec.PluginType) } } -func GetEVMEffectiveTransmitterID(jb *job.Job, chain evm.Chain, lggr logger.SugaredLogger) (string, error) { +func GetEVMEffectiveTransmitterID(jb *job.Job, chain legacyevm.Chain, lggr logger.SugaredLogger) (string, error) { spec := jb.OCR2OracleSpec if spec.PluginType == types.Mercury { return spec.TransmitterID.String, nil @@ -473,11 +501,155 @@ func GetEVMEffectiveTransmitterID(jb *job.Job, chain evm.Chain, lggr logger.Suga return spec.TransmitterID.String, nil } +type connProvider interface { + ClientConn() grpc.ClientConnInterface +} + +func defaultPathFromPluginName(pluginName string) string { + // By default we install the command on the system path, in the + // form: `chainlink-` + return fmt.Sprintf("chainlink-%s", pluginName) +} + +func (d *Delegate) newServicesGenericPlugin( + ctx context.Context, + lggr logger.SugaredLogger, + jb job.Job, + bootstrapPeers []commontypes.BootstrapperLocator, + kb ocr2key.KeyBundle, + ocrDB *db, + lc ocrtypes.LocalConfig, + ocrLogger commontypes.Logger, +) (srvs []job.ServiceCtx, err error) { + spec := jb.OCR2OracleSpec + + // NOTE: we don't need to validate this config, since that happens as part of creating the job. + // See: validate/validate.go's `validateSpec`. + p := validate.OCR2GenericPluginConfig{} + err = json.Unmarshal(spec.PluginConfig.Bytes(), &p) + if err != nil { + return nil, err + } + + command := p.Command + if command == "" { + command = defaultPathFromPluginName(p.PluginName) + } + + // Add the default pipeline to the pluginConfig + p.Pipelines = append( + p.Pipelines, + validate.PipelineSpec{Name: "__DEFAULT_PIPELINE__", Spec: jb.Pipeline.Source}, + ) + + rid, err := spec.RelayID() + if err != nil { + return nil, ErrJobSpecNoRelayer{PluginName: p.PluginName, Err: err} + } + + relayer, err := d.RelayGetter.Get(rid) + if err != nil { + return nil, ErrRelayNotEnabled{Err: err, Relay: spec.Relay, PluginName: p.PluginName} + } + + provider, err := relayer.NewPluginProvider(ctx, types.RelayArgs{ + ExternalJobID: jb.ExternalJobID, + JobID: spec.ID, + ContractID: spec.ContractID, + New: d.isNewlyCreatedJob, + RelayConfig: spec.RelayConfig.Bytes(), + ProviderType: p.ProviderType, + }, types.PluginArgs{ + TransmitterID: spec.TransmitterID.String, + PluginConfig: spec.PluginConfig.Bytes(), + }) + if err != nil { + return nil, err + } + srvs = append(srvs, provider) + + oracleEndpoint := d.monitoringEndpointGen.GenMonitoringEndpoint( + rid.Network, + rid.ChainID, + spec.ContractID, + synchronization.TelemetryType(p.TelemetryType), + ) + oracleArgs := libocr2.OCR2OracleArgs{ + BinaryNetworkEndpointFactory: d.peerWrapper.Peer2, + V2Bootstrappers: bootstrapPeers, + Database: ocrDB, + LocalConfig: lc, + Logger: ocrLogger, + MonitoringEndpoint: oracleEndpoint, + OffchainKeyring: kb, + OnchainKeyring: kb, + ContractTransmitter: provider.ContractTransmitter(), + ContractConfigTracker: provider.ContractConfigTracker(), + OffchainConfigDigester: provider.OffchainConfigDigester(), + } + + pluginLggr := lggr.Named(p.PluginName).Named(spec.ContractID).Named(spec.GetID()) + cmdFn, grpcOpts, err := d.cfg.RegisterLOOP(fmt.Sprintf("%s-%s-%s", p.PluginName, spec.ContractID, spec.GetID()), command) + if err != nil { + return nil, fmt.Errorf("failed to register loop: %w", err) + } + + errorLog := &errorLog{jobID: jb.ID, recordError: d.jobORM.RecordError} + var providerClientConn grpc.ClientConnInterface + providerConn, ok := provider.(connProvider) + if ok { + providerClientConn = providerConn.ClientConn() + } else { + //We chose to deal with the difference between a LOOP provider and an embedded provider here rather than + //in NewServerAdapter because this has a smaller blast radius, as the scope of this workaround is to + //enable the medianpoc for EVM and not touch the other providers. + //TODO: remove this workaround when the EVM relayer is running inside of an LOOPP + d.lggr.Info("provider is not a LOOPP provider, switching to provider server") + + ps, err2 := relay.NewProviderServer(provider, types.OCR2PluginType(p.ProviderType), d.lggr) + if err2 != nil { + return nil, fmt.Errorf("cannot start EVM provider server: %s", err) + } + providerClientConn, err2 = ps.GetConn() + if err2 != nil { + return nil, fmt.Errorf("cannot connect to EVM provider server: %s", err) + } + srvs = append(srvs, ps) + } + + pc, err := json.Marshal(p.Config) + if err != nil { + return nil, fmt.Errorf("cannot dump plugin config to string before sending to plugin: %s", err) + } + + pluginConfig := types.ReportingPluginServiceConfig{ + PluginName: p.PluginName, + Command: command, + ProviderType: p.ProviderType, + TelemetryType: p.TelemetryType, + PluginConfig: string(pc), + } + + pr := generic.NewPipelineRunnerAdapter(pluginLggr, jb, d.pipelineRunner) + ta := generic.NewTelemetryAdapter(d.monitoringEndpointGen) + + plugin := reportingplugins.NewLOOPPService(pluginLggr, grpcOpts, cmdFn, pluginConfig, providerClientConn, pr, ta, errorLog) + oracleArgs.ReportingPluginFactory = plugin + srvs = append(srvs, plugin) + + oracle, err := libocr2.NewOracle(oracleArgs) + if err != nil { + return nil, err + } + + srvs = append(srvs, job.NewServiceAdapter(oracle)) + return srvs, nil +} + func (d *Delegate) newServicesMercury( ctx context.Context, lggr logger.SugaredLogger, jb job.Job, - runResults chan *pipeline.Run, bootstrapPeers []commontypes.BootstrapperLocator, kb ocr2key.KeyBundle, ocrDB *db, @@ -498,18 +670,14 @@ func (d *Delegate) newServicesMercury( rid, err := spec.RelayID() if err != nil { - return nil, fmt.Errorf("mercury services: %w: %w", ErrJobSpecNoRelayer, err) + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: "mercury"} } if rid.Network != relay.EVM { return nil, fmt.Errorf("mercury services: expected EVM relayer got %s", rid.Network) } relayer, err := d.RelayGetter.Get(rid) if err != nil { - return nil, fmt.Errorf("failed to get relay %s is it enabled?: %w", spec.Relay, err) - } - chain, err := d.legacyChains.Get(rid.ChainID) - if err != nil { - return nil, fmt.Errorf("mercury services: failed to get chain %s: %w", rid.ChainID, err) + return nil, ErrRelayNotEnabled{Err: err, Relay: spec.Relay, PluginName: "mercury"} } provider, err2 := relayer.NewPluginProvider(ctx, @@ -541,7 +709,7 @@ func (d *Delegate) newServicesMercury( Database: ocrDB, LocalConfig: lc, Logger: ocrLogger, - MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(spec.FeedID.String(), synchronization.OCR3Mercury, rid.Network, rid.ChainID), + MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.FeedID.String(), synchronization.OCR3Mercury), OffchainConfigDigester: mercuryProvider.OffchainConfigDigester(), OffchainKeyring: kb, OnchainKeyring: kb, @@ -549,11 +717,13 @@ func (d *Delegate) newServicesMercury( chEnhancedTelem := make(chan ocrcommon.EnhancedTelemetryMercuryData, 100) - mercuryServices, err2 := mercury.NewServices(jb, mercuryProvider, d.pipelineRunner, runResults, lggr, oracleArgsNoPlugin, d.cfg.JobPipeline(), chEnhancedTelem, chain, d.mercuryORM, (mercuryutils.FeedID)(*spec.FeedID)) + mercuryServices, err2 := mercury.NewServices(jb, mercuryProvider, d.pipelineRunner, lggr, oracleArgsNoPlugin, d.cfg.JobPipeline(), chEnhancedTelem, d.mercuryORM, (mercuryutils.FeedID)(*spec.FeedID)) - if ocrcommon.ShouldCollectEnhancedTelemetryMercury(&jb) { - enhancedTelemService := ocrcommon.NewEnhancedTelemetryService(&jb, chEnhancedTelem, make(chan struct{}), d.monitoringEndpointGen.GenMonitoringEndpoint(spec.FeedID.String(), synchronization.EnhancedEAMercury, rid.Network, rid.ChainID), lggr.Named("EnhancedTelemetryMercury")) + if ocrcommon.ShouldCollectEnhancedTelemetryMercury(jb) { + enhancedTelemService := ocrcommon.NewEnhancedTelemetryService(&jb, chEnhancedTelem, make(chan struct{}), d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.FeedID.String(), synchronization.EnhancedEAMercury), lggr.Named("EnhancedTelemetryMercury")) mercuryServices = append(mercuryServices, enhancedTelemService) + } else { + lggr.Infow("Enhanced telemetry is disabled for mercury job", "job", jb.Name) } return mercuryServices, err2 @@ -563,7 +733,6 @@ func (d *Delegate) newServicesMedian( ctx context.Context, lggr logger.SugaredLogger, jb job.Job, - runResults chan *pipeline.Run, bootstrapPeers []commontypes.BootstrapperLocator, kb ocr2key.KeyBundle, ocrDB *db, @@ -574,7 +743,7 @@ func (d *Delegate) newServicesMedian( rid, err := spec.RelayID() if err != nil { - return nil, fmt.Errorf("median services: %w: %w", ErrJobSpecNoRelayer, err) + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: "median"} } oracleArgsNoPlugin := libocr2.OCR2OracleArgs{ @@ -583,24 +752,30 @@ func (d *Delegate) newServicesMedian( Database: ocrDB, LocalConfig: lc, Logger: ocrLogger, - MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(spec.ContractID, synchronization.OCR2Median, rid.Network, rid.ChainID), + MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.ContractID, synchronization.OCR2Median), OffchainKeyring: kb, OnchainKeyring: kb, } errorLog := &errorLog{jobID: jb.ID, recordError: d.jobORM.RecordError} enhancedTelemChan := make(chan ocrcommon.EnhancedTelemetryData, 100) - mConfig := median.NewMedianConfig(d.cfg.JobPipeline().MaxSuccessfulRuns(), d.cfg) + mConfig := median.NewMedianConfig( + d.cfg.JobPipeline().MaxSuccessfulRuns(), + d.cfg.JobPipeline().ResultWriteQueueDepth(), + d.cfg, + ) relayer, err := d.RelayGetter.Get(rid) if err != nil { - return nil, fmt.Errorf("median services; failed to get relay %s is it enabled?: %w", spec.Relay, err) + return nil, ErrRelayNotEnabled{Err: err, PluginName: "median", Relay: spec.Relay} } - medianServices, err2 := median.NewMedianServices(ctx, jb, d.isNewlyCreatedJob, relayer, d.pipelineRunner, runResults, lggr, oracleArgsNoPlugin, mConfig, enhancedTelemChan, errorLog) + medianServices, err2 := median.NewMedianServices(ctx, jb, d.isNewlyCreatedJob, relayer, d.pipelineRunner, lggr, oracleArgsNoPlugin, mConfig, enhancedTelemChan, errorLog) if ocrcommon.ShouldCollectEnhancedTelemetry(&jb) { - enhancedTelemService := ocrcommon.NewEnhancedTelemetryService(&jb, enhancedTelemChan, make(chan struct{}), d.monitoringEndpointGen.GenMonitoringEndpoint(spec.ContractID, synchronization.EnhancedEA, rid.Network, rid.ChainID), lggr.Named("EnhancedTelemetry")) + enhancedTelemService := ocrcommon.NewEnhancedTelemetryService(&jb, enhancedTelemChan, make(chan struct{}), d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.ContractID, synchronization.EnhancedEA), lggr.Named("EnhancedTelemetry")) medianServices = append(medianServices, enhancedTelemService) + } else { + lggr.Infow("Enhanced telemetry is disabled for job", "job", jb.Name) } return medianServices, err2 @@ -618,7 +793,7 @@ func (d *Delegate) newServicesDKG( spec := jb.OCR2OracleSpec rid, err := spec.RelayID() if err != nil { - return nil, fmt.Errorf("DKG services: %w: %w", ErrJobSpecNoRelayer, err) + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: "DKG"} } if rid.Network != relay.EVM { return nil, fmt.Errorf("DKG services: expected EVM relayer got %s", rid.Network) @@ -677,7 +852,6 @@ func (d *Delegate) newServicesDKG( func (d *Delegate) newServicesOCR2VRF( lggr logger.SugaredLogger, jb job.Job, - runResults chan *pipeline.Run, bootstrapPeers []commontypes.BootstrapperLocator, kb ocr2key.KeyBundle, ocrDB *db, @@ -687,7 +861,7 @@ func (d *Delegate) newServicesOCR2VRF( rid, err := spec.RelayID() if err != nil { - return nil, fmt.Errorf("VRF services: %w: %w", ErrJobSpecNoRelayer, err) + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: "VRF"} } if rid.Network != relay.EVM { return nil, fmt.Errorf("VRF services: expected EVM relayer got %s", rid.Network) @@ -793,11 +967,11 @@ func (d *Delegate) newServicesOCR2VRF( "jobName", jb.Name.ValueOrZero(), "jobID", jb.ID, ) - vrfLogger := relaylogger.NewOCRWrapper(l.With( + vrfLogger := commonlogger.NewOCRWrapper(l.With( "vrfContractID", spec.ContractID), d.cfg.OCR2().TraceLogging(), func(msg string) { lggr.ErrorIf(d.jobORM.RecordError(jb.ID, msg), "unable to record error") }) - dkgLogger := relaylogger.NewOCRWrapper(l.With( + dkgLogger := commonlogger.NewOCRWrapper(l.With( "dkgContractID", cfg.DKGContractAddress), d.cfg.OCR2().TraceLogging(), func(msg string) { lggr.ErrorIf(d.jobORM.RecordError(jb.ID, msg), "unable to record error") }) @@ -820,7 +994,7 @@ func (d *Delegate) newServicesOCR2VRF( VRFContractTransmitter: vrfProvider.ContractTransmitter(), VRFDatabase: ocrDB, VRFLocalConfig: lc, - VRFMonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(spec.ContractID, synchronization.OCR2VRF, rid.Network, rid.ChainID), + VRFMonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.ContractID, synchronization.OCR2VRF), DKGContractConfigTracker: dkgProvider.ContractConfigTracker(), DKGOffchainConfigDigester: dkgProvider.OffchainConfigDigester(), DKGContract: dkgpkg.NewOnchainContract(dkgContract, &altbn_128.G2{}), @@ -844,28 +1018,16 @@ func (d *Delegate) newServicesOCR2VRF( return nil, errors.Wrap(err2, "new ocr2vrf") } - // 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{}), - lggr, - d.cfg.JobPipeline().MaxSuccessfulRuns(), - ) - // NOTE: we return from here with the services because the OCR2VRF oracles are defined // and exported from the ocr2vrf library. It takes care of running the DKG and OCR2VRF // oracles under the hood together. oracleCtx := job.NewServiceAdapter(oracles) - return []job.ServiceCtx{runResultSaver, vrfProvider, dkgProvider, oracleCtx}, nil + return []job.ServiceCtx{vrfProvider, dkgProvider, oracleCtx}, nil } func (d *Delegate) newServicesOCR2Keepers( lggr logger.SugaredLogger, jb job.Job, - runResults chan *pipeline.Run, bootstrapPeers []commontypes.BootstrapperLocator, kb ocr2key.KeyBundle, ocrDB *db, @@ -884,18 +1046,17 @@ func (d *Delegate) newServicesOCR2Keepers( switch cfg.ContractVersion { case "v2.1": - return d.newServicesOCR2Keepers21(lggr, jb, runResults, bootstrapPeers, kb, ocrDB, lc, ocrLogger, cfg, spec) + return d.newServicesOCR2Keepers21(lggr, jb, bootstrapPeers, kb, ocrDB, lc, ocrLogger, cfg, spec) case "v2.0": - return d.newServicesOCR2Keepers20(lggr, jb, runResults, bootstrapPeers, kb, ocrDB, lc, ocrLogger, cfg, spec) + return d.newServicesOCR2Keepers20(lggr, jb, bootstrapPeers, kb, ocrDB, lc, ocrLogger, cfg, spec) default: - return d.newServicesOCR2Keepers20(lggr, jb, runResults, bootstrapPeers, kb, ocrDB, lc, ocrLogger, cfg, spec) + return d.newServicesOCR2Keepers20(lggr, jb, bootstrapPeers, kb, ocrDB, lc, ocrLogger, cfg, spec) } } func (d *Delegate) newServicesOCR2Keepers21( lggr logger.SugaredLogger, jb job.Job, - runResults chan *pipeline.Run, bootstrapPeers []commontypes.BootstrapperLocator, kb ocr2key.KeyBundle, ocrDB *db, @@ -912,7 +1073,7 @@ func (d *Delegate) newServicesOCR2Keepers21( mc := d.cfg.Mercury().Credentials(credName) rid, err := spec.RelayID() if err != nil { - return nil, fmt.Errorf("keeper2 services: %w: %w", ErrJobSpecNoRelayer, err) + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: "keeper2"} } if rid.Network != relay.EVM { return nil, fmt.Errorf("keeper2 services: expected EVM relayer got %s", rid.Network) @@ -959,7 +1120,7 @@ func (d *Delegate) newServicesOCR2Keepers21( ContractConfigTracker: keeperProvider.ContractConfigTracker(), KeepersDatabase: ocrDB, Logger: ocrLogger, - MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(spec.ContractID, synchronization.OCR2Automation, rid.Network, rid.ChainID), + MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.ContractID, synchronization.OCR3Automation), OffchainConfigDigester: keeperProvider.OffchainConfigDigester(), OffchainKeyring: kb, OnchainKeyring: services.Keyring(), @@ -987,19 +1148,7 @@ func (d *Delegate) newServicesOCR2Keepers21( return nil, errors.Wrap(err, "could not create new keepers ocr2 delegate") } - // 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{}), - lggr, - d.cfg.JobPipeline().MaxSuccessfulRuns(), - ) - - return []job.ServiceCtx{ - runResultSaver, + automationServices := []job.ServiceCtx{ keeperProvider, services.Registry(), services.BlockSubscriber(), @@ -1008,13 +1157,29 @@ func (d *Delegate) newServicesOCR2Keepers21( services.UpkeepStateStore(), services.TransmitEventProvider(), pluginService, - }, nil + } + + if cfg.CaptureAutomationCustomTelemetry != nil && *cfg.CaptureAutomationCustomTelemetry || + cfg.CaptureAutomationCustomTelemetry == nil && d.cfg.OCR2().CaptureAutomationCustomTelemetry() { + endpoint := d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.ContractID, synchronization.AutomationCustom) + customTelemService, custErr := autotelemetry21.NewAutomationCustomTelemetryService( + endpoint, + lggr, + services.BlockSubscriber(), + keeperProvider.ContractConfigTracker(), + ) + if custErr != nil { + return nil, errors.Wrap(custErr, "Error when creating AutomationCustomTelemetryService") + } + automationServices = append(automationServices, customTelemService) + } + + return automationServices, nil } func (d *Delegate) newServicesOCR2Keepers20( lggr logger.SugaredLogger, jb job.Job, - runResults chan *pipeline.Run, bootstrapPeers []commontypes.BootstrapperLocator, kb ocr2key.KeyBundle, ocrDB *db, @@ -1026,7 +1191,7 @@ func (d *Delegate) newServicesOCR2Keepers20( rid, err := spec.RelayID() if err != nil { - return nil, fmt.Errorf("keepers2.0 services: %w: %w", ErrJobSpecNoRelayer, err) + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: "keepers2.0"} } if rid.Network != relay.EVM { return nil, fmt.Errorf("keepers2.0 services: expected EVM relayer got %s", rid.Network) @@ -1104,7 +1269,7 @@ func (d *Delegate) newServicesOCR2Keepers20( KeepersDatabase: ocrDB, LocalConfig: lc, Logger: ocrLogger, - MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(spec.ContractID, synchronization.OCR2Automation, rid.Network, rid.ChainID), + MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.ContractID, synchronization.OCR2Automation), OffchainConfigDigester: keeperProvider.OffchainConfigDigester(), OffchainKeyring: kb, OnchainKeyring: kb, @@ -1124,20 +1289,8 @@ func (d *Delegate) newServicesOCR2Keepers20( return nil, errors.Wrap(err, "could not create new keepers ocr2 delegate") } - // 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{}), - lggr, - d.cfg.JobPipeline().MaxSuccessfulRuns(), - ) - return []job.ServiceCtx{ job.NewServiceAdapter(runr), - runResultSaver, keeperProvider, rgstry, logProvider, @@ -1148,7 +1301,6 @@ func (d *Delegate) newServicesOCR2Keepers20( func (d *Delegate) newServicesOCR2Functions( lggr logger.SugaredLogger, jb job.Job, - runResults chan *pipeline.Run, bootstrapPeers []commontypes.BootstrapperLocator, kb ocr2key.KeyBundle, functionsOcrDB *db, @@ -1161,7 +1313,7 @@ func (d *Delegate) newServicesOCR2Functions( rid, err := spec.RelayID() if err != nil { - return nil, fmt.Errorf("functions services: %w: %w", ErrJobSpecNoRelayer, err) + return nil, ErrJobSpecNoRelayer{Err: err, PluginName: "functions"} } if rid.Network != relay.EVM { return nil, fmt.Errorf("functions services: expected EVM relayer got %s", rid.Network) @@ -1213,7 +1365,7 @@ func (d *Delegate) newServicesOCR2Functions( Database: functionsOcrDB, LocalConfig: lc, Logger: ocrLogger, - MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(spec.ContractID, synchronization.OCR2Functions, rid.Network, rid.ChainID), + MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.ContractID, synchronization.OCR2Functions), OffchainConfigDigester: functionsProvider.OffchainConfigDigester(), OffchainKeyring: kb, OnchainKeyring: kb, @@ -1277,7 +1429,7 @@ func (d *Delegate) newServicesOCR2Functions( ContractID: spec.ContractID, Logger: lggr, MailMon: d.mailMon, - URLsMonEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(spec.ContractID, synchronization.FunctionsRequests, rid.Network, rid.ChainID), + URLsMonEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.ContractID, synchronization.FunctionsRequests), EthKeystore: d.ethKs, ThresholdKeyShare: thresholdKeyShare, LogPollerWrapper: functionsProvider.LogPollerWrapper(), @@ -1288,18 +1440,7 @@ func (d *Delegate) newServicesOCR2Functions( return nil, errors.Wrap(err, "error calling NewFunctionsServices") } - // 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{}), - lggr, - d.cfg.JobPipeline().MaxSuccessfulRuns(), - ) - - return append([]job.ServiceCtx{runResultSaver, functionsProvider, thresholdProvider, s4Provider}, functionsServices...), nil + return append([]job.ServiceCtx{functionsProvider, thresholdProvider, s4Provider}, functionsServices...), nil } // errorLog implements [loop.ErrorLog] diff --git a/core/services/ocr2/delegate_test.go b/core/services/ocr2/delegate_test.go index 3973774c56a..b55e128119d 100644 --- a/core/services/ocr2/delegate_test.go +++ b/core/services/ocr2/delegate_test.go @@ -8,12 +8,12 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/ocr2/plugins/dkg/config/config_test.go b/core/services/ocr2/plugins/dkg/config/config_test.go index b49a5277d9c..fe796a9ad6c 100644 --- a/core/services/ocr2/plugins/dkg/config/config_test.go +++ b/core/services/ocr2/plugins/dkg/config/config_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/dkg/config" ) diff --git a/core/services/ocr2/plugins/dkg/key_consumer.go b/core/services/ocr2/plugins/dkg/key_consumer.go index 99115af70de..23ca7e795bf 100644 --- a/core/services/ocr2/plugins/dkg/key_consumer.go +++ b/core/services/ocr2/plugins/dkg/key_consumer.go @@ -4,7 +4,7 @@ import ( "encoding/hex" "fmt" - "github.com/smartcontractkit/ocr2vrf/dkg" + "github.com/smartcontractkit/chainlink-vrf/dkg" ) type dummyKeyConsumer struct{} diff --git a/core/services/ocr2/plugins/dkg/onchain_contract.go b/core/services/ocr2/plugins/dkg/onchain_contract.go index 5f23f33c5d9..c6ea84de235 100644 --- a/core/services/ocr2/plugins/dkg/onchain_contract.go +++ b/core/services/ocr2/plugins/dkg/onchain_contract.go @@ -8,9 +8,9 @@ import ( "github.com/pkg/errors" "go.dedis.ch/kyber/v3/sign/anon" - "github.com/smartcontractkit/ocr2vrf/dkg" - dkgwrapper "github.com/smartcontractkit/ocr2vrf/gethwrappers/dkg" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" + "github.com/smartcontractkit/chainlink-vrf/dkg" + dkgwrapper "github.com/smartcontractkit/chainlink-vrf/gethwrappers/dkg" + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" ) diff --git a/core/services/ocr2/plugins/dkg/persistence/db.go b/core/services/ocr2/plugins/dkg/persistence/db.go index 75fb3b391fa..304422ace2d 100644 --- a/core/services/ocr2/plugins/dkg/persistence/db.go +++ b/core/services/ocr2/plugins/dkg/persistence/db.go @@ -9,13 +9,15 @@ import ( "time" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" - "github.com/smartcontractkit/ocr2vrf/types/hash" - "github.com/smartcontractkit/sqlx" + + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" + "github.com/smartcontractkit/chainlink-vrf/types/hash" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" diff --git a/core/services/ocr2/plugins/dkg/persistence/db_test.go b/core/services/ocr2/plugins/dkg/persistence/db_test.go index b830a8db3bc..b4fa000cb99 100644 --- a/core/services/ocr2/plugins/dkg/persistence/db_test.go +++ b/core/services/ocr2/plugins/dkg/persistence/db_test.go @@ -1,18 +1,18 @@ package persistence import ( - "context" "fmt" "math/big" "testing" "github.com/ethereum/go-ethereum/crypto" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" - "github.com/smartcontractkit/ocr2vrf/types/hash" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" + "github.com/smartcontractkit/chainlink-vrf/types/hash" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -50,7 +50,7 @@ func TestShareDB_WriteShareRecords(t *testing.T) { expectedRecords = append(expectedRecords, rec) } - err := shareDB.WriteShareRecords(context.TODO(), configDigest, keyID, expectedRecords) + err := shareDB.WriteShareRecords(testutils.Context(t), configDigest, keyID, expectedRecords) require.NoError(tt, err) rows, err := db.Query(`SELECT COUNT(*) AS count FROM dkg_shares`) @@ -79,7 +79,7 @@ func TestShareDB_WriteShareRecords(t *testing.T) { } // no error, but there will be no rows inserted in the db - err = shareDB.WriteShareRecords(context.TODO(), configDigest, keyID, records) + err = shareDB.WriteShareRecords(testutils.Context(t), configDigest, keyID, records) require.NoError(tt, err) rows, err := db.Query(`SELECT COUNT(*) AS count FROM dkg_shares`) @@ -116,7 +116,7 @@ func TestShareDB_WriteShareRecords(t *testing.T) { records = append(records, rec) } - err := shareDB.WriteShareRecords(context.TODO(), configDigest, keyID, records) + err := shareDB.WriteShareRecords(testutils.Context(t), configDigest, keyID, records) require.Error(tt, err) // no rows should have been inserted @@ -160,7 +160,7 @@ func TestShareDBE2E(t *testing.T) { expectedRecordsMap[*playerIdx] = rec } - err := shareDB.WriteShareRecords(context.TODO(), configDigest, keyID, expectedRecords) + err := shareDB.WriteShareRecords(testutils.Context(t), configDigest, keyID, expectedRecords) require.NoError(t, err) actualRecords, err := shareDB.ReadShareRecords(configDigest, keyID) diff --git a/core/services/ocr2/plugins/dkg/plugin.go b/core/services/ocr2/plugins/dkg/plugin.go index 540518b553c..0310122655f 100644 --- a/core/services/ocr2/plugins/dkg/plugin.go +++ b/core/services/ocr2/plugins/dkg/plugin.go @@ -6,12 +6,14 @@ import ( "fmt" "math/big" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/commontypes" libocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus" - "github.com/smartcontractkit/ocr2vrf/altbn_128" - "github.com/smartcontractkit/ocr2vrf/dkg" - "github.com/smartcontractkit/sqlx" + + "github.com/smartcontractkit/chainlink-vrf/altbn_128" + "github.com/smartcontractkit/chainlink-vrf/dkg" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/ocr2/plugins/functions/config/config.go b/core/services/ocr2/plugins/functions/config/config.go index 3f35d1dba9b..13e02042506 100644 --- a/core/services/ocr2/plugins/functions/config/config.go +++ b/core/services/ocr2/plugins/functions/config/config.go @@ -10,7 +10,7 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" @@ -23,7 +23,11 @@ type PluginConfig struct { EnableRequestSignatureCheck bool `json:"enableRequestSignatureCheck"` DONID string `json:"donID"` ContractVersion uint32 `json:"contractVersion"` + MinRequestConfirmations uint32 `json:"minRequestConfirmations"` + MinResponseConfirmations uint32 `json:"minResponseConfirmations"` MinIncomingConfirmations uint32 `json:"minIncomingConfirmations"` + PastBlocksToPoll uint32 `json:"pastBlocksToPoll"` + LogPollerCacheDurationSec uint32 `json:"logPollerCacheDurationSec"` // Duration to cache previously detected request or response logs such that they can be filtered when calling logpoller_wrapper.LatestEvents() RequestTimeoutSec uint32 `json:"requestTimeoutSec"` RequestTimeoutCheckFrequencySec uint32 `json:"requestTimeoutCheckFrequencySec"` RequestTimeoutBatchLookupSize uint32 `json:"requestTimeoutBatchLookupSize"` diff --git a/core/services/ocr2/plugins/functions/integration_tests/v1/internal/testutils.go b/core/services/ocr2/plugins/functions/integration_tests/v1/internal/testutils.go index 5c824323eb6..e576e829677 100644 --- a/core/services/ocr2/plugins/functions/integration_tests/v1/internal/testutils.go +++ b/core/services/ocr2/plugins/functions/integration_tests/v1/internal/testutils.go @@ -29,8 +29,8 @@ import ( confighelper2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" ocrtypes2 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_allow_list" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_client_example" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_coordinator" @@ -302,7 +302,6 @@ func StartNewNode( t *testing.T, owner *bind.TransactOpts, port int, - dbName string, b *backends.SimulatedBackend, maxGas uint32, p2pV2Bootstrappers []commontypes.BootstrapperLocator, @@ -310,7 +309,7 @@ func StartNewNode( thresholdKeyShare string, ) *Node { p2pKey := keystest.NewP2PKeyV2(t) - config, _ := heavyweight.FullTestDBV2(t, fmt.Sprintf("%s%d", dbName, port), func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Insecure.OCRDevelopmentMode = ptr(true) c.Feature.LogPoller = ptr(true) @@ -550,7 +549,7 @@ func CreateFunctionsNodes( } bootstrapPort := freeport.GetOne(t) - bootstrapNode = StartNewNode(t, owner, bootstrapPort, "bootstrap", b, uint32(maxGas), nil, nil, "") + bootstrapNode = StartNewNode(t, owner, bootstrapPort, b, uint32(maxGas), nil, nil, "") AddBootstrapJob(t, bootstrapNode.App, routerAddress) // oracle nodes with jobs, bridges and mock EAs @@ -568,7 +567,7 @@ func CreateFunctionsNodes( } else { ocr2Keystore = ocr2Keystores[i] } - oracleNode := StartNewNode(t, owner, ports[i], fmt.Sprintf("oracle%d", i), b, uint32(maxGas), []commontypes.BootstrapperLocator{ + oracleNode := StartNewNode(t, owner, ports[i], b, uint32(maxGas), []commontypes.BootstrapperLocator{ {PeerID: bootstrapNode.PeerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapPort)}}, }, ocr2Keystore, thresholdKeyShare) oracleNodes = append(oracleNodes, oracleNode.App) diff --git a/core/services/ocr2/plugins/functions/plugin.go b/core/services/ocr2/plugins/functions/plugin.go index 10f780371b2..2ebd7e30805 100644 --- a/core/services/ocr2/plugins/functions/plugin.go +++ b/core/services/ocr2/plugins/functions/plugin.go @@ -7,15 +7,15 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/libocr/commontypes" libocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/functions" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" @@ -39,7 +39,7 @@ type FunctionsServicesConfig struct { BridgeORM bridges.ORM QConfig pg.QConfig DB *sqlx.DB - Chain evm.Chain + Chain legacyevm.Chain ContractID string Logger logger.Logger MailMon *utils.MailboxMonitor @@ -50,9 +50,10 @@ type FunctionsServicesConfig struct { } const ( - FunctionsBridgeName string = "ea_bridge" - FunctionsS4Namespace string = "functions" - MaxAdapterResponseBytes int64 = 1_000_000 + FunctionsBridgeName string = "ea_bridge" + FunctionsS4Namespace string = "functions" + MaxAdapterResponseBytes int64 = 1_000_000 + DefaultOffchainTransmitterChannelSize uint32 = 1000 ) // Create all OCR2 plugin Oracles and all extra services needed to run a Functions job. @@ -100,6 +101,7 @@ func NewFunctionsServices(functionsOracleArgs, thresholdOracleArgs, s4OracleArgs s4Storage = s4.NewStorage(conf.Logger, *pluginConfig.S4Constraints, s4ORM, utils.NewRealClock()) } + offchainTransmitter := functions.NewOffchainTransmitter(DefaultOffchainTransmitterChannelSize) listenerLogger := conf.Logger.Named("FunctionsListener") bridgeAccessor := functions.NewBridgeAccessor(conf.BridgeORM, FunctionsBridgeName, MaxAdapterResponseBytes) functionsListener := functions.NewFunctionsListener( @@ -118,10 +120,11 @@ func NewFunctionsServices(functionsOracleArgs, thresholdOracleArgs, s4OracleArgs allServices = append(allServices, functionsListener) functionsOracleArgs.ReportingPluginFactory = FunctionsReportingPluginFactory{ - Logger: functionsOracleArgs.Logger, - PluginORM: pluginORM, - JobID: conf.Job.ExternalJobID, - ContractVersion: pluginConfig.ContractVersion, + Logger: functionsOracleArgs.Logger, + PluginORM: pluginORM, + JobID: conf.Job.ExternalJobID, + ContractVersion: pluginConfig.ContractVersion, + OffchainTransmitter: offchainTransmitter, } functionsReportingPluginOracle, err := libocr2.NewOracle(*functionsOracleArgs) if err != nil { @@ -143,7 +146,7 @@ func NewFunctionsServices(functionsOracleArgs, thresholdOracleArgs, s4OracleArgs return nil, errors.Wrap(err, "failed to create a OnchainSubscriptions") } connectorLogger := conf.Logger.Named("GatewayConnector").With("jobName", conf.Job.PipelineSpec.JobName) - connector, err2 := NewConnector(pluginConfig.GatewayConnectorConfig, conf.EthKeystore, conf.Chain.ID(), s4Storage, allowlist, rateLimiter, subscriptions, pluginConfig.MinimumSubscriptionBalance, connectorLogger) + connector, err2 := NewConnector(pluginConfig.GatewayConnectorConfig, conf.EthKeystore, conf.Chain.ID(), s4Storage, allowlist, rateLimiter, subscriptions, functionsListener, offchainTransmitter, pluginConfig.MinimumSubscriptionBalance, connectorLogger) if err2 != nil { return nil, errors.Wrap(err, "failed to create a GatewayConnector") } @@ -170,7 +173,7 @@ func NewFunctionsServices(functionsOracleArgs, thresholdOracleArgs, s4OracleArgs return allServices, nil } -func NewConnector(gwcCfg *connector.ConnectorConfig, ethKeystore keystore.Eth, chainID *big.Int, s4Storage s4.Storage, allowlist gwFunctions.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions gwFunctions.OnchainSubscriptions, minimumBalance assets.Link, lggr logger.Logger) (connector.GatewayConnector, error) { +func NewConnector(gwcCfg *connector.ConnectorConfig, ethKeystore keystore.Eth, chainID *big.Int, s4Storage s4.Storage, allowlist gwFunctions.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions gwFunctions.OnchainSubscriptions, listener functions.FunctionsListener, offchainTransmitter functions.OffchainTransmitter, minimumBalance assets.Link, lggr logger.Logger) (connector.GatewayConnector, error) { enabledKeys, err := ethKeystore.EnabledKeysForChain(chainID) if err != nil { return nil, err @@ -183,7 +186,7 @@ func NewConnector(gwcCfg *connector.ConnectorConfig, ethKeystore keystore.Eth, c signerKey := enabledKeys[idx].ToEcdsaPrivKey() nodeAddress := enabledKeys[idx].ID() - handler, err := functions.NewFunctionsConnectorHandler(nodeAddress, signerKey, s4Storage, allowlist, rateLimiter, subscriptions, minimumBalance, lggr) + handler, err := functions.NewFunctionsConnectorHandler(nodeAddress, signerKey, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, minimumBalance, lggr) if err != nil { return nil, err } diff --git a/core/services/ocr2/plugins/functions/plugin_test.go b/core/services/ocr2/plugins/functions/plugin_test.go index 2e35672e920..d77fabcc437 100644 --- a/core/services/ocr2/plugins/functions/plugin_test.go +++ b/core/services/ocr2/plugins/functions/plugin_test.go @@ -8,8 +8,9 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/logger" + sfmocks "github.com/smartcontractkit/chainlink/v2/core/services/functions/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" gfmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/mocks" @@ -35,8 +36,10 @@ func TestNewConnector_Success(t *testing.T) { subscriptions := gfmocks.NewOnchainSubscriptions(t) rateLimiter, err := hc.NewRateLimiter(hc.RateLimiterConfig{GlobalRPS: 100.0, GlobalBurst: 100, PerSenderRPS: 100.0, PerSenderBurst: 100}) require.NoError(t, err) + listener := sfmocks.NewFunctionsListener(t) + offchainTransmitter := sfmocks.NewOffchainTransmitter(t) ethKeystore.On("EnabledKeysForChain", mock.Anything).Return([]ethkey.KeyV2{keyV2}, nil) - _, err = functions.NewConnector(gwcCfg, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, *assets.NewLinkFromJuels(0), logger.TestLogger(t)) + _, err = functions.NewConnector(gwcCfg, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, *assets.NewLinkFromJuels(0), logger.TestLogger(t)) require.NoError(t, err) } @@ -58,7 +61,9 @@ func TestNewConnector_NoKeyForConfiguredAddress(t *testing.T) { subscriptions := gfmocks.NewOnchainSubscriptions(t) rateLimiter, err := hc.NewRateLimiter(hc.RateLimiterConfig{GlobalRPS: 100.0, GlobalBurst: 100, PerSenderRPS: 100.0, PerSenderBurst: 100}) require.NoError(t, err) + listener := sfmocks.NewFunctionsListener(t) + offchainTransmitter := sfmocks.NewOffchainTransmitter(t) ethKeystore.On("EnabledKeysForChain", mock.Anything).Return([]ethkey.KeyV2{{Address: common.HexToAddress(addresses[1])}}, nil) - _, err = functions.NewConnector(gwcCfg, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, *assets.NewLinkFromJuels(0), logger.TestLogger(t)) + _, err = functions.NewConnector(gwcCfg, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, *assets.NewLinkFromJuels(0), logger.TestLogger(t)) require.Error(t, err) } diff --git a/core/services/ocr2/plugins/functions/reporting.go b/core/services/ocr2/plugins/functions/reporting.go index dfa8f575d1b..36e8a882734 100644 --- a/core/services/ocr2/plugins/functions/reporting.go +++ b/core/services/ocr2/plugins/functions/reporting.go @@ -1,6 +1,7 @@ package functions import ( + "bytes" "context" "fmt" @@ -21,22 +22,24 @@ import ( ) type FunctionsReportingPluginFactory struct { - Logger commontypes.Logger - PluginORM functions.ORM - JobID uuid.UUID - ContractVersion uint32 + Logger commontypes.Logger + PluginORM functions.ORM + JobID uuid.UUID + ContractVersion uint32 + OffchainTransmitter functions.OffchainTransmitter } var _ types.ReportingPluginFactory = (*FunctionsReportingPluginFactory)(nil) type functionsReporting struct { - logger commontypes.Logger - pluginORM functions.ORM - jobID uuid.UUID - reportCodec encoding.ReportCodec - genericConfig *types.ReportingPluginConfig - specificConfig *config.ReportingPluginConfigWrapper - contractVersion uint32 + logger commontypes.Logger + pluginORM functions.ORM + jobID uuid.UUID + reportCodec encoding.ReportCodec + genericConfig *types.ReportingPluginConfig + specificConfig *config.ReportingPluginConfigWrapper + contractVersion uint32 + offchainTransmitter functions.OffchainTransmitter } var _ types.ReportingPlugin = &functionsReporting{} @@ -112,13 +115,14 @@ func (f FunctionsReportingPluginFactory) NewReportingPlugin(rpConfig types.Repor }, } plugin := functionsReporting{ - logger: f.Logger, - pluginORM: f.PluginORM, - jobID: f.JobID, - reportCodec: codec, - genericConfig: &rpConfig, - specificConfig: pluginConfig, - contractVersion: f.ContractVersion, + logger: f.Logger, + pluginORM: f.PluginORM, + jobID: f.JobID, + reportCodec: codec, + genericConfig: &rpConfig, + specificConfig: pluginConfig, + contractVersion: f.ContractVersion, + offchainTransmitter: f.OffchainTransmitter, } promReportingPlugins.WithLabelValues(f.JobID.String()).Inc() return &plugin, info, nil @@ -437,6 +441,14 @@ func (r *functionsReporting) ShouldAcceptFinalizedReport(ctx context.Context, ts r.logger.Debug("FunctionsReporting ShouldAcceptFinalizedReport: state couldn't be changed to FINALIZED. Not transmitting.", commontypes.LogFields{"requestID": reqIdStr, "err": err}) continue } + if bytes.Equal(item.OnchainMetadata, []byte(functions.OffchainRequestMarker)) { + r.logger.Debug("FunctionsReporting ShouldAcceptFinalizedReport: transmitting offchain", commontypes.LogFields{"requestID": reqIdStr}) + result := functions.OffchainResponse{RequestId: item.RequestID, Result: item.Result, Error: item.Error} + if err := r.offchainTransmitter.TransmitReport(ctx, &result); err != nil { + r.logger.Error("FunctionsReporting ShouldAcceptFinalizedReport: unable to transmit offchain", commontypes.LogFields{"requestID": reqIdStr, "err": err}) + } + continue // doesn't need onchain transmission + } needTransmissionIds = append(needTransmissionIds, reqIdStr) } r.logger.Debug("FunctionsReporting ShouldAcceptFinalizedReport end", commontypes.LogFields{ diff --git a/core/services/ocr2/plugins/functions/reporting_test.go b/core/services/ocr2/plugins/functions/reporting_test.go index 24b430abd93..5b9f59ccb23 100644 --- a/core/services/ocr2/plugins/functions/reporting_test.go +++ b/core/services/ocr2/plugins/functions/reporting_test.go @@ -12,7 +12,7 @@ import ( "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - relaylogger "github.com/smartcontractkit/chainlink-relay/pkg/logger" + commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" functions_srv "github.com/smartcontractkit/chainlink/v2/core/services/functions" @@ -22,14 +22,16 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions/encoding" ) -func preparePlugin(t *testing.T, batchSize uint32, maxTotalGasLimit uint32) (types.ReportingPlugin, *functions_mocks.ORM, encoding.ReportCodec) { +func preparePlugin(t *testing.T, batchSize uint32, maxTotalGasLimit uint32) (types.ReportingPlugin, *functions_mocks.ORM, encoding.ReportCodec, *functions_mocks.OffchainTransmitter) { lggr := logger.TestLogger(t) - ocrLogger := relaylogger.NewOCRWrapper(lggr, true, func(msg string) {}) + ocrLogger := commonlogger.NewOCRWrapper(lggr, true, func(msg string) {}) orm := functions_mocks.NewORM(t) + offchainTransmitter := functions_mocks.NewOffchainTransmitter(t) factory := functions.FunctionsReportingPluginFactory{ - Logger: ocrLogger, - PluginORM: orm, - ContractVersion: 1, + Logger: ocrLogger, + PluginORM: orm, + ContractVersion: 1, + OffchainTransmitter: offchainTransmitter, } pluginConfig := config.ReportingPluginConfigWrapper{ @@ -48,7 +50,7 @@ func preparePlugin(t *testing.T, batchSize uint32, maxTotalGasLimit uint32) (typ require.NoError(t, err) codec, err := encoding.NewReportCodec(1) require.NoError(t, err) - return plugin, orm, codec + return plugin, orm, codec, offchainTransmitter } func newRequestID() functions_srv.RequestID { @@ -130,7 +132,7 @@ func newObservation(t *testing.T, observerId uint8, requests ...*encoding.Proces func TestFunctionsReporting_Query(t *testing.T) { t.Parallel() const batchSize = 10 - plugin, orm, _ := preparePlugin(t, batchSize, 0) + plugin, orm, _, _ := preparePlugin(t, batchSize, 0) reqs := []functions_srv.Request{newRequest(), newRequest()} orm.On("FindOldestEntriesByState", functions_srv.RESULT_READY, uint32(batchSize), mock.Anything).Return(reqs, nil) @@ -148,7 +150,7 @@ func TestFunctionsReporting_Query(t *testing.T) { func TestFunctionsReporting_Query_HandleCoordinatorMismatch(t *testing.T) { t.Parallel() const batchSize = 10 - plugin, orm, _ := preparePlugin(t, batchSize, 1000000) + plugin, orm, _, _ := preparePlugin(t, batchSize, 1000000) reqs := []functions_srv.Request{newRequest(), newRequest()} reqs[0].CoordinatorContractAddress = &common.Address{1} reqs[1].CoordinatorContractAddress = &common.Address{2} @@ -167,7 +169,7 @@ func TestFunctionsReporting_Query_HandleCoordinatorMismatch(t *testing.T) { func TestFunctionsReporting_Observation(t *testing.T) { t.Parallel() - plugin, orm, _ := preparePlugin(t, 10, 0) + plugin, orm, _, _ := preparePlugin(t, 10, 0) req1 := newRequestWithResult([]byte("abc")) req2 := newRequest() @@ -202,7 +204,7 @@ func TestFunctionsReporting_Observation(t *testing.T) { func TestFunctionsReporting_Observation_IncorrectQuery(t *testing.T) { t.Parallel() - plugin, orm, _ := preparePlugin(t, 10, 0) + plugin, orm, _, _ := preparePlugin(t, 10, 0) req1 := newRequestWithResult([]byte("abc")) invalidId := []byte("invalid") @@ -229,7 +231,7 @@ func TestFunctionsReporting_Observation_IncorrectQuery(t *testing.T) { func TestFunctionsReporting_Report(t *testing.T) { t.Parallel() - plugin, _, codec := preparePlugin(t, 10, 1000000) + plugin, _, codec, _ := preparePlugin(t, 10, 1000000) reqId1, reqId2, reqId3 := newRequestID(), newRequestID(), newRequestID() compResult := []byte("aaa") procReq1 := newProcessedRequest(reqId1, compResult, []byte{}) @@ -266,7 +268,7 @@ func TestFunctionsReporting_Report(t *testing.T) { func TestFunctionsReporting_Report_WithGasLimitAndMetadata(t *testing.T) { t.Parallel() - plugin, _, codec := preparePlugin(t, 10, 300000) + plugin, _, codec, _ := preparePlugin(t, 10, 300000) reqId1, reqId2, reqId3 := newRequestID(), newRequestID(), newRequestID() compResult := []byte("aaa") gasLimit1, gasLimit2 := uint32(100_000), uint32(200_000) @@ -307,7 +309,7 @@ func TestFunctionsReporting_Report_WithGasLimitAndMetadata(t *testing.T) { func TestFunctionsReporting_Report_HandleCoordinatorMismatch(t *testing.T) { t.Parallel() - plugin, _, codec := preparePlugin(t, 10, 300000) + plugin, _, codec, _ := preparePlugin(t, 10, 300000) reqId1, reqId2, reqId3 := newRequestID(), newRequestID(), newRequestID() compResult, meta := []byte("aaa"), []byte("meta") coordinatorContractA, coordinatorContractB := common.Address{1}, common.Address{2} @@ -337,7 +339,7 @@ func TestFunctionsReporting_Report_HandleCoordinatorMismatch(t *testing.T) { func TestFunctionsReporting_Report_CallbackGasLimitExceeded(t *testing.T) { t.Parallel() - plugin, _, codec := preparePlugin(t, 10, 200000) + plugin, _, codec, _ := preparePlugin(t, 10, 200000) reqId1, reqId2 := newRequestID(), newRequestID() compResult := []byte("aaa") gasLimit1, gasLimit2 := uint32(100_000), uint32(200_000) @@ -368,7 +370,7 @@ func TestFunctionsReporting_Report_CallbackGasLimitExceeded(t *testing.T) { func TestFunctionsReporting_Report_DeterministicOrderOfRequests(t *testing.T) { t.Parallel() - plugin, _, codec := preparePlugin(t, 10, 0) + plugin, _, codec, _ := preparePlugin(t, 10, 0) reqId1, reqId2, reqId3 := newRequestID(), newRequestID(), newRequestID() compResult := []byte("aaa") @@ -397,7 +399,7 @@ func TestFunctionsReporting_Report_DeterministicOrderOfRequests(t *testing.T) { func TestFunctionsReporting_Report_IncorrectObservation(t *testing.T) { t.Parallel() - plugin, _, _ := preparePlugin(t, 10, 0) + plugin, _, _, _ := preparePlugin(t, 10, 0) reqId1 := newRequestID() compResult := []byte("aaa") @@ -416,7 +418,14 @@ func getReportBytes(t *testing.T, codec encoding.ReportCodec, reqs ...functions_ var report []*encoding.ProcessedRequest for _, req := range reqs { req := req - report = append(report, &encoding.ProcessedRequest{RequestID: req.RequestID[:], Result: req.Result}) + report = append(report, &encoding.ProcessedRequest{ + RequestID: req.RequestID[:], + Result: req.Result, + Error: req.Error, + CallbackGasLimit: *req.CallbackGasLimit, + CoordinatorContract: req.CoordinatorContractAddress[:], + OnchainMetadata: req.OnchainMetadata, + }) } reportBytes, err := codec.EncodeReport(report) require.NoError(t, err) @@ -425,7 +434,7 @@ func getReportBytes(t *testing.T, codec encoding.ReportCodec, reqs ...functions_ func TestFunctionsReporting_ShouldAcceptFinalizedReport(t *testing.T) { t.Parallel() - plugin, orm, codec := preparePlugin(t, 10, 0) + plugin, orm, codec, _ := preparePlugin(t, 10, 0) req1 := newRequestWithResult([]byte("xxx")) // nonexistent req2 := newRequestWithResult([]byte("abc")) @@ -462,9 +471,24 @@ func TestFunctionsReporting_ShouldAcceptFinalizedReport(t *testing.T) { require.True(t, should) } +func TestFunctionsReporting_ShouldAcceptFinalizedReport_OffchainTransmission(t *testing.T) { + t.Parallel() + plugin, orm, codec, offchainTransmitter := preparePlugin(t, 10, 0) + req1 := newRequestWithResult([]byte("abc")) + req1.OnchainMetadata = []byte(functions_srv.OffchainRequestMarker) + + orm.On("FindById", req1.RequestID, mock.Anything).Return(&req1, nil) + orm.On("SetFinalized", req1.RequestID, mock.Anything, mock.Anything, mock.Anything).Return(nil) + offchainTransmitter.On("TransmitReport", mock.Anything, mock.Anything).Return(nil) + + should, err := plugin.ShouldAcceptFinalizedReport(testutils.Context(t), types.ReportTimestamp{}, getReportBytes(t, codec, req1)) + require.NoError(t, err) + require.False(t, should) +} + func TestFunctionsReporting_ShouldTransmitAcceptedReport(t *testing.T) { t.Parallel() - plugin, orm, codec := preparePlugin(t, 10, 0) + plugin, orm, codec, _ := preparePlugin(t, 10, 0) req1 := newRequestWithResult([]byte("xxx")) // nonexistent req2 := newRequestWithResult([]byte("abc")) diff --git a/core/services/ocr2/plugins/generic/helpers_test.go b/core/services/ocr2/plugins/generic/helpers_test.go new file mode 100644 index 00000000000..e23e8e46429 --- /dev/null +++ b/core/services/ocr2/plugins/generic/helpers_test.go @@ -0,0 +1,7 @@ +package generic + +import "github.com/smartcontractkit/libocr/commontypes" + +func (t *TelemetryAdapter) Endpoints() map[[4]string]commontypes.MonitoringEndpoint { + return t.endpoints +} diff --git a/core/services/ocr2/plugins/generic/merge_test.go b/core/services/ocr2/plugins/generic/merge_test.go new file mode 100644 index 00000000000..9618c62357d --- /dev/null +++ b/core/services/ocr2/plugins/generic/merge_test.go @@ -0,0 +1,32 @@ +package generic + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMerge(t *testing.T) { + vars := map[string]interface{}{ + "jb": map[string]interface{}{ + "databaseID": "some-job-id", + }, + } + addedVars := map[string]interface{}{ + "jb": map[string]interface{}{ + "some-other-var": "foo", + }, + "val": 0, + } + + merge(vars, addedVars) + + assert.True(t, reflect.DeepEqual(vars, map[string]interface{}{ + "jb": map[string]interface{}{ + "databaseID": "some-job-id", + "some-other-var": "foo", + }, + "val": 0, + }), vars) +} diff --git a/core/services/ocr2/plugins/generic/pipeline_runner_adapter.go b/core/services/ocr2/plugins/generic/pipeline_runner_adapter.go new file mode 100644 index 00000000000..872f83d3c35 --- /dev/null +++ b/core/services/ocr2/plugins/generic/pipeline_runner_adapter.go @@ -0,0 +1,93 @@ +package generic + +import ( + "context" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/store/models" +) + +var _ types.PipelineRunnerService = (*PipelineRunnerAdapter)(nil) + +type pipelineRunner interface { + ExecuteRun(ctx context.Context, spec pipeline.Spec, vars pipeline.Vars, l logger.Logger) (run *pipeline.Run, trrs pipeline.TaskRunResults, err error) +} + +type PipelineRunnerAdapter struct { + runner pipelineRunner + job job.Job + logger logger.Logger +} + +func (p *PipelineRunnerAdapter) ExecuteRun(ctx context.Context, spec string, vars types.Vars, options types.Options) (types.TaskResults, error) { + s := pipeline.Spec{ + DotDagSource: spec, + CreatedAt: time.Now(), + MaxTaskDuration: models.Interval(options.MaxTaskDuration), + JobID: p.job.ID, + JobName: p.job.Name.ValueOrZero(), + JobType: string(p.job.Type), + } + + defaultVars := map[string]interface{}{ + "jb": map[string]interface{}{ + "databaseID": p.job.ID, + "externalJobID": p.job.ExternalJobID, + "name": p.job.Name.ValueOrZero(), + }, + } + + merge(defaultVars, vars.Vars) + + finalVars := pipeline.NewVarsFrom(defaultVars) + _, trrs, err := p.runner.ExecuteRun(ctx, s, finalVars, p.logger) + if err != nil { + return nil, err + } + + taskResults := make([]types.TaskResult, len(trrs)) + for i, trr := range trrs { + taskResults[i] = types.TaskResult{ + ID: trr.ID.String(), + Type: string(trr.Task.Type()), + Index: int(trr.Task.OutputIndex()), + + TaskValue: types.TaskValue{ + Value: trr.Result.Value, + Error: trr.Result.Error, + IsTerminal: len(trr.Task.Outputs()) == 0, + }, + } + } + return taskResults, nil +} + +func NewPipelineRunnerAdapter(logger logger.Logger, job job.Job, runner pipelineRunner) *PipelineRunnerAdapter { + return &PipelineRunnerAdapter{ + logger: logger, + job: job, + runner: runner, + } +} + +// merge merges mapTwo into mapOne, modifying mapOne in the process. +func merge(mapOne, mapTwo map[string]interface{}) { + for k, v := range mapTwo { + // if `mapOne` doesn't have `k`, then nothing to do, just assign v to `mapOne`. + if _, ok := mapOne[k]; !ok { + mapOne[k] = v + } else { + vAsMap, vOK := v.(map[string]interface{}) + mapOneVAsMap, moOK := mapOne[k].(map[string]interface{}) + if vOK && moOK { + merge(mapOneVAsMap, vAsMap) + } else { + mapOne[k] = v + } + } + } +} diff --git a/core/services/ocr2/plugins/generic/pipeline_runner_adapter_test.go b/core/services/ocr2/plugins/generic/pipeline_runner_adapter_test.go new file mode 100644 index 00000000000..c2060a92905 --- /dev/null +++ b/core/services/ocr2/plugins/generic/pipeline_runner_adapter_test.go @@ -0,0 +1,119 @@ +package generic_test + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/google/uuid" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" + _ "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/generic" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +const spec = ` +answer [type=sum values=<[ $(val), 2 ]>] +answer; +` + +func TestAdapter_Integration(t *testing.T) { + logger := logger.TestLogger(t) + cfg := configtest.NewTestGeneralConfig(t) + url := cfg.Database().URL() + db, err := pg.NewConnection(url.String(), cfg.Database().Dialect(), cfg.Database()) + require.NoError(t, err) + + keystore := keystore.NewInMemory(db, utils.FastScryptParams, logger, cfg.Database()) + pipelineORM := pipeline.NewORM(db, logger, cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) + bridgesORM := bridges.NewORM(db, logger, cfg.Database()) + pr := pipeline.NewRunner( + pipelineORM, + bridgesORM, + cfg.JobPipeline(), + cfg.WebServer(), + nil, + keystore.Eth(), + keystore.VRF(), + logger, + http.DefaultClient, + http.DefaultClient, + ) + pra := generic.NewPipelineRunnerAdapter(logger, job.Job{}, pr) + results, err := pra.ExecuteRun(testutils.Context(t), spec, types.Vars{Vars: map[string]interface{}{"val": 1}}, types.Options{}) + require.NoError(t, err) + + finalResult := results[0].Value.(decimal.Decimal) + + assert.True(t, decimal.NewFromInt(3).Equal(finalResult)) +} + +func newMockPipelineRunner() *mockPipelineRunner { + return &mockPipelineRunner{} +} + +type mockPipelineRunner struct { + results pipeline.TaskRunResults + err error + run *pipeline.Run + spec pipeline.Spec + vars pipeline.Vars +} + +func (m *mockPipelineRunner) ExecuteRun(ctx context.Context, spec pipeline.Spec, vars pipeline.Vars, l logger.Logger) (*pipeline.Run, pipeline.TaskRunResults, error) { + m.spec = spec + m.vars = vars + return m.run, m.results, m.err +} + +func TestAdapter_AddsDefaultVars(t *testing.T) { + logger := logger.TestLogger(t) + mpr := newMockPipelineRunner() + jobID, externalJobID, name := int32(100), uuid.New(), null.StringFrom("job-name") + pra := generic.NewPipelineRunnerAdapter(logger, job.Job{ID: jobID, ExternalJobID: externalJobID, Name: name}, mpr) + + _, err := pra.ExecuteRun(testutils.Context(t), spec, types.Vars{}, types.Options{}) + require.NoError(t, err) + + gotName, err := mpr.vars.Get("jb.name") + require.NoError(t, err) + assert.Equal(t, name.String, gotName) + + gotID, err := mpr.vars.Get("jb.databaseID") + require.NoError(t, err) + assert.Equal(t, jobID, gotID) + + gotExternalID, err := mpr.vars.Get("jb.externalJobID") + require.NoError(t, err) + assert.Equal(t, externalJobID, gotExternalID) +} + +func TestPipelineRunnerAdapter_SetsVarsOnSpec(t *testing.T) { + logger := logger.TestLogger(t) + mpr := newMockPipelineRunner() + jobID, externalJobID, name, jobType := int32(100), uuid.New(), null.StringFrom("job-name"), job.Type("generic") + pra := generic.NewPipelineRunnerAdapter(logger, job.Job{ID: jobID, ExternalJobID: externalJobID, Name: name, Type: jobType}, mpr) + + maxDuration := 100 * time.Second + _, err := pra.ExecuteRun(testutils.Context(t), spec, types.Vars{}, types.Options{MaxTaskDuration: maxDuration}) + require.NoError(t, err) + + assert.Equal(t, jobID, mpr.spec.JobID) + assert.Equal(t, name.ValueOrZero(), mpr.spec.JobName) + assert.Equal(t, string(jobType), mpr.spec.JobType) + assert.Equal(t, maxDuration, mpr.spec.MaxTaskDuration.Duration()) +} diff --git a/core/services/ocr2/plugins/generic/telemetry_adapter.go b/core/services/ocr2/plugins/generic/telemetry_adapter.go new file mode 100644 index 00000000000..a2ec6ba20cf --- /dev/null +++ b/core/services/ocr2/plugins/generic/telemetry_adapter.go @@ -0,0 +1,59 @@ +package generic + +import ( + "context" + "errors" + + "github.com/smartcontractkit/libocr/commontypes" + + "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" + "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" + + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +var _ types.TelemetryService = (*TelemetryAdapter)(nil) + +type TelemetryAdapter struct { + endpointGenerator telemetry.MonitoringEndpointGenerator + endpoints map[[4]string]commontypes.MonitoringEndpoint +} + +func NewTelemetryAdapter(endpointGen telemetry.MonitoringEndpointGenerator) *TelemetryAdapter { + return &TelemetryAdapter{ + endpoints: make(map[[4]string]commontypes.MonitoringEndpoint), + endpointGenerator: endpointGen, + } +} + +func (t *TelemetryAdapter) Send(ctx context.Context, network string, chainID string, contractID string, telemetryType string, payload []byte) error { + e, err := t.getOrCreateEndpoint(network, chainID, contractID, telemetryType) + if err != nil { + return err + } + e.SendLog(payload) + return nil +} + +func (t *TelemetryAdapter) getOrCreateEndpoint(network string, chainID string, contractID string, telemetryType string) (commontypes.MonitoringEndpoint, error) { + if contractID == "" { + return nil, errors.New("contractID cannot be empty") + } + if telemetryType == "" { + return nil, errors.New("telemetryType cannot be empty") + } + if network == "" { + return nil, errors.New("network cannot be empty") + } + if chainID == "" { + return nil, errors.New("chainID cannot be empty") + } + + key := [4]string{network, chainID, contractID, telemetryType} + e, ok := t.endpoints[key] + if !ok { + e = t.endpointGenerator.GenMonitoringEndpoint(network, chainID, contractID, synchronization.TelemetryType(telemetryType)) + t.endpoints[key] = e + } + return e, nil +} diff --git a/core/services/ocr2/plugins/generic/telemetry_adapter_test.go b/core/services/ocr2/plugins/generic/telemetry_adapter_test.go new file mode 100644 index 00000000000..24f422bf0cb --- /dev/null +++ b/core/services/ocr2/plugins/generic/telemetry_adapter_test.go @@ -0,0 +1,110 @@ +package generic_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/libocr/commontypes" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/generic" + "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" +) + +type mockEndpoint struct { + network string + chainID string + contractID string + telemetryType string + payload []byte +} + +func (m *mockEndpoint) SendLog(payload []byte) { m.payload = payload } + +type mockGenerator struct{} + +func (m *mockGenerator) GenMonitoringEndpoint(network string, chainID string, contractID string, telemetryType synchronization.TelemetryType) commontypes.MonitoringEndpoint { + return &mockEndpoint{ + network: network, + chainID: chainID, + contractID: contractID, + telemetryType: string(telemetryType), + } +} + +func TestTelemetryAdapter(t *testing.T) { + ta := generic.NewTelemetryAdapter(&mockGenerator{}) + + tests := []struct { + name string + contractID string + telemetryType string + networkID string + chainID string + payload []byte + errorMsg string + }{ + { + name: "valid request", + contractID: "contract", + telemetryType: "mercury", + networkID: "solana", + chainID: "1337", + payload: []byte("uh oh"), + }, + { + name: "no valid contractID", + telemetryType: "mercury", + networkID: "solana", + chainID: "1337", + payload: []byte("uh oh"), + errorMsg: "contractID cannot be empty", + }, + { + name: "no valid chainID", + contractID: "contract", + telemetryType: "mercury", + networkID: "solana", + payload: []byte("uh oh"), + errorMsg: "chainID cannot be empty", + }, + { + name: "no valid telemetryType", + contractID: "contract", + networkID: "solana", + chainID: "1337", + payload: []byte("uh oh"), + errorMsg: "telemetryType cannot be empty", + }, + { + name: "no valid network", + contractID: "contract", + telemetryType: "mercury", + chainID: "1337", + payload: []byte("uh oh"), + errorMsg: "network cannot be empty", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := ta.Send(testutils.Context(t), test.networkID, test.chainID, test.contractID, test.telemetryType, test.payload) + if test.errorMsg != "" { + assert.ErrorContains(t, err, test.errorMsg) + } else { + require.NoError(t, err) + key := [4]string{test.networkID, test.chainID, test.contractID, test.telemetryType} + endpoint, ok := ta.Endpoints()[key] + require.True(t, ok) + + me := endpoint.(*mockEndpoint) + assert.Equal(t, test.networkID, me.network) + assert.Equal(t, test.chainID, me.chainID) + assert.Equal(t, test.contractID, me.contractID) + assert.Equal(t, test.telemetryType, me.telemetryType) + assert.Equal(t, test.payload, me.payload) + } + }) + } +} diff --git a/core/services/ocr2/plugins/median/plugin.go b/core/services/ocr2/plugins/median/plugin.go deleted file mode 100644 index b78b75cbfcb..00000000000 --- a/core/services/ocr2/plugins/median/plugin.go +++ /dev/null @@ -1,68 +0,0 @@ -package median - -import ( - "context" - - "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - - "github.com/smartcontractkit/chainlink-relay/pkg/logger" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - "github.com/smartcontractkit/chainlink-relay/pkg/types" - - "github.com/smartcontractkit/chainlink/v2/core/utils" -) - -type Plugin struct { - loop.Plugin - stop utils.StopChan -} - -func NewPlugin(lggr logger.Logger) *Plugin { - return &Plugin{Plugin: loop.Plugin{Logger: lggr}, stop: make(utils.StopChan)} -} - -func (p *Plugin) NewMedianFactory(ctx context.Context, provider types.MedianProvider, dataSource, juelsPerFeeCoin median.DataSource, errorLog loop.ErrorLog) (loop.ReportingPluginFactory, error) { - var ctxVals loop.ContextValues - ctxVals.SetValues(ctx) - lggr := logger.With(p.Logger, ctxVals.Args()...) - factory := median.NumericalMedianFactory{ - ContractTransmitter: provider.MedianContract(), - DataSource: dataSource, - JuelsPerFeeCoinDataSource: juelsPerFeeCoin, - Logger: logger.NewOCRWrapper(lggr, true, func(msg string) { - ctx, cancelFn := p.stop.NewCtx() - defer cancelFn() - if err := errorLog.SaveError(ctx, msg); err != nil { - lggr.Errorw("Unable to save error", "err", msg) - } - }), - OnchainConfigCodec: provider.OnchainConfigCodec(), - ReportCodec: provider.ReportCodec(), - } - s := &reportingPluginFactoryService{lggr: logger.Named(lggr, "ReportingPluginFactory"), ReportingPluginFactory: factory} - - p.SubService(s) - - return s, nil -} - -type reportingPluginFactoryService struct { - utils.StartStopOnce - lggr logger.Logger - ocrtypes.ReportingPluginFactory -} - -func (r *reportingPluginFactoryService) Name() string { return r.lggr.Name() } - -func (r *reportingPluginFactoryService) Start(ctx context.Context) error { - return r.StartOnce("ReportingPluginFactory", func() error { return nil }) -} - -func (r *reportingPluginFactoryService) Close() error { - return r.StopOnce("ReportingPluginFactory", func() error { return nil }) -} - -func (r *reportingPluginFactoryService) HealthReport() map[string]error { - return map[string]error{r.Name(): r.Healthy()} -} diff --git a/core/services/ocr2/plugins/median/services.go b/core/services/ocr2/plugins/median/services.go index 8d121083f3f..2d1ef720545 100644 --- a/core/services/ocr2/plugins/median/services.go +++ b/core/services/ocr2/plugins/median/services.go @@ -9,8 +9,9 @@ import ( libocr "github.com/smartcontractkit/libocr/offchainreporting2plus" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-feeds/median" "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -24,19 +25,22 @@ import ( type MedianConfig interface { JobPipelineMaxSuccessfulRuns() uint64 + JobPipelineResultWriteQueueDepth() uint64 plugins.RegistrarConfig } // concrete implementation of MedianConfig type medianConfig struct { - jobPipelineMaxSuccessfulRuns uint64 + jobPipelineMaxSuccessfulRuns uint64 + jobPipelineResultWriteQueueDepth uint64 plugins.RegistrarConfig } -func NewMedianConfig(jobPipelineMaxSuccessfulRuns uint64, pluginProcessCfg plugins.RegistrarConfig) MedianConfig { +func NewMedianConfig(jobPipelineMaxSuccessfulRuns uint64, jobPipelineResultWriteQueueDepth uint64, pluginProcessCfg plugins.RegistrarConfig) MedianConfig { return &medianConfig{ - jobPipelineMaxSuccessfulRuns: jobPipelineMaxSuccessfulRuns, - RegistrarConfig: pluginProcessCfg, + jobPipelineMaxSuccessfulRuns: jobPipelineMaxSuccessfulRuns, + jobPipelineResultWriteQueueDepth: jobPipelineResultWriteQueueDepth, + RegistrarConfig: pluginProcessCfg, } } @@ -44,12 +48,15 @@ func (m *medianConfig) JobPipelineMaxSuccessfulRuns() uint64 { return m.jobPipelineMaxSuccessfulRuns } +func (m *medianConfig) JobPipelineResultWriteQueueDepth() uint64 { + return m.jobPipelineResultWriteQueueDepth +} + func NewMedianServices(ctx context.Context, jb job.Job, isNewlyCreatedJob bool, relayer loop.Relayer, pipelineRunner pipeline.Runner, - runResults chan *pipeline.Run, lggr logger.Logger, argsNoPlugin libocr.OCR2OracleArgs, cfg MedianConfig, @@ -68,6 +75,13 @@ func NewMedianServices(ctx context.Context, } spec := jb.OCR2OracleSpec + runSaver := ocrcommon.NewResultRunSaver( + pipelineRunner, + lggr, + cfg.JobPipelineMaxSuccessfulRuns(), + cfg.JobPipelineResultWriteQueueDepth(), + ) + provider, err := relayer.NewPluginProvider(ctx, types.RelayArgs{ ExternalJobID: jb.ExternalJobID, JobID: jb.ID, @@ -103,7 +117,7 @@ func NewMedianServices(ctx context.Context, jb, *jb.PipelineSpec, lggr, - runResults, + runSaver, chEnhancedTelem, ), ocrcommon.NewInMemoryDataSource(pipelineRunner, jb, pipeline.Spec{ ID: jb.ID, @@ -125,7 +139,7 @@ func NewMedianServices(ctx context.Context, argsNoPlugin.ReportingPluginFactory = median srvs = append(srvs, median) } else { - argsNoPlugin.ReportingPluginFactory, err = NewPlugin(lggr).NewMedianFactory(ctx, medianProvider, dataSource, juelsPerFeeCoinSource, errorLog) + argsNoPlugin.ReportingPluginFactory, err = median.NewPlugin(lggr).NewMedianFactory(ctx, medianProvider, dataSource, juelsPerFeeCoinSource, errorLog) if err != nil { err = fmt.Errorf("failed to create median factory: %w", err) abort() @@ -139,13 +153,6 @@ func NewMedianServices(ctx context.Context, abort() return } - runSaver := ocrcommon.NewResultRunSaver( - runResults, - pipelineRunner, - make(chan struct{}), - lggr, - cfg.JobPipelineMaxSuccessfulRuns(), - ) srvs = append(srvs, runSaver, job.NewServiceAdapter(oracle)) if !jb.OCR2OracleSpec.CaptureEATelemetry { lggr.Infof("Enhanced EA telemetry is disabled for job %s", jb.Name.ValueOrZero()) diff --git a/core/services/ocr2/plugins/mercury/helpers_test.go b/core/services/ocr2/plugins/mercury/helpers_test.go index 60904b58139..ed59213840c 100644 --- a/core/services/ocr2/plugins/mercury/helpers_test.go +++ b/core/services/ocr2/plugins/mercury/helpers_test.go @@ -138,14 +138,14 @@ func (node *Node) AddJob(t *testing.T, spec string) { c := node.App.GetConfig() job, err := validate.ValidatedOracleSpecToml(c.OCR2(), c.Insecure(), spec) require.NoError(t, err) - err = node.App.AddJobV2(context.Background(), &job) + err = node.App.AddJobV2(testutils.Context(t), &job) require.NoError(t, err) } func (node *Node) AddBootstrapJob(t *testing.T, spec string) { job, err := ocrbootstrap.ValidatedBootstrapSpecToml(spec) require.NoError(t, err) - err = node.App.AddJobV2(context.Background(), &job) + err = node.App.AddJobV2(testutils.Context(t), &job) require.NoError(t, err) } @@ -163,7 +163,7 @@ func setupNode( p2paddresses := []string{fmt.Sprintf("127.0.0.1:%d", port)} - config, _ := heavyweight.FullTestDBV2(t, fmt.Sprintf("%s%d", dbName, port), func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { // [JobPipeline] // MaxSuccessfulRuns = 0 c.JobPipeline.MaxSuccessfulRuns = ptr(uint64(0)) diff --git a/core/services/ocr2/plugins/mercury/integration_test.go b/core/services/ocr2/plugins/mercury/integration_test.go index e7e059289a2..e8adb55b397 100644 --- a/core/services/ocr2/plugins/mercury/integration_test.go +++ b/core/services/ocr2/plugins/mercury/integration_test.go @@ -34,13 +34,13 @@ import ( ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/wsrpc/credentials" - relaymercury "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury" - relaycodecv1 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v1" - relaycodecv2 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v2" - relaycodecv3 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v3" + relaymercury "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury" + relaycodecv1 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v1" + relaycodecv2 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v2" + relaycodecv3 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v3" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" token "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/fee_manager" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/reward_manager" diff --git a/core/services/ocr2/plugins/mercury/plugin.go b/core/services/ocr2/plugins/mercury/plugin.go index 69a3b53c284..d443008334c 100644 --- a/core/services/ocr2/plugins/mercury/plugin.go +++ b/core/services/ocr2/plugins/mercury/plugin.go @@ -7,10 +7,10 @@ import ( libocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus" - relaymercuryv1 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v1" - relaymercuryv2 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v2" - relaymercuryv3 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v3" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + relaymercuryv1 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v1" + relaymercuryv2 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v2" + relaymercuryv3 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v3" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -26,18 +26,17 @@ import ( type Config interface { MaxSuccessfulRuns() uint64 + ResultWriteQueueDepth() uint64 } func NewServices( jb job.Job, - ocr2Provider relaytypes.MercuryProvider, + ocr2Provider commontypes.MercuryProvider, pipelineRunner pipeline.Runner, - runResults chan *pipeline.Run, lggr logger.Logger, argsNoPlugin libocr2.MercuryOracleArgs, cfg Config, chEnhancedTelem chan ocrcommon.EnhancedTelemetryMercuryData, - chainHeadTracker types.ChainHeadTracker, orm types.DataSourceORM, feedID utils.FeedID, ) ([]job.ServiceCtx, error) { @@ -56,6 +55,8 @@ func NewServices( } lggr = lggr.Named("MercuryPlugin").With("jobID", jb.ID, "jobName", jb.Name.ValueOrZero()) + saver := ocrcommon.NewResultRunSaver(pipelineRunner, lggr, cfg.MaxSuccessfulRuns(), cfg.ResultWriteQueueDepth()) + switch feedID.Version() { case 1: ds := mercuryv1.NewDataSource( @@ -64,9 +65,9 @@ func NewServices( jb, *jb.PipelineSpec, lggr, - runResults, + saver, chEnhancedTelem, - chainHeadTracker, + ocr2Provider.ChainReader(), ocr2Provider.MercuryServerFetcher(), pluginConfig.InitialBlockNumber.Ptr(), feedID, @@ -85,7 +86,7 @@ func NewServices( *jb.PipelineSpec, feedID, lggr, - runResults, + saver, chEnhancedTelem, ocr2Provider.MercuryServerFetcher(), *pluginConfig.LinkFeedID, @@ -105,7 +106,7 @@ func NewServices( *jb.PipelineSpec, feedID, lggr, - runResults, + saver, chEnhancedTelem, ocr2Provider.MercuryServerFetcher(), *pluginConfig.LinkFeedID, @@ -125,6 +126,5 @@ func NewServices( if err != nil { return nil, errors.WithStack(err) } - saver := ocrcommon.NewResultRunSaver(runResults, pipelineRunner, make(chan struct{}), lggr, cfg.MaxSuccessfulRuns()) return []job.ServiceCtx{ocr2Provider, saver, job.NewServiceAdapter(oracle)}, nil } diff --git a/core/services/ocr2/plugins/ocr2keeper/config.go b/core/services/ocr2/plugins/ocr2keeper/config.go index d3035878ece..ec56f9c6993 100644 --- a/core/services/ocr2/plugins/ocr2keeper/config.go +++ b/core/services/ocr2/plugins/ocr2keeper/config.go @@ -58,6 +58,8 @@ type PluginConfig struct { ServiceQueueLength int `json:"serviceQueueLength"` // ContractVersion is the contract version ContractVersion string `json:"contractVersion"` + // CaptureAutomationCustomTelemetry is a bool flag to toggle Custom Telemetry Service + CaptureAutomationCustomTelemetry *bool `json:"captureAutomationCustomTelemetry,omitempty"` } func ValidatePluginConfig(cfg PluginConfig) error { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/interface.go b/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/interface.go deleted file mode 100644 index 217f0dcb571..00000000000 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/interface.go +++ /dev/null @@ -1,60 +0,0 @@ -package encoding - -import ( - "math/big" - - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" - - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" - iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" -) - -type UpkeepFailureReason uint8 -type PipelineExecutionState uint8 - -const ( - // upkeep failure onchain reasons - UpkeepFailureReasonNone UpkeepFailureReason = 0 - UpkeepFailureReasonUpkeepCancelled UpkeepFailureReason = 1 - UpkeepFailureReasonUpkeepPaused UpkeepFailureReason = 2 - UpkeepFailureReasonTargetCheckReverted UpkeepFailureReason = 3 - UpkeepFailureReasonUpkeepNotNeeded UpkeepFailureReason = 4 - UpkeepFailureReasonPerformDataExceedsLimit UpkeepFailureReason = 5 - UpkeepFailureReasonInsufficientBalance UpkeepFailureReason = 6 - UpkeepFailureReasonMercuryCallbackReverted UpkeepFailureReason = 7 - UpkeepFailureReasonRevertDataExceedsLimit UpkeepFailureReason = 8 - UpkeepFailureReasonRegistryPaused UpkeepFailureReason = 9 - // leaving a gap here for more onchain failure reasons in the future - // upkeep failure offchain reasons - UpkeepFailureReasonMercuryAccessNotAllowed UpkeepFailureReason = 32 - UpkeepFailureReasonTxHashNoLongerExists UpkeepFailureReason = 33 - UpkeepFailureReasonInvalidRevertDataInput UpkeepFailureReason = 34 - UpkeepFailureReasonSimulationFailed UpkeepFailureReason = 35 - UpkeepFailureReasonTxHashReorged UpkeepFailureReason = 36 - - // pipeline execution error - NoPipelineError PipelineExecutionState = 0 - CheckBlockTooOld PipelineExecutionState = 1 - CheckBlockInvalid PipelineExecutionState = 2 - RpcFlakyFailure PipelineExecutionState = 3 - MercuryFlakyFailure PipelineExecutionState = 4 - PackUnpackDecodeFailed PipelineExecutionState = 5 - MercuryUnmarshalError PipelineExecutionState = 6 - InvalidMercuryRequest PipelineExecutionState = 7 - InvalidMercuryResponse PipelineExecutionState = 8 // this will only happen if Mercury server sends bad responses - UpkeepNotAuthorized PipelineExecutionState = 9 -) - -type UpkeepInfo = iregistry21.KeeperRegistryBase21UpkeepInfo - -type Packer interface { - UnpackCheckResult(payload ocr2keepers.UpkeepPayload, raw string) (ocr2keepers.CheckResult, error) - UnpackCheckCallbackResult(callbackResp []byte) (PipelineExecutionState, bool, []byte, uint8, *big.Int, error) - UnpackPerformResult(raw string) (PipelineExecutionState, bool, error) - UnpackLogTriggerConfig(raw []byte) (automation_utils_2_1.LogTriggerConfig, error) - PackReport(report automation_utils_2_1.KeeperRegistryBase21Report) ([]byte, error) - UnpackReport(raw []byte) (automation_utils_2_1.KeeperRegistryBase21Report, error) - PackGetUpkeepPrivilegeConfig(upkeepId *big.Int) ([]byte, error) - UnpackGetUpkeepPrivilegeConfig(resp []byte) ([]byte, error) - DecodeStreamsLookupRequest(data []byte) (*StreamsLookupError, error) -} diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/streams_lookup.go b/core/services/ocr2/plugins/ocr2keeper/evm21/streams_lookup.go deleted file mode 100644 index 6f2594b6c38..00000000000 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/streams_lookup.go +++ /dev/null @@ -1,595 +0,0 @@ -package evm - -import ( - "context" - "crypto/hmac" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "math/big" - "net/http" - "net/url" - "strconv" - "strings" - "sync" - "time" - - "github.com/avast/retry-go/v4" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/patrickmn/go-cache" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" - - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/encoding" -) - -const ( - applicationJson = "application/json" - blockNumber = "blockNumber" // valid for v0.2 - feedIDs = "feedIDs" // valid for v0.3 - feedIdHex = "feedIdHex" // valid for v0.2 - headerAuthorization = "Authorization" - headerContentType = "Content-Type" - headerTimestamp = "X-Authorization-Timestamp" - headerSignature = "X-Authorization-Signature-SHA256" - headerUpkeepId = "X-Authorization-Upkeep-Id" - mercuryPathV02 = "/client?" // only used to access mercury v0.2 server - mercuryBatchPathV03 = "/api/v1/reports/bulk?" // only used to access mercury v0.3 server - mercuryBatchPathV03BlockNumber = "/api/v1gmx/reports/bulk?" // only used to access mercury v0.3 server with blockNumber - retryDelay = 500 * time.Millisecond - timestamp = "timestamp" // valid for v0.3 - totalAttempt = 3 -) - -type StreamsLookup struct { - *encoding.StreamsLookupError - upkeepId *big.Int - block uint64 -} - -// MercuryV02Response represents a JSON structure used by Mercury v0.2 -type MercuryV02Response struct { - ChainlinkBlob string `json:"chainlinkBlob"` -} - -// MercuryV03Response represents a JSON structure used by Mercury v0.3 -type MercuryV03Response struct { - Reports []MercuryV03Report `json:"reports"` -} - -type MercuryV03Report struct { - FeedID string `json:"feedID"` // feed id in hex encoded - ValidFromTimestamp uint32 `json:"validFromTimestamp"` - ObservationsTimestamp uint32 `json:"observationsTimestamp"` - FullReport string `json:"fullReport"` // the actual hex encoded mercury report of this feed, can be sent to verifier -} - -type MercuryData struct { - Index int - Error error - Retryable bool - Bytes [][]byte - State encoding.PipelineExecutionState -} - -// UpkeepPrivilegeConfig represents the administrative offchain config for each upkeep. It can be set by s_upkeepPrivilegeManager -// role on the registry. Upkeeps allowed to use Mercury server will have this set to true. -type UpkeepPrivilegeConfig struct { - MercuryEnabled bool `json:"mercuryEnabled"` -} - -// streamsLookup looks through check upkeep results looking for any that need off chain lookup -func (r *EvmRegistry) streamsLookup(ctx context.Context, checkResults []ocr2keepers.CheckResult) []ocr2keepers.CheckResult { - lggr := r.lggr.With("where", "StreamsLookup") - lookups := map[int]*StreamsLookup{} - for i, res := range checkResults { - if res.IneligibilityReason != uint8(encoding.UpkeepFailureReasonTargetCheckReverted) { - // Streams Lookup only works when upkeep target check reverts - continue - } - - block := big.NewInt(int64(res.Trigger.BlockNumber)) - upkeepId := res.UpkeepID - - // Try to decode the revert error into streams lookup format. User upkeeps can revert with any reason, see if they - // tried to call mercury - lggr.Infof("at block %d upkeep %s trying to DecodeStreamsLookupRequest performData=%s", block, upkeepId, hexutil.Encode(checkResults[i].PerformData)) - streamsLookupErr, err := r.packer.DecodeStreamsLookupRequest(res.PerformData) - if err != nil { - lggr.Debugf("at block %d upkeep %s DecodeStreamsLookupRequest failed: %v", block, upkeepId, err) - // user contract did not revert with StreamsLookup error - continue - } - l := &StreamsLookup{StreamsLookupError: streamsLookupErr} - if r.mercury.cred == nil { - lggr.Errorf("at block %d upkeep %s tries to access mercury server but mercury credential is not configured", block, upkeepId) - continue - } - - if len(l.Feeds) == 0 { - checkResults[i].IneligibilityReason = uint8(encoding.UpkeepFailureReasonInvalidRevertDataInput) - lggr.Debugf("at block %s upkeep %s has empty feeds array", block, upkeepId) - continue - } - // mercury permission checking for v0.3 is done by mercury server - if l.FeedParamKey == feedIdHex && l.TimeParamKey == blockNumber { - // check permission on the registry for mercury v0.2 - opts := r.buildCallOpts(ctx, block) - state, reason, retryable, allowed, err := r.allowedToUseMercury(opts, upkeepId.BigInt()) - if err != nil { - lggr.Warnf("at block %s upkeep %s failed to query mercury allow list: %s", block, upkeepId, err) - checkResults[i].PipelineExecutionState = uint8(state) - checkResults[i].IneligibilityReason = uint8(reason) - checkResults[i].Retryable = retryable - continue - } - - if !allowed { - lggr.Debugf("at block %d upkeep %s NOT allowed to query Mercury server", block, upkeepId) - checkResults[i].IneligibilityReason = uint8(encoding.UpkeepFailureReasonMercuryAccessNotAllowed) - continue - } - } else if l.FeedParamKey != feedIDs { - // if mercury version cannot be determined, set failure reason - lggr.Debugf("at block %d upkeep %s NOT allowed to query Mercury server", block, upkeepId) - checkResults[i].IneligibilityReason = uint8(encoding.UpkeepFailureReasonInvalidRevertDataInput) - continue - } - - l.upkeepId = upkeepId.BigInt() - // the block here is exclusively used to call checkCallback at this block, not to be confused with the block number - // in the revert for mercury v0.2, which is denoted by time in the struct bc starting from v0.3, only timestamp will be supported - l.block = uint64(block.Int64()) - lggr.Infof("at block %d upkeep %s DecodeStreamsLookupRequest feedKey=%s timeKey=%s feeds=%v time=%s extraData=%s", block, upkeepId, l.FeedParamKey, l.TimeParamKey, l.Feeds, l.Time, hexutil.Encode(l.ExtraData)) - lookups[i] = l - } - - var wg sync.WaitGroup - for i, lookup := range lookups { - wg.Add(1) - go r.doLookup(ctx, &wg, lookup, i, checkResults, lggr) - } - wg.Wait() - - // don't surface error to plugin bc StreamsLookup process should be self-contained. - return checkResults -} - -func (r *EvmRegistry) doLookup(ctx context.Context, wg *sync.WaitGroup, lookup *StreamsLookup, i int, checkResults []ocr2keepers.CheckResult, lggr logger.Logger) { - defer wg.Done() - - state, reason, values, retryable, err := r.doMercuryRequest(ctx, lookup, lggr) - if err != nil { - lggr.Errorf("upkeep %s retryable %v doMercuryRequest: %s", lookup.upkeepId, retryable, err.Error()) - checkResults[i].Retryable = retryable - checkResults[i].PipelineExecutionState = uint8(state) - checkResults[i].IneligibilityReason = uint8(reason) - return - } - - for j, v := range values { - lggr.Infof("upkeep %s doMercuryRequest values[%d]: %s", lookup.upkeepId, j, hexutil.Encode(v)) - } - - state, retryable, mercuryBytes, err := r.checkCallback(ctx, values, lookup) - if err != nil { - lggr.Errorf("at block %d upkeep %s checkCallback err: %s", lookup.block, lookup.upkeepId, err.Error()) - checkResults[i].Retryable = retryable - checkResults[i].PipelineExecutionState = uint8(state) - return - } - lggr.Infof("checkCallback mercuryBytes=%s", hexutil.Encode(mercuryBytes)) - - state, needed, performData, failureReason, _, err := r.packer.UnpackCheckCallbackResult(mercuryBytes) - if err != nil { - lggr.Errorf("at block %d upkeep %s UnpackCheckCallbackResult err: %s", lookup.block, lookup.upkeepId, err.Error()) - checkResults[i].PipelineExecutionState = uint8(state) - return - } - - if failureReason == uint8(encoding.UpkeepFailureReasonMercuryCallbackReverted) { - checkResults[i].IneligibilityReason = uint8(encoding.UpkeepFailureReasonMercuryCallbackReverted) - lggr.Debugf("at block %d upkeep %s mercury callback reverts", lookup.block, lookup.upkeepId) - return - } - - if !needed { - checkResults[i].IneligibilityReason = uint8(encoding.UpkeepFailureReasonUpkeepNotNeeded) - lggr.Debugf("at block %d upkeep %s callback reports upkeep not needed", lookup.block, lookup.upkeepId) - return - } - - checkResults[i].IneligibilityReason = uint8(encoding.UpkeepFailureReasonNone) - checkResults[i].Eligible = true - checkResults[i].PerformData = performData - lggr.Infof("at block %d upkeep %s successful with perform data: %s", lookup.block, lookup.upkeepId, hexutil.Encode(performData)) -} - -// allowedToUseMercury retrieves upkeep's administrative offchain config and decode a mercuryEnabled bool to indicate if -// this upkeep is allowed to use Mercury service. -func (r *EvmRegistry) allowedToUseMercury(opts *bind.CallOpts, upkeepId *big.Int) (state encoding.PipelineExecutionState, reason encoding.UpkeepFailureReason, retryable bool, allow bool, err error) { - allowed, ok := r.mercury.allowListCache.Get(upkeepId.String()) - if ok { - return encoding.NoPipelineError, encoding.UpkeepFailureReasonNone, false, allowed.(bool), nil - } - - payload, err := r.packer.PackGetUpkeepPrivilegeConfig(upkeepId) - if err != nil { - // pack error, no retryable - r.lggr.Warnf("failed to pack getUpkeepPrivilegeConfig data for upkeepId %s: %s", upkeepId, err) - - return encoding.PackUnpackDecodeFailed, encoding.UpkeepFailureReasonNone, false, false, fmt.Errorf("failed to pack upkeepId: %w", err) - } - - var resultBytes hexutil.Bytes - args := map[string]interface{}{ - "to": r.addr.Hex(), - "data": hexutil.Bytes(payload), - } - - // call checkCallback function at the block which OCR3 has agreed upon - err = r.client.CallContext(opts.Context, &resultBytes, "eth_call", args, hexutil.EncodeBig(opts.BlockNumber)) - if err != nil { - return encoding.RpcFlakyFailure, encoding.UpkeepFailureReasonNone, true, false, fmt.Errorf("failed to get upkeep privilege config: %v", err) - } - - cfg, err := r.packer.UnpackGetUpkeepPrivilegeConfig(resultBytes) - if err != nil { - return encoding.PackUnpackDecodeFailed, encoding.UpkeepFailureReasonNone, false, false, fmt.Errorf("failed to get upkeep privilege config: %v", err) - } - - if len(cfg) == 0 { - r.mercury.allowListCache.Set(upkeepId.String(), false, cache.DefaultExpiration) - return encoding.NoPipelineError, encoding.UpkeepFailureReasonMercuryAccessNotAllowed, false, false, fmt.Errorf("upkeep privilege config is empty") - } - - var privilegeConfig UpkeepPrivilegeConfig - if err := json.Unmarshal(cfg, &privilegeConfig); err != nil { - return encoding.MercuryUnmarshalError, encoding.UpkeepFailureReasonNone, false, false, fmt.Errorf("failed to unmarshal privilege config: %v", err) - } - - r.mercury.allowListCache.Set(upkeepId.String(), privilegeConfig.MercuryEnabled, cache.DefaultExpiration) - - return encoding.NoPipelineError, encoding.UpkeepFailureReasonNone, false, privilegeConfig.MercuryEnabled, nil -} - -func (r *EvmRegistry) checkCallback(ctx context.Context, values [][]byte, lookup *StreamsLookup) (encoding.PipelineExecutionState, bool, hexutil.Bytes, error) { - payload, err := r.abi.Pack("checkCallback", lookup.upkeepId, values, lookup.ExtraData) - if err != nil { - return encoding.PackUnpackDecodeFailed, false, nil, err - } - - var b hexutil.Bytes - args := map[string]interface{}{ - "to": r.addr.Hex(), - "data": hexutil.Bytes(payload), - } - - // call checkCallback function at the block which OCR3 has agreed upon - err = r.client.CallContext(ctx, &b, "eth_call", args, hexutil.EncodeUint64(lookup.block)) - if err != nil { - return encoding.RpcFlakyFailure, true, nil, err - } - return encoding.NoPipelineError, false, b, nil -} - -// doMercuryRequest sends requests to Mercury API to retrieve mercury data. -func (r *EvmRegistry) doMercuryRequest(ctx context.Context, sl *StreamsLookup, lggr logger.Logger) (encoding.PipelineExecutionState, encoding.UpkeepFailureReason, [][]byte, bool, error) { - var isMercuryV03 bool - resultLen := len(sl.Feeds) - ch := make(chan MercuryData, resultLen) - if len(sl.Feeds) == 0 { - return encoding.NoPipelineError, encoding.UpkeepFailureReasonInvalidRevertDataInput, [][]byte{}, false, fmt.Errorf("invalid revert data input: feed param key %s, time param key %s, feeds %s", sl.FeedParamKey, sl.TimeParamKey, sl.Feeds) - } - if sl.FeedParamKey == feedIdHex && sl.TimeParamKey == blockNumber { - // only mercury v0.2 - for i := range sl.Feeds { - go r.singleFeedRequest(ctx, ch, i, sl, lggr) - } - } else if sl.FeedParamKey == feedIDs { - // only mercury v0.3 - resultLen = 1 - isMercuryV03 = true - ch = make(chan MercuryData, resultLen) - go r.multiFeedsRequest(ctx, ch, sl, lggr) - } else { - return encoding.NoPipelineError, encoding.UpkeepFailureReasonInvalidRevertDataInput, [][]byte{}, false, fmt.Errorf("invalid revert data input: feed param key %s, time param key %s, feeds %s", sl.FeedParamKey, sl.TimeParamKey, sl.Feeds) - } - - var reqErr error - results := make([][]byte, len(sl.Feeds)) - retryable := true - allSuccess := true - // in v0.2, use the last execution error as the state, if no execution errors, state will be no error - state := encoding.NoPipelineError - for i := 0; i < resultLen; i++ { - m := <-ch - if m.Error != nil { - reqErr = errors.Join(reqErr, m.Error) - retryable = retryable && m.Retryable - allSuccess = false - if m.State != encoding.NoPipelineError { - state = m.State - } - continue - } - if isMercuryV03 { - results = m.Bytes - } else { - results[m.Index] = m.Bytes[0] - } - } - // only retry when not all successful AND none are not retryable - return state, encoding.UpkeepFailureReasonNone, results, retryable && !allSuccess, reqErr -} - -// singleFeedRequest sends a v0.2 Mercury request for a single feed report. -func (r *EvmRegistry) singleFeedRequest(ctx context.Context, ch chan<- MercuryData, index int, sl *StreamsLookup, lggr logger.Logger) { - q := url.Values{ - sl.FeedParamKey: {sl.Feeds[index]}, - sl.TimeParamKey: {sl.Time.String()}, - } - mercuryURL := r.mercury.cred.LegacyURL - reqUrl := fmt.Sprintf("%s%s%s", mercuryURL, mercuryPathV02, q.Encode()) - lggr.Debugf("request URL for upkeep %s feed %s: %s", sl.upkeepId.String(), sl.Feeds[index], reqUrl) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, nil) - if err != nil { - ch <- MercuryData{Index: index, Error: err, Retryable: false, State: encoding.InvalidMercuryRequest} - return - } - - ts := time.Now().UTC().UnixMilli() - signature := r.generateHMAC(http.MethodGet, mercuryPathV02+q.Encode(), []byte{}, r.mercury.cred.Username, r.mercury.cred.Password, ts) - req.Header.Set(headerContentType, applicationJson) - req.Header.Set(headerAuthorization, r.mercury.cred.Username) - req.Header.Set(headerTimestamp, strconv.FormatInt(ts, 10)) - req.Header.Set(headerSignature, signature) - - // in the case of multiple retries here, use the last attempt's data - state := encoding.NoPipelineError - retryable := false - sent := false - retryErr := retry.Do( - func() error { - retryable = false - resp, err1 := r.hc.Do(req) - if err1 != nil { - lggr.Warnf("at block %s upkeep %s GET request fails for feed %s: %v", sl.Time.String(), sl.upkeepId.String(), sl.Feeds[index], err1) - retryable = true - state = encoding.MercuryFlakyFailure - return err1 - } - defer func(Body io.ReadCloser) { - err = Body.Close() - if err != nil { - lggr.Warnf("failed to close mercury response Body: %s", err) - } - }(resp.Body) - - body, err1 := io.ReadAll(resp.Body) - if err1 != nil { - retryable = false - state = encoding.InvalidMercuryResponse - return err1 - } - - if resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusInternalServerError { - lggr.Warnf("at block %s upkeep %s received status code %d for feed %s", sl.Time.String(), sl.upkeepId.String(), resp.StatusCode, sl.Feeds[index]) - retryable = true - state = encoding.MercuryFlakyFailure - return errors.New(strconv.FormatInt(int64(resp.StatusCode), 10)) - } else if resp.StatusCode != http.StatusOK { - retryable = false - state = encoding.InvalidMercuryRequest - return fmt.Errorf("at block %s upkeep %s received status code %d for feed %s", sl.Time.String(), sl.upkeepId.String(), resp.StatusCode, sl.Feeds[index]) - } - - lggr.Debugf("at block %s upkeep %s received status code %d from mercury v0.2 with BODY=%s", sl.Time.String(), sl.upkeepId.String(), resp.StatusCode, hexutil.Encode(body)) - - var m MercuryV02Response - err1 = json.Unmarshal(body, &m) - if err1 != nil { - lggr.Warnf("at block %s upkeep %s failed to unmarshal body to MercuryV02Response for feed %s: %v", sl.Time.String(), sl.upkeepId.String(), sl.Feeds[index], err1) - retryable = false - state = encoding.MercuryUnmarshalError - return err1 - } - blobBytes, err1 := hexutil.Decode(m.ChainlinkBlob) - if err1 != nil { - lggr.Warnf("at block %s upkeep %s failed to decode chainlinkBlob %s for feed %s: %v", sl.Time.String(), sl.upkeepId.String(), m.ChainlinkBlob, sl.Feeds[index], err1) - retryable = false - state = encoding.InvalidMercuryResponse - return err1 - } - ch <- MercuryData{ - Index: index, - Bytes: [][]byte{blobBytes}, - Retryable: false, - State: encoding.NoPipelineError, - } - sent = true - return nil - }, - // only retry when the error is 404 Not Found or 500 Internal Server Error - retry.RetryIf(func(err error) bool { - return err.Error() == fmt.Sprintf("%d", http.StatusNotFound) || err.Error() == fmt.Sprintf("%d", http.StatusInternalServerError) - }), - retry.Context(ctx), - retry.Delay(retryDelay), - retry.Attempts(totalAttempt)) - - if !sent { - md := MercuryData{ - Index: index, - Bytes: [][]byte{}, - Retryable: retryable, - Error: fmt.Errorf("failed to request feed for %s: %w", sl.Feeds[index], retryErr), - State: state, - } - ch <- md - } -} - -// multiFeedsRequest sends a Mercury v0.3 request for a multi-feed report -func (r *EvmRegistry) multiFeedsRequest(ctx context.Context, ch chan<- MercuryData, sl *StreamsLookup, lggr logger.Logger) { - // this won't work bc q.Encode() will encode commas as '%2C' but the server is strictly expecting a comma separated list - //q := url.Values{ - // feedIDs: {strings.Join(sl.Feeds, ",")}, - // timestamp: {sl.Time.String()}, - //} - params := fmt.Sprintf("%s=%s&%s=%s", feedIDs, strings.Join(sl.Feeds, ","), sl.TimeParamKey, sl.Time.String()) - batchPathV03 := mercuryBatchPathV03 - if sl.TimeParamKey == blockNumber { - batchPathV03 = mercuryBatchPathV03BlockNumber - } - reqUrl := fmt.Sprintf("%s%s%s", r.mercury.cred.URL, batchPathV03, params) - lggr.Debugf("request URL for upkeep %s userId %s: %s", sl.upkeepId.String(), r.mercury.cred.Username, reqUrl) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, nil) - if err != nil { - ch <- MercuryData{Index: 0, Error: err, Retryable: false, State: encoding.InvalidMercuryRequest} - return - } - - ts := time.Now().UTC().UnixMilli() - signature := r.generateHMAC(http.MethodGet, batchPathV03+params, []byte{}, r.mercury.cred.Username, r.mercury.cred.Password, ts) - req.Header.Set(headerContentType, applicationJson) - // username here is often referred to as user id - req.Header.Set(headerAuthorization, r.mercury.cred.Username) - req.Header.Set(headerTimestamp, strconv.FormatInt(ts, 10)) - req.Header.Set(headerSignature, signature) - // mercury will inspect authorization headers above to make sure this user (in automation's context, this node) is eligible to access mercury - // and if it has an automation role. it will then look at this upkeep id to check if it has access to all the requested feeds. - req.Header.Set(headerUpkeepId, sl.upkeepId.String()) - - // in the case of multiple retries here, use the last attempt's data - state := encoding.NoPipelineError - retryable := false - sent := false - retryErr := retry.Do( - func() error { - retryable = false - resp, err1 := r.hc.Do(req) - if err1 != nil { - lggr.Warnf("at timestamp %s upkeep %s GET request fails from mercury v0.3: %v", sl.Time.String(), sl.upkeepId.String(), err1) - retryable = true - state = encoding.MercuryFlakyFailure - return err1 - } - defer func(Body io.ReadCloser) { - err = Body.Close() - if err != nil { - lggr.Warnf("failed to close mercury response Body: %s", err) - } - }(resp.Body) - - body, err1 := io.ReadAll(resp.Body) - if err1 != nil { - retryable = false - state = encoding.InvalidMercuryResponse - return err1 - } - - lggr.Infof("at timestamp %s upkeep %s received status code %d from mercury v0.3", sl.Time.String(), sl.upkeepId.String(), resp.StatusCode) - if resp.StatusCode == http.StatusUnauthorized { - retryable = false - state = encoding.UpkeepNotAuthorized - return fmt.Errorf("at timestamp %s upkeep %s received status code %d from mercury v0.3, most likely this is caused by unauthorized upkeep", sl.Time.String(), sl.upkeepId.String(), resp.StatusCode) - } else if resp.StatusCode == http.StatusBadRequest { - retryable = false - state = encoding.InvalidMercuryRequest - return fmt.Errorf("at timestamp %s upkeep %s received status code %d from mercury v0.3 with message: %s", sl.Time.String(), sl.upkeepId.String(), resp.StatusCode, string(body)) - } else if resp.StatusCode == http.StatusInternalServerError { - retryable = true - state = encoding.MercuryFlakyFailure - return fmt.Errorf("%d", http.StatusInternalServerError) - } else if resp.StatusCode == 420 { - // in 0.3, this will happen when missing/malformed query args, missing or bad required headers, non-existent feeds, or no permissions for feeds - retryable = false - state = encoding.InvalidMercuryRequest - return fmt.Errorf("at timestamp %s upkeep %s received status code %d from mercury v0.3, most likely this is caused by missing/malformed query args, missing or bad required headers, non-existent feeds, or no permissions for feeds", sl.Time.String(), sl.upkeepId.String(), resp.StatusCode) - } else if resp.StatusCode != http.StatusOK { - retryable = false - state = encoding.InvalidMercuryRequest - return fmt.Errorf("at timestamp %s upkeep %s received status code %d from mercury v0.3", sl.Time.String(), sl.upkeepId.String(), resp.StatusCode) - } - - lggr.Debugf("at block %s upkeep %s received status code %d from mercury v0.3 with BODY=%s", sl.Time.String(), sl.upkeepId.String(), resp.StatusCode, hexutil.Encode(body)) - - var response MercuryV03Response - err1 = json.Unmarshal(body, &response) - if err1 != nil { - lggr.Warnf("at timestamp %s upkeep %s failed to unmarshal body to MercuryV03Response from mercury v0.3: %v", sl.Time.String(), sl.upkeepId.String(), err1) - retryable = false - state = encoding.MercuryUnmarshalError - return err1 - } - // in v0.3, if some feeds are not available, the server will only return available feeds, but we need to make sure ALL feeds are retrieved before calling user contract - // hence, retry in this case. retry will help when we send a very new timestamp and reports are not yet generated - if len(response.Reports) != len(sl.Feeds) { - // TODO: AUTO-5044: calculate what reports are missing and log a warning - lggr.Warnf("at timestamp %s upkeep %s mercury v0.3 server retruned 200 status with %d reports while we requested %d feeds, treating as 404 (not found) and retrying", sl.Time.String(), sl.upkeepId.String(), len(response.Reports), len(sl.Feeds)) - retryable = true - state = encoding.MercuryFlakyFailure - return fmt.Errorf("%d", http.StatusNotFound) - } - var reportBytes [][]byte - for _, rsp := range response.Reports { - b, err := hexutil.Decode(rsp.FullReport) - if err != nil { - lggr.Warnf("at timestamp %s upkeep %s failed to decode reportBlob %s: %v", sl.Time.String(), sl.upkeepId.String(), rsp.FullReport, err) - retryable = false - state = encoding.InvalidMercuryResponse - return err - } - reportBytes = append(reportBytes, b) - } - ch <- MercuryData{ - Index: 0, - Bytes: reportBytes, - Retryable: false, - State: encoding.NoPipelineError, - } - sent = true - return nil - }, - // only retry when the error is 404 Not Found or 500 Internal Server Error - retry.RetryIf(func(err error) bool { - return err.Error() == fmt.Sprintf("%d", http.StatusNotFound) || err.Error() == fmt.Sprintf("%d", http.StatusInternalServerError) - }), - retry.Context(ctx), - retry.Delay(retryDelay), - retry.Attempts(totalAttempt)) - - if !sent { - md := MercuryData{ - Index: 0, - Bytes: [][]byte{}, - Retryable: retryable, - Error: retryErr, - State: state, - } - ch <- md - } -} - -// generateHMAC calculates a user HMAC for Mercury server authentication. -func (r *EvmRegistry) generateHMAC(method string, path string, body []byte, clientId string, secret string, ts int64) string { - bodyHash := sha256.New() - bodyHash.Write(body) - hashString := fmt.Sprintf("%s %s %s %s %d", - method, - path, - hex.EncodeToString(bodyHash.Sum(nil)), - clientId, - ts) - signedMessage := hmac.New(sha256.New, []byte(secret)) - signedMessage.Write([]byte(hashString)) - userHmac := hex.EncodeToString(signedMessage.Sum(nil)) - return userHmac -} diff --git a/core/services/ocr2/plugins/ocr2keeper/evm20/abi.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/abi.go similarity index 98% rename from core/services/ocr2/plugins/ocr2keeper/evm20/abi.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/abi.go index 4c0beee8fd6..98aeed66787 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm20/abi.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/abi.go @@ -8,7 +8,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v2" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v2" ) type evmRegistryPackerV2_0 struct { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm20/abi_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/abi_test.go similarity index 99% rename from core/services/ocr2/plugins/ocr2keeper/evm20/abi_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/abi_test.go index 7efc3feb1f0..c63c0d00f33 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm20/abi_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/abi_test.go @@ -8,9 +8,10 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common/hexutil" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v2" "github.com/stretchr/testify/assert" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v2" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper2_0" ) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm20/encoder.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/encoder.go similarity index 97% rename from core/services/ocr2/plugins/ocr2keeper/evm20/encoder.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/encoder.go index 3984cb7496b..aa98fd44cec 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm20/encoder.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/encoder.go @@ -6,8 +6,9 @@ import ( "reflect" "github.com/ethereum/go-ethereum/accounts/abi" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v2" - "github.com/smartcontractkit/ocr2keepers/pkg/v2/encoding" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v2" + "github.com/smartcontractkit/chainlink-automation/pkg/v2/encoding" ) type EVMAutomationEncoder20 struct { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm20/encoder_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/encoder_test.go similarity index 99% rename from core/services/ocr2/plugins/ocr2keeper/evm20/encoder_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/encoder_test.go index 74be6b46ee5..76212892657 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm20/encoder_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/encoder_test.go @@ -5,8 +5,9 @@ import ( "testing" "github.com/pkg/errors" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v2" "github.com/stretchr/testify/assert" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v2" ) func TestEVMAutomationEncoder20(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm20/head.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/head.go similarity index 94% rename from core/services/ocr2/plugins/ocr2keeper/evm20/head.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/head.go index 1023bff5dd2..78983173558 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm20/head.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/head.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v2" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v2" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" diff --git a/core/services/ocr2/plugins/ocr2keeper/evm20/log_provider.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/log_provider.go similarity index 94% rename from core/services/ocr2/plugins/ocr2keeper/evm20/log_provider.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/log_provider.go index e32c4a0662e..45884d2f726 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm20/log_provider.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/log_provider.go @@ -10,16 +10,18 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" - pluginutils "github.com/smartcontractkit/ocr2keepers/pkg/util" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v2" - "github.com/smartcontractkit/ocr2keepers/pkg/v2/encoding" + + pluginutils "github.com/smartcontractkit/chainlink-automation/pkg/util" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v2" + "github.com/smartcontractkit/chainlink-automation/pkg/v2/encoding" + + "github.com/smartcontractkit/chainlink-common/pkg/services" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" registry "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper2_0" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) type TransmitUnpacker interface { @@ -27,7 +29,7 @@ type TransmitUnpacker interface { } type LogProvider struct { - sync utils.StartStopOnce + sync services.StateMachine mu sync.RWMutex runState int runError error @@ -150,8 +152,8 @@ func (c *LogProvider) PerformLogs(ctx context.Context) ([]ocr2keepers.PerformLog // always check the last lookback number of blocks and rebroadcast // this allows the plugin to make decisions based on event confirmations logs, err := c.logPoller.LogsWithSigs( - end-c.lookbackBlocks, - end, + end.BlockNumber-c.lookbackBlocks, + end.BlockNumber, []common.Hash{ registry.KeeperRegistryUpkeepPerformed{}.Topic(), }, @@ -174,7 +176,7 @@ func (c *LogProvider) PerformLogs(ctx context.Context) ([]ocr2keepers.PerformLog Key: UpkeepKeyHelper[uint32]{}.MakeUpkeepKey(p.CheckBlockNumber, p.Id), TransmitBlock: BlockKeyHelper[int64]{}.MakeBlockKey(p.BlockNumber), TransactionHash: p.TxHash.Hex(), - Confirmations: end - p.BlockNumber, + Confirmations: end.BlockNumber - p.BlockNumber, } vals = append(vals, l) } @@ -193,8 +195,8 @@ func (c *LogProvider) StaleReportLogs(ctx context.Context) ([]ocr2keepers.StaleR // ReorgedUpkeepReportLogs logs, err := c.logPoller.LogsWithSigs( - end-c.lookbackBlocks, - end, + end.BlockNumber-c.lookbackBlocks, + end.BlockNumber, []common.Hash{ registry.KeeperRegistryReorgedUpkeepReport{}.Topic(), }, @@ -211,8 +213,8 @@ func (c *LogProvider) StaleReportLogs(ctx context.Context) ([]ocr2keepers.StaleR // StaleUpkeepReportLogs logs, err = c.logPoller.LogsWithSigs( - end-c.lookbackBlocks, - end, + end.BlockNumber-c.lookbackBlocks, + end.BlockNumber, []common.Hash{ registry.KeeperRegistryStaleUpkeepReport{}.Topic(), }, @@ -229,8 +231,8 @@ func (c *LogProvider) StaleReportLogs(ctx context.Context) ([]ocr2keepers.StaleR // InsufficientFundsUpkeepReportLogs logs, err = c.logPoller.LogsWithSigs( - end-c.lookbackBlocks, - end, + end.BlockNumber-c.lookbackBlocks, + end.BlockNumber, []common.Hash{ registry.KeeperRegistryInsufficientFundsUpkeepReport{}.Topic(), }, @@ -257,7 +259,7 @@ func (c *LogProvider) StaleReportLogs(ctx context.Context) ([]ocr2keepers.StaleR Key: encoding.BasicEncoder{}.MakeUpkeepKey(checkBlockNumber, upkeepId), TransmitBlock: BlockKeyHelper[int64]{}.MakeBlockKey(r.BlockNumber), TransactionHash: r.TxHash.Hex(), - Confirmations: end - r.BlockNumber, + Confirmations: end.BlockNumber - r.BlockNumber, } vals = append(vals, l) } @@ -272,7 +274,7 @@ func (c *LogProvider) StaleReportLogs(ctx context.Context) ([]ocr2keepers.StaleR Key: encoding.BasicEncoder{}.MakeUpkeepKey(checkBlockNumber, upkeepId), TransmitBlock: BlockKeyHelper[int64]{}.MakeBlockKey(r.BlockNumber), TransactionHash: r.TxHash.Hex(), - Confirmations: end - r.BlockNumber, + Confirmations: end.BlockNumber - r.BlockNumber, } vals = append(vals, l) } @@ -287,7 +289,7 @@ func (c *LogProvider) StaleReportLogs(ctx context.Context) ([]ocr2keepers.StaleR Key: encoding.BasicEncoder{}.MakeUpkeepKey(checkBlockNumber, upkeepId), TransmitBlock: BlockKeyHelper[int64]{}.MakeBlockKey(r.BlockNumber), TransactionHash: r.TxHash.Hex(), - Confirmations: end - r.BlockNumber, + Confirmations: end.BlockNumber - r.BlockNumber, } vals = append(vals, l) } diff --git a/core/services/ocr2/plugins/ocr2keeper/evm20/mocks/registry.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/mocks/registry.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm20/mocks/registry.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/mocks/registry.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm20/registry.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/registry.go similarity index 97% rename from core/services/ocr2/plugins/ocr2keeper/evm20/registry.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/registry.go index 7d1de17a5b0..a6a2f40f855 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm20/registry.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/registry.go @@ -15,17 +15,19 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v2" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v2" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper2_0" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) const ( @@ -70,7 +72,7 @@ type LatestBlockGetter interface { LatestBlock() int64 } -func NewEVMRegistryService(addr common.Address, client evm.Chain, lggr logger.Logger) (*EvmRegistry, error) { +func NewEVMRegistryService(addr common.Address, client legacyevm.Chain, lggr logger.Logger) (*EvmRegistry, error) { keeperRegistryABI, err := abi.JSON(strings.NewReader(keeper_registry_wrapper2_0.KeeperRegistryABI)) if err != nil { return nil, fmt.Errorf("%w: %s", ErrABINotParsable, err) @@ -135,7 +137,7 @@ type activeUpkeep struct { type EvmRegistry struct { HeadProvider - sync utils.StartStopOnce + sync services.StateMachine lggr logger.Logger poller logpoller.LogPoller addr common.Address @@ -346,7 +348,7 @@ func (r *EvmRegistry) initialize() error { func (r *EvmRegistry) pollLogs() error { var latest int64 - var end int64 + var end logpoller.LogPollerBlock var err error if end, err = r.poller.LatestBlock(pg.WithParentCtx(r.ctx)); err != nil { @@ -355,11 +357,11 @@ func (r *EvmRegistry) pollLogs() error { r.mu.Lock() latest = r.lastPollBlock - r.lastPollBlock = end + r.lastPollBlock = end.BlockNumber r.mu.Unlock() // if start and end are the same, no polling needs to be done - if latest == 0 || latest == end { + if latest == 0 || latest == end.BlockNumber { return nil } @@ -367,8 +369,8 @@ func (r *EvmRegistry) pollLogs() error { var logs []logpoller.Log if logs, err = r.poller.LogsWithSigs( - end-logEventLookback, - end, + end.BlockNumber-logEventLookback, + end.BlockNumber, upkeepStateEvents, r.addr, pg.WithParentCtx(r.ctx), diff --git a/core/services/ocr2/plugins/ocr2keeper/evm20/registry_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/registry_test.go similarity index 94% rename from core/services/ocr2/plugins/ocr2keeper/evm20/registry_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/registry_test.go index 348b5a47c0f..0e0ceba7160 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm20/registry_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20/registry_test.go @@ -1,21 +1,22 @@ package evm import ( - "context" "fmt" "math/big" "testing" "time" "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v2" + commonmocks "github.com/smartcontractkit/chainlink/v2/common/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -54,7 +55,7 @@ func TestGetActiveUpkeepKeys(t *testing.T) { active: actives, } - keys, err := rg.GetActiveUpkeepIDs(context.Background()) + keys, err := rg.GetActiveUpkeepIDs(testutils.Context(t)) if test.ExpectedErr != nil { assert.ErrorIs(t, err, test.ExpectedErr) @@ -189,7 +190,7 @@ func TestPollLogs(t *testing.T) { if test.LatestBlock != nil { mp.On("LatestBlock", mock.Anything). - Return(test.LatestBlock.OutputBlock, test.LatestBlock.OutputErr) + Return(logpoller.LogPollerBlock{BlockNumber: test.LatestBlock.OutputBlock}, test.LatestBlock.OutputErr) } if test.LogsWithSigs != nil { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/active_list.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/active_list.go similarity index 95% rename from core/services/ocr2/plugins/ocr2keeper/evm21/active_list.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/active_list.go index 899dd398d03..7ea476ee773 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/active_list.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/active_list.go @@ -4,9 +4,9 @@ import ( "math/big" "sync" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) // ActiveUpkeepList is a list to manage active upkeep IDs diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/active_list_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/active_list_test.go similarity index 96% rename from core/services/ocr2/plugins/ocr2keeper/evm21/active_list_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/active_list_test.go index 76ca6bfdf19..378589d1779 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/active_list_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/active_list_test.go @@ -5,10 +5,11 @@ import ( "sort" "testing" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) func TestActiveUpkeepList(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21/custom_telemetry.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21/custom_telemetry.go new file mode 100644 index 00000000000..e7e9728c3a0 --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21/custom_telemetry.go @@ -0,0 +1,163 @@ +package autotelemetry21 + +import ( + "context" + "encoding/hex" + "time" + + "google.golang.org/protobuf/proto" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/core/logger" + evm21 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21" + "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" + "github.com/smartcontractkit/chainlink/v2/core/static" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +type AutomationCustomTelemetryService struct { + services.StateMachine + monitoringEndpoint commontypes.MonitoringEndpoint + blockSubscriber *evm21.BlockSubscriber + blockSubChanID int + threadCtrl utils.ThreadControl + lggr logger.Logger + configDigest [32]byte + contractConfigTracker types.ContractConfigTracker +} + +// NewAutomationCustomTelemetryService creates a telemetry service for new blocks and node version +func NewAutomationCustomTelemetryService(me commontypes.MonitoringEndpoint, + lggr logger.Logger, blocksub *evm21.BlockSubscriber, configTracker types.ContractConfigTracker) (*AutomationCustomTelemetryService, error) { + return &AutomationCustomTelemetryService{ + monitoringEndpoint: me, + threadCtrl: utils.NewThreadControl(), + lggr: lggr.Named("AutomationCustomTelem"), + contractConfigTracker: configTracker, + blockSubscriber: blocksub, + }, nil +} + +// Start starts Custom Telemetry Service, sends 1 NodeVersion message to endpoint at start and sends new BlockNumber messages +func (e *AutomationCustomTelemetryService) Start(ctx context.Context) error { + return e.StartOnce("AutomationCustomTelemetryService", func() error { + e.lggr.Infof("Starting: Custom Telemetry Service") + _, configDetails, err := e.contractConfigTracker.LatestConfigDetails(ctx) + if err != nil { + e.lggr.Errorf("Error occurred while getting newestConfigDetails for initialization %s", err) + } else { + e.configDigest = configDetails + e.sendNodeVersionMsg() + } + e.threadCtrl.Go(func(ctx context.Context) { + minuteTicker := time.NewTicker(1 * time.Minute) + hourTicker := time.NewTicker(1 * time.Hour) + defer minuteTicker.Stop() + defer hourTicker.Stop() + for { + select { + case <-minuteTicker.C: + _, newConfigDigest, err := e.contractConfigTracker.LatestConfigDetails(ctx) + if err != nil { + e.lggr.Errorf("Error occurred while getting newestConfigDetails in configDigest loop %s", err) + } + if newConfigDigest != e.configDigest { + e.configDigest = newConfigDigest + e.sendNodeVersionMsg() + } + case <-hourTicker.C: + e.sendNodeVersionMsg() + case <-ctx.Done(): + return + } + } + }) + + chanID, blockSubscriberChan, blockSubErr := e.blockSubscriber.Subscribe() + if blockSubErr != nil { + e.lggr.Errorf("Block Subscriber Error: Subscribe(): %s", blockSubErr) + return blockSubErr + } + e.blockSubChanID = chanID + e.threadCtrl.Go(func(ctx context.Context) { + e.lggr.Debug("Started: Sending BlockNumber Messages") + for { + select { + case blockHistory := <-blockSubscriberChan: + // Exploratory: Debounce blocks to avoid overflow in case of re-org + latestBlockKey, err := blockHistory.Latest() + if err != nil { + e.lggr.Errorf("BlockSubscriber BlockHistory.Latest() failed: %s", err) + continue + } + e.sendBlockNumberMsg(latestBlockKey) + case <-ctx.Done(): + return + } + } + }) + return nil + }) +} + +// Close stops go routines and closes channels +func (e *AutomationCustomTelemetryService) Close() error { + return e.StopOnce("AutomationCustomTelemetryService", func() error { + e.lggr.Debug("Stopping: custom telemetry service") + e.threadCtrl.Close() + err := e.blockSubscriber.Unsubscribe(e.blockSubChanID) + if err != nil { + e.lggr.Errorf("Custom telemetry service encounters error %v when stopping", err) + return err + } + e.lggr.Infof("Stopped: Custom telemetry service") + return nil + }) +} + +func (e *AutomationCustomTelemetryService) sendNodeVersionMsg() { + vMsg := &telem.NodeVersion{ + Timestamp: uint64(time.Now().UTC().UnixMilli()), + NodeVersion: static.Version, + ConfigDigest: e.configDigest[:], + } + wrappedVMsg := &telem.AutomationTelemWrapper{ + Msg: &telem.AutomationTelemWrapper_NodeVersion{ + NodeVersion: vMsg, + }, + } + bytes, err := proto.Marshal(wrappedVMsg) + if err != nil { + e.lggr.Errorf("Error occurred while marshalling the Node Version Message %s: %v", wrappedVMsg.String(), err) + } else { + e.monitoringEndpoint.SendLog(bytes) + e.lggr.Debugf("NodeVersion Message Sent to Endpoint: %d", vMsg.Timestamp) + } +} + +func (e *AutomationCustomTelemetryService) sendBlockNumberMsg(blockKey ocr2keepers.BlockKey) { + blockNumMsg := &telem.BlockNumber{ + Timestamp: uint64(time.Now().UTC().UnixMilli()), + BlockNumber: uint64(blockKey.Number), + BlockHash: hex.EncodeToString(blockKey.Hash[:]), + ConfigDigest: e.configDigest[:], + } + wrappedBlockNumMsg := &telem.AutomationTelemWrapper{ + Msg: &telem.AutomationTelemWrapper_BlockNumber{ + BlockNumber: blockNumMsg, + }, + } + b, err := proto.Marshal(wrappedBlockNumMsg) + if err != nil { + e.lggr.Errorf("Error occurred while marshalling the Block Num Message %s: %v", wrappedBlockNumMsg.String(), err) + } else { + e.monitoringEndpoint.SendLog(b) + e.lggr.Debugf("BlockNumber Message Sent to Endpoint: %d", blockNumMsg.Timestamp) + } +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21/custom_telemetry_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21/custom_telemetry_test.go new file mode 100644 index 00000000000..5fce8718cba --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21/custom_telemetry_test.go @@ -0,0 +1,56 @@ +package autotelemetry21 + +import ( + "sync" + "testing" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/assert" + + headtracker "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/logger" + evm "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21" +) + +// const historySize = 4 +// const blockSize = int64(4) +const finality = uint32(4) + +func TestNewAutomationCustomTelemetryService(t *testing.T) { + me := &MockMonitoringEndpoint{} + lggr := logger.TestLogger(t) + var hb headtracker.HeadBroadcaster + var lp logpoller.LogPoller + + bs := evm.NewBlockSubscriber(hb, lp, finality, lggr) + // configTracker := &MockContractConfigTracker{} + var configTracker types.ContractConfigTracker + + service, err := NewAutomationCustomTelemetryService(me, lggr, bs, configTracker) + if err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + service.monitoringEndpoint.SendLog([]byte("test")) + assert.Equal(t, me.LogCount(), 1) + service.monitoringEndpoint.SendLog([]byte("test2")) + assert.Equal(t, me.LogCount(), 2) + service.Close() +} + +type MockMonitoringEndpoint struct { + sentLogs [][]byte + lock sync.RWMutex +} + +func (me *MockMonitoringEndpoint) SendLog(log []byte) { + me.lock.Lock() + defer me.lock.Unlock() + me.sentLogs = append(me.sentLogs, log) +} + +func (me *MockMonitoringEndpoint) LogCount() int { + me.lock.RLock() + defer me.lock.RUnlock() + return len(me.sentLogs) +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/block_subscriber.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber.go similarity index 94% rename from core/services/ocr2/plugins/ocr2keeper/evm21/block_subscriber.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber.go index 9766d988767..134a75fc2c7 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/block_subscriber.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber.go @@ -8,7 +8,10 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink-common/pkg/services" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -34,7 +37,7 @@ var ( ) type BlockSubscriber struct { - utils.StartStopOnce + services.StateMachine threadCtrl utils.ThreadControl mu sync.RWMutex @@ -54,6 +57,10 @@ type BlockSubscriber struct { lggr logger.Logger } +func (bs *BlockSubscriber) LatestBlock() *ocr2keepers.BlockKey { + return bs.latestBlock.Load() +} + var _ ocr2keepers.BlockSubscriber = &BlockSubscriber{} func NewBlockSubscriber(hb httypes.HeadBroadcaster, lp logpoller.LogPoller, finalityDepth uint32, lggr logger.Logger) *BlockSubscriber { @@ -77,12 +84,13 @@ func (bs *BlockSubscriber) getBlockRange(ctx context.Context) ([]uint64, error) if err != nil { return nil, err } - bs.lggr.Infof("latest block from log poller is %d", h) + latestBlockNumber := h.BlockNumber + bs.lggr.Infof("latest block from log poller is %d", latestBlockNumber) var blocks []uint64 for i := bs.blockSize - 1; i >= 0; i-- { - if h-i > 0 { - blocks = append(blocks, uint64(h-i)) + if latestBlockNumber-i > 0 { + blocks = append(blocks, uint64(latestBlockNumber-i)) } } return blocks, nil diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/block_subscriber_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber_test.go similarity index 97% rename from core/services/ocr2/plugins/ocr2keeper/evm21/block_subscriber_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber_test.go index 618ea83d4e9..41a43b951e7 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/block_subscriber_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber_test.go @@ -1,16 +1,16 @@ package evm import ( - "context" "fmt" "testing" "time" "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + commonmocks "github.com/smartcontractkit/chainlink/v2/common/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -97,7 +97,7 @@ func TestBlockSubscriber_GetBlockRange(t *testing.T) { for _, tc := range tests { t.Run(tc.Name, func(t *testing.T) { lp := new(mocks.LogPoller) - lp.On("LatestBlock", mock.Anything).Return(tc.LatestBlock, tc.LatestBlockErr) + lp.On("LatestBlock", mock.Anything).Return(logpoller.LogPollerBlock{BlockNumber: tc.LatestBlock}, tc.LatestBlockErr) bs := NewBlockSubscriber(hb, lp, finality, lggr) bs.blockHistorySize = historySize bs.blockSize = blockSize @@ -278,7 +278,7 @@ func TestBlockSubscriber_Start(t *testing.T) { hb := commonmocks.NewHeadBroadcaster[*evmtypes.Head, common.Hash](t) hb.On("Subscribe", mock.Anything).Return(&evmtypes.Head{Number: 42}, func() {}) lp := new(mocks.LogPoller) - lp.On("LatestBlock", mock.Anything).Return(int64(100), nil) + lp.On("LatestBlock", mock.Anything).Return(logpoller.LogPollerBlock{BlockNumber: 100}, nil) blocks := []uint64{97, 98, 99, 100} pollerBlocks := []logpoller.LogPollerBlock{ { @@ -304,7 +304,7 @@ func TestBlockSubscriber_Start(t *testing.T) { bs := NewBlockSubscriber(hb, lp, finality, lggr) bs.blockHistorySize = historySize bs.blockSize = blockSize - err := bs.Start(context.Background()) + err := bs.Start(testutils.Context(t)) assert.Nil(t, err) h97 := evmtypes.Head{ diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/abi.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/abi.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/abi.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/abi.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/interfaces.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/interfaces.go similarity index 81% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/interfaces.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/interfaces.go index d9417c72a19..49975855b54 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/core/interfaces.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/interfaces.go @@ -3,7 +3,7 @@ package core import ( "context" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ) // UpkeepStateReader is the interface for reading the current state of upkeeps. diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/mocks/upkeep_state_reader.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/mocks/upkeep_state_reader.go similarity index 95% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/mocks/upkeep_state_reader.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/mocks/upkeep_state_reader.go index 40b63611c90..b036fbb26c3 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/core/mocks/upkeep_state_reader.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/mocks/upkeep_state_reader.go @@ -7,7 +7,7 @@ import ( mock "github.com/stretchr/testify/mock" - types "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + types "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ) // UpkeepStateReader is an autogenerated mock type for the UpkeepStateReader type diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/payload.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/payload.go similarity index 92% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/payload.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/payload.go index d7336737674..ed5530ae7b5 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/core/payload.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/payload.go @@ -6,7 +6,8 @@ import ( "math/big" "github.com/ethereum/go-ethereum/crypto" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ) var ( diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/payload_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/payload_test.go similarity index 98% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/payload_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/payload_test.go index 1ca280c0d2d..cb3a67dde0c 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/core/payload_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/payload_test.go @@ -5,9 +5,9 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" - "github.com/stretchr/testify/assert" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ) func TestWorkID(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/testutil.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/testutil.go similarity index 91% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/testutil.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/testutil.go index 47935b56f68..3c0eeb1c44e 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/core/testutil.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/testutil.go @@ -4,7 +4,8 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ) // GenUpkeepID generates an ocr2keepers.UpkeepIdentifier with a specific UpkeepType and some random string diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/trigger.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/trigger.go similarity index 97% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/trigger.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/trigger.go index 79273479596..02e6946c8fb 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/core/trigger.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/trigger.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" ) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/trigger_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/trigger_test.go similarity index 97% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/trigger_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/trigger_test.go index 0233e40679d..366d59b6397 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/core/trigger_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/trigger_test.go @@ -7,8 +7,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/assert" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ) func TestPackUnpackTrigger(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/type.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/type.go similarity index 92% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/type.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/type.go index aeb56a16f62..7820b47e6eb 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/core/type.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/type.go @@ -3,7 +3,7 @@ package core import ( "math/big" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ) const ( diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/type_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/type_test.go similarity index 93% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/type_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/type_test.go index 8a2a51533e5..6f81ca7690e 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/core/type_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/type_test.go @@ -4,9 +4,9 @@ import ( "math/big" "testing" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" - "github.com/stretchr/testify/assert" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ) func TestGetUpkeepType(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/utils.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/utils.go similarity index 55% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/utils.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/utils.go index 6a31b938fc6..1da28c1ad09 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/core/utils.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/utils.go @@ -3,7 +3,6 @@ package core import ( "context" "math/big" - "strings" "github.com/ethereum/go-ethereum/common" @@ -14,20 +13,8 @@ import ( // GetTxBlock calls eth_getTransactionReceipt on the eth client to obtain a tx receipt func GetTxBlock(ctx context.Context, client client.Client, txHash common.Hash) (*big.Int, common.Hash, error) { receipt := types.Receipt{} - err := client.CallContext(ctx, &receipt, "eth_getTransactionReceipt", txHash) - if err != nil { - if strings.Contains(err.Error(), "not yet been implemented") { - // workaround for simulated chains - // Exploratory: fix this properly (e.g. in the simulated backend) - r, err1 := client.TransactionReceipt(ctx, txHash) - if err1 != nil { - return nil, common.Hash{}, err1 - } - if r.Status != 1 { - return nil, common.Hash{}, nil - } - return r.BlockNumber, r.BlockHash, nil - } + + if err := client.CallContext(ctx, &receipt, "eth_getTransactionReceipt", txHash); err != nil { return nil, common.Hash{}, err } diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/core/utils_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/utils_test.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/core/utils_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/utils_test.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/encoder.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/encoder.go similarity index 96% rename from core/services/ocr2/plugins/ocr2keeper/evm21/encoding/encoder.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/encoder.go index 239de099c01..89fcbb4a0ef 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/encoder.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/encoder.go @@ -4,10 +4,10 @@ import ( "fmt" "math/big" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) var ( diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/encoder_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/encoder_test.go similarity index 74% rename from core/services/ocr2/plugins/ocr2keeper/evm21/encoding/encoder_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/encoder_test.go index 578153e07a4..aa549ab3ec8 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/encoder_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/encoder_test.go @@ -1,16 +1,33 @@ package encoding import ( + "bytes" + "encoding/hex" "math/big" + "os" "testing" "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/assert" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) +var expectedEncodedReport []byte + +func init() { + b, err := os.ReadFile("../fixtures/expected_encoded_report.txt") + if err != nil { + panic(err) + } + expectedEncodedReport, err = hex.DecodeString(string(b)) + if err != nil { + panic(err) + } +} + func TestReportEncoder_EncodeExtract(t *testing.T) { encoder := reportEncoder{ packer: NewAbiPacker(), @@ -92,6 +109,25 @@ func TestReportEncoder_EncodeExtract(t *testing.T) { } } +func TestReportEncoder_BackwardsCompatibility(t *testing.T) { + encoder := reportEncoder{ + packer: NewAbiPacker(), + } + results := []ocr2keepers.CheckResult{ + newResult(1, 2, core.GenUpkeepID(ocr2keepers.LogTrigger, "10"), 5, 6), + newResult(3, 4, core.GenUpkeepID(ocr2keepers.ConditionTrigger, "20"), 7, 8), + } + encoded, err := encoder.Encode(results...) + assert.NoError(t, err) + if !bytes.Equal(encoded, expectedEncodedReport) { + assert.Fail(t, + "encoded report does not match expected encoded report; "+ + "this means a breaking change has been made to the report encoding function; "+ + "only update this test if non-backwards-compatible changes are necessary", + ) + } +} + func newResult(block int64, checkBlock ocr2keepers.BlockNumber, id ocr2keepers.UpkeepIdentifier, fastGasWei, linkNative int64) ocr2keepers.CheckResult { tp := core.GetUpkeepType(id) diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/interface.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/interface.go new file mode 100644 index 00000000000..06a3e7b106b --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/interface.go @@ -0,0 +1,62 @@ +package encoding + +import ( + "math/big" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" + iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" +) + +const ( + // NOTE: This enum should be kept in sync with evmregistry/v21/mercury/upkeep_failure_reasons.go + // TODO (AUTO-7928) Remove this duplication + // upkeep failure onchain reasons + UpkeepFailureReasonNone uint8 = 0 + UpkeepFailureReasonUpkeepCancelled uint8 = 1 + UpkeepFailureReasonUpkeepPaused uint8 = 2 + UpkeepFailureReasonTargetCheckReverted uint8 = 3 + UpkeepFailureReasonUpkeepNotNeeded uint8 = 4 + UpkeepFailureReasonPerformDataExceedsLimit uint8 = 5 + UpkeepFailureReasonInsufficientBalance uint8 = 6 + UpkeepFailureReasonMercuryCallbackReverted uint8 = 7 + UpkeepFailureReasonRevertDataExceedsLimit uint8 = 8 + UpkeepFailureReasonRegistryPaused uint8 = 9 + // leaving a gap here for more onchain failure reasons in the future + // upkeep failure offchain reasons + UpkeepFailureReasonMercuryAccessNotAllowed uint8 = 32 + UpkeepFailureReasonTxHashNoLongerExists uint8 = 33 + UpkeepFailureReasonInvalidRevertDataInput uint8 = 34 + UpkeepFailureReasonSimulationFailed uint8 = 35 + UpkeepFailureReasonTxHashReorged uint8 = 36 + + // NOTE: This enum should be kept in sync with evmregistry/v21/mercury/upkeep_states.go + // TODO (AUTO-7928) Remove this duplication + // pipeline execution error + NoPipelineError uint8 = 0 + CheckBlockTooOld uint8 = 1 + CheckBlockInvalid uint8 = 2 + RpcFlakyFailure uint8 = 3 + MercuryFlakyFailure uint8 = 4 + PackUnpackDecodeFailed uint8 = 5 + MercuryUnmarshalError uint8 = 6 + InvalidMercuryRequest uint8 = 7 + InvalidMercuryResponse uint8 = 8 // this will only happen if Mercury server sends bad responses + UpkeepNotAuthorized uint8 = 9 +) + +type UpkeepInfo = iregistry21.KeeperRegistryBase21UpkeepInfo + +type Packer interface { + UnpackCheckResult(payload ocr2keepers.UpkeepPayload, raw string) (ocr2keepers.CheckResult, error) + UnpackCheckCallbackResult(callbackResp []byte) (uint8, bool, []byte, uint8, *big.Int, error) + UnpackPerformResult(raw string) (uint8, bool, error) + UnpackLogTriggerConfig(raw []byte) (automation_utils_2_1.LogTriggerConfig, error) + PackReport(report automation_utils_2_1.KeeperRegistryBase21Report) ([]byte, error) + UnpackReport(raw []byte) (automation_utils_2_1.KeeperRegistryBase21Report, error) + PackGetUpkeepPrivilegeConfig(upkeepId *big.Int) ([]byte, error) + UnpackGetUpkeepPrivilegeConfig(resp []byte) ([]byte, error) + DecodeStreamsLookupRequest(data []byte) (*mercury.StreamsLookupError, error) +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/packer.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/packer.go similarity index 91% rename from core/services/ocr2/plugins/ocr2keeper/evm21/encoding/packer.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/packer.go index 9e070b2838c..57013a6277a 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/packer.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/packer.go @@ -6,10 +6,12 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common/hexutil" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" ) // triggerWrapper is a wrapper for the different trigger types (log and condition triggers). @@ -80,7 +82,7 @@ func (p *abiPacker) UnpackGetUpkeepPrivilegeConfig(resp []byte) ([]byte, error) return bts, nil } -func (p *abiPacker) UnpackCheckCallbackResult(callbackResp []byte) (PipelineExecutionState, bool, []byte, uint8, *big.Int, error) { +func (p *abiPacker) UnpackCheckCallbackResult(callbackResp []byte) (uint8, bool, []byte, uint8, *big.Int, error) { out, err := p.registryABI.Methods["checkCallback"].Outputs.UnpackValues(callbackResp) if err != nil { return PackUnpackDecodeFailed, false, nil, 0, nil, fmt.Errorf("%w: unpack checkUpkeep return: %s", err, hexutil.Encode(callbackResp)) @@ -94,7 +96,7 @@ func (p *abiPacker) UnpackCheckCallbackResult(callbackResp []byte) (PipelineExec return NoPipelineError, upkeepNeeded, rawPerformData, failureReason, gasUsed, nil } -func (p *abiPacker) UnpackPerformResult(raw string) (PipelineExecutionState, bool, error) { +func (p *abiPacker) UnpackPerformResult(raw string) (uint8, bool, error) { b, err := hexutil.Decode(raw) if err != nil { return PackUnpackDecodeFailed, false, err @@ -161,16 +163,8 @@ func (p *abiPacker) UnpackReport(raw []byte) (automation_utils_2_1.KeeperRegistr return report, nil } -type StreamsLookupError struct { - FeedParamKey string - Feeds []string - TimeParamKey string - Time *big.Int - ExtraData []byte -} - // DecodeStreamsLookupRequest decodes the revert error StreamsLookup(string feedParamKey, string[] feeds, string feedParamKey, uint256 time, byte[] extraData) -func (p *abiPacker) DecodeStreamsLookupRequest(data []byte) (*StreamsLookupError, error) { +func (p *abiPacker) DecodeStreamsLookupRequest(data []byte) (*mercury.StreamsLookupError, error) { e := p.streamsABI.Errors["StreamsLookup"] unpack, err := e.Unpack(data) if err != nil { @@ -178,7 +172,7 @@ func (p *abiPacker) DecodeStreamsLookupRequest(data []byte) (*StreamsLookupError } errorParameters := unpack.([]interface{}) - return &StreamsLookupError{ + return &mercury.StreamsLookupError{ FeedParamKey: *abi.ConvertType(errorParameters[0], new(string)).(*string), Feeds: *abi.ConvertType(errorParameters[1], new([]string)).(*[]string), TimeParamKey: *abi.ConvertType(errorParameters[2], new(string)).(*string), @@ -188,10 +182,10 @@ func (p *abiPacker) DecodeStreamsLookupRequest(data []byte) (*StreamsLookupError } // GetIneligibleCheckResultWithoutPerformData returns an ineligible check result with ineligibility reason and pipeline execution state but without perform data -func GetIneligibleCheckResultWithoutPerformData(p ocr2keepers.UpkeepPayload, reason UpkeepFailureReason, state PipelineExecutionState, retryable bool) ocr2keepers.CheckResult { +func GetIneligibleCheckResultWithoutPerformData(p ocr2keepers.UpkeepPayload, reason uint8, state uint8, retryable bool) ocr2keepers.CheckResult { return ocr2keepers.CheckResult{ - IneligibilityReason: uint8(reason), - PipelineExecutionState: uint8(state), + IneligibilityReason: reason, + PipelineExecutionState: state, Retryable: retryable, UpkeepID: p.UpkeepID, Trigger: p.Trigger, diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/packer_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/packer_test.go similarity index 98% rename from core/services/ocr2/plugins/ocr2keeper/evm21/encoding/packer_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/packer_test.go index 1801b018572..42fcd40d618 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/encoding/packer_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding/packer_test.go @@ -9,13 +9,15 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" automation21Utils "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" ) func TestPacker_PackReport(t *testing.T) { @@ -210,7 +212,7 @@ func TestPacker_UnpackPerformResult(t *testing.T) { tests := []struct { Name string RawData string - State PipelineExecutionState + State uint8 }{ { Name: "unpack success", @@ -238,7 +240,7 @@ func TestPacker_UnpackCheckCallbackResult(t *testing.T) { FailureReason uint8 GasUsed *big.Int ErrorString string - State PipelineExecutionState + State uint8 }{ { Name: "unpack upkeep needed", @@ -441,14 +443,14 @@ func TestPacker_DecodeStreamsLookupRequest(t *testing.T) { tests := []struct { name string data []byte - expected *StreamsLookupError - state PipelineExecutionState + expected *mercury.StreamsLookupError + state uint8 err error }{ { name: "success - decode to streams lookup", data: hexutil.MustDecode("0xf055e4a200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000002435eb50000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000966656564496448657800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000423078343535343438326435353533343432643431353234323439353435323535346432643534343535333534346534353534303030303030303030303030303030300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307834323534343332643535353334343264343135323432343935343532353534643264353434353533353434653435353430303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b626c6f636b4e756d62657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), - expected: &StreamsLookupError{ + expected: &mercury.StreamsLookupError{ FeedParamKey: "feedIdHex", Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, TimeParamKey: "blockNumber", diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/fixtures/expected_encoded_report.txt b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/fixtures/expected_encoded_report.txt new file mode 100644 index 00000000000..3fb42146c8c --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/fixtures/expected_encoded_report.txt @@ -0,0 +1 @@ +00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000020100000000000000000000000000000131300000000000000000000000000000010000000000000000000000000000003230000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a0aaaaaaaa9012345678901234567890123456789012345678901234567890123412345678901234567890123456789012345678901234567890123456789012340000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000101020304050607080102030405060708010203040506070801020304050607080000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000301020304050607080102030405060708010203040506070801020304050607080000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005646174613000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056461746130000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/keyring.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/keyring.go similarity index 95% rename from core/services/ocr2/plugins/ocr2keeper/evm21/keyring.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/keyring.go index df4e3a86502..2d84f096378 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/keyring.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/keyring.go @@ -3,7 +3,8 @@ package evm import ( "github.com/smartcontractkit/libocr/offchainreporting2/types" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" - "github.com/smartcontractkit/ocr2keepers/pkg/v3/plugin" + + "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" ) var _ ocr3types.OnchainKeyring[plugin.AutomationReportInfo] = &onchainKeyringV3Wrapper{} diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/keyring_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/keyring_test.go similarity index 98% rename from core/services/ocr2/plugins/ocr2keeper/evm21/keyring_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/keyring_test.go index 14c42a810c1..92a05c2d760 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/keyring_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/keyring_test.go @@ -3,10 +3,12 @@ package evm import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/ocr2keepers/pkg/v3/plugin" - "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" ) func TestNewOnchainKeyringV3Wrapper(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/block_time.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/block_time.go similarity index 91% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/block_time.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/block_time.go index 9fc35dd84be..814ed29d900 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/block_time.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/block_time.go @@ -34,10 +34,11 @@ func (r *blockTimeResolver) BlockTime(ctx context.Context, blockSampleSize int64 if err != nil { return 0, fmt.Errorf("failed to get latest block from poller: %w", err) } - if latest <= blockSampleSize { + latestBlockNumber := latest.BlockNumber + if latestBlockNumber <= blockSampleSize { return defaultBlockTime, nil } - start, end := latest-blockSampleSize, latest + start, end := latestBlockNumber-blockSampleSize, latestBlockNumber startTime, endTime, err := r.getSampleTimestamps(ctx, uint64(start), uint64(end)) if err != nil { return 0, err diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/block_time_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/block_time_test.go similarity index 87% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/block_time_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/block_time_test.go index 0ad9990e185..683ba378940 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/block_time_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/block_time_test.go @@ -1,7 +1,6 @@ package logprovider import ( - "context" "fmt" "testing" "time" @@ -11,6 +10,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" lpmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" ) func TestBlockTimeResolver_BlockTime(t *testing.T) { @@ -63,13 +63,12 @@ func TestBlockTimeResolver_BlockTime(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := testutils.Context(t) lp := new(lpmocks.LogPoller) resolver := newBlockTimeResolver(lp) - lp.On("LatestBlock", mock.Anything).Return(tc.latestBlock, tc.latestBlockErr) + lp.On("LatestBlock", mock.Anything).Return(logpoller.LogPollerBlock{BlockNumber: tc.latestBlock}, tc.latestBlockErr) lp.On("GetBlocksRange", mock.Anything, mock.Anything).Return(tc.blocksRange, tc.blocksRangeErr) blockTime, err := resolver.BlockTime(ctx, tc.blockSampleSize) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/buffer.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer.go similarity index 98% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/buffer.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer.go index b06a3ca809f..81f51311d44 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/buffer.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer.go @@ -7,8 +7,8 @@ import ( "sync" "sync/atomic" - "github.com/smartcontractkit/ocr2keepers/pkg/v3/random" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/random" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/buffer_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_test.go similarity index 99% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/buffer_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_test.go index 5c8908f9be7..046c93a428a 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/buffer_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_test.go @@ -7,12 +7,13 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/require" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) func TestLogEventBuffer_GetBlocksInRange(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/factory.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/factory.go similarity index 98% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/factory.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/factory.go index 38342550c58..2b48fec2b37 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/factory.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/factory.go @@ -8,7 +8,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) // New creates a new log event provider and recoverer. diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/filter.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/filter.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/filter.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/filter.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/filter_store.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/filter_store.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/filter_store.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/filter_store.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/filter_store_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/filter_store_test.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/filter_store_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/filter_store_test.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/filter_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/filter_test.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/filter_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/filter_test.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/integration_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/integration_test.go similarity index 92% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/integration_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/integration_test.go index 506dcb9ea33..caad1adc9ad 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/integration_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/integration_test.go @@ -3,7 +3,6 @@ package logprovider_test import ( "context" "errors" - "fmt" "math/big" "testing" "time" @@ -13,16 +12,15 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" "golang.org/x/time/rate" - "github.com/smartcontractkit/sqlx" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" - - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -33,8 +31,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - kevmcore "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider" + evmregistry21 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) @@ -152,7 +150,7 @@ func TestIntegration_LogEventProvider_UpdateConfig(t *testing.T) { require.Equal(t, 1, len(addrs)) t.Run("update filter config", func(t *testing.T) { - upkeepID := kevmcore.GenUpkeepID(ocr2keepers.LogTrigger, "111") + upkeepID := evmregistry21.GenUpkeepID(ocr2keepers.LogTrigger, "111") id := upkeepID.BigInt() cfg := newPlainLogTriggerConfig(addrs[0]) b, err := ethClient.BlockByHash(ctx, backend.Commit()) @@ -184,7 +182,7 @@ func TestIntegration_LogEventProvider_UpdateConfig(t *testing.T) { }) t.Run("register same log filter", func(t *testing.T) { - upkeepID := kevmcore.GenUpkeepID(ocr2keepers.LogTrigger, "222") + upkeepID := evmregistry21.GenUpkeepID(ocr2keepers.LogTrigger, "222") id := upkeepID.BigInt() cfg := newPlainLogTriggerConfig(addrs[0]) b, err := ethClient.BlockByHash(ctx, backend.Commit()) @@ -317,7 +315,7 @@ func TestIntegration_LogEventProvider_RateLimit(t *testing.T) { var minimumBlockCount int64 = 500 latestBlock, _ := lp.LatestBlock() - assert.GreaterOrEqual(t, latestBlock, minimumBlockCount, "to ensure the integrety of the test, the minimum block count before the test should be %d but got %d", minimumBlockCount, latestBlock) + assert.GreaterOrEqual(t, latestBlock.BlockNumber, minimumBlockCount, "to ensure the integrety of the test, the minimum block count before the test should be %d but got %d", minimumBlockCount, latestBlock) } require.NoError(t, logProvider.ReadLogs(ctx, ids...)) @@ -455,9 +453,7 @@ func TestIntegration_LogEventProvider_RateLimit(t *testing.T) { } func TestIntegration_LogRecoverer_Backfill(t *testing.T) { - t.Skip() // TODO: remove skip after removing constant timeouts - ctx, cancel := context.WithTimeout(testutils.Context(t), time.Second*60) - defer cancel() + ctx := testutils.Context(t) backend, stopMining, accounts := setupBackend(t) defer stopMining() @@ -515,21 +511,21 @@ func TestIntegration_LogRecoverer_Backfill(t *testing.T) { }() defer recoverer.Close() - lctx, lcancel := context.WithTimeout(ctx, time.Second*15) - defer lcancel() var allProposals []ocr2keepers.UpkeepPayload - for lctx.Err() == nil { + for { poll(backend.Commit()) proposals, err := recoverer.GetRecoveryProposals(ctx) require.NoError(t, err) allProposals = append(allProposals, proposals...) - if len(allProposals) < n { - time.Sleep(100 * time.Millisecond) - continue + if len(allProposals) >= n { + break // success + } + select { + case <-ctx.Done(): + t.Fatalf("could not recover logs before timeout: %s", ctx.Err()) + case <-time.After(100 * time.Millisecond): } - break } - require.NoError(t, lctx.Err(), "could not recover logs before timeout") } func collectPayloads(ctx context.Context, t *testing.T, logProvider logprovider.LogEventProvider, n, rounds int) []ocr2keepers.UpkeepPayload { @@ -566,7 +562,7 @@ func waitLogPoller(ctx context.Context, t *testing.T, backend *backends.Simulate for { latestPolled, lberr := lp.LatestBlock(pg.WithParentCtx(ctx)) require.NoError(t, lberr) - if latestPolled >= latestBlock { + if latestPolled.BlockNumber >= latestBlock { break } lp.PollAndSaveLogs(ctx, latestBlock) @@ -617,10 +613,11 @@ func deployUpkeepCounter( backend *backends.SimulatedBackend, account *bind.TransactOpts, logProvider logprovider.LogEventProvider, -) ([]*big.Int, []common.Address, []*log_upkeep_counter_wrapper.LogUpkeepCounter) { - var ids []*big.Int - var contracts []*log_upkeep_counter_wrapper.LogUpkeepCounter - var contractsAddrs []common.Address +) ( + ids []*big.Int, + contractsAddrs []common.Address, + contracts []*log_upkeep_counter_wrapper.LogUpkeepCounter, +) { for i := 0; i < n; i++ { upkeepAddr, _, upkeepContract, err := log_upkeep_counter_wrapper.DeployLogUpkeepCounter( account, backend, @@ -636,7 +633,7 @@ func deployUpkeepCounter( upkeepID := ocr2keepers.UpkeepIdentifier(append(common.LeftPadBytes([]byte{1}, 16), upkeepAddr[:16]...)) id := upkeepID.BigInt() ids = append(ids, id) - b, err := ethClient.BlockByHash(context.Background(), backend.Commit()) + b, err := ethClient.BlockByHash(ctx, backend.Commit()) require.NoError(t, err) bn := b.Number() err = logProvider.RegisterFilter(ctx, logprovider.FilterOptions{ @@ -646,7 +643,7 @@ func deployUpkeepCounter( }) require.NoError(t, err) } - return ids, contractsAddrs, contracts + return } func newPlainLogTriggerConfig(upkeepAddr common.Address) logprovider.LogTriggerConfig { @@ -666,7 +663,7 @@ func setupDependencies(t *testing.T, db *sqlx.DB, backend *backends.SimulatedBac return lp, ethClient } -func setup(lggr logger.Logger, poller logpoller.LogPoller, c client.Client, stateStore kevmcore.UpkeepStateReader, filterStore logprovider.UpkeepFilterStore, opts *logprovider.LogTriggersOptions) (logprovider.LogEventProvider, logprovider.LogRecoverer) { +func setup(lggr logger.Logger, poller logpoller.LogPoller, c client.Client, stateStore evmregistry21.UpkeepStateReader, filterStore logprovider.UpkeepFilterStore, opts *logprovider.LogTriggersOptions) (logprovider.LogEventProvider, logprovider.LogRecoverer) { packer := logprovider.NewLogEventsPacker() if opts == nil { o := logprovider.NewOptions(200) @@ -695,7 +692,7 @@ func setupBackend(t *testing.T) (*backends.SimulatedBackend, func(), []*bind.Tra func ptr[T any](v T) *T { return &v } func setupDB(t *testing.T) *sqlx.DB { - _, db := heavyweight.FullTestDBV2(t, fmt.Sprintf("%s%d", "chainlink_test", 5432), func(c *chainlink.Config, s *chainlink.Secrets) { + _, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Feature.LogPoller = ptr(true) c.OCR.Enabled = ptr(false) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/log_packer.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/log_packer.go similarity index 96% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/log_packer.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/log_packer.go index 655d7dacfe6..5902af73f03 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/log_packer.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/log_packer.go @@ -7,7 +7,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) type LogDataPacker interface { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go similarity index 96% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go index b62fb370847..2dabcc82671 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go @@ -15,12 +15,15 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -81,7 +84,7 @@ var _ LogEventProviderTest = &logEventProvider{} // logEventProvider manages log filters for upkeeps and enables to read the log events. type logEventProvider struct { - utils.StartStopOnce + services.StateMachine threadCtrl utils.ThreadControl lggr logger.Logger @@ -159,11 +162,11 @@ func (p *logEventProvider) GetLatestPayloads(ctx context.Context) ([]ocr2keepers if err != nil { return nil, fmt.Errorf("%w: %s", ErrHeadNotAvailable, err) } - start := latest - p.opts.LookbackBlocks + start := latest.BlockNumber - p.opts.LookbackBlocks if start <= 0 { start = 1 } - logs := p.buffer.dequeueRange(start, latest, AllowedLogsPerUpkeep, MaxPayloads) + logs := p.buffer.dequeueRange(start, latest.BlockNumber, AllowedLogsPerUpkeep, MaxPayloads) // p.lggr.Debugw("got latest logs from buffer", "latest", latest, "diff", diff, "logs", len(logs)) @@ -197,12 +200,12 @@ func (p *logEventProvider) ReadLogs(pctx context.Context, ids ...*big.Int) error if err != nil { return fmt.Errorf("%w: %s", ErrHeadNotAvailable, err) } - if latest == 0 { + if latest.BlockNumber == 0 { return fmt.Errorf("%w: %s", ErrHeadNotAvailable, "latest block is 0") } - filters := p.getFilters(latest, ids...) + filters := p.getFilters(latest.BlockNumber, ids...) - err = p.readLogs(ctx, latest, filters) + err = p.readLogs(ctx, latest.BlockNumber, filters) p.updateFiltersLastPoll(filters) // p.lggr.Debugw("read logs for entries", "latestBlock", latest, "entries", len(entries), "err", err) if err != nil { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider_life_cycle.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_life_cycle.go similarity index 99% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider_life_cycle.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_life_cycle.go index ab816adb1b3..69a4872351d 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider_life_cycle.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_life_cycle.go @@ -128,7 +128,7 @@ func (p *logEventProvider) register(ctx context.Context, lpFilter logpoller.Filt // already registered in DB before, no need to backfill return nil } - backfillBlock := latest - int64(LogBackfillBuffer) + backfillBlock := latest.BlockNumber - int64(LogBackfillBuffer) if backfillBlock < 1 { // New chain, backfill from start backfillBlock = 1 diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider_life_cycle_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_life_cycle_test.go similarity index 92% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider_life_cycle_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_life_cycle_test.go index 4b1ff06f316..278c727a06a 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider_life_cycle_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_life_cycle_test.go @@ -1,20 +1,22 @@ package logprovider import ( - "context" "fmt" "math/big" "testing" "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) func TestLogEventProvider_LifeCycle(t *testing.T) { @@ -102,14 +104,13 @@ func TestLogEventProvider_LifeCycle(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := testutils.Context(t) if tc.mockPoller { lp := new(mocks.LogPoller) lp.On("RegisterFilter", mock.Anything).Return(nil) lp.On("UnregisterFilter", mock.Anything).Return(nil) - lp.On("LatestBlock", mock.Anything).Return(int64(0), nil) + lp.On("LatestBlock", mock.Anything).Return(logpoller.LogPollerBlock{}, nil) hasFitlerTimes := 1 if tc.unregister { hasFitlerTimes = 2 @@ -143,13 +144,12 @@ func TestLogEventProvider_LifeCycle(t *testing.T) { } func TestEventLogProvider_RefreshActiveUpkeeps(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := testutils.Context(t) mp := new(mocks.LogPoller) mp.On("RegisterFilter", mock.Anything).Return(nil) mp.On("UnregisterFilter", mock.Anything).Return(nil) mp.On("HasFilter", mock.Anything).Return(false) - mp.On("LatestBlock", mock.Anything).Return(int64(0), nil) + mp.On("LatestBlock", mock.Anything).Return(logpoller.LogPollerBlock{}, nil) mp.On("ReplayAsync", mock.Anything).Return(nil) p := NewLogProvider(logger.TestLogger(t), mp, &mockedPacker{}, NewUpkeepFilterStore(), NewOptions(200)) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_test.go similarity index 96% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_test.go index db22886cbb7..81774e26387 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/provider_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_test.go @@ -11,14 +11,14 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "golang.org/x/time/rate" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" - - "golang.org/x/time/rate" ) func TestLogEventProvider_GetFilters(t *testing.T) { @@ -174,8 +174,7 @@ func TestLogEventProvider_ScheduleReadJobs(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := testutils.Context(t) readInterval := 10 * time.Millisecond opts := NewOptions(200) @@ -239,8 +238,7 @@ func TestLogEventProvider_ScheduleReadJobs(t *testing.T) { } func TestLogEventProvider_ReadLogs(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := testutils.Context(t) mp := new(mocks.LogPoller) @@ -248,7 +246,7 @@ func TestLogEventProvider_ReadLogs(t *testing.T) { mp.On("ReplayAsync", mock.Anything).Return() mp.On("HasFilter", mock.Anything).Return(false) mp.On("UnregisterFilter", mock.Anything, mock.Anything).Return(nil) - mp.On("LatestBlock", mock.Anything).Return(int64(1), nil) + mp.On("LatestBlock", mock.Anything).Return(logpoller.LogPollerBlock{BlockNumber: int64(1)}, nil) mp.On("LogsWithSigs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]logpoller.Log{ { BlockNumber: 1, diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/recoverer.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/recoverer.go similarity index 97% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/recoverer.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/recoverer.go index 3994f1d8413..c7f6884426f 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/recoverer.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/recoverer.go @@ -15,13 +15,16 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ocr2keepers/pkg/v3/random" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + + "github.com/smartcontractkit/chainlink-automation/pkg/v3/random" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -63,7 +66,7 @@ type visitedRecord struct { } type logRecoverer struct { - utils.StartStopOnce + services.StateMachine threadCtrl utils.ThreadControl lggr logger.Logger @@ -206,7 +209,7 @@ func (r *logRecoverer) getLogTriggerCheckData(ctx context.Context, proposal ocr2 return nil, err } - start, offsetBlock := r.getRecoveryWindow(latest) + start, offsetBlock := r.getRecoveryWindow(latest.BlockNumber) if proposal.Trigger.LogTriggerExtension == nil { return nil, errors.New("missing log trigger extension") } @@ -295,7 +298,7 @@ func (r *logRecoverer) GetRecoveryProposals(ctx context.Context) ([]ocr2keepers. allLogsCounter := 0 logsCount := map[string]int{} - r.sortPending(uint64(latestBlock)) + r.sortPending(uint64(latestBlock.BlockNumber)) var results, pending []ocr2keepers.UpkeepPayload for _, payload := range r.pending { @@ -328,7 +331,7 @@ func (r *logRecoverer) recover(ctx context.Context) error { return fmt.Errorf("%w: %s", ErrHeadNotAvailable, err) } - start, offsetBlock := r.getRecoveryWindow(latest) + start, offsetBlock := r.getRecoveryWindow(latest.BlockNumber) if offsetBlock < 0 { // too soon to recover, we don't have enough blocks return nil @@ -609,7 +612,7 @@ func (r *logRecoverer) tryExpire(ctx context.Context, ids ...string) error { return fmt.Errorf("failed to get states: %w", err) } lggr := r.lggr.With("where", "clean") - start, _ := r.getRecoveryWindow(latestBlock) + start, _ := r.getRecoveryWindow(latestBlock.BlockNumber) r.lock.Lock() defer r.lock.Unlock() var removed int diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/recoverer_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/recoverer_test.go similarity index 97% rename from core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/recoverer_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/recoverer_test.go index 2fdf04f76c7..89b19b4a819 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/recoverer_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/recoverer_test.go @@ -11,28 +11,28 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" lpmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) func TestLogRecoverer_GetRecoverables(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := testutils.Context(t) lp := &lpmocks.LogPoller{} - lp.On("LatestBlock", mock.Anything).Return(int64(100), nil) + lp.On("LatestBlock", mock.Anything).Return(logpoller.LogPollerBlock{BlockNumber: 100}, nil) r := NewLogRecoverer(logger.TestLogger(t), lp, nil, nil, nil, nil, NewOptions(200)) tests := []struct { @@ -182,7 +182,7 @@ func TestLogRecoverer_Clean(t *testing.T) { start, _ := r.getRecoveryWindow(0) block24h := int64(math.Abs(float64(start))) - lp.On("LatestBlock", mock.Anything).Return(block24h+oldLogsOffset, nil) + lp.On("LatestBlock", mock.Anything).Return(logpoller.LogPollerBlock{BlockNumber: block24h + oldLogsOffset}, nil) statesReader.On("SelectByWorkIDs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.states, nil) r.lock.Lock() @@ -213,8 +213,7 @@ func TestLogRecoverer_Clean(t *testing.T) { } func TestLogRecoverer_Recover(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := testutils.Context(t) tests := []struct { name string @@ -423,7 +422,7 @@ func TestLogRecoverer_Recover(t *testing.T) { recoverer, filterStore, lp, statesReader := setupTestRecoverer(t, time.Millisecond*50, lookbackBlocks) filterStore.AddActiveUpkeeps(tc.active...) - lp.On("LatestBlock", mock.Anything).Return(tc.latestBlock, tc.latestBlockErr) + lp.On("LatestBlock", mock.Anything).Return(logpoller.LogPollerBlock{BlockNumber: tc.latestBlock}, tc.latestBlockErr) lp.On("LogsWithSigs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.logs, tc.logsErr) statesReader.On("SelectByWorkIDs", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.states, tc.statesErr) @@ -1079,7 +1078,7 @@ func TestLogRecoverer_GetProposalData(t *testing.T) { recoverer.states = tc.stateReader } - b, err := recoverer.GetProposalData(context.Background(), tc.proposal) + b, err := recoverer.GetProposalData(testutils.Context(t), tc.proposal) if tc.expectErr { assert.Error(t, err) assert.Equal(t, tc.wantErr.Error(), err.Error()) @@ -1206,8 +1205,9 @@ type mockLogPoller struct { func (p *mockLogPoller) LogsWithSigs(start, end int64, eventSigs []common.Hash, address common.Address, qopts ...pg.QOpt) ([]logpoller.Log, error) { return p.LogsWithSigsFn(start, end, eventSigs, address, qopts...) } -func (p *mockLogPoller) LatestBlock(qopts ...pg.QOpt) (int64, error) { - return p.LatestBlockFn(qopts...) +func (p *mockLogPoller) LatestBlock(qopts ...pg.QOpt) (logpoller.LogPollerBlock, error) { + block, err := p.LatestBlockFn(qopts...) + return logpoller.LogPollerBlock{BlockNumber: block}, err } type mockClient struct { diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/mercury.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/mercury.go new file mode 100644 index 00000000000..cf6ebeafc6e --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/mercury.go @@ -0,0 +1,122 @@ +package mercury + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + "math/big" + "net/http" + "time" + + "github.com/patrickmn/go-cache" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" +) + +const ( + FeedIDs = "feedIDs" // valid for v0.3 + FeedIdHex = "feedIdHex" // valid for v0.2 + BlockNumber = "blockNumber" // valid for v0.2 + Timestamp = "timestamp" // valid for v0.3 + totalFastPluginRetries = 5 + totalMediumPluginRetries = 10 +) + +var GenerateHMACFn = func(method string, path string, body []byte, clientId string, secret string, ts int64) string { + bodyHash := sha256.New() + bodyHash.Write(body) + hashString := fmt.Sprintf("%s %s %s %s %d", + method, + path, + hex.EncodeToString(bodyHash.Sum(nil)), + clientId, + ts) + signedMessage := hmac.New(sha256.New, []byte(secret)) + signedMessage.Write([]byte(hashString)) + userHmac := hex.EncodeToString(signedMessage.Sum(nil)) + return userHmac +} + +// CalculateRetryConfig returns plugin retry interval based on how many times plugin has retried this work +var CalculateRetryConfigFn = func(prk string, mercuryConfig MercuryConfigProvider) time.Duration { + var retryInterval time.Duration + var retries int + totalAttempts, ok := mercuryConfig.GetPluginRetry(prk) + if ok { + retries = totalAttempts.(int) + if retries < totalFastPluginRetries { + retryInterval = 1 * time.Second + } else if retries < totalMediumPluginRetries { + retryInterval = 5 * time.Second + } + // if the core node has retried totalMediumPluginRetries times, do not set retry interval and plugin will use + // the default interval + } else { + retryInterval = 1 * time.Second + } + mercuryConfig.SetPluginRetry(prk, retries+1, cache.DefaultExpiration) + return retryInterval +} + +type MercuryData struct { + Index int + Error error + Retryable bool + Bytes [][]byte + State MercuryUpkeepState +} + +type Packer interface { + UnpackCheckCallbackResult(callbackResp []byte) (uint8, bool, []byte, uint8, *big.Int, error) + PackGetUpkeepPrivilegeConfig(upkeepId *big.Int) ([]byte, error) + UnpackGetUpkeepPrivilegeConfig(resp []byte) ([]byte, error) + DecodeStreamsLookupRequest(data []byte) (*StreamsLookupError, error) +} + +type MercuryConfigProvider interface { + Credentials() *models.MercuryCredentials + IsUpkeepAllowed(string) (interface{}, bool) + SetUpkeepAllowed(string, interface{}, time.Duration) + GetPluginRetry(string) (interface{}, bool) + SetPluginRetry(string, interface{}, time.Duration) +} + +type HttpClient interface { + Do(req *http.Request) (*http.Response, error) +} + +type MercuryClient interface { + DoRequest(ctx context.Context, streamsLookup *StreamsLookup, pluginRetryKey string) (MercuryUpkeepState, MercuryUpkeepFailureReason, [][]byte, bool, time.Duration, error) +} + +type StreamsLookupError struct { + FeedParamKey string + Feeds []string + TimeParamKey string + Time *big.Int + ExtraData []byte +} + +type StreamsLookup struct { + *StreamsLookupError + UpkeepId *big.Int + Block uint64 +} + +func (l *StreamsLookup) IsMercuryVersionUnkown() bool { + return l.FeedParamKey != FeedIDs +} + +func (l *StreamsLookup) IsMercuryV02() bool { + return l.FeedParamKey == FeedIdHex && l.TimeParamKey == BlockNumber +} + +func (l *StreamsLookup) IsMercuryV03() bool { + return l.FeedParamKey == FeedIDs +} + +func (l *StreamsLookup) IsMercuryUsingBatchPathV03() bool { + return l.TimeParamKey == BlockNumber +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/mercury_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/mercury_test.go new file mode 100644 index 00000000000..baa939dbecc --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/mercury_test.go @@ -0,0 +1,49 @@ +package mercury + +import ( + "testing" +) + +func TestGenerateHMACFn(t *testing.T) { + testCases := []struct { + name string + method string + path string + body []byte + clientId string + secret string + ts int64 + expected string + }{ + { + name: "generate hmac function", + method: "GET", + path: "/example", + body: []byte(""), + clientId: "yourClientId", + secret: "yourSecret", + ts: 1234567890, + expected: "17b0bb6b14f7b48ef9d24f941ff8f33ad2d5e94ac343380be02c2f1ca32fdbd8", + }, + { + name: "generate hmac function with non-empty body", + method: "POST", + path: "/api", + body: []byte("request body"), + clientId: "anotherClientId", + secret: "anotherSecret", + ts: 1597534567, + expected: "d326c168c50c996e271d6b3b4c97944db01163994090f73fcf4fd42f23f06bbb", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := GenerateHMACFn(tc.method, tc.path, tc.body, tc.clientId, tc.secret, tc.ts) + + if result != tc.expected { + t.Errorf("Expected: %s, Got: %s", tc.expected, result) + } + }) + } +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams.go new file mode 100644 index 00000000000..aec23431921 --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams.go @@ -0,0 +1,330 @@ +package streams + +import ( + "context" + "encoding/json" + "fmt" + "math/big" + "net/http" + "sync" + "time" + + "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/patrickmn/go-cache" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + + iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" + v02 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02" + v03 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +type Lookup interface { + Lookup(ctx context.Context, checkResults []ocr2keepers.CheckResult) []ocr2keepers.CheckResult +} + +type latestBlockProvider interface { + LatestBlock() *ocr2keepers.BlockKey +} + +type streamsRegistry interface { + GetUpkeepPrivilegeConfig(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) + CheckCallback(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) + Address() common.Address +} + +type contextCaller interface { + CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error +} + +type streams struct { + services.StateMachine + packer mercury.Packer + mercuryConfig mercury.MercuryConfigProvider + abi abi.ABI + blockSubscriber latestBlockProvider + registry streamsRegistry + client contextCaller + lggr logger.Logger + threadCtrl utils.ThreadControl + v02Client mercury.MercuryClient + v03Client mercury.MercuryClient +} + +// UpkeepPrivilegeConfig represents the administrative offchain config for each upkeep. It can be set by s_upkeepPrivilegeManager +// role on the registry. Upkeeps allowed to use Mercury server will have this set to true. +type UpkeepPrivilegeConfig struct { + MercuryEnabled bool `json:"mercuryEnabled"` +} + +func NewStreamsLookup( + packer mercury.Packer, + mercuryConfig mercury.MercuryConfigProvider, + blockSubscriber latestBlockProvider, + client contextCaller, + registry streamsRegistry, + lggr logger.Logger) *streams { + httpClient := http.DefaultClient + threadCtrl := utils.NewThreadControl() + return &streams{ + packer: packer, + mercuryConfig: mercuryConfig, + abi: core.RegistryABI, + blockSubscriber: blockSubscriber, + registry: registry, + client: client, + lggr: lggr, + threadCtrl: threadCtrl, + v02Client: v02.NewClient(mercuryConfig, httpClient, threadCtrl, lggr), + v03Client: v03.NewClient(mercuryConfig, httpClient, threadCtrl, lggr), + } +} + +// Lookup looks through check upkeep results looking for any that need off chain lookup +func (s *streams) Lookup(ctx context.Context, checkResults []ocr2keepers.CheckResult) []ocr2keepers.CheckResult { + lookups := map[int]*mercury.StreamsLookup{} + for i, checkResult := range checkResults { + s.buildResult(ctx, i, checkResult, checkResults, lookups) + } + + var wg sync.WaitGroup + for i, lookup := range lookups { + wg.Add(1) + func(i int, lookup *mercury.StreamsLookup) { + s.threadCtrl.Go(func(ctx context.Context) { + s.doLookup(ctx, &wg, lookup, i, checkResults) + }) + }(i, lookup) + } + wg.Wait() + + // don't surface error to plugin bc StreamsLookup process should be self-contained. + return checkResults +} + +// buildResult checks if the upkeep is allowed by Mercury and builds a streams lookup request from the check result +func (s *streams) buildResult(ctx context.Context, i int, checkResult ocr2keepers.CheckResult, checkResults []ocr2keepers.CheckResult, lookups map[int]*mercury.StreamsLookup) { + lookupLggr := s.lggr.With("where", "StreamsLookup") + if checkResult.IneligibilityReason != uint8(mercury.MercuryUpkeepFailureReasonTargetCheckReverted) { + // Streams Lookup only works when upkeep target check reverts + return + } + + block := big.NewInt(int64(checkResult.Trigger.BlockNumber)) + upkeepId := checkResult.UpkeepID + + if s.mercuryConfig.Credentials() == nil { + lookupLggr.Errorf("at block %d upkeep %s tries to access mercury server but mercury credential is not configured", block, upkeepId) + return + } + + // Try to decode the revert error into streams lookup format. User upkeeps can revert with any reason, see if they + // tried to call mercury + lookupLggr.Infof("at block %d upkeep %s trying to DecodeStreamsLookupRequest performData=%s", block, upkeepId, hexutil.Encode(checkResults[i].PerformData)) + streamsLookupErr, err := s.packer.DecodeStreamsLookupRequest(checkResult.PerformData) + if err != nil { + lookupLggr.Debugf("at block %d upkeep %s DecodeStreamsLookupRequest failed: %v", block, upkeepId, err) + // user contract did not revert with StreamsLookup error + return + } + streamsLookupResponse := &mercury.StreamsLookup{StreamsLookupError: streamsLookupErr} + + if len(streamsLookupResponse.Feeds) == 0 { + checkResults[i].IneligibilityReason = uint8(mercury.MercuryUpkeepFailureReasonInvalidRevertDataInput) + lookupLggr.Debugf("at block %s upkeep %s has empty feeds array", block, upkeepId) + return + } + + // mercury permission checking for v0.3 is done by mercury server + if streamsLookupResponse.IsMercuryV02() { + // check permission on the registry for mercury v0.2 + opts := s.buildCallOpts(ctx, block) + if state, reason, retryable, allowed, err := s.allowedToUseMercury(opts, upkeepId.BigInt()); err != nil { + lookupLggr.Warnf("at block %s upkeep %s failed to query mercury allow list: %s", block, upkeepId, err) + checkResults[i].PipelineExecutionState = uint8(state) + checkResults[i].IneligibilityReason = uint8(reason) + checkResults[i].Retryable = retryable + return + } else if !allowed { + lookupLggr.Debugf("at block %d upkeep %s NOT allowed to query Mercury server", block, upkeepId) + checkResults[i].IneligibilityReason = uint8(mercury.MercuryUpkeepFailureReasonMercuryAccessNotAllowed) + return + } + } else if streamsLookupResponse.IsMercuryVersionUnkown() { + // if mercury version cannot be determined, set failure reason + lookupLggr.Debugf("at block %d upkeep %s NOT allowed to query Mercury server", block, upkeepId) + checkResults[i].IneligibilityReason = uint8(mercury.MercuryUpkeepFailureReasonInvalidRevertDataInput) + return + } + + streamsLookupResponse.UpkeepId = upkeepId.BigInt() + // the block here is exclusively used to call checkCallback at this block, not to be confused with the block number + // in the revert for mercury v0.2, which is denoted by time in the struct bc starting from v0.3, only timestamp will be supported + streamsLookupResponse.Block = uint64(block.Int64()) + lookupLggr.Infof("at block %d upkeep %s DecodeStreamsLookupRequest feedKey=%s timeKey=%s feeds=%v time=%s extraData=%s", block, upkeepId, streamsLookupResponse.FeedParamKey, streamsLookupResponse.TimeParamKey, streamsLookupResponse.Feeds, streamsLookupResponse.Time, hexutil.Encode(streamsLookupResponse.ExtraData)) + lookups[i] = streamsLookupResponse +} + +func (s *streams) doLookup(ctx context.Context, wg *sync.WaitGroup, lookup *mercury.StreamsLookup, i int, checkResults []ocr2keepers.CheckResult) { + defer wg.Done() + + state, reason, values, retryable, retryInterval, err := mercury.NoPipelineError, mercury.MercuryUpkeepFailureReasonInvalidRevertDataInput, [][]byte{}, false, 0*time.Second, fmt.Errorf("invalid revert data input: feed param key %s, time param key %s, feeds %s", lookup.FeedParamKey, lookup.TimeParamKey, lookup.Feeds) + pluginRetryKey := generatePluginRetryKey(checkResults[i].WorkID, lookup.Block) + + if lookup.IsMercuryV02() { + state, reason, values, retryable, retryInterval, err = s.v02Client.DoRequest(ctx, lookup, pluginRetryKey) + } else if lookup.IsMercuryV03() { + state, reason, values, retryable, retryInterval, err = s.v03Client.DoRequest(ctx, lookup, pluginRetryKey) + } + + if err != nil { + s.lggr.Errorf("at block %d upkeep %s requested time %s retryable %v retryInterval %s doMercuryRequest: %s", lookup.Block, lookup.UpkeepId, lookup.Time, retryable, retryInterval, err.Error()) + checkResults[i].Retryable = retryable + checkResults[i].RetryInterval = retryInterval + checkResults[i].PipelineExecutionState = uint8(state) + checkResults[i].IneligibilityReason = uint8(reason) + return + } + + for j, v := range values { + s.lggr.Infof("at block %d upkeep %s requested time %s doMercuryRequest values[%d]: %s", lookup.Block, lookup.UpkeepId, lookup.Time, j, hexutil.Encode(v)) + } + + state, retryable, mercuryBytes, err := s.checkCallback(ctx, values, lookup) + if err != nil { + s.lggr.Errorf("at block %d upkeep %s checkCallback err: %s", lookup.Block, lookup.UpkeepId, err.Error()) + checkResults[i].Retryable = retryable + checkResults[i].PipelineExecutionState = uint8(state) + return + } + s.lggr.Infof("at block %d upkeep %s requested time %s checkCallback mercuryBytes: %s", lookup.Block, lookup.UpkeepId, lookup.Time, hexutil.Encode(mercuryBytes)) + + unpackCallBackState, needed, performData, failureReason, _, err := s.packer.UnpackCheckCallbackResult(mercuryBytes) + if err != nil { + s.lggr.Errorf("at block %d upkeep %s requested time %s UnpackCheckCallbackResult err: %s", lookup.Block, lookup.UpkeepId, lookup.Time, err.Error()) + checkResults[i].PipelineExecutionState = unpackCallBackState + return + } + + if failureReason == uint8(mercury.MercuryUpkeepFailureReasonMercuryCallbackReverted) { + checkResults[i].IneligibilityReason = uint8(mercury.MercuryUpkeepFailureReasonMercuryCallbackReverted) + s.lggr.Debugf("at block %d upkeep %s requested time %s mercury callback reverts", lookup.Block, lookup.UpkeepId, lookup.Time) + return + } + + if !needed { + checkResults[i].IneligibilityReason = uint8(mercury.MercuryUpkeepFailureReasonUpkeepNotNeeded) + s.lggr.Debugf("at block %d upkeep %s requested time %s callback reports upkeep not needed", lookup.Block, lookup.UpkeepId, lookup.Time) + return + } + + checkResults[i].IneligibilityReason = uint8(mercury.MercuryUpkeepFailureReasonNone) + checkResults[i].Eligible = true + checkResults[i].PerformData = performData + s.lggr.Infof("at block %d upkeep %s requested time %s successful with perform data: %s", lookup.Block, lookup.UpkeepId, lookup.Time, hexutil.Encode(performData)) +} + +// allowedToUseMercury retrieves upkeep's administrative offchain config and decode a mercuryEnabled bool to indicate if +// this upkeep is allowed to use Mercury service. +func (s *streams) allowedToUseMercury(opts *bind.CallOpts, upkeepId *big.Int) (state mercury.MercuryUpkeepState, reason mercury.MercuryUpkeepFailureReason, retryable bool, allow bool, err error) { + allowed, ok := s.mercuryConfig.IsUpkeepAllowed(upkeepId.String()) + if ok { + return mercury.NoPipelineError, mercury.MercuryUpkeepFailureReasonNone, false, allowed.(bool), nil + } + + payload, err := s.packer.PackGetUpkeepPrivilegeConfig(upkeepId) + if err != nil { + // pack error, no retryable + s.lggr.Warnf("failed to pack getUpkeepPrivilegeConfig data for upkeepId %s: %s", upkeepId, err) + + return mercury.PackUnpackDecodeFailed, mercury.MercuryUpkeepFailureReasonNone, false, false, fmt.Errorf("failed to pack upkeepId: %w", err) + } + + var resultBytes hexutil.Bytes + args := map[string]interface{}{ + "to": s.registry.Address().Hex(), + "data": hexutil.Bytes(payload), + } + + // call checkCallback function at the block which OCR3 has agreed upon + if err = s.client.CallContext(opts.Context, &resultBytes, "eth_call", args, hexutil.EncodeBig(opts.BlockNumber)); err != nil { + return mercury.RpcFlakyFailure, mercury.MercuryUpkeepFailureReasonNone, true, false, fmt.Errorf("failed to get upkeep privilege config: %v", err) + } + + var upkeepPrivilegeConfigBytes []byte + upkeepPrivilegeConfigBytes, err = s.packer.UnpackGetUpkeepPrivilegeConfig(resultBytes) + if err != nil { + return mercury.PackUnpackDecodeFailed, mercury.MercuryUpkeepFailureReasonNone, false, false, fmt.Errorf("failed to get upkeep privilege config: %v", err) + } + + if len(upkeepPrivilegeConfigBytes) == 0 { + s.mercuryConfig.SetUpkeepAllowed(upkeepId.String(), false, cache.DefaultExpiration) + return mercury.NoPipelineError, mercury.MercuryUpkeepFailureReasonMercuryAccessNotAllowed, false, false, fmt.Errorf("upkeep privilege config is empty") + } + + var privilegeConfig UpkeepPrivilegeConfig + if err = json.Unmarshal(upkeepPrivilegeConfigBytes, &privilegeConfig); err != nil { + return mercury.MercuryUnmarshalError, mercury.MercuryUpkeepFailureReasonNone, false, false, fmt.Errorf("failed to unmarshal privilege config: %v", err) + } + + s.mercuryConfig.SetUpkeepAllowed(upkeepId.String(), privilegeConfig.MercuryEnabled, cache.DefaultExpiration) + + return mercury.NoPipelineError, mercury.MercuryUpkeepFailureReasonNone, false, privilegeConfig.MercuryEnabled, nil +} + +func (s *streams) checkCallback(ctx context.Context, values [][]byte, lookup *mercury.StreamsLookup) (mercury.MercuryUpkeepState, bool, hexutil.Bytes, error) { + payload, err := s.abi.Pack("checkCallback", lookup.UpkeepId, values, lookup.ExtraData) + if err != nil { + return mercury.PackUnpackDecodeFailed, false, nil, err + } + + var b hexutil.Bytes + args := map[string]interface{}{ + "to": s.registry.Address().Hex(), + "data": hexutil.Bytes(payload), + } + + // call checkCallback function at the block which OCR3 has agreed upon + if err := s.client.CallContext(ctx, &b, "eth_call", args, hexutil.EncodeUint64(lookup.Block)); err != nil { + return mercury.RpcFlakyFailure, true, nil, err + } + + return mercury.NoPipelineError, false, b, nil +} + +func (s *streams) buildCallOpts(ctx context.Context, block *big.Int) *bind.CallOpts { + opts := bind.CallOpts{ + Context: ctx, + } + + if block == nil || block.Int64() == 0 { + if latestBlock := s.blockSubscriber.LatestBlock(); latestBlock != nil && latestBlock.Number != 0 { + opts.BlockNumber = big.NewInt(int64(latestBlock.Number)) + } + } else { + opts.BlockNumber = block + } + + return &opts +} + +// generatePluginRetryKey returns a plugin retry cache key +func generatePluginRetryKey(workID string, block uint64) string { + return workID + "|" + fmt.Sprintf("%d", block) +} + +func (s *streams) Close() error { + return s.StopOnce("streams_lookup", func() error { + s.threadCtrl.Close() + return nil + }) +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/streams_lookup_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams_test.go similarity index 52% rename from core/services/ocr2/plugins/ocr2keeper/evm21/streams_lookup_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams_test.go index 6f7065ef875..abcc37dca18 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/streams_lookup_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams_test.go @@ -1,8 +1,7 @@ -package evm +package streams import ( "bytes" - "context" "encoding/json" "fmt" "io" @@ -10,70 +9,442 @@ import ( "net/http" "strings" "testing" + "time" - "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/patrickmn/go-cache" "github.com/pkg/errors" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/encoding" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/mocks" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" evmClientMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/streams_lookup_compatible_interface" + iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" + v02 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02" + v03 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03" ) -// setups up an evm registry for tests. -func setupEVMRegistry(t *testing.T) *EvmRegistry { +type MockMercuryConfigProvider struct { + mock.Mock +} + +func (m *MockMercuryConfigProvider) Credentials() *models.MercuryCredentials { + mc := &models.MercuryCredentials{ + LegacyURL: "https://google.old.com", + URL: "https://google.com", + Username: "FakeClientID", + Password: "FakeClientKey", + } + return mc +} + +func (m *MockMercuryConfigProvider) IsUpkeepAllowed(s string) (interface{}, bool) { + args := m.Called(s) + return args.Get(0), args.Bool(1) +} + +func (m *MockMercuryConfigProvider) SetUpkeepAllowed(s string, i interface{}, d time.Duration) { + m.Called(s, i, d) +} + +func (m *MockMercuryConfigProvider) GetPluginRetry(s string) (interface{}, bool) { + args := m.Called(s) + return args.Get(0), args.Bool(1) +} + +func (m *MockMercuryConfigProvider) SetPluginRetry(s string, i interface{}, d time.Duration) { + m.Called(s, i, d) +} + +type MockBlockSubscriber struct { + mock.Mock +} + +func (b *MockBlockSubscriber) LatestBlock() *ocr2keepers.BlockKey { + return nil +} + +type MockHttpClient struct { + mock.Mock +} + +func (mock *MockHttpClient) Do(req *http.Request) (*http.Response, error) { + args := mock.Called(req) + return args.Get(0).(*http.Response), args.Error(1) +} + +type mockRegistry struct { + GetUpkeepPrivilegeConfigFn func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) + CheckCallbackFn func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) +} + +func (r *mockRegistry) Address() common.Address { + return common.HexToAddress("0x6cA639822c6C241Fa9A7A6b5032F6F7F1C513CAD") +} + +func (r *mockRegistry) GetUpkeepPrivilegeConfig(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return r.GetUpkeepPrivilegeConfigFn(opts, upkeepId) +} + +func (r *mockRegistry) CheckCallback(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return r.CheckCallbackFn(opts, id, values, extraData) +} + +// setups up a streams object for tests. +func setupStreams(t *testing.T) *streams { lggr := logger.TestLogger(t) - addr := common.HexToAddress("0x6cA639822c6C241Fa9A7A6b5032F6F7F1C513CAD") - keeperRegistryABI, err := abi.JSON(strings.NewReader(i_keeper_registry_master_wrapper_2_1.IKeeperRegistryMasterABI)) - require.Nil(t, err, "need registry abi") - streamsLookupCompatibleABI, err := abi.JSON(strings.NewReader(streams_lookup_compatible_interface.StreamsLookupCompatibleInterfaceABI)) - require.Nil(t, err, "need mercury abi") - var logPoller logpoller.LogPoller - mockReg := mocks.NewRegistry(t) - mockHttpClient := mocks.NewHttpClient(t) + packer := encoding.NewAbiPacker() + mercuryConfig := new(MockMercuryConfigProvider) + blockSubscriber := new(MockBlockSubscriber) + registry := &mockRegistry{} client := evmClientMocks.NewClient(t) - r := &EvmRegistry{ - lggr: lggr, - poller: logPoller, - addr: addr, - client: client, - logProcessed: make(map[string]bool), - registry: mockReg, - abi: keeperRegistryABI, - active: NewActiveUpkeepList(), - packer: encoding.NewAbiPacker(), - headFunc: func(ocr2keepers.BlockKey) {}, - chLog: make(chan logpoller.Log, 1000), - mercury: &MercuryConfig{ - cred: &models.MercuryCredentials{ - LegacyURL: "https://google.old.com", - URL: "https://google.com", - Username: "FakeClientID", - Password: "FakeClientKey", + streams := NewStreamsLookup( + packer, + mercuryConfig, + blockSubscriber, + client, + registry, + lggr, + ) + return streams +} + +func TestStreams_CheckCallback(t *testing.T) { + upkeepId := big.NewInt(123456789) + bn := uint64(999) + bs := []byte{183, 114, 215, 10, 0, 0, 0, 0, 0, 0} + values := [][]byte{bs} + tests := []struct { + name string + lookup *mercury.StreamsLookup + values [][]byte + statusCode int + + callbackResp []byte + callbackErr error + + upkeepNeeded bool + performData []byte + wantErr assert.ErrorAssertionFunc + + state mercury.MercuryUpkeepState + retryable bool + registry streamsRegistry + }{ + { + name: "success - empty extra data", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"ETD-USD", "BTC-ETH"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(100), + ExtraData: []byte{48, 120, 48, 48}, + }, + UpkeepId: upkeepId, + Block: bn, + }, + values: values, + statusCode: http.StatusOK, + callbackResp: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 48, 120, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + upkeepNeeded: true, + performData: []byte{48, 120, 48, 48}, + wantErr: assert.NoError, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return []byte(`{"mercuryEnabled":true}`), nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{ + UpkeepNeeded: true, + PerformData: []byte{48, 120, 48, 48}, + }, nil + }, + }, + }, + { + name: "success - with extra data", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(18952430), + // this is the address of precompile contract ArbSys(0x0000000000000000000000000000000000000064) + ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, + UpkeepId: upkeepId, + Block: bn, + }, + values: values, + statusCode: http.StatusOK, + callbackResp: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + upkeepNeeded: true, + performData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + wantErr: assert.NoError, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return []byte(`{"mercuryEnabled":true}`), nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{ + UpkeepNeeded: true, + PerformData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, nil + }, + }, + }, + { + name: "failure - bad response", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"ETD-USD", "BTC-ETH"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(100), + ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 48, 120, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + UpkeepId: upkeepId, + Block: bn, + }, + values: values, + statusCode: http.StatusOK, + callbackResp: []byte{}, + callbackErr: errors.New("bad response"), + wantErr: assert.Error, + state: mercury.RpcFlakyFailure, + retryable: true, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return []byte(`{"mercuryEnabled":true}`), nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{}, errors.New("bad response") + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := setupStreams(t) + defer r.Close() + r.registry = tt.registry + + client := new(evmClientMocks.Client) + s := setupStreams(t) + payload, err := s.abi.Pack("checkCallback", tt.lookup.UpkeepId, values, tt.lookup.ExtraData) + require.Nil(t, err) + args := map[string]interface{}{ + "to": s.registry.Address().Hex(), + "data": hexutil.Bytes(payload), + } + client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, hexutil.EncodeUint64(tt.lookup.Block)).Return(tt.callbackErr). + Run(func(args mock.Arguments) { + by := args.Get(1).(*hexutil.Bytes) + *by = tt.callbackResp + }).Once() + s.client = client + + state, retryable, _, err := s.checkCallback(testutils.Context(t), tt.values, tt.lookup) + tt.wantErr(t, err, fmt.Sprintf("Error asserion failed: %v", tt.name)) + assert.Equal(t, tt.state, state) + assert.Equal(t, tt.retryable, retryable) + }) + } +} + +func TestStreams_AllowedToUseMercury(t *testing.T) { + upkeepId, ok := new(big.Int).SetString("71022726777042968814359024671382968091267501884371696415772139504780367423725", 10) + assert.True(t, ok, t.Name()) + tests := []struct { + name string + cached bool + allowed bool + ethCallErr error + err error + state mercury.MercuryUpkeepState + reason mercury.MercuryUpkeepFailureReason + registry streamsRegistry + retryable bool + config []byte + }{ + { + name: "success - allowed via cache", + cached: true, + allowed: true, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return nil, nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{}, nil + }, + }, + }, + { + name: "success - allowed via fetching privilege config", + allowed: true, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return []byte(`{"mercuryEnabled":true}`), nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{}, nil + }, + }, + }, + { + name: "success - not allowed via cache", + cached: true, + allowed: false, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return nil, nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{}, nil + }, + }, + }, + { + name: "success - not allowed via fetching privilege config", + allowed: false, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return []byte(`{"mercuryEnabled":false}`), nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{}, nil + }, + }, + }, + { + name: "failure - cannot unmarshal privilege config", + err: fmt.Errorf("failed to unmarshal privilege config: invalid character '\\x00' looking for beginning of value"), + state: mercury.MercuryUnmarshalError, + config: []byte{0, 1}, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return []byte(`{`), nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{}, nil + }, + }, + }, + { + name: "failure - flaky RPC", + retryable: true, + err: fmt.Errorf("failed to get upkeep privilege config: flaky RPC"), + state: mercury.RpcFlakyFailure, + ethCallErr: fmt.Errorf("flaky RPC"), + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return nil, errors.New("flaky RPC") + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{}, nil + }, + }, + }, + { + name: "failure - empty upkeep privilege config", + err: fmt.Errorf("upkeep privilege config is empty"), + reason: mercury.MercuryUpkeepFailureReasonMercuryAccessNotAllowed, + config: []byte{}, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return []byte(``), nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{}, nil + }, }, - abi: streamsLookupCompatibleABI, - allowListCache: cache.New(defaultAllowListExpiration, allowListCleanupInterval), }, - hc: mockHttpClient, } - return r + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := setupStreams(t) + defer s.Close() + s.registry = tt.registry + + client := new(evmClientMocks.Client) + s.client = client + + mc := new(MockMercuryConfigProvider) + mc.On("IsUpkeepAllowed", mock.Anything).Return(tt.allowed, tt.cached).Once() + mc.On("SetUpkeepAllowed", mock.Anything, mock.Anything, mock.Anything).Return().Once() + s.mercuryConfig = mc + + if !tt.cached { + if tt.err != nil { + bContractCfg, err := s.abi.Methods["getUpkeepPrivilegeConfig"].Outputs.PackValues([]interface{}{tt.config}) + require.Nil(t, err) + + payload, err := s.abi.Pack("getUpkeepPrivilegeConfig", upkeepId) + require.Nil(t, err) + + args := map[string]interface{}{ + "to": s.registry.Address().Hex(), + "data": hexutil.Bytes(payload), + } + + client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, mock.AnythingOfType("string")). + Return(tt.ethCallErr). + Run(func(args mock.Arguments) { + b := args.Get(1).(*hexutil.Bytes) + *b = bContractCfg + }).Once() + } else { + cfg := UpkeepPrivilegeConfig{MercuryEnabled: tt.allowed} + bCfg, err := json.Marshal(cfg) + require.Nil(t, err) + + bContractCfg, err := s.abi.Methods["getUpkeepPrivilegeConfig"].Outputs.PackValues([]interface{}{bCfg}) + require.Nil(t, err) + + payload, err := s.abi.Pack("getUpkeepPrivilegeConfig", upkeepId) + require.Nil(t, err) + + args := map[string]interface{}{ + "to": s.registry.Address().Hex(), + "data": hexutil.Bytes(payload), + } + + client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, mock.AnythingOfType("string")).Return(nil). + Run(func(args mock.Arguments) { + b := args.Get(1).(*hexutil.Bytes) + *b = bContractCfg + }).Once() + } + } + + opts := &bind.CallOpts{ + BlockNumber: big.NewInt(10), + } + + state, reason, retryable, allowed, err := s.allowedToUseMercury(opts, upkeepId) + assert.Equal(t, tt.err, err) + assert.Equal(t, tt.allowed, allowed) + assert.Equal(t, tt.state, state) + assert.Equal(t, tt.reason, reason) + assert.Equal(t, tt.retryable, retryable) + }) + } } -func TestEvmRegistry_StreamsLookup(t *testing.T) { +func TestStreams_StreamsLookup(t *testing.T) { upkeepId, ok := new(big.Int).SetString("71022726777042968814359024671382968091267501884371696415772139504780367423725", 10) var upkeepIdentifier [32]byte copy(upkeepIdentifier[:], upkeepId.Bytes()) @@ -93,6 +464,7 @@ func TestEvmRegistry_StreamsLookup(t *testing.T) { hasError bool hasPermission bool v3 bool + registry streamsRegistry }{ { name: "success - happy path no cache", @@ -103,7 +475,7 @@ func TestEvmRegistry_StreamsLookup(t *testing.T) { Trigger: ocr2keepers.Trigger{ BlockNumber: blockNum, }, - IneligibilityReason: uint8(encoding.UpkeepFailureReasonTargetCheckReverted), + IneligibilityReason: uint8(mercury.MercuryUpkeepFailureReasonTargetCheckReverted), }, }, blobs: map[string]string{ @@ -127,6 +499,18 @@ func TestEvmRegistry_StreamsLookup(t *testing.T) { }, }, hasPermission: true, + v3: false, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return []byte(`{"mercuryEnabled":true}`), nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{ + UpkeepNeeded: true, + PerformData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), + }, nil + }, + }, }, { name: "success - happy path no cache - v0.3", @@ -162,6 +546,17 @@ func TestEvmRegistry_StreamsLookup(t *testing.T) { }, hasPermission: true, v3: true, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return []byte(`{"mercuryEnabled":true}`), nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{ + UpkeepNeeded: true, + PerformData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e000066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000004555638000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000000269ecbb83b000000000000000000000000000000000000000000000000000000269e4a4e14000000000000000000000000000000000000000000000000000000269f4d0edb000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002381e91cffa9502c20de1ddcee350db3f715a5ab449448e3184a5b03c682356c6e2115f20663b3731e373cf33465a96da26f2876debb548f281e62e48f64c374200000000000000000000000000000000000000000000000000000000000000027db99e34135098d4e0bb9ae143ec9cd72fd63150c6d0cc5b38f4aa1aa42408377e8fe8e5ac489c9b7f62ff5aa7b05d2e892e7dee4cac631097247969b3b03fa300000000000000000000000000000000000000000000000000000000000002e00006da4a86c4933dd4a87b21dd2871aea29f706bcde43c70039355ac5b664fb5000000000000000000000000000000000000000000000000000000000454d118000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204254432d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064f0d4a0000000000000000000000000000000000000000000000000000002645f00877a000000000000000000000000000000000000000000000000000002645e1e1010000000000000000000000000000000000000000000000000000002645fe2fee4000000000000000000000000000000000000000000000000000000000243716664b42d20423a47fb13ad3098b49b37f667548e6745fff958b663afe25a845f6100000000000000000000000000000000000000000000000000000000024371660000000000000000000000000000000000000000000000000000000064f0d4a00000000000000000000000000000000000000000000000000000000000000002a0373c0bce7393673f819eb9681cac2773c2d718ce933eb858252195b17a9c832d7b0bee173c02c3c25fb65912b8b13b9302ede8423bab3544cb7a8928d5eb3600000000000000000000000000000000000000000000000000000000000000027d7b79d7646383a5dbf51edf14d53bd3ad0a9f3ca8affab3165e89d3ddce9cb17b58e892fafe4ecb24d2fde07c6a756029e752a5114c33c173df4e7d309adb4d00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000064000000000000000000000000"), + }, nil + }, + }, }, { name: "skip - failure reason is insufficient balance", @@ -187,6 +582,17 @@ func TestEvmRegistry_StreamsLookup(t *testing.T) { }, }, hasError: true, + registry: &mockRegistry{ + GetUpkeepPrivilegeConfigFn: func(opts *bind.CallOpts, upkeepId *big.Int) ([]byte, error) { + return []byte(`{"mercuryEnabled":true}`), nil + }, + CheckCallbackFn: func(opts *bind.CallOpts, id *big.Int, values [][]byte, extraData []byte) (iregistry21.CheckCallback, error) { + return iregistry21.CheckCallback{ + UpkeepNeeded: true, + PerformData: []byte{}, + }, nil + }, + }, }, { name: "skip - invalid revert data", @@ -217,23 +623,31 @@ func TestEvmRegistry_StreamsLookup(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - r := setupEVMRegistry(t) + s := setupStreams(t) + defer s.Close() + s.registry = tt.registry + client := new(evmClientMocks.Client) - r.client = client + s.client = client + + mc := new(MockMercuryConfigProvider) + mc.On("IsUpkeepAllowed", mock.Anything).Return(tt.hasPermission, tt.cachedAdminCfg).Once() + mc.On("SetUpkeepAllowed", mock.Anything, mock.Anything, mock.Anything).Return().Once() + s.mercuryConfig = mc if !tt.cachedAdminCfg && !tt.hasError { cfg := UpkeepPrivilegeConfig{MercuryEnabled: tt.hasPermission} bCfg, err := json.Marshal(cfg) require.Nil(t, err) - bContractCfg, err := r.abi.Methods["getUpkeepPrivilegeConfig"].Outputs.PackValues([]interface{}{bCfg}) + bContractCfg, err := s.abi.Methods["getUpkeepPrivilegeConfig"].Outputs.PackValues([]interface{}{bCfg}) require.Nil(t, err) - payload, err := r.abi.Pack("getUpkeepPrivilegeConfig", upkeepId) + payload, err := s.abi.Pack("getUpkeepPrivilegeConfig", upkeepId) require.Nil(t, err) args := map[string]interface{}{ - "to": r.addr.Hex(), + "to": s.registry.Address().Hex(), "data": hexutil.Bytes(payload), } @@ -246,49 +660,54 @@ func TestEvmRegistry_StreamsLookup(t *testing.T) { if len(tt.blobs) > 0 { if tt.v3 { - hc := mocks.NewHttpClient(t) - mr1 := MercuryV03Response{ - Reports: []MercuryV03Report{{FullReport: tt.blobs["0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"]}, {FullReport: tt.blobs["0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"]}}} + hc03 := new(MockHttpClient) + v03HttpClient := v03.NewClient(s.mercuryConfig, hc03, s.threadCtrl, s.lggr) + s.v03Client = v03HttpClient + + mr1 := v03.MercuryV03Response{ + Reports: []v03.MercuryV03Report{{FullReport: tt.blobs["0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"]}, {FullReport: tt.blobs["0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"]}}} b1, err := json.Marshal(mr1) assert.Nil(t, err) resp1 := &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(b1)), } - hc.On("Do", mock.Anything).Return(resp1, nil).Once() - r.hc = hc + hc03.On("Do", mock.Anything).Return(resp1, nil).Once() } else { - hc := mocks.NewHttpClient(t) - mr1 := MercuryV02Response{ChainlinkBlob: tt.blobs["0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"]} + hc02 := new(MockHttpClient) + v02HttpClient := v02.NewClient(s.mercuryConfig, hc02, s.threadCtrl, s.lggr) + s.v02Client = v02HttpClient + + mr1 := v02.MercuryV02Response{ChainlinkBlob: tt.blobs["0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"]} b1, err := json.Marshal(mr1) assert.Nil(t, err) resp1 := &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(b1)), } - mr2 := MercuryV02Response{ChainlinkBlob: tt.blobs["0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"]} + mr2 := v02.MercuryV02Response{ChainlinkBlob: tt.blobs["0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"]} b2, err := json.Marshal(mr2) assert.Nil(t, err) resp2 := &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(b2)), } - hc.On("Do", mock.MatchedBy(func(req *http.Request) bool { + + hc02.On("Do", mock.MatchedBy(func(req *http.Request) bool { return strings.Contains(req.URL.String(), "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000") })).Return(resp2, nil).Once() - hc.On("Do", mock.MatchedBy(func(req *http.Request) bool { + hc02.On("Do", mock.MatchedBy(func(req *http.Request) bool { return strings.Contains(req.URL.String(), "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000") })).Return(resp1, nil).Once() - r.hc = hc } } if tt.callbackNeeded { - payload, err := r.abi.Pack("checkCallback", upkeepId, tt.values, tt.extraData) + payload, err := s.abi.Pack("checkCallback", upkeepId, tt.values, tt.extraData) require.Nil(t, err) args := map[string]interface{}{ - "to": r.addr.Hex(), + "to": s.registry.Address().Hex(), "data": hexutil.Bytes(payload), } client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, hexutil.EncodeUint64(uint64(blockNum))).Return(nil). @@ -298,933 +717,8 @@ func TestEvmRegistry_StreamsLookup(t *testing.T) { }).Once() } - got := r.streamsLookup(context.Background(), tt.input) + got := s.Lookup(testutils.Context(t), tt.input) assert.Equal(t, tt.expectedResults, got, tt.name) }) } } - -func TestEvmRegistry_AllowedToUseMercury(t *testing.T) { - upkeepId, ok := new(big.Int).SetString("71022726777042968814359024671382968091267501884371696415772139504780367423725", 10) - assert.True(t, ok, t.Name()) - tests := []struct { - name string - cached bool - allowed bool - ethCallErr error - err error - state encoding.PipelineExecutionState - reason encoding.UpkeepFailureReason - retryable bool - config []byte - }{ - { - name: "success - allowed via cache", - cached: true, - allowed: true, - }, - { - name: "success - allowed via fetching privilege config", - allowed: true, - }, - { - name: "success - not allowed via cache", - cached: true, - allowed: false, - }, - { - name: "success - not allowed via fetching privilege config", - allowed: false, - }, - { - name: "failure - cannot unmarshal privilege config", - err: fmt.Errorf("failed to unmarshal privilege config: invalid character '\\x00' looking for beginning of value"), - state: encoding.MercuryUnmarshalError, - config: []byte{0, 1}, - }, - { - name: "failure - flaky RPC", - retryable: true, - err: fmt.Errorf("failed to get upkeep privilege config: flaky RPC"), - state: encoding.RpcFlakyFailure, - ethCallErr: fmt.Errorf("flaky RPC"), - }, - { - name: "failure - empty upkeep privilege config", - err: fmt.Errorf("upkeep privilege config is empty"), - reason: encoding.UpkeepFailureReasonMercuryAccessNotAllowed, - config: []byte{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := setupEVMRegistry(t) - - client := new(evmClientMocks.Client) - r.client = client - - if tt.cached { - r.mercury.allowListCache.Set(upkeepId.String(), tt.allowed, cache.DefaultExpiration) - } else { - if tt.err != nil { - bContractCfg, err := r.abi.Methods["getUpkeepPrivilegeConfig"].Outputs.PackValues([]interface{}{tt.config}) - require.Nil(t, err) - - payload, err := r.abi.Pack("getUpkeepPrivilegeConfig", upkeepId) - require.Nil(t, err) - - args := map[string]interface{}{ - "to": r.addr.Hex(), - "data": hexutil.Bytes(payload), - } - - client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, mock.AnythingOfType("string")). - Return(tt.ethCallErr). - Run(func(args mock.Arguments) { - b := args.Get(1).(*hexutil.Bytes) - *b = bContractCfg - }).Once() - } else { - cfg := UpkeepPrivilegeConfig{MercuryEnabled: tt.allowed} - bCfg, err := json.Marshal(cfg) - require.Nil(t, err) - - bContractCfg, err := r.abi.Methods["getUpkeepPrivilegeConfig"].Outputs.PackValues([]interface{}{bCfg}) - require.Nil(t, err) - - payload, err := r.abi.Pack("getUpkeepPrivilegeConfig", upkeepId) - require.Nil(t, err) - - args := map[string]interface{}{ - "to": r.addr.Hex(), - "data": hexutil.Bytes(payload), - } - - client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, mock.AnythingOfType("string")).Return(nil). - Run(func(args mock.Arguments) { - b := args.Get(1).(*hexutil.Bytes) - *b = bContractCfg - }).Once() - } - } - - opts := &bind.CallOpts{ - BlockNumber: big.NewInt(10), - } - - state, reason, retryable, allowed, err := r.allowedToUseMercury(opts, upkeepId) - assert.Equal(t, tt.err, err) - assert.Equal(t, tt.allowed, allowed) - assert.Equal(t, tt.state, state) - assert.Equal(t, tt.reason, reason) - assert.Equal(t, tt.retryable, retryable) - }) - } -} - -func TestEvmRegistry_DoMercuryRequestV02(t *testing.T) { - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - - tests := []struct { - name string - lookup *StreamsLookup - mockHttpStatusCode int - mockChainlinkBlobs []string - expectedValues [][]byte - expectedRetryable bool - expectedError error - state encoding.PipelineExecutionState - reason encoding.UpkeepFailureReason - }{ - { - name: "success", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - upkeepId: upkeepId, - }, - mockHttpStatusCode: http.StatusOK, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte{{0, 6, 109, 252, 209, 237, 45, 149, 177, 140, 148, 141, 188, 91, 214, 76, 104, 122, 254, 147, 228, 202, 125, 102, 61, 222, 193, 76, 32, 9, 10, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 128, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 69, 84, 72, 45, 85, 83, 68, 45, 65, 82, 66, 73, 84, 82, 85, 77, 45, 84, 69, 83, 84, 78, 69, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 216, 211, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 207, 11, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 155, 61, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 206, 116, 217, 250, 37, 42, 137, 131, 151, 110, 171, 96, 13, 199, 89, 12, 119, 141, 4, 129, 52, 48, 132, 27, 198, 231, 101, 195, 76, 216, 26, 22, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 96, 65, 43, 148, 229, 37, 202, 108, 237, 201, 245, 68, 253, 134, 247, 118, 6, 213, 47, 231, 49, 165, 208, 105, 219, 232, 54, 168, 191, 192, 251, 140, 145, 25, 99, 176, 174, 122, 20, 151, 31, 59, 70, 33, 191, 251, 128, 46, 240, 96, 83, 146, 185, 166, 200, 156, 127, 171, 29, 248, 99, 58, 90, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 69, 0, 194, 245, 33, 248, 63, 186, 94, 252, 43, 243, 239, 250, 174, 221, 228, 61, 10, 74, 223, 247, 133, 193, 33, 59, 113, 42, 58, 237, 13, 129, 87, 100, 42, 132, 50, 77, 176, 207, 150, 149, 235, 210, 119, 8, 212, 96, 142, 176, 51, 126, 13, 216, 123, 14, 67, 240, 250, 112, 199, 0, 217, 17}}, - expectedRetryable: false, - expectedError: nil, - }, - { - name: "failure - retryable", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - upkeepId: upkeepId, - }, - mockHttpStatusCode: http.StatusInternalServerError, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte{nil}, - expectedRetryable: true, - expectedError: errors.New("failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 500\n#2: 500\n#3: 500"), - state: encoding.MercuryFlakyFailure, - }, - { - name: "failure - not retryable", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - upkeepId: upkeepId, - }, - mockHttpStatusCode: http.StatusBadGateway, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte{nil}, - expectedRetryable: false, - expectedError: errors.New("failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: at block 25880526 upkeep 88786950015966611018675766524283132478093844178961698330929478019253453382042 received status code 502 for feed 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"), - state: encoding.InvalidMercuryRequest, - }, - { - name: "failure - no feeds", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{}, - TimeParamKey: blockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - upkeepId: upkeepId, - }, - expectedValues: [][]byte{}, - reason: encoding.UpkeepFailureReasonInvalidRevertDataInput, - }, - { - name: "failure - invalid revert data", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{}, - TimeParamKey: blockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - upkeepId: upkeepId, - }, - expectedValues: [][]byte{}, - reason: encoding.UpkeepFailureReasonInvalidRevertDataInput, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := setupEVMRegistry(t) - hc := mocks.NewHttpClient(t) - - for _, blob := range tt.mockChainlinkBlobs { - mr := MercuryV02Response{ChainlinkBlob: blob} - b, err := json.Marshal(mr) - assert.Nil(t, err) - - resp := &http.Response{ - StatusCode: tt.mockHttpStatusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - if tt.expectedError != nil && tt.expectedRetryable { - hc.On("Do", mock.Anything).Return(resp, nil).Times(totalAttempt) - } else { - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - } - r.hc = hc - - state, reason, values, retryable, reqErr := r.doMercuryRequest(context.Background(), tt.lookup, r.lggr) - assert.Equal(t, tt.expectedValues, values) - assert.Equal(t, tt.expectedRetryable, retryable) - assert.Equal(t, tt.state, state) - assert.Equal(t, tt.reason, reason) - if tt.expectedError != nil { - assert.Equal(t, tt.expectedError.Error(), reqErr.Error()) - } - }) - } -} - -func TestEvmRegistry_DoMercuryRequestV03(t *testing.T) { - upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) - - tests := []struct { - name string - lookup *StreamsLookup - mockHttpStatusCode int - mockChainlinkBlobs []string - expectedValues [][]byte - expectedRetryable bool - expectedError error - state encoding.PipelineExecutionState - reason encoding.UpkeepFailureReason - }{ - { - name: "success v0.3", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(25880526), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - upkeepId: upkeepId, - }, - mockHttpStatusCode: http.StatusOK, - mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, - expectedValues: [][]byte{{0, 6, 109, 252, 209, 237, 45, 149, 177, 140, 148, 141, 188, 91, 214, 76, 104, 122, 254, 147, 228, 202, 125, 102, 61, 222, 193, 76, 32, 9, 10, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 128, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 69, 84, 72, 45, 85, 83, 68, 45, 65, 82, 66, 73, 84, 82, 85, 77, 45, 84, 69, 83, 84, 78, 69, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 216, 211, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 207, 11, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 155, 61, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 206, 116, 217, 250, 37, 42, 137, 131, 151, 110, 171, 96, 13, 199, 89, 12, 119, 141, 4, 129, 52, 48, 132, 27, 198, 231, 101, 195, 76, 216, 26, 22, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 96, 65, 43, 148, 229, 37, 202, 108, 237, 201, 245, 68, 253, 134, 247, 118, 6, 213, 47, 231, 49, 165, 208, 105, 219, 232, 54, 168, 191, 192, 251, 140, 145, 25, 99, 176, 174, 122, 20, 151, 31, 59, 70, 33, 191, 251, 128, 46, 240, 96, 83, 146, 185, 166, 200, 156, 127, 171, 29, 248, 99, 58, 90, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 69, 0, 194, 245, 33, 248, 63, 186, 94, 252, 43, 243, 239, 250, 174, 221, 228, 61, 10, 74, 223, 247, 133, 193, 33, 59, 113, 42, 58, 237, 13, 129, 87, 100, 42, 132, 50, 77, 176, 207, 150, 149, 235, 210, 119, 8, 212, 96, 142, 176, 51, 126, 13, 216, 123, 14, 67, 240, 250, 112, 199, 0, 217, 17}}, - expectedRetryable: false, - expectedError: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := setupEVMRegistry(t) - hc := mocks.NewHttpClient(t) - - mr := MercuryV03Response{} - for i, blob := range tt.mockChainlinkBlobs { - r := MercuryV03Report{ - FeedID: tt.lookup.Feeds[i], - ValidFromTimestamp: 0, - ObservationsTimestamp: 0, - FullReport: blob, - } - mr.Reports = append(mr.Reports, r) - } - - b, err := json.Marshal(mr) - assert.Nil(t, err) - resp := &http.Response{ - StatusCode: tt.mockHttpStatusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - if tt.expectedError != nil && tt.expectedRetryable { - hc.On("Do", mock.Anything).Return(resp, nil).Times(totalAttempt) - } else { - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - r.hc = hc - - state, reason, values, retryable, reqErr := r.doMercuryRequest(context.Background(), tt.lookup, r.lggr) - assert.Equal(t, tt.expectedValues, values) - assert.Equal(t, tt.expectedRetryable, retryable) - assert.Equal(t, tt.state, state) - assert.Equal(t, tt.reason, reason) - if tt.expectedError != nil { - assert.Equal(t, tt.expectedError.Error(), reqErr.Error()) - } - }) - } -} - -func TestEvmRegistry_SingleFeedRequest(t *testing.T) { - upkeepId := big.NewInt(123456789) - tests := []struct { - name string - index int - lookup *StreamsLookup - blob string - statusCode int - lastStatusCode int - retryNumber int - retryable bool - errorMessage string - }{ - { - name: "success - mercury responds in the first try", - index: 0, - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - blob: "0xab2123dc00000012", - }, - { - name: "success - retry for 404", - index: 0, - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - blob: "0xab2123dcbabbad", - retryNumber: 1, - statusCode: http.StatusNotFound, - lastStatusCode: http.StatusOK, - }, - { - name: "success - retry for 500", - index: 0, - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - blob: "0xab2123dcbbabad", - retryNumber: 2, - statusCode: http.StatusInternalServerError, - lastStatusCode: http.StatusOK, - }, - { - name: "failure - returns retryable", - index: 0, - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - blob: "0xab2123dc", - retryNumber: totalAttempt, - statusCode: http.StatusNotFound, - retryable: true, - errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 404\n#2: 404\n#3: 404", - }, - { - name: "failure - returns retryable and then non-retryable", - index: 0, - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - blob: "0xab2123dc", - retryNumber: 1, - statusCode: http.StatusNotFound, - lastStatusCode: http.StatusBadGateway, - errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 404\n#2: at block 123456 upkeep 123456789 received status code 502 for feed 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - }, - { - name: "failure - returns not retryable", - index: 0, - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - blob: "0xab2123dc", - statusCode: http.StatusBadGateway, - errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: at block 123456 upkeep 123456789 received status code 502 for feed 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := setupEVMRegistry(t) - hc := mocks.NewHttpClient(t) - - mr := MercuryV02Response{ChainlinkBlob: tt.blob} - b, err := json.Marshal(mr) - assert.Nil(t, err) - - if tt.retryNumber == 0 { - if tt.errorMessage != "" { - resp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } else { - resp := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - } else if tt.retryNumber > 0 && tt.retryNumber < totalAttempt { - retryResp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(retryResp, nil).Times(tt.retryNumber) - - resp := &http.Response{ - StatusCode: tt.lastStatusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } else { - resp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Times(tt.retryNumber) - } - r.hc = hc - - ch := make(chan MercuryData, 1) - r.singleFeedRequest(context.Background(), ch, tt.index, tt.lookup, r.lggr) - - m := <-ch - assert.Equal(t, tt.index, m.Index) - assert.Equal(t, tt.retryable, m.Retryable) - if tt.retryNumber >= totalAttempt || tt.errorMessage != "" { - assert.Equal(t, tt.errorMessage, m.Error.Error()) - assert.Equal(t, [][]byte{}, m.Bytes) - } else { - blobBytes, err := hexutil.Decode(tt.blob) - assert.Nil(t, err) - assert.Nil(t, m.Error) - assert.Equal(t, [][]byte{blobBytes}, m.Bytes) - } - }) - } -} - -func TestEvmRegistry_MultiFeedRequest(t *testing.T) { - upkeepId := big.NewInt(123456789) - tests := []struct { - name string - lookup *StreamsLookup - statusCode int - lastStatusCode int - retryNumber int - retryable bool - errorMessage string - firstResponse *MercuryV03Response - response *MercuryV03Response - }{ - { - name: "success - mercury responds in the first try", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: timestamp, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000016", - }, - }, - }, - statusCode: http.StatusOK, - }, - { - name: "success - mercury responds in the first try with blocknumber", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000016", - }, - }, - }, - statusCode: http.StatusOK, - }, - { - name: "success - retry for 500", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: timestamp, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - retryNumber: 2, - statusCode: http.StatusInternalServerError, - lastStatusCode: http.StatusOK, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000019", - }, - }, - }, - }, - { - name: "failure - fail to decode reportBlob", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: timestamp, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "qerwiu", // invalid hex blob - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000016", - }, - }, - }, - statusCode: http.StatusOK, - retryable: false, - errorMessage: "All attempts fail:\n#1: hex string without 0x prefix", - }, - { - name: "failure - returns retryable", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: timestamp, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - retryNumber: totalAttempt, - statusCode: http.StatusInternalServerError, - retryable: true, - errorMessage: "All attempts fail:\n#1: 500\n#2: 500\n#3: 500", - }, - { - name: "failure - returns retryable and then non-retryable", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: timestamp, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - retryNumber: 1, - statusCode: http.StatusInternalServerError, - lastStatusCode: http.StatusUnauthorized, - errorMessage: "All attempts fail:\n#1: 500\n#2: at timestamp 123456 upkeep 123456789 received status code 401 from mercury v0.3, most likely this is caused by unauthorized upkeep", - }, - { - name: "failure - returns status code 420 not retryable", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: timestamp, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - statusCode: 420, - errorMessage: "All attempts fail:\n#1: at timestamp 123456 upkeep 123456789 received status code 420 from mercury v0.3, most likely this is caused by missing/malformed query args, missing or bad required headers, non-existent feeds, or no permissions for feeds", - }, - { - name: "failure - returns status code 502 not retryable", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: timestamp, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - statusCode: http.StatusBadGateway, - errorMessage: "All attempts fail:\n#1: at timestamp 123456 upkeep 123456789 received status code 502 from mercury v0.3", - }, - { - name: "success - retry when reports length does not match feeds length", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIDs, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: timestamp, - Time: big.NewInt(123456), - }, - upkeepId: upkeepId, - }, - firstResponse: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - }, - }, - response: &MercuryV03Response{ - Reports: []MercuryV03Report{ - { - FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123456, - ObservationsTimestamp: 123456, - FullReport: "0xab2123dc00000012", - }, - { - FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", - ValidFromTimestamp: 123458, - ObservationsTimestamp: 123458, - FullReport: "0xab2123dc00000019", - }, - }, - }, - retryNumber: 1, - statusCode: http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := setupEVMRegistry(t) - hc := mocks.NewHttpClient(t) - b, err := json.Marshal(tt.response) - assert.Nil(t, err) - - if tt.retryNumber == 0 { - resp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } else if tt.retryNumber < totalAttempt { - if tt.firstResponse != nil && tt.response != nil { - b0, err := json.Marshal(tt.firstResponse) - assert.Nil(t, err) - resp0 := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b0)), - } - b1, err := json.Marshal(tt.response) - assert.Nil(t, err) - resp1 := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b1)), - } - hc.On("Do", mock.Anything).Return(resp0, nil).Once().On("Do", mock.Anything).Return(resp1, nil).Once() - } else { - retryResp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(retryResp, nil).Times(tt.retryNumber) - - resp := &http.Response{ - StatusCode: tt.lastStatusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Once() - } - } else { - resp := &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(bytes.NewReader(b)), - } - hc.On("Do", mock.Anything).Return(resp, nil).Times(tt.retryNumber) - } - r.hc = hc - - ch := make(chan MercuryData, 1) - r.multiFeedsRequest(context.Background(), ch, tt.lookup, r.lggr) - - m := <-ch - assert.Equal(t, 0, m.Index) - assert.Equal(t, tt.retryable, m.Retryable) - if tt.retryNumber >= totalAttempt || tt.errorMessage != "" { - assert.Equal(t, tt.errorMessage, m.Error.Error()) - assert.Equal(t, [][]byte{}, m.Bytes) - } else { - assert.Nil(t, m.Error) - var reports [][]byte - for _, rsp := range tt.response.Reports { - b, _ := hexutil.Decode(rsp.FullReport) - reports = append(reports, b) - } - assert.Equal(t, reports, m.Bytes) - } - }) - } -} - -func TestEvmRegistry_CheckCallback(t *testing.T) { - upkeepId := big.NewInt(123456789) - bn := uint64(999) - bs := []byte{183, 114, 215, 10, 0, 0, 0, 0, 0, 0} - values := [][]byte{bs} - tests := []struct { - name string - lookup *StreamsLookup - values [][]byte - statusCode int - - callbackResp []byte - callbackErr error - - upkeepNeeded bool - performData []byte - wantErr assert.ErrorAssertionFunc - - state encoding.PipelineExecutionState - retryable bool - }{ - { - name: "success - empty extra data", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"ETD-USD", "BTC-ETH"}, - TimeParamKey: blockNumber, - Time: big.NewInt(100), - ExtraData: []byte{48, 120, 48, 48}, - }, - upkeepId: upkeepId, - block: bn, - }, - values: values, - statusCode: http.StatusOK, - callbackResp: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 48, 120, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - upkeepNeeded: true, - performData: []byte{48, 120, 48, 48}, - wantErr: assert.NoError, - }, - { - name: "success - with extra data", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, - TimeParamKey: blockNumber, - Time: big.NewInt(18952430), - // this is the address of precompile contract ArbSys(0x0000000000000000000000000000000000000064) - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - }, - upkeepId: upkeepId, - block: bn, - }, - values: values, - statusCode: http.StatusOK, - callbackResp: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - upkeepNeeded: true, - performData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, - wantErr: assert.NoError, - }, - { - name: "failure - bad response", - lookup: &StreamsLookup{ - StreamsLookupError: &encoding.StreamsLookupError{ - FeedParamKey: feedIdHex, - Feeds: []string{"ETD-USD", "BTC-ETH"}, - TimeParamKey: blockNumber, - Time: big.NewInt(100), - ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 48, 120, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - upkeepId: upkeepId, - block: bn, - }, - values: values, - statusCode: http.StatusOK, - callbackResp: []byte{}, - callbackErr: errors.New("bad response"), - wantErr: assert.Error, - state: encoding.RpcFlakyFailure, - retryable: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client := new(evmClientMocks.Client) - r := setupEVMRegistry(t) - payload, err := r.abi.Pack("checkCallback", tt.lookup.upkeepId, values, tt.lookup.ExtraData) - require.Nil(t, err) - args := map[string]interface{}{ - "to": r.addr.Hex(), - "data": hexutil.Bytes(payload), - } - client.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), "eth_call", args, hexutil.EncodeUint64(tt.lookup.block)).Return(tt.callbackErr). - Run(func(args mock.Arguments) { - by := args.Get(1).(*hexutil.Bytes) - *by = tt.callbackResp - }).Once() - r.client = client - - state, retryable, _, err := r.checkCallback(context.Background(), tt.values, tt.lookup) - tt.wantErr(t, err, fmt.Sprintf("Error asserion failed: %v", tt.name)) - assert.Equal(t, tt.state, state) - assert.Equal(t, tt.retryable, retryable) - }) - } -} diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/upkeep_failure_reasons.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/upkeep_failure_reasons.go new file mode 100644 index 00000000000..66f8bca402f --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/upkeep_failure_reasons.go @@ -0,0 +1,17 @@ +package mercury + +type MercuryUpkeepFailureReason uint8 + +// NOTE: This enum should be kept in sync with evmregistry/v21/encoding/interface.go +// TODO (AUTO-7928) Remove this duplication +const ( + // upkeep failure onchain reasons + MercuryUpkeepFailureReasonNone MercuryUpkeepFailureReason = 0 + MercuryUpkeepFailureReasonTargetCheckReverted MercuryUpkeepFailureReason = 3 + MercuryUpkeepFailureReasonUpkeepNotNeeded MercuryUpkeepFailureReason = 4 + MercuryUpkeepFailureReasonMercuryCallbackReverted MercuryUpkeepFailureReason = 7 + // leaving a gap here for more onchain failure reasons in the future + // upkeep failure offchain reasons + MercuryUpkeepFailureReasonMercuryAccessNotAllowed MercuryUpkeepFailureReason = 32 + MercuryUpkeepFailureReasonInvalidRevertDataInput MercuryUpkeepFailureReason = 34 +) diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/upkeep_states.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/upkeep_states.go new file mode 100644 index 00000000000..0d8276ff2de --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/upkeep_states.go @@ -0,0 +1,16 @@ +package mercury + +type MercuryUpkeepState uint8 + +// NOTE: This enum should be kept in sync with evmregistry/v21/encoding/interface.go +// TODO (AUTO-7928) Remove this duplication +const ( + NoPipelineError MercuryUpkeepState = 0 + RpcFlakyFailure MercuryUpkeepState = 3 + MercuryFlakyFailure MercuryUpkeepState = 4 + PackUnpackDecodeFailed MercuryUpkeepState = 5 + MercuryUnmarshalError MercuryUpkeepState = 6 + InvalidMercuryRequest MercuryUpkeepState = 7 + InvalidMercuryResponse MercuryUpkeepState = 8 // this will only happen if Mercury server sends bad responses + UpkeepNotAuthorized MercuryUpkeepState = 9 +) diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02/request.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02/request.go new file mode 100644 index 00000000000..8135617b6fd --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02/request.go @@ -0,0 +1,205 @@ +package v02 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/avast/retry-go/v4" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +const ( + mercuryPathV02 = "/client?" // only used to access mercury v0.2 server + retryDelay = 500 * time.Millisecond + totalAttempt = 3 + contentTypeHeader = "Content-Type" + authorizationHeader = "Authorization" + timestampHeader = "X-Authorization-Timestamp" + signatureHeader = "X-Authorization-Signature-SHA256" +) + +type MercuryV02Response struct { + ChainlinkBlob string `json:"chainlinkBlob"` +} + +type client struct { + services.StateMachine + mercuryConfig mercury.MercuryConfigProvider + httpClient mercury.HttpClient + threadCtrl utils.ThreadControl + lggr logger.Logger +} + +func NewClient(mercuryConfig mercury.MercuryConfigProvider, httpClient mercury.HttpClient, threadCtrl utils.ThreadControl, lggr logger.Logger) *client { + return &client{ + mercuryConfig: mercuryConfig, + httpClient: httpClient, + threadCtrl: threadCtrl, + lggr: lggr, + } +} + +func (c *client) DoRequest(ctx context.Context, streamsLookup *mercury.StreamsLookup, pluginRetryKey string) (mercury.MercuryUpkeepState, mercury.MercuryUpkeepFailureReason, [][]byte, bool, time.Duration, error) { + resultLen := len(streamsLookup.Feeds) + ch := make(chan mercury.MercuryData, resultLen) + if len(streamsLookup.Feeds) == 0 { + return mercury.NoPipelineError, mercury.MercuryUpkeepFailureReasonInvalidRevertDataInput, [][]byte{}, false, 0 * time.Second, fmt.Errorf("invalid revert data input: feed param key %s, time param key %s, feeds %s", streamsLookup.FeedParamKey, streamsLookup.TimeParamKey, streamsLookup.Feeds) + } + for i := range streamsLookup.Feeds { + // TODO (AUTO-7209): limit the number of concurrent requests + i := i + c.threadCtrl.Go(func(ctx context.Context) { + c.singleFeedRequest(ctx, ch, i, streamsLookup) + }) + } + + var reqErr error + var retryInterval time.Duration + results := make([][]byte, len(streamsLookup.Feeds)) + retryable := true + allSuccess := true + // in v0.2, use the last execution error as the state, if no execution errors, state will be no error + state := mercury.NoPipelineError + for i := 0; i < resultLen; i++ { + m := <-ch + if m.Error != nil { + reqErr = errors.Join(reqErr, m.Error) + retryable = retryable && m.Retryable + allSuccess = false + if m.State != mercury.NoPipelineError { + state = mercury.MercuryUpkeepState(m.State) + } + continue + } + results[m.Index] = m.Bytes[0] + } + if retryable && !allSuccess { + retryInterval = mercury.CalculateRetryConfigFn(pluginRetryKey, c.mercuryConfig) + } + // only retry when not all successful AND none are not retryable + return state, mercury.MercuryUpkeepFailureReasonNone, results, retryable && !allSuccess, retryInterval, reqErr +} + +func (c *client) singleFeedRequest(ctx context.Context, ch chan<- mercury.MercuryData, index int, sl *mercury.StreamsLookup) { + var httpRequest *http.Request + var err error + + q := url.Values{ + sl.FeedParamKey: {sl.Feeds[index]}, + sl.TimeParamKey: {sl.Time.String()}, + } + mercuryURL := c.mercuryConfig.Credentials().LegacyURL + reqUrl := fmt.Sprintf("%s%s%s", mercuryURL, mercuryPathV02, q.Encode()) + c.lggr.Debugf("request URL for upkeep %s feed %s: %s", sl.UpkeepId.String(), sl.Feeds[index], reqUrl) + + httpRequest, err = http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, nil) + if err != nil { + ch <- mercury.MercuryData{Index: index, Error: err, Retryable: false, State: mercury.InvalidMercuryRequest} + return + } + + ts := time.Now().UTC().UnixMilli() + signature := mercury.GenerateHMACFn(http.MethodGet, mercuryPathV02+q.Encode(), []byte{}, c.mercuryConfig.Credentials().Username, c.mercuryConfig.Credentials().Password, ts) + httpRequest.Header.Set(contentTypeHeader, "application/json") + httpRequest.Header.Set(authorizationHeader, c.mercuryConfig.Credentials().Username) + httpRequest.Header.Set(timestampHeader, strconv.FormatInt(ts, 10)) + httpRequest.Header.Set(signatureHeader, signature) + + // in the case of multiple retries here, use the last attempt's data + state := mercury.NoPipelineError + retryable := false + sent := false + retryErr := retry.Do( + func() error { + var httpResponse *http.Response + var responseBody []byte + var blobBytes []byte + + retryable = false + if httpResponse, err = c.httpClient.Do(httpRequest); err != nil { + c.lggr.Warnf("at block %s upkeep %s GET request fails for feed %s: %v", sl.Time.String(), sl.UpkeepId.String(), sl.Feeds[index], err) + retryable = true + state = mercury.MercuryFlakyFailure + return err + } + defer httpResponse.Body.Close() + + if responseBody, err = io.ReadAll(httpResponse.Body); err != nil { + state = mercury.InvalidMercuryResponse + return err + } + + switch httpResponse.StatusCode { + case http.StatusNotFound, http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout: + c.lggr.Warnf("at block %s upkeep %s received status code %d for feed %s", sl.Time.String(), sl.UpkeepId.String(), httpResponse.StatusCode, sl.Feeds[index]) + retryable = true + state = mercury.MercuryFlakyFailure + return errors.New(strconv.FormatInt(int64(httpResponse.StatusCode), 10)) + case http.StatusOK: + // continue + default: + state = mercury.InvalidMercuryRequest + return fmt.Errorf("at block %s upkeep %s received status code %d for feed %s", sl.Time.String(), sl.UpkeepId.String(), httpResponse.StatusCode, sl.Feeds[index]) + } + + c.lggr.Debugf("at block %s upkeep %s received status code %d from mercury v0.2 with BODY=%s", sl.Time.String(), sl.UpkeepId.String(), httpResponse.StatusCode, hexutil.Encode(responseBody)) + + var m MercuryV02Response + if err = json.Unmarshal(responseBody, &m); err != nil { + c.lggr.Warnf("at block %s upkeep %s failed to unmarshal body to MercuryV02Response for feed %s: %v", sl.Time.String(), sl.UpkeepId.String(), sl.Feeds[index], err) + state = mercury.MercuryUnmarshalError + return err + } + if blobBytes, err = hexutil.Decode(m.ChainlinkBlob); err != nil { + c.lggr.Warnf("at block %s upkeep %s failed to decode chainlinkBlob %s for feed %s: %v", sl.Time.String(), sl.UpkeepId.String(), m.ChainlinkBlob, sl.Feeds[index], err) + state = mercury.InvalidMercuryResponse + return err + } + ch <- mercury.MercuryData{ + Index: index, + Bytes: [][]byte{blobBytes}, + Retryable: false, + State: mercury.NoPipelineError, + } + sent = true + return nil + }, + // only retry when the error is 404 Not Found, 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout + retry.RetryIf(func(err error) bool { + return err.Error() == fmt.Sprintf("%d", http.StatusNotFound) || err.Error() == fmt.Sprintf("%d", http.StatusInternalServerError) || err.Error() == fmt.Sprintf("%d", http.StatusBadGateway) || err.Error() == fmt.Sprintf("%d", http.StatusServiceUnavailable) || err.Error() == fmt.Sprintf("%d", http.StatusGatewayTimeout) + }), + retry.Context(ctx), + retry.Delay(retryDelay), + retry.Attempts(totalAttempt), + ) + + if !sent { + ch <- mercury.MercuryData{ + Index: index, + Bytes: [][]byte{}, + Retryable: retryable, + Error: fmt.Errorf("failed to request feed for %s: %w", sl.Feeds[index], retryErr), + State: state, + } + } +} + +func (c *client) Close() error { + return c.StopOnce("v02_request", func() error { + c.threadCtrl.Close() + return nil + }) +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02/v02_request_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02/v02_request_test.go new file mode 100644 index 00000000000..ac945859d89 --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02/v02_request_test.go @@ -0,0 +1,467 @@ +package v02 + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "math/big" + "net/http" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/patrickmn/go-cache" + "github.com/stretchr/testify/mock" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" + "github.com/smartcontractkit/chainlink/v2/core/utils" + + "github.com/stretchr/testify/assert" +) + +const ( + defaultPluginRetryExpiration = 30 * time.Minute + cleanupInterval = 5 * time.Minute +) + +type MockMercuryConfigProvider struct { + cache *cache.Cache + mock.Mock +} + +func NewMockMercuryConfigProvider() *MockMercuryConfigProvider { + return &MockMercuryConfigProvider{ + cache: cache.New(defaultPluginRetryExpiration, cleanupInterval), + } +} + +func (m *MockMercuryConfigProvider) Credentials() *models.MercuryCredentials { + mc := &models.MercuryCredentials{ + LegacyURL: "https://google.old.com", + URL: "https://google.com", + Username: "FakeClientID", + Password: "FakeClientKey", + } + return mc +} + +func (m *MockMercuryConfigProvider) IsUpkeepAllowed(s string) (interface{}, bool) { + args := m.Called(s) + return args.Get(0), args.Bool(1) +} + +func (m *MockMercuryConfigProvider) SetUpkeepAllowed(s string, i interface{}, d time.Duration) { + m.Called(s, i, d) +} + +func (m *MockMercuryConfigProvider) GetPluginRetry(s string) (interface{}, bool) { + if value, found := m.cache.Get(s); found { + return value, true + } + + return nil, false +} + +func (m *MockMercuryConfigProvider) SetPluginRetry(s string, i interface{}, d time.Duration) { + m.cache.Set(s, i, d) +} + +type MockHttpClient struct { + mock.Mock +} + +func (mock *MockHttpClient) Do(req *http.Request) (*http.Response, error) { + args := mock.Called(req) + return args.Get(0).(*http.Response), args.Error(1) +} + +// setups up a client object for tests. +func setupClient(t *testing.T) *client { + lggr := logger.TestLogger(t) + mockHttpClient := new(MockHttpClient) + mercuryConfig := NewMockMercuryConfigProvider() + threadCtl := utils.NewThreadControl() + + client := NewClient( + mercuryConfig, + mockHttpClient, + threadCtl, + lggr, + ) + return client +} + +func TestV02_SingleFeedRequest(t *testing.T) { + upkeepId := big.NewInt(123456789) + tests := []struct { + name string + index int + lookup *mercury.StreamsLookup + blob string + statusCode int + lastStatusCode int + retryNumber int + retryable bool + errorMessage string + }{ + { + name: "success - mercury responds in the first try", + index: 0, + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + blob: "0xab2123dc00000012", + }, + { + name: "success - retry for 404", + index: 0, + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + blob: "0xab2123dcbabbad", + retryNumber: 1, + statusCode: http.StatusNotFound, + lastStatusCode: http.StatusOK, + }, + { + name: "success - retry for 500", + index: 0, + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + blob: "0xab2123dcbbabad", + retryNumber: 2, + statusCode: http.StatusInternalServerError, + lastStatusCode: http.StatusOK, + }, + { + name: "failure - returns retryable", + index: 0, + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + blob: "0xab2123dc", + retryNumber: totalAttempt, + statusCode: http.StatusNotFound, + retryable: true, + errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 404\n#2: 404\n#3: 404", + }, + { + name: "failure - returns retryable and then non-retryable", + index: 0, + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + blob: "0xab2123dc", + retryNumber: 1, + statusCode: http.StatusNotFound, + lastStatusCode: http.StatusTooManyRequests, + errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 404\n#2: at block 123456 upkeep 123456789 received status code 429 for feed 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", + }, + { + name: "failure - returns not retryable", + index: 0, + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + blob: "0xab2123dc", + statusCode: http.StatusConflict, + errorMessage: "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: at block 123456 upkeep 123456789 received status code 409 for feed 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := setupClient(t) + defer c.Close() + hc := new(MockHttpClient) + + mr := MercuryV02Response{ChainlinkBlob: tt.blob} + b, err := json.Marshal(mr) + assert.Nil(t, err) + + if tt.retryNumber == 0 { + if tt.errorMessage != "" { + resp := &http.Response{ + StatusCode: tt.statusCode, + Body: io.NopCloser(bytes.NewReader(b)), + } + hc.On("Do", mock.Anything).Return(resp, nil).Once() + } else { + resp := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(b)), + } + hc.On("Do", mock.Anything).Return(resp, nil).Once() + } + } else if tt.retryNumber > 0 && tt.retryNumber < totalAttempt { + retryResp := &http.Response{ + StatusCode: tt.statusCode, + Body: io.NopCloser(bytes.NewReader(b)), + } + hc.On("Do", mock.Anything).Return(retryResp, nil).Times(tt.retryNumber) + + resp := &http.Response{ + StatusCode: tt.lastStatusCode, + Body: io.NopCloser(bytes.NewReader(b)), + } + hc.On("Do", mock.Anything).Return(resp, nil).Once() + } else { + resp := &http.Response{ + StatusCode: tt.statusCode, + Body: io.NopCloser(bytes.NewReader(b)), + } + hc.On("Do", mock.Anything).Return(resp, nil).Times(tt.retryNumber) + } + c.httpClient = hc + + ch := make(chan mercury.MercuryData, 1) + c.singleFeedRequest(testutils.Context(t), ch, tt.index, tt.lookup) + + m := <-ch + assert.Equal(t, tt.index, m.Index) + assert.Equal(t, tt.retryable, m.Retryable) + if tt.retryNumber >= totalAttempt || tt.errorMessage != "" { + assert.Equal(t, tt.errorMessage, m.Error.Error()) + assert.Equal(t, [][]byte{}, m.Bytes) + } else { + blobBytes, err := hexutil.Decode(tt.blob) + assert.Nil(t, err) + assert.Nil(t, m.Error) + assert.Equal(t, [][]byte{blobBytes}, m.Bytes) + } + }) + } +} + +func TestV02_DoMercuryRequestV02(t *testing.T) { + upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) + + tests := []struct { + name string + lookup *mercury.StreamsLookup + mockHttpStatusCode int + mockChainlinkBlobs []string + pluginRetries int + pluginRetryKey string + expectedValues [][]byte + expectedRetryable bool + expectedRetryInterval time.Duration + expectedError error + state mercury.MercuryUpkeepState + reason mercury.MercuryUpkeepFailureReason + }{ + { + name: "success", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(25880526), + ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, + UpkeepId: upkeepId, + }, + mockHttpStatusCode: http.StatusOK, + mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, + expectedValues: [][]byte{{0, 6, 109, 252, 209, 237, 45, 149, 177, 140, 148, 141, 188, 91, 214, 76, 104, 122, 254, 147, 228, 202, 125, 102, 61, 222, 193, 76, 32, 9, 10, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 128, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 69, 84, 72, 45, 85, 83, 68, 45, 65, 82, 66, 73, 84, 82, 85, 77, 45, 84, 69, 83, 84, 78, 69, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 216, 211, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 207, 11, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 155, 61, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 206, 116, 217, 250, 37, 42, 137, 131, 151, 110, 171, 96, 13, 199, 89, 12, 119, 141, 4, 129, 52, 48, 132, 27, 198, 231, 101, 195, 76, 216, 26, 22, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 96, 65, 43, 148, 229, 37, 202, 108, 237, 201, 245, 68, 253, 134, 247, 118, 6, 213, 47, 231, 49, 165, 208, 105, 219, 232, 54, 168, 191, 192, 251, 140, 145, 25, 99, 176, 174, 122, 20, 151, 31, 59, 70, 33, 191, 251, 128, 46, 240, 96, 83, 146, 185, 166, 200, 156, 127, 171, 29, 248, 99, 58, 90, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 69, 0, 194, 245, 33, 248, 63, 186, 94, 252, 43, 243, 239, 250, 174, 221, 228, 61, 10, 74, 223, 247, 133, 193, 33, 59, 113, 42, 58, 237, 13, 129, 87, 100, 42, 132, 50, 77, 176, 207, 150, 149, 235, 210, 119, 8, 212, 96, 142, 176, 51, 126, 13, 216, 123, 14, 67, 240, 250, 112, 199, 0, 217, 17}}, + expectedRetryable: false, + expectedError: nil, + }, + { + name: "failure - retryable and interval is 1s", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(25880526), + ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, + UpkeepId: upkeepId, + }, + mockHttpStatusCode: http.StatusInternalServerError, + mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, + expectedValues: [][]byte{nil}, + expectedRetryable: true, + pluginRetries: 0, + expectedRetryInterval: 1 * time.Second, + expectedError: errors.New("failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 500\n#2: 500\n#3: 500"), + state: mercury.MercuryFlakyFailure, + }, + { + name: "failure - retryable and interval is 5s", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(25880526), + ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, + UpkeepId: upkeepId, + }, + pluginRetries: 5, + mockHttpStatusCode: http.StatusInternalServerError, + mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, + expectedValues: [][]byte{nil}, + expectedRetryable: true, + expectedRetryInterval: 5 * time.Second, + expectedError: errors.New("failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 500\n#2: 500\n#3: 500"), + state: mercury.MercuryFlakyFailure, + }, + { + name: "failure - not retryable because there are many plugin retries already", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(25880526), + ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, + UpkeepId: upkeepId, + }, + pluginRetries: 10, + mockHttpStatusCode: http.StatusInternalServerError, + mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, + expectedValues: [][]byte{nil}, + expectedRetryable: true, + expectedError: errors.New("failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: 500\n#2: 500\n#3: 500"), + state: mercury.MercuryFlakyFailure, + }, + { + name: "failure - not retryable", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(25880526), + ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, + UpkeepId: upkeepId, + }, + mockHttpStatusCode: http.StatusTooManyRequests, + mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, + expectedValues: [][]byte{nil}, + expectedRetryable: false, + expectedError: errors.New("failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000: All attempts fail:\n#1: at block 25880526 upkeep 88786950015966611018675766524283132478093844178961698330929478019253453382042 received status code 429 for feed 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"), + state: mercury.InvalidMercuryRequest, + }, + { + name: "failure - no feeds", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIdHex, + Feeds: []string{}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(25880526), + ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, + UpkeepId: upkeepId, + }, + expectedValues: [][]byte{}, + reason: mercury.MercuryUpkeepFailureReasonInvalidRevertDataInput, + }, + { + name: "failure - invalid revert data", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(25880526), + ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, + UpkeepId: upkeepId, + }, + expectedValues: [][]byte{}, + reason: mercury.MercuryUpkeepFailureReasonInvalidRevertDataInput, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := setupClient(t) + defer c.Close() + if tt.pluginRetries != 0 { + c.mercuryConfig.SetPluginRetry(tt.pluginRetryKey, tt.pluginRetries, cache.DefaultExpiration) + } + hc := new(MockHttpClient) + + for _, blob := range tt.mockChainlinkBlobs { + mr := MercuryV02Response{ChainlinkBlob: blob} + b, err := json.Marshal(mr) + assert.Nil(t, err) + + resp := &http.Response{ + StatusCode: tt.mockHttpStatusCode, + Body: io.NopCloser(bytes.NewReader(b)), + } + if tt.expectedError != nil && tt.expectedRetryable || tt.pluginRetries > 0 { + hc.On("Do", mock.Anything).Return(resp, nil).Times(totalAttempt) + } else { + hc.On("Do", mock.Anything).Return(resp, nil).Once() + } + } + c.httpClient = hc + + state, reason, values, retryable, retryInterval, reqErr := c.DoRequest(testutils.Context(t), tt.lookup, tt.pluginRetryKey) + assert.Equal(t, tt.expectedValues, values) + assert.Equal(t, tt.expectedRetryable, retryable) + if retryable { + newRetries, _ := c.mercuryConfig.GetPluginRetry(tt.pluginRetryKey) + assert.Equal(t, tt.pluginRetries+1, newRetries.(int)) + } + assert.Equal(t, tt.expectedRetryInterval, retryInterval) + assert.Equal(t, tt.state, state) + assert.Equal(t, tt.reason, reason) + if tt.expectedError != nil { + assert.True(t, strings.HasPrefix(reqErr.Error(), "failed to request feed for 0x4554482d5553442d415242495452554d2d544553544e45540000000000000000")) + } + }) + } +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03/request.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03/request.go new file mode 100644 index 00000000000..cb3c9ef3222 --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03/request.go @@ -0,0 +1,245 @@ +package v03 + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "github.com/avast/retry-go/v4" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +const ( + mercuryBatchPathV03 = "/api/v1/reports/bulk?" // only used to access mercury v0.3 server + mercuryBatchPathV03BlockNumber = "/api/v1gmx/reports/bulk?" // only used to access mercury v0.3 server with blockNumber + retryDelay = 500 * time.Millisecond + totalAttempt = 3 + contentTypeHeader = "Content-Type" + authorizationHeader = "Authorization" + timestampHeader = "X-Authorization-Timestamp" + signatureHeader = "X-Authorization-Signature-SHA256" + upkeepIDHeader = "X-Authorization-Upkeep-Id" +) + +type MercuryV03Response struct { + Reports []MercuryV03Report `json:"reports"` +} + +type MercuryV03Report struct { + FeedID string `json:"feedID"` // feed id in hex encoded + ValidFromTimestamp uint32 `json:"validFromTimestamp"` + ObservationsTimestamp uint32 `json:"observationsTimestamp"` + FullReport string `json:"fullReport"` // the actual hex encoded mercury report of this feed, can be sent to verifier +} + +type client struct { + services.StateMachine + mercuryConfig mercury.MercuryConfigProvider + httpClient mercury.HttpClient + threadCtrl utils.ThreadControl + lggr logger.Logger +} + +func NewClient(mercuryConfig mercury.MercuryConfigProvider, httpClient mercury.HttpClient, threadCtrl utils.ThreadControl, lggr logger.Logger) *client { + return &client{ + mercuryConfig: mercuryConfig, + httpClient: httpClient, + threadCtrl: threadCtrl, + lggr: lggr, + } +} + +func (c *client) DoRequest(ctx context.Context, streamsLookup *mercury.StreamsLookup, pluginRetryKey string) (mercury.MercuryUpkeepState, mercury.MercuryUpkeepFailureReason, [][]byte, bool, time.Duration, error) { + if len(streamsLookup.Feeds) == 0 { + return mercury.NoPipelineError, mercury.MercuryUpkeepFailureReasonInvalidRevertDataInput, [][]byte{}, false, 0 * time.Second, fmt.Errorf("invalid revert data input: feed param key %s, time param key %s, feeds %s", streamsLookup.FeedParamKey, streamsLookup.TimeParamKey, streamsLookup.Feeds) + } + resultLen := 1 // Only 1 multi-feed request is made for all feeds + ch := make(chan mercury.MercuryData, resultLen) + c.threadCtrl.Go(func(ctx context.Context) { + c.multiFeedsRequest(ctx, ch, streamsLookup) + }) + + var reqErr error + var retryInterval time.Duration + results := make([][]byte, len(streamsLookup.Feeds)) + retryable := false + state := mercury.NoPipelineError + + m := <-ch + if m.Error != nil { + reqErr = m.Error + retryable = m.Retryable + state = m.State + if retryable { + retryInterval = mercury.CalculateRetryConfigFn(pluginRetryKey, c.mercuryConfig) + } + } else { + results = m.Bytes + } + + return state, mercury.MercuryUpkeepFailureReasonNone, results, retryable, retryInterval, reqErr +} + +func (c *client) multiFeedsRequest(ctx context.Context, ch chan<- mercury.MercuryData, sl *mercury.StreamsLookup) { + // this won't work bc q.Encode() will encode commas as '%2C' but the server is strictly expecting a comma separated list + //q := url.Values{ + // feedIDs: {strings.Join(sl.Feeds, ",")}, + // timestamp: {sl.Time.String()}, + //} + + params := fmt.Sprintf("%s=%s&%s=%s", mercury.FeedIDs, strings.Join(sl.Feeds, ","), mercury.Timestamp, sl.Time.String()) + batchPathV03 := mercuryBatchPathV03 + if sl.IsMercuryUsingBatchPathV03() { + batchPathV03 = mercuryBatchPathV03BlockNumber + } + reqUrl := fmt.Sprintf("%s%s%s", c.mercuryConfig.Credentials().URL, batchPathV03, params) + + c.lggr.Debugf("request URL for upkeep %s userId %s: %s", sl.UpkeepId.String(), c.mercuryConfig.Credentials().Username, reqUrl) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, nil) + if err != nil { + ch <- mercury.MercuryData{Index: 0, Error: err, Retryable: false, State: mercury.InvalidMercuryRequest} + return + } + + ts := time.Now().UTC().UnixMilli() + signature := mercury.GenerateHMACFn(http.MethodGet, mercuryBatchPathV03+params, []byte{}, c.mercuryConfig.Credentials().Username, c.mercuryConfig.Credentials().Password, ts) + + req.Header.Set(contentTypeHeader, "application/json") + // username here is often referred to as user id + req.Header.Set(authorizationHeader, c.mercuryConfig.Credentials().Username) + req.Header.Set(timestampHeader, strconv.FormatInt(ts, 10)) + req.Header.Set(signatureHeader, signature) + // mercury will inspect authorization headers above to make sure this user (in automation's context, this node) is eligible to access mercury + // and if it has an automation role. it will then look at this upkeep id to check if it has access to all the requested feeds. + req.Header.Set(upkeepIDHeader, sl.UpkeepId.String()) + + // in the case of multiple retries here, use the last attempt's data + state := mercury.NoPipelineError + retryable := false + sent := false + retryErr := retry.Do( + func() error { + retryable = false + resp, err := c.httpClient.Do(req) + if err != nil { + c.lggr.Warnf("at timestamp %s upkeep %s GET request fails from mercury v0.3: %v", sl.Time.String(), sl.UpkeepId.String(), err) + retryable = true + state = mercury.MercuryFlakyFailure + return err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + retryable = false + state = mercury.InvalidMercuryResponse + return err + } + + c.lggr.Infof("at timestamp %s upkeep %s received status code %d from mercury v0.3", sl.Time.String(), sl.UpkeepId.String(), resp.StatusCode) + switch resp.StatusCode { + case http.StatusUnauthorized: + retryable = false + state = mercury.UpkeepNotAuthorized + return fmt.Errorf("at timestamp %s upkeep %s received status code %d from mercury v0.3, most likely this is caused by unauthorized upkeep", sl.Time.String(), sl.UpkeepId.String(), resp.StatusCode) + case http.StatusBadRequest: + retryable = false + state = mercury.InvalidMercuryRequest + return fmt.Errorf("at timestamp %s upkeep %s received status code %d from mercury v0.3, most likely this is caused by invalid format of timestamp", sl.Time.String(), sl.UpkeepId.String(), resp.StatusCode) + case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout: + retryable = true + state = mercury.MercuryFlakyFailure + return fmt.Errorf("%d", resp.StatusCode) + case http.StatusPartialContent: + // TODO (AUTO-5044): handle response code 206 entirely with errors field parsing + c.lggr.Warnf("at timestamp %s upkeep %s requested [%s] feeds but mercury v0.3 server returned 206 status, treating it as 404 and retrying", sl.Time.String(), sl.UpkeepId.String(), sl.Feeds) + retryable = true + state = mercury.MercuryFlakyFailure + return fmt.Errorf("%d", http.StatusPartialContent) + case http.StatusOK: + // continue + default: + retryable = false + state = mercury.InvalidMercuryRequest + return fmt.Errorf("at timestamp %s upkeep %s received status code %d from mercury v0.3", sl.Time.String(), sl.UpkeepId.String(), resp.StatusCode) + } + c.lggr.Debugf("at block %s upkeep %s received status code %d from mercury v0.3 with BODY=%s", sl.Time.String(), sl.UpkeepId.String(), resp.StatusCode, hexutil.Encode(body)) + + var response MercuryV03Response + if err := json.Unmarshal(body, &response); err != nil { + c.lggr.Warnf("at timestamp %s upkeep %s failed to unmarshal body to MercuryV03Response from mercury v0.3: %v", sl.Time.String(), sl.UpkeepId.String(), err) + retryable = false + state = mercury.MercuryUnmarshalError + return err + } + + // in v0.3, if some feeds are not available, the server will only return available feeds, but we need to make sure ALL feeds are retrieved before calling user contract + // hence, retry in this case. retry will help when we send a very new timestamp and reports are not yet generated + if len(response.Reports) != len(sl.Feeds) { + var receivedFeeds []string + for _, f := range response.Reports { + receivedFeeds = append(receivedFeeds, f.FeedID) + } + c.lggr.Warnf("at timestamp %s upkeep %s mercury v0.3 server returned 206 status with [%s] reports while we requested [%s] feeds, retrying", sl.Time.String(), sl.UpkeepId.String(), receivedFeeds, sl.Feeds) + retryable = true + state = mercury.MercuryFlakyFailure + return fmt.Errorf("%d", http.StatusNotFound) + } + var reportBytes [][]byte + for _, rsp := range response.Reports { + b, err := hexutil.Decode(rsp.FullReport) + if err != nil { + c.lggr.Warnf("at timestamp %s upkeep %s failed to decode reportBlob %s: %v", sl.Time.String(), sl.UpkeepId.String(), rsp.FullReport, err) + retryable = false + state = mercury.InvalidMercuryResponse + return err + } + reportBytes = append(reportBytes, b) + } + ch <- mercury.MercuryData{ + Index: 0, + Bytes: reportBytes, + Retryable: false, + State: mercury.NoPipelineError, + } + sent = true + return nil + }, + // only retry when the error is 206 Partial Content, 404 Not Found, 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout + retry.RetryIf(func(err error) bool { + return err.Error() == fmt.Sprintf("%d", http.StatusPartialContent) || err.Error() == fmt.Sprintf("%d", http.StatusNotFound) || err.Error() == fmt.Sprintf("%d", http.StatusInternalServerError) || err.Error() == fmt.Sprintf("%d", http.StatusBadGateway) || err.Error() == fmt.Sprintf("%d", http.StatusServiceUnavailable) || err.Error() == fmt.Sprintf("%d", http.StatusGatewayTimeout) + }), + retry.Context(ctx), + retry.Delay(retryDelay), + retry.Attempts(totalAttempt), + ) + + if !sent { + ch <- mercury.MercuryData{ + Index: 0, + Bytes: [][]byte{}, + Retryable: retryable, + Error: retryErr, + State: state, + } + } +} + +func (c *client) Close() error { + return c.StopOnce("v03_request", func() error { + c.threadCtrl.Close() + return nil + }) +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03/v03_request_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03/v03_request_test.go new file mode 100644 index 00000000000..16ccdac5d1f --- /dev/null +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03/v03_request_test.go @@ -0,0 +1,535 @@ +package v03 + +import ( + "bytes" + "encoding/json" + "io" + "math/big" + "net/http" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/patrickmn/go-cache" + "github.com/stretchr/testify/mock" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mocks" + "github.com/smartcontractkit/chainlink/v2/core/utils" + + "github.com/stretchr/testify/assert" +) + +const ( + defaultPluginRetryExpiration = 30 * time.Minute + cleanupInterval = 5 * time.Minute +) + +type MockMercuryConfigProvider struct { + cache *cache.Cache + mock.Mock +} + +func NewMockMercuryConfigProvider() *MockMercuryConfigProvider { + return &MockMercuryConfigProvider{ + cache: cache.New(defaultPluginRetryExpiration, cleanupInterval), + } +} + +func (m *MockMercuryConfigProvider) Credentials() *models.MercuryCredentials { + mc := &models.MercuryCredentials{ + LegacyURL: "https://google.old.com", + URL: "https://google.com", + Username: "FakeClientID", + Password: "FakeClientKey", + } + return mc +} + +func (m *MockMercuryConfigProvider) IsUpkeepAllowed(s string) (interface{}, bool) { + args := m.Called(s) + return args.Get(0), args.Bool(1) +} + +func (m *MockMercuryConfigProvider) SetUpkeepAllowed(s string, i interface{}, d time.Duration) { + m.Called(s, i, d) +} + +func (m *MockMercuryConfigProvider) GetPluginRetry(s string) (interface{}, bool) { + if value, found := m.cache.Get(s); found { + return value, true + } + + return nil, false +} + +func (m *MockMercuryConfigProvider) SetPluginRetry(s string, i interface{}, d time.Duration) { + m.cache.Set(s, i, d) +} + +type MockHttpClient struct { + mock.Mock +} + +func (mock *MockHttpClient) Do(req *http.Request) (*http.Response, error) { + args := mock.Called(req) + return args.Get(0).(*http.Response), args.Error(1) +} + +// setups up a client object for tests. +func setupClient(t *testing.T) *client { + lggr := logger.TestLogger(t) + mockHttpClient := new(MockHttpClient) + mercuryConfig := NewMockMercuryConfigProvider() + threadCtl := utils.NewThreadControl() + + client := NewClient( + mercuryConfig, + mockHttpClient, + threadCtl, + lggr, + ) + return client +} + +func TestV03_DoMercuryRequestV03(t *testing.T) { + upkeepId, _ := new(big.Int).SetString("88786950015966611018675766524283132478093844178961698330929478019253453382042", 10) + + tests := []struct { + name string + lookup *mercury.StreamsLookup + mockHttpStatusCode int + mockChainlinkBlobs []string + pluginRetryKey string + expectedValues [][]byte + expectedRetryable bool + expectedRetryInterval time.Duration + expectedError error + state mercury.MercuryUpkeepState + reason mercury.MercuryUpkeepFailureReason + }{ + { + name: "success v0.3", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(25880526), + ExtraData: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}, + }, + UpkeepId: upkeepId, + }, + mockHttpStatusCode: http.StatusOK, + mockChainlinkBlobs: []string{"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}, + expectedValues: [][]byte{{0, 6, 109, 252, 209, 237, 45, 149, 177, 140, 148, 141, 188, 91, 214, 76, 104, 122, 254, 147, 228, 202, 125, 102, 61, 222, 193, 76, 32, 9, 10, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 128, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 69, 84, 72, 45, 85, 83, 68, 45, 65, 82, 66, 73, 84, 82, 85, 77, 45, 84, 69, 83, 84, 78, 69, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 216, 211, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 154, 207, 11, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 155, 61, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 206, 116, 217, 250, 37, 42, 137, 131, 151, 110, 171, 96, 13, 199, 89, 12, 119, 141, 4, 129, 52, 48, 132, 27, 198, 231, 101, 195, 76, 216, 26, 22, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 138, 231, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 137, 28, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 96, 65, 43, 148, 229, 37, 202, 108, 237, 201, 245, 68, 253, 134, 247, 118, 6, 213, 47, 231, 49, 165, 208, 105, 219, 232, 54, 168, 191, 192, 251, 140, 145, 25, 99, 176, 174, 122, 20, 151, 31, 59, 70, 33, 191, 251, 128, 46, 240, 96, 83, 146, 185, 166, 200, 156, 127, 171, 29, 248, 99, 58, 90, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 69, 0, 194, 245, 33, 248, 63, 186, 94, 252, 43, 243, 239, 250, 174, 221, 228, 61, 10, 74, 223, 247, 133, 193, 33, 59, 113, 42, 58, 237, 13, 129, 87, 100, 42, 132, 50, 77, 176, 207, 150, 149, 235, 210, 119, 8, 212, 96, 142, 176, 51, 126, 13, 216, 123, 14, 67, 240, 250, 112, 199, 0, 217, 17}}, + expectedRetryable: false, + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := setupClient(t) + defer c.Close() + hc := mocks.NewHttpClient(t) + + mr := MercuryV03Response{} + for i, blob := range tt.mockChainlinkBlobs { + r := MercuryV03Report{ + FeedID: tt.lookup.Feeds[i], + ValidFromTimestamp: 0, + ObservationsTimestamp: 0, + FullReport: blob, + } + mr.Reports = append(mr.Reports, r) + } + + b, err := json.Marshal(mr) + assert.Nil(t, err) + resp := &http.Response{ + StatusCode: tt.mockHttpStatusCode, + Body: io.NopCloser(bytes.NewReader(b)), + } + if tt.expectedError != nil && tt.expectedRetryable { + hc.On("Do", mock.Anything).Return(resp, nil).Times(totalAttempt) + } else { + hc.On("Do", mock.Anything).Return(resp, nil).Once() + } + c.httpClient = hc + + state, reason, values, retryable, retryInterval, reqErr := c.DoRequest(testutils.Context(t), tt.lookup, tt.pluginRetryKey) + + assert.Equal(t, tt.expectedValues, values) + assert.Equal(t, tt.expectedRetryable, retryable) + assert.Equal(t, tt.expectedRetryInterval, retryInterval) + assert.Equal(t, tt.state, state) + assert.Equal(t, tt.reason, reason) + if tt.expectedError != nil { + assert.Equal(t, tt.expectedError.Error(), reqErr.Error()) + } + }) + } +} + +func TestV03_MultiFeedRequest(t *testing.T) { + upkeepId := big.NewInt(123456789) + tests := []struct { + name string + lookup *mercury.StreamsLookup + statusCode int + lastStatusCode int + pluginRetries int + pluginRetryKey string + retryNumber int + retryable bool + errorMessage string + firstResponse *MercuryV03Response + response *MercuryV03Response + }{ + { + name: "success - mercury responds in the first try", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.Timestamp, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + response: &MercuryV03Response{ + Reports: []MercuryV03Report{ + { + FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123456, + ObservationsTimestamp: 123456, + FullReport: "0xab2123dc00000012", + }, + { + FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123458, + ObservationsTimestamp: 123458, + FullReport: "0xab2123dc00000016", + }, + }, + }, + statusCode: http.StatusOK, + }, + { + name: "success - mercury responds in the first try with blocknumber", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.BlockNumber, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + response: &MercuryV03Response{ + Reports: []MercuryV03Report{ + { + FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123456, + ObservationsTimestamp: 123456, + FullReport: "0xab2123dc00000012", + }, + { + FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123458, + ObservationsTimestamp: 123458, + FullReport: "0xab2123dc00000016", + }, + }, + }, + statusCode: http.StatusOK, + }, + { + name: "success - retry 206", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.Timestamp, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + firstResponse: &MercuryV03Response{ + Reports: []MercuryV03Report{ + { + FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123456, + ObservationsTimestamp: 123456, + FullReport: "0xab2123dc00000012", + }, + }, + }, + response: &MercuryV03Response{ + Reports: []MercuryV03Report{ + { + FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123456, + ObservationsTimestamp: 123456, + FullReport: "0xab2123dc00000012", + }, + { + FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123458, + ObservationsTimestamp: 123458, + FullReport: "0xab2123dc00000019", + }, + }, + }, + retryNumber: 1, + statusCode: http.StatusPartialContent, + lastStatusCode: http.StatusOK, + }, + { + name: "success - retry for 500", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.Timestamp, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + retryNumber: 2, + statusCode: http.StatusInternalServerError, + lastStatusCode: http.StatusOK, + response: &MercuryV03Response{ + Reports: []MercuryV03Report{ + { + FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123456, + ObservationsTimestamp: 123456, + FullReport: "0xab2123dc00000012", + }, + { + FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123458, + ObservationsTimestamp: 123458, + FullReport: "0xab2123dc00000019", + }, + }, + }, + }, + { + name: "failure - fail to decode reportBlob", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.Timestamp, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + response: &MercuryV03Response{ + Reports: []MercuryV03Report{ + { + FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123456, + ObservationsTimestamp: 123456, + FullReport: "qerwiu", // invalid hex blob + }, + { + FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123458, + ObservationsTimestamp: 123458, + FullReport: "0xab2123dc00000016", + }, + }, + }, + statusCode: http.StatusOK, + retryable: false, + errorMessage: "All attempts fail:\n#1: hex string without 0x prefix", + }, + { + name: "failure - returns retryable with 1s plugin retry interval", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.Timestamp, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + retryNumber: totalAttempt, + statusCode: http.StatusInternalServerError, + retryable: true, + errorMessage: "All attempts fail:\n#1: 500\n#2: 500\n#3: 500", + }, + { + name: "failure - returns retryable with 5s plugin retry interval", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.Timestamp, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + pluginRetries: 6, + retryNumber: totalAttempt, + statusCode: http.StatusInternalServerError, + retryable: true, + errorMessage: "All attempts fail:\n#1: 500\n#2: 500\n#3: 500", + }, + { + name: "failure - returns retryable and then non-retryable", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.Timestamp, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + retryNumber: 1, + statusCode: http.StatusInternalServerError, + lastStatusCode: http.StatusUnauthorized, + errorMessage: "All attempts fail:\n#1: 500\n#2: at timestamp 123456 upkeep 123456789 received status code 401 from mercury v0.3, most likely this is caused by unauthorized upkeep", + }, + { + name: "failure - returns status code 422 not retryable", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.Timestamp, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + statusCode: http.StatusUnprocessableEntity, + errorMessage: "All attempts fail:\n#1: at timestamp 123456 upkeep 123456789 received status code 422 from mercury v0.3", + }, + { + name: "success - retry when reports length does not match feeds length", + lookup: &mercury.StreamsLookup{ + StreamsLookupError: &mercury.StreamsLookupError{ + FeedParamKey: mercury.FeedIDs, + Feeds: []string{"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000"}, + TimeParamKey: mercury.Timestamp, + Time: big.NewInt(123456), + }, + UpkeepId: upkeepId, + }, + firstResponse: &MercuryV03Response{ + Reports: []MercuryV03Report{ + { + FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123456, + ObservationsTimestamp: 123456, + FullReport: "0xab2123dc00000012", + }, + }, + }, + response: &MercuryV03Response{ + Reports: []MercuryV03Report{ + { + FeedID: "0x4554482d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123456, + ObservationsTimestamp: 123456, + FullReport: "0xab2123dc00000012", + }, + { + FeedID: "0x4254432d5553442d415242495452554d2d544553544e45540000000000000000", + ValidFromTimestamp: 123458, + ObservationsTimestamp: 123458, + FullReport: "0xab2123dc00000019", + }, + }, + }, + retryNumber: 1, + statusCode: http.StatusOK, + lastStatusCode: http.StatusOK, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := setupClient(t) + defer c.Close() + if tt.pluginRetries != 0 { + c.mercuryConfig.SetPluginRetry(tt.pluginRetryKey, tt.pluginRetries, cache.DefaultExpiration) + } + + hc := new(MockHttpClient) + b, err := json.Marshal(tt.response) + assert.Nil(t, err) + + if tt.retryNumber == 0 { + resp := &http.Response{ + StatusCode: tt.statusCode, + Body: io.NopCloser(bytes.NewReader(b)), + } + hc.On("Do", mock.Anything).Return(resp, nil).Once() + } else if tt.retryNumber < totalAttempt { + if tt.firstResponse != nil && tt.response != nil { + b0, err := json.Marshal(tt.firstResponse) + assert.Nil(t, err) + resp0 := &http.Response{ + StatusCode: tt.statusCode, + Body: io.NopCloser(bytes.NewReader(b0)), + } + b1, err := json.Marshal(tt.response) + assert.Nil(t, err) + resp1 := &http.Response{ + StatusCode: tt.lastStatusCode, + Body: io.NopCloser(bytes.NewReader(b1)), + } + hc.On("Do", mock.Anything).Return(resp0, nil).Once().On("Do", mock.Anything).Return(resp1, nil).Once() + } else { + retryResp := &http.Response{ + StatusCode: tt.statusCode, + Body: io.NopCloser(bytes.NewReader(b)), + } + hc.On("Do", mock.Anything).Return(retryResp, nil).Times(tt.retryNumber) + + resp := &http.Response{ + StatusCode: tt.lastStatusCode, + Body: io.NopCloser(bytes.NewReader(b)), + } + hc.On("Do", mock.Anything).Return(resp, nil).Once() + } + } else { + resp := &http.Response{ + StatusCode: tt.statusCode, + Body: io.NopCloser(bytes.NewReader(b)), + } + hc.On("Do", mock.Anything).Return(resp, nil).Times(tt.retryNumber) + } + c.httpClient = hc + + ch := make(chan mercury.MercuryData, 1) + c.multiFeedsRequest(testutils.Context(t), ch, tt.lookup) + + m := <-ch + assert.Equal(t, 0, m.Index) + assert.Equal(t, tt.retryable, m.Retryable) + if tt.retryNumber >= totalAttempt || tt.errorMessage != "" { + assert.Equal(t, tt.errorMessage, m.Error.Error()) + assert.Equal(t, [][]byte{}, m.Bytes) + } else { + assert.Nil(t, m.Error) + var reports [][]byte + for _, rsp := range tt.response.Reports { + b, _ := hexutil.Decode(rsp.FullReport) + reports = append(reports, b) + } + assert.Equal(t, reports, m.Bytes) + } + }) + } +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/mocks/http_client.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mocks/http_client.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/mocks/http_client.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mocks/http_client.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/mocks/registry.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mocks/registry.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/mocks/registry.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mocks/registry.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/payload_builder.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/payload_builder.go similarity index 92% rename from core/services/ocr2/plugins/ocr2keeper/evm21/payload_builder.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/payload_builder.go index b14e687b5d1..37c458ae6eb 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/payload_builder.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/payload_builder.go @@ -3,11 +3,11 @@ package evm import ( "context" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" ) type payloadBuilder struct { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/payload_builder_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/payload_builder_test.go similarity index 95% rename from core/services/ocr2/plugins/ocr2keeper/evm21/payload_builder_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/payload_builder_test.go index e75084ff968..7e55267f6d3 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/payload_builder_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/payload_builder_test.go @@ -6,12 +6,14 @@ import ( "testing" "github.com/pkg/errors" - "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/assert" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" ) func TestNewPayloadBuilder(t *testing.T) { @@ -191,7 +193,7 @@ func TestNewPayloadBuilder(t *testing.T) { t.Run(tc.name, func(t *testing.T) { lggr, _ := logger.NewLogger() builder := NewPayloadBuilder(tc.activeList, tc.recoverer, lggr) - payloads, err := builder.BuildPayloads(context.Background(), tc.proposals...) + payloads, err := builder.BuildPayloads(testutils.Context(t), tc.proposals...) assert.NoError(t, err) assert.Equal(t, tc.wantPayloads, payloads) }) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/registry.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry.go similarity index 86% rename from core/services/ocr2/plugins/ocr2keeper/evm21/registry.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry.go index 1cad587e635..bb05b8029fe 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/registry.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry.go @@ -17,27 +17,31 @@ import ( "github.com/pkg/errors" "go.uber.org/multierr" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/encoding" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) const ( + defaultPluginRetryExpiration = 30 * time.Minute // defaultAllowListExpiration decides how long an upkeep's allow list info will be valid for. - defaultAllowListExpiration = 20 * time.Minute - // allowListCleanupInterval decides when the expired items in allowList cache will be deleted. - allowListCleanupInterval = 5 * time.Minute + defaultAllowListExpiration = 10 * time.Minute + // cleanupInterval decides when the expired items in cache will be deleted. + cleanupInterval = 5 * time.Minute logTriggerRefreshBatchSize = 32 ) @@ -75,7 +79,7 @@ type HttpClient interface { func NewEvmRegistry( lggr logger.Logger, addr common.Address, - client evm.Chain, + client legacyevm.Chain, registry *iregistry21.IKeeperRegistryMaster, mc *models.MercuryCredentials, al ActiveUpkeepList, @@ -84,29 +88,34 @@ func NewEvmRegistry( blockSub *BlockSubscriber, finalityDepth uint32, ) *EvmRegistry { + mercuryConfig := &MercuryConfig{ + cred: mc, + Abi: core.StreamsCompatibleABI, + AllowListCache: cache.New(defaultAllowListExpiration, cleanupInterval), + pluginRetryCache: cache.New(defaultPluginRetryExpiration, cleanupInterval), + } + + hc := http.DefaultClient + return &EvmRegistry{ - ctx: context.Background(), - threadCtrl: utils.NewThreadControl(), - lggr: lggr.Named(RegistryServiceName), - poller: client.LogPoller(), - addr: addr, - client: client.Client(), - logProcessed: make(map[string]bool), - registry: registry, - abi: core.RegistryABI, - active: al, - packer: packer, - headFunc: func(ocr2keepers.BlockKey) {}, - chLog: make(chan logpoller.Log, 1000), - mercury: &MercuryConfig{ - cred: mc, - abi: core.StreamsCompatibleABI, - allowListCache: cache.New(defaultAllowListExpiration, allowListCleanupInterval), - }, - hc: http.DefaultClient, + ctx: context.Background(), + threadCtrl: utils.NewThreadControl(), + lggr: lggr.Named(RegistryServiceName), + poller: client.LogPoller(), + addr: addr, + client: client.Client(), + logProcessed: make(map[string]bool), + registry: registry, + abi: core.RegistryABI, + active: al, + packer: packer, + headFunc: func(ocr2keepers.BlockKey) {}, + chLog: make(chan logpoller.Log, 1000), + hc: hc, logEventProvider: logEventProvider, bs: blockSub, finalityDepth: finalityDepth, + streams: streams.NewStreamsLookup(packer, mercuryConfig, blockSub, client.Client(), registry, lggr), } } @@ -122,13 +131,43 @@ var upkeepStateEvents = []common.Hash{ type MercuryConfig struct { cred *models.MercuryCredentials - abi abi.ABI - // allowListCache stores the upkeeps privileges. In 2.1, this only includes a JSON bytes for allowed to use mercury - allowListCache *cache.Cache + Abi abi.ABI + // AllowListCache stores the upkeeps privileges. In 2.1, this only includes a JSON bytes for allowed to use mercury + AllowListCache *cache.Cache + pluginRetryCache *cache.Cache +} + +func NewMercuryConfig(credentials *models.MercuryCredentials, abi abi.ABI) *MercuryConfig { + return &MercuryConfig{ + cred: credentials, + Abi: abi, + AllowListCache: cache.New(defaultPluginRetryExpiration, cleanupInterval), + pluginRetryCache: cache.New(defaultPluginRetryExpiration, cleanupInterval), + } +} + +func (c *MercuryConfig) Credentials() *models.MercuryCredentials { + return c.cred +} + +func (c *MercuryConfig) IsUpkeepAllowed(k string) (interface{}, bool) { + return c.AllowListCache.Get(k) +} + +func (c *MercuryConfig) SetUpkeepAllowed(k string, v interface{}, d time.Duration) { + c.AllowListCache.Set(k, v, d) +} + +func (c *MercuryConfig) GetPluginRetry(k string) (interface{}, bool) { + return c.pluginRetryCache.Get(k) +} + +func (c *MercuryConfig) SetPluginRetry(k string, v interface{}, d time.Duration) { + c.pluginRetryCache.Set(k, v, d) } type EvmRegistry struct { - utils.StartStopOnce + services.StateMachine threadCtrl utils.ThreadControl lggr logger.Logger poller logpoller.LogPoller @@ -150,6 +189,7 @@ type EvmRegistry struct { bs *BlockSubscriber logEventProvider logprovider.LogEventProvider finalityDepth uint32 + streams streams.Lookup } func (r *EvmRegistry) Name() string { @@ -361,7 +401,7 @@ func (r *EvmRegistry) refreshLogTriggerUpkeepsBatch(logTriggerIDs []*big.Int) er func (r *EvmRegistry) pollUpkeepStateLogs() error { var latest int64 - var end int64 + var end logpoller.LogPollerBlock var err error if end, err = r.poller.LatestBlock(pg.WithParentCtx(r.ctx)); err != nil { @@ -370,18 +410,18 @@ func (r *EvmRegistry) pollUpkeepStateLogs() error { r.mu.Lock() latest = r.lastPollBlock - r.lastPollBlock = end + r.lastPollBlock = end.BlockNumber r.mu.Unlock() // if start and end are the same, no polling needs to be done - if latest == 0 || latest == end { + if latest == 0 || latest == end.BlockNumber { return nil } var logs []logpoller.Log if logs, err = r.poller.LogsWithSigs( - end-logEventLookback, - end, + end.BlockNumber-logEventLookback, + end.BlockNumber, upkeepStateEvents, r.addr, pg.WithParentCtx(r.ctx), diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/registry_check_pipeline.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_check_pipeline.go similarity index 97% rename from core/services/ocr2/plugins/ocr2keeper/evm21/registry_check_pipeline.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_check_pipeline.go index d3530994702..f5a931aae45 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/registry_check_pipeline.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_check_pipeline.go @@ -9,10 +9,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/encoding" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" ) const ( @@ -41,7 +42,10 @@ func (r *EvmRegistry) CheckUpkeeps(ctx context.Context, keys ...ocr2keepers.Upke } chResult := make(chan checkResult, 1) - go r.doCheck(ctx, keys, chResult) + + r.threadCtrl.Go(func(ctx context.Context) { + r.doCheck(ctx, keys, chResult) + }) select { case rs := <-chResult: @@ -67,7 +71,7 @@ func (r *EvmRegistry) doCheck(ctx context.Context, keys []ocr2keepers.UpkeepPayl return } - upkeepResults = r.streamsLookup(ctx, upkeepResults) + upkeepResults = r.streams.Lookup(ctx, upkeepResults) upkeepResults, err = r.simulatePerformUpkeeps(ctx, upkeepResults) if err != nil { @@ -101,7 +105,7 @@ func (r *EvmRegistry) getBlockHash(blockNumber *big.Int) (common.Hash, error) { } // verifyCheckBlock checks that the check block and hash are valid, returns the pipeline execution state and retryable -func (r *EvmRegistry) verifyCheckBlock(_ context.Context, checkBlock, upkeepId *big.Int, checkHash common.Hash) (state encoding.PipelineExecutionState, retryable bool) { +func (r *EvmRegistry) verifyCheckBlock(_ context.Context, checkBlock, upkeepId *big.Int, checkHash common.Hash) (state uint8, retryable bool) { // verify check block number and hash are valid h, ok := r.bs.queryBlocksMap(checkBlock.Int64()) // if this block number/hash combo exists in block subscriber, this check block and hash still exist on chain and are valid @@ -124,7 +128,7 @@ func (r *EvmRegistry) verifyCheckBlock(_ context.Context, checkBlock, upkeepId * } // verifyLogExists checks that the log still exists on chain, returns failure reason, pipeline error, and retryable -func (r *EvmRegistry) verifyLogExists(upkeepId *big.Int, p ocr2keepers.UpkeepPayload) (encoding.UpkeepFailureReason, encoding.PipelineExecutionState, bool) { +func (r *EvmRegistry) verifyLogExists(upkeepId *big.Int, p ocr2keepers.UpkeepPayload) (uint8, uint8, bool) { logBlockNumber := int64(p.Trigger.LogTriggerExtension.BlockNumber) logBlockHash := common.BytesToHash(p.Trigger.LogTriggerExtension.BlockHash[:]) checkBlockHash := common.BytesToHash(p.Trigger.BlockHash[:]) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/registry_check_pipeline_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_check_pipeline_test.go similarity index 89% rename from core/services/ocr2/plugins/ocr2keeper/evm21/registry_check_pipeline_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_check_pipeline_test.go index 5ea2bdc6670..a14aaec0c5e 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/registry_check_pipeline_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_check_pipeline_test.go @@ -4,22 +4,31 @@ import ( "context" "fmt" "math/big" + "strings" "sync/atomic" "testing" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" - - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + "github.com/patrickmn/go-cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" evmClientMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/streams_lookup_compatible_interface" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/encoding" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) @@ -81,7 +90,7 @@ func TestRegistry_VerifyCheckBlock(t *testing.T) { payload ocr2keepers.UpkeepPayload blocks map[int64]string poller logpoller.LogPoller - state encoding.PipelineExecutionState + state uint8 retryable bool makeEthCall bool }{ @@ -194,7 +203,7 @@ func TestRegistry_VerifyCheckBlock(t *testing.T) { e.client = client } - state, retryable := e.verifyCheckBlock(context.Background(), tc.checkBlock, tc.upkeepId, tc.checkHash) + state, retryable := e.verifyCheckBlock(testutils.Context(t), tc.checkBlock, tc.upkeepId, tc.checkHash) assert.Equal(t, tc.state, state) assert.Equal(t, tc.retryable, retryable) }) @@ -239,8 +248,8 @@ func TestRegistry_VerifyLogExists(t *testing.T) { payload ocr2keepers.UpkeepPayload blocks map[int64]string makeEthCall bool - reason encoding.UpkeepFailureReason - state encoding.PipelineExecutionState + reason uint8 + state uint8 retryable bool ethCallErr error receipt *types.Receipt @@ -339,7 +348,7 @@ func TestRegistry_VerifyLogExists(t *testing.T) { e := &EvmRegistry{ lggr: lggr, bs: bs, - ctx: context.Background(), + ctx: testutils.Context(t), } if tc.makeEthCall { @@ -519,7 +528,7 @@ func TestRegistry_CheckUpkeeps(t *testing.T) { } e.client = client - results, err := e.checkUpkeeps(context.Background(), tc.inputs) + results, err := e.checkUpkeeps(testutils.Context(t), tc.inputs) assert.Equal(t, tc.results, results) assert.Equal(t, tc.err, err) }) @@ -640,10 +649,49 @@ func TestRegistry_SimulatePerformUpkeeps(t *testing.T) { }).Once() e.client = client - results, err := e.simulatePerformUpkeeps(context.Background(), tc.inputs) + results, err := e.simulatePerformUpkeeps(testutils.Context(t), tc.inputs) assert.Equal(t, tc.results, results) assert.Equal(t, tc.err, err) }) } +} +// setups up an evm registry for tests. +func setupEVMRegistry(t *testing.T) *EvmRegistry { + lggr := logger.TestLogger(t) + addr := common.HexToAddress("0x6cA639822c6C241Fa9A7A6b5032F6F7F1C513CAD") + keeperRegistryABI, err := abi.JSON(strings.NewReader(i_keeper_registry_master_wrapper_2_1.IKeeperRegistryMasterABI)) + require.Nil(t, err, "need registry abi") + streamsLookupCompatibleABI, err := abi.JSON(strings.NewReader(streams_lookup_compatible_interface.StreamsLookupCompatibleInterfaceABI)) + require.Nil(t, err, "need mercury abi") + var logPoller logpoller.LogPoller + mockReg := mocks.NewRegistry(t) + mockHttpClient := mocks.NewHttpClient(t) + client := evmClientMocks.NewClient(t) + + r := &EvmRegistry{ + lggr: lggr, + poller: logPoller, + addr: addr, + client: client, + logProcessed: make(map[string]bool), + registry: mockReg, + abi: keeperRegistryABI, + active: NewActiveUpkeepList(), + packer: encoding.NewAbiPacker(), + headFunc: func(ocr2keepers.BlockKey) {}, + chLog: make(chan logpoller.Log, 1000), + mercury: &MercuryConfig{ + cred: &models.MercuryCredentials{ + LegacyURL: "https://google.old.com", + URL: "https://google.com", + Username: "FakeClientID", + Password: "FakeClientKey", + }, + Abi: streamsLookupCompatibleABI, + AllowListCache: cache.New(defaultAllowListExpiration, cleanupInterval), + }, + hc: mockHttpClient, + } + return r } diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/registry_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_test.go similarity index 98% rename from core/services/ocr2/plugins/ocr2keeper/evm21/registry_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_test.go index 0cd5ecd2592..f3e4402092c 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/registry_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_test.go @@ -11,10 +11,11 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" coreTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + types3 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" @@ -22,9 +23,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/encoding" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -145,7 +146,7 @@ func TestPollLogs(t *testing.T) { if test.LatestBlock != nil { mp.On("LatestBlock", mock.Anything). - Return(test.LatestBlock.OutputBlock, test.LatestBlock.OutputErr) + Return(logpoller.LogPollerBlock{BlockNumber: test.LatestBlock.OutputBlock}, test.LatestBlock.OutputErr) } if test.LogsWithSigs != nil { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/services.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/services.go similarity index 88% rename from core/services/ocr2/plugins/ocr2keeper/evm21/services.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/services.go index f9d3dd92591..71ccee872ff 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/services.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/services.go @@ -4,20 +4,22 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/ocr2keepers/pkg/v3/plugin" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" - "github.com/smartcontractkit/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/encoding" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/transmit" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate" "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) @@ -34,7 +36,7 @@ type AutomationServices interface { Keyring() ocr3types.OnchainKeyring[plugin.AutomationReportInfo] } -func New(addr common.Address, client evm.Chain, mc *models.MercuryCredentials, keyring ocrtypes.OnchainKeyring, lggr logger.Logger, db *sqlx.DB, dbCfg pg.QConfig) (AutomationServices, error) { +func New(addr common.Address, client legacyevm.Chain, mc *models.MercuryCredentials, keyring ocrtypes.OnchainKeyring, lggr logger.Logger, db *sqlx.DB, dbCfg pg.QConfig) (AutomationServices, error) { registryContract, err := iregistry21.NewIKeeperRegistryMaster(addr, client.Client()) if err != nil { return nil, fmt.Errorf("%w: failed to create caller for address and backend", ErrInitializationFailure) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/testdata/btc-usd.json b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/testdata/btc-usd.json similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/testdata/btc-usd.json rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/testdata/btc-usd.json diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/testdata/eth-usd.json b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/testdata/eth-usd.json similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/testdata/eth-usd.json rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/testdata/eth-usd.json diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/cache.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/cache.go similarity index 95% rename from core/services/ocr2/plugins/ocr2keeper/evm21/transmit/cache.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/cache.go index 5e0dadbdc4f..99e1f1dc164 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/cache.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/cache.go @@ -3,7 +3,7 @@ package transmit import ( "sync" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ) // transmitEventCache holds a ring buffer of the last visited blocks (transmit block), diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/cache_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/cache_test.go similarity index 96% rename from core/services/ocr2/plugins/ocr2keeper/evm21/transmit/cache_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/cache_test.go index 55d245c168f..7f4a1296567 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/cache_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/cache_test.go @@ -3,8 +3,9 @@ package transmit import ( "testing" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/require" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ) func TestTransmitEventCache_Sanity(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/encoding.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/encoding.go similarity index 97% rename from core/services/ocr2/plugins/ocr2keeper/evm21/transmit/encoding.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/encoding.go index ec709d04bd8..17d3cd439f8 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/encoding.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/encoding.go @@ -4,7 +4,7 @@ import ( "fmt" "math/big" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/encoding_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/encoding_test.go similarity index 95% rename from core/services/ocr2/plugins/ocr2keeper/evm21/transmit/encoding_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/encoding_test.go index f081f82f2a2..da32376aab6 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/encoding_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/encoding_test.go @@ -4,12 +4,13 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/require" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) func TestTransmitEventLog(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/event_provider.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/event_provider.go similarity index 95% rename from core/services/ocr2/plugins/ocr2keeper/evm21/transmit/event_provider.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/event_provider.go index b0ae2a7bf63..f66711c2b71 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/event_provider.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/event_provider.go @@ -7,15 +7,17 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink-common/pkg/services" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) var _ ocr2keepers.TransmitEventProvider = &EventProvider{} @@ -23,7 +25,7 @@ var _ ocr2keepers.TransmitEventProvider = &EventProvider{} type logParser func(registry *iregistry21.IKeeperRegistryMaster, log logpoller.Log) (transmitEventLog, error) type EventProvider struct { - sync utils.StartStopOnce + sync services.StateMachine mu sync.RWMutex runState int runError error @@ -140,8 +142,8 @@ func (c *EventProvider) GetLatestEvents(ctx context.Context) ([]ocr2keepers.Tran // always check the last lookback number of blocks and rebroadcast // this allows the plugin to make decisions based on event confirmations logs, err := c.logPoller.LogsWithSigs( - end-c.lookbackBlocks, - end, + end.BlockNumber-c.lookbackBlocks, + end.BlockNumber, []common.Hash{ iregistry21.IKeeperRegistryMasterUpkeepPerformed{}.Topic(), iregistry21.IKeeperRegistryMasterStaleUpkeepReport{}.Topic(), @@ -155,7 +157,7 @@ func (c *EventProvider) GetLatestEvents(ctx context.Context) ([]ocr2keepers.Tran return nil, fmt.Errorf("%w: failed to collect logs from log poller", err) } - return c.processLogs(end, logs...) + return c.processLogs(end.BlockNumber, logs...) } // processLogs will parse the unseen logs and return the corresponding transmit events. diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/event_provider_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/event_provider_test.go similarity index 95% rename from core/services/ocr2/plugins/ocr2keeper/evm21/transmit/event_provider_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/event_provider_test.go index 72f3b63088d..950ec810402 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/transmit/event_provider_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/event_provider_test.go @@ -1,29 +1,29 @@ package transmit import ( - "context" "math/big" "runtime" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmClientMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) func TestTransmitEventProvider_Sanity(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := testutils.Context(t) lp := new(mocks.LogPoller) @@ -89,7 +89,7 @@ func TestTransmitEventProvider_Sanity(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - lp.On("LatestBlock", mock.Anything).Return(tc.latestBlock, nil) + lp.On("LatestBlock", mock.Anything).Return(logpoller.LogPollerBlock{BlockNumber: tc.latestBlock}, nil) lp.On("LogsWithSigs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.logs, nil) res, err := provider.GetLatestEvents(ctx) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeep_provider.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeep_provider.go similarity index 91% rename from core/services/ocr2/plugins/ocr2keeper/evm21/upkeep_provider.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeep_provider.go index 5cb60d5dc1d..b30215e4fbf 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeep_provider.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeep_provider.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) var _ ocr2keepers.ConditionalUpkeepProvider = &upkeepProvider{} diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeep_provider_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeep_provider_test.go similarity index 95% rename from core/services/ocr2/plugins/ocr2keeper/evm21/upkeep_provider_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeep_provider_test.go index ac49675812e..81ea3913188 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeep_provider_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeep_provider_test.go @@ -5,12 +5,13 @@ import ( "sync/atomic" "testing" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" "github.com/stretchr/testify/require" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) func TestUpkeepProvider_GetActiveUpkeeps(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/orm.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/orm.go similarity index 98% rename from core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/orm.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/orm.go index 5db2f8bd0f3..c918ad595fa 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/orm.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/orm.go @@ -4,8 +4,8 @@ import ( "math/big" "time" + "github.com/jmoiron/sqlx" "github.com/lib/pq" - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/orm_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/orm_test.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/orm_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/orm_test.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/scanner.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/scanner.go similarity index 98% rename from core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/scanner.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/scanner.go index d70611313af..30a50977d17 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/scanner.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/scanner.go @@ -11,7 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/scanner_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/scanner_test.go similarity index 100% rename from core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/scanner_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/scanner_test.go diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/store.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/store.go similarity index 97% rename from core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/store.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/store.go index 34bd6822d69..4a4de5ea1ad 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/store.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/store.go @@ -8,10 +8,12 @@ import ( "sync" "time" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/core" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -59,7 +61,7 @@ type upkeepStateRecord struct { // It stores the state of ineligible upkeeps in a local, in-memory cache. // In addition, performed events are fetched by the scanner on demand. type upkeepStateStore struct { - utils.StartStopOnce + services.StateMachine threadCtrl utils.ThreadControl orm ORM diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/store_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/store_test.go similarity index 96% rename from core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/store_test.go rename to core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/store_test.go index 8e2e77f7fb4..138b9ffd782 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/upkeepstate/store_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/store_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" @@ -357,7 +357,7 @@ func TestUpkeepStateStore_SetSelectIntegration(t *testing.T) { }) for _, insert := range test.storedValues { - require.NoError(t, store.SetUpkeepState(context.Background(), insert.result, insert.state), "storing states should not produce an error") + require.NoError(t, store.SetUpkeepState(ctx, insert.result, insert.state), "storing states should not produce an error") } tickerCh <- time.Now() @@ -408,7 +408,7 @@ func TestUpkeepStateStore_emptyDB(t *testing.T) { scanner := &mockScanner{} store := NewUpkeepStateStore(orm, lggr, scanner) - states, err := store.SelectByWorkIDs(context.Background(), []string{"0x1", "0x2", "0x3", "0x4"}...) + states, err := store.SelectByWorkIDs(testutils.Context(t), []string{"0x1", "0x2", "0x3", "0x4"}...) assert.NoError(t, err) assert.Equal(t, []ocr2keepers.UpkeepState{ ocr2keepers.UnknownState, @@ -454,6 +454,7 @@ func TestUpkeepStateStore_Upsert(t *testing.T) { } func TestUpkeepStateStore_Service(t *testing.T) { + ctx := testutils.Context(t) orm := &mockORM{ onDelete: func(tm time.Time) { @@ -466,10 +467,10 @@ func TestUpkeepStateStore_Service(t *testing.T) { store.retention = 500 * time.Millisecond store.cleanCadence = 100 * time.Millisecond - assert.NoError(t, store.Start(context.Background()), "no error from starting service") + assert.NoError(t, store.Start(ctx), "no error from starting service") // add a value to set up the test - require.NoError(t, store.SetUpkeepState(context.Background(), ocr2keepers.CheckResult{ + require.NoError(t, store.SetUpkeepState(ctx, ocr2keepers.CheckResult{ Eligible: false, WorkID: "0x2", Trigger: ocr2keepers.Trigger{ @@ -481,7 +482,7 @@ func TestUpkeepStateStore_Service(t *testing.T) { time.Sleep(110 * time.Millisecond) // select from store to ensure values still exist - values, err := store.SelectByWorkIDs(context.Background(), "0x2") + values, err := store.SelectByWorkIDs(ctx, "0x2") require.NoError(t, err, "no error from selecting states") require.Equal(t, []ocr2keepers.UpkeepState{ocr2keepers.Ineligible}, values, "selected values should match expected") @@ -489,7 +490,7 @@ func TestUpkeepStateStore_Service(t *testing.T) { time.Sleep(700 * time.Millisecond) // select from store to ensure cached values were removed - values, err = store.SelectByWorkIDs(context.Background(), "0x2") + values, err = store.SelectByWorkIDs(ctx, "0x2") require.NoError(t, err, "no error from selecting states") require.Equal(t, []ocr2keepers.UpkeepState{ocr2keepers.UnknownState}, values, "selected values should match expected") diff --git a/core/services/ocr2/plugins/ocr2keeper/integration_21_test.go b/core/services/ocr2/plugins/ocr2keeper/integration_21_test.go index 15280de73cf..7ccd785a668 100644 --- a/core/services/ocr2/plugins/ocr2keeper/integration_21_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/integration_21_test.go @@ -18,7 +18,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" + gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/hashicorp/consul/sdk/freeport" "github.com/onsi/gomega" @@ -30,10 +30,12 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" ocrTypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/ocr2keepers/pkg/v3/config" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" automationForwarderLogic "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_forwarder_logic" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/basic_upkeep_contract" @@ -52,7 +54,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper" - evm21 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" ) @@ -64,7 +66,7 @@ func TestFilterNamesFromSpec21(t *testing.T) { address := common.HexToAddress(hexutil.Encode(b)) spec := &job.OCR2OracleSpec{ - PluginType: relaytypes.OCR2Keeper, + PluginType: types.OCR2Keeper, ContractID: address.String(), // valid contract addr } @@ -76,7 +78,7 @@ func TestFilterNamesFromSpec21(t *testing.T) { assert.Equal(t, logpoller.FilterName("KeeperRegistry Events", address), names[1]) spec = &job.OCR2OracleSpec{ - PluginType: relaytypes.OCR2Keeper, + PluginType: types.OCR2Keeper, ContractID: "0x5431", // invalid contract addr } _, err = ocr2keeper.FilterNamesFromSpec21(spec) @@ -479,7 +481,7 @@ func setupNodes(t *testing.T, nodeKeys [5]ethkey.KeyV2, registry *iregistry21.IK // Setup bootstrap + oracle nodes bootstrapNodePort := freeport.GetOne(t) - appBootstrap, bootstrapPeerID, bootstrapTransmitter, bootstrapKb := setupNode(t, bootstrapNodePort, "bootstrap_keeper_ocr", nodeKeys[0], backend, nil, mServer) + appBootstrap, bootstrapPeerID, bootstrapTransmitter, bootstrapKb := setupNode(t, bootstrapNodePort, nodeKeys[0], backend, nil, mServer) bootstrapNode := Node{ appBootstrap, bootstrapTransmitter, bootstrapKb, } @@ -490,7 +492,7 @@ func setupNodes(t *testing.T, nodeKeys [5]ethkey.KeyV2, registry *iregistry21.IK // Set up the minimum 4 oracles all funded ports := freeport.GetN(t, 4) for i := 0; i < 4; i++ { - app, peerID, transmitter, kb := setupNode(t, ports[i], fmt.Sprintf("oracle_keeper%d", i), nodeKeys[i+1], backend, []commontypes.BootstrapperLocator{ + app, peerID, transmitter, kb := setupNode(t, ports[i], nodeKeys[i+1], backend, []commontypes.BootstrapperLocator{ // Supply the bootstrap IP and port as a V2 peer address {PeerID: bootstrapPeerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, }, mServer) @@ -720,7 +722,7 @@ func deployKeeper21Registry( return registryMaster } -func getUpkeepIdFromTx21(t *testing.T, registry *iregistry21.IKeeperRegistryMaster, registrationTx *types.Transaction, backend *backends.SimulatedBackend) *big.Int { +func getUpkeepIdFromTx21(t *testing.T, registry *iregistry21.IKeeperRegistryMaster, registrationTx *gethtypes.Transaction, backend *backends.SimulatedBackend) *big.Int { receipt, err := backend.TransactionReceipt(testutils.Context(t), registrationTx.Hash()) require.NoError(t, err) parsedLog, err := registry.ParseUpkeepRegistered(*receipt.Logs[0]) @@ -884,10 +886,11 @@ func (c *feedLookupUpkeepController) EnableMercury( registry *iregistry21.IKeeperRegistryMaster, registryOwner *bind.TransactOpts, ) error { - adminBytes, _ := json.Marshal(evm21.UpkeepPrivilegeConfig{ + adminBytes, _ := json.Marshal(streams.UpkeepPrivilegeConfig{ MercuryEnabled: true, }) + ctx := testutils.Context(t) for _, id := range c.upkeepIds { if _, err := registry.SetUpkeepPrivilegeConfig(registryOwner, id, adminBytes); err != nil { require.NoError(t, err) @@ -898,7 +901,7 @@ func (c *feedLookupUpkeepController) EnableMercury( callOpts := &bind.CallOpts{ Pending: true, From: registryOwner.From, - Context: context.Background(), + Context: ctx, } bts, err := registry.GetUpkeepPrivilegeConfig(callOpts, id) @@ -908,7 +911,7 @@ func (c *feedLookupUpkeepController) EnableMercury( return err } - var checkBytes evm21.UpkeepPrivilegeConfig + var checkBytes streams.UpkeepPrivilegeConfig if err := json.Unmarshal(bts, &checkBytes); err != nil { require.NoError(t, err) @@ -987,7 +990,7 @@ func (c *feedLookupUpkeepController) EmitEvents( backend.Commit() // verify event was emitted - block, _ := backend.BlockByHash(context.Background(), backend.Commit()) + block, _ := backend.BlockByHash(ctx, backend.Commit()) t.Logf("block number after emit event: %d", block.NumberU64()) iter, _ := c.protocol.FilterLimitOrderExecuted( diff --git a/core/services/ocr2/plugins/ocr2keeper/integration_test.go b/core/services/ocr2/plugins/ocr2keeper/integration_test.go index 7c881a18eb9..1ab9194c496 100644 --- a/core/services/ocr2/plugins/ocr2keeper/integration_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/integration_test.go @@ -1,7 +1,6 @@ package ocr2keeper_test import ( - "context" "crypto/rand" "encoding/hex" "encoding/json" @@ -19,22 +18,23 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" + gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/hashicorp/consul/sdk/freeport" "github.com/onsi/gomega" "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo/abi" "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" ocrTypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/ocr2keepers/pkg/v2/config" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo/abi" + "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" + "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/config/toml" @@ -61,8 +61,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" - - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" ) const ( @@ -111,7 +109,6 @@ func deployKeeper20Registry( func setupNode( t *testing.T, port int, - dbName string, nodeKey ethkey.KeyV2, backend *backends.SimulatedBackend, p2pV2Bootstrappers []commontypes.BootstrapperLocator, @@ -119,7 +116,7 @@ func setupNode( ) (chainlink.Application, string, common.Address, ocr2key.KeyBundle) { p2pKey := keystest.NewP2PKeyV2(t) p2paddresses := []string{fmt.Sprintf("127.0.0.1:%d", port)} - cfg, _ := heavyweight.FullTestDBV2(t, fmt.Sprintf("%s%d", dbName, port), func(c *chainlink.Config, s *chainlink.Secrets) { + cfg, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Feature.LogPoller = ptr(true) c.OCR.Enabled = ptr(false) @@ -172,14 +169,14 @@ func (node *Node) AddJob(t *testing.T, spec string) { c := node.App.GetConfig() jb, err := validate.ValidatedOracleSpecToml(c.OCR2(), c.Insecure(), spec) require.NoError(t, err) - err = node.App.AddJobV2(context.Background(), &jb) + err = node.App.AddJobV2(testutils.Context(t), &jb) require.NoError(t, err) } func (node *Node) AddBootstrapJob(t *testing.T, spec string) { jb, err := ocrbootstrap.ValidatedBootstrapSpecToml(spec) require.NoError(t, err) - err = node.App.AddJobV2(context.Background(), &jb) + err = node.App.AddJobV2(testutils.Context(t), &jb) require.NoError(t, err) } @@ -197,7 +194,7 @@ func accountsToAddress(accounts []ocrTypes.Account) (addresses []common.Address, return addresses, nil } -func getUpkeepIdFromTx(t *testing.T, registry *keeper_registry_wrapper2_0.KeeperRegistry, registrationTx *types.Transaction, backend *backends.SimulatedBackend) *big.Int { +func getUpkeepIdFromTx(t *testing.T, registry *keeper_registry_wrapper2_0.KeeperRegistry, registrationTx *gethtypes.Transaction, backend *backends.SimulatedBackend) *big.Int { receipt, err := backend.TransactionReceipt(testutils.Context(t), registrationTx.Hash()) require.NoError(t, err) parsedLog, err := registry.ParseUpkeepRegistered(*receipt.Logs[0]) @@ -240,7 +237,7 @@ func TestIntegration_KeeperPluginBasic(t *testing.T) { // Setup bootstrap + oracle nodes bootstrapNodePort := freeport.GetOne(t) - appBootstrap, bootstrapPeerID, bootstrapTransmitter, bootstrapKb := setupNode(t, bootstrapNodePort, "bootstrap_keeper_ocr", nodeKeys[0], backend, nil, NewSimulatedMercuryServer()) + appBootstrap, bootstrapPeerID, bootstrapTransmitter, bootstrapKb := setupNode(t, bootstrapNodePort, nodeKeys[0], backend, nil, NewSimulatedMercuryServer()) bootstrapNode := Node{ appBootstrap, bootstrapTransmitter, bootstrapKb, } @@ -251,7 +248,7 @@ func TestIntegration_KeeperPluginBasic(t *testing.T) { // Set up the minimum 4 oracles all funded ports := freeport.GetN(t, 4) for i := 0; i < 4; i++ { - app, peerID, transmitter, kb := setupNode(t, ports[i], fmt.Sprintf("oracle_keeper%d", i), nodeKeys[i+1], backend, []commontypes.BootstrapperLocator{ + app, peerID, transmitter, kb := setupNode(t, ports[i], nodeKeys[i+1], backend, []commontypes.BootstrapperLocator{ // Supply the bootstrap IP and port as a V2 peer address {PeerID: bootstrapPeerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, }, NewSimulatedMercuryServer()) @@ -501,7 +498,7 @@ func TestIntegration_KeeperPluginForwarderEnabled(t *testing.T) { effectiveTransmitters := make([]common.Address, 0) // Setup bootstrap + oracle nodes bootstrapNodePort := freeport.GetOne(t) - appBootstrap, bootstrapPeerID, bootstrapTransmitter, bootstrapKb := setupNode(t, bootstrapNodePort, "bootstrap_keeper_ocr", nodeKeys[0], backend, nil, NewSimulatedMercuryServer()) + appBootstrap, bootstrapPeerID, bootstrapTransmitter, bootstrapKb := setupNode(t, bootstrapNodePort, nodeKeys[0], backend, nil, NewSimulatedMercuryServer()) bootstrapNode := Node{ appBootstrap, bootstrapTransmitter, bootstrapKb, @@ -513,7 +510,7 @@ func TestIntegration_KeeperPluginForwarderEnabled(t *testing.T) { // Set up the minimum 4 oracles all funded ports := freeport.GetN(t, 4) for i := 0; i < 4; i++ { - app, peerID, transmitter, kb := setupNode(t, ports[i], fmt.Sprintf("oracle_keeper%d", i), nodeKeys[i+1], backend, []commontypes.BootstrapperLocator{ + app, peerID, transmitter, kb := setupNode(t, ports[i], nodeKeys[i+1], backend, []commontypes.BootstrapperLocator{ // Supply the bootstrap IP and port as a V2 peer address {PeerID: bootstrapPeerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, }, NewSimulatedMercuryServer()) @@ -715,7 +712,7 @@ func TestFilterNamesFromSpec20(t *testing.T) { address := common.HexToAddress(hexutil.Encode(b)) spec := &job.OCR2OracleSpec{ - PluginType: relaytypes.OCR2Keeper, + PluginType: types.OCR2Keeper, ContractID: address.String(), // valid contract addr } @@ -727,7 +724,7 @@ func TestFilterNamesFromSpec20(t *testing.T) { assert.Equal(t, logpoller.FilterName("EvmRegistry - Upkeep events for", address), names[1]) spec = &job.OCR2OracleSpec{ - PluginType: relaytypes.OCR2Keeper, + PluginType: types.OCR2Keeper, ContractID: "0x5431", // invalid contract addr } _, err = ocr2keeper.FilterNamesFromSpec20(spec) diff --git a/core/services/ocr2/plugins/ocr2keeper/util.go b/core/services/ocr2/plugins/ocr2keeper/util.go index 132afd0d29d..02a0d033bc1 100644 --- a/core/services/ocr2/plugins/ocr2keeper/util.go +++ b/core/services/ocr2/plugins/ocr2keeper/util.go @@ -3,25 +3,26 @@ package ocr2keeper import ( "fmt" + "github.com/jmoiron/sqlx" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - ocr2keepers20 "github.com/smartcontractkit/ocr2keepers/pkg/v2" - ocr2keepers20coordinator "github.com/smartcontractkit/ocr2keepers/pkg/v2/coordinator" - ocr2keepers20polling "github.com/smartcontractkit/ocr2keepers/pkg/v2/observer/polling" - ocr2keepers20runner "github.com/smartcontractkit/ocr2keepers/pkg/v2/runner" - "github.com/smartcontractkit/sqlx" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + ocr2keepers20 "github.com/smartcontractkit/chainlink-automation/pkg/v2" + ocr2keepers20coordinator "github.com/smartcontractkit/chainlink-automation/pkg/v2/coordinator" + ocr2keepers20polling "github.com/smartcontractkit/chainlink-automation/pkg/v2/observer/polling" + ocr2keepers20runner "github.com/smartcontractkit/chainlink-automation/pkg/v2/runner" + ocr2keepers21 "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - ocr2keepers21 "github.com/smartcontractkit/ocr2keepers/pkg/v3/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" - kevm20 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm20" - kevm21 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21" - kevm21transmit "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/transmit" + evmregistry20 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v20" + evmregistry21 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21" + evmregistry21transmit "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" @@ -43,7 +44,7 @@ var ( ErrNoChainFromSpec = fmt.Errorf("could not create chain from spec") ) -func EVMProvider(db *sqlx.DB, chain evm.Chain, lggr logger.Logger, spec job.Job, pr pipeline.Runner) (evmrelay.OCR2KeeperProvider, error) { +func EVMProvider(db *sqlx.DB, chain legacyevm.Chain, lggr logger.Logger, spec job.Job, pr pipeline.Runner) (evmrelay.OCR2KeeperProvider, error) { oSpec := spec.OCR2OracleSpec ocr2keeperRelayer := evmrelay.NewOCR2KeeperRelayer(db, chain, pr, spec, lggr.Named("OCR2KeeperRelayer")) @@ -70,13 +71,13 @@ func EVMDependencies20( spec job.Job, db *sqlx.DB, lggr logger.Logger, - chain evm.Chain, + chain legacyevm.Chain, pr pipeline.Runner, -) (evmrelay.OCR2KeeperProvider, *kevm20.EvmRegistry, Encoder20, *kevm20.LogProvider, error) { +) (evmrelay.OCR2KeeperProvider, *evmregistry20.EvmRegistry, Encoder20, *evmregistry20.LogProvider, error) { var err error var keeperProvider evmrelay.OCR2KeeperProvider - var registry *kevm20.EvmRegistry + var registry *evmregistry20.EvmRegistry // the provider will be returned as a dependency if keeperProvider, err = EVMProvider(db, chain, lggr, spec, pr); err != nil { @@ -84,17 +85,17 @@ func EVMDependencies20( } rAddr := ethkey.MustEIP55Address(spec.OCR2OracleSpec.ContractID).Address() - if registry, err = kevm20.NewEVMRegistryService(rAddr, chain, lggr); err != nil { + if registry, err = evmregistry20.NewEVMRegistryService(rAddr, chain, lggr); err != nil { return nil, nil, nil, nil, err } - encoder := kevm20.EVMAutomationEncoder20{} + encoder := evmregistry20.EVMAutomationEncoder20{} // lookback blocks is hard coded and should provide ample time for logs // to be detected in most cases var lookbackBlocks int64 = 250 // TODO: accept a version of the registry contract and use the correct interfaces - logProvider, err := kevm20.NewLogProvider(lggr, chain.LogPoller(), rAddr, chain.Client(), lookbackBlocks) + logProvider, err := evmregistry20.NewLogProvider(lggr, chain.LogPoller(), rAddr, chain.Client(), lookbackBlocks) return keeperProvider, registry, encoder, logProvider, err } @@ -104,19 +105,19 @@ func FilterNamesFromSpec20(spec *job.OCR2OracleSpec) (names []string, err error) if err != nil { return nil, err } - return []string{kevm20.LogProviderFilterName(addr.Address()), kevm20.UpkeepFilterName(addr.Address())}, err + return []string{evmregistry20.LogProviderFilterName(addr.Address()), evmregistry20.UpkeepFilterName(addr.Address())}, err } func EVMDependencies21( spec job.Job, db *sqlx.DB, lggr logger.Logger, - chain evm.Chain, + chain legacyevm.Chain, pr pipeline.Runner, mc *models.MercuryCredentials, keyring ocrtypes.OnchainKeyring, dbCfg pg.QConfig, -) (evmrelay.OCR2KeeperProvider, kevm21.AutomationServices, error) { +) (evmrelay.OCR2KeeperProvider, evmregistry21.AutomationServices, error) { var err error var keeperProvider evmrelay.OCR2KeeperProvider @@ -127,7 +128,7 @@ func EVMDependencies21( } rAddr := ethkey.MustEIP55Address(oSpec.ContractID).Address() - services, err := kevm21.New(rAddr, chain, mc, keyring, lggr, db, dbCfg) + services, err := evmregistry21.New(rAddr, chain, mc, keyring, lggr, db, dbCfg) if err != nil { return nil, nil, err } @@ -140,5 +141,5 @@ func FilterNamesFromSpec21(spec *job.OCR2OracleSpec) (names []string, err error) if err != nil { return nil, err } - return []string{kevm21transmit.EventProviderFilterName(addr.Address()), kevm21.RegistryUpkeepFilterName(addr.Address())}, err + return []string{evmregistry21transmit.EventProviderFilterName(addr.Address()), evmregistry21.RegistryUpkeepFilterName(addr.Address())}, err } diff --git a/core/services/ocr2/plugins/ocr2vrf/coordinator/coordinator.go b/core/services/ocr2/plugins/ocr2vrf/coordinator/coordinator.go index e51b68f415d..63538941532 100644 --- a/core/services/ocr2/plugins/ocr2vrf/coordinator/coordinator.go +++ b/core/services/ocr2/plugins/ocr2vrf/coordinator/coordinator.go @@ -17,13 +17,15 @@ import ( "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/smartcontractkit/libocr/commontypes" - ocr2Types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/ocr2vrf/dkg" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" "golang.org/x/exp/maps" "google.golang.org/protobuf/proto" + "github.com/smartcontractkit/libocr/commontypes" + ocr2Types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-vrf/dkg" + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -227,7 +229,7 @@ func (c *coordinator) CurrentChainHeight(ctx context.Context) (uint64, error) { if err != nil { return 0, err } - return uint64(head), nil + return uint64(head.BlockNumber), nil } // ReportIsOnchain returns true iff a report for the given OCR epoch/round is diff --git a/core/services/ocr2/plugins/ocr2vrf/coordinator/coordinator_test.go b/core/services/ocr2/plugins/ocr2vrf/coordinator/coordinator_test.go index 26d0f2996a9..418e4356c68 100644 --- a/core/services/ocr2/plugins/ocr2vrf/coordinator/coordinator_test.go +++ b/core/services/ocr2/plugins/ocr2vrf/coordinator/coordinator_test.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" + gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -21,11 +21,12 @@ import ( "github.com/smartcontractkit/libocr/commontypes" ocr2Types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/ocr2vrf/dkg" - "github.com/smartcontractkit/ocr2vrf/ocr2vrf" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-vrf/dkg" + "github.com/smartcontractkit/chainlink-vrf/ocr2vrf" + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" + + "github.com/smartcontractkit/chainlink-common/pkg/types" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -1032,7 +1033,7 @@ func TestCoordinator_ReportBlocks(t *testing.T) { requestedBlocks := []uint64{195, 196} lp := lp_mocks.NewLogPoller(t) lp.On("LatestBlock", mock.Anything). - Return(int64(latestHeadNumber), nil) + Return(logpoller.LogPollerBlock{BlockNumber: int64(latestHeadNumber)}, nil) lp.On("GetBlocksRange", mock.Anything, append(requestedBlocks, uint64(latestHeadNumber-lookbackBlocks+1), uint64(latestHeadNumber)), mock.Anything). Return(nil, errors.New("GetBlocks error")) @@ -1469,7 +1470,7 @@ func newRandomnessRequestedLog( SubID: big.NewInt(1), CostJuels: big.NewInt(50_000), NewSubBalance: big.NewInt(100_000), - Raw: types.Log{ + Raw: gethtypes.Log{ BlockNumber: requestBlock, }, } @@ -1544,7 +1545,7 @@ func newRandomnessFulfillmentRequestedLog( Requester: common.HexToAddress("0x1234567890"), CostJuels: big.NewInt(50_000), NewSubBalance: big.NewInt(100_000), - Raw: types.Log{ + Raw: gethtypes.Log{ BlockNumber: requestBlock, }, } @@ -1720,7 +1721,7 @@ func getLogPoller( lp := lp_mocks.NewLogPoller(t) if needsLatestBlock { lp.On("LatestBlock", mock.Anything). - Return(int64(latestHeadNumber), nil) + Return(logpoller.LogPollerBlock{BlockNumber: int64(latestHeadNumber)}, nil) } var logPollerBlocks []logpoller.LogPollerBlock @@ -1763,7 +1764,7 @@ func TestFilterNamesFromSpec(t *testing.T) { spec := &job.OCR2OracleSpec{ ContractID: beaconAddress.String(), - PluginType: relaytypes.OCR2VRF, + PluginType: types.OCR2VRF, PluginConfig: job.JSONConfig{ "VRFCoordinatorAddress": coordinatorAddress.String(), "DKGContractAddress": dkgAddress.String(), @@ -1777,7 +1778,7 @@ func TestFilterNamesFromSpec(t *testing.T) { assert.Equal(t, logpoller.FilterName("VRF Coordinator", beaconAddress, coordinatorAddress, dkgAddress), names[0]) spec = &job.OCR2OracleSpec{ - PluginType: relaytypes.OCR2VRF, + PluginType: types.OCR2VRF, ContractID: beaconAddress.String(), PluginConfig: nil, // missing coordinator & dkg addresses } diff --git a/core/services/ocr2/plugins/ocr2vrf/internal/ocr2vrf_integration_test.go b/core/services/ocr2/plugins/ocr2vrf/internal/ocr2vrf_integration_test.go index b9a4d500026..e93824283ba 100644 --- a/core/services/ocr2/plugins/ocr2vrf/internal/ocr2vrf_integration_test.go +++ b/core/services/ocr2/plugins/ocr2vrf/internal/ocr2vrf_integration_test.go @@ -1,7 +1,6 @@ package internal_test import ( - "context" "crypto/rand" "encoding/hex" "errors" @@ -26,12 +25,13 @@ import ( "github.com/smartcontractkit/libocr/commontypes" confighelper2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" ocrtypes2 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/ocr2vrf/altbn_128" - ocr2dkg "github.com/smartcontractkit/ocr2vrf/dkg" - "github.com/smartcontractkit/ocr2vrf/ocr2vrf" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-vrf/altbn_128" + ocr2dkg "github.com/smartcontractkit/chainlink-vrf/dkg" + "github.com/smartcontractkit/chainlink-vrf/ocr2vrf" + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_forwarder" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" @@ -226,7 +226,7 @@ func setupNodeOCR2( p2pV2Bootstrappers []commontypes.BootstrapperLocator, ) *ocr2Node { p2pKey := keystest.NewP2PKeyV2(t) - config, _ := heavyweight.FullTestDBV2(t, fmt.Sprintf("%s%d", dbName, port), func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Insecure.OCRDevelopmentMode = ptr(true) // Disables ocr spec validation so we can have fast polling for the test. c.Feature.LogPoller = ptr(true) @@ -326,16 +326,17 @@ func setupNodeOCR2( } func TestIntegration_OCR2VRF_ForwarderFlow(t *testing.T) { - t.Skip() + testutils.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/VRF-688") runOCR2VRFTest(t, true) } func TestIntegration_OCR2VRF(t *testing.T) { - t.Skip() + testutils.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/VRF-688") runOCR2VRFTest(t, false) } func runOCR2VRFTest(t *testing.T, useForwarders bool) { + ctx := testutils.Context(t) keyID := randomKeyID(t) uni := setupOCR2VRFContracts(t, 5, keyID, false) @@ -409,7 +410,7 @@ func runOCR2VRFTest(t *testing.T, useForwarders bool) { } }() - blockBeforeConfig, err := uni.backend.BlockByNumber(context.Background(), nil) + blockBeforeConfig, err := uni.backend.BlockByNumber(ctx, nil) require.NoError(t, err) t.Log("Setting DKG config before block:", blockBeforeConfig.Number().String()) @@ -447,7 +448,7 @@ fromBlock = %d t.Log("Creating bootstrap job:", bootstrapJobSpec) ocrJob, err := ocrbootstrap.ValidatedBootstrapSpecToml(bootstrapJobSpec) require.NoError(t, err) - err = bootstrapNode.app.AddJobV2(context.Background(), &ocrJob) + err = bootstrapNode.app.AddJobV2(ctx, &ocrJob) require.NoError(t, err) t.Log("Creating OCR2VRF jobs") @@ -499,7 +500,7 @@ linkEthFeedAddress = "%s" t.Log("Creating OCR2VRF job with spec:", jobSpec) ocrJob2, err2 := validate.ValidatedOracleSpecToml(apps[i].Config.OCR2(), apps[i].Config.Insecure(), jobSpec) require.NoError(t, err2) - err2 = apps[i].AddJobV2(context.Background(), &ocrJob2) + err2 = apps[i].AddJobV2(ctx, &ocrJob2) require.NoError(t, err2) } diff --git a/core/services/ocr2/plugins/ocr2vrf/juelsfeecoin/link_eth_price_provider.go b/core/services/ocr2/plugins/ocr2vrf/juelsfeecoin/link_eth_price_provider.go index d2330d87e4d..2601ae14668 100644 --- a/core/services/ocr2/plugins/ocr2vrf/juelsfeecoin/link_eth_price_provider.go +++ b/core/services/ocr2/plugins/ocr2vrf/juelsfeecoin/link_eth_price_provider.go @@ -10,7 +10,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/ocr2vrf/types" + + "github.com/smartcontractkit/chainlink-vrf/types" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/aggregator_v3_interface" diff --git a/core/services/ocr2/plugins/ocr2vrf/reasonablegasprice/reasonable_gas_price_provider.go b/core/services/ocr2/plugins/ocr2vrf/reasonablegasprice/reasonable_gas_price_provider.go index 43262d6ad50..880b8084338 100644 --- a/core/services/ocr2/plugins/ocr2vrf/reasonablegasprice/reasonable_gas_price_provider.go +++ b/core/services/ocr2/plugins/ocr2vrf/reasonablegasprice/reasonable_gas_price_provider.go @@ -4,9 +4,9 @@ import ( "math/big" "time" - "github.com/smartcontractkit/ocr2vrf/types" + "github.com/smartcontractkit/chainlink-vrf/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" ) diff --git a/core/services/ocr2/plugins/ocr2vrf/reasonablegasprice/reasonable_gas_price_test.go b/core/services/ocr2/plugins/ocr2vrf/reasonablegasprice/reasonable_gas_price_test.go index a33c146564b..ec8b085dea8 100644 --- a/core/services/ocr2/plugins/ocr2vrf/reasonablegasprice/reasonable_gas_price_test.go +++ b/core/services/ocr2/plugins/ocr2vrf/reasonablegasprice/reasonable_gas_price_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" ) func Test_ReasonableGasPrice(t *testing.T) { diff --git a/core/services/ocr2/plugins/ocr2vrf/reportserializer/report_serializer.go b/core/services/ocr2/plugins/ocr2vrf/reportserializer/report_serializer.go index 19b625ff6d8..e4112b55438 100644 --- a/core/services/ocr2/plugins/ocr2vrf/reportserializer/report_serializer.go +++ b/core/services/ocr2/plugins/ocr2vrf/reportserializer/report_serializer.go @@ -4,8 +4,8 @@ import ( "github.com/pkg/errors" "go.dedis.ch/kyber/v3" - "github.com/smartcontractkit/ocr2vrf/ocr2vrf" - types "github.com/smartcontractkit/ocr2vrf/types" + "github.com/smartcontractkit/chainlink-vrf/ocr2vrf" + "github.com/smartcontractkit/chainlink-vrf/types" ) type reportSerializer struct { diff --git a/core/services/ocr2/plugins/ocr2vrf/reportserializer/report_serializer_test.go b/core/services/ocr2/plugins/ocr2vrf/reportserializer/report_serializer_test.go index dcfd627ac9f..32704a55b0f 100644 --- a/core/services/ocr2/plugins/ocr2vrf/reportserializer/report_serializer_test.go +++ b/core/services/ocr2/plugins/ocr2vrf/reportserializer/report_serializer_test.go @@ -7,8 +7,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/ocr2vrf/altbn_128" - "github.com/smartcontractkit/ocr2vrf/types" + "github.com/smartcontractkit/chainlink-vrf/altbn_128" + "github.com/smartcontractkit/chainlink-vrf/types" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2vrf/reportserializer" ) diff --git a/core/services/ocr2/plugins/promwrapper/plugin_test.go b/core/services/ocr2/plugins/promwrapper/plugin_test.go index 5c12c18f852..b4de7f027f3 100644 --- a/core/services/ocr2/plugins/promwrapper/plugin_test.go +++ b/core/services/ocr2/plugins/promwrapper/plugin_test.go @@ -8,10 +8,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/promwrapper/mocks" ) @@ -194,14 +196,16 @@ func TestPlugin_GetLatencies(t *testing.T) { ).(*promPlugin) require.NotEqual(t, nil, promPlugin) + ctx := testutils.Context(t) + // Run OCR methods. - _, err := promPlugin.Query(context.Background(), reportTimestamp) + _, err := promPlugin.Query(ctx, reportTimestamp) require.NoError(t, err) _, ok := promPlugin.queryEndTimes.Load(reportTimestamp) require.Equal(t, true, ok) time.Sleep(qToOLatency) - _, err = promPlugin.Observation(context.Background(), reportTimestamp, nil) + _, err = promPlugin.Observation(ctx, reportTimestamp, nil) require.NoError(t, err) _, ok = promPlugin.queryEndTimes.Load(reportTimestamp) require.Equal(t, false, ok) @@ -209,7 +213,7 @@ func TestPlugin_GetLatencies(t *testing.T) { require.Equal(t, true, ok) time.Sleep(oToRLatency) - _, _, err = promPlugin.Report(context.Background(), reportTimestamp, nil, nil) + _, _, err = promPlugin.Report(ctx, reportTimestamp, nil, nil) require.NoError(t, err) _, ok = promPlugin.observationEndTimes.Load(reportTimestamp) require.Equal(t, false, ok) @@ -217,7 +221,7 @@ func TestPlugin_GetLatencies(t *testing.T) { require.Equal(t, true, ok) time.Sleep(rToALatency) - _, err = promPlugin.ShouldAcceptFinalizedReport(context.Background(), reportTimestamp, nil) + _, err = promPlugin.ShouldAcceptFinalizedReport(ctx, reportTimestamp, nil) require.NoError(t, err) _, ok = promPlugin.reportEndTimes.Load(reportTimestamp) require.Equal(t, false, ok) @@ -225,7 +229,7 @@ func TestPlugin_GetLatencies(t *testing.T) { require.Equal(t, true, ok) time.Sleep(aToTLatency) - _, err = promPlugin.ShouldTransmitAcceptedReport(context.Background(), reportTimestamp, nil) + _, err = promPlugin.ShouldTransmitAcceptedReport(ctx, reportTimestamp, nil) require.NoError(t, err) _, ok = promPlugin.acceptFinalizedReportEndTimes.Load(reportTimestamp) require.Equal(t, false, ok) diff --git a/core/services/ocr2/plugins/s4/factory_test.go b/core/services/ocr2/plugins/s4/factory_test.go index 1b75988d83d..13a36a53823 100644 --- a/core/services/ocr2/plugins/s4/factory_test.go +++ b/core/services/ocr2/plugins/s4/factory_test.go @@ -8,7 +8,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/s4" s4_mocks "github.com/smartcontractkit/chainlink/v2/core/services/s4/mocks" - relaylogger "github.com/smartcontractkit/chainlink-relay/pkg/logger" + commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/libocr/offchainreporting2/types" @@ -18,7 +18,7 @@ import ( func TestS4ReportingPluginFactory_NewReportingPlugin(t *testing.T) { t.Parallel() - logger := relaylogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) + logger := commonlogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) orm := s4_mocks.NewORM(t) f := s4.S4ReportingPluginFactory{ diff --git a/core/services/ocr2/plugins/s4/integration_test.go b/core/services/ocr2/plugins/s4/integration_test.go index 98ccd312e4b..54f0f02ad98 100644 --- a/core/services/ocr2/plugins/s4/integration_test.go +++ b/core/services/ocr2/plugins/s4/integration_test.go @@ -18,7 +18,7 @@ import ( s4_svc "github.com/smartcontractkit/chainlink/v2/core/services/s4" "github.com/smartcontractkit/chainlink/v2/core/utils" - relaylogger "github.com/smartcontractkit/chainlink-relay/pkg/logger" + commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/offchainreporting2/types" @@ -56,7 +56,7 @@ func newDON(t *testing.T, size int, config *s4.PluginConfig) *don { orm := s4_svc.NewPostgresORM(db, logger, pgtest.NewQConfig(false), s4_svc.SharedTableName, ns) orms[i] = orm - ocrLogger := relaylogger.NewOCRWrapper(logger, true, func(msg string) {}) + ocrLogger := commonlogger.NewOCRWrapper(logger, true, func(msg string) {}) plugin, err := s4.NewReportingPlugin(ocrLogger, config, orm) require.NoError(t, err) plugins[i] = plugin diff --git a/core/services/ocr2/plugins/s4/plugin_test.go b/core/services/ocr2/plugins/s4/plugin_test.go index 94c876a4f7f..e2b5d21b847 100644 --- a/core/services/ocr2/plugins/s4/plugin_test.go +++ b/core/services/ocr2/plugins/s4/plugin_test.go @@ -13,13 +13,14 @@ import ( s4_mocks "github.com/smartcontractkit/chainlink/v2/core/services/s4/mocks" "github.com/smartcontractkit/chainlink/v2/core/utils" - relaylogger "github.com/smartcontractkit/chainlink-relay/pkg/logger" + commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "google.golang.org/protobuf/proto" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) func createPluginConfig(maxEntries uint) *s4.PluginConfig { @@ -121,7 +122,7 @@ func rowsToShapshotRows(rows []*s4_svc.Row) []*s4_svc.SnapshotRow { func TestPlugin_NewReportingPlugin(t *testing.T) { t.Parallel() - logger := relaylogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) + logger := commonlogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) orm := s4_mocks.NewORM(t) t.Run("ErrInvalidIntervals", func(t *testing.T) { @@ -167,7 +168,7 @@ func TestPlugin_NewReportingPlugin(t *testing.T) { func TestPlugin_Close(t *testing.T) { t.Parallel() - logger := relaylogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) + logger := commonlogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) config := createPluginConfig(10) orm := s4_mocks.NewORM(t) plugin, err := s4.NewReportingPlugin(logger, config, orm) @@ -180,7 +181,7 @@ func TestPlugin_Close(t *testing.T) { func TestPlugin_ShouldTransmitAcceptedReport(t *testing.T) { t.Parallel() - logger := relaylogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) + logger := commonlogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) config := createPluginConfig(10) orm := s4_mocks.NewORM(t) plugin, err := s4.NewReportingPlugin(logger, config, orm) @@ -194,7 +195,7 @@ func TestPlugin_ShouldTransmitAcceptedReport(t *testing.T) { func TestPlugin_ShouldAcceptFinalizedReport(t *testing.T) { t.Parallel() - logger := relaylogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) + logger := commonlogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) config := createPluginConfig(10) orm := s4_mocks.NewORM(t) plugin, err := s4.NewReportingPlugin(logger, config, orm) @@ -255,7 +256,7 @@ func TestPlugin_ShouldAcceptFinalizedReport(t *testing.T) { func TestPlugin_Query(t *testing.T) { t.Parallel() - logger := relaylogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) + logger := commonlogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) config := createPluginConfig(10) orm := s4_mocks.NewORM(t) plugin, err := s4.NewReportingPlugin(logger, config, orm) @@ -332,7 +333,7 @@ func TestPlugin_Query(t *testing.T) { func TestPlugin_Observation(t *testing.T) { t.Parallel() - logger := relaylogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) + logger := commonlogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) config := createPluginConfig(10) orm := s4_mocks.NewORM(t) plugin, err := s4.NewReportingPlugin(logger, config, orm) @@ -465,7 +466,7 @@ func TestPlugin_Observation(t *testing.T) { func TestPlugin_Report(t *testing.T) { t.Parallel() - logger := relaylogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) + logger := commonlogger.NewOCRWrapper(logger.TestLogger(t), true, func(msg string) {}) config := createPluginConfig(10) orm := s4_mocks.NewORM(t) plugin, err := s4.NewReportingPlugin(logger, config, orm) diff --git a/core/services/ocr2/validate/validate.go b/core/services/ocr2/validate/validate.go index cde1a1f9276..bb9bb03a8ac 100644 --- a/core/services/ocr2/validate/validate.go +++ b/core/services/ocr2/validate/validate.go @@ -12,7 +12,7 @@ import ( libocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/services/job" dkgconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/dkg/config" mercuryconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury/config" @@ -114,6 +114,8 @@ func validateSpec(tree *toml.Tree, spec job.Job) error { return nil case types.Mercury: return validateOCR2MercurySpec(spec.OCR2OracleSpec.PluginConfig, *spec.OCR2OracleSpec.FeedID) + case types.GenericPlugin: + return validateOCR2GenericPluginSpec(spec.OCR2OracleSpec.PluginConfig) case "": return errors.New("no plugin specified") default: @@ -123,6 +125,62 @@ func validateSpec(tree *toml.Tree, spec job.Job) error { return nil } +type PipelineSpec struct { + Name string `json:"name"` + Spec string `json:"spec"` +} + +type Config struct { + Pipelines []PipelineSpec `json:"pipelines"` + PluginConfig map[string]any `json:"pluginConfig"` +} + +type innerConfig struct { + Command string `json:"command"` + ProviderType string `json:"providerType"` + PluginName string `json:"pluginName"` + TelemetryType string `json:"telemetryType"` + Config +} + +type OCR2GenericPluginConfig struct { + innerConfig +} + +func (o *OCR2GenericPluginConfig) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &o.innerConfig) + if err != nil { + return nil + } + + m := map[string]any{} + err = json.Unmarshal(data, &m) + if err != nil { + return err + } + + o.PluginConfig = m + return nil +} + +func validateOCR2GenericPluginSpec(jsonConfig job.JSONConfig) error { + p := OCR2GenericPluginConfig{} + err := json.Unmarshal(jsonConfig.Bytes(), &p) + if err != nil { + return err + } + + if p.PluginName == "" { + return errors.New("generic config invalid: must provide plugin name") + } + + if p.TelemetryType == "" { + return errors.New("generic config invalid: must provide telemetry type") + } + + return nil +} + func validateDKGSpec(jsonConfig job.JSONConfig) error { if jsonConfig == nil { return errors.New("pluginConfig is empty") diff --git a/core/services/ocr2/validate/validate_test.go b/core/services/ocr2/validate/validate_test.go index 98a13ebf8a7..b03f08f6b08 100644 --- a/core/services/ocr2/validate/validate_test.go +++ b/core/services/ocr2/validate/validate_test.go @@ -7,11 +7,12 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/manyminds/api2go/jsonapi" + "github.com/pelletier/go-toml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/job" medianconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/median/config" @@ -580,11 +581,94 @@ KeyID = "6f3b82406688b8ddb944c6f2e6d808f014c8fa8d568d639c25019568c require.Contains(t, err.Error(), "validation error for keyID") }, }, + { + name: "Generic plugin config validation - nothing provided", + toml: ` +type = "offchainreporting2" +schemaVersion = 1 +name = "dkg" +externalJobID = "6d46d85f-d38c-4f4a-9f00-ac29a25b6330" +maxTaskDuration = "1s" +contractID = "0x3e54dCc49F16411A3aaa4cDbC41A25bCa9763Cee" +ocrKeyBundleID = "08d14c6eed757414d72055d28de6caf06535806c6a14e450f3a2f1c854420e17" +p2pv2Bootstrappers = [ + "12D3KooWSbPRwXY4gxFRJT7LWCnjgGbR4S839nfCRCDgQUiNenxa@127.0.0.1:8000" +] +relay = "evm" +pluginType = "plugin" +transmitterID = "0x74103Cf8b436465870b26aa9Fa2F62AD62b22E35" + +[relayConfig] +chainID = 4 + +[pluginConfig.coreConfig] +`, + assertion: func(t *testing.T, os job.Job, err error) { + require.Error(t, err) + require.ErrorContains(t, err, "must provide plugin name") + }, + }, + { + name: "Generic plugin config validation - plugin name provided", + toml: ` +type = "offchainreporting2" +schemaVersion = 1 +name = "dkg" +externalJobID = "6d46d85f-d38c-4f4a-9f00-ac29a25b6330" +maxTaskDuration = "1s" +contractID = "0x3e54dCc49F16411A3aaa4cDbC41A25bCa9763Cee" +ocrKeyBundleID = "08d14c6eed757414d72055d28de6caf06535806c6a14e450f3a2f1c854420e17" +p2pv2Bootstrappers = [ + "12D3KooWSbPRwXY4gxFRJT7LWCnjgGbR4S839nfCRCDgQUiNenxa@127.0.0.1:8000" +] +relay = "evm" +pluginType = "plugin" +transmitterID = "0x74103Cf8b436465870b26aa9Fa2F62AD62b22E35" + +[relayConfig] +chainID = 4 + +[pluginConfig] +pluginName = "median" +`, + assertion: func(t *testing.T, os job.Job, err error) { + require.Error(t, err) + require.ErrorContains(t, err, "must provide telemetry type") + }, + }, + { + name: "Generic plugin config validation - all provided", + toml: ` +type = "offchainreporting2" +schemaVersion = 1 +name = "dkg" +externalJobID = "6d46d85f-d38c-4f4a-9f00-ac29a25b6330" +maxTaskDuration = "1s" +contractID = "0x3e54dCc49F16411A3aaa4cDbC41A25bCa9763Cee" +ocrKeyBundleID = "08d14c6eed757414d72055d28de6caf06535806c6a14e450f3a2f1c854420e17" +p2pv2Bootstrappers = [ + "12D3KooWSbPRwXY4gxFRJT7LWCnjgGbR4S839nfCRCDgQUiNenxa@127.0.0.1:8000" +] +relay = "evm" +pluginType = "plugin" +transmitterID = "0x74103Cf8b436465870b26aa9Fa2F62AD62b22E35" + +[relayConfig] +chainID = 4 + +[pluginConfig] +pluginName = "median" +telemetryType = "median" +`, + assertion: func(t *testing.T, os job.Job, err error) { + require.NoError(t, err) + }, + }, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - c := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Insecure.OCRDevelopmentMode = testutils.Ptr(false) // tests run with OCRDevelopmentMode by default. if tc.overrides != nil { tc.overrides(c, s) @@ -595,3 +679,42 @@ KeyID = "6f3b82406688b8ddb944c6f2e6d808f014c8fa8d568d639c25019568c }) } } + +type envelope struct { + PluginConfig *validate.OCR2GenericPluginConfig +} + +func TestOCR2GenericPluginConfig_Unmarshal(t *testing.T) { + payload := ` +[pluginConfig] +pluginName = "median" +telemetryType = "median" +foo = "bar" + +[[pluginConfig.pipelines]] +name = "default" +spec = "a spec" +` + tree, err := toml.Load(payload) + require.NoError(t, err) + + // Load the toml how we load it in the plugin, i.e. convert to + // map[string]any first, then treat as JSON + o := map[string]any{} + err = tree.Unmarshal(&o) + require.NoError(t, err) + + b, err := json.Marshal(o) + require.NoError(t, err) + + e := &envelope{} + err = json.Unmarshal(b, e) + require.NoError(t, err) + + pc := e.PluginConfig + assert.Equal(t, "bar", pc.PluginConfig["foo"]) + assert.Len(t, pc.Pipelines, 1) + assert.Equal(t, validate.PipelineSpec{Name: "default", Spec: "a spec"}, pc.Pipelines[0]) + assert.Equal(t, "median", pc.PluginName) + assert.Equal(t, "median", pc.TelemetryType) +} diff --git a/core/services/ocrbootstrap/database_test.go b/core/services/ocrbootstrap/database_test.go index 2f160eff582..e00e318c69c 100644 --- a/core/services/ocrbootstrap/database_test.go +++ b/core/services/ocrbootstrap/database_test.go @@ -3,7 +3,7 @@ package ocrbootstrap_test import ( "testing" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" diff --git a/core/services/ocrbootstrap/delegate.go b/core/services/ocrbootstrap/delegate.go index 3910ca05d38..7912741802c 100644 --- a/core/services/ocrbootstrap/delegate.go +++ b/core/services/ocrbootstrap/delegate.go @@ -7,12 +7,13 @@ import ( "github.com/pkg/errors" + "github.com/jmoiron/sqlx" + ocr "github.com/smartcontractkit/libocr/offchainreporting2plus" - "github.com/smartcontractkit/sqlx" - relaylogger "github.com/smartcontractkit/chainlink-relay/pkg/logger" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -165,7 +166,7 @@ func (d *Delegate) ServicesForSpec(jb job.Job) (services []job.ServiceCtx, err e ContractConfigTracker: configProvider.ContractConfigTracker(), Database: NewDB(d.db.DB, spec.ID, lggr), LocalConfig: lc, - Logger: relaylogger.NewOCRWrapper(lggr.Named("OCRBootstrap"), d.ocr2Cfg.TraceLogging(), func(msg string) { + Logger: commonlogger.NewOCRWrapper(lggr.Named("OCRBootstrap"), d.ocr2Cfg.TraceLogging(), func(msg string) { logger.Sugared(lggr).ErrorIf(d.jobORM.RecordError(jb.ID, msg), "unable to record error") }), OffchainConfigDigester: configProvider.OffchainConfigDigester(), diff --git a/core/services/ocrcommon/block_translator.go b/core/services/ocrcommon/block_translator.go index 48b96416998..dcac83fd2bd 100644 --- a/core/services/ocrcommon/block_translator.go +++ b/core/services/ocrcommon/block_translator.go @@ -4,9 +4,9 @@ import ( "context" "math/big" + "github.com/smartcontractkit/chainlink/v2/common/config" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" ) diff --git a/core/services/ocrcommon/config.go b/core/services/ocrcommon/config.go index a61a47519cc..2fcc877610c 100644 --- a/core/services/ocrcommon/config.go +++ b/core/services/ocrcommon/config.go @@ -5,7 +5,7 @@ import ( "github.com/smartcontractkit/libocr/commontypes" - "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/common/config" ) type Config interface { diff --git a/core/services/ocrcommon/data_source.go b/core/services/ocrcommon/data_source.go index ed832e45fcf..706376fbfd1 100644 --- a/core/services/ocrcommon/data_source.go +++ b/core/services/ocrcommon/data_source.go @@ -33,9 +33,13 @@ type inMemoryDataSource struct { chEnhancedTelemetry chan<- EnhancedTelemetryData } +type Saver interface { + Save(run *pipeline.Run) +} + type dataSourceBase struct { inMemoryDataSource - runResults chan<- *pipeline.Run + saver Saver } // dataSource implements dataSourceBase with the proper Observe return type for ocr1 @@ -55,7 +59,7 @@ type ObservationTimestamp struct { ConfigDigest string } -func NewDataSourceV1(pr pipeline.Runner, jb job.Job, spec pipeline.Spec, lggr logger.Logger, runResults chan<- *pipeline.Run, chEnhancedTelemetry chan EnhancedTelemetryData) ocr1types.DataSource { +func NewDataSourceV1(pr pipeline.Runner, jb job.Job, spec pipeline.Spec, lggr logger.Logger, s Saver, chEnhancedTelemetry chan EnhancedTelemetryData) ocr1types.DataSource { return &dataSource{ dataSourceBase: dataSourceBase{ inMemoryDataSource: inMemoryDataSource{ @@ -65,12 +69,12 @@ func NewDataSourceV1(pr pipeline.Runner, jb job.Job, spec pipeline.Spec, lggr lo lggr: lggr, chEnhancedTelemetry: chEnhancedTelemetry, }, - runResults: runResults, + saver: s, }, } } -func NewDataSourceV2(pr pipeline.Runner, jb job.Job, spec pipeline.Spec, lggr logger.Logger, runResults chan<- *pipeline.Run, enhancedTelemChan chan EnhancedTelemetryData) median.DataSource { +func NewDataSourceV2(pr pipeline.Runner, jb job.Job, spec pipeline.Spec, lggr logger.Logger, s Saver, enhancedTelemChan chan EnhancedTelemetryData) median.DataSource { return &dataSourceV2{ dataSourceBase: dataSourceBase{ inMemoryDataSource: inMemoryDataSource{ @@ -80,7 +84,7 @@ func NewDataSourceV2(pr pipeline.Runner, jb job.Job, spec pipeline.Spec, lggr lo lggr: lggr, chEnhancedTelemetry: enhancedTelemChan, }, - runResults: runResults, + saver: s, }, } } @@ -144,6 +148,8 @@ func (ds *inMemoryDataSource) executeRun(ctx context.Context, timestamp Observat FinalResults: finalResult, RepTimestamp: timestamp, }) + } else { + ds.lggr.Infow("Enhanced telemetry is disabled for job", "job", ds.jb.Name) } return run, finalResult, err @@ -187,19 +193,13 @@ func (ds *dataSourceBase) observe(ctx context.Context, timestamp ObservationTime return nil, err } - // Do the database write in a non-blocking fashion + // Save() does the database write in a non-blocking fashion // so we can return the observation results immediately. // This is helpful in the case of a blocking API call, where // we reach the passed in context deadline and we want to // immediately return any result we have and do not want to have // a db write block that. - select { - case ds.runResults <- run: - default: - // If we're unable to enqueue a write, still return the value we have but warn. - ds.lggr.Warnf("unable to enqueue run save for job ID %d, buffer full", ds.inMemoryDataSource.spec.JobID) - return ds.inMemoryDataSource.parse(finalResult) - } + ds.saver.Save(run) return ds.inMemoryDataSource.parse(finalResult) } diff --git a/core/services/ocrcommon/data_source_test.go b/core/services/ocrcommon/data_source_test.go index 51a004f1f05..3ffc6b31e8c 100644 --- a/core/services/ocrcommon/data_source_test.go +++ b/core/services/ocrcommon/data_source_test.go @@ -93,8 +93,17 @@ func Test_InMemoryDataSourceWithProm(t *testing.T) { } +type mockSaver struct { + r *pipeline.Run +} + +func (ms *mockSaver) Save(r *pipeline.Run) { + ms.r = r +} + func Test_NewDataSourceV2(t *testing.T) { runner := pipelinemocks.NewRunner(t) + ms := &mockSaver{} runner.On("ExecuteRun", mock.Anything, mock.AnythingOfType("pipeline.Spec"), mock.Anything, mock.Anything). Return(&pipeline.Run{}, pipeline.TaskRunResults{ { @@ -106,16 +115,16 @@ func Test_NewDataSourceV2(t *testing.T) { }, }, nil) - resChan := make(chan *pipeline.Run, 100) - ds := ocrcommon.NewDataSourceV2(runner, job.Job{}, pipeline.Spec{}, logger.TestLogger(t), resChan, nil) + ds := ocrcommon.NewDataSourceV2(runner, job.Job{}, pipeline.Spec{}, logger.TestLogger(t), ms, nil) val, err := ds.Observe(testutils.Context(t), types.ReportTimestamp{}) 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 + assert.Equal(t, mockValue, val.String()) // returns expected value after pipeline run + assert.Equal(t, &pipeline.Run{}, ms.r) // expected data properly passed to channel } func Test_NewDataSourceV1(t *testing.T) { runner := pipelinemocks.NewRunner(t) + ms := &mockSaver{} runner.On("ExecuteRun", mock.Anything, mock.AnythingOfType("pipeline.Spec"), mock.Anything, mock.Anything). Return(&pipeline.Run{}, pipeline.TaskRunResults{ { @@ -127,10 +136,9 @@ func Test_NewDataSourceV1(t *testing.T) { }, }, nil) - resChan := make(chan *pipeline.Run, 100) - ds := ocrcommon.NewDataSourceV1(runner, job.Job{}, pipeline.Spec{}, logger.TestLogger(t), resChan, nil) + ds := ocrcommon.NewDataSourceV1(runner, job.Job{}, pipeline.Spec{}, logger.TestLogger(t), ms, nil) val, err := ds.Observe(testutils.Context(t), ocrtypes.ReportTimestamp{}) require.NoError(t, err) assert.Equal(t, mockValue, new(big.Int).Set(val).String()) // returns expected value after pipeline run - assert.Equal(t, &pipeline.Run{}, <-resChan) // expected data properly passed to channel + assert.Equal(t, &pipeline.Run{}, ms.r) // expected data properly passed to channel } diff --git a/core/services/ocrcommon/discoverer_database_test.go b/core/services/ocrcommon/discoverer_database_test.go index 2300316092d..ff1a931b017 100644 --- a/core/services/ocrcommon/discoverer_database_test.go +++ b/core/services/ocrcommon/discoverer_database_test.go @@ -1,23 +1,24 @@ package ocrcommon_test import ( + "crypto/rand" "testing" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" - + cryptop2p "github.com/libp2p/go-libp2p-core/crypto" + p2ppeer "github.com/libp2p/go-libp2p-core/peer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" ) func Test_DiscovererDatabase(t *testing.T) { db := pgtest.NewSqlDB(t) - localPeerID1 := cltest.MustRandomP2PPeerID(t) - localPeerID2 := cltest.MustRandomP2PPeerID(t) + localPeerID1 := mustRandomP2PPeerID(t) + localPeerID2 := mustRandomP2PPeerID(t) dd1 := ocrcommon.NewDiscovererDatabase(db, localPeerID1) dd2 := ocrcommon.NewDiscovererDatabase(db, localPeerID2) @@ -81,3 +82,11 @@ func Test_DiscovererDatabase(t *testing.T) { }) } + +func mustRandomP2PPeerID(t *testing.T) p2ppeer.ID { + p2pPrivkey, _, err := cryptop2p.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + id, err := p2ppeer.IDFromPrivateKey(p2pPrivkey) + require.NoError(t, err) + return id +} diff --git a/core/services/ocrcommon/peer_wrapper.go b/core/services/ocrcommon/peer_wrapper.go index 1d2eca46664..a7d510ef901 100644 --- a/core/services/ocrcommon/peer_wrapper.go +++ b/core/services/ocrcommon/peer_wrapper.go @@ -9,21 +9,23 @@ import ( p2ppeer "github.com/libp2p/go-libp2p-core/peer" p2ppeerstore "github.com/libp2p/go-libp2p-core/peerstore" "github.com/pkg/errors" + "go.uber.org/multierr" + + "github.com/jmoiron/sqlx" + ocrnetworking "github.com/smartcontractkit/libocr/networking" ocrnetworkingtypes "github.com/smartcontractkit/libocr/networking/types" ocr1types "github.com/smartcontractkit/libocr/offchainreporting/types" ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/sqlx" - "go.uber.org/multierr" - relaylogger "github.com/smartcontractkit/chainlink-relay/pkg/logger" + commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) type PeerWrapperOCRConfig interface { @@ -43,7 +45,7 @@ type ( // SingletonPeerWrapper manages all libocr peers for the application SingletonPeerWrapper struct { - utils.StartStopOnce + services.StateMachine keyStore keystore.Master p2pCfg config.P2P ocrCfg PeerWrapperOCRConfig @@ -97,9 +99,7 @@ func NewSingletonPeerWrapper(keyStore keystore.Master, p2pCfg config.P2P, ocrCfg } } -func (p *SingletonPeerWrapper) IsStarted() bool { - return p.State() == utils.StartStopOnce_Started -} +func (p *SingletonPeerWrapper) IsStarted() bool { return p.Ready() == nil } // Start starts SingletonPeerWrapper. func (p *SingletonPeerWrapper) Start(context.Context) error { @@ -195,7 +195,7 @@ func (p *SingletonPeerWrapper) peerConfig() (ocrnetworking.PeerConfig, error) { peerConfig := ocrnetworking.PeerConfig{ NetworkingStack: config.NetworkStack(), PrivKey: key.PrivKey, - Logger: relaylogger.NewOCRWrapper(p.lggr, p.ocrCfg.TraceLogging(), func(string) {}), + Logger: commonlogger.NewOCRWrapper(p.lggr, p.ocrCfg.TraceLogging(), func(string) {}), // V1 config V1ListenIP: config.V1().ListenIP(), V1ListenPort: p2pPort, diff --git a/core/services/ocrcommon/peer_wrapper_test.go b/core/services/ocrcommon/peer_wrapper_test.go index 45edc64e9db..209bc6b969b 100644 --- a/core/services/ocrcommon/peer_wrapper_test.go +++ b/core/services/ocrcommon/peer_wrapper_test.go @@ -10,7 +10,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -25,7 +25,7 @@ func Test_SingletonPeerWrapper_Start(t *testing.T) { db := pgtest.NewSqlxDB(t) - peerID, err := p2ppeer.Decode("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") + peerID, err := p2ppeer.Decode(configtest.DefaultPeerID) require.NoError(t, err) t.Run("with no p2p keys returns error", func(t *testing.T) { diff --git a/core/services/ocrcommon/peerstore.go b/core/services/ocrcommon/peerstore.go index 6bad2db4ee5..1d859184ab5 100644 --- a/core/services/ocrcommon/peerstore.go +++ b/core/services/ocrcommon/peerstore.go @@ -11,7 +11,10 @@ import ( "github.com/libp2p/go-libp2p-peerstore/pstoremem" ma "github.com/multiformats/go-multiaddr" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/recovery" @@ -30,7 +33,7 @@ type ( } Pstorewrapper struct { - utils.StartStopOnce + services.StateMachine Peerstore p2ppeerstore.Peerstore peerID string q pg.Q @@ -50,7 +53,7 @@ func NewPeerstoreWrapper(db *sqlx.DB, writeInterval time.Duration, peerID p2pkey q := pg.NewQ(db, namedLogger, cfg) return &Pstorewrapper{ - utils.StartStopOnce{}, + services.StateMachine{}, pstoremem.NewPeerstore(), peerID.Raw(), q, diff --git a/core/services/ocrcommon/peerstore_test.go b/core/services/ocrcommon/peerstore_test.go index ba55e0767ab..6e692153564 100644 --- a/core/services/ocrcommon/peerstore_test.go +++ b/core/services/ocrcommon/peerstore_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" @@ -21,7 +21,7 @@ import ( func Test_Peerstore_Start(t *testing.T) { db := pgtest.NewSqlxDB(t) - peerID, err := p2ppeer.Decode("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") + peerID, err := p2ppeer.Decode(configtest.DefaultPeerID) require.NoError(t, err) nonExistentP2PPeerID, err := p2ppeer.Decode("12D3KooWAdCzaesXyezatDzgGvCngqsBqoUqnV9PnVc46jsVt2i9") @@ -72,7 +72,7 @@ func Test_Peerstore_Start(t *testing.T) { func Test_Peerstore_WriteToDB(t *testing.T) { db := pgtest.NewSqlxDB(t) - peerID, err := p2ppeer.Decode("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") + peerID, err := p2ppeer.Decode(configtest.DefaultPeerID) require.NoError(t, err) cfg := configtest.NewTestGeneralConfig(t) diff --git a/core/services/ocrcommon/run_saver.go b/core/services/ocrcommon/run_saver.go index 3aa3aff876e..b1a0fc7b141 100644 --- a/core/services/ocrcommon/run_saver.go +++ b/core/services/ocrcommon/run_saver.go @@ -3,16 +3,16 @@ package ocrcommon import ( "context" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) type RunResultSaver struct { - utils.StartStopOnce + services.StateMachine maxSuccessfulRuns uint64 - runResults <-chan *pipeline.Run + runResults chan *pipeline.Run pipelineRunner pipeline.Runner done chan struct{} logger logger.Logger @@ -24,18 +24,28 @@ func (r *RunResultSaver) HealthReport() map[string]error { func (r *RunResultSaver) Name() string { return r.logger.Name() } -func NewResultRunSaver(runResults <-chan *pipeline.Run, pipelineRunner pipeline.Runner, done chan struct{}, - logger logger.Logger, maxSuccessfulRuns uint64, +func NewResultRunSaver(pipelineRunner pipeline.Runner, + logger logger.Logger, maxSuccessfulRuns uint64, resultsWriteDepth uint64, ) *RunResultSaver { return &RunResultSaver{ maxSuccessfulRuns: maxSuccessfulRuns, - runResults: runResults, + runResults: make(chan *pipeline.Run, resultsWriteDepth), pipelineRunner: pipelineRunner, - done: done, + done: make(chan struct{}), logger: logger.Named("RunResultSaver"), } } +// Save sends the run on the internal `runResults` channel for saving. +// IMPORTANT: if the `runResults` pipeline is full, the run will be dropped. +func (r *RunResultSaver) Save(run *pipeline.Run) { + select { + case r.runResults <- run: + default: + r.logger.Warnw("RunSaver: the write queue was full, dropping run") + } +} + // Start starts RunResultSaver. func (r *RunResultSaver) Start(context.Context) error { return r.StartOnce("RunResultSaver", func() error { diff --git a/core/services/ocrcommon/run_saver_test.go b/core/services/ocrcommon/run_saver_test.go index 7d20a7a202e..73697d181bc 100644 --- a/core/services/ocrcommon/run_saver_test.go +++ b/core/services/ocrcommon/run_saver_test.go @@ -14,13 +14,11 @@ import ( func TestRunSaver(t *testing.T) { pipelineRunner := mocks.NewRunner(t) - rr := make(chan *pipeline.Run, 100) rs := NewResultRunSaver( - rr, pipelineRunner, - make(chan struct{}), logger.TestLogger(t), 1000, + 100, ) require.NoError(t, rs.Start(testutils.Context(t))) for i := 0; i < 100; i++ { @@ -31,7 +29,7 @@ func TestRunSaver(t *testing.T) { args.Get(0).(*pipeline.Run).ID = int64(d) }). Once() - rr <- &pipeline.Run{ID: int64(i)} + rs.Save(&pipeline.Run{ID: int64(i)}) } require.NoError(t, rs.Close()) } diff --git a/core/services/ocrcommon/telemetry.go b/core/services/ocrcommon/telemetry.go index 54a5002093d..c9d3e85cd2c 100644 --- a/core/services/ocrcommon/telemetry.go +++ b/core/services/ocrcommon/telemetry.go @@ -4,20 +4,26 @@ import ( "context" "encoding/json" "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" + "google.golang.org/protobuf/proto" + "github.com/smartcontractkit/libocr/commontypes" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "google.golang.org/protobuf/proto" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" "github.com/smartcontractkit/chainlink/v2/core/utils" - relaymercuryv1 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v1" + relaymercuryv1 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v1" + relaymercuryv2 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v2" + relaymercuryv3 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v3" ) type eaTelemetry struct { @@ -35,13 +41,19 @@ type EnhancedTelemetryData struct { } type EnhancedTelemetryMercuryData struct { - TaskRunResults pipeline.TaskRunResults - Observation relaymercuryv1.Observation - RepTimestamp ocrtypes.ReportTimestamp + V1Observation *relaymercuryv1.Observation + V2Observation *relaymercuryv2.Observation + V3Observation *relaymercuryv3.Observation + TaskRunResults pipeline.TaskRunResults + RepTimestamp ocrtypes.ReportTimestamp + FeedVersion mercuryutils.FeedVersion + FetchMaxFinalizedTimestamp bool + IsLinkFeed bool + IsNativeFeed bool } type EnhancedTelemetryService[T EnhancedTelemetryData | EnhancedTelemetryMercuryData] struct { - utils.StartStopOnce + services.StateMachine chTelem <-chan T chDone chan struct{} @@ -68,13 +80,13 @@ func (e *EnhancedTelemetryService[T]) Start(context.Context) error { for { select { case t := <-e.chTelem: - switch any(t).(type) { + switch v := any(t).(type) { case EnhancedTelemetryData: - s := any(t).(EnhancedTelemetryData) - e.collectEATelemetry(s.TaskRunResults, s.FinalResults, s.RepTimestamp) + e.collectEATelemetry(v.TaskRunResults, v.FinalResults, v.RepTimestamp) case EnhancedTelemetryMercuryData: - s := any(t).(EnhancedTelemetryMercuryData) - e.collectMercuryEnhancedTelemetry(s.Observation, s.TaskRunResults, s.RepTimestamp) + e.collectMercuryEnhancedTelemetry(v) + default: + e.lggr.Errorf("unrecognised telemetry data type: %T", t) } case <-e.chDone: return @@ -223,14 +235,19 @@ func (e *EnhancedTelemetryService[T]) collectAndSend(trrs *pipeline.TaskRunResul continue } + if trr.Result.Error != nil { + e.lggr.Warnw(fmt.Sprintf("cannot get bridge response from bridge task, job %d, id %s", e.job.ID, trr.Task.DotID()), "err", trr.Result.Error) + continue + } bridgeRawResponse, ok := trr.Result.Value.(string) if !ok { - e.lggr.Warnf("cannot get bridge response from bridge task, job %d, id %s", e.job.ID, trr.Task.DotID()) + e.lggr.Warnf("cannot parse bridge response from bridge task, job %d, id %s: expected string, got: %v (type %T)", e.job.ID, trr.Task.DotID(), trr.Result.Value, trr.Result.Value) continue } eaTelem, err := parseEATelemetry([]byte(bridgeRawResponse)) if err != nil { - e.lggr.Warnf("cannot parse EA telemetry, job %d, id %s", e.job.ID, trr.Task.DotID()) + e.lggr.Warnw(fmt.Sprintf("cannot parse EA telemetry, job %d, id %s", e.job.ID, trr.Task.DotID()), "err", err) + continue } value := e.getParsedValue(trrs, trr) @@ -253,7 +270,7 @@ func (e *EnhancedTelemetryService[T]) collectAndSend(trrs *pipeline.TaskRunResul bytes, err := proto.Marshal(t) if err != nil { - e.lggr.Warnf("protobuf marshal failed %v", err.Error()) + e.lggr.Warnw("protobuf marshal failed", "err", err) continue } @@ -263,14 +280,81 @@ func (e *EnhancedTelemetryService[T]) collectAndSend(trrs *pipeline.TaskRunResul // collectMercuryEnhancedTelemetry checks if enhanced telemetry should be collected, fetches the information needed and // sends the telemetry -func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(obs relaymercuryv1.Observation, trrs pipeline.TaskRunResults, repts ocrtypes.ReportTimestamp) { +func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(d EnhancedTelemetryMercuryData) { if e.monitoringEndpoint == nil { return } - obsBenchmarkPrice, obsBid, obsAsk, obsBlockNum, obsBlockHash, obsBlockTimestamp := e.getFinalValues(obs) + // v1 fields + var bn int64 + var bh string + var bt uint64 + // v1+v2+v3 fields + bp := big.NewInt(0) + //v1+v3 fields + bid := big.NewInt(0) + ask := big.NewInt(0) + // v2+v3 fields + var mfts, lp, np int64 + + switch { + case d.V1Observation != nil: + obs := *d.V1Observation + if obs.CurrentBlockNum.Err == nil { + bn = obs.CurrentBlockNum.Val + } + if obs.CurrentBlockHash.Err == nil { + bh = common.BytesToHash(obs.CurrentBlockHash.Val).Hex() + } + if obs.CurrentBlockTimestamp.Err == nil { + bt = obs.CurrentBlockTimestamp.Val + } + if obs.BenchmarkPrice.Err == nil && obs.BenchmarkPrice.Val != nil { + bp = obs.BenchmarkPrice.Val + } + if obs.Bid.Err == nil && obs.Bid.Val != nil { + bid = obs.Bid.Val + } + if obs.Ask.Err == nil && obs.Ask.Val != nil { + ask = obs.Ask.Val + } + case d.V2Observation != nil: + obs := *d.V2Observation + if obs.MaxFinalizedTimestamp.Err == nil { + mfts = obs.MaxFinalizedTimestamp.Val + } + if obs.LinkPrice.Err == nil && obs.LinkPrice.Val != nil { + lp = obs.LinkPrice.Val.Int64() + } + if obs.NativePrice.Err == nil && obs.NativePrice.Val != nil { + np = obs.NativePrice.Val.Int64() + } + if obs.BenchmarkPrice.Err == nil && obs.BenchmarkPrice.Val != nil { + bp = obs.BenchmarkPrice.Val + } + case d.V3Observation != nil: + obs := *d.V3Observation + if obs.MaxFinalizedTimestamp.Err == nil { + mfts = obs.MaxFinalizedTimestamp.Val + } + if obs.LinkPrice.Err == nil && obs.LinkPrice.Val != nil { + lp = obs.LinkPrice.Val.Int64() + } + if obs.NativePrice.Err == nil && obs.NativePrice.Val != nil { + np = obs.NativePrice.Val.Int64() + } + if obs.BenchmarkPrice.Err == nil && obs.BenchmarkPrice.Val != nil { + bp = obs.BenchmarkPrice.Val + } + if obs.Bid.Err == nil && obs.Bid.Val != nil { + bid = obs.Bid.Val + } + if obs.Ask.Err == nil && obs.Ask.Val != nil { + ask = obs.Ask.Val + } + } - for _, trr := range trrs { + for _, trr := range d.TaskRunResults { if trr.Task.Type() != pipeline.TaskTypeBridge { continue } @@ -278,39 +362,50 @@ func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(obs relaym bridgeRawResponse, ok := trr.Result.Value.(string) if !ok { - e.lggr.Warnf("cannot get bridge response from bridge task, job %d, id %s", e.job.ID, trr.Task.DotID()) + e.lggr.Warnf("cannot get bridge response from bridge task, job %d, id %s, expected string got %T", e.job.ID, trr.Task.DotID(), trr.Result.Value) continue } eaTelem, err := parseEATelemetry([]byte(bridgeRawResponse)) if err != nil { - e.lggr.Warnf("cannot parse EA telemetry, job %d, id %s", e.job.ID, trr.Task.DotID()) + e.lggr.Warnw(fmt.Sprintf("cannot parse EA telemetry, job %d, id %s", e.job.ID, trr.Task.DotID()), "err", err) } assetSymbol := e.getAssetSymbolFromRequestData(bridgeTask.RequestData) - benchmarkPrice, bidPrice, askPrice := e.getPricesFromResults(trr, &trrs) + + benchmarkPrice, bidPrice, askPrice := e.getPricesFromResults(trr, d.TaskRunResults, d.FeedVersion) t := &telem.EnhancedEAMercury{ - DataSource: eaTelem.DataSource, - DpBenchmarkPrice: benchmarkPrice, - DpBid: bidPrice, - DpAsk: askPrice, - CurrentBlockNumber: obsBlockNum, - CurrentBlockHash: common.BytesToHash(obsBlockHash).String(), - CurrentBlockTimestamp: obsBlockTimestamp, - BridgeTaskRunStartedTimestamp: trr.CreatedAt.UnixMilli(), - BridgeTaskRunEndedTimestamp: trr.FinishedAt.Time.UnixMilli(), - ProviderRequestedTimestamp: eaTelem.ProviderRequestedTimestamp, - ProviderReceivedTimestamp: eaTelem.ProviderReceivedTimestamp, - ProviderDataStreamEstablished: eaTelem.ProviderDataStreamEstablished, - ProviderIndicatedTime: eaTelem.ProviderIndicatedTime, - Feed: e.job.OCR2OracleSpec.FeedID.Hex(), - ObservationBenchmarkPrice: obsBenchmarkPrice, - ObservationBid: obsBid, - ObservationAsk: obsAsk, - ConfigDigest: repts.ConfigDigest.Hex(), - Round: int64(repts.Round), - Epoch: int64(repts.Epoch), - AssetSymbol: assetSymbol, + DataSource: eaTelem.DataSource, + DpBenchmarkPrice: benchmarkPrice, + DpBid: bidPrice, + DpAsk: askPrice, + CurrentBlockNumber: bn, + CurrentBlockHash: bh, + CurrentBlockTimestamp: bt, + FetchMaxFinalizedTimestamp: d.FetchMaxFinalizedTimestamp, + MaxFinalizedTimestamp: mfts, + BridgeTaskRunStartedTimestamp: trr.CreatedAt.UnixMilli(), + BridgeTaskRunEndedTimestamp: trr.FinishedAt.Time.UnixMilli(), + ProviderRequestedTimestamp: eaTelem.ProviderRequestedTimestamp, + ProviderReceivedTimestamp: eaTelem.ProviderReceivedTimestamp, + ProviderDataStreamEstablished: eaTelem.ProviderDataStreamEstablished, + ProviderIndicatedTime: eaTelem.ProviderIndicatedTime, + Feed: e.job.OCR2OracleSpec.FeedID.Hex(), + ObservationBenchmarkPrice: bp.Int64(), + ObservationBid: bid.Int64(), + ObservationAsk: ask.Int64(), + ObservationBenchmarkPriceString: stringOrEmpty(bp), + ObservationBidString: stringOrEmpty(bid), + ObservationAskString: stringOrEmpty(ask), + IsLinkFeed: d.IsLinkFeed, + LinkPrice: lp, + IsNativeFeed: d.IsNativeFeed, + NativePrice: np, + ConfigDigest: d.RepTimestamp.ConfigDigest.Hex(), + Round: int64(d.RepTimestamp.Round), + Epoch: int64(d.RepTimestamp.Epoch), + AssetSymbol: assetSymbol, + Version: uint32(d.FeedVersion), } bytes, err := proto.Marshal(t) @@ -343,19 +438,19 @@ func (e *EnhancedTelemetryService[T]) getAssetSymbolFromRequestData(requestData } // ShouldCollectEnhancedTelemetryMercury checks if enhanced telemetry should be collected and sent -func ShouldCollectEnhancedTelemetryMercury(job *job.Job) bool { - if job.Type.String() == pipeline.OffchainReporting2JobType && job.OCR2OracleSpec != nil { - return job.OCR2OracleSpec.CaptureEATelemetry +func ShouldCollectEnhancedTelemetryMercury(jb job.Job) bool { + if jb.Type.String() == pipeline.OffchainReporting2JobType && jb.OCR2OracleSpec != nil { + return jb.OCR2OracleSpec.CaptureEATelemetry } return false } // getPricesFromResults parses the pipeline.TaskRunResults for pipeline.TaskTypeJSONParse and gets the benchmarkPrice, // bid and ask. This functions expects the pipeline.TaskRunResults to be correctly ordered -func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.TaskRunResult, allTasks *pipeline.TaskRunResults) (float64, float64, float64) { +func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.TaskRunResult, allTasks pipeline.TaskRunResults, mercuryVersion mercuryutils.FeedVersion) (float64, float64, float64) { var benchmarkPrice, askPrice, bidPrice float64 var err error - //We rely on task results to be sorted in the correct order + // We rely on task results to be sorted in the correct order benchmarkPriceTask := allTasks.GetNextTaskOf(startTask) if benchmarkPriceTask == nil { e.lggr.Warnf("cannot parse enhanced EA telemetry benchmark price, task is nil, job %d, id %s", e.job.ID) @@ -372,12 +467,18 @@ func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.Ta } } + // mercury version 2 only supports benchmarkPrice + if mercuryVersion == 2 { + return benchmarkPrice, 0, 0 + } + bidTask := allTasks.GetNextTaskOf(*benchmarkPriceTask) if bidTask == nil { e.lggr.Warnf("cannot parse enhanced EA telemetry bid price, task is nil, job %d, id %s", e.job.ID) return benchmarkPrice, 0, 0 } - if bidTask.Task.Type() == pipeline.TaskTypeJSONParse { + + if bidTask != nil && bidTask.Task.Type() == pipeline.TaskTypeJSONParse { if bidTask.Result.Error != nil { e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry bid price, job %d, id %s: %s", e.job.ID, bidTask.Task.DotID(), bidTask.Result.Error), "err", bidTask.Result.Error) } else { @@ -393,7 +494,7 @@ func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.Ta e.lggr.Warnf("cannot parse enhanced EA telemetry ask price, task is nil, job %d, id %s", e.job.ID) return benchmarkPrice, bidPrice, 0 } - if askTask.Task.Type() == pipeline.TaskTypeJSONParse { + if askTask != nil && askTask.Task.Type() == pipeline.TaskTypeJSONParse { if bidTask.Result.Error != nil { e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry ask price, job %d, id %s: %s", e.job.ID, askTask.Task.DotID(), askTask.Result.Error), "err", askTask.Result.Error) } else { @@ -407,21 +508,11 @@ func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.Ta return benchmarkPrice, bidPrice, askPrice } -// getFinalValues runs a parse on the pipeline.TaskRunResults and returns the values -func (e *EnhancedTelemetryService[T]) getFinalValues(obs relaymercuryv1.Observation) (int64, int64, int64, int64, []byte, uint64) { - var benchmarkPrice, bid, ask int64 - - if obs.BenchmarkPrice.Val != nil { - benchmarkPrice = obs.BenchmarkPrice.Val.Int64() - } - if obs.Bid.Val != nil { - bid = obs.Bid.Val.Int64() - } - if obs.Ask.Val != nil { - ask = obs.Ask.Val.Int64() +// MaybeEnqueueEnhancedTelem sends data to the telemetry channel for processing +func MaybeEnqueueEnhancedTelem(jb job.Job, ch chan<- EnhancedTelemetryMercuryData, data EnhancedTelemetryMercuryData) { + if ShouldCollectEnhancedTelemetryMercury(jb) { + EnqueueEnhancedTelem[EnhancedTelemetryMercuryData](ch, data) } - - return benchmarkPrice, bid, ask, obs.CurrentBlockNum.Val, obs.CurrentBlockHash.Val, obs.CurrentBlockTimestamp.Val } // EnqueueEnhancedTelem sends data to the telemetry channel for processing @@ -441,3 +532,10 @@ func getResultFloat64(task *pipeline.TaskRunResult) (float64, error) { resultFloat64, _ := result.Float64() return resultFloat64, nil } + +func stringOrEmpty(n *big.Int) string { + if n.Cmp(big.NewInt(0)) == 0 { + return "" + } + return n.String() +} diff --git a/core/services/ocrcommon/telemetry_test.go b/core/services/ocrcommon/telemetry_test.go index 88fb7de3e39..24c798259d9 100644 --- a/core/services/ocrcommon/telemetry_test.go +++ b/core/services/ocrcommon/telemetry_test.go @@ -6,15 +6,17 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" "google.golang.org/protobuf/proto" - "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury" - mercuryv1 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v1" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury" + mercuryv1 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v1" + mercury_v2 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v2" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -188,7 +190,7 @@ func TestSendEATelemetry(t *testing.T) { wg := sync.WaitGroup{} ingressClient := mocks.NewTelemetryService(t) ingressAgent := telemetry.NewIngressAgentWrapper(ingressClient) - monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("0xa", synchronization.EnhancedEA, "test-network", "test-chainID") + monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("test-network", "test-chainID", "0xa", synchronization.EnhancedEA) var sentMessage []byte ingressClient.On("Send", mock.Anything, mock.AnythingOfType("[]uint8"), mock.AnythingOfType("string"), mock.AnythingOfType("TelemetryType")).Return().Run(func(args mock.Arguments) { @@ -304,7 +306,7 @@ func TestCollectAndSend(t *testing.T) { wg := sync.WaitGroup{} ingressClient := mocks.NewTelemetryService(t) ingressAgent := telemetry.NewIngressAgentWrapper(ingressClient) - monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("0xa", synchronization.EnhancedEA, "test-network", "test-chainID") + monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("test-network", "test-chainID", "0xa", synchronization.EnhancedEA) ingressClient.On("Send", mock.Anything, mock.AnythingOfType("[]uint8"), mock.AnythingOfType("string"), mock.AnythingOfType("TelemetryType")).Return().Run(func(args mock.Arguments) { wg.Done() }) @@ -361,7 +363,7 @@ func TestCollectAndSend(t *testing.T) { wg.Wait() assert.Equal(t, logs.Len(), 2) - assert.Contains(t, logs.All()[0].Message, "cannot get bridge response from bridge task") + assert.Contains(t, logs.All()[0].Message, "cannot parse bridge response from bridge task") badTrrs = &pipeline.TaskRunResults{ pipeline.TaskRunResult{ @@ -372,20 +374,19 @@ func TestCollectAndSend(t *testing.T) { Value: "[]", }, }} - wg.Add(1) enhancedTelemChan <- EnhancedTelemetryData{ TaskRunResults: *badTrrs, FinalResults: *finalResult, RepTimestamp: observationTimestamp, } wg.Wait() - assert.Equal(t, logs.Len(), 4) - assert.Contains(t, logs.All()[2].Message, "cannot parse EA telemetry") - assert.Contains(t, logs.All()[3].Message, "cannot get json parse value") + assert.Equal(t, 2, logs.Len()) + assert.Contains(t, logs.All()[0].Message, "cannot parse bridge response from bridge task") + assert.Contains(t, logs.All()[1].Message, "cannot get json parse value") doneCh <- struct{}{} } -var trrsMercury = pipeline.TaskRunResults{ +var trrsMercuryV1 = pipeline.TaskRunResults{ pipeline.TaskRunResult{ Task: &pipeline.BridgeTask{ BaseTask: pipeline.NewBaseTask(0, "ds1", nil, nil, 0), @@ -421,32 +422,24 @@ var trrsMercury = pipeline.TaskRunResults{ }, } -func TestGetFinalValues(t *testing.T) { - e := EnhancedTelemetryService[EnhancedTelemetryMercuryData]{} - o := mercuryv1.Observation{ - BenchmarkPrice: mercury.ObsResult[*big.Int]{Val: big.NewInt(111111)}, - Bid: mercury.ObsResult[*big.Int]{Val: big.NewInt(222222)}, - Ask: mercury.ObsResult[*big.Int]{Val: big.NewInt(333333)}, - CurrentBlockNum: mercury.ObsResult[int64]{Val: 123456789}, - CurrentBlockHash: mercury.ObsResult[[]byte]{Val: common.HexToHash("0x123321").Bytes()}, - CurrentBlockTimestamp: mercury.ObsResult[uint64]{Val: 987654321}, - } - - benchmarkPrice, bid, ask, blockNr, blockHash, blockTimestamp := e.getFinalValues(o) - require.Equal(t, benchmarkPrice, int64(111111)) - require.Equal(t, bid, int64(222222)) - require.Equal(t, ask, int64(333333)) - require.Equal(t, blockNr, int64(123456789)) - require.Equal(t, blockHash, common.HexToHash("0x123321").Bytes()) - require.Equal(t, blockTimestamp, uint64(987654321)) - - benchmarkPrice, bid, ask, blockNr, blockHash, blockTimestamp = e.getFinalValues(mercuryv1.Observation{}) - require.Equal(t, benchmarkPrice, int64(0)) - require.Equal(t, bid, int64(0)) - require.Equal(t, ask, int64(0)) - require.Equal(t, blockNr, int64(0)) - require.Nil(t, blockHash) - require.Equal(t, blockTimestamp, uint64(0)) +var trrsMercuryV2 = pipeline.TaskRunResults{ + pipeline.TaskRunResult{ + Task: &pipeline.BridgeTask{ + BaseTask: pipeline.NewBaseTask(0, "ds1", nil, nil, 0), + RequestData: `{"data":{"to":"LINK","from":"USD"}}`, + }, + Result: pipeline.Result{ + Value: bridgeResponse, + }, + }, + pipeline.TaskRunResult{ + Task: &pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "ds1_benchmark", nil, nil, 1), + }, + Result: pipeline.Result{ + Value: float64(123456.123456), + }, + }, } func TestGetPricesFromResults(t *testing.T) { @@ -458,25 +451,25 @@ func TestGetPricesFromResults(t *testing.T) { }, } - benchmarkPrice, bid, ask := e.getPricesFromResults(trrsMercury[0], &trrsMercury) + benchmarkPrice, bid, ask := e.getPricesFromResults(trrsMercuryV1[0], trrsMercuryV1, 1) require.Equal(t, 123456.123456, benchmarkPrice) require.Equal(t, 1234567.1234567, bid) require.Equal(t, float64(321123), ask) - benchmarkPrice, bid, ask = e.getPricesFromResults(trrsMercury[0], &pipeline.TaskRunResults{}) + benchmarkPrice, bid, ask = e.getPricesFromResults(trrsMercuryV1[0], pipeline.TaskRunResults{}, 1) require.Equal(t, float64(0), benchmarkPrice) require.Equal(t, float64(0), bid) require.Equal(t, float64(0), ask) require.Equal(t, 1, logs.Len()) require.Contains(t, logs.All()[0].Message, "cannot parse enhanced EA telemetry") - tt := trrsMercury[:2] - e.getPricesFromResults(trrsMercury[0], &tt) + tt := trrsMercuryV1[:2] + e.getPricesFromResults(trrsMercuryV1[0], tt, 1) require.Equal(t, 2, logs.Len()) require.Contains(t, logs.All()[1].Message, "cannot parse enhanced EA telemetry bid price, task is nil") - tt = trrsMercury[:3] - e.getPricesFromResults(trrsMercury[0], &tt) + tt = trrsMercuryV1[:3] + e.getPricesFromResults(trrsMercuryV1[0], tt, 1) require.Equal(t, 3, logs.Len()) require.Contains(t, logs.All()[2].Message, "cannot parse enhanced EA telemetry ask price, task is nil") @@ -513,7 +506,7 @@ func TestGetPricesFromResults(t *testing.T) { Value: nil, }, }} - benchmarkPrice, bid, ask = e.getPricesFromResults(trrsMercury[0], &trrs2) + benchmarkPrice, bid, ask = e.getPricesFromResults(trrsMercuryV1[0], trrs2, 3) require.Equal(t, benchmarkPrice, float64(0)) require.Equal(t, bid, float64(0)) require.Equal(t, ask, float64(0)) @@ -521,11 +514,16 @@ func TestGetPricesFromResults(t *testing.T) { require.Contains(t, logs.All()[3].Message, "cannot parse enhanced EA telemetry benchmark price") require.Contains(t, logs.All()[4].Message, "cannot parse enhanced EA telemetry bid price") require.Contains(t, logs.All()[5].Message, "cannot parse enhanced EA telemetry ask price") + + benchmarkPrice, bid, ask = e.getPricesFromResults(trrsMercuryV1[0], trrsMercuryV2, 2) + require.Equal(t, 123456.123456, benchmarkPrice) + require.Equal(t, float64(0), bid) + require.Equal(t, float64(0), ask) } func TestShouldCollectEnhancedTelemetryMercury(t *testing.T) { - j := &job.Job{ + j := job.Job{ Type: job.Type(pipeline.OffchainReporting2JobType), OCR2OracleSpec: &job.OCR2OracleSpec{ CaptureEATelemetry: true, @@ -547,11 +545,11 @@ func TestGetAssetSymbolFromRequestData(t *testing.T) { require.Equal(t, e.getAssetSymbolFromRequestData(reqData), "USD/LINK") } -func TestCollectMercuryEnhancedTelemetry(t *testing.T) { +func TestCollectMercuryEnhancedTelemetryV1(t *testing.T) { wg := sync.WaitGroup{} ingressClient := mocks.NewTelemetryService(t) ingressAgent := telemetry.NewIngressAgentWrapper(ingressClient) - monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("0xa", synchronization.EnhancedEAMercury, "test-network", "test-chainID") + monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("test-network", "test-chainID", "0xa", synchronization.EnhancedEAMercury) var sentMessage []byte ingressClient.On("Send", mock.Anything, mock.AnythingOfType("[]uint8"), mock.AnythingOfType("string"), mock.AnythingOfType("TelemetryType")).Return().Run(func(args mock.Arguments) { @@ -581,8 +579,8 @@ func TestCollectMercuryEnhancedTelemetry(t *testing.T) { wg.Add(1) chTelem <- EnhancedTelemetryMercuryData{ - TaskRunResults: trrsMercury, - Observation: mercuryv1.Observation{ + TaskRunResults: trrsMercuryV1, + V1Observation: &mercuryv1.Observation{ BenchmarkPrice: mercury.ObsResult[*big.Int]{Val: big.NewInt(111111)}, Bid: mercury.ObsResult[*big.Int]{Val: big.NewInt(222222)}, Ask: mercury.ObsResult[*big.Int]{Val: big.NewInt(333333)}, @@ -598,27 +596,30 @@ func TestCollectMercuryEnhancedTelemetry(t *testing.T) { } expectedTelemetry := telem.EnhancedEAMercury{ - DataSource: "data-source-name", - DpBenchmarkPrice: 123456.123456, - DpBid: 1234567.1234567, - DpAsk: 321123, - CurrentBlockNumber: 123456789, - CurrentBlockHash: common.HexToHash("0x123321").String(), - CurrentBlockTimestamp: 987654321, - BridgeTaskRunStartedTimestamp: trrsMercury[0].CreatedAt.UnixMilli(), - BridgeTaskRunEndedTimestamp: trrsMercury[0].FinishedAt.Time.UnixMilli(), - ProviderRequestedTimestamp: 92233720368547760, - ProviderReceivedTimestamp: -92233720368547760, - ProviderDataStreamEstablished: 1, - ProviderIndicatedTime: -123456789, - Feed: common.HexToHash("0x111").String(), - ObservationBenchmarkPrice: 111111, - ObservationBid: 222222, - ObservationAsk: 333333, - ConfigDigest: "0200000000000000000000000000000000000000000000000000000000000000", - Round: 22, - Epoch: 11, - AssetSymbol: "USD/LINK", + DataSource: "data-source-name", + DpBenchmarkPrice: 123456.123456, + DpBid: 1234567.1234567, + DpAsk: 321123, + CurrentBlockNumber: 123456789, + CurrentBlockHash: common.HexToHash("0x123321").String(), + CurrentBlockTimestamp: 987654321, + BridgeTaskRunStartedTimestamp: trrsMercuryV1[0].CreatedAt.UnixMilli(), + BridgeTaskRunEndedTimestamp: trrsMercuryV1[0].FinishedAt.Time.UnixMilli(), + ProviderRequestedTimestamp: 92233720368547760, + ProviderReceivedTimestamp: -92233720368547760, + ProviderDataStreamEstablished: 1, + ProviderIndicatedTime: -123456789, + Feed: common.HexToHash("0x111").String(), + ObservationBenchmarkPrice: 111111, + ObservationBid: 222222, + ObservationAsk: 333333, + ConfigDigest: "0200000000000000000000000000000000000000000000000000000000000000", + Round: 22, + Epoch: 11, + AssetSymbol: "USD/LINK", + ObservationBenchmarkPriceString: "111111", + ObservationBidString: "222222", + ObservationAskString: "333333", } expectedMessage, _ := proto.Marshal(&expectedTelemetry) @@ -634,7 +635,7 @@ func TestCollectMercuryEnhancedTelemetry(t *testing.T) { Value: nil, }}, }, - Observation: mercuryv1.Observation{}, + V1Observation: &mercuryv1.Observation{}, RepTimestamp: types.ReportTimestamp{ ConfigDigest: types.ConfigDigest{2}, Epoch: 11, @@ -642,10 +643,10 @@ func TestCollectMercuryEnhancedTelemetry(t *testing.T) { }, } wg.Add(1) - trrsMercury[0].Result.Value = "" + trrsMercuryV1[0].Result.Value = "" chTelem <- EnhancedTelemetryMercuryData{ - TaskRunResults: trrsMercury, - Observation: mercuryv1.Observation{}, + TaskRunResults: trrsMercuryV1, + V1Observation: &mercuryv1.Observation{}, RepTimestamp: types.ReportTimestamp{ ConfigDigest: types.ConfigDigest{2}, Epoch: 11, @@ -659,3 +660,119 @@ func TestCollectMercuryEnhancedTelemetry(t *testing.T) { require.Contains(t, logs.All()[1].Message, "cannot parse EA telemetry") chDone <- struct{}{} } + +func TestCollectMercuryEnhancedTelemetryV2(t *testing.T) { + wg := sync.WaitGroup{} + ingressClient := mocks.NewTelemetryService(t) + ingressAgent := telemetry.NewIngressAgentWrapper(ingressClient) + monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("test-network", "test-chainID", "0xa", synchronization.EnhancedEAMercury) + + var sentMessage []byte + ingressClient.On("Send", mock.Anything, mock.AnythingOfType("[]uint8"), mock.AnythingOfType("string"), mock.AnythingOfType("TelemetryType")).Return().Run(func(args mock.Arguments) { + sentMessage = args[1].([]byte) + wg.Done() + }) + + lggr, logs := logger.TestLoggerObserved(t, zap.WarnLevel) + chTelem := make(chan EnhancedTelemetryMercuryData, 100) + chDone := make(chan struct{}) + feedID := common.HexToHash("0x111") + e := EnhancedTelemetryService[EnhancedTelemetryMercuryData]{ + chDone: chDone, + chTelem: chTelem, + job: &job.Job{ + Type: job.Type(pipeline.OffchainReporting2JobType), + OCR2OracleSpec: &job.OCR2OracleSpec{ + CaptureEATelemetry: true, + FeedID: &feedID, + }, + }, + lggr: lggr, + monitoringEndpoint: monitoringEndpoint, + } + require.NoError(t, e.Start(testutils.Context(t))) + + wg.Add(1) + + chTelem <- EnhancedTelemetryMercuryData{ + TaskRunResults: trrsMercuryV2, + V2Observation: &mercury_v2.Observation{ + BenchmarkPrice: mercury.ObsResult[*big.Int]{Val: big.NewInt(111111)}, + MaxFinalizedTimestamp: mercury.ObsResult[int64]{Val: 321}, + LinkPrice: mercury.ObsResult[*big.Int]{Val: big.NewInt(4321)}, + NativePrice: mercury.ObsResult[*big.Int]{Val: big.NewInt(54321)}, + }, + RepTimestamp: types.ReportTimestamp{ + ConfigDigest: types.ConfigDigest{2}, + Epoch: 11, + Round: 22, + }, + } + + expectedTelemetry := telem.EnhancedEAMercury{ + DataSource: "data-source-name", + DpBenchmarkPrice: 123456.123456, + CurrentBlockNumber: 0, + CurrentBlockHash: "", + CurrentBlockTimestamp: 0, + BridgeTaskRunStartedTimestamp: trrsMercuryV1[0].CreatedAt.UnixMilli(), + BridgeTaskRunEndedTimestamp: trrsMercuryV1[0].FinishedAt.Time.UnixMilli(), + ProviderRequestedTimestamp: 92233720368547760, + ProviderReceivedTimestamp: -92233720368547760, + ProviderDataStreamEstablished: 1, + ProviderIndicatedTime: -123456789, + Feed: common.HexToHash("0x111").String(), + ObservationBenchmarkPrice: 111111, + ObservationBid: 0, + ObservationAsk: 0, + ConfigDigest: "0200000000000000000000000000000000000000000000000000000000000000", + Round: 22, + Epoch: 11, + AssetSymbol: "USD/LINK", + ObservationBenchmarkPriceString: "111111", + MaxFinalizedTimestamp: 321, + LinkPrice: 4321, + NativePrice: 54321, + } + + expectedMessage, _ := proto.Marshal(&expectedTelemetry) + wg.Wait() + + require.Equal(t, expectedMessage, sentMessage) + + chTelem <- EnhancedTelemetryMercuryData{ + TaskRunResults: pipeline.TaskRunResults{ + pipeline.TaskRunResult{Task: &pipeline.BridgeTask{ + BaseTask: pipeline.NewBaseTask(0, "ds1", nil, nil, 0), + }, + Result: pipeline.Result{ + Value: nil, + }}, + }, + V2Observation: &mercury_v2.Observation{}, + RepTimestamp: types.ReportTimestamp{ + ConfigDigest: types.ConfigDigest{2}, + Epoch: 11, + Round: 22, + }, + } + wg.Add(1) + trrsMercuryV2[0].Result.Value = "" + chTelem <- EnhancedTelemetryMercuryData{ + TaskRunResults: trrsMercuryV2, + V2Observation: &mercury_v2.Observation{}, + RepTimestamp: types.ReportTimestamp{ + ConfigDigest: types.ConfigDigest{2}, + Epoch: 11, + Round: 22, + }, + } + + wg.Wait() + require.Equal(t, 4, logs.Len()) + require.Contains(t, logs.All()[0].Message, "cannot parse enhanced EA telemetry bid price") + require.Contains(t, logs.All()[1].Message, "cannot get bridge response from bridge task") + require.Contains(t, logs.All()[2].Message, "cannot parse EA telemetry") + require.Contains(t, logs.All()[3].Message, "cannot parse enhanced EA telemetry bid price") + chDone <- struct{}{} +} diff --git a/core/services/ocrcommon/transmitter_pipeline_test.go b/core/services/ocrcommon/transmitter_pipeline_test.go index 8a1f2f2a922..e0114d0aa0d 100644 --- a/core/services/ocrcommon/transmitter_pipeline_test.go +++ b/core/services/ocrcommon/transmitter_pipeline_test.go @@ -9,7 +9,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" diff --git a/core/services/ocrcommon/transmitter_test.go b/core/services/ocrcommon/transmitter_test.go index ac5d120eb0d..d954da869bc 100644 --- a/core/services/ocrcommon/transmitter_test.go +++ b/core/services/ocrcommon/transmitter_test.go @@ -13,7 +13,7 @@ import ( txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" ) diff --git a/core/services/periodicbackup/backup.go b/core/services/periodicbackup/backup.go index 8b9ff89cf44..b1bcf40ee32 100644 --- a/core/services/periodicbackup/backup.go +++ b/core/services/periodicbackup/backup.go @@ -11,11 +11,11 @@ import ( "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/static" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) var ( @@ -37,18 +37,18 @@ type backupResult struct { type ( DatabaseBackup interface { - services.ServiceCtx + services.Service RunBackup(version string) error } databaseBackup struct { + services.StateMachine logger logger.Logger databaseURL url.URL mode config.DatabaseBackupMode frequency time.Duration outputParentDir string done chan bool - utils.StartStopOnce } BackupConfig interface { @@ -77,13 +77,13 @@ func NewDatabaseBackup(dbUrl url.URL, rootDir string, backupConfig BackupConfig, } return &databaseBackup{ + services.StateMachine{}, lggr, dbUrl, backupConfig.Mode(), backupConfig.Frequency(), outputParentDir, make(chan bool), - utils.StartStopOnce{}, }, nil } @@ -129,7 +129,7 @@ func (backup *databaseBackup) Name() string { } func (backup *databaseBackup) HealthReport() map[string]error { - return map[string]error{backup.Name(): backup.StartStopOnce.Healthy()} + return map[string]error{backup.Name(): backup.Healthy()} } func (backup *databaseBackup) frequencyIsTooSmall() bool { diff --git a/core/services/pg/channels.go b/core/services/pg/channels.go index 736cd407962..aed132a7f2c 100644 --- a/core/services/pg/channels.go +++ b/core/services/pg/channels.go @@ -1,8 +1,4 @@ package pg // Postgres channel to listen for new evm.txes -const ( - ChannelInsertOnTx = "evm.insert_on_txes" - ChannelInsertOnCosmosMsg = "insert_on_cosmos_msg" - ChannelInsertOnEVMLogs = "evm.insert_on_logs" -) +const ChannelInsertOnEVMLogs = "evm.insert_on_logs" diff --git a/core/services/pg/connection.go b/core/services/pg/connection.go index 19c48a118b9..ee345bb6259 100644 --- a/core/services/pg/connection.go +++ b/core/services/pg/connection.go @@ -6,8 +6,8 @@ import ( "github.com/google/uuid" _ "github.com/jackc/pgx/v4/stdlib" // need to make sure pgx driver is registered before opening connection + "github.com/jmoiron/sqlx" "github.com/scylladb/go-reflectx" - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/v2/core/store/dialects" ) @@ -42,7 +42,7 @@ func NewConnection(uri string, dialect dialects.DialectName, config ConnectionCo lockTimeout := config.DefaultLockTimeout().Milliseconds() idleInTxSessionTimeout := config.DefaultIdleInTxSessionTimeout().Milliseconds() stmt := fmt.Sprintf(`SET TIME ZONE 'UTC'; SET lock_timeout = %d; SET idle_in_transaction_session_timeout = %d; SET default_transaction_isolation = %q`, - lockTimeout, idleInTxSessionTimeout, DefaultIsolation.String()) + lockTimeout, idleInTxSessionTimeout, defaultIsolation.String()) if _, err = db.Exec(stmt); err != nil { return nil, err } diff --git a/core/services/pg/connection_test.go b/core/services/pg/connection_test.go index 92781343c61..651bf9d2d9b 100644 --- a/core/services/pg/connection_test.go +++ b/core/services/pg/connection_test.go @@ -5,7 +5,7 @@ import ( "github.com/google/uuid" _ "github.com/jackc/pgx/v4/stdlib" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" diff --git a/core/services/pg/datatypes/json.go b/core/services/pg/datatypes/json.go index d84c89269cc..ac72a5a5ed4 100644 --- a/core/services/pg/datatypes/json.go +++ b/core/services/pg/datatypes/json.go @@ -1,59 +1,9 @@ package datatypes import ( - "database/sql/driver" - "encoding/json" - "errors" - "fmt" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" ) // JSON defined JSON data type, need to implements driver.Valuer, sql.Scanner interface -type JSON json.RawMessage - -// Value return json value, implement driver.Valuer interface -func (j JSON) Value() (driver.Value, error) { - if len(j) == 0 { - return nil, nil - } - bytes, err := json.RawMessage(j).MarshalJSON() - return string(bytes), err -} - -// Scan scan value into Jsonb, implements sql.Scanner interface -func (j *JSON) Scan(value interface{}) error { - if value == nil { - *j = JSON("null") - return nil - } - var bytes []byte - switch v := value.(type) { - case []byte: - bytes = v - case string: - bytes = []byte(v) - default: - return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value)) - } - - result := json.RawMessage{} - err := json.Unmarshal(bytes, &result) - *j = JSON(result) - return err -} - -// MarshalJSON to output non base64 encoded []byte -func (j JSON) MarshalJSON() ([]byte, error) { - return json.RawMessage(j).MarshalJSON() -} - -// UnmarshalJSON to deserialize []byte -func (j *JSON) UnmarshalJSON(b []byte) error { - result := json.RawMessage{} - err := result.UnmarshalJSON(b) - *j = JSON(result) - return err -} - -func (j JSON) String() string { - return string(j) -} +// Deprecated: Use sqlutil.JSON instead +type JSON = sqlutil.JSON diff --git a/core/services/pg/event_broadcaster.go b/core/services/pg/event_broadcaster.go index 3c74df6d745..70008f4c0de 100644 --- a/core/services/pg/event_broadcaster.go +++ b/core/services/pg/event_broadcaster.go @@ -11,8 +11,9 @@ import ( "github.com/lib/pq" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/static" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -23,12 +24,13 @@ import ( // EventBroadcaster opaquely manages a collection of Postgres event listeners // and broadcasts events to subscribers (with an optional payload filter). type EventBroadcaster interface { - services.ServiceCtx + services.Service Subscribe(channel, payloadFilter string) (Subscription, error) Notify(channel string, payload string) error } type eventBroadcaster struct { + services.StateMachine uri string minReconnectInterval time.Duration maxReconnectDuration time.Duration @@ -39,7 +41,6 @@ type eventBroadcaster struct { chStop chan struct{} chDone chan struct{} lggr logger.Logger - utils.StartStopOnce } var _ EventBroadcaster = (*eventBroadcaster)(nil) diff --git a/core/services/pg/event_broadcaster_test.go b/core/services/pg/event_broadcaster_test.go index bea7dfb5a85..41dcbb0176f 100644 --- a/core/services/pg/event_broadcaster_test.go +++ b/core/services/pg/event_broadcaster_test.go @@ -5,20 +5,20 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/services/pg" - + "github.com/google/uuid" "github.com/onsi/gomega" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) func TestEventBroadcaster(t *testing.T) { - config, _ := heavyweight.FullTestDBNoFixturesV2(t, "event_broadcaster", nil) + config, _ := heavyweight.FullTestDBNoFixturesV2(t, nil) - eventBroadcaster := cltest.NewEventBroadcaster(t, config.Database().URL()) + eventBroadcaster := pg.NewEventBroadcaster(config.Database().URL(), 0, 0, logger.TestLogger(t), uuid.New()) require.NoError(t, eventBroadcaster.Start(testutils.Context(t))) t.Cleanup(func() { require.NoError(t, eventBroadcaster.Close()) }) diff --git a/core/services/pg/helpers_test.go b/core/services/pg/helpers_test.go index c5ccda6bd9a..52158535a2e 100644 --- a/core/services/pg/helpers_test.go +++ b/core/services/pg/helpers_test.go @@ -1,6 +1,6 @@ package pg -import "github.com/smartcontractkit/sqlx" +import "github.com/jmoiron/sqlx" func SetConn(lock interface{}, conn *sqlx.Conn) { switch v := lock.(type) { diff --git a/core/services/pg/lease_lock.go b/core/services/pg/lease_lock.go index 656005016ef..e21cec44bda 100644 --- a/core/services/pg/lease_lock.go +++ b/core/services/pg/lease_lock.go @@ -8,8 +8,8 @@ import ( "time" "github.com/google/uuid" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" "go.uber.org/multierr" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/pg/lease_lock_test.go b/core/services/pg/lease_lock_test.go index 9f857ffa20b..1b4116b5bf9 100644 --- a/core/services/pg/lease_lock_test.go +++ b/core/services/pg/lease_lock_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" @@ -24,7 +24,7 @@ func newLeaseLock(t *testing.T, db *sqlx.DB, cfg pg.LeaseLockConfig) pg.LeaseLoc } func Test_LeaseLock(t *testing.T) { - cfg, db := heavyweight.FullTestDBNoFixturesV2(t, "leaselock", func(c *chainlink.Config, s *chainlink.Secrets) { + cfg, db := heavyweight.FullTestDBNoFixturesV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { t := true c.Database.Lock.Enabled = &t }) @@ -207,7 +207,7 @@ func Test_LeaseLock(t *testing.T) { require.NoError(t, db.Close()) t.Run("on virgin database", func(t *testing.T) { - _, db := heavyweight.FullTestDBEmptyV2(t, "leaselock", nil) + _, db := heavyweight.FullTestDBEmptyV2(t, nil) cfg := pg.LeaseLockConfig{ DefaultQueryTimeout: cfg.Database().DefaultQueryTimeout(), LeaseDuration: 15 * time.Second, diff --git a/core/services/pg/locked_db.go b/core/services/pg/locked_db.go index af4481285ce..a9157fe1ae1 100644 --- a/core/services/pg/locked_db.go +++ b/core/services/pg/locked_db.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/pg/locked_db_test.go b/core/services/pg/locked_db_test.go index aaf7ebf32c4..a2aebcd57f4 100644 --- a/core/services/pg/locked_db_test.go +++ b/core/services/pg/locked_db_test.go @@ -6,7 +6,7 @@ import ( "time" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/pg" diff --git a/core/services/pg/q.go b/core/services/pg/q.go index 9c70d813ab6..050606c7937 100644 --- a/core/services/pg/q.go +++ b/core/services/pg/q.go @@ -15,9 +15,9 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink-relay/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" ) var promSQLQueryTime = promauto.NewHistogram(prometheus.HistogramOpts{ @@ -165,7 +165,7 @@ func (q Q) Context() (context.Context, context.CancelFunc) { return context.WithTimeout(q.ParentCtx, q.QueryTimeout) } -func (q Q) Transaction(fc func(q Queryer) error, txOpts ...TxOptions) error { +func (q Q) Transaction(fc func(q Queryer) error, txOpts ...TxOption) error { ctx, cancel := q.Context() defer cancel() return SqlxTransaction(ctx, q.Queryer, q.originalLogger(), fc, txOpts...) diff --git a/core/services/pg/sqlx.go b/core/services/pg/sqlx.go index cd5427463dd..c252edf9f5a 100644 --- a/core/services/pg/sqlx.go +++ b/core/services/pg/sqlx.go @@ -7,9 +7,9 @@ import ( "github.com/pkg/errors" mapper "github.com/scylladb/go-reflectx" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink-relay/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" ) type Queryer interface { @@ -35,7 +35,7 @@ func WrapDbWithSqlx(rdb *sql.DB) *sqlx.DB { return db } -func SqlxTransaction(ctx context.Context, q Queryer, lggr logger.Logger, fc func(q Queryer) error, txOpts ...TxOptions) (err error) { +func SqlxTransaction(ctx context.Context, q Queryer, lggr logger.Logger, fc func(q Queryer) error, txOpts ...TxOption) (err error) { switch db := q.(type) { case *sqlx.Tx: // nested transaction: just use the outer transaction diff --git a/core/services/pg/transaction.go b/core/services/pg/transaction.go index 932b1120859..fd7e74baca3 100644 --- a/core/services/pg/transaction.go +++ b/core/services/pg/transaction.go @@ -7,69 +7,29 @@ import ( "time" "github.com/getsentry/sentry-go" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" "go.uber.org/multierr" - "github.com/smartcontractkit/sqlx" - - "github.com/smartcontractkit/chainlink-relay/pkg/logger" - + "github.com/smartcontractkit/chainlink-common/pkg/logger" corelogger "github.com/smartcontractkit/chainlink/v2/core/logger" ) -type TxOptions struct { - sql.TxOptions - LockTimeout time.Duration - IdleInTxSessionTimeout time.Duration -} - -// NOTE: In an ideal world the timeouts below would be set to something sane in -// the postgres configuration by the user. Since we do not live in an ideal -// world, it is necessary to override them here. -// -// They cannot easily be set at a session level due to how Go's connection -// pooling works. -const ( - // NOTE: This is the default level in Postgres anyway, we just make it - // explicit here - DefaultIsolation = sql.LevelReadCommitted -) +// NOTE: This is the default level in Postgres anyway, we just make it +// explicit here +const defaultIsolation = sql.LevelReadCommitted -func OptReadOnlyTx() TxOptions { - return TxOptions{TxOptions: sql.TxOptions{ReadOnly: true}} -} +// TxOption is a functional option for SQL transactions. +type TxOption func(*sql.TxOptions) -func applyDefaults(optss []TxOptions) (lockTimeout, idleInTxSessionTimeout time.Duration, txOpts sql.TxOptions) { - lockTimeout = defaultLockTimeout - idleInTxSessionTimeout = defaultIdleInTxSessionTimeout - txIsolation := DefaultIsolation - readOnly := false - if len(optss) > 0 { - opts := optss[0] - if opts.LockTimeout != 0 { - lockTimeout = opts.LockTimeout - } - if opts.IdleInTxSessionTimeout != 0 { - idleInTxSessionTimeout = opts.IdleInTxSessionTimeout - } - if opts.Isolation != 0 { - txIsolation = opts.Isolation - } - readOnly = opts.ReadOnly - } - txOpts = sql.TxOptions{ - Isolation: txIsolation, - ReadOnly: readOnly, +func OptReadOnlyTx() TxOption { + return func(opts *sql.TxOptions) { + opts.ReadOnly = true } - return } -func SqlTransaction(ctx context.Context, rdb *sql.DB, lggr logger.Logger, fn func(tx *sqlx.Tx) error, optss ...TxOptions) (err error) { +func SqlTransaction(ctx context.Context, rdb *sql.DB, lggr logger.Logger, fn func(tx *sqlx.Tx) error, opts ...TxOption) (err error) { db := WrapDbWithSqlx(rdb) - return sqlxTransaction(ctx, db, lggr, fn, optss...) -} - -func sqlxTransaction(ctx context.Context, db *sqlx.DB, lggr logger.Logger, fn func(tx *sqlx.Tx) error, optss ...TxOptions) (err error) { wrapFn := func(q Queryer) error { tx, ok := q.(*sqlx.Tx) if !ok { @@ -77,16 +37,19 @@ func sqlxTransaction(ctx context.Context, db *sqlx.DB, lggr logger.Logger, fn fu } return fn(tx) } - return sqlxTransactionQ(ctx, db, lggr, wrapFn, optss...) + return sqlxTransactionQ(ctx, db, lggr, wrapFn, opts...) } -// TxBeginner can be a db or a conn, anything that implements BeginTxx -type TxBeginner interface { +// txBeginner can be a db or a conn, anything that implements BeginTxx +type txBeginner interface { BeginTxx(context.Context, *sql.TxOptions) (*sqlx.Tx, error) } -func sqlxTransactionQ(ctx context.Context, db TxBeginner, lggr logger.Logger, fn func(q Queryer) error, optss ...TxOptions) (err error) { - lockTimeout, idleInTxSessionTimeout, txOpts := applyDefaults(optss) +func sqlxTransactionQ(ctx context.Context, db txBeginner, lggr logger.Logger, fn func(q Queryer) error, opts ...TxOption) (err error) { + var txOpts sql.TxOptions + for _, o := range opts { + o(&txOpts) + } var tx *sqlx.Tx tx, err = db.BeginTxx(ctx, &txOpts) @@ -126,19 +89,6 @@ func sqlxTransactionQ(ctx context.Context, db TxBeginner, lggr logger.Logger, fn } }() - if lockTimeout != defaultLockTimeout { - _, err = tx.Exec(fmt.Sprintf(`SET LOCAL lock_timeout = %d`, lockTimeout.Milliseconds())) - if err != nil { - return errors.Wrap(err, "error setting transaction local lock_timeout") - } - } - if idleInTxSessionTimeout != defaultIdleInTxSessionTimeout { - _, err = tx.Exec(fmt.Sprintf(`SET LOCAL idle_in_transaction_session_timeout = %d`, idleInTxSessionTimeout.Milliseconds())) - if err != nil { - return errors.Wrap(err, "error setting transaction local idle_in_transaction_session_timeout") - } - } - err = fn(tx) return diff --git a/core/services/pg/utils.go b/core/services/pg/utils.go index 5be2a4915bb..eb53c261296 100644 --- a/core/services/pg/utils.go +++ b/core/services/pg/utils.go @@ -12,12 +12,6 @@ const ( 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 - // defaultIdleInTxSessionTimeout controls the max time we leave a transaction open and idle. - // It's good to set this to _something_ because leaving transactions open forever is really bad. - defaultIdleInTxSessionTimeout = 1 * time.Hour ) var _ driver.Valuer = Limit(-1) diff --git a/core/services/pipeline/common_test.go b/core/services/pipeline/common_test.go index 37cc60f27e9..7da80d3af47 100644 --- a/core/services/pipeline/common_test.go +++ b/core/services/pipeline/common_test.go @@ -14,7 +14,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" @@ -323,7 +323,7 @@ func TestTaskRunResult_IsPending(t *testing.T) { func TestSelectGasLimit(t *testing.T) { t.Parallel() - gcfg := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + gcfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].GasEstimator.LimitDefault = ptr(uint32(999)) c.EVM[0].GasEstimator.LimitJobType = toml.GasLimitJobType{ DR: ptr(uint32(100)), diff --git a/core/services/pipeline/helpers_test.go b/core/services/pipeline/helpers_test.go index 974ff29d11c..9ee2dc693f2 100644 --- a/core/services/pipeline/helpers_test.go +++ b/core/services/pipeline/helpers_test.go @@ -6,7 +6,7 @@ import ( "github.com/google/uuid" "github.com/smartcontractkit/chainlink/v2/core/bridges" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" ) const ( @@ -50,14 +50,14 @@ func (t *HTTPTask) HelperSetDependencies(config Config, restrictedHTTPClient, un t.unrestrictedHTTPClient = unrestrictedHTTPClient } -func (t *ETHCallTask) HelperSetDependencies(legacyChains evm.LegacyChainContainer, config Config, specGasLimit *uint32, jobType string) { +func (t *ETHCallTask) HelperSetDependencies(legacyChains legacyevm.LegacyChainContainer, config Config, specGasLimit *uint32, jobType string) { t.legacyChains = legacyChains t.config = config t.specGasLimit = specGasLimit t.jobType = jobType } -func (t *ETHTxTask) HelperSetDependencies(legacyChains evm.LegacyChainContainer, keyStore ETHKeyStore, specGasLimit *uint32, jobType string) { +func (t *ETHTxTask) HelperSetDependencies(legacyChains legacyevm.LegacyChainContainer, keyStore ETHKeyStore, specGasLimit *uint32, jobType string) { t.legacyChains = legacyChains t.keyStore = keyStore t.specGasLimit = specGasLimit diff --git a/core/services/pipeline/orm.go b/core/services/pipeline/orm.go index f4e69df1ec8..eb242e62765 100644 --- a/core/services/pipeline/orm.go +++ b/core/services/pipeline/orm.go @@ -11,13 +11,13 @@ import ( "github.com/google/uuid" "github.com/pkg/errors" + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/store/models" - "github.com/smartcontractkit/chainlink/v2/core/utils" - - "github.com/smartcontractkit/sqlx" ) // KeepersObservationSource is the same for all keeper jobs and it is not persisted in DB @@ -74,7 +74,7 @@ const KeepersObservationSource = ` //go:generate mockery --quiet --name ORM --output ./mocks/ --case=underscore type ORM interface { - services.ServiceCtx + services.Service CreateSpec(pipeline Pipeline, maxTaskTimeout models.Interval, qopts ...pg.QOpt) (int32, error) CreateRun(run *Run, qopts ...pg.QOpt) (err error) InsertRun(run *Run, qopts ...pg.QOpt) error @@ -95,7 +95,7 @@ type ORM interface { } type orm struct { - utils.StartStopOnce + services.StateMachine q pg.Q lggr logger.Logger maxSuccessfulRuns uint64 @@ -111,7 +111,7 @@ var _ ORM = (*orm)(nil) func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig, jobPipelineMaxSuccessfulRuns uint64) *orm { ctx, cancel := context.WithCancel(context.Background()) return &orm{ - utils.StartStopOnce{}, + services.StateMachine{}, pg.NewQ(db, lggr, cfg), lggr.Named("PipelineORM"), jobPipelineMaxSuccessfulRuns, @@ -306,13 +306,13 @@ func (o *orm) UpdateTaskRunResult(taskID uuid.UUID, result Result) (run Run, sta WHERE pipeline_task_runs.id = $1 AND pipeline_runs.state in ('running', 'suspended') FOR UPDATE` if err = tx.Get(&run, sql, taskID); err != nil { - return err + return fmt.Errorf("failed to find pipeline run for ID %s: %w", taskID.String(), err) } // Update the task with result sql = `UPDATE pipeline_task_runs SET output = $2, error = $3, finished_at = $4 WHERE id = $1` if _, err = tx.Exec(sql, taskID, result.OutputDB(), result.ErrorDB(), time.Now()); err != nil { - return errors.Wrap(err, "UpdateTaskRunResult") + return fmt.Errorf("failed to update pipeline task run: %w", err) } if run.State == RunStatusSuspended { @@ -321,7 +321,7 @@ func (o *orm) UpdateTaskRunResult(taskID uuid.UUID, result Result) (run Run, sta sql = `UPDATE pipeline_runs SET state = $2 WHERE id = $1` if _, err = tx.Exec(sql, run.ID, run.State); err != nil { - return errors.Wrap(err, "UpdateTaskRunResult") + return fmt.Errorf("failed to update pipeline run state: %w", err) } } diff --git a/core/services/pipeline/orm_test.go b/core/services/pipeline/orm_test.go index 4c03ce16ef8..dcbbfd9c97e 100644 --- a/core/services/pipeline/orm_test.go +++ b/core/services/pipeline/orm_test.go @@ -5,25 +5,24 @@ import ( "time" "github.com/google/uuid" - "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" "gopkg.in/guregu/null.v4" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -34,11 +33,11 @@ type ormconfig struct { func (ormconfig) JobPipelineMaxSuccessfulRuns() uint64 { return 123456 } -func setupORM(t *testing.T, name string) (db *sqlx.DB, orm pipeline.ORM) { +func setupORM(t *testing.T, heavy bool) (db *sqlx.DB, orm pipeline.ORM) { t.Helper() - if name != "" { - _, db = heavyweight.FullTestDBV2(t, name, nil) + if heavy { + _, db = heavyweight.FullTestDBV2(t, nil) } else { db = pgtest.NewSqlxDB(t) } @@ -48,12 +47,12 @@ func setupORM(t *testing.T, name string) (db *sqlx.DB, orm pipeline.ORM) { return } -func setupHeavyORM(t *testing.T, name string) (db *sqlx.DB, orm pipeline.ORM) { - return setupORM(t, name) +func setupHeavyORM(t *testing.T) (db *sqlx.DB, orm pipeline.ORM) { + return setupORM(t, true) } func setupLiteORM(t *testing.T) (db *sqlx.DB, orm pipeline.ORM) { - return setupORM(t, "") + return setupORM(t, false) } func Test_PipelineORM_CreateSpec(t *testing.T) { @@ -465,7 +464,7 @@ func Test_PipelineORM_DeleteRun(t *testing.T) { } func Test_PipelineORM_DeleteRunsOlderThan(t *testing.T) { - _, orm := setupHeavyORM(t, "pipeline_runs_reaper") + _, orm := setupHeavyORM(t) var runsIds []int64 @@ -514,16 +513,14 @@ func Test_GetUnfinishedRuns_Keepers(t *testing.T) { // The test configures single Keeper job with two running tasks. // GetUnfinishedRuns() expects to catch both running tasks. - config := configtest2.NewTestGeneralConfig(t) + config := configtest.NewTestGeneralConfig(t) lggr := logger.TestLogger(t) db := pgtest.NewSqlxDB(t) keyStore := cltest.NewKeyStore(t, db, config.Database()) porm := pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgeORM := bridges.NewORM(db, lggr, config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jorm := job.NewORM(db, legacyChains, porm, bridgeORM, keyStore, lggr, config.Database()) + jorm := job.NewORM(db, porm, bridgeORM, keyStore, lggr, config.Database()) defer func() { assert.NoError(t, jorm.Close()) }() timestamp := time.Now() @@ -616,16 +613,14 @@ func Test_GetUnfinishedRuns_DirectRequest(t *testing.T) { // The test configures single DR job with two task runs: one is running and one is suspended. // GetUnfinishedRuns() expects to catch the one that is running. - config := configtest2.NewTestGeneralConfig(t) + config := configtest.NewTestGeneralConfig(t) lggr := logger.TestLogger(t) db := pgtest.NewSqlxDB(t) keyStore := cltest.NewKeyStore(t, db, config.Database()) porm := pipeline.NewORM(db, lggr, config.Database(), config.JobPipeline().MaxSuccessfulRuns()) bridgeORM := bridges.NewORM(db, lggr, config.Database()) - relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: config, KeyStore: keyStore.Eth()}) - legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jorm := job.NewORM(db, legacyChains, porm, bridgeORM, keyStore, lggr, config.Database()) + jorm := job.NewORM(db, porm, bridgeORM, keyStore, lggr, config.Database()) defer func() { assert.NoError(t, jorm.Close()) }() timestamp := time.Now() @@ -709,7 +704,7 @@ func Test_Prune(t *testing.T) { n := uint64(2) - cfg := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.JobPipeline.MaxSuccessfulRuns = &n }) lggr, observed := logger.TestLoggerObserved(t, zapcore.DebugLevel) diff --git a/core/services/pipeline/runner.go b/core/services/pipeline/runner.go index 20319682ef6..768f163f9b1 100644 --- a/core/services/pipeline/runner.go +++ b/core/services/pipeline/runner.go @@ -14,13 +14,13 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/bridges" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/recovery" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -28,7 +28,7 @@ import ( //go:generate mockery --quiet --name Runner --output ./mocks/ --case=underscore type Runner interface { - services.ServiceCtx + services.Service // 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. @@ -52,11 +52,12 @@ type Runner interface { } type runner struct { + services.StateMachine orm ORM btORM bridges.ORM config Config bridgeConfig BridgeConfig - legacyEVMChains evm.LegacyChainContainer + legacyEVMChains legacyevm.LegacyChainContainer ethKeyStore ETHKeyStore vrfKeyStore VRFKeyStore runReaperWorker utils.SleeperTask @@ -67,8 +68,7 @@ type runner struct { // test helper runFinished func(*Run) - utils.StartStopOnce - chStop utils.StopChan + chStop services.StopChan wgDone sync.WaitGroup } @@ -102,7 +102,7 @@ var ( ) ) -func NewRunner(orm ORM, btORM bridges.ORM, cfg Config, bridgeCfg BridgeConfig, legacyChains evm.LegacyChainContainer, ethks ETHKeyStore, vrfks VRFKeyStore, lggr logger.Logger, httpClient, unrestrictedHTTPClient *http.Client) *runner { +func NewRunner(orm ORM, btORM bridges.ORM, cfg Config, bridgeCfg BridgeConfig, legacyChains legacyevm.LegacyChainContainer, ethks ETHKeyStore, vrfks VRFKeyStore, lggr logger.Logger, httpClient, unrestrictedHTTPClient *http.Client) *runner { r := &runner{ orm: orm, btORM: btORM, @@ -150,7 +150,7 @@ func (r *runner) Name() string { } func (r *runner) HealthReport() map[string]error { - return map[string]error{r.Name(): r.StartStopOnce.Healthy()} + return map[string]error{r.Name(): r.Healthy()} } func (r *runner) destroy() { @@ -609,7 +609,7 @@ func (r *runner) ResumeRun(taskID uuid.UUID, value interface{}, err error) error Error: err, }) if err != nil { - return err + return fmt.Errorf("failed to update task run result: %w", err) } // TODO: Should probably replace this with a listener to update events diff --git a/core/services/pipeline/runner_test.go b/core/services/pipeline/runner_test.go index 22b70829ba5..695590e7bd0 100644 --- a/core/services/pipeline/runner_test.go +++ b/core/services/pipeline/runner_test.go @@ -26,7 +26,7 @@ import ( bridgesMocks "github.com/smartcontractkit/chainlink/v2/core/bridges/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" clhttptest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/httptest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" @@ -37,7 +37,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/pipeline/mocks" "github.com/smartcontractkit/chainlink/v2/core/utils" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" ) func newRunner(t testing.TB, db *sqlx.DB, bridgeORM bridges.ORM, cfg chainlink.GeneralConfig) (pipeline.Runner, *mocks.ORM) { diff --git a/core/services/pipeline/task.bridge_test.go b/core/services/pipeline/task.bridge_test.go index 6f542d485e0..03a804c9c12 100644 --- a/core/services/pipeline/task.bridge_test.go +++ b/core/services/pipeline/task.bridge_test.go @@ -24,8 +24,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" clhttptest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/httptest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -208,7 +207,7 @@ func TestBridgeTask_HandlesIntermittentFailure(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - cfg := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) {}) + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) {}) s1 := httptest.NewServer(fakeIntermittentlyFailingPriceResponder(t, utils.MustUnmarshalToMap(btcUSDPairing), decimal.NewFromInt(9700), "", nil)) defer s1.Close() @@ -270,7 +269,7 @@ func TestBridgeTask_DoesNotReturnStaleResults(t *testing.T) { db := pgtest.NewSqlxDB(t) - cfg := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.WebServer.BridgeCacheTTL = models.MustNewDuration(30 * time.Second) }) queryer := pg.NewQ(db, logger.TestLogger(t), cfg.Database()) @@ -336,7 +335,7 @@ func TestBridgeTask_DoesNotReturnStaleResults(t *testing.T) { require.NoError(t, result2.Error) require.Equal(t, string(big.NewInt(9700).Bytes()), result2.Value) - cfg2 := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + cfg2 := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.WebServer.BridgeCacheTTL = models.MustNewDuration(0 * time.Second) }) task.HelperSetDependencies(cfg2.JobPipeline(), cfg2.WebServer(), orm, specID, uuid.UUID{}, c) diff --git a/core/services/pipeline/task.estimategas.go b/core/services/pipeline/task.estimategas.go index 88c6f6facc3..1c0159819b4 100644 --- a/core/services/pipeline/task.estimategas.go +++ b/core/services/pipeline/task.estimategas.go @@ -12,7 +12,7 @@ import ( "github.com/shopspring/decimal" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -30,7 +30,7 @@ type EstimateGasLimitTask struct { EVMChainID string `json:"evmChainID" mapstructure:"evmChainID"` specGasLimit *uint32 - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer jobType string } diff --git a/core/services/pipeline/task.eth_call.go b/core/services/pipeline/task.eth_call.go index e877e1e90f4..3862ea10301 100644 --- a/core/services/pipeline/task.eth_call.go +++ b/core/services/pipeline/task.eth_call.go @@ -12,8 +12,8 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -35,7 +35,7 @@ type ETHCallTask struct { EVMChainID string `json:"evmChainID" mapstructure:"evmChainID"` specGasLimit *uint32 - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer config Config jobType string } diff --git a/core/services/pipeline/task.eth_call_test.go b/core/services/pipeline/task.eth_call_test.go index 77a10681fb4..cb58a03a9df 100644 --- a/core/services/pipeline/task.eth_call_test.go +++ b/core/services/pipeline/task.eth_call_test.go @@ -12,12 +12,12 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/chains" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -257,7 +257,7 @@ func TestETHCallTask(t *testing.T) { txManager := txmmocks.NewMockEvmTxManager(t) db := pgtest.NewSqlxDB(t) - var legacyChains evm.LegacyChainContainer + var legacyChains legacyevm.LegacyChainContainer if test.expectedErrorCause != nil || test.expectedErrorContains != "" { exts := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{DB: db, GeneralConfig: cfg, TxManager: txManager, KeyStore: keyStore}) legacyChains = evmrelay.NewLegacyChainsFromRelayerExtenders(exts) diff --git a/core/services/pipeline/task.eth_tx.go b/core/services/pipeline/task.eth_tx.go index 57f1c0a7ed8..c421b340c91 100644 --- a/core/services/pipeline/task.eth_tx.go +++ b/core/services/pipeline/task.eth_tx.go @@ -14,8 +14,8 @@ import ( "gopkg.in/guregu/null.v4" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" clnull "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -42,7 +42,7 @@ type ETHTxTask struct { forwardingAllowed bool specGasLimit *uint32 keyStore ETHKeyStore - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer jobType string } @@ -155,6 +155,7 @@ func (t *ETHTxTask) Run(ctx context.Context, lggr logger.Logger, vars Vars, inpu ForwarderAddress: forwarderAddress, Strategy: strategy, Checker: transmitChecker, + SignalCallback: true, } if minOutgoingConfirmations > 0 { diff --git a/core/services/pipeline/task.eth_tx_test.go b/core/services/pipeline/task.eth_tx_test.go index af09d793852..a0ff54d4448 100644 --- a/core/services/pipeline/task.eth_tx_test.go +++ b/core/services/pipeline/task.eth_tx_test.go @@ -15,7 +15,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -95,6 +95,7 @@ func TestETHTxTask(t *testing.T) { CheckerType: txmgr.TransmitCheckerTypeVRFV2, VRFCoordinatorAddress: &addr, }, + SignalCallback: true, }).Return(txmgr.Tx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, @@ -138,6 +139,7 @@ func TestETHTxTask(t *testing.T) { FeeLimit: gasLimit, Meta: txMeta, Strategy: txmgrcommon.NewSendEveryStrategy(), + SignalCallback: true, }).Return(txmgr.Tx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, @@ -215,6 +217,7 @@ func TestETHTxTask(t *testing.T) { FeeLimit: gasLimit, Meta: txMeta, Strategy: txmgrcommon.NewSendEveryStrategy(), + SignalCallback: true, }).Return(txmgr.Tx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, @@ -260,6 +263,7 @@ func TestETHTxTask(t *testing.T) { FeeLimit: gasLimit, Meta: txMeta, Strategy: txmgrcommon.NewSendEveryStrategy(), + SignalCallback: true, }).Return(txmgr.Tx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, @@ -290,6 +294,7 @@ func TestETHTxTask(t *testing.T) { FeeLimit: gasLimit, Meta: txMeta, Strategy: txmgrcommon.NewSendEveryStrategy(), + SignalCallback: true, }).Return(txmgr.Tx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, @@ -324,6 +329,7 @@ func TestETHTxTask(t *testing.T) { FeeLimit: drJobTypeGasLimit, Meta: txMeta, Strategy: txmgrcommon.NewSendEveryStrategy(), + SignalCallback: true, }).Return(txmgr.Tx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, @@ -358,6 +364,7 @@ func TestETHTxTask(t *testing.T) { FeeLimit: specGasLimit, Meta: txMeta, Strategy: txmgrcommon.NewSendEveryStrategy(), + SignalCallback: true, }).Return(txmgr.Tx{}, nil) }, nil, nil, "", pipeline.RunInfo{}, @@ -423,6 +430,7 @@ func TestETHTxTask(t *testing.T) { FeeLimit: gasLimit, Meta: txMeta, Strategy: txmgrcommon.NewSendEveryStrategy(), + SignalCallback: true, }).Return(txmgr.Tx{}, errors.New("uh oh")) }, nil, pipeline.ErrTaskRunFailed, "while creating transaction", pipeline.RunInfo{IsRetryable: true}, diff --git a/core/services/pipeline/task.http_test.go b/core/services/pipeline/task.http_test.go index eee3a9aa783..c0dd93df430 100644 --- a/core/services/pipeline/task.http_test.go +++ b/core/services/pipeline/task.http_test.go @@ -20,7 +20,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" clhttptest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/httptest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/pipeline/test_helpers_test.go b/core/services/pipeline/test_helpers_test.go index 8353f5fb5b4..3b72a1625be 100644 --- a/core/services/pipeline/test_helpers_test.go +++ b/core/services/pipeline/test_helpers_test.go @@ -16,7 +16,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" ) func fakeExternalAdapter(t *testing.T, expectedRequest, response interface{}) http.Handler { diff --git a/core/services/promreporter/prom_reporter.go b/core/services/promreporter/prom_reporter.go index c0b48b46e3a..3e1444a6da1 100644 --- a/core/services/promreporter/prom_reporter.go +++ b/core/services/promreporter/prom_reporter.go @@ -3,17 +3,22 @@ package promreporter import ( "context" "database/sql" + "fmt" "math/big" "sync" "time" + txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/multierr" - "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink-common/pkg/services" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -21,15 +26,15 @@ import ( //go:generate mockery --quiet --name PrometheusBackend --output ../../internal/mocks/ --case=underscore type ( promReporter struct { + services.StateMachine db *sql.DB + chains legacyevm.LegacyChainContainer lggr logger.Logger backend PrometheusBackend newHeads *utils.Mailbox[*evmtypes.Head] - chStop utils.StopChan + chStop services.StopChan wgDone sync.WaitGroup reportPeriod time.Duration - - utils.StartStopOnce } PrometheusBackend interface { @@ -86,7 +91,7 @@ func (defaultBackend) SetPipelineTaskRunsQueued(n int) { promPipelineRunsQueued.Set(float64(n)) } -func NewPromReporter(db *sql.DB, lggr logger.Logger, opts ...interface{}) *promReporter { +func NewPromReporter(db *sql.DB, chainContainer legacyevm.LegacyChainContainer, lggr logger.Logger, opts ...interface{}) *promReporter { var backend PrometheusBackend = defaultBackend{} period := 15 * time.Second for _, opt := range opts { @@ -101,6 +106,7 @@ func NewPromReporter(db *sql.DB, lggr logger.Logger, opts ...interface{}) *promR chStop := make(chan struct{}) return &promReporter{ db: db, + chains: chainContainer, lggr: lggr.Named("PromReporter"), backend: backend, newHeads: utils.NewSingleMailbox[*evmtypes.Head](), @@ -130,7 +136,7 @@ func (pr *promReporter) Name() string { } func (pr *promReporter) HealthReport() map[string]error { - return map[string]error{pr.Name(): pr.StartStopOnce.Healthy()} + return map[string]error{pr.Name(): pr.Healthy()} } func (pr *promReporter) OnNewLongestChain(ctx context.Context, head *evmtypes.Head) { @@ -161,6 +167,14 @@ func (pr *promReporter) eventLoop() { } } +func (pr *promReporter) getTxm(evmChainID *big.Int) (txmgr.TxManager, error) { + chain, err := pr.chains.Get(evmChainID.String()) + if err != nil { + return nil, fmt.Errorf("failed to get chain: %w", err) + } + return chain.TxManager(), nil +} + func (pr *promReporter) reportHeadMetrics(ctx context.Context, head *evmtypes.Head) { evmChainID := head.EVMChainID.ToInt() err := multierr.Combine( @@ -175,40 +189,49 @@ func (pr *promReporter) reportHeadMetrics(ctx context.Context, head *evmtypes.He } func (pr *promReporter) reportPendingEthTxes(ctx context.Context, evmChainID *big.Int) (err error) { - var unconfirmed int64 - if err := pr.db.QueryRowContext(ctx, `SELECT count(*) FROM evm.txes WHERE state = 'unconfirmed' AND evm_chain_id = $1`, evmChainID.String()).Scan(&unconfirmed); err != nil { - return errors.Wrap(err, "failed to query for unconfirmed eth_tx count") + txm, err := pr.getTxm(evmChainID) + if err != nil { + return fmt.Errorf("failed to get txm: %w", err) + } + + unconfirmed, err := txm.CountTransactionsByState(ctx, txmgrcommon.TxUnconfirmed) + if err != nil { + return fmt.Errorf("failed to query for unconfirmed eth_tx count: %w", err) } - pr.backend.SetUnconfirmedTransactions(evmChainID, unconfirmed) + pr.backend.SetUnconfirmedTransactions(evmChainID, int64(unconfirmed)) return nil } func (pr *promReporter) reportMaxUnconfirmedAge(ctx context.Context, evmChainID *big.Int) (err error) { - var broadcastAt null.Time - now := time.Now() - if err := pr.db.QueryRowContext(ctx, `SELECT min(initial_broadcast_at) FROM evm.txes WHERE state = 'unconfirmed' AND evm_chain_id = $1`, evmChainID.String()).Scan(&broadcastAt); err != nil { - return errors.Wrap(err, "failed to query for unconfirmed eth_tx count") + txm, err := pr.getTxm(evmChainID) + if err != nil { + return fmt.Errorf("failed to get txm: %w", err) } + + broadcastAt, err := txm.FindEarliestUnconfirmedBroadcastTime(ctx) + if err != nil { + return fmt.Errorf("failed to query for min broadcast time: %w", err) + } + var seconds float64 if broadcastAt.Valid { - nanos := now.Sub(broadcastAt.ValueOrZero()) - seconds = float64(nanos) / 1000000000 + seconds = time.Since(broadcastAt.ValueOrZero()).Seconds() } pr.backend.SetMaxUnconfirmedAge(evmChainID, seconds) return nil } func (pr *promReporter) reportMaxUnconfirmedBlocks(ctx context.Context, head *evmtypes.Head) (err error) { - var earliestUnconfirmedTxBlock null.Int - err = pr.db.QueryRowContext(ctx, ` -SELECT MIN(broadcast_before_block_num) FROM evm.tx_attempts -JOIN evm.txes ON evm.txes.id = evm.tx_attempts.eth_tx_id -WHERE evm.txes.state = 'unconfirmed' -AND evm_chain_id = $1 -AND evm.txes.state = 'unconfirmed'`, head.EVMChainID.String()).Scan(&earliestUnconfirmedTxBlock) + txm, err := pr.getTxm(head.EVMChainID.ToInt()) if err != nil { - return errors.Wrap(err, "failed to query for min broadcast_before_block_num") + return fmt.Errorf("failed to get txm: %w", err) } + + earliestUnconfirmedTxBlock, err := txm.FindEarliestUnconfirmedTxAttemptBlock(ctx) + if err != nil { + return fmt.Errorf("failed to query for earliest unconfirmed tx block: %w", err) + } + var blocksUnconfirmed int64 if !earliestUnconfirmedTxBlock.IsZero() { blocksUnconfirmed = head.Number - earliestUnconfirmedTxBlock.ValueOrZero() diff --git a/core/services/promreporter/prom_reporter_test.go b/core/services/promreporter/prom_reporter_test.go index f57cf8f45a8..54e3a5d3fab 100644 --- a/core/services/promreporter/prom_reporter_test.go +++ b/core/services/promreporter/prom_reporter_test.go @@ -6,15 +6,21 @@ import ( "testing" "time" + "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/promreporter" @@ -25,12 +31,38 @@ func newHead() evmtypes.Head { return evmtypes.Head{Number: 42, EVMChainID: utils.NewBigI(0)} } +func newLegacyChainContainer(t *testing.T, db *sqlx.DB) legacyevm.LegacyChainContainer { + config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) + keyStore := cltest.NewKeyStore(t, db, dbConfig).Eth() + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + estimator := gas.NewEstimator(logger.TestLogger(t), ethClient, config, evmConfig.GasEstimator()) + lggr := logger.TestLogger(t) + lp := logpoller.NewLogPoller(logpoller.NewORM(testutils.FixtureChainID, db, lggr, pgtest.NewQConfig(true)), ethClient, lggr, 100*time.Millisecond, false, 2, 3, 2, 1000) + + txm, err := txmgr.NewTxm( + db, + evmConfig, + evmConfig.GasEstimator(), + evmConfig.Transactions(), + dbConfig, + dbConfig.Listener(), + ethClient, + lggr, + lp, + keyStore, + estimator) + require.NoError(t, err) + + cfg := configtest.NewGeneralConfig(t, nil) + return cltest.NewLegacyChainsWithMockChainAndTxManager(t, ethClient, cfg, txm) +} + func Test_PromReporter_OnNewLongestChain(t *testing.T) { t.Run("with nothing in the database", func(t *testing.T) { - d := pgtest.NewSqlDB(t) + db := pgtest.NewSqlxDB(t) backend := mocks.NewPrometheusBackend(t) - reporter := promreporter.NewPromReporter(d, logger.TestLogger(t), backend, 10*time.Millisecond) + reporter := promreporter.NewPromReporter(db.DB, newLegacyChainContainer(t, db), logger.TestLogger(t), backend, 10*time.Millisecond) var subscribeCalls atomic.Int32 @@ -74,7 +106,7 @@ func Test_PromReporter_OnNewLongestChain(t *testing.T) { subscribeCalls.Add(1) }). Return() - reporter := promreporter.NewPromReporter(db.DB, logger.TestLogger(t), backend, 10*time.Millisecond) + reporter := promreporter.NewPromReporter(db.DB, newLegacyChainContainer(t, db), logger.TestLogger(t), backend, 10*time.Millisecond) require.NoError(t, reporter.Start(testutils.Context(t))) defer func() { assert.NoError(t, reporter.Close()) }() @@ -91,11 +123,10 @@ func Test_PromReporter_OnNewLongestChain(t *testing.T) { t.Run("with unfinished pipeline task runs", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - pgtest.MustExec(t, db, `SET CONSTRAINTS pipeline_task_runs_pipeline_run_id_fkey DEFERRED`) backend := mocks.NewPrometheusBackend(t) - reporter := promreporter.NewPromReporter(db.DB, logger.TestLogger(t), backend, 10*time.Millisecond) + reporter := promreporter.NewPromReporter(db.DB, newLegacyChainContainer(t, db), logger.TestLogger(t), backend, 10*time.Millisecond) cltest.MustInsertUnfinishedPipelineTaskRun(t, db, 1) cltest.MustInsertUnfinishedPipelineTaskRun(t, db, 1) diff --git a/core/services/relay/evm/config_poller.go b/core/services/relay/evm/config_poller.go index 504155bf1e8..fe39ed0e343 100644 --- a/core/services/relay/evm/config_poller.go +++ b/core/services/relay/evm/config_poller.go @@ -11,16 +11,17 @@ import ( "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" "github.com/smartcontractkit/libocr/gethwrappers2/ocrconfigurationstoreevmsimple" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" evmRelayTypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) var ( @@ -89,7 +90,7 @@ func configFromLog(logData []byte) (ocrtypes.ContractConfig, error) { } type configPoller struct { - utils.StartStopOnce + services.StateMachine lggr logger.Logger filterName string @@ -211,7 +212,7 @@ func (cp *configPoller) LatestBlockHeight(ctx context.Context) (blockHeight uint } return 0, err } - return uint64(latest), nil + return uint64(latest.BlockNumber), nil } func (cp *configPoller) isConfigStoreAvailable() bool { diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index e7a2bca3448..b3e22ec5dbc 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -10,22 +10,21 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" pkgerrors "github.com/pkg/errors" - "go.uber.org/multierr" "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median/evmreportcodec" "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/sqlx" - "github.com/smartcontractkit/chainlink-relay/pkg/services" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/services" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" txm "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" @@ -42,14 +41,13 @@ import ( reportcodecv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) -var _ relaytypes.Relayer = &Relayer{} //nolint:staticcheck +var _ commontypes.Relayer = &Relayer{} //nolint:staticcheck type Relayer struct { db *sqlx.DB - chain evm.Chain + chain legacyevm.Chain lggr logger.Logger ks CSAETHKeystore mercuryPool wsrpc.Pool @@ -67,6 +65,7 @@ type RelayerOpts struct { pg.QConfig CSAETHKeystore pg.EventBroadcaster + MercuryPool wsrpc.Pool } func (c RelayerOpts) Validate() error { @@ -90,7 +89,7 @@ func (c RelayerOpts) Validate() error { return err } -func NewRelayer(lggr logger.Logger, chain evm.Chain, opts RelayerOpts) (*Relayer, error) { +func NewRelayer(lggr logger.Logger, chain legacyevm.Chain, opts RelayerOpts) (*Relayer, error) { err := opts.Validate() if err != nil { return nil, fmt.Errorf("cannot create evm relayer: %w", err) @@ -101,7 +100,7 @@ func NewRelayer(lggr logger.Logger, chain evm.Chain, opts RelayerOpts) (*Relayer chain: chain, lggr: lggr, ks: opts.CSAETHKeystore, - mercuryPool: wsrpc.NewPool(lggr), + mercuryPool: opts.MercuryPool, eventBroadcaster: opts.EventBroadcaster, pgCfg: opts.QConfig, }, nil @@ -117,21 +116,20 @@ func (r *Relayer) Start(context.Context) error { } func (r *Relayer) Close() error { - return r.mercuryPool.Close() + return nil } // Ready does noop: always ready func (r *Relayer) Ready() error { - return r.mercuryPool.Ready() + return nil } func (r *Relayer) HealthReport() (report map[string]error) { report = make(map[string]error) - services.CopyHealth(report, r.mercuryPool.HealthReport()) return } -func (r *Relayer) NewMercuryProvider(rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs) (relaytypes.MercuryProvider, error) { +func (r *Relayer) NewMercuryProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.MercuryProvider, error) { lggr := r.lggr.Named("MercuryProvider").Named(rargs.ExternalJobID.String()) relayOpts := types.NewRelayOpts(rargs) relayConfig, err := relayOpts.RelayConfig() @@ -190,16 +188,17 @@ func (r *Relayer) NewMercuryProvider(rargs relaytypes.RelayArgs, pargs relaytype } transmitter := mercury.NewTransmitter(lggr, cw.ContractConfigTracker(), client, privKey.PublicKey, rargs.JobID, *relayConfig.FeedID, r.db, r.pgCfg, transmitterCodec) - return NewMercuryProvider(cw, transmitter, reportCodecV1, reportCodecV2, reportCodecV3, lggr), nil + chainReader := NewChainReader(r.chain.HeadTracker()) + return NewMercuryProvider(cw, transmitter, reportCodecV1, reportCodecV2, reportCodecV3, chainReader, lggr), nil } -func (r *Relayer) NewFunctionsProvider(rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs) (relaytypes.FunctionsProvider, error) { +func (r *Relayer) NewFunctionsProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.FunctionsProvider, error) { lggr := r.lggr.Named("FunctionsProvider").Named(rargs.ExternalJobID.String()) // TODO(FUN-668): Not ready yet (doesn't implement FunctionsEvents() properly) return NewFunctionsProvider(r.chain, rargs, pargs, lggr, r.ks.Eth(), functions.FunctionsPlugin) } -func (r *Relayer) NewConfigProvider(args relaytypes.RelayArgs) (relaytypes.ConfigProvider, error) { +func (r *Relayer) NewConfigProvider(args commontypes.RelayArgs) (commontypes.ConfigProvider, error) { lggr := r.lggr.Named("ConfigProvider").Named(args.ExternalJobID.String()) relayOpts := types.NewRelayOpts(args) relayConfig, err := relayOpts.RelayConfig() @@ -219,7 +218,7 @@ func (r *Relayer) NewConfigProvider(args relaytypes.RelayArgs) (relaytypes.Confi return configProvider, err } -func FilterNamesFromRelayArgs(args relaytypes.RelayArgs) (filterNames []string, err error) { +func FilterNamesFromRelayArgs(args commontypes.RelayArgs) (filterNames []string, err error) { var addr ethkey.EIP55Address if addr, err = ethkey.NewEIP55Address(args.ContractID); err != nil { return nil, err @@ -238,13 +237,13 @@ func FilterNamesFromRelayArgs(args relaytypes.RelayArgs) (filterNames []string, } type configWatcher struct { - utils.StartStopOnce + services.StateMachine lggr logger.Logger contractAddress common.Address contractABI abi.ABI offchainDigester ocrtypes.OffchainConfigDigester configPoller types.ConfigPoller - chain evm.Chain + chain legacyevm.Chain runReplay bool fromBlock uint64 replayCtx context.Context @@ -257,13 +256,12 @@ func newConfigWatcher(lggr logger.Logger, contractABI abi.ABI, offchainDigester ocrtypes.OffchainConfigDigester, configPoller types.ConfigPoller, - chain evm.Chain, + chain legacyevm.Chain, fromBlock uint64, runReplay bool, ) *configWatcher { replayCtx, replayCancel := context.WithCancel(context.Background()) return &configWatcher{ - StartStopOnce: utils.StartStopOnce{}, lggr: lggr.Named("ConfigWatcher").Named(contractAddress.String()), contractAddress: contractAddress, contractABI: contractABI, @@ -274,7 +272,6 @@ func newConfigWatcher(lggr logger.Logger, fromBlock: fromBlock, replayCtx: replayCtx, replayCancel: replayCancel, - wg: sync.WaitGroup{}, } } @@ -312,7 +309,7 @@ func (c *configWatcher) Close() error { } func (c *configWatcher) HealthReport() map[string]error { - return map[string]error{c.Name(): c.StartStopOnce.Healthy()} + return map[string]error{c.Name(): c.Healthy()} } func (c *configWatcher) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { @@ -323,7 +320,7 @@ func (c *configWatcher) ContractConfigTracker() ocrtypes.ContractConfigTracker { return c.configPoller } -func newConfigProvider(lggr logger.Logger, chain evm.Chain, opts *types.RelayOpts, eventBroadcaster pg.EventBroadcaster) (*configWatcher, error) { +func newConfigProvider(lggr logger.Logger, chain legacyevm.Chain, opts *types.RelayOpts, eventBroadcaster pg.EventBroadcaster) (*configWatcher, error) { if !common.IsHexAddress(opts.ContractID) { return nil, pkgerrors.Errorf("invalid contractID, expected hex address") } @@ -375,7 +372,7 @@ func newConfigProvider(lggr logger.Logger, chain evm.Chain, opts *types.RelayOpt return newConfigWatcher(lggr, aggregatorAddress, contractABI, offchainConfigDigester, cp, chain, relayConfig.FromBlock, opts.New), nil } -func newContractTransmitter(lggr logger.Logger, rargs relaytypes.RelayArgs, transmitterID string, configWatcher *configWatcher, ethKeystore keystore.Eth) (*contractTransmitter, error) { +func newContractTransmitter(lggr logger.Logger, rargs commontypes.RelayArgs, transmitterID string, configWatcher *configWatcher, ethKeystore keystore.Eth) (*contractTransmitter, error) { var relayConfig types.RelayConfig if err := json.Unmarshal(rargs.RelayConfig, &relayConfig); err != nil { return nil, err @@ -444,7 +441,7 @@ func newContractTransmitter(lggr logger.Logger, rargs relaytypes.RelayArgs, tran ) } -func newPipelineContractTransmitter(lggr logger.Logger, rargs relaytypes.RelayArgs, transmitterID string, pluginGasLimit *uint32, configWatcher *configWatcher, spec job.Job, pr pipeline.Runner) (*contractTransmitter, error) { +func newPipelineContractTransmitter(lggr logger.Logger, rargs commontypes.RelayArgs, transmitterID string, pluginGasLimit *uint32, configWatcher *configWatcher, spec job.Job, pr pipeline.Runner) (*contractTransmitter, error) { var relayConfig types.RelayConfig if err := json.Unmarshal(rargs.RelayConfig, &relayConfig); err != nil { return nil, err @@ -493,7 +490,7 @@ func newPipelineContractTransmitter(lggr logger.Logger, rargs relaytypes.RelayAr ) } -func (r *Relayer) NewMedianProvider(rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs) (relaytypes.MedianProvider, error) { +func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.MedianProvider, error) { lggr := r.lggr.Named("MedianProvider").Named(rargs.ExternalJobID.String()) relayOpts := types.NewRelayOpts(rargs) relayConfig, err := relayOpts.RelayConfig() @@ -521,6 +518,7 @@ func (r *Relayer) NewMedianProvider(rargs relaytypes.RelayArgs, pargs relaytypes return nil, err } return &medianProvider{ + lggr: lggr.Named("MedianProvider"), configWatcher: configWatcher, reportCodec: reportCodec, contractTransmitter: contractTransmitter, @@ -528,9 +526,10 @@ func (r *Relayer) NewMedianProvider(rargs relaytypes.RelayArgs, pargs relaytypes }, nil } -var _ relaytypes.MedianProvider = (*medianProvider)(nil) +var _ commontypes.MedianProvider = (*medianProvider)(nil) type medianProvider struct { + lggr logger.Logger configWatcher *configWatcher contractTransmitter ContractTransmitter reportCodec median.ReportCodec @@ -539,26 +538,22 @@ type medianProvider struct { ms services.MultiStart } -func (p *medianProvider) Name() string { - return "EVM.MedianProvider" -} +func (p *medianProvider) Name() string { return p.lggr.Name() } func (p *medianProvider) Start(ctx context.Context) error { - return p.ms.Start(ctx, p.configWatcher, p.contractTransmitter) + return p.ms.Start(ctx, p.configWatcher, p.contractTransmitter, p.medianContract) } -func (p *medianProvider) Close() error { - return p.ms.Close() -} +func (p *medianProvider) Close() error { return p.ms.Close() } -func (p *medianProvider) Ready() error { - return multierr.Combine(p.configWatcher.Ready(), p.contractTransmitter.Ready()) -} +func (p *medianProvider) Ready() error { return nil } func (p *medianProvider) HealthReport() map[string]error { - report := p.configWatcher.HealthReport() - services.CopyHealth(report, p.contractTransmitter.HealthReport()) - return report + hp := map[string]error{p.Name(): p.Ready()} + services.CopyHealth(hp, p.configWatcher.HealthReport()) + services.CopyHealth(hp, p.contractTransmitter.HealthReport()) + services.CopyHealth(hp, p.medianContract.HealthReport()) + return hp } func (p *medianProvider) ContractTransmitter() ocrtypes.ContractTransmitter { diff --git a/core/services/relay/evm/evm_test.go b/core/services/relay/evm/evm_test.go index df7cd8eb818..8f49128ff2d 100644 --- a/core/services/relay/evm/evm_test.go +++ b/core/services/relay/evm/evm_test.go @@ -3,10 +3,11 @@ package evm_test import ( "testing" - "github.com/smartcontractkit/sqlx" "github.com/stretchr/testify/assert" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" ) diff --git a/core/services/relay/evm/functions.go b/core/services/relay/evm/functions.go index 43a3d2ff86f..10e5d543b1a 100644 --- a/core/services/relay/evm/functions.go +++ b/core/services/relay/evm/functions.go @@ -9,26 +9,28 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "go.uber.org/multierr" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" txm "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" functionsRelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/functions" evmRelayTypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) type functionsProvider struct { - utils.StartStopOnce + services.StateMachine configWatcher *configWatcher contractTransmitter ContractTransmitter logPollerWrapper evmRelayTypes.LogPollerWrapper @@ -44,7 +46,7 @@ func (p *functionsProvider) LogPollerWrapper() evmRelayTypes.LogPollerWrapper { return p.logPollerWrapper } -func (p *functionsProvider) FunctionsEvents() relaytypes.FunctionsEvents { +func (p *functionsProvider) FunctionsEvents() commontypes.FunctionsEvents { // TODO (FUN-668): implement return nil } @@ -83,7 +85,7 @@ func (p *functionsProvider) Name() string { return p.configWatcher.Name() } -func NewFunctionsProvider(chain evm.Chain, rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs, lggr logger.Logger, ethKeystore keystore.Eth, pluginType functionsRelay.FunctionsPluginType) (evmRelayTypes.FunctionsProvider, error) { +func NewFunctionsProvider(chain legacyevm.Chain, rargs commontypes.RelayArgs, pargs commontypes.PluginArgs, lggr logger.Logger, ethKeystore keystore.Eth, pluginType functionsRelay.FunctionsPluginType) (evmRelayTypes.FunctionsProvider, error) { relayOpts := evmRelayTypes.NewRelayOpts(rargs) relayConfig, err := relayOpts.RelayConfig() if err != nil { @@ -128,7 +130,7 @@ func NewFunctionsProvider(chain evm.Chain, rargs relaytypes.RelayArgs, pargs rel }, nil } -func newFunctionsConfigProvider(pluginType functionsRelay.FunctionsPluginType, chain evm.Chain, args relaytypes.RelayArgs, fromBlock uint64, logPollerWrapper evmRelayTypes.LogPollerWrapper, lggr logger.Logger) (*configWatcher, error) { +func newFunctionsConfigProvider(pluginType functionsRelay.FunctionsPluginType, chain legacyevm.Chain, args commontypes.RelayArgs, fromBlock uint64, logPollerWrapper evmRelayTypes.LogPollerWrapper, lggr logger.Logger) (*configWatcher, error) { if !common.IsHexAddress(args.ContractID) { return nil, errors.Errorf("invalid contractID, expected hex address") } @@ -151,7 +153,7 @@ func newFunctionsConfigProvider(pluginType functionsRelay.FunctionsPluginType, c return newConfigWatcher(lggr, routerContractAddress, contractABI, offchainConfigDigester, cp, chain, fromBlock, args.New), nil } -func newFunctionsContractTransmitter(contractVersion uint32, rargs relaytypes.RelayArgs, transmitterID string, configWatcher *configWatcher, ethKeystore keystore.Eth, logPollerWrapper evmRelayTypes.LogPollerWrapper, lggr logger.Logger) (ContractTransmitter, error) { +func newFunctionsContractTransmitter(contractVersion uint32, rargs commontypes.RelayArgs, transmitterID string, configWatcher *configWatcher, ethKeystore keystore.Eth, logPollerWrapper evmRelayTypes.LogPollerWrapper, lggr logger.Logger) (ContractTransmitter, error) { var relayConfig evmRelayTypes.RelayConfig if err := json.Unmarshal(rargs.RelayConfig, &relayConfig); err != nil { return nil, err diff --git a/core/services/relay/evm/functions/config_poller.go b/core/services/relay/evm/functions/config_poller.go index f068f13cc77..7a59d499898 100644 --- a/core/services/relay/evm/functions/config_poller.go +++ b/core/services/relay/evm/functions/config_poller.go @@ -181,7 +181,7 @@ func (cp *configPoller) LatestBlockHeight(ctx context.Context) (blockHeight uint } return 0, err } - return uint64(latest), nil + return uint64(latest.BlockNumber), nil } // called from LogPollerWrapper in a separate goroutine diff --git a/core/services/relay/evm/functions/logpoller_wrapper.go b/core/services/relay/evm/functions/logpoller_wrapper.go index db2c7fd68ce..95f45022ab3 100644 --- a/core/services/relay/evm/functions/logpoller_wrapper.go +++ b/core/services/relay/evm/functions/logpoller_wrapper.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_coordinator" @@ -21,23 +22,41 @@ import ( ) type logPollerWrapper struct { - utils.StartStopOnce - - routerContract *functions_router.FunctionsRouter - pluginConfig config.PluginConfig - client client.Client - logPoller logpoller.LogPoller - subscribers map[string]evmRelayTypes.RouteUpdateSubscriber - activeCoordinator common.Address - proposedCoordinator common.Address - blockOffset int64 - nextBlock int64 - mu sync.Mutex - closeWait sync.WaitGroup - stopCh utils.StopChan - lggr logger.Logger + services.StateMachine + + routerContract *functions_router.FunctionsRouter + pluginConfig config.PluginConfig + client client.Client + logPoller logpoller.LogPoller + subscribers map[string]evmRelayTypes.RouteUpdateSubscriber + activeCoordinator common.Address + proposedCoordinator common.Address + requestBlockOffset int64 + responseBlockOffset int64 + pastBlocksToPoll int64 + logPollerCacheDurationSec int64 + detectedRequests detectedEvents + detectedResponses detectedEvents + mu sync.Mutex + closeWait sync.WaitGroup + stopCh services.StopChan + lggr logger.Logger } +type detectedEvent struct { + requestId [32]byte + timeDetected time.Time +} + +type detectedEvents struct { + isPreviouslyDetected map[[32]byte]struct{} + detectedEventsOrdered []detectedEvent +} + +const logPollerCacheDurationSecDefault = 300 +const pastBlocksToPollDefault = 50 +const maxLogsToProcess = 1000 + var _ evmRelayTypes.LogPollerWrapper = &logPollerWrapper{} func NewLogPollerWrapper(routerContractAddress common.Address, pluginConfig config.PluginConfig, client client.Client, logPoller logpoller.LogPoller, lggr logger.Logger) (evmRelayTypes.LogPollerWrapper, error) { @@ -47,18 +66,48 @@ func NewLogPollerWrapper(routerContractAddress common.Address, pluginConfig conf } blockOffset := int64(pluginConfig.MinIncomingConfirmations) - 1 if blockOffset < 0 { + lggr.Warnw("invalid minIncomingConfirmations, using 1 instead", "minIncomingConfirmations", pluginConfig.MinIncomingConfirmations) blockOffset = 0 } + requestBlockOffset := int64(pluginConfig.MinRequestConfirmations) - 1 + if requestBlockOffset < 0 { + lggr.Warnw("invalid minRequestConfirmations, using minIncomingConfirmations instead", "minRequestConfirmations", pluginConfig.MinRequestConfirmations) + requestBlockOffset = blockOffset + } + responseBlockOffset := int64(pluginConfig.MinResponseConfirmations) - 1 + if responseBlockOffset < 0 { + lggr.Warnw("invalid minResponseConfirmations, using minIncomingConfirmations instead", "minResponseConfirmations", pluginConfig.MinResponseConfirmations) + responseBlockOffset = blockOffset + } + logPollerCacheDurationSec := int64(pluginConfig.LogPollerCacheDurationSec) + if logPollerCacheDurationSec <= 0 { + lggr.Warnw("invalid logPollerCacheDuration, using 300 instead", "logPollerCacheDurationSec", logPollerCacheDurationSec) + logPollerCacheDurationSec = logPollerCacheDurationSecDefault + } + pastBlocksToPoll := int64(pluginConfig.PastBlocksToPoll) + if pastBlocksToPoll <= 0 { + lggr.Warnw("invalid pastBlocksToPoll, using 50 instead", "pastBlocksToPoll", pastBlocksToPoll) + pastBlocksToPoll = pastBlocksToPollDefault + } + if blockOffset >= pastBlocksToPoll || requestBlockOffset >= pastBlocksToPoll || responseBlockOffset >= pastBlocksToPoll { + lggr.Errorw("invalid config: number of required confirmation blocks >= pastBlocksToPoll", "pastBlocksToPoll", pastBlocksToPoll, "minIncomingConfirmations", pluginConfig.MinIncomingConfirmations, "minRequestConfirmations", pluginConfig.MinRequestConfirmations, "minResponseConfirmations", pluginConfig.MinResponseConfirmations) + return nil, errors.Errorf("invalid config: number of required confirmation blocks >= pastBlocksToPoll") + } return &logPollerWrapper{ - routerContract: routerContract, - pluginConfig: pluginConfig, - blockOffset: blockOffset, - logPoller: logPoller, - client: client, - subscribers: make(map[string]evmRelayTypes.RouteUpdateSubscriber), - stopCh: make(utils.StopChan), - lggr: lggr, + routerContract: routerContract, + pluginConfig: pluginConfig, + requestBlockOffset: requestBlockOffset, + responseBlockOffset: responseBlockOffset, + pastBlocksToPoll: pastBlocksToPoll, + logPollerCacheDurationSec: logPollerCacheDurationSec, + detectedRequests: detectedEvents{isPreviouslyDetected: make(map[[32]byte]struct{})}, + detectedResponses: detectedEvents{isPreviouslyDetected: make(map[[32]byte]struct{})}, + logPoller: logPoller, + client: client, + subscribers: make(map[string]evmRelayTypes.RouteUpdateSubscriber), + stopCh: make(services.StopChan), + lggr: lggr, }, nil } @@ -67,20 +116,11 @@ func (l *logPollerWrapper) Start(context.Context) error { l.lggr.Infow("starting LogPollerWrapper", "routerContract", l.routerContract.Address().Hex(), "contractVersion", l.pluginConfig.ContractVersion) l.mu.Lock() defer l.mu.Unlock() - if l.pluginConfig.ContractVersion == 0 { - l.activeCoordinator = l.routerContract.Address() - l.proposedCoordinator = l.routerContract.Address() - } else if l.pluginConfig.ContractVersion == 1 { - nextBlock, err := l.logPoller.LatestBlock() - if err != nil { - l.lggr.Errorw("LogPollerWrapper: LatestBlock() failed, starting from 0", "error", err) - } else { - l.lggr.Debugw("LogPollerWrapper: LatestBlock() got starting block", "block", nextBlock) - l.nextBlock = nextBlock - l.blockOffset - } - l.closeWait.Add(1) - go l.checkForRouteUpdates() + if l.pluginConfig.ContractVersion != 1 { + return errors.New("only contract version 1 is supported") } + l.closeWait.Add(1) + go l.checkForRouteUpdates() return nil }) } @@ -116,15 +156,15 @@ func (l *logPollerWrapper) LatestEvents() ([]evmRelayTypes.OracleRequest, []evmR if l.proposedCoordinator != (common.Address{}) && l.activeCoordinator != l.proposedCoordinator { coordinators = append(coordinators, l.proposedCoordinator) } - nextBlock := l.nextBlock latest, err := l.logPoller.LatestBlock() if err != nil { l.mu.Unlock() return nil, nil, err } - latest -= l.blockOffset - if latest >= nextBlock { - l.nextBlock = latest + 1 + latestBlockNum := latest.BlockNumber + startBlockNum := latestBlockNum - l.pastBlocksToPoll + if startBlockNum < 0 { + startBlockNum = 0 } l.mu.Unlock() @@ -135,22 +175,24 @@ func (l *logPollerWrapper) LatestEvents() ([]evmRelayTypes.OracleRequest, []evmR l.lggr.Debug("LatestEvents: no non-zero coordinators to check") return resultsReq, resultsResp, errors.New("no non-zero coordinators to check") } - if latest < nextBlock { - l.lggr.Debugw("LatestEvents: no new blocks to check", "latest", latest, "nextBlock", nextBlock) - return resultsReq, resultsResp, nil - } for _, coordinator := range coordinators { - requestLogs, err := l.logPoller.Logs(nextBlock, latest, functions_coordinator.FunctionsCoordinatorOracleRequest{}.Topic(), coordinator) + requestEndBlock := latestBlockNum - l.requestBlockOffset + requestLogs, err := l.logPoller.Logs(startBlockNum, requestEndBlock, functions_coordinator.FunctionsCoordinatorOracleRequest{}.Topic(), coordinator) if err != nil { - l.lggr.Errorw("LatestEvents: fetching request logs from LogPoller failed", "latest", latest, "nextBlock", nextBlock) + l.lggr.Errorw("LatestEvents: fetching request logs from LogPoller failed", "startBlock", startBlockNum, "endBlock", requestEndBlock) return nil, nil, err } - responseLogs, err := l.logPoller.Logs(nextBlock, latest, functions_coordinator.FunctionsCoordinatorOracleResponse{}.Topic(), coordinator) + l.lggr.Debugw("LatestEvents: fetched request logs", "nRequestLogs", len(requestLogs), "latestBlock", latest, "startBlock", startBlockNum, "endBlock", requestEndBlock) + requestLogs = l.filterPreviouslyDetectedEvents(requestLogs, &l.detectedRequests, "requests") + responseEndBlock := latestBlockNum - l.responseBlockOffset + responseLogs, err := l.logPoller.Logs(startBlockNum, responseEndBlock, functions_coordinator.FunctionsCoordinatorOracleResponse{}.Topic(), coordinator) if err != nil { - l.lggr.Errorw("LatestEvents: fetching response logs from LogPoller failed", "latest", latest, "nextBlock", nextBlock) + l.lggr.Errorw("LatestEvents: fetching response logs from LogPoller failed", "startBlock", startBlockNum, "endBlock", responseEndBlock) return nil, nil, err } + l.lggr.Debugw("LatestEvents: fetched request logs", "nResponseLogs", len(responseLogs), "latestBlock", latest, "startBlock", startBlockNum, "endBlock", responseEndBlock) + responseLogs = l.filterPreviouslyDetectedEvents(responseLogs, &l.detectedResponses, "responses") parsingContract, err := functions_coordinator.NewFunctionsCoordinator(coordinator, l.client) if err != nil { @@ -163,7 +205,7 @@ func (l *logPollerWrapper) LatestEvents() ([]evmRelayTypes.OracleRequest, []evmR gethLog := log.ToGethLog() oracleRequest, err := parsingContract.ParseOracleRequest(gethLog) if err != nil { - l.lggr.Errorw("LatestEvents: failed to parse a request log, skipping") + l.lggr.Errorw("LatestEvents: failed to parse a request log, skipping", "err", err) continue } @@ -239,10 +281,46 @@ func (l *logPollerWrapper) LatestEvents() ([]evmRelayTypes.OracleRequest, []evmR } } - l.lggr.Debugw("LatestEvents: done", "nRequestLogs", len(resultsReq), "nResponseLogs", len(resultsResp), "nextBlock", nextBlock, "latest", latest) + l.lggr.Debugw("LatestEvents: done", "nRequestLogs", len(resultsReq), "nResponseLogs", len(resultsResp), "startBlock", startBlockNum, "endBlock", latestBlockNum) return resultsReq, resultsResp, nil } +func (l *logPollerWrapper) filterPreviouslyDetectedEvents(logs []logpoller.Log, detectedEvents *detectedEvents, filterType string) []logpoller.Log { + if len(logs) > maxLogsToProcess { + l.lggr.Errorw("filterPreviouslyDetectedEvents: too many logs to process, only processing latest maxLogsToProcess logs", "filterType", filterType, "nLogs", len(logs), "maxLogsToProcess", maxLogsToProcess) + logs = logs[len(logs)-maxLogsToProcess:] + } + l.mu.Lock() + defer l.mu.Unlock() + filteredLogs := []logpoller.Log{} + for _, log := range logs { + var requestId [32]byte + if len(log.Topics) < 2 || len(log.Topics[1]) != 32 { + l.lggr.Errorw("filterPreviouslyDetectedEvents: invalid log, skipping", "filterType", filterType, "log", log) + continue + } + copy(requestId[:], log.Topics[1]) // requestId is the second topic (1st topic is the event signature) + if _, ok := detectedEvents.isPreviouslyDetected[requestId]; !ok { + filteredLogs = append(filteredLogs, log) + detectedEvents.isPreviouslyDetected[requestId] = struct{}{} + detectedEvents.detectedEventsOrdered = append(detectedEvents.detectedEventsOrdered, detectedEvent{requestId: requestId, timeDetected: time.Now()}) + } + } + expiredRequests := 0 + for _, detectedEvent := range detectedEvents.detectedEventsOrdered { + expirationTime := time.Now().Add(-time.Second * time.Duration(l.logPollerCacheDurationSec)) + if detectedEvent.timeDetected.Before(expirationTime) { + delete(detectedEvents.isPreviouslyDetected, detectedEvent.requestId) + expiredRequests++ + } else { + break + } + } + detectedEvents.detectedEventsOrdered = detectedEvents.detectedEventsOrdered[expiredRequests:] + l.lggr.Debugw("filterPreviouslyDetectedEvents: done", "filterType", filterType, "nLogs", len(logs), "nFilteredLogs", len(filteredLogs), "nExpiredRequests", expiredRequests, "previouslyDetectedCacheSize", len(detectedEvents.detectedEventsOrdered)) + return filteredLogs +} + // "internal" method called only by EVM relayer components func (l *logPollerWrapper) SubscribeToUpdates(subscriberName string, subscriber evmRelayTypes.RouteUpdateSubscriber) { if l.pluginConfig.ContractVersion == 0 { diff --git a/core/services/relay/evm/functions/logpoller_wrapper_test.go b/core/services/relay/evm/functions/logpoller_wrapper_test.go index 224aa51a5da..2108e822d5e 100644 --- a/core/services/relay/evm/functions/logpoller_wrapper_test.go +++ b/core/services/relay/evm/functions/logpoller_wrapper_test.go @@ -1,22 +1,24 @@ -package functions_test +package functions import ( + "crypto/rand" "encoding/hex" "sync" "testing" + "time" "github.com/ethereum/go-ethereum/common" - gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" lpmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_coordinator" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions/config" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/functions" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -57,17 +59,34 @@ func setUp(t *testing.T, updateFrequencySec uint32) (*lpmocks.LogPoller, types.L ContractUpdateCheckFrequencySec: updateFrequencySec, ContractVersion: 1, } - lpWrapper, err := functions.NewLogPollerWrapper(gethcommon.Address{}, config, client, lp, lggr) + lpWrapper, err := NewLogPollerWrapper(common.Address{}, config, client, lp, lggr) require.NoError(t, err) - lp.On("LatestBlock").Return(int64(100), nil) - return lp, lpWrapper, client } +func getMockedRequestLog(t *testing.T) logpoller.Log { + // NOTE: Change this to be a more readable log generation + data, err := hex.DecodeString("000000000000000000000000c113ba31b0080f940ca5812bbccc1e038ea9efb40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c113ba31b0080f940ca5812bbccc1e038ea9efb4000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001117082cd81744eb9504dc37f53a86db7e3fb24929b8e7507b097d501ab5b315fb20e0000000000000000000000001b4f2b0e6363097f413c249910d5bc632993ed08000000000000000000000000000000000000000000000000015bcf880382c000000000000000000000000000665785a800593e8fa915208c1ce62f6e57fd75ba0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000001117000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004f588000000000000000000000000000000000000000000000000000000000000c350000000000000000000000000000000000000000000000000000000000000021c00000000000000000000000000000000000000000000000000000000000008866c636f64654c6f636174696f6ec258200000000000000000000000000000000000000000000000000000000000000000686c616e6775616765c25820000000000000000000000000000000000000000000000000000000000000000066736f757263657907d0633836366665643238326533313137636466303836633934396662613133643834666331376131656335353934656361643034353133646632326137623538356333363763633132326236373138306334383737303435616235383033373463353066313862346564386132346131323437383532363731623030633035663237373163663036363632333663333236393939323139363866323833346438626462616266306661643165313237613837643237363936323831643965656539326134646263316337356137316136656333613135356438633230616661643064623432383362613433353736303734653035633433633561653061656466643332323838346536613231386466323430323630316436356437316131303061633065376563643037663565646364633535643562373932646130626632353665623038363139336463376431333965613764373965653531653831356465333834386565643363366330353837393265366461333434363738626436373239346636643639656564356132663836323835343965616530323235323835346232666361333635646265623032383433386537326234383465383864316136646563373933633739656265353834666465363465663831383363313365386231623735663037636532303963393138633532643637613735343862653236366433663964316439656132613162303166633838376231316162383739663164333861373833303563373031316533643938346130393863663634383931316536653065383038396365306130363230393136663134323935343036336630376239343931326435666331393366303138633764616135363136323562313966376463323036663930353365623234643036323234616164326338623430646162663631656166666635326234653831373239353837333830313561643730663739316663643864333739343035353737393563383937363164636665333639373938373437353439633234643530646464303563623337613465613863353162306530313032363738643433653766306563353039653434633564343764353335626261363831303936383264643864653439326532363633646336653133653532383539663664336565306533633430336236366362653338643236366137356163373639363863613465653331396166363965373431333137393162653630376537353832373430366164653038306335623239653665343262386563386137373761663865383166336234616337626263666531643066616633393338613664353061316561633835643933643234343066313863333037356237306433626134663930323836396439383937663266636562626262366263646439333436633336633663643838626434336265306562333134323562343665613765386338336638386230363933343836383666366134313839623535666132666431396634326264333730313634616339356530303635656461663130373761633131366632393930303833616631333839636661666336613433323439376531363437393762633738616633366335613435366136646661326636626430626639326136613930366130653930313130626266323265613066333163663364353132663466303331653236343330633831663935656431323362323938356266623830623161396432646337306232356264613961386261303839323833666166663634383661316231646235613938353564346237363966623835663531353063393935306462303964373536326537353133633234653531636163366634366634633231636234373561613937363166666466626434656138613531626465613432383037313466363538393630656336643139656539373237626339316635313665346466306665346264613762623035343161393462326334396636323938616132396337656130646662653635346632306437663164323239633066303262356535326137363031376237306439383232643533383166623966613166393361353861376338383632326631326462643363623937323363626132313639633337643538303939336333663666393065323039336331336130363132323334303064393731363031656262313631343332613966666333373033396562663537326364326566666635636562323539346236346462336261616431633734663532653938343938353964383363313238353465376263393764363432363464653931343735386333386438383739343132333937653263643534653431366234373962363331623830626633306266653062366239353564393066356362303435346361373531303963393938366330636536316165356566376534653433353036313432633633646235363862383634353139623463306636366137633161376661336538666431323231376666336665383164663830643138386232646334343833356132663332323733666133353139633531343764643233353763326161346336326461386238353232306535386130333565373662633133316634623734376632663731643263663933376431303832356138316533623963323136663962316134646431663239383463656635656363656265353530363662363061373263363063323864303336653766386635323131343735386638326366323330646636363930636364617267739f64617267316461726732ff6f736563726574734c6f636174696f6ec2582000000000000000000000000000000000000000000000000000000000000000016773656372657473430102030000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + topic0, err := hex.DecodeString("bf50768ccf13bd0110ca6d53a9c4f1f3271abdd4c24a56878863ed25b20598ff") + require.NoError(t, err) + // Create a random requestID + topic1 := make([]byte, 32) + _, err = rand.Read(topic1) + require.NoError(t, err) + topic2, err := hex.DecodeString("000000000000000000000000665785a800593e8fa915208c1ce62f6e57fd75ba") + require.NoError(t, err) + return logpoller.Log{ + Topics: [][]byte{topic0, topic1, topic2}, + Data: data, + } +} + func TestLogPollerWrapper_SingleSubscriberEmptyEvents(t *testing.T) { t.Parallel() lp, lpWrapper, client := setUp(t, 100_000) // check only once + lp.On("LatestBlock").Return(logpoller.LogPollerBlock{BlockNumber: int64(100)}, nil) lp.On("Logs", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]logpoller.Log{}, nil) client.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(addr(t, "01"), nil) @@ -87,7 +106,8 @@ func TestLogPollerWrapper_SingleSubscriberEmptyEvents(t *testing.T) { func TestLogPollerWrapper_ErrorOnZeroAddresses(t *testing.T) { t.Parallel() - _, lpWrapper, client := setUp(t, 100_000) // check only once + lp, lpWrapper, client := setUp(t, 100_000) // check only once + lp.On("LatestBlock").Return(logpoller.LogPollerBlock{BlockNumber: int64(100)}, nil) client.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(addr(t, "00"), nil) @@ -96,3 +116,96 @@ func TestLogPollerWrapper_ErrorOnZeroAddresses(t *testing.T) { require.Error(t, err) lpWrapper.Close() } + +func TestLogPollerWrapper_LatestEvents_ReorgHandling(t *testing.T) { + t.Parallel() + lp, lpWrapper, client := setUp(t, 100_000) + lp.On("LatestBlock").Return(logpoller.LogPollerBlock{BlockNumber: int64(100)}, nil) + client.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(addr(t, "01"), nil) + lp.On("RegisterFilter", mock.Anything).Return(nil) + subscriber := newSubscriber(1) + lpWrapper.SubscribeToUpdates("mock_subscriber", subscriber) + mockedLog := getMockedRequestLog(t) + // All logPoller queries for responses return none + lp.On("Logs", mock.Anything, mock.Anything, functions_coordinator.FunctionsCoordinatorOracleResponse{}.Topic(), mock.Anything).Return([]logpoller.Log{}, nil) + // On the first logPoller query for requests, the request log appears + lp.On("Logs", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]logpoller.Log{mockedLog}, nil).Once() + // On the 2nd query, the request log disappears + lp.On("Logs", mock.Anything, mock.Anything, functions_coordinator.FunctionsCoordinatorOracleRequest{}.Topic(), mock.Anything).Return([]logpoller.Log{}, nil).Once() + // On the 3rd query, the original request log appears again + lp.On("Logs", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]logpoller.Log{mockedLog}, nil).Once() + + require.NoError(t, lpWrapper.Start(testutils.Context(t))) + subscriber.updates.Wait() + + oracleRequests, _, err := lpWrapper.LatestEvents() + require.NoError(t, err) + assert.Equal(t, 1, len(oracleRequests)) + oracleRequests, _, err = lpWrapper.LatestEvents() + require.NoError(t, err) + assert.Equal(t, 0, len(oracleRequests)) + require.NoError(t, err) + oracleRequests, _, err = lpWrapper.LatestEvents() + require.NoError(t, err) + assert.Equal(t, 0, len(oracleRequests)) +} + +func TestLogPollerWrapper_FilterPreviouslyDetectedEvents_TruncatesLogs(t *testing.T) { + t.Parallel() + _, lpWrapper, _ := setUp(t, 100_000) + + inputLogs := make([]logpoller.Log, maxLogsToProcess+100) + for i := 0; i < 1100; i++ { + inputLogs[i] = getMockedRequestLog(t) + } + + functionsLpWrapper := lpWrapper.(*logPollerWrapper) + mockedDetectedEvents := detectedEvents{isPreviouslyDetected: make(map[[32]byte]struct{})} + outputLogs := functionsLpWrapper.filterPreviouslyDetectedEvents(inputLogs, &mockedDetectedEvents, "request") + + assert.Equal(t, maxLogsToProcess, len(outputLogs)) + assert.Equal(t, 1000, len(mockedDetectedEvents.detectedEventsOrdered)) + assert.Equal(t, 1000, len(mockedDetectedEvents.isPreviouslyDetected)) +} + +func TestLogPollerWrapper_FilterPreviouslyDetectedEvents_SkipsInvalidLog(t *testing.T) { + t.Parallel() + _, lpWrapper, _ := setUp(t, 100_000) + inputLogs := []logpoller.Log{getMockedRequestLog(t)} + inputLogs[0].Topics = [][]byte{[]byte("invalid topic")} + mockedDetectedEvents := detectedEvents{isPreviouslyDetected: make(map[[32]byte]struct{})} + + functionsLpWrapper := lpWrapper.(*logPollerWrapper) + outputLogs := functionsLpWrapper.filterPreviouslyDetectedEvents(inputLogs, &mockedDetectedEvents, "request") + + assert.Equal(t, 0, len(outputLogs)) + assert.Equal(t, 0, len(mockedDetectedEvents.detectedEventsOrdered)) + assert.Equal(t, 0, len(mockedDetectedEvents.isPreviouslyDetected)) +} + +func TestLogPollerWrapper_FilterPreviouslyDetectedEvents_FiltersPreviouslyDetectedEvent(t *testing.T) { + t.Parallel() + _, lpWrapper, _ := setUp(t, 100_000) + mockedRequestLog := getMockedRequestLog(t) + inputLogs := []logpoller.Log{mockedRequestLog} + var mockedRequestId [32]byte + copy(mockedRequestId[:], mockedRequestLog.Topics[1]) + + mockedDetectedEvents := detectedEvents{ + isPreviouslyDetected: make(map[[32]byte]struct{}), + detectedEventsOrdered: make([]detectedEvent, 1), + } + mockedDetectedEvents.isPreviouslyDetected[mockedRequestId] = struct{}{} + mockedDetectedEvents.detectedEventsOrdered[0] = detectedEvent{ + requestId: mockedRequestId, + timeDetected: time.Now().Add(-time.Second * time.Duration(logPollerCacheDurationSecDefault+1)), + } + + functionsLpWrapper := lpWrapper.(*logPollerWrapper) + outputLogs := functionsLpWrapper.filterPreviouslyDetectedEvents(inputLogs, &mockedDetectedEvents, "request") + + assert.Equal(t, 0, len(outputLogs)) + // Ensure that expired events are removed from the cache + assert.Equal(t, 0, len(mockedDetectedEvents.detectedEventsOrdered)) + assert.Equal(t, 0, len(mockedDetectedEvents.isPreviouslyDetected)) +} diff --git a/core/services/relay/evm/loop_impl.go b/core/services/relay/evm/loop_impl.go index f69660373e6..57a09dd49ae 100644 --- a/core/services/relay/evm/loop_impl.go +++ b/core/services/relay/evm/loop_impl.go @@ -1,16 +1,16 @@ package evm import ( - "github.com/smartcontractkit/chainlink-relay/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/services/relay" ) //go:generate mockery --quiet --name LoopRelayAdapter --output ./mocks/ --case=underscore type LoopRelayAdapter interface { loop.Relayer - Chain() evm.Chain + Chain() legacyevm.Chain } type LoopRelayer struct { loop.Relayer @@ -27,6 +27,6 @@ func NewLoopRelayServerAdapter(r *Relayer, cs EVMChainRelayerExtender) *LoopRela } } -func (la *LoopRelayer) Chain() evm.Chain { +func (la *LoopRelayer) Chain() legacyevm.Chain { return la.ext.Chain() } diff --git a/core/services/relay/evm/median.go b/core/services/relay/evm/median.go index b7d751e01e1..3f86d6f8233 100644 --- a/core/services/relay/evm/median.go +++ b/core/services/relay/evm/median.go @@ -7,14 +7,16 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" offchain_aggregator_wrapper "github.com/smartcontractkit/chainlink/v2/core/internal/gethwrappers2/generated/offchainaggregator" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -22,12 +24,15 @@ import ( var _ median.MedianContract = &medianContract{} type medianContract struct { + services.StateMachine + lggr logger.Logger configTracker types.ContractConfigTracker contractCaller *ocr2aggregator.OCR2AggregatorCaller requestRoundTracker *RequestRoundTracker } -func newMedianContract(configTracker types.ContractConfigTracker, contractAddress common.Address, chain evm.Chain, specID int32, db *sqlx.DB, lggr logger.Logger) (*medianContract, error) { +func newMedianContract(configTracker types.ContractConfigTracker, contractAddress common.Address, chain legacyevm.Chain, specID int32, db *sqlx.DB, lggr logger.Logger) (*medianContract, error) { + lggr = lggr.Named("MedianContract") contract, err := offchain_aggregator_wrapper.NewOffchainAggregator(contractAddress, chain.Client()) if err != nil { return nil, errors.Wrap(err, "could not instantiate NewOffchainAggregator") @@ -44,6 +49,7 @@ func newMedianContract(configTracker types.ContractConfigTracker, contractAddres } return &medianContract{ + lggr: lggr, configTracker: configTracker, contractCaller: contractCaller, requestRoundTracker: NewRequestRoundTracker( @@ -60,13 +66,22 @@ func newMedianContract(configTracker types.ContractConfigTracker, contractAddres ), }, nil } - -func (oc *medianContract) Start() error { - return oc.requestRoundTracker.Start() +func (oc *medianContract) Start(context.Context) error { + return oc.StartOnce("MedianContract", func() error { + return oc.requestRoundTracker.Start() + }) } func (oc *medianContract) Close() error { - return oc.requestRoundTracker.Close() + return oc.StopOnce("MedianContract", func() error { + return oc.requestRoundTracker.Close() + }) +} + +func (oc *medianContract) Name() string { return oc.lggr.Name() } + +func (oc *medianContract) HealthReport() map[string]error { + return map[string]error{oc.Name(): oc.Ready()} } func (oc *medianContract) LatestTransmissionDetails(ctx context.Context) (ocrtypes.ConfigDigest, uint32, uint8, *big.Int, time.Time, error) { diff --git a/core/services/relay/evm/mercury/config_poller.go b/core/services/relay/evm/mercury/config_poller.go index 2f16157bfac..8964a283049 100644 --- a/core/services/relay/evm/mercury/config_poller.go +++ b/core/services/relay/evm/mercury/config_poller.go @@ -188,7 +188,7 @@ func (cp *ConfigPoller) LatestBlockHeight(ctx context.Context) (blockHeight uint } return 0, err } - return uint64(latest), nil + return uint64(latest.BlockNumber), nil } func (cp *ConfigPoller) startLogSubscription() { diff --git a/core/services/relay/evm/mercury/config_poller_test.go b/core/services/relay/evm/mercury/config_poller_test.go index 6a692b0eacd..1b3ba72128d 100644 --- a/core/services/relay/evm/mercury/config_poller_test.go +++ b/core/services/relay/evm/mercury/config_poller_test.go @@ -115,6 +115,7 @@ func TestMercuryConfigPoller(t *testing.T) { } func TestNotify(t *testing.T) { + testutils.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/BCF-2746") feedIDStr := "8257737fdf4f79639585fd0ed01bea93c248a9ad940e98dd27f41c9b6230fed1" feedIDBytes, err := hexutil.Decode("0x" + feedIDStr) require.NoError(t, err) diff --git a/core/services/relay/evm/mercury/mocks/chain_head_tracker.go b/core/services/relay/evm/mercury/mocks/chain_head_tracker.go deleted file mode 100644 index 1a5a7e47c5b..00000000000 --- a/core/services/relay/evm/mercury/mocks/chain_head_tracker.go +++ /dev/null @@ -1,65 +0,0 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. - -package mocks - -import ( - common "github.com/ethereum/go-ethereum/common" - client "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - - commontypes "github.com/smartcontractkit/chainlink/v2/common/types" - - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - - mock "github.com/stretchr/testify/mock" -) - -// ChainHeadTracker is an autogenerated mock type for the ChainHeadTracker type -type ChainHeadTracker struct { - mock.Mock -} - -// Client provides a mock function with given fields: -func (_m *ChainHeadTracker) Client() client.Client { - ret := _m.Called() - - var r0 client.Client - if rf, ok := ret.Get(0).(func() client.Client); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(client.Client) - } - } - - return r0 -} - -// HeadTracker provides a mock function with given fields: -func (_m *ChainHeadTracker) HeadTracker() commontypes.HeadTracker[*evmtypes.Head, common.Hash] { - ret := _m.Called() - - var r0 commontypes.HeadTracker[*evmtypes.Head, common.Hash] - if rf, ok := ret.Get(0).(func() commontypes.HeadTracker[*evmtypes.Head, common.Hash]); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(commontypes.HeadTracker[*evmtypes.Head, common.Hash]) - } - } - - return r0 -} - -// NewChainHeadTracker creates a new instance of ChainHeadTracker. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewChainHeadTracker(t interface { - mock.TestingT - Cleanup(func()) -}) *ChainHeadTracker { - mock := &ChainHeadTracker{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/core/services/relay/evm/mercury/orm.go b/core/services/relay/evm/mercury/orm.go index dd7d7b33e74..f8d4c8cb1ee 100644 --- a/core/services/relay/evm/mercury/orm.go +++ b/core/services/relay/evm/mercury/orm.go @@ -8,9 +8,9 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" "github.com/lib/pq" pkgerrors "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" @@ -23,8 +23,8 @@ import ( type ORM interface { InsertTransmitRequest(req *pb.TransmitRequest, jobID int32, reportCtx ocrtypes.ReportContext, qopts ...pg.QOpt) error DeleteTransmitRequests(reqs []*pb.TransmitRequest, qopts ...pg.QOpt) error - GetTransmitRequests(qopts ...pg.QOpt) ([]*Transmission, error) - PruneTransmitRequests(maxSize int, qopts ...pg.QOpt) error + GetTransmitRequests(jobID int32, qopts ...pg.QOpt) ([]*Transmission, error) + PruneTransmitRequests(jobID int32, maxSize int, qopts ...pg.QOpt) error LatestReport(ctx context.Context, feedID [32]byte, qopts ...pg.QOpt) (report []byte, err error) } @@ -49,6 +49,11 @@ func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig) ORM { // InsertTransmitRequest inserts one transmit request if the payload does not exist already. func (o *orm) InsertTransmitRequest(req *pb.TransmitRequest, jobID int32, reportCtx ocrtypes.ReportContext, qopts ...pg.QOpt) error { + feedID, err := FeedIDFromReport(req.Payload) + if err != nil { + return err + } + q := o.q.WithOpts(qopts...) var wg sync.WaitGroup wg.Add(2) @@ -57,16 +62,12 @@ func (o *orm) InsertTransmitRequest(req *pb.TransmitRequest, jobID int32, report go func() { defer wg.Done() err1 = q.ExecQ(` - INSERT INTO mercury_transmit_requests (payload, payload_hash, config_digest, epoch, round, extra_hash, job_id) - VALUES ($1, $2, $3, $4, $5, $6, $7) + INSERT INTO mercury_transmit_requests (payload, payload_hash, config_digest, epoch, round, extra_hash, job_id, feed_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (payload_hash) DO NOTHING - `, req.Payload, hashPayload(req.Payload), reportCtx.ConfigDigest[:], reportCtx.Epoch, reportCtx.Round, reportCtx.ExtraHash[:], jobID) + `, req.Payload, hashPayload(req.Payload), reportCtx.ConfigDigest[:], reportCtx.Epoch, reportCtx.Round, reportCtx.ExtraHash[:], jobID, feedID[:]) }() - feedID, err := FeedIDFromReport(req.Payload) - if err != nil { - return err - } go func() { defer wg.Done() err2 = q.ExecQ(` @@ -101,15 +102,16 @@ func (o *orm) DeleteTransmitRequests(reqs []*pb.TransmitRequest, qopts ...pg.QOp } // GetTransmitRequests returns all transmit requests in chronologically descending order. -func (o *orm) GetTransmitRequests(qopts ...pg.QOpt) ([]*Transmission, error) { +func (o *orm) GetTransmitRequests(jobID int32, qopts ...pg.QOpt) ([]*Transmission, error) { q := o.q.WithOpts(qopts...) // The priority queue uses epoch and round to sort transmissions so order by // the same fields here for optimal insertion into the pq. rows, err := q.QueryContext(q.ParentCtx, ` SELECT payload, config_digest, epoch, round, extra_hash FROM mercury_transmit_requests + WHERE job_id = $1 ORDER BY epoch DESC, round DESC - `) + `, jobID) if err != nil { return nil, err } @@ -142,20 +144,22 @@ func (o *orm) GetTransmitRequests(qopts ...pg.QOpt) ([]*Transmission, error) { return transmissions, nil } -// PruneTransmitRequests keeps at most maxSize rows in the table, deleting the -// oldest transactions. -func (o *orm) PruneTransmitRequests(maxSize int, qopts ...pg.QOpt) error { +// PruneTransmitRequests keeps at most maxSize rows for the given job ID, +// deleting the oldest transactions. +func (o *orm) PruneTransmitRequests(jobID int32, maxSize int, qopts ...pg.QOpt) error { q := o.q.WithOpts(qopts...) // Prune the oldest requests by epoch and round. return q.ExecQ(` DELETE FROM mercury_transmit_requests - WHERE payload_hash NOT IN ( + WHERE job_id = $1 AND + payload_hash NOT IN ( SELECT payload_hash FROM mercury_transmit_requests + WHERE job_id = $1 ORDER BY epoch DESC, round DESC - LIMIT $1 + LIMIT $2 ) - `, maxSize) + `, jobID, maxSize) } func (o *orm) LatestReport(ctx context.Context, feedID [32]byte, qopts ...pg.QOpt) (report []byte, err error) { diff --git a/core/services/relay/evm/mercury/orm_test.go b/core/services/relay/evm/mercury/orm_test.go index a6a72327677..56dea70417b 100644 --- a/core/services/relay/evm/mercury/orm_test.go +++ b/core/services/relay/evm/mercury/orm_test.go @@ -3,6 +3,7 @@ package mercury import ( "testing" + "github.com/cometbft/cometbft/libs/rand" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -16,7 +17,7 @@ import ( func TestORM(t *testing.T) { db := pgtest.NewSqlxDB(t) - var jobID int32 // foreign key constraints disabled so can leave as 0 + jobID := rand.Int32() // foreign key constraints disabled so value doesn't matter pgtest.MustExec(t, db, `SET CONSTRAINTS mercury_transmit_requests_job_id_fkey DEFERRED`) pgtest.MustExec(t, db, `SET CONSTRAINTS feed_latest_reports_job_id_fkey DEFERRED`) lggr := logger.TestLogger(t) @@ -48,7 +49,7 @@ func TestORM(t *testing.T) { err = orm.InsertTransmitRequest(&pb.TransmitRequest{Payload: reports[2]}, jobID, reportContexts[2]) require.NoError(t, err) - transmissions, err := orm.GetTransmitRequests() + transmissions, err := orm.GetTransmitRequests(jobID) require.NoError(t, err) require.Equal(t, transmissions, []*Transmission{ {Req: &pb.TransmitRequest{Payload: reports[2]}, ReportCtx: reportContexts[2]}, @@ -65,7 +66,7 @@ func TestORM(t *testing.T) { err = orm.DeleteTransmitRequests([]*pb.TransmitRequest{{Payload: reports[1]}}) require.NoError(t, err) - transmissions, err = orm.GetTransmitRequests() + transmissions, err = orm.GetTransmitRequests(jobID) require.NoError(t, err) require.Equal(t, transmissions, []*Transmission{ {Req: &pb.TransmitRequest{Payload: reports[2]}, ReportCtx: reportContexts[2]}, @@ -80,7 +81,7 @@ func TestORM(t *testing.T) { err = orm.DeleteTransmitRequests([]*pb.TransmitRequest{{Payload: []byte("does-not-exist")}}) require.NoError(t, err) - transmissions, err = orm.GetTransmitRequests() + transmissions, err = orm.GetTransmitRequests(jobID) require.NoError(t, err) require.Equal(t, transmissions, []*Transmission{ {Req: &pb.TransmitRequest{Payload: reports[2]}, ReportCtx: reportContexts[2]}, @@ -98,7 +99,7 @@ func TestORM(t *testing.T) { require.NoError(t, err) assert.Equal(t, reports[2], l) - transmissions, err = orm.GetTransmitRequests() + transmissions, err = orm.GetTransmitRequests(jobID) require.NoError(t, err) require.Empty(t, transmissions) @@ -106,7 +107,7 @@ func TestORM(t *testing.T) { err = orm.InsertTransmitRequest(&pb.TransmitRequest{Payload: reports[3]}, jobID, reportContexts[3]) require.NoError(t, err) - transmissions, err = orm.GetTransmitRequests() + transmissions, err = orm.GetTransmitRequests(jobID) require.NoError(t, err) require.Equal(t, transmissions, []*Transmission{ {Req: &pb.TransmitRequest{Payload: reports[3]}, ReportCtx: reportContexts[3]}, @@ -118,7 +119,7 @@ func TestORM(t *testing.T) { err = orm.InsertTransmitRequest(&pb.TransmitRequest{Payload: reports[3]}, jobID, reportContexts[3]) require.NoError(t, err) - transmissions, err = orm.GetTransmitRequests() + transmissions, err = orm.GetTransmitRequests(jobID) require.NoError(t, err) require.Equal(t, transmissions, []*Transmission{ {Req: &pb.TransmitRequest{Payload: reports[3]}, ReportCtx: reportContexts[3]}, @@ -131,7 +132,7 @@ func TestORM(t *testing.T) { func TestORM_PruneTransmitRequests(t *testing.T) { db := pgtest.NewSqlxDB(t) - var jobID int32 // foreign key constraints disabled so can leave as 0 + jobID := rand.Int32() // foreign key constraints disabled so value doesn't matter pgtest.MustExec(t, db, `SET CONSTRAINTS mercury_transmit_requests_job_id_fkey DEFERRED`) pgtest.MustExec(t, db, `SET CONSTRAINTS feed_latest_reports_job_id_fkey DEFERRED`) @@ -157,10 +158,10 @@ func TestORM_PruneTransmitRequests(t *testing.T) { require.NoError(t, err) // Max size greater than table size, expect no-op - err = orm.PruneTransmitRequests(5) + err = orm.PruneTransmitRequests(jobID, 5) require.NoError(t, err) - transmissions, err := orm.GetTransmitRequests() + transmissions, err := orm.GetTransmitRequests(jobID) require.NoError(t, err) require.Equal(t, transmissions, []*Transmission{ {Req: &pb.TransmitRequest{Payload: reports[1]}, ReportCtx: makeReportContext(1, 2)}, @@ -168,37 +169,48 @@ func TestORM_PruneTransmitRequests(t *testing.T) { }) // Max size equal to table size, expect no-op - err = orm.PruneTransmitRequests(2) + err = orm.PruneTransmitRequests(jobID, 2) require.NoError(t, err) - transmissions, err = orm.GetTransmitRequests() + transmissions, err = orm.GetTransmitRequests(jobID) require.NoError(t, err) require.Equal(t, transmissions, []*Transmission{ {Req: &pb.TransmitRequest{Payload: reports[1]}, ReportCtx: makeReportContext(1, 2)}, {Req: &pb.TransmitRequest{Payload: reports[0]}, ReportCtx: makeReportContext(1, 1)}, }) + // Max size is table size + 1, but jobID differs, expect no-op + err = orm.PruneTransmitRequests(-1, 2) + require.NoError(t, err) + + transmissions, err = orm.GetTransmitRequests(jobID) + require.NoError(t, err) + require.Equal(t, []*Transmission{ + {Req: &pb.TransmitRequest{Payload: reports[1]}, ReportCtx: makeReportContext(1, 2)}, + {Req: &pb.TransmitRequest{Payload: reports[0]}, ReportCtx: makeReportContext(1, 1)}, + }, transmissions) + err = orm.InsertTransmitRequest(&pb.TransmitRequest{Payload: reports[2]}, jobID, makeReportContext(2, 1)) require.NoError(t, err) err = orm.InsertTransmitRequest(&pb.TransmitRequest{Payload: reports[3]}, jobID, makeReportContext(2, 2)) require.NoError(t, err) - // Max size is table size + 1, expect the oldest row to be pruned. - err = orm.PruneTransmitRequests(3) + // Max size is table size - 1, expect the oldest row to be pruned. + err = orm.PruneTransmitRequests(jobID, 3) require.NoError(t, err) - transmissions, err = orm.GetTransmitRequests() + transmissions, err = orm.GetTransmitRequests(jobID) require.NoError(t, err) - require.Equal(t, transmissions, []*Transmission{ + require.Equal(t, []*Transmission{ {Req: &pb.TransmitRequest{Payload: reports[3]}, ReportCtx: makeReportContext(2, 2)}, {Req: &pb.TransmitRequest{Payload: reports[2]}, ReportCtx: makeReportContext(2, 1)}, {Req: &pb.TransmitRequest{Payload: reports[1]}, ReportCtx: makeReportContext(1, 2)}, - }) + }, transmissions) } func TestORM_InsertTransmitRequest_LatestReport(t *testing.T) { db := pgtest.NewSqlxDB(t) - var jobID int32 // foreign key constraints disabled so can leave as 0 + jobID := rand.Int32() // foreign key constraints disabled so value doesn't matter pgtest.MustExec(t, db, `SET CONSTRAINTS mercury_transmit_requests_job_id_fkey DEFERRED`) pgtest.MustExec(t, db, `SET CONSTRAINTS feed_latest_reports_job_id_fkey DEFERRED`) diff --git a/core/services/relay/evm/mercury/persistence_manager.go b/core/services/relay/evm/mercury/persistence_manager.go index 9e8df72a155..779e275f154 100644 --- a/core/services/relay/evm/mercury/persistence_manager.go +++ b/core/services/relay/evm/mercury/persistence_manager.go @@ -7,6 +7,7 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" @@ -22,8 +23,8 @@ type PersistenceManager struct { lggr logger.Logger orm ORM - once utils.StartStopOnce - stopCh utils.StopChan + once services.StateMachine + stopCh services.StopChan wg sync.WaitGroup deleteMu sync.Mutex @@ -40,7 +41,7 @@ func NewPersistenceManager(lggr logger.Logger, orm ORM, jobID int32, maxTransmit return &PersistenceManager{ lggr: lggr.Named("MercuryPersistenceManager"), orm: orm, - stopCh: make(chan struct{}), + stopCh: make(services.StopChan), jobID: jobID, maxTransmitQueueSize: maxTransmitQueueSize, flushDeletesFrequency: flushDeletesFrequency, @@ -78,7 +79,7 @@ func (pm *PersistenceManager) AsyncDelete(req *pb.TransmitRequest) { } func (pm *PersistenceManager) Load(ctx context.Context) ([]*Transmission, error) { - return pm.orm.GetTransmitRequests(pg.WithParentCtx(ctx)) + return pm.orm.GetTransmitRequests(pm.jobID, pg.WithParentCtx(ctx)) } func (pm *PersistenceManager) runFlushDeletesLoop() { @@ -118,7 +119,7 @@ func (pm *PersistenceManager) runPruneLoop() { ticker.Stop() return case <-ticker.C: - if err := pm.orm.PruneTransmitRequests(pm.maxTransmitQueueSize, pg.WithParentCtx(ctx), pg.WithLongQueryTimeout()); err != nil { + if err := pm.orm.PruneTransmitRequests(pm.jobID, pm.maxTransmitQueueSize, pg.WithParentCtx(ctx), pg.WithLongQueryTimeout()); err != nil { pm.lggr.Errorw("Failed to prune transmit requests table", "err", err) } else { pm.lggr.Debugw("Pruned transmit requests table") diff --git a/core/services/relay/evm/mercury/persistence_manager_test.go b/core/services/relay/evm/mercury/persistence_manager_test.go index 97628ed9c2b..755d64a5a23 100644 --- a/core/services/relay/evm/mercury/persistence_manager_test.go +++ b/core/services/relay/evm/mercury/persistence_manager_test.go @@ -1,34 +1,40 @@ package mercury import ( - "context" "testing" "time" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/cometbft/cometbft/libs/rand" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" ) -func bootstrapPersistenceManager(t *testing.T) (*PersistenceManager, *observer.ObservedLogs) { +func bootstrapPersistenceManager(t *testing.T, jobID int32, db *sqlx.DB) (*PersistenceManager, *observer.ObservedLogs) { t.Helper() - db := pgtest.NewSqlxDB(t) - pgtest.MustExec(t, db, `SET CONSTRAINTS mercury_transmit_requests_job_id_fkey DEFERRED`) - pgtest.MustExec(t, db, `SET CONSTRAINTS feed_latest_reports_job_id_fkey DEFERRED`) lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.DebugLevel) orm := NewORM(db, lggr, pgtest.NewQConfig(true)) - return NewPersistenceManager(lggr, orm, 0, 2, 5*time.Millisecond, 5*time.Millisecond), observedLogs + return NewPersistenceManager(lggr, orm, jobID, 2, 5*time.Millisecond, 5*time.Millisecond), observedLogs } func TestPersistenceManager(t *testing.T) { - ctx := context.Background() - pm, _ := bootstrapPersistenceManager(t) + jobID1 := rand.Int32() + jobID2 := jobID1 + 1 + + ctx := testutils.Context(t) + db := pgtest.NewSqlxDB(t) + pgtest.MustExec(t, db, `SET CONSTRAINTS mercury_transmit_requests_job_id_fkey DEFERRED`) + pgtest.MustExec(t, db, `SET CONSTRAINTS feed_latest_reports_job_id_fkey DEFERRED`) + pm, _ := bootstrapPersistenceManager(t, jobID1, db) reports := sampleReports @@ -52,11 +58,23 @@ func TestPersistenceManager(t *testing.T) { require.Equal(t, []*Transmission{ {Req: &pb.TransmitRequest{Payload: reports[1]}}, }, transmissions) + + t.Run("scopes load to only transmissions with matching job ID", func(t *testing.T) { + pm2, _ := bootstrapPersistenceManager(t, jobID2, db) + transmissions, err = pm2.Load(ctx) + require.NoError(t, err) + + assert.Len(t, transmissions, 0) + }) } func TestPersistenceManagerAsyncDelete(t *testing.T) { - ctx := context.Background() - pm, observedLogs := bootstrapPersistenceManager(t) + ctx := testutils.Context(t) + jobID := rand.Int32() + db := pgtest.NewSqlxDB(t) + pgtest.MustExec(t, db, `SET CONSTRAINTS mercury_transmit_requests_job_id_fkey DEFERRED`) + pgtest.MustExec(t, db, `SET CONSTRAINTS feed_latest_reports_job_id_fkey DEFERRED`) + pm, observedLogs := bootstrapPersistenceManager(t, jobID, db) reports := sampleReports @@ -96,16 +114,32 @@ func TestPersistenceManagerAsyncDelete(t *testing.T) { } func TestPersistenceManagerPrune(t *testing.T) { - ctx := context.Background() - pm, observedLogs := bootstrapPersistenceManager(t) + jobID1 := rand.Int32() + jobID2 := jobID1 + 1 + db := pgtest.NewSqlxDB(t) + pgtest.MustExec(t, db, `SET CONSTRAINTS mercury_transmit_requests_job_id_fkey DEFERRED`) + pgtest.MustExec(t, db, `SET CONSTRAINTS feed_latest_reports_job_id_fkey DEFERRED`) - reports := sampleReports + ctx := testutils.Context(t) - err := pm.Insert(ctx, &pb.TransmitRequest{Payload: reports[0]}, ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 1}}) + reports := make([][]byte, 25) + for i := 0; i < 25; i++ { + reports[i] = buildSampleV1Report(int64(i)) + } + + pm2, _ := bootstrapPersistenceManager(t, jobID2, db) + for i := 0; i < 20; i++ { + err := pm2.Insert(ctx, &pb.TransmitRequest{Payload: reports[i]}, ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: uint32(i)}}) + require.NoError(t, err) + } + + pm, observedLogs := bootstrapPersistenceManager(t, jobID1, db) + + err := pm.Insert(ctx, &pb.TransmitRequest{Payload: reports[21]}, ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 21}}) require.NoError(t, err) - err = pm.Insert(ctx, &pb.TransmitRequest{Payload: reports[1]}, ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 2}}) + err = pm.Insert(ctx, &pb.TransmitRequest{Payload: reports[22]}, ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 22}}) require.NoError(t, err) - err = pm.Insert(ctx, &pb.TransmitRequest{Payload: reports[2]}, ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 3}}) + err = pm.Insert(ctx, &pb.TransmitRequest{Payload: reports[23]}, ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 23}}) require.NoError(t, err) err = pm.Start(ctx) @@ -118,24 +152,28 @@ func TestPersistenceManagerPrune(t *testing.T) { transmissions, err := pm.Load(ctx) require.NoError(t, err) require.Equal(t, []*Transmission{ - {Req: &pb.TransmitRequest{Payload: reports[2]}, ReportCtx: ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 3}}}, - {Req: &pb.TransmitRequest{Payload: reports[1]}, ReportCtx: ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 2}}}, + {Req: &pb.TransmitRequest{Payload: reports[23]}, ReportCtx: ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 23}}}, + {Req: &pb.TransmitRequest{Payload: reports[22]}, ReportCtx: ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 22}}}, }, transmissions) // Test pruning stops after Close. err = pm.Close() require.NoError(t, err) - err = pm.Insert(ctx, &pb.TransmitRequest{Payload: reports[3]}, ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 4}}) + err = pm.Insert(ctx, &pb.TransmitRequest{Payload: reports[24]}, ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 24}}) require.NoError(t, err) - time.Sleep(15 * time.Millisecond) - transmissions, err = pm.Load(ctx) require.NoError(t, err) require.Equal(t, []*Transmission{ - {Req: &pb.TransmitRequest{Payload: reports[3]}, ReportCtx: ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 4}}}, - {Req: &pb.TransmitRequest{Payload: reports[2]}, ReportCtx: ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 3}}}, - {Req: &pb.TransmitRequest{Payload: reports[1]}, ReportCtx: ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 2}}}, + {Req: &pb.TransmitRequest{Payload: reports[24]}, ReportCtx: ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 24}}}, + {Req: &pb.TransmitRequest{Payload: reports[23]}, ReportCtx: ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 23}}}, + {Req: &pb.TransmitRequest{Payload: reports[22]}, ReportCtx: ocrtypes.ReportContext{ReportTimestamp: ocrtypes.ReportTimestamp{Epoch: 22}}}, }, transmissions) + + t.Run("prune was scoped to job ID", func(t *testing.T) { + transmissions, err = pm2.Load(ctx) + require.NoError(t, err) + assert.Len(t, transmissions, 20) + }) } diff --git a/core/services/relay/evm/mercury/queue.go b/core/services/relay/evm/mercury/queue.go index 44042c57725..07ef8a97426 100644 --- a/core/services/relay/evm/mercury/queue.go +++ b/core/services/relay/evm/mercury/queue.go @@ -13,8 +13,9 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -24,7 +25,7 @@ type asyncDeleter interface { AsyncDelete(req *pb.TransmitRequest) } -var _ services.ServiceCtx = (*TransmitQueue)(nil) +var _ services.Service = (*TransmitQueue)(nil) var transmitQueueLoad = promauto.NewGaugeVec(prometheus.GaugeOpts{ Name: "mercury_transmit_queue_load", @@ -40,7 +41,7 @@ const promInterval = 6500 * time.Millisecond // TransmitQueue is the high-level package that everything outside of this file should be using // It stores pending transmissions, yielding the latest (highest priority) first to the caller type TransmitQueue struct { - utils.StartStopOnce + services.StateMachine cond sync.Cond lggr logger.Logger @@ -68,7 +69,7 @@ func NewTransmitQueue(lggr logger.Logger, feedID string, maxlen int, transmissio heap.Init(&pq) // ensure the heap is ordered mu := new(sync.RWMutex) return &TransmitQueue{ - utils.StartStopOnce{}, + services.StateMachine{}, sync.Cond{L: mu}, lggr.Named("TransmitQueue"), asyncDeleter, diff --git a/core/services/relay/evm/mercury/transmitter.go b/core/services/relay/evm/mercury/transmitter.go index 0c701e3b4b3..d5346ad28cc 100644 --- a/core/services/relay/evm/mercury/transmitter.go +++ b/core/services/relay/evm/mercury/transmitter.go @@ -16,12 +16,13 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/sqlx" - relaymercury "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury" - "github.com/smartcontractkit/chainlink-relay/pkg/services" + relaymercury "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" @@ -33,6 +34,7 @@ import ( var ( maxTransmitQueueSize = 10_000 + maxDeleteQueueSize = 10_000 transmitTimeout = 5 * time.Second ) @@ -60,6 +62,24 @@ var ( }, []string{"feedID"}, ) + transmitQueueDeleteErrorCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "mercury_transmit_queue_delete_error_count", + Help: "Running count of DB errors when trying to delete an item from the queue DB", + }, + []string{"feedID"}, + ) + transmitQueueInsertErrorCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "mercury_transmit_queue_insert_error_count", + Help: "Running count of DB errors when trying to insert an item into the queue DB", + }, + []string{"feedID"}, + ) + transmitQueuePushErrorCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "mercury_transmit_queue_push_error_count", + Help: "Running count of DB errors when trying to push an item onto the queue", + }, + []string{"feedID"}, + ) transmitServerErrorCount = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "mercury_transmit_server_error_count", Help: "Number of errored transmissions that failed due to an error returned by the mercury server", @@ -84,7 +104,7 @@ type TransmitterReportDecoder interface { var _ Transmitter = (*mercuryTransmitter)(nil) type mercuryTransmitter struct { - utils.StartStopOnce + services.StateMachine lggr logger.Logger rpcClient wsrpc.Client cfgTracker ConfigTracker @@ -95,13 +115,18 @@ type mercuryTransmitter struct { jobID int32 fromAccount string - stopCh utils.StopChan + stopCh services.StopChan queue *TransmitQueue wg sync.WaitGroup - transmitSuccessCount prometheus.Counter - transmitDuplicateCount prometheus.Counter - transmitConnectionErrorCount prometheus.Counter + deleteQueue chan *pb.TransmitRequest + + transmitSuccessCount prometheus.Counter + transmitDuplicateCount prometheus.Counter + transmitConnectionErrorCount prometheus.Counter + transmitQueueDeleteErrorCount prometheus.Counter + transmitQueueInsertErrorCount prometheus.Counter + transmitQueuePushErrorCount prometheus.Counter } var PayloadTypes = getPayloadTypes() @@ -127,7 +152,7 @@ func NewTransmitter(lggr logger.Logger, cfgTracker ConfigTracker, rpcClient wsrp feedIDHex := fmt.Sprintf("0x%x", feedID[:]) persistenceManager := NewPersistenceManager(lggr, NewORM(db, lggr, cfg), jobID, maxTransmitQueueSize, flushDeletesFrequency, pruneFrequency) return &mercuryTransmitter{ - utils.StartStopOnce{}, + services.StateMachine{}, lggr.Named("MercuryTransmitter").With("feedID", feedIDHex), rpcClient, cfgTracker, @@ -136,12 +161,16 @@ func NewTransmitter(lggr logger.Logger, cfgTracker ConfigTracker, rpcClient wsrp feedID, jobID, fmt.Sprintf("%x", fromAccount), - make(chan (struct{})), - NewTransmitQueue(lggr, feedIDHex, maxTransmitQueueSize, nil, persistenceManager), + make(services.StopChan), + nil, sync.WaitGroup{}, + make(chan *pb.TransmitRequest, maxDeleteQueueSize), transmitSuccessCount.WithLabelValues(feedIDHex), transmitDuplicateCount.WithLabelValues(feedIDHex), transmitConnectionErrorCount.WithLabelValues(feedIDHex), + transmitQueueDeleteErrorCount.WithLabelValues(feedIDHex), + transmitQueueInsertErrorCount.WithLabelValues(feedIDHex), + transmitQueuePushErrorCount.WithLabelValues(feedIDHex), } } @@ -164,6 +193,8 @@ func (mt *mercuryTransmitter) Start(ctx context.Context) (err error) { return err } mt.wg.Add(1) + go mt.runDeleteQueueLoop() + mt.wg.Add(1) go mt.runQueueLoop() return nil }) @@ -192,6 +223,46 @@ func (mt *mercuryTransmitter) HealthReport() map[string]error { return report } +func (mt *mercuryTransmitter) runDeleteQueueLoop() { + defer mt.wg.Done() + runloopCtx, cancel := mt.stopCh.Ctx(context.Background()) + defer cancel() + + // Exponential backoff for very rarely occurring errors (DB disconnect etc) + b := backoff.Backoff{ + Min: 1 * time.Second, + Max: 120 * time.Second, + Factor: 2, + Jitter: true, + } + + for { + select { + case req := <-mt.deleteQueue: + for { + if err := mt.persistenceManager.Delete(runloopCtx, req); err != nil { + mt.lggr.Errorw("Failed to delete transmit request record", "error", err, "req", req) + mt.transmitQueueDeleteErrorCount.Inc() + select { + case <-time.After(b.Duration()): + // Wait a backoff duration before trying to delete again + continue + case <-mt.stopCh: + // abort and return immediately on stop even if items remain in queue + return + } + } + break + } + // success + b.Reset() + case <-mt.stopCh: + // abort and return immediately on stop even if items remain in queue + return + } + } +} + func (mt *mercuryTransmitter) runQueueLoop() { defer mt.wg.Done() // Exponential backoff with very short retry interval (since latency is a priority) @@ -253,9 +324,10 @@ func (mt *mercuryTransmitter) runQueueLoop() { } } - if err := mt.persistenceManager.Delete(runloopCtx, t.Req); err != nil { - mt.lggr.Errorw("Failed to delete transmit request record", "error", err, "reportCtx", t.ReportCtx) - return + select { + case mt.deleteQueue <- t.Req: + default: + mt.lggr.Criticalw("Delete queue is full", "reportCtx", t.ReportCtx) } } } @@ -288,9 +360,11 @@ func (mt *mercuryTransmitter) Transmit(ctx context.Context, reportCtx ocrtypes.R mt.lggr.Tracew("Transmit enqueue", "req", req, "report", report, "reportCtx", reportCtx, "signatures", signatures) if err := mt.persistenceManager.Insert(ctx, req, reportCtx); err != nil { + mt.transmitQueueInsertErrorCount.Inc() return err } if ok := mt.queue.Push(req, reportCtx); !ok { + mt.transmitQueuePushErrorCount.Inc() return errors.New("transmit queue is closed") } return nil diff --git a/core/services/relay/evm/mercury/transmitter_test.go b/core/services/relay/evm/mercury/transmitter_test.go index 6723ffcbcac..c8a68d41a16 100644 --- a/core/services/relay/evm/mercury/transmitter_test.go +++ b/core/services/relay/evm/mercury/transmitter_test.go @@ -26,6 +26,7 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { var jobID int32 pgtest.MustExec(t, db, `SET CONSTRAINTS mercury_transmit_requests_job_id_fkey DEFERRED`) pgtest.MustExec(t, db, `SET CONSTRAINTS feed_latest_reports_job_id_fkey DEFERRED`) + q := NewTransmitQueue(lggr, "", 0, nil, nil) t.Run("v1 report transmission successfully enqueued", func(t *testing.T) { report := sampleV1Report @@ -40,6 +41,7 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { }, } mt := NewTransmitter(lggr, nil, c, sampleClientPubKey, jobID, sampleFeedID, db, pgtest.NewQConfig(true), nil) + mt.queue = q err := mt.Transmit(testutils.Context(t), sampleReportContext, report, sampleSigs) require.NoError(t, err) @@ -57,6 +59,7 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { }, } mt := NewTransmitter(lggr, nil, c, sampleClientPubKey, jobID, sampleFeedID, db, pgtest.NewQConfig(true), nil) + mt.queue = q err := mt.Transmit(testutils.Context(t), sampleReportContext, report, sampleSigs) require.NoError(t, err) @@ -74,6 +77,7 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { }, } mt := NewTransmitter(lggr, nil, c, sampleClientPubKey, jobID, sampleFeedID, db, pgtest.NewQConfig(true), nil) + mt.queue = q err := mt.Transmit(testutils.Context(t), sampleReportContext, report, sampleSigs) require.NoError(t, err) diff --git a/core/services/relay/evm/mercury/types/types.go b/core/services/relay/evm/mercury/types/types.go index ca266ca8ccd..49bffb6c290 100644 --- a/core/services/relay/evm/mercury/types/types.go +++ b/core/services/relay/evm/mercury/types/types.go @@ -8,17 +8,9 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) -//go:generate mockery --quiet --name ChainHeadTracker --output ../mocks/ --case=underscore -type ChainHeadTracker interface { - Client() evmclient.Client - HeadTracker() httypes.HeadTracker -} - type DataSourceORM interface { LatestReport(ctx context.Context, feedID [32]byte, qopts ...pg.QOpt) (report []byte, err error) } diff --git a/core/services/relay/evm/mercury/utils/feeds.go b/core/services/relay/evm/mercury/utils/feeds.go index b1a20618ef6..6f8978bbf0d 100644 --- a/core/services/relay/evm/mercury/utils/feeds.go +++ b/core/services/relay/evm/mercury/utils/feeds.go @@ -88,6 +88,10 @@ const ( type FeedID [32]byte +func BytesToFeedID(b []byte) FeedID { + return (FeedID)(utils.BytesToHash(b)) +} + func (f FeedID) Hex() string { return (utils.Hash)(f).Hex() } func (f FeedID) String() string { return (utils.Hash)(f).String() } diff --git a/core/services/relay/evm/mercury/v1/data_source.go b/core/services/relay/evm/mercury/v1/data_source.go index 5c1f55ddab7..bc94166e566 100644 --- a/core/services/relay/evm/mercury/v1/data_source.go +++ b/core/services/relay/evm/mercury/v1/data_source.go @@ -8,23 +8,41 @@ import ( "sync" pkgerrors "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - relaymercury "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury" - relaymercuryv1 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v1" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/types" + relaymercury "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury" + relaymercuryv1 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v1" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/types" + mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v1/reportcodec" "github.com/smartcontractkit/chainlink/v2/core/utils" ) +var ( + insufficientBlocksCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "mercury_insufficient_blocks_count", + Help: fmt.Sprintf("Count of times that there were not enough blocks in the chain during observation (need: %d)", nBlocksObservation), + }, + []string{"feedID"}, + ) + zeroBlocksCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "mercury_zero_blocks_count", + Help: "Count of times that there were zero blocks in the chain during observation", + }, + []string{"feedID"}, + ) +) + +const nBlocksObservation int = relaymercuryv1.MaxAllowedBlocks + type Runner interface { ExecuteRun(ctx context.Context, spec pipeline.Spec, vars pipeline.Vars, l logger.Logger) (run *pipeline.Run, trrs pipeline.TaskRunResults, err error) } @@ -40,7 +58,7 @@ type datasource struct { jb job.Job spec pipeline.Spec lggr logger.Logger - runResults chan<- *pipeline.Run + saver ocrcommon.Saver orm types.DataSourceORM codec reportcodec.ReportCodec feedID [32]byte @@ -48,21 +66,38 @@ type datasource struct { mu sync.RWMutex chEnhancedTelem chan<- ocrcommon.EnhancedTelemetryMercuryData - chainHeadTracker types.ChainHeadTracker + chainReader relaymercury.ChainReader fetcher Fetcher initialBlockNumber *int64 + + insufficientBlocksCounter prometheus.Counter + zeroBlocksCounter prometheus.Counter } var _ relaymercuryv1.DataSource = &datasource{} -func NewDataSource(orm types.DataSourceORM, pr pipeline.Runner, jb job.Job, spec pipeline.Spec, lggr logger.Logger, rr chan *pipeline.Run, enhancedTelemChan chan ocrcommon.EnhancedTelemetryMercuryData, chainHeadTracker types.ChainHeadTracker, fetcher Fetcher, initialBlockNumber *int64, feedID [32]byte) *datasource { - return &datasource{pr, jb, spec, lggr, rr, orm, reportcodec.ReportCodec{}, feedID, sync.RWMutex{}, enhancedTelemChan, chainHeadTracker, fetcher, initialBlockNumber} +func NewDataSource(orm types.DataSourceORM, pr pipeline.Runner, jb job.Job, spec pipeline.Spec, lggr logger.Logger, s ocrcommon.Saver, enhancedTelemChan chan ocrcommon.EnhancedTelemetryMercuryData, chainReader relaymercury.ChainReader, fetcher Fetcher, initialBlockNumber *int64, feedID mercuryutils.FeedID) *datasource { + return &datasource{pr, jb, spec, lggr, s, orm, reportcodec.ReportCodec{}, feedID, sync.RWMutex{}, enhancedTelemChan, chainReader, fetcher, initialBlockNumber, insufficientBlocksCount.WithLabelValues(feedID.String()), zeroBlocksCount.WithLabelValues(feedID.String())} +} + +type ErrEmptyLatestReport struct { + Err error } -func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestamp, fetchMaxFinalizedBlockNum bool) (obs relaymercuryv1.Observation, err error) { - // setCurrentBlock must come first, along with observationTimestamp, to - // avoid front-running - ds.setCurrentBlock(ctx, &obs) +func (e ErrEmptyLatestReport) Unwrap() error { return e.Err } + +func (e ErrEmptyLatestReport) Error() string { + return fmt.Sprintf("FetchInitialMaxFinalizedBlockNumber returned empty LatestReport; this is a new feed. No initialBlockNumber was set, tried to use current block number to determine maxFinalizedBlockNumber but got error: %v", e.Err) +} + +func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestamp, fetchMaxFinalizedBlockNum bool) (obs relaymercuryv1.Observation, pipelineExecutionErr error) { + // setLatestBlocks must come chronologically before observations, along + // with observationTimestamp, to avoid front-running + + // Errors are not expected when reading from the underlying ChainReader + if err := ds.setLatestBlocks(ctx, &obs); err != nil { + return obs, err + } var wg sync.WaitGroup if fetchMaxFinalizedBlockNum { @@ -89,7 +124,7 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam } if ds.initialBlockNumber == nil { if obs.CurrentBlockNum.Err != nil { - obs.MaxFinalizedBlockNumber.Err = fmt.Errorf("FetchInitialMaxFinalizedBlockNumber returned empty LatestReport; this is a new feed. No initialBlockNumber was set, tried to use current block number to determine maxFinalizedBlockNumber but got error: %w", obs.CurrentBlockNum.Err) + obs.MaxFinalizedBlockNumber.Err = ErrEmptyLatestReport{Err: obs.CurrentBlockNum.Err} } else { // Subract 1 here because we will later add 1 to the // maxFinalizedBlockNumber to get the first validFromBlockNum, which @@ -116,16 +151,13 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam go func() { defer wg.Done() var run *pipeline.Run - run, trrs, err = ds.executeRun(ctx) - if err != nil { - err = fmt.Errorf("Observe failed while executing run: %w", err) + run, trrs, pipelineExecutionErr = ds.executeRun(ctx) + if pipelineExecutionErr != nil { + pipelineExecutionErr = fmt.Errorf("Observe failed while executing run: %w", pipelineExecutionErr) return } - select { - case ds.runResults <- run: - default: - ds.lggr.Warnf("unable to enqueue run save for job ID %d, buffer full", ds.spec.JobID) - } + + ds.saver.Save(run) // NOTE: trrs comes back as _all_ tasks, but we only want the terminal ones // They are guaranteed to be sorted by index asc so should be in the correct order @@ -137,27 +169,30 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam } var parsed parseOutput - parsed, err = ds.parse(finaltrrs) - if err != nil { - err = fmt.Errorf("Observe failed while parsing run results: %w", err) + parsed, pipelineExecutionErr = ds.parse(finaltrrs) + if pipelineExecutionErr != nil { + pipelineExecutionErr = fmt.Errorf("Observe failed while parsing run results: %w", pipelineExecutionErr) return } obs.BenchmarkPrice = parsed.benchmarkPrice obs.Bid = parsed.bid obs.Ask = parsed.ask }() - wg.Wait() - if ocrcommon.ShouldCollectEnhancedTelemetryMercury(&ds.jb) { - ocrcommon.EnqueueEnhancedTelem(ds.chEnhancedTelem, ocrcommon.EnhancedTelemetryMercuryData{ - TaskRunResults: trrs, - Observation: obs, - RepTimestamp: repts, - }) + wg.Wait() + if pipelineExecutionErr != nil { + return } - return obs, err + ocrcommon.MaybeEnqueueEnhancedTelem(ds.jb, ds.chEnhancedTelem, ocrcommon.EnhancedTelemetryMercuryData{ + V1Observation: &obs, + TaskRunResults: trrs, + RepTimestamp: repts, + FeedVersion: mercuryutils.REPORT_V1, + }) + + return obs, nil } func toBigInt(val interface{}) (*big.Int, error) { @@ -255,37 +290,36 @@ func (ds *datasource) executeRun(ctx context.Context) (*pipeline.Run, pipeline.T return run, trrs, err } -func (ds *datasource) setCurrentBlock(ctx context.Context, obs *relaymercuryv1.Observation) { - latestHead, err := ds.getCurrentBlock(ctx) +func (ds *datasource) setLatestBlocks(ctx context.Context, obs *relaymercuryv1.Observation) error { + latestBlocks, err := ds.chainReader.LatestHeads(ctx, nBlocksObservation) if err != nil { - obs.CurrentBlockNum.Err = err - obs.CurrentBlockHash.Err = err - obs.CurrentBlockTimestamp.Err = err - return + ds.lggr.Errorw("failed to read latest blocks", "error", err) + return err + } + + if len(latestBlocks) < nBlocksObservation { + ds.insufficientBlocksCounter.Inc() + ds.lggr.Warnw("Insufficient blocks", "latestBlocks", latestBlocks, "lenLatestBlocks", len(latestBlocks), "nBlocksObservation", nBlocksObservation) } - obs.CurrentBlockNum.Val = latestHead.Number - obs.CurrentBlockHash.Val = latestHead.Hash.Bytes() - if latestHead.Timestamp.IsZero() { - obs.CurrentBlockTimestamp.Val = 0 + // TODO: remove with https://smartcontract-it.atlassian.net/browse/BCF-2209 + if len(latestBlocks) == 0 { + obsErr := fmt.Errorf("no blocks available") + ds.zeroBlocksCounter.Inc() + obs.CurrentBlockNum.Err = obsErr + obs.CurrentBlockHash.Err = obsErr + obs.CurrentBlockTimestamp.Err = obsErr } else { - obs.CurrentBlockTimestamp.Val = uint64(latestHead.Timestamp.Unix()) + obs.CurrentBlockNum.Val = int64(latestBlocks[0].Number) + obs.CurrentBlockHash.Val = latestBlocks[0].Hash + obs.CurrentBlockTimestamp.Val = latestBlocks[0].Timestamp } -} -func (ds *datasource) getCurrentBlock(ctx context.Context) (*evmtypes.Head, error) { - // Use the headtracker's view of the latest block, this is very fast since - // it doesn't make any external network requests, and it is the - // headtracker's job to ensure it has an up-to-date view of the chain based - // on responses from all available RPC nodes - latestHead := ds.chainHeadTracker.HeadTracker().LatestChain() - if latestHead == nil { - logger.Sugared(ds.lggr).AssumptionViolation("HeadTracker unexpectedly returned nil head, falling back to RPC call") - var err error - latestHead, err = ds.chainHeadTracker.Client().HeadByNumber(ctx, nil) - if err != nil { - return nil, err - } + for _, block := range latestBlocks { + obs.LatestBlocks = append( + obs.LatestBlocks, + relaymercuryv1.NewBlock(int64(block.Number), block.Hash, block.Timestamp)) } - return latestHead, nil + + return nil } diff --git a/core/services/relay/evm/mercury/v1/data_source_test.go b/core/services/relay/evm/mercury/v1/data_source_test.go index 6e460951301..d8d7d39dbb8 100644 --- a/core/services/relay/evm/mercury/v1/data_source_test.go +++ b/core/services/relay/evm/mercury/v1/data_source_test.go @@ -3,6 +3,7 @@ package mercury_v1 import ( "context" "fmt" + "io" "math/big" "math/rand" "testing" @@ -11,25 +12,23 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - relaymercury "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury" - relaymercuryv1 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v1" + relaymercury "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury" + relaymercuryv1 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v1" commonmocks "github.com/smartcontractkit/chainlink/v2/common/mocks" - "github.com/smartcontractkit/chainlink/v2/core/assets" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" - httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" mercurymocks "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/mocks" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/types" + mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" reportcodecv1 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v1/reportcodec" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -53,15 +52,13 @@ func (m *mockFetcher) LatestTimestamp(context.Context) (int64, error) { return 0, nil } -var _ types.ChainHeadTracker = &mockHeadTracker{} - -type mockHeadTracker struct { - c evmclient.Client - h httypes.HeadTracker +type mockSaver struct { + r *pipeline.Run } -func (m *mockHeadTracker) Client() evmclient.Client { return m.c } -func (m *mockHeadTracker) HeadTracker() httypes.HeadTracker { return m.h } +func (ms *mockSaver) Save(r *pipeline.Run) { + ms.r = r +} type mockORM struct { report []byte @@ -72,15 +69,28 @@ func (m *mockORM) LatestReport(ctx context.Context, feedID [32]byte, qopts ...pg return m.report, m.err } +type mockChainReader struct { + err error + obs []relaymercury.Head +} + +func (m *mockChainReader) LatestHeads(context.Context, int) ([]relaymercury.Head, error) { + return m.obs, m.err +} + func TestMercury_Observe(t *testing.T) { orm := &mockORM{} - ds := &datasource{lggr: logger.TestLogger(t), orm: orm, codec: (reportcodecv1.ReportCodec{})} + lggr := logger.TestLogger(t) + ds := NewDataSource(orm, nil, job.Job{}, pipeline.Spec{}, lggr, nil, nil, nil, nil, nil, mercuryutils.FeedID{}) ctx := testutils.Context(t) repts := ocrtypes.ReportTimestamp{} fetcher := &mockFetcher{} ds.fetcher = fetcher + saver := &mockSaver{} + ds.saver = saver + trrs := []pipeline.TaskRunResult{ { // benchmark price @@ -108,12 +118,7 @@ func TestMercury_Observe(t *testing.T) { ds.spec = spec h := commonmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) - c := evmclimocks.NewClient(t) - ht := &mockHeadTracker{ - c: c, - h: h, - } - ds.chainHeadTracker = ht + ds.chainReader = evm.NewChainReader(h) head := &evmtypes.Head{ Number: int64(rand.Int31()), @@ -202,25 +207,21 @@ func TestMercury_Observe(t *testing.T) { assert.NoError(t, obs.MaxFinalizedBlockNumber.Err) assert.Equal(t, head.Number-1, obs.MaxFinalizedBlockNumber.Val) }) - t.Run("if current block num errored", func(t *testing.T) { + t.Run("if no current block available", func(t *testing.T) { h2 := commonmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) h2.On("LatestChain").Return((*evmtypes.Head)(nil)) - ht.h = h2 - c2 := evmclimocks.NewClient(t) - c2.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(nil, errors.New("head retrieval failed")) - ht.c = c2 + ds.chainReader = evm.NewChainReader(h2) obs, err := ds.Observe(ctx, repts, true) assert.NoError(t, err) - assert.EqualError(t, obs.MaxFinalizedBlockNumber.Err, "FetchInitialMaxFinalizedBlockNumber returned empty LatestReport; this is a new feed. No initialBlockNumber was set, tried to use current block number to determine maxFinalizedBlockNumber but got error: head retrieval failed") + assert.EqualError(t, obs.MaxFinalizedBlockNumber.Err, "FetchInitialMaxFinalizedBlockNumber returned empty LatestReport; this is a new feed. No initialBlockNumber was set, tried to use current block number to determine maxFinalizedBlockNumber but got error: no blocks available") }) }) }) }) - ht.h = h - ht.c = c + ds.chainReader = evm.NewChainReader(h) t.Run("when fetchMaxFinalizedBlockNum=false", func(t *testing.T) { t.Run("when run execution fails, returns error", func(t *testing.T) { @@ -303,71 +304,101 @@ func TestMercury_Observe(t *testing.T) { _, err := ds.Observe(ctx, repts, false) assert.EqualError(t, err, "Observe failed while parsing run results: failed to parse Bid: can't convert foo to decimal") }) - t.Run("sends run to runResults channel", func(t *testing.T) { + t.Run("saves run", func(t *testing.T) { for i := range trrs { trrs[i].Result.Value = "123" trrs[i].Result.Error = nil } - ch := make(chan *pipeline.Run, 1) - - ds.runResults = ch _, err := ds.Observe(ctx, repts, false) assert.NoError(t, err) - select { - case run := <-ch: - assert.Equal(t, int64(42), run.ID) - default: - t.Fatal("expected run on channel") + assert.Equal(t, int64(42), saver.r.ID) + }) + }) + + t.Run("LatestBlocks is populated correctly", func(t *testing.T) { + t.Run("when chain length is zero", func(t *testing.T) { + ht2 := commonmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) + ht2.On("LatestChain").Return((*evmtypes.Head)(nil)) + ds.chainReader = evm.NewChainReader(ht2) + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.Len(t, obs.LatestBlocks, 0) + + ht2.AssertExpectations(t) + }) + t.Run("when chain is too short", func(t *testing.T) { + h4 := &evmtypes.Head{ + Number: 4, + Parent: nil, + } + h5 := &evmtypes.Head{ + Number: 5, + Parent: h4, } + h6 := &evmtypes.Head{ + Number: 6, + Parent: h5, + } + + ht2 := commonmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) + ht2.On("LatestChain").Return(h6) + ds.chainReader = evm.NewChainReader(ht2) + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.Len(t, obs.LatestBlocks, 3) + assert.Equal(t, 6, int(obs.LatestBlocks[0].Num)) + assert.Equal(t, 5, int(obs.LatestBlocks[1].Num)) + assert.Equal(t, 4, int(obs.LatestBlocks[2].Num)) + + ht2.AssertExpectations(t) }) - t.Run("if head tracker returns nil, falls back to RPC method", func(t *testing.T) { - t.Run("if call succeeds", func(t *testing.T) { - h = commonmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) - h.On("LatestChain").Return((*evmtypes.Head)(nil)) - ht.h = h - c.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(head, nil).Once() - - obs, err := ds.Observe(ctx, repts, false) - assert.NoError(t, err) + t.Run("when chain is long enough", func(t *testing.T) { + heads := make([]*evmtypes.Head, nBlocksObservation+5) + for i := range heads { + heads[i] = &evmtypes.Head{Number: int64(i)} + if i > 0 { + heads[i].Parent = heads[i-1] + } + } - assert.Equal(t, head.Number, obs.CurrentBlockNum.Val) - assert.NoError(t, obs.CurrentBlockNum.Err) - assert.Equal(t, fmt.Sprintf("%x", head.Hash), fmt.Sprintf("%x", obs.CurrentBlockHash.Val)) - assert.NoError(t, obs.CurrentBlockHash.Err) - assert.Equal(t, uint64(head.Timestamp.Unix()), obs.CurrentBlockTimestamp.Val) - assert.NoError(t, obs.CurrentBlockTimestamp.Err) + ht2 := commonmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) + ht2.On("LatestChain").Return(heads[len(heads)-1]) + ds.chainReader = evm.NewChainReader(ht2) - h.AssertExpectations(t) - c.AssertExpectations(t) - }) - t.Run("if call fails, returns error for that observation", func(t *testing.T) { - c = evmclimocks.NewClient(t) - ht.c = c - c.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(nil, errors.New("client call failed")).Once() + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) - obs, err := ds.Observe(ctx, repts, false) - assert.NoError(t, err) + assert.Len(t, obs.LatestBlocks, nBlocksObservation) + highestBlockNum := heads[len(heads)-1].Number + for i := range obs.LatestBlocks { + assert.Equal(t, int(highestBlockNum)-i, int(obs.LatestBlocks[i].Num)) + } - assert.Zero(t, obs.CurrentBlockNum.Val) - assert.EqualError(t, obs.CurrentBlockNum.Err, "client call failed") - assert.Zero(t, obs.CurrentBlockHash.Val) - assert.EqualError(t, obs.CurrentBlockHash.Err, "client call failed") - assert.Zero(t, obs.CurrentBlockTimestamp.Val) - assert.EqualError(t, obs.CurrentBlockTimestamp.Err, "client call failed") + ht2.AssertExpectations(t) + }) - c.AssertExpectations(t) - }) + t.Run("when chain reader returns an error", func(t *testing.T) { + ds.chainReader = &mockChainReader{ + err: io.EOF, + obs: nil, + } + + obs, err := ds.Observe(ctx, repts, true) + assert.Error(t, err) + assert.Equal(t, obs, relaymercuryv1.Observation{}) }) }) } -func TestMercury_SetCurrentBlock(t *testing.T) { +func TestMercury_SetLatestBlocks(t *testing.T) { lggr := logger.TestLogger(t) - ds := datasource{ - lggr: lggr, - } + ds := NewDataSource(nil, nil, job.Job{}, pipeline.Spec{}, lggr, nil, nil, nil, nil, nil, mercuryutils.FeedID{}) h := evmtypes.Head{ Number: testutils.NewRandomPositiveInt64(), @@ -382,72 +413,39 @@ func TestMercury_SetCurrentBlock(t *testing.T) { t.Run("returns head from headtracker if present", func(t *testing.T) { headTracker := commonmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) - chainHeadTracker := mercurymocks.NewChainHeadTracker(t) - - chainHeadTracker.On("HeadTracker").Return(headTracker) headTracker.On("LatestChain").Return(&h, nil) - - ds.chainHeadTracker = chainHeadTracker - - obs := relaymercuryv1.Observation{} - ds.setCurrentBlock(context.Background(), &obs) - - assert.Equal(t, h.Number, obs.CurrentBlockNum.Val) - assert.Equal(t, h.Hash.Bytes(), obs.CurrentBlockHash.Val) - assert.Equal(t, uint64(h.Timestamp.Unix()), obs.CurrentBlockTimestamp.Val) - - chainHeadTracker.AssertExpectations(t) - headTracker.AssertExpectations(t) - }) - - t.Run("if headtracker returns nil head and eth call succeeds", func(t *testing.T) { - ethClient := evmclimocks.NewClient(t) - headTracker := commonmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) - chainHeadTracker := mercurymocks.NewChainHeadTracker(t) - - chainHeadTracker.On("Client").Return(ethClient) - chainHeadTracker.On("HeadTracker").Return(headTracker) - // This can happen in some cases e.g. RPC node is offline - headTracker.On("LatestChain").Return((*evmtypes.Head)(nil)) - ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(&h, nil) - - ds.chainHeadTracker = chainHeadTracker + ds.chainReader = evm.NewChainReader(headTracker) obs := relaymercuryv1.Observation{} - ds.setCurrentBlock(context.Background(), &obs) + err := ds.setLatestBlocks(testutils.Context(t), &obs) + assert.NoError(t, err) assert.Equal(t, h.Number, obs.CurrentBlockNum.Val) assert.Equal(t, h.Hash.Bytes(), obs.CurrentBlockHash.Val) assert.Equal(t, uint64(h.Timestamp.Unix()), obs.CurrentBlockTimestamp.Val) - chainHeadTracker.AssertExpectations(t) - ethClient.AssertExpectations(t) + assert.Len(t, obs.LatestBlocks, 1) headTracker.AssertExpectations(t) }) - t.Run("if headtracker returns nil head and eth call fails", func(t *testing.T) { - ethClient := evmclimocks.NewClient(t) + t.Run("if headtracker returns nil head", func(t *testing.T) { headTracker := commonmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) - chainHeadTracker := mercurymocks.NewChainHeadTracker(t) - - chainHeadTracker.On("Client").Return(ethClient) - chainHeadTracker.On("HeadTracker").Return(headTracker) // This can happen in some cases e.g. RPC node is offline headTracker.On("LatestChain").Return((*evmtypes.Head)(nil)) - err := errors.New("foo") - ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(nil, err) - - ds.chainHeadTracker = chainHeadTracker + ds.chainReader = evm.NewChainReader(headTracker) obs := relaymercuryv1.Observation{} - ds.setCurrentBlock(context.Background(), &obs) + err := ds.setLatestBlocks(testutils.Context(t), &obs) - assert.Equal(t, err, obs.CurrentBlockNum.Err) - assert.Equal(t, err, obs.CurrentBlockHash.Err) - assert.Equal(t, err, obs.CurrentBlockTimestamp.Err) + assert.NoError(t, err) + assert.Zero(t, obs.CurrentBlockNum.Val) + assert.Zero(t, obs.CurrentBlockHash.Val) + assert.Zero(t, obs.CurrentBlockTimestamp.Val) + assert.EqualError(t, obs.CurrentBlockNum.Err, "no blocks available") + assert.EqualError(t, obs.CurrentBlockHash.Err, "no blocks available") + assert.EqualError(t, obs.CurrentBlockTimestamp.Err, "no blocks available") - chainHeadTracker.AssertExpectations(t) - ethClient.AssertExpectations(t) + assert.Len(t, obs.LatestBlocks, 0) headTracker.AssertExpectations(t) }) } diff --git a/core/services/relay/evm/mercury/v1/reportcodec/report_codec.go b/core/services/relay/evm/mercury/v1/reportcodec/report_codec.go index fefddd6395b..28688e3b17a 100644 --- a/core/services/relay/evm/mercury/v1/reportcodec/report_codec.go +++ b/core/services/relay/evm/mercury/v1/reportcodec/report_codec.go @@ -8,9 +8,10 @@ import ( "github.com/ethereum/go-ethereum/common" pkgerrors "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - reportcodec "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v1" + reportcodec "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v1" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" diff --git a/core/services/relay/evm/mercury/v1/reportcodec/report_codec_test.go b/core/services/relay/evm/mercury/v1/reportcodec/report_codec_test.go index 3f4838c3e7c..f630b4522b4 100644 --- a/core/services/relay/evm/mercury/v1/reportcodec/report_codec_test.go +++ b/core/services/relay/evm/mercury/v1/reportcodec/report_codec_test.go @@ -11,7 +11,7 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - relaymercuryv1 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v1" + relaymercuryv1 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v1" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/services/relay/evm/mercury/v2/data_source.go b/core/services/relay/evm/mercury/v2/data_source.go index 10c8839a3c5..ec6cf5ad106 100644 --- a/core/services/relay/evm/mercury/v2/data_source.go +++ b/core/services/relay/evm/mercury/v2/data_source.go @@ -10,8 +10,8 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - relaymercury "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury" - relaymercuryv2 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v2" + relaymercury "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury" + relaymercuryv2 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v2" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -39,7 +39,7 @@ type datasource struct { spec pipeline.Spec feedID mercuryutils.FeedID lggr logger.Logger - runResults chan<- *pipeline.Run + saver ocrcommon.Saver orm types.DataSourceORM codec reportcodec.ReportCodec @@ -54,11 +54,11 @@ type datasource struct { var _ relaymercuryv2.DataSource = &datasource{} -func NewDataSource(orm types.DataSourceORM, pr pipeline.Runner, jb job.Job, spec pipeline.Spec, feedID mercuryutils.FeedID, lggr logger.Logger, rr chan *pipeline.Run, enhancedTelemChan chan ocrcommon.EnhancedTelemetryMercuryData, fetcher LatestReportFetcher, linkFeedID, nativeFeedID mercuryutils.FeedID) *datasource { - return &datasource{pr, jb, spec, feedID, lggr, rr, orm, reportcodec.ReportCodec{}, fetcher, linkFeedID, nativeFeedID, sync.RWMutex{}, enhancedTelemChan} +func NewDataSource(orm types.DataSourceORM, pr pipeline.Runner, jb job.Job, spec pipeline.Spec, feedID mercuryutils.FeedID, lggr logger.Logger, s ocrcommon.Saver, enhancedTelemChan chan ocrcommon.EnhancedTelemetryMercuryData, fetcher LatestReportFetcher, linkFeedID, nativeFeedID mercuryutils.FeedID) *datasource { + return &datasource{pr, jb, spec, feedID, lggr, s, orm, reportcodec.ReportCodec{}, fetcher, linkFeedID, nativeFeedID, sync.RWMutex{}, enhancedTelemChan} } -func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestamp, fetchMaxFinalizedTimestamp bool) (obs relaymercuryv2.Observation, err error) { +func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestamp, fetchMaxFinalizedTimestamp bool) (obs relaymercuryv2.Observation, pipelineExecutionErr error) { var wg sync.WaitGroup ctx, cancel := context.WithCancel(ctx) @@ -80,30 +80,27 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam }() } + var trrs pipeline.TaskRunResults wg.Add(1) go func() { defer wg.Done() - var trrs pipeline.TaskRunResults var run *pipeline.Run - run, trrs, err = ds.executeRun(ctx) - if err != nil { + run, trrs, pipelineExecutionErr = ds.executeRun(ctx) + if pipelineExecutionErr != nil { cancel() - err = fmt.Errorf("Observe failed while executing run: %w", err) + pipelineExecutionErr = fmt.Errorf("Observe failed while executing run: %w", pipelineExecutionErr) return } - select { - case ds.runResults <- run: - default: - ds.lggr.Warnf("unable to enqueue run save for job ID %d, buffer full", ds.spec.JobID) - } + + ds.saver.Save(run) var parsed parseOutput - parsed, err = ds.parse(trrs) - if err != nil { + parsed, pipelineExecutionErr = ds.parse(trrs) + if pipelineExecutionErr != nil { cancel() // This is not expected under normal circumstances - ds.lggr.Errorw("Observe failed while parsing run results", "err", err) - err = fmt.Errorf("Observe failed while parsing run results: %w", err) + ds.lggr.Errorw("Observe failed while parsing run results", "err", pipelineExecutionErr) + pipelineExecutionErr = fmt.Errorf("Observe failed while parsing run results: %w", pipelineExecutionErr) return } obs.BenchmarkPrice = parsed.benchmarkPrice @@ -149,11 +146,12 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam wg.Wait() cancel() + if pipelineExecutionErr != nil { + return + } + if isLink || isNative { - // run has now completed so it is safe to use err or benchmark price - if err != nil { - return - } + // run has now completed so it is safe to use benchmark price if isLink { // This IS the LINK feed, use our observed price obs.LinkPrice.Val, obs.LinkPrice.Err = obs.BenchmarkPrice.Val, obs.BenchmarkPrice.Err @@ -164,16 +162,17 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam } } - // todo: implement telemetry - https://smartcontract-it.atlassian.net/browse/MERC-1388 - // if ocrcommon.ShouldCollectEnhancedTelemetryMercury(&ds.jb) { - // ocrcommon.EnqueueEnhancedTelem(ds.chEnhancedTelem, ocrcommon.EnhancedTelemetryMercuryData{ - // TaskRunResults: trrs, - // Observation: obs, - // RepTimestamp: repts, - // }) - // } + ocrcommon.MaybeEnqueueEnhancedTelem(ds.jb, ds.chEnhancedTelem, ocrcommon.EnhancedTelemetryMercuryData{ + V2Observation: &obs, + TaskRunResults: trrs, + RepTimestamp: repts, + FeedVersion: mercuryutils.REPORT_V2, + FetchMaxFinalizedTimestamp: fetchMaxFinalizedTimestamp, + IsLinkFeed: isLink, + IsNativeFeed: isNative, + }) - return obs, err + return obs, nil } func toBigInt(val interface{}) (*big.Int, error) { diff --git a/core/services/relay/evm/mercury/v2/data_source_test.go b/core/services/relay/evm/mercury/v2/data_source_test.go index b66355ac7fb..d1030327e10 100644 --- a/core/services/relay/evm/mercury/v2/data_source_test.go +++ b/core/services/relay/evm/mercury/v2/data_source_test.go @@ -8,8 +8,8 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/assert" - relaymercury "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury" - relaymercuryv2 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v2" + relaymercury "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury" + relaymercuryv2 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v2" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" @@ -63,6 +63,14 @@ func (m *mockORM) LatestReport(ctx context.Context, feedID [32]byte, qopts ...pg return m.report, m.err } +type mockSaver struct { + r *pipeline.Run +} + +func (ms *mockSaver) Save(r *pipeline.Run) { + ms.r = r +} + func Test_Datasource(t *testing.T) { orm := &mockORM{} ds := &datasource{orm: orm, lggr: logger.TestLogger(t)} @@ -72,6 +80,9 @@ func Test_Datasource(t *testing.T) { fetcher := &mockFetcher{} ds.fetcher = fetcher + saver := &mockSaver{} + ds.saver = saver + goodTrrs := []pipeline.TaskRunResult{ { // bp diff --git a/core/services/relay/evm/mercury/v2/reportcodec/report_codec.go b/core/services/relay/evm/mercury/v2/reportcodec/report_codec.go index 0e1dfe9c46f..6b13b3b6ef8 100644 --- a/core/services/relay/evm/mercury/v2/reportcodec/report_codec.go +++ b/core/services/relay/evm/mercury/v2/reportcodec/report_codec.go @@ -6,9 +6,10 @@ import ( "math/big" pkgerrors "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - reportcodec "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v2" + reportcodec "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v2" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" diff --git a/core/services/relay/evm/mercury/v2/reportcodec/report_codec_test.go b/core/services/relay/evm/mercury/v2/reportcodec/report_codec_test.go index 8cf16a5dab4..3a58337b4ce 100644 --- a/core/services/relay/evm/mercury/v2/reportcodec/report_codec_test.go +++ b/core/services/relay/evm/mercury/v2/reportcodec/report_codec_test.go @@ -9,7 +9,7 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - relaymercuryv2 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v2" + relaymercuryv2 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v2" ) func newValidReportFields() relaymercuryv2.ReportFields { diff --git a/core/services/relay/evm/mercury/v3/data_source.go b/core/services/relay/evm/mercury/v3/data_source.go index 4eadfc35c23..d5e1e5716dd 100644 --- a/core/services/relay/evm/mercury/v3/data_source.go +++ b/core/services/relay/evm/mercury/v3/data_source.go @@ -11,8 +11,8 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - relaymercury "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury" - relaymercuryv3 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v3" + relaymercury "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury" + relaymercuryv3 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v3" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -40,7 +40,7 @@ type datasource struct { spec pipeline.Spec feedID mercuryutils.FeedID lggr logger.Logger - runResults chan<- *pipeline.Run + saver ocrcommon.Saver orm types.DataSourceORM codec reportcodec.ReportCodec @@ -55,11 +55,11 @@ type datasource struct { var _ relaymercuryv3.DataSource = &datasource{} -func NewDataSource(orm types.DataSourceORM, pr pipeline.Runner, jb job.Job, spec pipeline.Spec, feedID mercuryutils.FeedID, lggr logger.Logger, rr chan *pipeline.Run, enhancedTelemChan chan ocrcommon.EnhancedTelemetryMercuryData, fetcher LatestReportFetcher, linkFeedID, nativeFeedID mercuryutils.FeedID) *datasource { - return &datasource{pr, jb, spec, feedID, lggr, rr, orm, reportcodec.ReportCodec{}, fetcher, linkFeedID, nativeFeedID, sync.RWMutex{}, enhancedTelemChan} +func NewDataSource(orm types.DataSourceORM, pr pipeline.Runner, jb job.Job, spec pipeline.Spec, feedID mercuryutils.FeedID, lggr logger.Logger, s ocrcommon.Saver, enhancedTelemChan chan ocrcommon.EnhancedTelemetryMercuryData, fetcher LatestReportFetcher, linkFeedID, nativeFeedID mercuryutils.FeedID) *datasource { + return &datasource{pr, jb, spec, feedID, lggr, s, orm, reportcodec.ReportCodec{}, fetcher, linkFeedID, nativeFeedID, sync.RWMutex{}, enhancedTelemChan} } -func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestamp, fetchMaxFinalizedTimestamp bool) (obs relaymercuryv3.Observation, err error) { +func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestamp, fetchMaxFinalizedTimestamp bool) (obs relaymercuryv3.Observation, pipelineExecutionErr error) { var wg sync.WaitGroup ctx, cancel := context.WithCancel(ctx) @@ -81,30 +81,27 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam }() } + var trrs pipeline.TaskRunResults wg.Add(1) go func() { defer wg.Done() - var trrs pipeline.TaskRunResults var run *pipeline.Run - run, trrs, err = ds.executeRun(ctx) - if err != nil { + run, trrs, pipelineExecutionErr = ds.executeRun(ctx) + if pipelineExecutionErr != nil { cancel() - err = fmt.Errorf("Observe failed while executing run: %w", err) + pipelineExecutionErr = fmt.Errorf("Observe failed while executing run: %w", pipelineExecutionErr) return } - select { - case ds.runResults <- run: - default: - ds.lggr.Warnf("unable to enqueue run save for job ID %d, buffer full", ds.spec.JobID) - } + + ds.saver.Save(run) var parsed parseOutput - parsed, err = ds.parse(trrs) - if err != nil { + parsed, pipelineExecutionErr = ds.parse(trrs) + if pipelineExecutionErr != nil { cancel() // This is not expected under normal circumstances - ds.lggr.Errorw("Observe failed while parsing run results", "err", err) - err = fmt.Errorf("Observe failed while parsing run results: %w", err) + ds.lggr.Errorw("Observe failed while parsing run results", "err", pipelineExecutionErr) + pipelineExecutionErr = fmt.Errorf("Observe failed while parsing run results: %w", pipelineExecutionErr) return } obs.BenchmarkPrice = parsed.benchmarkPrice @@ -152,11 +149,12 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam wg.Wait() cancel() + if pipelineExecutionErr != nil { + return + } + if isLink || isNative { - // run has now completed so it is safe to use err or benchmark price - if err != nil { - return - } + // run has now completed so it is safe to use benchmark price if isLink { // This IS the LINK feed, use our observed price obs.LinkPrice.Val, obs.LinkPrice.Err = obs.BenchmarkPrice.Val, obs.BenchmarkPrice.Err @@ -167,17 +165,17 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam } } - // todo: implement telemetry https://smartcontract-it.atlassian.net/browse/MERC-1388 - // if ocrcommon.ShouldCollectEnhancedTelemetryMercury(&ds.jb) { - // ocrcommon.EnqueueEnhancedTelem(ds.chEnhancedTelem, ocrcommon.EnhancedTelemetryMercuryData{ - // TaskRunResults: trrs, - // Observation: obs, - // RepTimestamp: repts, - // }) - // } + ocrcommon.MaybeEnqueueEnhancedTelem(ds.jb, ds.chEnhancedTelem, ocrcommon.EnhancedTelemetryMercuryData{ + V3Observation: &obs, + TaskRunResults: trrs, + RepTimestamp: repts, + FeedVersion: mercuryutils.REPORT_V3, + FetchMaxFinalizedTimestamp: fetchMaxFinalizedTimestamp, + IsLinkFeed: isLink, + IsNativeFeed: isNative, + }) - cancel() - return obs, err + return obs, nil } func toBigInt(val interface{}) (*big.Int, error) { diff --git a/core/services/relay/evm/mercury/v3/data_source_test.go b/core/services/relay/evm/mercury/v3/data_source_test.go index aefc766996e..e03e321d093 100644 --- a/core/services/relay/evm/mercury/v3/data_source_test.go +++ b/core/services/relay/evm/mercury/v3/data_source_test.go @@ -8,8 +8,8 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/assert" - relaymercury "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury" - relaymercuryv3 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v3" + relaymercury "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury" + relaymercuryv3 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v3" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" @@ -63,6 +63,14 @@ func (m *mockORM) LatestReport(ctx context.Context, feedID [32]byte, qopts ...pg return m.report, m.err } +type mockSaver struct { + r *pipeline.Run +} + +func (ms *mockSaver) Save(r *pipeline.Run) { + ms.r = r +} + func Test_Datasource(t *testing.T) { orm := &mockORM{} ds := &datasource{orm: orm, lggr: logger.TestLogger(t)} @@ -72,6 +80,9 @@ func Test_Datasource(t *testing.T) { fetcher := &mockFetcher{} ds.fetcher = fetcher + saver := &mockSaver{} + ds.saver = saver + goodTrrs := []pipeline.TaskRunResult{ { // bp diff --git a/core/services/relay/evm/mercury/v3/reportcodec/report_codec.go b/core/services/relay/evm/mercury/v3/reportcodec/report_codec.go index 4c0b3756d7b..6b379dc1948 100644 --- a/core/services/relay/evm/mercury/v3/reportcodec/report_codec.go +++ b/core/services/relay/evm/mercury/v3/reportcodec/report_codec.go @@ -7,9 +7,10 @@ import ( "math/big" pkgerrors "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - reportcodec "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v3" + reportcodec "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v3" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" diff --git a/core/services/relay/evm/mercury/v3/reportcodec/report_codec_test.go b/core/services/relay/evm/mercury/v3/reportcodec/report_codec_test.go index 98b81edb002..88416f7ea61 100644 --- a/core/services/relay/evm/mercury/v3/reportcodec/report_codec_test.go +++ b/core/services/relay/evm/mercury/v3/reportcodec/report_codec_test.go @@ -9,7 +9,7 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - relaymercuryv3 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v3" + relaymercuryv3 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v3" ) func newValidReportFields() relaymercuryv3.ReportFields { diff --git a/core/services/relay/evm/mercury/wsrpc/cache/cache.go b/core/services/relay/evm/mercury/wsrpc/cache/cache.go new file mode 100644 index 00000000000..cab5654081b --- /dev/null +++ b/core/services/relay/evm/mercury/wsrpc/cache/cache.go @@ -0,0 +1,394 @@ +package cache + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "github.com/jpillora/backoff" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/logger" + mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +var ( + promFetchFailedCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "mercury_cache_fetch_failure_count", + Help: "Number of times we tried to call LatestReport from the mercury server, but some kind of error occurred", + }, + []string{"serverURL", "feedID"}, + ) + promCacheHitCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "mercury_cache_hit_count", + Help: "Running count of cache hits", + }, + []string{"serverURL", "feedID"}, + ) + promCacheWaitCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "mercury_cache_wait_count", + Help: "Running count of times that we had to wait for a fetch to complete before reading from cache", + }, + []string{"serverURL", "feedID"}, + ) + promCacheMissCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "mercury_cache_miss_count", + Help: "Running count of cache misses", + }, + []string{"serverURL", "feedID"}, + ) +) + +type Fetcher interface { + LatestReport(ctx context.Context, req *pb.LatestReportRequest) (resp *pb.LatestReportResponse, err error) +} + +type Client interface { + Fetcher + ServerURL() string + RawClient() pb.MercuryClient +} + +// Cache is scoped to one particular mercury server +// Use CacheSet to hold lookups for multiple servers +type Cache interface { + Fetcher + services.Service +} + +type Config struct { + // LatestReportTTL controls how "stale" we will allow a price to be e.g. if + // set to 1s, a new price will always be fetched if the last result was + // from more than 1 second ago. + // + // Another way of looking at it is such: the cache will _never_ return a + // price that was queried from before now-LatestReportTTL. + // + // Setting to zero disables caching entirely. + LatestReportTTL time.Duration + // MaxStaleAge is that maximum amount of time that a value can be stale + // before it is deleted from the cache (a form of garbage collection). + // + // This should generally be set to something much larger than + // LatestReportTTL. Setting to zero disables garbage collection. + MaxStaleAge time.Duration + // LatestReportDeadline controls how long to wait for a response before + // retrying. Setting this to zero will wait indefinitely. + LatestReportDeadline time.Duration +} + +func NewCache(lggr logger.Logger, client Client, cfg Config) Cache { + return newMemCache(lggr, client, cfg) +} + +type cacheVal struct { + sync.RWMutex + + fetching bool + fetchCh chan (struct{}) + + val *pb.LatestReportResponse + err error + + expiresAt time.Time +} + +func (v *cacheVal) read() (*pb.LatestReportResponse, error) { + v.RLock() + defer v.RUnlock() + return v.val, v.err +} + +// caller expected to hold lock +func (v *cacheVal) initiateFetch() <-chan struct{} { + if v.fetching { + panic("cannot initiateFetch on cache val that is already fetching") + } + v.fetching = true + v.fetchCh = make(chan struct{}) + return v.fetchCh +} + +func (v *cacheVal) setError(err error) { + v.Lock() + defer v.Unlock() + v.err = err +} + +func (v *cacheVal) completeFetch(val *pb.LatestReportResponse, err error, expiresAt time.Time) { + v.Lock() + defer v.Unlock() + if !v.fetching { + panic("can only completeFetch on cache val that is fetching") + } + v.val = val + v.err = err + if err == nil { + v.expiresAt = expiresAt + } + close(v.fetchCh) + v.fetchCh = nil + v.fetching = false +} + +func (v *cacheVal) abandonFetch(err error) { + v.completeFetch(nil, err, time.Now()) +} + +func (v *cacheVal) waitForResult(ctx context.Context, chResult <-chan struct{}, chStop <-chan struct{}) (*pb.LatestReportResponse, error) { + select { + case <-ctx.Done(): + _, err := v.read() + return nil, errors.Join(err, ctx.Err()) + case <-chStop: + return nil, errors.New("stopped") + case <-chResult: + return v.read() + } +} + +// memCache stores values in memory +// it will never return a stale value older than latestPriceTTL, instead +// waiting for a successful fetch or caller context cancels, whichever comes +// first +type memCache struct { + services.StateMachine + lggr logger.Logger + + client Client + + cfg Config + + cache sync.Map + + wg sync.WaitGroup + chStop services.StopChan +} + +func newMemCache(lggr logger.Logger, client Client, cfg Config) *memCache { + return &memCache{ + services.StateMachine{}, + lggr.Named("MemCache"), + client, + cfg, + sync.Map{}, + sync.WaitGroup{}, + make(chan (struct{})), + } +} + +// LatestReport +// NOTE: This will actually block on all types of errors, even non-timeouts. +// Context should be set carefully and timed to be the maximum time we are +// willing to wait for a result, the background thread will keep re-querying +// until it gets one even on networking errors etc. +func (m *memCache) LatestReport(ctx context.Context, req *pb.LatestReportRequest) (resp *pb.LatestReportResponse, err error) { + if req == nil { + return nil, errors.New("req must not be nil") + } + feedIDHex := mercuryutils.BytesToFeedID(req.FeedId).String() + if m.cfg.LatestReportTTL <= 0 { + return m.client.RawClient().LatestReport(ctx, req) + } + vi, loaded := m.cache.LoadOrStore(feedIDHex, &cacheVal{ + sync.RWMutex{}, + false, + nil, + nil, + nil, + time.Now(), // first result is always "expired" and requires fetch + }) + v := vi.(*cacheVal) + + m.lggr.Tracew("LatestReport", "feedID", feedIDHex, "loaded", loaded) + + // HOT PATH + v.RLock() + if time.Now().Before(v.expiresAt) { + // CACHE HIT + promCacheHitCount.WithLabelValues(m.client.ServerURL(), feedIDHex).Inc() + + defer v.RUnlock() + return v.val, nil + } else if v.fetching { + // CACHE WAIT + promCacheWaitCount.WithLabelValues(m.client.ServerURL(), feedIDHex).Inc() + // if someone else is fetching then wait for the fetch to complete + ch := v.fetchCh + v.RUnlock() + return v.waitForResult(ctx, ch, m.chStop) + } + // CACHE MISS + promCacheMissCount.WithLabelValues(m.client.ServerURL(), feedIDHex).Inc() + // fallthrough to cold path and fetch + v.RUnlock() + + // COLD PATH + v.Lock() + if time.Now().Before(v.expiresAt) { + // CACHE HIT + promCacheHitCount.WithLabelValues(m.client.ServerURL(), feedIDHex).Inc() + defer v.RUnlock() + return v.val, nil + } else if v.fetching { + // CACHE WAIT + promCacheWaitCount.WithLabelValues(m.client.ServerURL(), feedIDHex).Inc() + // if someone else is fetching then wait for the fetch to complete + ch := v.fetchCh + v.Unlock() + return v.waitForResult(ctx, ch, m.chStop) + } + // CACHE MISS + promCacheMissCount.WithLabelValues(m.client.ServerURL(), feedIDHex).Inc() + // initiate the fetch and wait for result + ch := v.initiateFetch() + v.Unlock() + + ok := m.IfStarted(func() { + m.wg.Add(1) + go m.fetch(req, v) + }) + if !ok { + err := fmt.Errorf("memCache must be started, but is: %v", m.State()) + v.abandonFetch(err) + return nil, err + } + return v.waitForResult(ctx, ch, m.chStop) +} + +const minBackoffRetryInterval = 50 * time.Millisecond + +// newBackoff creates a backoff for retrying +func (m *memCache) newBackoff() backoff.Backoff { + min := minBackoffRetryInterval + max := m.cfg.LatestReportTTL / 2 + if min > max { + // avoid setting a min that is greater than max + min = max + } + return backoff.Backoff{ + Min: min, + Max: max, + Factor: 2, + Jitter: true, + } +} + +// fetch continually tries to call FetchLatestReport and write the result to v +// it writes errors as they come up +func (m *memCache) fetch(req *pb.LatestReportRequest, v *cacheVal) { + defer m.wg.Done() + b := m.newBackoff() + memcacheCtx, cancel := m.chStop.NewCtx() + defer cancel() + var t time.Time + var val *pb.LatestReportResponse + var err error + defer func() { + v.completeFetch(val, err, t.Add(m.cfg.LatestReportTTL)) + }() + + for { + t = time.Now() + + ctx := memcacheCtx + cancel := func() {} + if m.cfg.LatestReportDeadline > 0 { + ctx, cancel = context.WithTimeoutCause(memcacheCtx, m.cfg.LatestReportDeadline, errors.New("latest report fetch deadline exceeded")) + } + + // NOTE: must drop down to RawClient here otherwise we enter an + // infinite loop of calling a client that calls back to this same cache + // and on and on + val, err = m.client.RawClient().LatestReport(ctx, req) + cancel() + v.setError(err) + if memcacheCtx.Err() != nil { + // stopped + return + } else if err != nil { + m.lggr.Warnw("FetchLatestReport failed", "err", err) + promFetchFailedCount.WithLabelValues(m.client.ServerURL(), mercuryutils.BytesToFeedID(req.FeedId).String()).Inc() + select { + case <-m.chStop: + return + case <-time.After(b.Duration()): + continue + } + } + return + } +} + +func (m *memCache) Start(context.Context) error { + return m.StartOnce(m.Name(), func() error { + m.lggr.Debugw("MemCache starting", "config", m.cfg) + m.wg.Add(1) + go m.runloop() + return nil + }) +} + +func (m *memCache) runloop() { + defer m.wg.Done() + + if m.cfg.MaxStaleAge == 0 { + return + } + t := time.NewTicker(utils.WithJitter(m.cfg.MaxStaleAge)) + + for { + select { + case <-t.C: + m.cleanup() + t.Reset(utils.WithJitter(m.cfg.MaxStaleAge)) + case <-m.chStop: + return + } + } +} + +// remove anything that has been stale for longer than maxStaleAge so that +// cache doesn't grow forever and cause memory leaks +// +// NOTE: This should be concurrent-safe with LatestReport. The only time they +// can race is if the cache item has expired past the stale age between +// creation of the cache item and start of fetch. This is unlikely, and even if +// it does occur, the worst case is that we discard a cache item early and +// double fetch, which isn't bad at all. +func (m *memCache) cleanup() { + m.cache.Range(func(k, vi any) bool { + v := vi.(*cacheVal) + v.RLock() + defer v.RUnlock() + if v.fetching { + // skip cleanup if fetching + return true + } + if time.Now().After(v.expiresAt.Add(m.cfg.MaxStaleAge)) { + // garbage collection + m.cache.Delete(k) + } + return true + }) +} + +func (m *memCache) Close() error { + return m.StopOnce(m.Name(), func() error { + close(m.chStop) + m.wg.Wait() + return nil + }) +} +func (m *memCache) HealthReport() map[string]error { + return map[string]error{ + m.Name(): m.Ready(), + } +} +func (m *memCache) Name() string { return m.lggr.Name() } diff --git a/core/services/relay/evm/mercury/wsrpc/cache/cache_set.go b/core/services/relay/evm/mercury/wsrpc/cache/cache_set.go new file mode 100644 index 00000000000..3171c8ab643 --- /dev/null +++ b/core/services/relay/evm/mercury/wsrpc/cache/cache_set.go @@ -0,0 +1,113 @@ +package cache + +import ( + "context" + "fmt" + "sync" + + "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +// CacheSet holds a set of mercury caches keyed by server URL +type CacheSet interface { + services.Service + Get(ctx context.Context, client Client) (Fetcher, error) +} + +var _ CacheSet = (*cacheSet)(nil) + +type cacheSet struct { + sync.RWMutex + services.StateMachine + + lggr logger.Logger + caches map[string]Cache + + cfg Config +} + +func NewCacheSet(lggr logger.Logger, cfg Config) CacheSet { + return newCacheSet(lggr, cfg) +} + +func newCacheSet(lggr logger.Logger, cfg Config) *cacheSet { + return &cacheSet{ + sync.RWMutex{}, + services.StateMachine{}, + lggr.Named("CacheSet"), + make(map[string]Cache), + cfg, + } +} + +func (cs *cacheSet) Start(context.Context) error { + return cs.StartOnce("CacheSet", func() error { + cs.lggr.Debugw("CacheSet starting", "config", cs.cfg) + return nil + }) +} + +func (cs *cacheSet) Close() error { + return cs.StopOnce("CacheSet", func() error { + cs.Lock() + defer cs.Unlock() + caches := maps.Values(cs.caches) + if err := services.MultiCloser(caches).Close(); err != nil { + return err + } + cs.caches = nil + return nil + }) +} + +func (cs *cacheSet) Get(ctx context.Context, client Client) (f Fetcher, err error) { + ok := cs.IfStarted(func() { + f, err = cs.get(ctx, client) + }) + if !ok { + return nil, fmt.Errorf("cacheSet must be started, but is: %v", cs.State()) + } + return +} + +func (cs *cacheSet) get(ctx context.Context, client Client) (Fetcher, error) { + sURL := client.ServerURL() + // HOT PATH + cs.RLock() + c, exists := cs.caches[sURL] + cs.RUnlock() + if exists { + return c, nil + } + + // COLD PATH + cs.Lock() + defer cs.Unlock() + c, exists = cs.caches[sURL] + if exists { + return c, nil + } + c = newMemCache(cs.lggr, client, cs.cfg) + if err := c.Start(ctx); err != nil { + return nil, err + } + cs.caches[sURL] = c + return c, nil +} + +func (cs *cacheSet) HealthReport() map[string]error { + report := map[string]error{ + cs.Name(): cs.Ready(), + } + cs.RLock() + defer cs.RUnlock() + for _, c := range cs.caches { + services.CopyHealth(report, c.HealthReport()) + } + return report +} +func (cs *cacheSet) Name() string { return cs.lggr.Name() } diff --git a/core/services/relay/evm/mercury/wsrpc/cache/cache_set_test.go b/core/services/relay/evm/mercury/wsrpc/cache/cache_set_test.go new file mode 100644 index 00000000000..4e19c7b56de --- /dev/null +++ b/core/services/relay/evm/mercury/wsrpc/cache/cache_set_test.go @@ -0,0 +1,48 @@ +package cache + +import ( + "testing" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_CacheSet(t *testing.T) { + lggr := logger.TestLogger(t) + cs := newCacheSet(lggr, Config{}) + ctx := testutils.Context(t) + require.NoError(t, cs.Start(ctx)) + t.Cleanup(func() { + assert.NoError(t, cs.Close()) + }) + + t.Run("Get", func(t *testing.T) { + c := &mockClient{} + + var err error + var f Fetcher + t.Run("with virgin cacheset, makes new entry and returns it", func(t *testing.T) { + assert.Len(t, cs.caches, 0) + + f, err = cs.Get(ctx, c) + require.NoError(t, err) + + assert.IsType(t, f, &memCache{}) + assert.Len(t, cs.caches, 1) + }) + t.Run("with existing cache for value, returns that", func(t *testing.T) { + var f2 Fetcher + assert.Len(t, cs.caches, 1) + + f2, err = cs.Get(ctx, c) + require.NoError(t, err) + + assert.IsType(t, f, &memCache{}) + assert.Equal(t, f, f2) + assert.Len(t, cs.caches, 1) + }) + }) +} diff --git a/core/services/relay/evm/mercury/wsrpc/cache/cache_test.go b/core/services/relay/evm/mercury/wsrpc/cache/cache_test.go new file mode 100644 index 00000000000..6378577d8d5 --- /dev/null +++ b/core/services/relay/evm/mercury/wsrpc/cache/cache_test.go @@ -0,0 +1,201 @@ +package cache + +import ( + "context" + "errors" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" +) + +const neverExpireTTL = 1000 * time.Hour // some massive value that will never expire during a test + +func Test_Cache(t *testing.T) { + lggr := logger.TestLogger(t) + client := &mockClient{} + cfg := Config{} + ctx := testutils.Context(t) + + req1 := &pb.LatestReportRequest{FeedId: []byte{1}} + req2 := &pb.LatestReportRequest{FeedId: []byte{2}} + req3 := &pb.LatestReportRequest{FeedId: []byte{3}} + + feedID1Hex := mercuryutils.BytesToFeedID(req1.FeedId).String() + + t.Run("errors with nil req", func(t *testing.T) { + c := newMemCache(lggr, client, cfg) + + _, err := c.LatestReport(ctx, nil) + assert.EqualError(t, err, "req must not be nil") + }) + + t.Run("with LatestReportTTL=0 does no caching", func(t *testing.T) { + c := newMemCache(lggr, client, cfg) + + req := &pb.LatestReportRequest{} + for i := 0; i < 5; i++ { + client.resp = &pb.LatestReportResponse{Report: &pb.Report{Price: []byte(strconv.Itoa(i))}} + + resp, err := c.LatestReport(ctx, req) + require.NoError(t, err) + assert.Equal(t, client.resp, resp) + } + + client.resp = nil + client.err = errors.New("something exploded") + + resp, err := c.LatestReport(ctx, req) + assert.EqualError(t, err, "something exploded") + assert.Nil(t, resp) + }) + + t.Run("caches repeated calls to LatestReport, keyed by request", func(t *testing.T) { + cfg.LatestReportTTL = neverExpireTTL + client.err = nil + c := newMemCache(lggr, client, cfg) + + t.Run("if cache is unstarted, returns error", func(t *testing.T) { + // starting the cache is required for state management if we + // actually cache results, since fetches are initiated async and + // need to be cleaned up properly on close + _, err := c.LatestReport(ctx, &pb.LatestReportRequest{}) + assert.EqualError(t, err, "memCache must be started, but is: Unstarted") + }) + + err := c.StartOnce("test start", func() error { return nil }) + require.NoError(t, err) + + t.Run("returns cached value for key", func(t *testing.T) { + var firstResp *pb.LatestReportResponse + for i := 0; i < 5; i++ { + client.resp = &pb.LatestReportResponse{Report: &pb.Report{Price: []byte(strconv.Itoa(i))}} + if firstResp == nil { + firstResp = client.resp + } + + resp, err := c.LatestReport(ctx, req1) + require.NoError(t, err) + assert.Equal(t, firstResp, resp) + } + }) + + t.Run("cache keys do not conflict", func(t *testing.T) { + var firstResp1 *pb.LatestReportResponse + for i := 5; i < 10; i++ { + client.resp = &pb.LatestReportResponse{Report: &pb.Report{Price: []byte(strconv.Itoa(i))}} + if firstResp1 == nil { + firstResp1 = client.resp + } + + resp, err := c.LatestReport(ctx, req2) + require.NoError(t, err) + assert.Equal(t, firstResp1, resp) + } + + var firstResp2 *pb.LatestReportResponse + for i := 10; i < 15; i++ { + client.resp = &pb.LatestReportResponse{Report: &pb.Report{Price: []byte(strconv.Itoa(i))}} + if firstResp2 == nil { + firstResp2 = client.resp + } + + resp, err := c.LatestReport(ctx, req3) + require.NoError(t, err) + assert.Equal(t, firstResp2, resp) + } + + // req1 key still has same value + resp, err := c.LatestReport(ctx, req1) + require.NoError(t, err) + assert.Equal(t, []byte(strconv.Itoa(0)), resp.Report.Price) + + // req2 key still has same value + resp, err = c.LatestReport(ctx, req2) + require.NoError(t, err) + assert.Equal(t, []byte(strconv.Itoa(5)), resp.Report.Price) + }) + + t.Run("re-queries when a cache item has expired", func(t *testing.T) { + vi, exists := c.cache.Load(feedID1Hex) + require.True(t, exists) + v := vi.(*cacheVal) + v.expiresAt = time.Now().Add(-1 * time.Second) + + client.resp = &pb.LatestReportResponse{Report: &pb.Report{Price: []byte(strconv.Itoa(15))}} + + resp, err := c.LatestReport(ctx, req1) + require.NoError(t, err) + assert.Equal(t, client.resp, resp) + + // querying again yields the same cached item + resp, err = c.LatestReport(ctx, req1) + require.NoError(t, err) + assert.Equal(t, client.resp, resp) + }) + }) + + t.Run("complete fetch", func(t *testing.T) { + t.Run("does not change expiry if fetch returns error", func(t *testing.T) { + expires := time.Now().Add(-1 * time.Second) + v := &cacheVal{ + fetching: true, + fetchCh: make(chan (struct{})), + val: nil, + err: nil, + expiresAt: expires, + } + v.completeFetch(nil, errors.New("foo"), time.Now().Add(neverExpireTTL)) + assert.Equal(t, expires, v.expiresAt) + + v = &cacheVal{ + fetching: true, + fetchCh: make(chan (struct{})), + val: nil, + err: nil, + expiresAt: expires, + } + expires = time.Now().Add(neverExpireTTL) + v.completeFetch(nil, nil, expires) + assert.Equal(t, expires, v.expiresAt) + }) + }) + + t.Run("timeouts", func(t *testing.T) { + c := newMemCache(lggr, client, cfg) + // simulate fetch already executing in background + v := &cacheVal{ + fetching: true, + fetchCh: make(chan (struct{})), + val: nil, + err: nil, + expiresAt: time.Now().Add(-1 * time.Second), + } + c.cache.Store(feedID1Hex, v) + + canceledCtx, cancel := context.WithCancel(testutils.Context(t)) + cancel() + + t.Run("returns context deadline exceeded error if fetch takes too long", func(t *testing.T) { + _, err := c.LatestReport(canceledCtx, req1) + require.Error(t, err) + assert.True(t, errors.Is(err, context.Canceled)) + assert.EqualError(t, err, "context canceled") + }) + t.Run("returns wrapped context deadline exceeded error if fetch has errored and is in the retry loop", func(t *testing.T) { + v.err = errors.New("some background fetch error") + + _, err := c.LatestReport(canceledCtx, req1) + require.Error(t, err) + assert.True(t, errors.Is(err, context.Canceled)) + assert.EqualError(t, err, "some background fetch error\ncontext canceled") + }) + }) +} diff --git a/core/services/relay/evm/mercury/wsrpc/cache/helpers_test.go b/core/services/relay/evm/mercury/wsrpc/cache/helpers_test.go new file mode 100644 index 00000000000..4cc08bdd52e --- /dev/null +++ b/core/services/relay/evm/mercury/wsrpc/cache/helpers_test.go @@ -0,0 +1,38 @@ +package cache + +import ( + "context" + + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" +) + +var _ Client = &mockClient{} + +type mockClient struct { + resp *pb.LatestReportResponse + err error +} + +func (m *mockClient) LatestReport(ctx context.Context, req *pb.LatestReportRequest) (resp *pb.LatestReportResponse, err error) { + return m.resp, m.err +} + +func (m *mockClient) ServerURL() string { + return "mock client url" +} + +func (m *mockClient) RawClient() pb.MercuryClient { + return &mockRawClient{m.resp, m.err} +} + +type mockRawClient struct { + resp *pb.LatestReportResponse + err error +} + +func (m *mockRawClient) Transmit(ctx context.Context, in *pb.TransmitRequest) (*pb.TransmitResponse, error) { + return nil, nil +} +func (m *mockRawClient) LatestReport(ctx context.Context, in *pb.LatestReportRequest) (*pb.LatestReportResponse, error) { + return m.resp, m.err +} diff --git a/core/services/relay/evm/mercury/wsrpc/client.go b/core/services/relay/evm/mercury/wsrpc/client.go index fb9a57d9a2d..5cdf1f44e96 100644 --- a/core/services/relay/evm/mercury/wsrpc/client.go +++ b/core/services/relay/evm/mercury/wsrpc/client.go @@ -11,12 +11,15 @@ import ( "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/smartcontractkit/wsrpc" "github.com/smartcontractkit/wsrpc/connectivity" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/cache" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -59,8 +62,10 @@ var ( ) type Client interface { - services.ServiceCtx + services.Service pb.MercuryClient + ServerURL() string + RawClient() pb.MercuryClient } type Conn interface { @@ -70,21 +75,24 @@ type Conn interface { } type client struct { - utils.StartStopOnce + services.StateMachine csaKey csakey.KeyV2 serverPubKey []byte serverURL string - logger logger.Logger - conn Conn - client pb.MercuryClient + logger logger.Logger + conn Conn + rawClient pb.MercuryClient consecutiveTimeoutCnt atomic.Int32 wg sync.WaitGroup - chStop utils.StopChan + chStop services.StopChan chResetTransport chan struct{} + cacheSet cache.CacheSet + cache cache.Fetcher + timeoutCountMetric prometheus.Counter dialCountMetric prometheus.Counter dialSuccessCountMetric prometheus.Counter @@ -93,18 +101,19 @@ type client struct { } // Consumers of wsrpc package should not usually call NewClient directly, but instead use the Pool -func NewClient(lggr logger.Logger, clientPrivKey csakey.KeyV2, serverPubKey []byte, serverURL string) Client { - return newClient(lggr, clientPrivKey, serverPubKey, serverURL) +func NewClient(lggr logger.Logger, clientPrivKey csakey.KeyV2, serverPubKey []byte, serverURL string, cacheSet cache.CacheSet) Client { + return newClient(lggr, clientPrivKey, serverPubKey, serverURL, cacheSet) } -func newClient(lggr logger.Logger, clientPrivKey csakey.KeyV2, serverPubKey []byte, serverURL string) *client { +func newClient(lggr logger.Logger, clientPrivKey csakey.KeyV2, serverPubKey []byte, serverURL string, cacheSet cache.CacheSet) *client { return &client{ csaKey: clientPrivKey, serverPubKey: serverPubKey, serverURL: serverURL, logger: lggr.Named("WSRPC").With("mercuryServerURL", serverURL), chResetTransport: make(chan struct{}, 1), - chStop: make(chan struct{}), + cacheSet: cacheSet, + chStop: make(services.StopChan), timeoutCountMetric: timeoutCount.WithLabelValues(serverURL), dialCountMetric: dialCount.WithLabelValues(serverURL), dialSuccessCountMetric: dialSuccessCount.WithLabelValues(serverURL), @@ -113,9 +122,15 @@ func newClient(lggr logger.Logger, clientPrivKey csakey.KeyV2, serverPubKey []by } } -func (w *client) Start(_ context.Context) error { - return w.StartOnce("WSRPC Client", func() error { - if err := w.dial(context.Background()); err != nil { +func (w *client) Start(ctx context.Context) error { + return w.StartOnce("WSRPC Client", func() (err error) { + // NOTE: This is not a mistake, dial is non-blocking so it should use a + // background context, not the Start context + if err = w.dial(context.Background()); err != nil { + return err + } + w.cache, err = w.cacheSet.Get(ctx, w) + if err != nil { return err } w.wg.Add(1) @@ -146,7 +161,7 @@ func (w *client) dial(ctx context.Context, opts ...wsrpc.DialOption) error { w.dialSuccessCountMetric.Inc() setLivenessMetric(true) w.conn = conn - w.client = pb.NewMercuryClient(conn) + w.rawClient = pb.NewMercuryClient(conn) return nil } @@ -211,7 +226,7 @@ func (w *client) HealthReport() map[string]error { // Healthy if connected func (w *client) Healthy() (err error) { - if err = w.StartStopOnce.Healthy(); err != nil { + if err = w.StateMachine.Healthy(); err != nil { return err } state := w.conn.GetState() @@ -240,7 +255,7 @@ func (w *client) Transmit(ctx context.Context, req *pb.TransmitRequest) (resp *p if err = w.waitForReady(ctx); err != nil { return nil, errors.Wrap(err, "Transmit call failed") } - resp, err = w.client.Transmit(ctx, req) + resp, err = w.rawClient.Transmit(ctx, req) if errors.Is(err, context.DeadlineExceeded) { w.timeoutCountMetric.Inc() cnt := w.consecutiveTimeoutCnt.Add(1) @@ -288,7 +303,11 @@ func (w *client) LatestReport(ctx context.Context, req *pb.LatestReportRequest) if err = w.waitForReady(ctx); err != nil { return nil, errors.Wrap(err, "LatestReport failed") } - resp, err = w.client.LatestReport(ctx, req) + if w.cache == nil { + resp, err = w.rawClient.LatestReport(ctx, req) + } else { + resp, err = w.cache.LatestReport(ctx, req) + } if err != nil { lggr.Errorw("LatestReport failed", "err", err, "resp", resp) } else if resp.Error != "" { @@ -298,3 +317,11 @@ func (w *client) LatestReport(ctx context.Context, req *pb.LatestReportRequest) } return } + +func (w *client) ServerURL() string { + return w.serverURL +} + +func (w *client) RawClient() pb.MercuryClient { + return w.rawClient +} diff --git a/core/services/relay/evm/mercury/wsrpc/client_test.go b/core/services/relay/evm/mercury/wsrpc/client_test.go index 2410cbc52f1..9b0100a3cdd 100644 --- a/core/services/relay/evm/mercury/wsrpc/client_test.go +++ b/core/services/relay/evm/mercury/wsrpc/client_test.go @@ -3,6 +3,7 @@ package wsrpc import ( "context" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -10,15 +11,48 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/cache" mocks "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" ) +var _ cache.CacheSet = &mockCacheSet{} + +type mockCacheSet struct{} + +func (m *mockCacheSet) Get(ctx context.Context, client cache.Client) (cache.Fetcher, error) { + return nil, nil +} +func (m *mockCacheSet) Start(context.Context) error { return nil } +func (m *mockCacheSet) Ready() error { return nil } +func (m *mockCacheSet) HealthReport() map[string]error { return nil } +func (m *mockCacheSet) Name() string { return "" } +func (m *mockCacheSet) Close() error { return nil } + +var _ cache.Cache = &mockCache{} + +type mockCache struct{} + +func (m *mockCache) LatestReport(ctx context.Context, req *pb.LatestReportRequest) (resp *pb.LatestReportResponse, err error) { + return nil, nil +} +func (m *mockCache) Start(context.Context) error { return nil } +func (m *mockCache) Ready() error { return nil } +func (m *mockCache) HealthReport() map[string]error { return nil } +func (m *mockCache) Name() string { return "" } +func (m *mockCache) Close() error { return nil } + +func newNoopCacheSet() cache.CacheSet { + return &mockCacheSet{} +} + func Test_Client_Transmit(t *testing.T) { lggr := logger.TestLogger(t) ctx := testutils.Context(t) req := &pb.TransmitRequest{} + noopCacheSet := newNoopCacheSet() + t.Run("sends on reset channel after MaxConsecutiveTransmitFailures timed out transmits", func(t *testing.T) { calls := 0 transmitErr := context.DeadlineExceeded @@ -31,9 +65,9 @@ func Test_Client_Transmit(t *testing.T) { conn := &mocks.MockConn{ Ready: true, } - c := newClient(lggr, csakey.KeyV2{}, nil, "") + c := newClient(lggr, csakey.KeyV2{}, nil, "", noopCacheSet) c.conn = conn - c.client = wsrpcClient + c.rawClient = wsrpcClient require.NoError(t, c.StartOnce("Mock WSRPC Client", func() error { return nil })) for i := 1; i < MaxConsecutiveTransmitFailures; i++ { _, err := c.Transmit(ctx, req) @@ -73,3 +107,109 @@ func Test_Client_Transmit(t *testing.T) { }) }) } + +func Test_Client_LatestReport(t *testing.T) { + lggr := logger.TestLogger(t) + ctx := testutils.Context(t) + + t.Run("with nil cache", func(t *testing.T) { + req := &pb.LatestReportRequest{} + noopCacheSet := newNoopCacheSet() + resp := &pb.LatestReportResponse{} + + wsrpcClient := &mocks.MockWSRPCClient{ + LatestReportF: func(ctx context.Context, in *pb.LatestReportRequest) (*pb.LatestReportResponse, error) { + assert.Equal(t, req, in) + return resp, nil + }, + } + + conn := &mocks.MockConn{ + Ready: true, + } + c := newClient(lggr, csakey.KeyV2{}, nil, "", noopCacheSet) + c.conn = conn + c.rawClient = wsrpcClient + require.NoError(t, c.StartOnce("Mock WSRPC Client", func() error { return nil })) + + r, err := c.LatestReport(ctx, req) + + require.NoError(t, err) + assert.Equal(t, resp, r) + }) + + t.Run("with cache disabled", func(t *testing.T) { + req := &pb.LatestReportRequest{} + cacheSet := cache.NewCacheSet(lggr, cache.Config{LatestReportTTL: 0}) + resp := &pb.LatestReportResponse{} + + var calls int + wsrpcClient := &mocks.MockWSRPCClient{ + LatestReportF: func(ctx context.Context, in *pb.LatestReportRequest) (*pb.LatestReportResponse, error) { + calls++ + assert.Equal(t, req, in) + return resp, nil + }, + } + + conn := &mocks.MockConn{ + Ready: true, + } + c := newClient(lggr, csakey.KeyV2{}, nil, "", cacheSet) + c.conn = conn + c.rawClient = wsrpcClient + + // simulate start without dialling + require.NoError(t, c.StartOnce("Mock WSRPC Client", func() error { return nil })) + var err error + require.NoError(t, cacheSet.Start(ctx)) + c.cache, err = cacheSet.Get(ctx, c) + require.NoError(t, err) + + for i := 0; i < 5; i++ { + r, err := c.LatestReport(ctx, req) + + require.NoError(t, err) + assert.Equal(t, resp, r) + } + assert.Equal(t, 5, calls, "expected 5 calls to LatestReport but it was called %d times", calls) + }) + + t.Run("with caching", func(t *testing.T) { + req := &pb.LatestReportRequest{} + const neverExpireTTL = 1000 * time.Hour // some massive value that will never expire during a test + cacheSet := cache.NewCacheSet(lggr, cache.Config{LatestReportTTL: neverExpireTTL}) + resp := &pb.LatestReportResponse{} + + var calls int + wsrpcClient := &mocks.MockWSRPCClient{ + LatestReportF: func(ctx context.Context, in *pb.LatestReportRequest) (*pb.LatestReportResponse, error) { + calls++ + assert.Equal(t, req, in) + return resp, nil + }, + } + + conn := &mocks.MockConn{ + Ready: true, + } + c := newClient(lggr, csakey.KeyV2{}, nil, "", cacheSet) + c.conn = conn + c.rawClient = wsrpcClient + + // simulate start without dialling + require.NoError(t, c.StartOnce("Mock WSRPC Client", func() error { return nil })) + var err error + require.NoError(t, cacheSet.Start(ctx)) + c.cache, err = cacheSet.Get(ctx, c) + require.NoError(t, err) + + for i := 0; i < 5; i++ { + r, err := c.LatestReport(ctx, req) + + require.NoError(t, err) + assert.Equal(t, resp, r) + } + assert.Equal(t, 1, calls, "expected only 1 call to LatestReport but it was called %d times", calls) + }) +} diff --git a/core/services/relay/evm/mercury/wsrpc/mocks/mocks.go b/core/services/relay/evm/mercury/wsrpc/mocks/mocks.go index d764143c5fc..c0caf0dee12 100644 --- a/core/services/relay/evm/mercury/wsrpc/mocks/mocks.go +++ b/core/services/relay/evm/mercury/wsrpc/mocks/mocks.go @@ -24,6 +24,9 @@ func (m MockWSRPCClient) Transmit(ctx context.Context, in *pb.TransmitRequest) ( func (m MockWSRPCClient) LatestReport(ctx context.Context, in *pb.LatestReportRequest) (*pb.LatestReportResponse, error) { return m.LatestReportF(ctx, in) } +func (m MockWSRPCClient) ServerURL() string { return "mock server url" } + +func (m MockWSRPCClient) RawClient() pb.MercuryClient { return nil } type MockConn struct { State connectivity.State diff --git a/core/services/relay/evm/mercury/wsrpc/pool.go b/core/services/relay/evm/mercury/wsrpc/pool.go index 6630a78437e..76f9bc808b9 100644 --- a/core/services/relay/evm/mercury/wsrpc/pool.go +++ b/core/services/relay/evm/mercury/wsrpc/pool.go @@ -10,6 +10,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/cache" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -58,7 +59,7 @@ func (conn *connection) checkout(ctx context.Context) (cco *clientCheckout, err // not thread-safe, access must be serialized func (conn *connection) ensureStartedClient(ctx context.Context) error { if len(conn.checkouts) == 0 { - conn.Client = conn.pool.newClient(conn.lggr, conn.clientPrivKey, conn.serverPubKey, conn.serverURL) + conn.Client = conn.pool.newClient(conn.lggr, conn.clientPrivKey, conn.serverPubKey, conn.serverURL, conn.pool.cacheSet) return conn.Client.Start(ctx) } return nil @@ -119,16 +120,20 @@ type pool struct { connections map[string]map[credentials.StaticSizedPublicKey]*connection // embedding newClient makes testing/mocking easier - newClient func(lggr logger.Logger, privKey csakey.KeyV2, serverPubKey []byte, serverURL string) Client + newClient func(lggr logger.Logger, privKey csakey.KeyV2, serverPubKey []byte, serverURL string, cacheSet cache.CacheSet) Client mu sync.RWMutex + cacheSet cache.CacheSet + closed bool } -func NewPool(lggr logger.Logger) Pool { - p := newPool(lggr.Named("Mercury.WSRPCPool")) +func NewPool(lggr logger.Logger, cacheCfg cache.Config) Pool { + lggr = lggr.Named("Mercury.WSRPCPool") + p := newPool(lggr) p.newClient = NewClient + p.cacheSet = cache.NewCacheSet(lggr, cacheCfg) return p } @@ -188,7 +193,9 @@ func (p *pool) newConnection(lggr logger.Logger, clientPrivKey csakey.KeyV2, ser } } -func (p *pool) Start(_ context.Context) error { return nil } +func (p *pool) Start(ctx context.Context) error { + return p.cacheSet.Start(ctx) +} func (p *pool) Close() (merr error) { p.mu.Lock() @@ -199,6 +206,7 @@ func (p *pool) Close() (merr error) { merr = errors.Join(merr, conn.forceCloseAll()) } } + merr = errors.Join(merr, p.cacheSet.Close()) return } diff --git a/core/services/relay/evm/mercury/wsrpc/pool_test.go b/core/services/relay/evm/mercury/wsrpc/pool_test.go index 980b9f4d742..3d418d39d87 100644 --- a/core/services/relay/evm/mercury/wsrpc/pool_test.go +++ b/core/services/relay/evm/mercury/wsrpc/pool_test.go @@ -12,6 +12,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/cache" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -19,8 +20,9 @@ import ( var _ Client = &mockClient{} type mockClient struct { - started bool - closed bool + started bool + closed bool + rawClient pb.MercuryClient } func (c *mockClient) Transmit(ctx context.Context, in *pb.TransmitRequest) (out *pb.TransmitResponse, err error) { @@ -40,6 +42,8 @@ func (c *mockClient) Close() error { func (c *mockClient) Name() string { return "mock client" } func (c *mockClient) Ready() error { return nil } func (c *mockClient) HealthReport() map[string]error { return nil } +func (c *mockClient) ServerURL() string { return "mock client url" } +func (c *mockClient) RawClient() pb.MercuryClient { return c.rawClient } func newMockClient(lggr logger.Logger) *mockClient { return &mockClient{} @@ -52,6 +56,7 @@ func Test_Pool(t *testing.T) { t.Run("Checkout", func(t *testing.T) { p := newPool(lggr) + p.cacheSet = &mockCacheSet{} t.Run("checks out one started client", func(t *testing.T) { clientPrivKey := csakey.MustNewV2XXXTestingOnly(big.NewInt(rand.Int63())) @@ -59,7 +64,7 @@ func Test_Pool(t *testing.T) { serverURL := "example.com:443/ws" client := newMockClient(lggr) - p.newClient = func(lggr logger.Logger, cprivk csakey.KeyV2, spubk []byte, surl string) Client { + p.newClient = func(lggr logger.Logger, cprivk csakey.KeyV2, spubk []byte, surl string, cs cache.CacheSet) Client { assert.Equal(t, clientPrivKey, cprivk) assert.Equal(t, serverPubKey, spubk) assert.Equal(t, serverURL, surl) @@ -105,7 +110,7 @@ func Test_Pool(t *testing.T) { "example.invalid:8000/ws", } - p.newClient = func(lggr logger.Logger, cprivk csakey.KeyV2, spubk []byte, surl string) Client { + p.newClient = func(lggr logger.Logger, cprivk csakey.KeyV2, spubk []byte, surl string, cs cache.CacheSet) Client { return newMockClient(lggr) } @@ -199,6 +204,7 @@ func Test_Pool(t *testing.T) { }) p := newPool(lggr) + p.cacheSet = &mockCacheSet{} t.Run("Name", func(t *testing.T) { assert.Equal(t, "PoolTestLogger", p.Name()) @@ -220,7 +226,7 @@ func Test_Pool(t *testing.T) { } var clients []*mockClient - p.newClient = func(lggr logger.Logger, cprivk csakey.KeyV2, spubk []byte, surl string) Client { + p.newClient = func(lggr logger.Logger, cprivk csakey.KeyV2, spubk []byte, surl string, cs cache.CacheSet) Client { c := newMockClient(lggr) clients = append(clients, c) return c diff --git a/core/services/relay/evm/mercury_provider.go b/core/services/relay/evm/mercury_provider.go index 914401c0897..90253808171 100644 --- a/core/services/relay/evm/mercury_provider.go +++ b/core/services/relay/evm/mercury_provider.go @@ -6,18 +6,19 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - relaymercury "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury" - relaymercuryv1 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v1" - relaymercuryv2 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v2" - relaymercuryv3 "github.com/smartcontractkit/chainlink-relay/pkg/reportingplugins/mercury/v3" - "github.com/smartcontractkit/chainlink-relay/pkg/services" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + relaymercury "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury" + relaymercuryv1 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v1" + relaymercuryv2 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v2" + relaymercuryv3 "github.com/smartcontractkit/chainlink-common/pkg/reportingplugins/mercury/v3" + "github.com/smartcontractkit/chainlink-common/pkg/services" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" ) -var _ relaytypes.MercuryProvider = (*mercuryProvider)(nil) +var _ commontypes.MercuryProvider = (*mercuryProvider)(nil) type mercuryProvider struct { configWatcher *configWatcher @@ -25,6 +26,7 @@ type mercuryProvider struct { reportCodecV1 relaymercuryv1.ReportCodec reportCodecV2 relaymercuryv2.ReportCodec reportCodecV3 relaymercuryv3.ReportCodec + chainReader relaymercury.ChainReader logger logger.Logger ms services.MultiStart @@ -36,6 +38,7 @@ func NewMercuryProvider( reportCodecV1 relaymercuryv1.ReportCodec, reportCodecV2 relaymercuryv2.ReportCodec, reportCodecV3 relaymercuryv3.ReportCodec, + chainReader relaymercury.ChainReader, lggr logger.Logger, ) *mercuryProvider { return &mercuryProvider{ @@ -44,6 +47,7 @@ func NewMercuryProvider( reportCodecV1, reportCodecV2, reportCodecV3, + chainReader, lggr, services.MultiStart{}, } @@ -103,3 +107,37 @@ func (p *mercuryProvider) ContractTransmitter() ocrtypes.ContractTransmitter { func (p *mercuryProvider) MercuryServerFetcher() relaymercury.MercuryServerFetcher { return p.transmitter } + +func (p *mercuryProvider) ChainReader() relaymercury.ChainReader { + return p.chainReader +} + +var _ relaymercury.ChainReader = (*chainReader)(nil) + +type chainReader struct { + tracker httypes.HeadTracker +} + +func NewChainReader(h httypes.HeadTracker) relaymercury.ChainReader { + return &chainReader{ + tracker: h, + } +} + +func (r *chainReader) LatestHeads(ctx context.Context, k int) ([]relaymercury.Head, error) { + evmBlocks := r.tracker.LatestChain().AsSlice(k) + if len(evmBlocks) == 0 { + return nil, nil + } + + blocks := make([]relaymercury.Head, len(evmBlocks)) + for x := 0; x < len(evmBlocks); x++ { + blocks[x] = relaymercury.Head{ + Number: uint64(evmBlocks[x].BlockNumber()), + Hash: evmBlocks[x].Hash.Bytes(), + Timestamp: uint64(evmBlocks[x].Timestamp.Unix()), + } + } + + return blocks, nil +} diff --git a/core/services/relay/evm/mocks/loop_relay_adapter.go b/core/services/relay/evm/mocks/loop_relay_adapter.go index 13107fe8ae5..0376c9f27a4 100644 --- a/core/services/relay/evm/mocks/loop_relay_adapter.go +++ b/core/services/relay/evm/mocks/loop_relay_adapter.go @@ -6,10 +6,11 @@ import ( context "context" big "math/big" - evm "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + legacyevm "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" + mock "github.com/stretchr/testify/mock" - types "github.com/smartcontractkit/chainlink-relay/pkg/types" + types "github.com/smartcontractkit/chainlink-common/pkg/types" ) // LoopRelayAdapter is an autogenerated mock type for the LoopRelayAdapter type @@ -18,15 +19,15 @@ type LoopRelayAdapter struct { } // Chain provides a mock function with given fields: -func (_m *LoopRelayAdapter) Chain() evm.Chain { +func (_m *LoopRelayAdapter) Chain() legacyevm.Chain { ret := _m.Called() - var r0 evm.Chain - if rf, ok := ret.Get(0).(func() evm.Chain); ok { + var r0 legacyevm.Chain + if rf, ok := ret.Get(0).(func() legacyevm.Chain); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(evm.Chain) + r0 = ret.Get(0).(legacyevm.Chain) } } diff --git a/core/services/relay/evm/ocr2keeper.go b/core/services/relay/evm/ocr2keeper.go index baf98b9b006..3b3bfeb652d 100644 --- a/core/services/relay/evm/ocr2keeper.go +++ b/core/services/relay/evm/ocr2keeper.go @@ -8,18 +8,19 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" - "github.com/smartcontractkit/ocr2keepers/pkg/v3/plugin" - "github.com/smartcontractkit/sqlx" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" @@ -33,32 +34,32 @@ var ( // OCR2KeeperProviderOpts is the custom options to create a keeper provider type OCR2KeeperProviderOpts struct { - RArgs relaytypes.RelayArgs - PArgs relaytypes.PluginArgs + RArgs commontypes.RelayArgs + PArgs commontypes.PluginArgs InstanceID int } // OCR2KeeperProvider provides all components needed for a OCR2Keeper plugin. type OCR2KeeperProvider interface { - relaytypes.Plugin + commontypes.Plugin } // OCR2KeeperRelayer contains the relayer and instantiating functions for OCR2Keeper providers. type OCR2KeeperRelayer interface { - NewOCR2KeeperProvider(rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs) (OCR2KeeperProvider, error) + NewOCR2KeeperProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (OCR2KeeperProvider, error) } // ocr2keeperRelayer is the relayer with added DKG and OCR2Keeper provider functions. type ocr2keeperRelayer struct { db *sqlx.DB - chain evm.Chain + chain legacyevm.Chain pr pipeline.Runner spec job.Job lggr logger.Logger } // NewOCR2KeeperRelayer is the constructor of ocr2keeperRelayer -func NewOCR2KeeperRelayer(db *sqlx.DB, chain evm.Chain, pr pipeline.Runner, spec job.Job, lggr logger.Logger) OCR2KeeperRelayer { +func NewOCR2KeeperRelayer(db *sqlx.DB, chain legacyevm.Chain, pr pipeline.Runner, spec job.Job, lggr logger.Logger) OCR2KeeperRelayer { return &ocr2keeperRelayer{ db: db, chain: chain, @@ -68,7 +69,7 @@ func NewOCR2KeeperRelayer(db *sqlx.DB, chain evm.Chain, pr pipeline.Runner, spec } } -func (r *ocr2keeperRelayer) NewOCR2KeeperProvider(rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs) (OCR2KeeperProvider, error) { +func (r *ocr2keeperRelayer) NewOCR2KeeperProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (OCR2KeeperProvider, error) { cfgWatcher, err := newOCR2KeeperConfigProvider(r.lggr, r.chain, rargs) if err != nil { return nil, err @@ -129,7 +130,7 @@ func (c *ocr2keeperProvider) ContractTransmitter() ocrtypes.ContractTransmitter return c.contractTransmitter } -func newOCR2KeeperConfigProvider(lggr logger.Logger, chain evm.Chain, rargs relaytypes.RelayArgs) (*configWatcher, error) { +func newOCR2KeeperConfigProvider(lggr logger.Logger, chain legacyevm.Chain, rargs commontypes.RelayArgs) (*configWatcher, error) { var relayConfig types.RelayConfig err := json.Unmarshal(rargs.RelayConfig, &relayConfig) if err != nil { diff --git a/core/services/relay/evm/ocr2vrf.go b/core/services/relay/evm/ocr2vrf.go index 14004d0b1aa..b7a2220588b 100644 --- a/core/services/relay/evm/ocr2vrf.go +++ b/core/services/relay/evm/ocr2vrf.go @@ -7,15 +7,16 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/sqlx" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/dkg/config" @@ -24,18 +25,18 @@ import ( // DKGProvider provides all components needed for a DKG plugin. type DKGProvider interface { - relaytypes.Plugin + commontypes.Plugin } // OCR2VRFProvider provides all components needed for a OCR2VRF plugin. type OCR2VRFProvider interface { - relaytypes.Plugin + commontypes.Plugin } // OCR2VRFRelayer contains the relayer and instantiating functions for OCR2VRF providers. type OCR2VRFRelayer interface { - NewDKGProvider(rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs) (DKGProvider, error) - NewOCR2VRFProvider(rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs) (OCR2VRFProvider, error) + NewDKGProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (DKGProvider, error) + NewOCR2VRFProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (OCR2VRFProvider, error) } var ( @@ -47,12 +48,12 @@ var ( // Relayer with added DKG and OCR2VRF provider functions. type ocr2vrfRelayer struct { db *sqlx.DB - chain evm.Chain + chain legacyevm.Chain lggr logger.Logger ethKeystore keystore.Eth } -func NewOCR2VRFRelayer(db *sqlx.DB, chain evm.Chain, lggr logger.Logger, ethKeystore keystore.Eth) OCR2VRFRelayer { +func NewOCR2VRFRelayer(db *sqlx.DB, chain legacyevm.Chain, lggr logger.Logger, ethKeystore keystore.Eth) OCR2VRFRelayer { return &ocr2vrfRelayer{ db: db, chain: chain, @@ -61,7 +62,7 @@ func NewOCR2VRFRelayer(db *sqlx.DB, chain evm.Chain, lggr logger.Logger, ethKeys } } -func (r *ocr2vrfRelayer) NewDKGProvider(rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs) (DKGProvider, error) { +func (r *ocr2vrfRelayer) NewDKGProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (DKGProvider, error) { configWatcher, err := newOCR2VRFConfigProvider(r.lggr, r.chain, rargs) if err != nil { return nil, err @@ -84,7 +85,7 @@ func (r *ocr2vrfRelayer) NewDKGProvider(rargs relaytypes.RelayArgs, pargs relayt }, nil } -func (r *ocr2vrfRelayer) NewOCR2VRFProvider(rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs) (OCR2VRFProvider, error) { +func (r *ocr2vrfRelayer) NewOCR2VRFProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (OCR2VRFProvider, error) { configWatcher, err := newOCR2VRFConfigProvider(r.lggr, r.chain, rargs) if err != nil { return nil, err @@ -118,7 +119,7 @@ func (c *ocr2vrfProvider) ContractTransmitter() ocrtypes.ContractTransmitter { return c.contractTransmitter } -func newOCR2VRFConfigProvider(lggr logger.Logger, chain evm.Chain, rargs relaytypes.RelayArgs) (*configWatcher, error) { +func newOCR2VRFConfigProvider(lggr logger.Logger, chain legacyevm.Chain, rargs commontypes.RelayArgs) (*configWatcher, error) { var relayConfig types.RelayConfig err := json.Unmarshal(rargs.RelayConfig, &relayConfig) if err != nil { diff --git a/core/services/relay/evm/relayer_extender.go b/core/services/relay/evm/relayer_extender.go index b6ca4d75fb7..83f03b47f9e 100644 --- a/core/services/relay/evm/relayer_extender.go +++ b/core/services/relay/evm/relayer_extender.go @@ -8,12 +8,11 @@ import ( "github.com/pkg/errors" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" - evmchain "github.com/smartcontractkit/chainlink/v2/core/chains/evm" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" ) // ErrNoChains indicates that no EVM chains have been started @@ -21,31 +20,31 @@ var ErrNoChains = errors.New("no EVM chains loaded") type EVMChainRelayerExtender interface { loop.RelayerExt - Chain() evmchain.Chain + Chain() legacyevm.Chain } type EVMChainRelayerExtenderSlicer interface { Slice() []EVMChainRelayerExtender Len() int - AppConfig() evmchain.AppConfig + AppConfig() legacyevm.AppConfig } type ChainRelayerExtenders struct { exts []EVMChainRelayerExtender - cfg evmchain.AppConfig + cfg legacyevm.AppConfig } var _ EVMChainRelayerExtenderSlicer = &ChainRelayerExtenders{} -func NewLegacyChainsFromRelayerExtenders(exts EVMChainRelayerExtenderSlicer) *evmchain.LegacyChains { - m := make(map[string]evmchain.Chain) +func NewLegacyChainsFromRelayerExtenders(exts EVMChainRelayerExtenderSlicer) *legacyevm.LegacyChains { + m := make(map[string]legacyevm.Chain) for _, r := range exts.Slice() { m[r.Chain().ID().String()] = r.Chain() } - return evmchain.NewLegacyChains(m, exts.AppConfig().EVMConfigs()) + return legacyevm.NewLegacyChains(m, exts.AppConfig().EVMConfigs()) } -func newChainRelayerExtsFromSlice(exts []*ChainRelayerExt, appConfig evm.AppConfig) *ChainRelayerExtenders { +func newChainRelayerExtsFromSlice(exts []*ChainRelayerExt, appConfig legacyevm.AppConfig) *ChainRelayerExtenders { temp := make([]EVMChainRelayerExtender, len(exts)) for i := range exts { temp[i] = exts[i] @@ -56,7 +55,7 @@ func newChainRelayerExtsFromSlice(exts []*ChainRelayerExt, appConfig evm.AppConf } } -func (c *ChainRelayerExtenders) AppConfig() evmchain.AppConfig { +func (c *ChainRelayerExtenders) AppConfig() legacyevm.AppConfig { return c.cfg } @@ -70,16 +69,16 @@ func (c *ChainRelayerExtenders) Len() int { // implements OneChain type ChainRelayerExt struct { - chain evmchain.Chain + chain legacyevm.Chain } var _ EVMChainRelayerExtender = &ChainRelayerExt{} -func (s *ChainRelayerExt) GetChainStatus(ctx context.Context) (relaytypes.ChainStatus, error) { +func (s *ChainRelayerExt) GetChainStatus(ctx context.Context) (commontypes.ChainStatus, error) { return s.chain.GetChainStatus(ctx) } -func (s *ChainRelayerExt) ListNodeStatuses(ctx context.Context, pageSize int32, pageToken string) (stats []relaytypes.NodeStatus, nextPageToken string, total int, err error) { +func (s *ChainRelayerExt) ListNodeStatuses(ctx context.Context, pageSize int32, pageToken string) (stats []commontypes.NodeStatus, nextPageToken string, total int, err error) { return s.chain.ListNodeStatuses(ctx, pageSize, pageToken) } @@ -91,7 +90,7 @@ func (s *ChainRelayerExt) ID() string { return s.chain.ID().String() } -func (s *ChainRelayerExt) Chain() evmchain.Chain { +func (s *ChainRelayerExt) Chain() legacyevm.Chain { return s.chain } @@ -117,7 +116,7 @@ func (s *ChainRelayerExt) Ready() (err error) { return s.chain.Ready() } -func NewChainRelayerExtenders(ctx context.Context, opts evmchain.ChainRelayExtenderConfig) (*ChainRelayerExtenders, error) { +func NewChainRelayerExtenders(ctx context.Context, opts legacyevm.ChainRelayExtenderConfig) (*ChainRelayerExtenders, error) { if err := opts.Validate(); err != nil { return nil, err } @@ -142,14 +141,14 @@ func NewChainRelayerExtenders(ctx context.Context, opts evmchain.ChainRelayExten for i := range enabled { cid := enabled[i].ChainID.String() - privOpts := evmchain.ChainRelayExtenderConfig{ + privOpts := legacyevm.ChainRelayExtenderConfig{ Logger: opts.Logger.Named(cid), ChainOpts: opts.ChainOpts, KeyStore: opts.KeyStore, } privOpts.Logger.Infow(fmt.Sprintf("Loading chain %s", cid), "evmChainID", cid) - chain, err2 := evmchain.NewTOMLChain(ctx, enabled[i], privOpts) + chain, err2 := legacyevm.NewTOMLChain(ctx, enabled[i], privOpts) if err2 != nil { err = multierr.Combine(err, fmt.Errorf("failed to create chain %s: %w", cid, err2)) continue diff --git a/core/services/relay/evm/relayer_extender_test.go b/core/services/relay/evm/relayer_extender_test.go index 361a7468f30..3f4a3749ac8 100644 --- a/core/services/relay/evm/relayer_extender_test.go +++ b/core/services/relay/evm/relayer_extender_test.go @@ -12,7 +12,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" diff --git a/core/services/relay/evm/request_round_tracker.go b/core/services/relay/evm/request_round_tracker.go index 3507f1dfa87..1f1ed71fc31 100644 --- a/core/services/relay/evm/request_round_tracker.go +++ b/core/services/relay/evm/request_round_tracker.go @@ -8,9 +8,13 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" + + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/sqlx" + + "github.com/smartcontractkit/chainlink-common/pkg/services" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" @@ -18,12 +22,11 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) // RequestRoundTracker subscribes to new request round logs. type RequestRoundTracker struct { - utils.StartStopOnce + services.StateMachine ethClient evmclient.Client contract *offchain_aggregator_wrapper.OffchainAggregator diff --git a/core/services/relay/evm/request_round_tracker_test.go b/core/services/relay/evm/request_round_tracker_test.go index b9f38d54ba3..cb2ee2a8d72 100644 --- a/core/services/relay/evm/request_round_tracker_test.go +++ b/core/services/relay/evm/request_round_tracker_test.go @@ -7,12 +7,13 @@ import ( 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/offchainreporting2plus/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + commonmocks "github.com/smartcontractkit/chainlink/v2/common/mocks" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" @@ -21,7 +22,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" offchain_aggregator_wrapper "github.com/smartcontractkit/chainlink/v2/core/internal/gethwrappers2/generated/offchainaggregator" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/services/relay/evm/types/types.go b/core/services/relay/evm/types/types.go index 6a1e66bd096..d2edef8b118 100644 --- a/core/services/relay/evm/types/types.go +++ b/core/services/relay/evm/types/types.go @@ -13,9 +13,9 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/chainlink-relay/pkg/services" - "github.com/smartcontractkit/chainlink-relay/pkg/types" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/types" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -36,7 +36,7 @@ type RelayConfig struct { type RelayOpts struct { // TODO BCF-2508 -- should anyone ever get the raw config bytes that are embedded in args? if not, // make this private and wrap the arg fields with funcs on RelayOpts - relaytypes.RelayArgs + commontypes.RelayArgs c *RelayConfig } @@ -71,9 +71,9 @@ type ConfigPoller interface { Replay(ctx context.Context, fromBlock int64) error } -// TODO(FUN-668): Migrate this fully into relaytypes.FunctionsProvider +// TODO(FUN-668): Migrate this fully into commontypes.FunctionsProvider type FunctionsProvider interface { - relaytypes.FunctionsProvider + commontypes.FunctionsProvider LogPollerWrapper() LogPollerWrapper } diff --git a/core/services/relay/grpc_provider_server.go b/core/services/relay/grpc_provider_server.go new file mode 100644 index 00000000000..67bbb8c6c2a --- /dev/null +++ b/core/services/relay/grpc_provider_server.go @@ -0,0 +1,68 @@ +package relay + +import ( + "context" + "net" + + "go.uber.org/multierr" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +type ProviderServer struct { + s *grpc.Server + lis net.Listener + lggr logger.Logger + conns []*grpc.ClientConn +} + +func (p *ProviderServer) Start(ctx context.Context) error { + p.serve() + return nil +} + +func (p *ProviderServer) Close() error { + var err error + for _, c := range p.conns { + err = multierr.Combine(err, c.Close()) + } + p.s.Stop() + return err +} + +func (p *ProviderServer) GetConn() (*grpc.ClientConn, error) { + cc, err := grpc.Dial(p.lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) + p.conns = append(p.conns, cc) + return cc, err +} + +// NewProviderServer creates a GRPC server that will wrap a provider, this is a workaround to test the Node API PoC until the EVM relayer is loopifyed +func NewProviderServer(p types.PluginProvider, pType types.OCR2PluginType, lggr logger.Logger) (*ProviderServer, error) { + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + ps := ProviderServer{ + s: grpc.NewServer(), + lis: lis, + lggr: lggr.Named("EVM.ProviderServer"), + } + err = loop.RegisterStandAloneProvider(ps.s, p, pType) + if err != nil { + return nil, err + } + + return &ps, nil +} + +func (p *ProviderServer) serve() { + go func() { + if err := p.s.Serve(p.lis); err != nil { + p.lggr.Errorf("Failed to serve EVM provider server: %v", err) + } + }() +} diff --git a/core/services/relay/grpc_provider_server_test.go b/core/services/relay/grpc_provider_server_test.go new file mode 100644 index 00000000000..6aff32f5e32 --- /dev/null +++ b/core/services/relay/grpc_provider_server_test.go @@ -0,0 +1,27 @@ +package relay + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestProviderServer(t *testing.T) { + r := &mockRelayer{} + sa := NewServerAdapter(r, mockRelayerExt{}) + mp, _ := sa.NewPluginProvider(testutils.Context(t), types.RelayArgs{ProviderType: string(types.Median)}, types.PluginArgs{}) + + lggr := logger.TestLogger(t) + _, err := NewProviderServer(mp, "unsupported-type", lggr) + require.Error(t, err) + + ps, err := NewProviderServer(staticMedianProvider{}, types.Median, lggr) + require.NoError(t, err) + + _, err = ps.GetConn() + require.NoError(t, err) +} diff --git a/core/services/relay/relay.go b/core/services/relay/relay.go index 5c7bf5cab57..eb6d3faf4fd 100644 --- a/core/services/relay/relay.go +++ b/core/services/relay/relay.go @@ -5,8 +5,8 @@ import ( "fmt" "regexp" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" ) type Network = string diff --git a/core/services/relay/relay_test.go b/core/services/relay/relay_test.go index d3a94773498..18a7b1b44ea 100644 --- a/core/services/relay/relay_test.go +++ b/core/services/relay/relay_test.go @@ -1,13 +1,16 @@ package relay import ( - "context" "testing" "github.com/stretchr/testify/assert" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" ) func TestIdentifier_UnmarshalString(t *testing.T) { @@ -61,6 +64,30 @@ type staticMedianProvider struct { types.MedianProvider } +func (s staticMedianProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { + return nil +} + +func (s staticMedianProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { + return nil +} + +func (s staticMedianProvider) ContractTransmitter() ocrtypes.ContractTransmitter { + return nil +} + +func (s staticMedianProvider) ReportCodec() median.ReportCodec { + return nil +} + +func (s staticMedianProvider) MedianContract() median.MedianContract { + return nil +} + +func (s staticMedianProvider) OnchainConfigCodec() median.OnchainConfigCodec { + return nil +} + type staticFunctionsProvider struct { types.FunctionsProvider } @@ -133,9 +160,10 @@ func TestRelayerServerAdapter(t *testing.T) { }, } + ctx := testutils.Context(t) for _, tc := range testCases { pp, err := sa.NewPluginProvider( - context.Background(), + ctx, types.RelayArgs{ProviderType: tc.ProviderType}, types.PluginArgs{}, ) diff --git a/core/services/s4/postgres_orm.go b/core/services/s4/postgres_orm.go index d0a79dba959..1f91270fd08 100644 --- a/core/services/s4/postgres_orm.go +++ b/core/services/s4/postgres_orm.go @@ -9,8 +9,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/utils" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" ) const ( diff --git a/core/services/service.go b/core/services/service.go index 066405ac012..19ca1183738 100644 --- a/core/services/service.go +++ b/core/services/service.go @@ -1,7 +1,7 @@ package services import ( - "github.com/smartcontractkit/chainlink-relay/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/services" ) type ServiceCtx = services.Service diff --git a/core/services/synchronization/common.go b/core/services/synchronization/common.go index 32f3a86c6f9..584f5b24380 100644 --- a/core/services/synchronization/common.go +++ b/core/services/synchronization/common.go @@ -22,6 +22,7 @@ const ( OCR3Mercury TelemetryType = "ocr3-mercury" OCR2VRF TelemetryType = "ocr2-vrf" AutomationCustom TelemetryType = "automation-custom" + OCR3Automation TelemetryType = "ocr3-automation" ) type TelemPayload struct { diff --git a/core/services/synchronization/helpers_test.go b/core/services/synchronization/helpers_test.go index 14aaf5a7a02..7bb2dde7633 100644 --- a/core/services/synchronization/helpers_test.go +++ b/core/services/synchronization/helpers_test.go @@ -12,14 +12,14 @@ import ( // NewTestTelemetryIngressClient calls NewTelemetryIngressClient and injects telemClient. func NewTestTelemetryIngressClient(t *testing.T, url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, telemClient telemPb.TelemClient) TelemetryService { - tc := NewTelemetryIngressClient(url, serverPubKeyHex, ks, logging, logger.TestLogger(t), 100) + tc := NewTelemetryIngressClient(url, serverPubKeyHex, ks, logging, logger.TestLogger(t), 100, "test", "test") tc.(*telemetryIngressClient).telemClient = telemClient return tc } // 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, uniconn bool) TelemetryService { - tc := NewTelemetryIngressBatchClient(url, serverPubKeyHex, ks, logging, logger.TestLogger(t), 100, 50, sendInterval, time.Second, uniconn) + tc := NewTelemetryIngressBatchClient(url, serverPubKeyHex, ks, logging, logger.TestLogger(t), 100, 50, sendInterval, time.Second, uniconn, "test", "test") tc.(*telemetryIngressBatchClient).close = func() error { return nil } tc.(*telemetryIngressBatchClient).telemClient = telemClient return tc diff --git a/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go b/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go index 571a725b883..9cda6ef99a1 100644 --- a/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go +++ b/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v3.21.12 -// source: telem_enhanced_ea_mercury.proto +// protoc-gen-go v1.31.0 +// protoc v4.24.3 +// source: core/services/synchronization/telem/telem_enhanced_ea_mercury.proto package telem @@ -25,33 +25,48 @@ type EnhancedEAMercury struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - DataSource string `protobuf:"bytes,1,opt,name=data_source,json=dataSource,proto3" json:"data_source,omitempty"` - DpBenchmarkPrice float64 `protobuf:"fixed64,2,opt,name=dp_benchmark_price,json=dpBenchmarkPrice,proto3" json:"dp_benchmark_price,omitempty"` - DpBid float64 `protobuf:"fixed64,3,opt,name=dp_bid,json=dpBid,proto3" json:"dp_bid,omitempty"` - DpAsk float64 `protobuf:"fixed64,4,opt,name=dp_ask,json=dpAsk,proto3" json:"dp_ask,omitempty"` - CurrentBlockNumber int64 `protobuf:"varint,5,opt,name=current_block_number,json=currentBlockNumber,proto3" json:"current_block_number,omitempty"` - CurrentBlockHash string `protobuf:"bytes,6,opt,name=current_block_hash,json=currentBlockHash,proto3" json:"current_block_hash,omitempty"` - CurrentBlockTimestamp uint64 `protobuf:"varint,7,opt,name=current_block_timestamp,json=currentBlockTimestamp,proto3" json:"current_block_timestamp,omitempty"` - BridgeTaskRunStartedTimestamp int64 `protobuf:"varint,8,opt,name=bridge_task_run_started_timestamp,json=bridgeTaskRunStartedTimestamp,proto3" json:"bridge_task_run_started_timestamp,omitempty"` - BridgeTaskRunEndedTimestamp int64 `protobuf:"varint,9,opt,name=bridge_task_run_ended_timestamp,json=bridgeTaskRunEndedTimestamp,proto3" json:"bridge_task_run_ended_timestamp,omitempty"` - ProviderRequestedTimestamp int64 `protobuf:"varint,10,opt,name=provider_requested_timestamp,json=providerRequestedTimestamp,proto3" json:"provider_requested_timestamp,omitempty"` - ProviderReceivedTimestamp int64 `protobuf:"varint,11,opt,name=provider_received_timestamp,json=providerReceivedTimestamp,proto3" json:"provider_received_timestamp,omitempty"` - ProviderDataStreamEstablished int64 `protobuf:"varint,12,opt,name=provider_data_stream_established,json=providerDataStreamEstablished,proto3" json:"provider_data_stream_established,omitempty"` - ProviderIndicatedTime int64 `protobuf:"varint,13,opt,name=provider_indicated_time,json=providerIndicatedTime,proto3" json:"provider_indicated_time,omitempty"` - Feed string `protobuf:"bytes,14,opt,name=feed,proto3" json:"feed,omitempty"` - ObservationBenchmarkPrice int64 `protobuf:"varint,15,opt,name=observation_benchmark_price,json=observationBenchmarkPrice,proto3" json:"observation_benchmark_price,omitempty"` - ObservationBid int64 `protobuf:"varint,16,opt,name=observation_bid,json=observationBid,proto3" json:"observation_bid,omitempty"` - ObservationAsk int64 `protobuf:"varint,17,opt,name=observation_ask,json=observationAsk,proto3" json:"observation_ask,omitempty"` - ConfigDigest string `protobuf:"bytes,18,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` - Round int64 `protobuf:"varint,19,opt,name=round,proto3" json:"round,omitempty"` - Epoch int64 `protobuf:"varint,20,opt,name=epoch,proto3" json:"epoch,omitempty"` - AssetSymbol string `protobuf:"bytes,21,opt,name=asset_symbol,json=assetSymbol,proto3" json:"asset_symbol,omitempty"` + Version uint32 `protobuf:"varint,32,opt,name=version,proto3" json:"version,omitempty"` + DataSource string `protobuf:"bytes,1,opt,name=data_source,json=dataSource,proto3" json:"data_source,omitempty"` + DpBenchmarkPrice float64 `protobuf:"fixed64,2,opt,name=dp_benchmark_price,json=dpBenchmarkPrice,proto3" json:"dp_benchmark_price,omitempty"` + DpBid float64 `protobuf:"fixed64,3,opt,name=dp_bid,json=dpBid,proto3" json:"dp_bid,omitempty"` + DpAsk float64 `protobuf:"fixed64,4,opt,name=dp_ask,json=dpAsk,proto3" json:"dp_ask,omitempty"` + // v1 fields (block range) + CurrentBlockNumber int64 `protobuf:"varint,5,opt,name=current_block_number,json=currentBlockNumber,proto3" json:"current_block_number,omitempty"` + CurrentBlockHash string `protobuf:"bytes,6,opt,name=current_block_hash,json=currentBlockHash,proto3" json:"current_block_hash,omitempty"` + CurrentBlockTimestamp uint64 `protobuf:"varint,7,opt,name=current_block_timestamp,json=currentBlockTimestamp,proto3" json:"current_block_timestamp,omitempty"` + // v2+v3 fields (timestamp range) + FetchMaxFinalizedTimestamp bool `protobuf:"varint,25,opt,name=fetch_max_finalized_timestamp,json=fetchMaxFinalizedTimestamp,proto3" json:"fetch_max_finalized_timestamp,omitempty"` + MaxFinalizedTimestamp int64 `protobuf:"varint,26,opt,name=max_finalized_timestamp,json=maxFinalizedTimestamp,proto3" json:"max_finalized_timestamp,omitempty"` + ObservationTimestamp uint32 `protobuf:"varint,27,opt,name=observation_timestamp,json=observationTimestamp,proto3" json:"observation_timestamp,omitempty"` + IsLinkFeed bool `protobuf:"varint,28,opt,name=is_link_feed,json=isLinkFeed,proto3" json:"is_link_feed,omitempty"` + LinkPrice int64 `protobuf:"varint,29,opt,name=link_price,json=linkPrice,proto3" json:"link_price,omitempty"` + IsNativeFeed bool `protobuf:"varint,30,opt,name=is_native_feed,json=isNativeFeed,proto3" json:"is_native_feed,omitempty"` + NativePrice int64 `protobuf:"varint,31,opt,name=native_price,json=nativePrice,proto3" json:"native_price,omitempty"` + BridgeTaskRunStartedTimestamp int64 `protobuf:"varint,8,opt,name=bridge_task_run_started_timestamp,json=bridgeTaskRunStartedTimestamp,proto3" json:"bridge_task_run_started_timestamp,omitempty"` + BridgeTaskRunEndedTimestamp int64 `protobuf:"varint,9,opt,name=bridge_task_run_ended_timestamp,json=bridgeTaskRunEndedTimestamp,proto3" json:"bridge_task_run_ended_timestamp,omitempty"` + ProviderRequestedTimestamp int64 `protobuf:"varint,10,opt,name=provider_requested_timestamp,json=providerRequestedTimestamp,proto3" json:"provider_requested_timestamp,omitempty"` + ProviderReceivedTimestamp int64 `protobuf:"varint,11,opt,name=provider_received_timestamp,json=providerReceivedTimestamp,proto3" json:"provider_received_timestamp,omitempty"` + ProviderDataStreamEstablished int64 `protobuf:"varint,12,opt,name=provider_data_stream_established,json=providerDataStreamEstablished,proto3" json:"provider_data_stream_established,omitempty"` + ProviderIndicatedTime int64 `protobuf:"varint,13,opt,name=provider_indicated_time,json=providerIndicatedTime,proto3" json:"provider_indicated_time,omitempty"` + Feed string `protobuf:"bytes,14,opt,name=feed,proto3" json:"feed,omitempty"` + // v1+v2+v3 + ObservationBenchmarkPrice int64 `protobuf:"varint,15,opt,name=observation_benchmark_price,json=observationBenchmarkPrice,proto3" json:"observation_benchmark_price,omitempty"` // This value overflows, will be reserved and removed in future versions + ObservationBenchmarkPriceString string `protobuf:"bytes,22,opt,name=observation_benchmark_price_string,json=observationBenchmarkPriceString,proto3" json:"observation_benchmark_price_string,omitempty"` + // v1+v3 + ObservationBid int64 `protobuf:"varint,16,opt,name=observation_bid,json=observationBid,proto3" json:"observation_bid,omitempty"` // This value overflows, will be reserved and removed in future versions + ObservationAsk int64 `protobuf:"varint,17,opt,name=observation_ask,json=observationAsk,proto3" json:"observation_ask,omitempty"` // This value overflows, will be reserved and removed in future versions + ObservationBidString string `protobuf:"bytes,23,opt,name=observation_bid_string,json=observationBidString,proto3" json:"observation_bid_string,omitempty"` + ObservationAskString string `protobuf:"bytes,24,opt,name=observation_ask_string,json=observationAskString,proto3" json:"observation_ask_string,omitempty"` + ConfigDigest string `protobuf:"bytes,18,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` + Round int64 `protobuf:"varint,19,opt,name=round,proto3" json:"round,omitempty"` + Epoch int64 `protobuf:"varint,20,opt,name=epoch,proto3" json:"epoch,omitempty"` + AssetSymbol string `protobuf:"bytes,21,opt,name=asset_symbol,json=assetSymbol,proto3" json:"asset_symbol,omitempty"` } func (x *EnhancedEAMercury) Reset() { *x = EnhancedEAMercury{} if protoimpl.UnsafeEnabled { - mi := &file_telem_enhanced_ea_mercury_proto_msgTypes[0] + mi := &file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -64,7 +79,7 @@ func (x *EnhancedEAMercury) String() string { func (*EnhancedEAMercury) ProtoMessage() {} func (x *EnhancedEAMercury) ProtoReflect() protoreflect.Message { - mi := &file_telem_enhanced_ea_mercury_proto_msgTypes[0] + mi := &file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -77,7 +92,14 @@ func (x *EnhancedEAMercury) ProtoReflect() protoreflect.Message { // Deprecated: Use EnhancedEAMercury.ProtoReflect.Descriptor instead. func (*EnhancedEAMercury) Descriptor() ([]byte, []int) { - return file_telem_enhanced_ea_mercury_proto_rawDescGZIP(), []int{0} + return file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescGZIP(), []int{0} +} + +func (x *EnhancedEAMercury) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 } func (x *EnhancedEAMercury) GetDataSource() string { @@ -129,6 +151,55 @@ func (x *EnhancedEAMercury) GetCurrentBlockTimestamp() uint64 { return 0 } +func (x *EnhancedEAMercury) GetFetchMaxFinalizedTimestamp() bool { + if x != nil { + return x.FetchMaxFinalizedTimestamp + } + return false +} + +func (x *EnhancedEAMercury) GetMaxFinalizedTimestamp() int64 { + if x != nil { + return x.MaxFinalizedTimestamp + } + return 0 +} + +func (x *EnhancedEAMercury) GetObservationTimestamp() uint32 { + if x != nil { + return x.ObservationTimestamp + } + return 0 +} + +func (x *EnhancedEAMercury) GetIsLinkFeed() bool { + if x != nil { + return x.IsLinkFeed + } + return false +} + +func (x *EnhancedEAMercury) GetLinkPrice() int64 { + if x != nil { + return x.LinkPrice + } + return 0 +} + +func (x *EnhancedEAMercury) GetIsNativeFeed() bool { + if x != nil { + return x.IsNativeFeed + } + return false +} + +func (x *EnhancedEAMercury) GetNativePrice() int64 { + if x != nil { + return x.NativePrice + } + return 0 +} + func (x *EnhancedEAMercury) GetBridgeTaskRunStartedTimestamp() int64 { if x != nil { return x.BridgeTaskRunStartedTimestamp @@ -185,6 +256,13 @@ func (x *EnhancedEAMercury) GetObservationBenchmarkPrice() int64 { return 0 } +func (x *EnhancedEAMercury) GetObservationBenchmarkPriceString() string { + if x != nil { + return x.ObservationBenchmarkPriceString + } + return "" +} + func (x *EnhancedEAMercury) GetObservationBid() int64 { if x != nil { return x.ObservationBid @@ -199,6 +277,20 @@ func (x *EnhancedEAMercury) GetObservationAsk() int64 { return 0 } +func (x *EnhancedEAMercury) GetObservationBidString() string { + if x != nil { + return x.ObservationBidString + } + return "" +} + +func (x *EnhancedEAMercury) GetObservationAskString() string { + if x != nil { + return x.ObservationAskString + } + return "" +} + func (x *EnhancedEAMercury) GetConfigDigest() string { if x != nil { return x.ConfigDigest @@ -227,98 +319,133 @@ func (x *EnhancedEAMercury) GetAssetSymbol() string { return "" } -var File_telem_enhanced_ea_mercury_proto protoreflect.FileDescriptor - -var file_telem_enhanced_ea_mercury_proto_rawDesc = []byte{ - 0x0a, 0x1f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x5f, 0x65, 0x6e, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x64, - 0x5f, 0x65, 0x61, 0x5f, 0x6d, 0x65, 0x72, 0x63, 0x75, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x05, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x22, 0xd5, 0x07, 0x0a, 0x11, 0x45, 0x6e, 0x68, - 0x61, 0x6e, 0x63, 0x65, 0x64, 0x45, 0x41, 0x4d, 0x65, 0x72, 0x63, 0x75, 0x72, 0x79, 0x12, 0x1f, - 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, - 0x2c, 0x0a, 0x12, 0x64, 0x70, 0x5f, 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x5f, - 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x64, 0x70, 0x42, - 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x15, 0x0a, - 0x06, 0x64, 0x70, 0x5f, 0x62, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x64, - 0x70, 0x42, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x70, 0x5f, 0x61, 0x73, 0x6b, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x64, 0x70, 0x41, 0x73, 0x6b, 0x12, 0x30, 0x0a, 0x14, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, - 0x12, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, - 0x61, 0x73, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x17, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x12, 0x48, 0x0a, 0x21, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, 0x74, 0x61, - 0x73, 0x6b, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1d, - 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x44, 0x0a, - 0x1f, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x72, 0x75, 0x6e, - 0x5f, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x54, 0x61, - 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x12, 0x40, 0x0a, 0x1c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1a, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3e, 0x0a, 0x1b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x47, 0x0a, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x73, - 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x1d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x36, - 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x64, 0x18, 0x0e, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x65, 0x65, 0x64, 0x12, 0x3e, 0x0a, 0x1b, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, - 0x61, 0x72, 0x6b, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x19, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x65, 0x6e, 0x63, - 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x64, 0x18, 0x10, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x42, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x61, 0x73, 0x6b, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x73, 0x6b, 0x12, 0x23, 0x0a, 0x0d, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x12, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x67, 0x65, 0x73, - 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, - 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x21, 0x0a, - 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x15, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, - 0x42, 0x4e, 0x5a, 0x4c, 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, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x72, - 0x65, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x68, - 0x72, 0x6f, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +var File_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto protoreflect.FileDescriptor + +var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDesc = []byte{ + 0x0a, 0x43, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, + 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, + 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x5f, 0x65, 0x6e, 0x68, 0x61, + 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x65, 0x61, 0x5f, 0x6d, 0x65, 0x72, 0x63, 0x75, 0x72, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x22, 0xe2, 0x0b, 0x0a, + 0x11, 0x45, 0x6e, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x45, 0x41, 0x4d, 0x65, 0x72, 0x63, 0x75, + 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x20, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, + 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2c, 0x0a, + 0x12, 0x64, 0x70, 0x5f, 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x5f, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x64, 0x70, 0x42, 0x65, 0x6e, + 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x64, + 0x70, 0x5f, 0x62, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x64, 0x70, 0x42, + 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x70, 0x5f, 0x61, 0x73, 0x6b, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x05, 0x64, 0x70, 0x41, 0x73, 0x6b, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x12, 0x41, 0x0a, 0x1d, 0x66, 0x65, 0x74, 0x63, 0x68, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x66, + 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x19, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x66, 0x65, 0x74, 0x63, 0x68, 0x4d, + 0x61, 0x78, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x12, 0x36, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x69, 0x6e, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, + 0x1a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6d, 0x61, 0x78, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x33, 0x0a, 0x15, + 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6f, 0x62, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x66, 0x65, 0x65, + 0x64, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x6b, 0x46, + 0x65, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x70, 0x72, 0x69, 0x63, + 0x65, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, + 0x66, 0x65, 0x65, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x4e, 0x61, + 0x74, 0x69, 0x76, 0x65, 0x46, 0x65, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x74, 0x69, + 0x76, 0x65, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x21, 0x62, + 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x44, 0x0a, 0x1f, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, + 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, + 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x45, 0x6e, 0x64, + 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x40, 0x0a, 0x1c, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x1a, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3e, 0x0a, + 0x1b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x19, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x63, 0x65, + 0x69, 0x76, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x47, 0x0a, + 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, + 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x44, 0x61, 0x74, 0x61, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x73, 0x74, 0x61, 0x62, + 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x66, 0x65, 0x65, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x65, + 0x65, 0x64, 0x12, 0x3e, 0x0a, 0x1b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x5f, 0x70, 0x72, 0x69, 0x63, + 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x12, 0x4b, 0x0a, 0x22, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x5f, 0x70, 0x72, 0x69, 0x63, + 0x65, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1f, + 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x65, 0x6e, 0x63, 0x68, + 0x6d, 0x61, 0x72, 0x6b, 0x50, 0x72, 0x69, 0x63, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, + 0x27, 0x0a, 0x0f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, + 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x73, 0x6b, 0x18, 0x11, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x73, + 0x6b, 0x12, 0x34, 0x0a, 0x16, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x62, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x17, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x14, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x69, + 0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x16, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x73, 0x6b, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, + 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x12, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x67, 0x65, + 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, + 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x21, + 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x15, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, 0x6f, + 0x6c, 0x42, 0x4e, 0x5a, 0x4c, 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, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, + 0x72, 0x65, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x79, 0x6e, 0x63, + 0x68, 0x72, 0x6f, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x74, 0x65, 0x6c, 0x65, + 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_telem_enhanced_ea_mercury_proto_rawDescOnce sync.Once - file_telem_enhanced_ea_mercury_proto_rawDescData = file_telem_enhanced_ea_mercury_proto_rawDesc + file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescOnce sync.Once + file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescData = file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDesc ) -func file_telem_enhanced_ea_mercury_proto_rawDescGZIP() []byte { - file_telem_enhanced_ea_mercury_proto_rawDescOnce.Do(func() { - file_telem_enhanced_ea_mercury_proto_rawDescData = protoimpl.X.CompressGZIP(file_telem_enhanced_ea_mercury_proto_rawDescData) +func file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescGZIP() []byte { + file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescOnce.Do(func() { + file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescData = protoimpl.X.CompressGZIP(file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescData) }) - return file_telem_enhanced_ea_mercury_proto_rawDescData + return file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescData } -var file_telem_enhanced_ea_mercury_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_telem_enhanced_ea_mercury_proto_goTypes = []interface{}{ +var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_goTypes = []interface{}{ (*EnhancedEAMercury)(nil), // 0: telem.EnhancedEAMercury } -var file_telem_enhanced_ea_mercury_proto_depIdxs = []int32{ +var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name @@ -326,13 +453,13 @@ var file_telem_enhanced_ea_mercury_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for field type_name } -func init() { file_telem_enhanced_ea_mercury_proto_init() } -func file_telem_enhanced_ea_mercury_proto_init() { - if File_telem_enhanced_ea_mercury_proto != nil { +func init() { file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_init() } +func file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_init() { + if File_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_telem_enhanced_ea_mercury_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EnhancedEAMercury); i { case 0: return &v.state @@ -349,18 +476,18 @@ func file_telem_enhanced_ea_mercury_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_telem_enhanced_ea_mercury_proto_rawDesc, + RawDescriptor: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_telem_enhanced_ea_mercury_proto_goTypes, - DependencyIndexes: file_telem_enhanced_ea_mercury_proto_depIdxs, - MessageInfos: file_telem_enhanced_ea_mercury_proto_msgTypes, + GoTypes: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_goTypes, + DependencyIndexes: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_depIdxs, + MessageInfos: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes, }.Build() - File_telem_enhanced_ea_mercury_proto = out.File - file_telem_enhanced_ea_mercury_proto_rawDesc = nil - file_telem_enhanced_ea_mercury_proto_goTypes = nil - file_telem_enhanced_ea_mercury_proto_depIdxs = nil + File_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto = out.File + file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDesc = nil + file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_goTypes = nil + file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_depIdxs = nil } diff --git a/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto b/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto index 805d976d2d6..a527552bb6b 100644 --- a/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto +++ b/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto @@ -5,25 +5,49 @@ option go_package = "github.com/smartcontractkit/chainlink/v2/core/services/sync package telem; message EnhancedEAMercury { + uint32 version = 32; + string data_source=1; double dp_benchmark_price=2; double dp_bid=3; double dp_ask=4; + + // v1 fields (block range) int64 current_block_number=5; string current_block_hash=6; uint64 current_block_timestamp=7; + + // v2+v3 fields (timestamp range) + bool fetch_max_finalized_timestamp = 25; + int64 max_finalized_timestamp=26; + uint32 observation_timestamp=27; + bool is_link_feed=28; + int64 link_price=29; + bool is_native_feed=30; + int64 native_price=31; + int64 bridge_task_run_started_timestamp=8; int64 bridge_task_run_ended_timestamp=9; int64 provider_requested_timestamp=10; int64 provider_received_timestamp=11; int64 provider_data_stream_established=12; int64 provider_indicated_time=13; + string feed=14; - int64 observation_benchmark_price=15; - int64 observation_bid=16; - int64 observation_ask=17; + + // v1+v2+v3 + int64 observation_benchmark_price=15; // This value overflows, will be reserved and removed in future versions + string observation_benchmark_price_string = 22; + // v1+v3 + int64 observation_bid=16; // This value overflows, will be reserved and removed in future versions + int64 observation_ask=17; // This value overflows, will be reserved and removed in future versions + string observation_bid_string = 23; + string observation_ask_string = 24; + string config_digest = 18; int64 round=19; int64 epoch=20; string asset_symbol=21; + + } diff --git a/core/services/synchronization/telemetry_ingress_batch_client.go b/core/services/synchronization/telemetry_ingress_batch_client.go index 4924bb2cd50..b8dbb5e5d37 100644 --- a/core/services/synchronization/telemetry_ingress_batch_client.go +++ b/core/services/synchronization/telemetry_ingress_batch_client.go @@ -12,10 +12,10 @@ import ( "github.com/smartcontractkit/wsrpc" "github.com/smartcontractkit/wsrpc/examples/simple/keys" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" telemPb "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) // NoopTelemetryIngressBatchClient is a no-op interface for TelemetryIngressBatchClient @@ -37,7 +37,7 @@ func (NoopTelemetryIngressBatchClient) Name() string { return func (NoopTelemetryIngressBatchClient) Ready() error { return nil } type telemetryIngressBatchClient struct { - utils.StartStopOnce + services.StateMachine url *url.URL ks keystore.CSA serverPubKeyHex string @@ -51,7 +51,7 @@ type telemetryIngressBatchClient struct { lggr logger.Logger wgDone sync.WaitGroup - chDone chan struct{} + chDone services.StopChan telemBufferSize uint telemMaxBatchSize uint @@ -66,7 +66,7 @@ type telemetryIngressBatchClient struct { // 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, telemSendTimeout time.Duration, useUniconn bool) TelemetryService { +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, network string, chainID string) TelemetryService { return &telemetryIngressBatchClient{ telemBufferSize: telemBufferSize, telemMaxBatchSize: telemMaxBatchSize, @@ -77,8 +77,8 @@ func NewTelemetryIngressBatchClient(url *url.URL, serverPubKeyHex string, ks key serverPubKeyHex: serverPubKeyHex, globalLogger: lggr, logging: logging, - lggr: lggr.Named("TelemetryIngressBatchClient"), - chDone: make(chan struct{}), + lggr: lggr.Named("TelemetryIngressBatchClient").Named(network).Named(chainID), + chDone: make(services.StopChan), workers: make(map[string]*telemetryIngressBatchWorker), useUniConn: useUniconn, } @@ -103,22 +103,24 @@ func (tc *telemetryIngressBatchClient) Start(ctx context.Context) error { // This is used to call RPC methods on the server if tc.telemClient == nil { // only preset for tests if tc.useUniConn { + tc.wgDone.Add(1) 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) + defer tc.wgDone.Done() + ctx2, cancel := tc.chDone.NewCtx() + defer cancel() + conn, err := wsrpc.DialUniWithContext(ctx2, tc.lggr, tc.url.String(), clientPrivKey, serverPubKey) if err != nil { - if ctx.Err() != nil { + if ctx2.Err() != nil { tc.lggr.Warnw("gave up connecting to telemetry endpoint", "err", err) } else { tc.lggr.Criticalw("telemetry endpoint dial errored unexpectedly", "err", err) tc.SvcErrBuffer.Append(err) } - } else { - tc.telemClient = telemPb.NewTelemClient(conn) - tc.close = conn.Close - tc.connected.Store(true) + return } + tc.telemClient = telemPb.NewTelemClient(conn) + tc.close = conn.Close + tc.connected.Store(true) }() } else { // Spawns a goroutine that will eventually connect diff --git a/core/services/synchronization/telemetry_ingress_batch_worker.go b/core/services/synchronization/telemetry_ingress_batch_worker.go index 141eb30c812..e7ea6595811 100644 --- a/core/services/synchronization/telemetry_ingress_batch_worker.go +++ b/core/services/synchronization/telemetry_ingress_batch_worker.go @@ -6,23 +6,23 @@ import ( "sync/atomic" "time" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services" telemPb "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) // telemetryIngressBatchWorker pushes telemetry in batches to the ingress server via wsrpc. // A worker is created per ContractID. type telemetryIngressBatchWorker struct { - services.ServiceCtx + services.Service telemMaxBatchSize uint telemSendInterval time.Duration telemSendTimeout time.Duration telemClient telemPb.TelemClient wgDone *sync.WaitGroup - chDone utils.StopChan + chDone services.StopChan chTelemetry chan TelemPayload contractID string telemType TelemetryType diff --git a/core/services/synchronization/telemetry_ingress_client.go b/core/services/synchronization/telemetry_ingress_client.go index 1db4f69afd8..75aa3106a8c 100644 --- a/core/services/synchronization/telemetry_ingress_client.go +++ b/core/services/synchronization/telemetry_ingress_client.go @@ -11,10 +11,10 @@ import ( "github.com/smartcontractkit/wsrpc" "github.com/smartcontractkit/wsrpc/examples/simple/keys" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" telemPb "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) type NoopTelemetryIngressClient struct{} @@ -35,7 +35,7 @@ func (NoopTelemetryIngressClient) Name() string { return "Noop func (NoopTelemetryIngressClient) Ready() error { return nil } type telemetryIngressClient struct { - utils.StartStopOnce + services.StateMachine url *url.URL ks keystore.CSA serverPubKeyHex string @@ -45,34 +45,34 @@ type telemetryIngressClient struct { lggr logger.Logger wgDone sync.WaitGroup - chDone chan struct{} + chDone services.StopChan dropMessageCount atomic.Uint32 chTelemetry chan TelemPayload } // NewTelemetryIngressClient returns a client backed by wsrpc that // can send telemetry to the telemetry ingress server -func NewTelemetryIngressClient(url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, lggr logger.Logger, telemBufferSize uint) TelemetryService { +func NewTelemetryIngressClient(url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, lggr logger.Logger, telemBufferSize uint, network string, chainID string) TelemetryService { return &telemetryIngressClient{ url: url, ks: ks, serverPubKeyHex: serverPubKeyHex, logging: logging, - lggr: lggr.Named("TelemetryIngressClient"), + lggr: lggr.Named("TelemetryIngressClient").Named(network).Named(chainID), chTelemetry: make(chan TelemPayload, telemBufferSize), - chDone: make(chan struct{}), + chDone: make(services.StopChan), } } // Start connects the wsrpc client to the telemetry ingress server -func (tc *telemetryIngressClient) Start(ctx context.Context) error { +func (tc *telemetryIngressClient) Start(context.Context) error { return tc.StartOnce("TelemetryIngressClient", func() error { privkey, err := tc.getCSAPrivateKey() if err != nil { return err } - tc.connect(ctx, privkey) + tc.connect(privkey) return nil }) @@ -95,14 +95,15 @@ func (tc *telemetryIngressClient) HealthReport() map[string]error { return map[string]error{tc.Name(): tc.Healthy()} } -func (tc *telemetryIngressClient) connect(ctx context.Context, clientPrivKey []byte) { +func (tc *telemetryIngressClient) connect(clientPrivKey []byte) { tc.wgDone.Add(1) go func() { defer tc.wgDone.Done() + ctx, cancel := tc.chDone.NewCtx() + defer cancel() serverPubKey := keys.FromHex(tc.serverPubKeyHex) - conn, err := wsrpc.DialWithContext(ctx, tc.url.String(), wsrpc.WithTransportCreds(clientPrivKey, serverPubKey), wsrpc.WithLogger(tc.lggr)) if err != nil { if ctx.Err() != nil { @@ -111,6 +112,7 @@ func (tc *telemetryIngressClient) connect(ctx context.Context, clientPrivKey []b tc.lggr.Criticalw("telemetry endpoint dial errored unexpectedly", "err", err) tc.SvcErrBuffer.Append(err) } + return } defer conn.Close() @@ -130,8 +132,10 @@ func (tc *telemetryIngressClient) connect(ctx context.Context, clientPrivKey []b } func (tc *telemetryIngressClient) handleTelemetry() { + tc.wgDone.Add(1) go func() { - ctx, cancel := utils.StopChan(tc.chDone).NewCtx() + defer tc.wgDone.Done() + ctx, cancel := tc.chDone.NewCtx() defer cancel() for { select { diff --git a/core/services/synchronization/uni_client_integration_test.go b/core/services/synchronization/uni_client_integration_test.go index 1ad2865669e..fcc0dc23717 100644 --- a/core/services/synchronization/uni_client_integration_test.go +++ b/core/services/synchronization/uni_client_integration_test.go @@ -6,16 +6,17 @@ import ( "testing" "time" - "github.com/smartcontractkit/wsrpc" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/wsrpc" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" ) func TestUniClient(t *testing.T) { - t.Skip() + t.Skip("Incomplete", "https://smartcontract-it.atlassian.net/browse/BCF-2729") privKey, err := hex.DecodeString("TODO") require.NoError(t, err) pubKey, err := hex.DecodeString("TODO") diff --git a/core/services/telemetry/common.go b/core/services/telemetry/common.go index 5a3f6706f7d..37a92f16c6d 100644 --- a/core/services/telemetry/common.go +++ b/core/services/telemetry/common.go @@ -7,5 +7,5 @@ import ( ) type MonitoringEndpointGenerator interface { - GenMonitoringEndpoint(contractID string, telemType synchronization.TelemetryType, network string, chainID string) ocrtypes.MonitoringEndpoint + GenMonitoringEndpoint(network string, chainID string, contractID string, telemType synchronization.TelemetryType) ocrtypes.MonitoringEndpoint } diff --git a/core/services/telemetry/ingress.go b/core/services/telemetry/ingress.go index 637fa0dd3ba..266155095bf 100644 --- a/core/services/telemetry/ingress.go +++ b/core/services/telemetry/ingress.go @@ -18,25 +18,25 @@ func NewIngressAgentWrapper(telemetryIngressClient synchronization.TelemetryServ return &IngressAgentWrapper{telemetryIngressClient} } -func (t *IngressAgentWrapper) GenMonitoringEndpoint(contractID string, telemType synchronization.TelemetryType, network string, chainID string) ocrtypes.MonitoringEndpoint { - return NewIngressAgent(t.telemetryIngressClient, contractID, telemType, network, chainID) +func (t *IngressAgentWrapper) GenMonitoringEndpoint(network, chainID string, contractID string, telemType synchronization.TelemetryType) ocrtypes.MonitoringEndpoint { + return NewIngressAgent(t.telemetryIngressClient, network, chainID, contractID, telemType) } type IngressAgent struct { telemetryIngressClient synchronization.TelemetryService - contractID string - telemType synchronization.TelemetryType network string chainID string + contractID string + telemType synchronization.TelemetryType } -func NewIngressAgent(telemetryIngressClient synchronization.TelemetryService, contractID string, telemType synchronization.TelemetryType, network string, chainID string) *IngressAgent { +func NewIngressAgent(telemetryIngressClient synchronization.TelemetryService, network string, chainID string, contractID string, telemType synchronization.TelemetryType) *IngressAgent { return &IngressAgent{ telemetryIngressClient, - contractID, - telemType, network, chainID, + contractID, + telemType, } } diff --git a/core/services/telemetry/ingress_batch.go b/core/services/telemetry/ingress_batch.go index df860853592..bb08c76d7e2 100644 --- a/core/services/telemetry/ingress_batch.go +++ b/core/services/telemetry/ingress_batch.go @@ -21,27 +21,27 @@ func NewIngressAgentBatchWrapper(telemetryIngressBatchClient synchronization.Tel } // GenMonitoringEndpoint returns a new ingress batch agent instantiated with the batch client and a contractID -func (t *IngressAgentBatchWrapper) GenMonitoringEndpoint(contractID string, telemType synchronization.TelemetryType, network string, chainID string) ocrtypes.MonitoringEndpoint { - return NewIngressAgentBatch(t.telemetryIngressBatchClient, contractID, telemType, network, chainID) +func (t *IngressAgentBatchWrapper) GenMonitoringEndpoint(network string, chainID string, contractID string, telemType synchronization.TelemetryType) ocrtypes.MonitoringEndpoint { + return NewIngressAgentBatch(t.telemetryIngressBatchClient, network, chainID, contractID, telemType) } // IngressAgentBatch allows for sending batch telemetry for a given contractID type IngressAgentBatch struct { telemetryIngressBatchClient synchronization.TelemetryService - contractID string - telemType synchronization.TelemetryType network string chainID string + contractID string + telemType synchronization.TelemetryType } // NewIngressAgentBatch creates a new IngressAgentBatch with the given batch client and contractID -func NewIngressAgentBatch(telemetryIngressBatchClient synchronization.TelemetryService, contractID string, telemType synchronization.TelemetryType, network string, chainID string) *IngressAgentBatch { +func NewIngressAgentBatch(telemetryIngressBatchClient synchronization.TelemetryService, network string, chainID string, contractID string, telemType synchronization.TelemetryType) *IngressAgentBatch { return &IngressAgentBatch{ telemetryIngressBatchClient, - contractID, - telemType, network, chainID, + contractID, + telemType, } } diff --git a/core/services/telemetry/ingress_batch_test.go b/core/services/telemetry/ingress_batch_test.go index 3923b569fed..91e6a07ad7f 100644 --- a/core/services/telemetry/ingress_batch_test.go +++ b/core/services/telemetry/ingress_batch_test.go @@ -14,7 +14,7 @@ import ( func TestIngressAgentBatch(t *testing.T) { telemetryBatchClient := mocks.NewTelemetryService(t) ingressAgentBatch := telemetry.NewIngressAgentWrapper(telemetryBatchClient) - monitoringEndpoint := ingressAgentBatch.GenMonitoringEndpoint("0xa", synchronization.OCR, "test-network", "test-chainID") + monitoringEndpoint := ingressAgentBatch.GenMonitoringEndpoint("test-network", "test-chainID", "0xa", synchronization.OCR) // Handle the Send call and store the telem var telemPayload synchronization.TelemPayload diff --git a/core/services/telemetry/ingress_test.go b/core/services/telemetry/ingress_test.go index 31028f2f605..7e83384dc6c 100644 --- a/core/services/telemetry/ingress_test.go +++ b/core/services/telemetry/ingress_test.go @@ -14,7 +14,7 @@ import ( func TestIngressAgent(t *testing.T) { telemetryClient := mocks.NewTelemetryService(t) ingressAgent := telemetry.NewIngressAgentWrapper(telemetryClient) - monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("0xa", synchronization.OCR, "test-network", "test-chainID") + monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("test-network", "test-chainID", "0xa", synchronization.OCR) // Handle the Send call and store the telem var telemPayload synchronization.TelemPayload diff --git a/core/services/telemetry/manager.go b/core/services/telemetry/manager.go index 3818341f5b1..c457aca5bf8 100644 --- a/core/services/telemetry/manager.go +++ b/core/services/telemetry/manager.go @@ -2,20 +2,20 @@ package telemetry import ( "context" - "fmt" "net/url" "strings" "time" "github.com/pkg/errors" - "github.com/smartcontractkit/libocr/commontypes" "go.uber.org/multierr" + "github.com/smartcontractkit/libocr/commontypes" + + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) //// Client encapsulates all the functionality needed to @@ -26,7 +26,7 @@ import ( //} type Manager struct { - utils.StartStopOnce + services.StateMachine bufferSize uint endpoints []*telemetryEndpoint ks keystore.CSA @@ -67,7 +67,6 @@ func (l *legacyEndpointConfig) URL() *url.URL { } type telemetryEndpoint struct { - utils.StartStopOnce ChainID string Network string URL *url.URL @@ -139,17 +138,16 @@ func (m *Manager) Name() string { } func (m *Manager) HealthReport() map[string]error { - hr := make(map[string]error) - hr[m.lggr.Name()] = m.Healthy() + hr := map[string]error{m.Name(): m.Healthy()} + for _, e := range m.endpoints { - name := fmt.Sprintf("%s.%s.%s", m.lggr.Name(), e.Network, e.ChainID) - hr[name] = e.StartStopOnce.Healthy() + services.CopyHealth(hr, e.client.HealthReport()) } return hr } // GenMonitoringEndpoint creates a new monitoring endpoints based on the existing available endpoints defined in the core config TOML, if no endpoint for the network and chainID exists, a NOOP agent will be used and the telemetry will not be sent -func (m *Manager) GenMonitoringEndpoint(contractID string, telemType synchronization.TelemetryType, network string, chainID string) commontypes.MonitoringEndpoint { +func (m *Manager) GenMonitoringEndpoint(network string, chainID string, contractID string, telemType synchronization.TelemetryType) commontypes.MonitoringEndpoint { e, found := m.getEndpoint(network, chainID) @@ -159,10 +157,10 @@ func (m *Manager) GenMonitoringEndpoint(contractID string, telemType synchroniza } if m.useBatchSend { - return NewIngressAgentBatch(e.client, contractID, telemType, network, chainID) + return NewIngressAgentBatch(e.client, network, chainID, contractID, telemType) } - return NewIngressAgent(e.client, contractID, telemType, network, chainID) + return NewIngressAgent(e.client, network, chainID, contractID, telemType) } @@ -189,9 +187,9 @@ func (m *Manager) addEndpoint(e config.TelemetryIngressEndpoint) error { var tClient synchronization.TelemetryService if m.useBatchSend { - tClient = synchronization.NewTelemetryIngressBatchClient(e.URL(), e.ServerPubKey(), m.ks, m.logging, m.lggr, m.bufferSize, m.maxBatchSize, m.sendInterval, m.sendTimeout, m.uniConn) + tClient = synchronization.NewTelemetryIngressBatchClient(e.URL(), e.ServerPubKey(), m.ks, m.logging, m.lggr, m.bufferSize, m.maxBatchSize, m.sendInterval, m.sendTimeout, m.uniConn, e.Network(), e.ChainID()) } else { - tClient = synchronization.NewTelemetryIngressClient(e.URL(), e.ServerPubKey(), m.ks, m.logging, m.lggr, m.bufferSize) + tClient = synchronization.NewTelemetryIngressClient(e.URL(), e.ServerPubKey(), m.ks, m.logging, m.lggr, m.bufferSize, e.Network(), e.ChainID()) } te := telemetryEndpoint{ diff --git a/core/services/telemetry/manager_test.go b/core/services/telemetry/manager_test.go index 4aaf3280155..8564be8466f 100644 --- a/core/services/telemetry/manager_test.go +++ b/core/services/telemetry/manager_test.go @@ -1,7 +1,6 @@ package telemetry import ( - "context" "fmt" "math/big" "net/url" @@ -24,7 +23,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" mocks2 "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/mocks" "github.com/smartcontractkit/chainlink/v2/core/store/models" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) func setupMockConfig(t *testing.T, useBatchSend bool) *mocks.TelemetryIngress { @@ -56,14 +54,14 @@ func TestManagerAgents(t *testing.T) { tm := NewManager(tic, ks, lggr) require.Equal(t, "*synchronization.telemetryIngressBatchClient", reflect.TypeOf(tm.endpoints[0].client).String()) - me := tm.GenMonitoringEndpoint("", "", "network-1", "network-1-chainID-1") + me := tm.GenMonitoringEndpoint("network-1", "network-1-chainID-1", "", "") require.Equal(t, "*telemetry.IngressAgentBatch", reflect.TypeOf(me).String()) tic = setupMockConfig(t, false) tic.On("Endpoints").Return([]config.TelemetryIngressEndpoint{te}) tm = NewManager(tic, ks, lggr) require.Equal(t, "*synchronization.telemetryIngressClient", reflect.TypeOf(tm.endpoints[0].client).String()) - me = tm.GenMonitoringEndpoint("", "", "network-1", "network-1-chainID-1") + me = tm.GenMonitoringEndpoint("network-1", "network-1-chainID-1", "", "") require.Equal(t, "*telemetry.IngressAgent", reflect.TypeOf(me).String()) } @@ -190,13 +188,14 @@ func TestNewManager(t *testing.T) { require.Equal(t, "TelemetryManager", m.Name()) - require.Nil(t, m.Start(context.Background())) + require.Nil(t, m.Start(testutils.Context(t))) + t.Cleanup(func() { + require.NoError(t, m.Close()) + }) testutils.WaitForLogMessageCount(t, logObs, "error connecting error while dialing dial tcp", 3) hr := m.HealthReport() require.Equal(t, 4, len(hr)) - require.Nil(t, m.Close()) - time.Sleep(time.Second * 1) } func TestCorrectEndpointRouting(t *testing.T) { @@ -246,25 +245,24 @@ func TestCorrectEndpointRouting(t *testing.T) { }) tm.endpoints[i] = &telemetryEndpoint{ - StartStopOnce: utils.StartStopOnce{}, - ChainID: e.chainID, - Network: e.network, - client: clientMock, + ChainID: e.chainID, + Network: e.network, + client: clientMock, } } //Unknown networks or chainID - noopEndpoint := tm.GenMonitoringEndpoint("some-contractID", "some-type", "unknown-network", "unknown-chainID") + noopEndpoint := tm.GenMonitoringEndpoint("unknown-network", "unknown-chainID", "some-contractID", "some-type") require.Equal(t, "*telemetry.NoopAgent", reflect.TypeOf(noopEndpoint).String()) require.Equal(t, 1, obsLogs.Len()) require.Contains(t, obsLogs.TakeAll()[0].Message, "no telemetry endpoint found") - noopEndpoint = tm.GenMonitoringEndpoint("some-contractID", "some-type", "network-1", "unknown-chainID") + noopEndpoint = tm.GenMonitoringEndpoint("network-1", "unknown-chainID", "some-contractID", "some-type") require.Equal(t, "*telemetry.NoopAgent", reflect.TypeOf(noopEndpoint).String()) require.Equal(t, 1, obsLogs.Len()) require.Contains(t, obsLogs.TakeAll()[0].Message, "no telemetry endpoint found") - noopEndpoint = tm.GenMonitoringEndpoint("some-contractID", "some-type", "network-2", "network-1-chainID-1") + noopEndpoint = tm.GenMonitoringEndpoint("network-2", "network-1-chainID-1", "some-contractID", "some-type") require.Equal(t, "*telemetry.NoopAgent", reflect.TypeOf(noopEndpoint).String()) require.Equal(t, 1, obsLogs.Len()) require.Contains(t, obsLogs.TakeAll()[0].Message, "no telemetry endpoint found") @@ -274,10 +272,10 @@ func TestCorrectEndpointRouting(t *testing.T) { telemType := fmt.Sprintf("TelemType_%s", e.chainID) contractID := fmt.Sprintf("contractID_%s", e.chainID) me := tm.GenMonitoringEndpoint( - contractID, - synchronization.TelemetryType(telemType), e.network, e.chainID, + contractID, + synchronization.TelemetryType(telemType), ) me.SendLog([]byte(e.chainID)) require.Equal(t, 0, obsLogs.Len()) @@ -316,7 +314,7 @@ func TestLegacyMode(t *testing.T) { }) tm.endpoints[0].client = clientMock - e := tm.GenMonitoringEndpoint("some-contractID", "some-type", "unknown-network", "unknown-chainID") + e := tm.GenMonitoringEndpoint("unknown-network", "unknown-chainID", "some-contractID", "some-type") require.Equal(t, "*telemetry.IngressAgentBatch", reflect.TypeOf(e).String()) e.SendLog([]byte("endpoint-1-message-1")) @@ -324,7 +322,7 @@ func TestLegacyMode(t *testing.T) { e.SendLog([]byte("endpoint-1-message-3")) require.Len(t, clientSent, 3) - e2 := tm.GenMonitoringEndpoint("another-contractID", "another-type", "another-unknown-network", "another-unknown-chainID") + e2 := tm.GenMonitoringEndpoint("another-unknown-network", "another-unknown-chainID", "another-contractID", "another-type") require.Equal(t, "*telemetry.IngressAgentBatch", reflect.TypeOf(e).String()) e2.SendLog([]byte("endpoint-2-message-1")) diff --git a/core/services/telemetry/noop.go b/core/services/telemetry/noop.go index cbeb0387089..4da8868c8f0 100644 --- a/core/services/telemetry/noop.go +++ b/core/services/telemetry/noop.go @@ -16,6 +16,6 @@ func (t *NoopAgent) SendLog(log []byte) { } // GenMonitoringEndpoint creates a monitoring endpoint for telemetry -func (t *NoopAgent) GenMonitoringEndpoint(contractID string, telemType synchronization.TelemetryType, network string, chainID string) ocrtypes.MonitoringEndpoint { +func (t *NoopAgent) GenMonitoringEndpoint(network string, chainID string, contractID string, telemType synchronization.TelemetryType) ocrtypes.MonitoringEndpoint { return t } diff --git a/core/services/transmission/integration_test.go b/core/services/transmission/integration_test.go index 0484b1d8cd6..c8c6137cad7 100644 --- a/core/services/transmission/integration_test.go +++ b/core/services/transmission/integration_test.go @@ -1,7 +1,6 @@ package transmission_test import ( - "context" "math/big" "testing" @@ -12,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_v3_aggregator_contract" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_consumer_interface_v08" @@ -398,7 +397,7 @@ func Test4337WithLinkTokenVRFRequestAndPaymaster(t *testing.T) { ) require.NoError(t, err) backend.Commit() - _, err = bind.WaitMined(context.Background(), backend, tx) + _, err = bind.WaitMined(testutils.Context(t), backend, tx) require.NoError(t, err) // Generate encoded paymaster data to fund the VRF consumer. diff --git a/core/services/versioning/orm.go b/core/services/versioning/orm.go index 03bd64fdd2b..8ed745955dc 100644 --- a/core/services/versioning/orm.go +++ b/core/services/versioning/orm.go @@ -7,8 +7,8 @@ import ( "github.com/Masterminds/semver/v3" "github.com/jackc/pgconn" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" diff --git a/core/services/vrf/delegate.go b/core/services/vrf/delegate.go index f6b6a460b89..a13df71d9a3 100644 --- a/core/services/vrf/delegate.go +++ b/core/services/vrf/delegate.go @@ -1,10 +1,7 @@ package vrf import ( - "encoding/hex" "fmt" - "math/big" - "strings" "time" "github.com/avast/retry-go/v4" @@ -13,11 +10,11 @@ import ( "github.com/theodesp/go-heaps/pairing" "go.uber.org/multierr" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/aggregator_v3_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_vrf_coordinator_v2" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_coordinator_interface" @@ -40,7 +37,7 @@ type Delegate struct { pr pipeline.Runner porm pipeline.ORM ks keystore.Master - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer lggr logger.Logger mailMon *utils.MailboxMonitor } @@ -50,7 +47,7 @@ func NewDelegate( ks keystore.Master, pr pipeline.Runner, porm pipeline.ORM, - legacyChains evm.LegacyChainContainer, + legacyChains legacyevm.LegacyChainContainer, lggr logger.Logger, cfg pg.QConfig, mailMon *utils.MailboxMonitor) *Delegate { @@ -69,10 +66,10 @@ func (d *Delegate) JobType() job.Type { return job.VRF } -func (d *Delegate) BeforeJobCreated(spec job.Job) {} -func (d *Delegate) AfterJobCreated(spec job.Job) {} -func (d *Delegate) BeforeJobDeleted(spec job.Job) {} -func (d *Delegate) OnDeleteJob(spec job.Job, q pg.Queryer) error { return nil } +func (d *Delegate) BeforeJobCreated(job.Job) {} +func (d *Delegate) AfterJobCreated(job.Job) {} +func (d *Delegate) BeforeJobDeleted(job.Job) {} +func (d *Delegate) OnDeleteJob(job.Job, pg.Queryer) error { return nil } // ServicesForSpec satisfies the job.Delegate interface. func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { @@ -87,7 +84,6 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { if err != nil { return nil, err } - chainId := chain.Client().ConfiguredChainID() coordinator, err := solidity_vrf_coordinator_interface.NewVRFCoordinator(jb.VRFSpec.CoordinatorAddress.Address(), chain.Client()) if err != nil { return nil, err @@ -164,28 +160,28 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { return nil, errors.Wrap(err2, "NewAggregatorV3Interface") } - return []job.ServiceCtx{v2.New( - chain.Config().EVM(), - chain.Config().EVM().GasEstimator(), - lV2Plus, - chain.Client(), - chain.ID(), - chain.LogBroadcaster(), - d.q, - v2.NewCoordinatorV2_5(coordinatorV2Plus), - batchCoordinatorV2, - vrfOwner, - aggregator, - chain.TxManager(), - d.pr, - d.ks.Eth(), - jb, - d.mailMon, - utils.NewHighCapacityMailbox[log.Broadcast](), - func() {}, - GetStartingResponseCountsV2(d.q, lV2Plus, chainId.Uint64(), chain.Config().EVM().FinalityDepth()), - chain.HeadBroadcaster(), - vrfcommon.NewLogDeduper(int(chain.Config().EVM().FinalityDepth())))}, nil + return []job.ServiceCtx{ + v2.New( + chain.Config().EVM(), + chain.Config().EVM().GasEstimator(), + lV2Plus, + chain, + chain.ID(), + d.q, + v2.NewCoordinatorV2_5(coordinatorV2Plus), + batchCoordinatorV2, + vrfOwner, + aggregator, + d.pr, + d.ks.Eth(), + jb, + func() {}, + // the lookback in the deduper must be >= the lookback specified for the log poller + // otherwise we will end up re-delivering logs that were already delivered. + vrfcommon.NewInflightCache(int(chain.Config().EVM().FinalityDepth())), + vrfcommon.NewLogDeduper(int(chain.Config().EVM().FinalityDepth())), + ), + }, nil } if _, ok := task.(*pipeline.VRFTaskV2); ok { if err2 := CheckFromAddressesExist(jb, d.ks.Eth()); err != nil { @@ -223,49 +219,45 @@ func (d *Delegate) ServicesForSpec(jb job.Job) ([]job.ServiceCtx, error) { chain.Config().EVM(), chain.Config().EVM().GasEstimator(), lV2, - chain.Client(), + chain, chain.ID(), - chain.LogBroadcaster(), d.q, v2.NewCoordinatorV2(coordinatorV2), batchCoordinatorV2, vrfOwner, aggregator, - chain.TxManager(), d.pr, d.ks.Eth(), jb, - d.mailMon, - utils.NewHighCapacityMailbox[log.Broadcast](), func() {}, - GetStartingResponseCountsV2(d.q, lV2, chainId.Uint64(), chain.Config().EVM().FinalityDepth()), - chain.HeadBroadcaster(), - vrfcommon.NewLogDeduper(int(chain.Config().EVM().FinalityDepth())))}, nil + // the lookback in the deduper must be >= the lookback specified for the log poller + // otherwise we will end up re-delivering logs that were already delivered. + vrfcommon.NewInflightCache(int(chain.Config().EVM().FinalityDepth())), + vrfcommon.NewLogDeduper(int(chain.Config().EVM().FinalityDepth())), + ), + }, nil } if _, ok := task.(*pipeline.VRFTask); ok { return []job.ServiceCtx{&v1.Listener{ - Cfg: chain.Config().EVM(), - FeeCfg: chain.Config().EVM().GasEstimator(), - L: logger.Sugared(lV1), - HeadBroadcaster: chain.HeadBroadcaster(), - LogBroadcaster: chain.LogBroadcaster(), - Q: d.q, - Txm: chain.TxManager(), - Coordinator: coordinator, - PipelineRunner: d.pr, - GethKs: d.ks.Eth(), - Job: jb, - MailMon: d.mailMon, + Cfg: chain.Config().EVM(), + FeeCfg: chain.Config().EVM().GasEstimator(), + L: logger.Sugared(lV1), + Q: d.q, + Coordinator: coordinator, + PipelineRunner: d.pr, + GethKs: d.ks.Eth(), + Job: jb, + MailMon: d.mailMon, // Note the mailbox size effectively sets a limit on how many logs we can replay // in the event of a VRF outage. ReqLogs: utils.NewHighCapacityMailbox[log.Broadcast](), ChStop: make(chan struct{}), WaitOnStop: make(chan struct{}), NewHead: make(chan struct{}, 1), - ResponseCount: GetStartingResponseCountsV1(d.q, lV1, chainId.Uint64(), chain.Config().EVM().FinalityDepth()), BlockNumberToReqID: pairing.New(), ReqAdded: func() {}, Deduper: vrfcommon.NewLogDeduper(int(chain.Config().EVM().FinalityDepth())), + Chain: chain, }}, nil } } @@ -314,101 +306,3 @@ func FromAddressMaxGasPricesAllEqual(jb job.Job, keySpecificMaxGasPriceWei keySp } return } - -func GetStartingResponseCountsV1(q pg.Q, l logger.Logger, chainID uint64, evmFinalityDepth uint32) map[[32]byte]uint64 { - respCounts := map[[32]byte]uint64{} - - // Only check as far back as the evm finality depth for completed transactions. - counts, err := getRespCounts(q, chainID, evmFinalityDepth) - if err != nil { - // Continue with an empty map, do not block job on this. - l.Errorw("Unable to read previous confirmed fulfillments", "err", err) - return respCounts - } - - for _, c := range counts { - // Remove the quotes from the json - req := strings.Replace(c.RequestID, `"`, ``, 2) - // Remove the 0x prefix - b, err := hex.DecodeString(req[2:]) - if err != nil { - l.Errorw("Unable to read fulfillment", "err", err, "reqID", c.RequestID) - continue - } - var reqID [32]byte - copy(reqID[:], b) - respCounts[reqID] = uint64(c.Count) - } - - return respCounts -} - -func GetStartingResponseCountsV2( - q pg.Q, - l logger.Logger, - chainID uint64, - evmFinalityDepth uint32, -) map[string]uint64 { - respCounts := map[string]uint64{} - - // Only check as far back as the evm finality depth for completed transactions. - counts, err := getRespCounts(q, chainID, evmFinalityDepth) - if err != nil { - // Continue with an empty map, do not block job on this. - l.Errorw("Unable to read previous confirmed fulfillments", "err", err) - return respCounts - } - - for _, c := range counts { - // Remove the quotes from the json - req := strings.Replace(c.RequestID, `"`, ``, 2) - // Remove the 0x prefix - b, err := hex.DecodeString(req[2:]) - if err != nil { - l.Errorw("Unable to read fulfillment", "err", err, "reqID", c.RequestID) - continue - } - bi := new(big.Int).SetBytes(b) - respCounts[bi.String()] = uint64(c.Count) - } - return respCounts -} - -func getRespCounts(q pg.Q, chainID uint64, evmFinalityDepth uint32) ( - []struct { - RequestID string - Count int - }, - error, -) { - counts := []struct { - RequestID string - Count int - }{} - // This query should use the idx_evm.txes_state_from_address_evm_chain_id - // index, since the quantity of unconfirmed/unstarted/in_progress transactions _should_ be small - // relative to the rest of the data. - unconfirmedQuery := ` -SELECT meta->'RequestID' AS request_id, count(meta->'RequestID') AS count -FROM evm.txes et -WHERE et.meta->'RequestID' IS NOT NULL -AND et.state IN ('unconfirmed', 'unstarted', 'in_progress') -GROUP BY meta->'RequestID' - ` - // Fetch completed transactions only as far back as the given cutoffBlockNumber. This avoids - // a table scan of the evm.txes table, which could be large if it is unpruned. - confirmedQuery := ` -SELECT meta->'RequestID' AS request_id, count(meta->'RequestID') AS count -FROM evm.txes et JOIN evm.tx_attempts eta on et.id = eta.eth_tx_id - join evm.receipts er on eta.hash = er.tx_hash -WHERE et.meta->'RequestID' is not null -AND er.block_number >= (SELECT number FROM evm.heads WHERE evm_chain_id = $1 ORDER BY number DESC LIMIT 1) - $2 -GROUP BY meta->'RequestID' - ` - query := unconfirmedQuery + "\nUNION ALL\n" + confirmedQuery - err := q.Select(&counts, query, chainID, evmFinalityDepth) - if err != nil { - return nil, err - } - return counts, nil -} diff --git a/core/services/vrf/delegate_test.go b/core/services/vrf/delegate_test.go index 8c522520faf..3c297026004 100644 --- a/core/services/vrf/delegate_test.go +++ b/core/services/vrf/delegate_test.go @@ -6,22 +6,22 @@ import ( "testing" "time" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" log_mocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" - txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_coordinator_interface" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -58,9 +58,9 @@ type vrfUniverse struct { ks keystore.Master vrfkey vrfkey.KeyV2 submitter common.Address - txm *txmmocks.MockEvmTxManager + txm *txmgr.TxManager hb httypes.HeadBroadcaster - legacyChains evm.LegacyChainContainer + legacyChains legacyevm.LegacyChainContainer cid big.Int } @@ -68,28 +68,33 @@ func buildVrfUni(t *testing.T, db *sqlx.DB, cfg chainlink.GeneralConfig) vrfUniv // Mock all chain interactions lb := log_mocks.NewBroadcaster(t) lb.On("AddDependents", 1).Maybe() + lb.On("Register", mock.Anything, mock.Anything).Return(func() {}).Maybe() ec := evmclimocks.NewClient(t) ec.On("ConfiguredChainID").Return(testutils.FixtureChainID) + ec.On("LatestBlockHeight", mock.Anything).Return(big.NewInt(51), nil).Maybe() lggr := logger.TestLogger(t) hb := headtracker.NewHeadBroadcaster(lggr) // Don't mock db interactions prm := pipeline.NewORM(db, lggr, cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) btORM := bridges.NewORM(db, lggr, cfg.Database()) - txm := txmmocks.NewMockEvmTxManager(t) ks := keystore.NewInMemory(db, utils.FastScryptParams, lggr, cfg.Database()) + _, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) + txm, err := txmgr.NewTxm(db, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), ec, logger.TestLogger(t), nil, ks.Eth(), nil) + orm := headtracker.NewORM(db, lggr, cfg.Database(), *testutils.FixtureChainID) + require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), cltest.Head(51))) + jrm := job.NewORM(db, prm, btORM, ks, lggr, cfg.Database()) + t.Cleanup(func() { assert.NoError(t, jrm.Close()) }) relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{LogBroadcaster: lb, KeyStore: ks.Eth(), Client: ec, DB: db, GeneralConfig: cfg, TxManager: txm}) legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) - jrm := job.NewORM(db, legacyChains, prm, btORM, ks, lggr, cfg.Database()) - t.Cleanup(func() { jrm.Close() }) pr := pipeline.NewRunner(prm, btORM, cfg.JobPipeline(), cfg.WebServer(), legacyChains, ks.Eth(), ks.VRF(), lggr, nil, nil) require.NoError(t, ks.Unlock(testutils.Password)) - k, err := ks.Eth().Create(testutils.FixtureChainID) - require.NoError(t, err) + k, err2 := ks.Eth().Create(testutils.FixtureChainID) + require.NoError(t, err2) submitter := k.Address require.NoError(t, err) - vrfkey, err := ks.VRF().Create() - require.NoError(t, err) + vrfkey, err3 := ks.VRF().Create() + require.NoError(t, err3) return vrfUniverse{ jrm: jrm, @@ -100,7 +105,7 @@ func buildVrfUni(t *testing.T, db *sqlx.DB, cfg chainlink.GeneralConfig) vrfUniv ks: ks, vrfkey: vrfkey, submitter: submitter, - txm: txm, + txm: &txm, hb: hb, legacyChains: legacyChains, cid: *ec.ConfiguredChainID(), @@ -172,6 +177,7 @@ func setup(t *testing.T) (vrfUniverse, *v1.Listener, job.Job) { listener.RunHeadListener(func() {}) }() t.Cleanup(func() { listener.Stop(t) }) + require.NoError(t, listener.Start(testutils.Context(t))) return vuni, listener, jb } @@ -302,20 +308,6 @@ func TestDelegate_ValidLog(t *testing.T) { // Expect a call to check if the req is already fulfilled. 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 - vuni.txm.On("CreateTransaction", - mock.Anything, - mock.MatchedBy(func(txRequest txmgr.TxRequest) bool { - meta := txRequest.Meta - return txRequest.FromAddress == vuni.submitter && - txRequest.ToAddress == common.HexToAddress(jb.VRFSpec.CoordinatorAddress.String()) && - txRequest.FeeLimit == uint32(500000) && - meta.JobID != nil && meta.RequestID != nil && meta.RequestTxHash != nil && - (*meta.JobID > 0 && *meta.RequestID == tc.reqID && *meta.RequestTxHash == txHash) - }), - ).Once().Return(txmgr.Tx{}, nil) - listener.HandleLog(log.NewLogBroadcast(tc.log, vuni.cid, nil)) // Wait until the log is present waitForChannel(t, added, time.Second, "request not added to the queue") diff --git a/core/services/vrf/mocks/fee_config.go b/core/services/vrf/mocks/fee_config.go index 55a6360c339..067ce7e4455 100644 --- a/core/services/vrf/mocks/fee_config.go +++ b/core/services/vrf/mocks/fee_config.go @@ -4,7 +4,7 @@ package mocks import ( common "github.com/ethereum/go-ethereum/common" - assets "github.com/smartcontractkit/chainlink/v2/core/assets" + assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" config "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" diff --git a/core/services/vrf/proof/proof_response_test.go b/core/services/vrf/proof/proof_response_test.go index 191c18b60d3..c547be2be2c 100644 --- a/core/services/vrf/proof/proof_response_test.go +++ b/core/services/vrf/proof/proof_response_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_verifier_wrapper" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" proof2 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/proof" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -16,7 +16,7 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) diff --git a/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface.go b/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface.go index f0c398f8cf0..35556c6b45f 100644 --- a/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface.go +++ b/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface_test.go b/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface_test.go index 9fab3737ae2..2601f800e9e 100644 --- a/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface_test.go +++ b/core/services/vrf/solidity_cross_tests/vrf_coordinator_interface_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/solidity_cross_tests" ) @@ -18,14 +17,14 @@ var ( jobID = common.BytesToHash([]byte("1234567890abcdef1234567890abcdef")) seed = big.NewInt(1) sender = common.HexToAddress("0xecfcab0a285d3380e488a39b4bb21e777f8a4eac") - fee = assets.NewLinkFromJuels(100) + fee = big.NewInt(100) requestID = common.HexToHash("0xcafe") raw = solidity_cross_tests.RawRandomnessRequestLog{ KeyHash: keyHash, Seed: seed, JobID: jobID, Sender: sender, - Fee: (*big.Int)(fee), + Fee: fee, RequestID: requestID, Raw: types.Log{ // A raw, on-the-wire RandomnessRequestLog is the concat of fields as uint256's @@ -33,7 +32,7 @@ var ( keyHash.Bytes(), common.BigToHash(seed).Bytes()...), sender.Hash().Bytes()...), - fee.ToHash().Bytes()...), + common.BigToHash(fee).Bytes()...), requestID.Bytes()...), Topics: []common.Hash{{}, jobID}, }, diff --git a/core/services/vrf/solidity_cross_tests/vrf_hash_to_curve_cost_test.go b/core/services/vrf/solidity_cross_tests/vrf_hash_to_curve_cost_test.go index 38675e2651d..29d1db437d1 100644 --- a/core/services/vrf/solidity_cross_tests/vrf_hash_to_curve_cost_test.go +++ b/core/services/vrf/solidity_cross_tests/vrf_hash_to_curve_cost_test.go @@ -11,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" "github.com/smartcontractkit/chainlink/v2/core/services/signatures/secp256k1" diff --git a/core/services/vrf/solidity_cross_tests/vrf_solidity_crosscheck_test.go b/core/services/vrf/solidity_cross_tests/vrf_solidity_crosscheck_test.go index c2896d42314..06875edd74e 100644 --- a/core/services/vrf/solidity_cross_tests/vrf_solidity_crosscheck_test.go +++ b/core/services/vrf/solidity_cross_tests/vrf_solidity_crosscheck_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/require" "go.dedis.ch/kyber/v3" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" "github.com/smartcontractkit/chainlink/v2/core/services/signatures/secp256k1" diff --git a/core/services/vrf/solidity_cross_tests/vrf_v08_solidity_crosscheck_test.go b/core/services/vrf/solidity_cross_tests/vrf_v08_solidity_crosscheck_test.go index 695a9dfd2f1..d1b21b58647 100644 --- a/core/services/vrf/solidity_cross_tests/vrf_v08_solidity_crosscheck_test.go +++ b/core/services/vrf/solidity_cross_tests/vrf_v08_solidity_crosscheck_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" "github.com/smartcontractkit/chainlink/v2/core/services/signatures/secp256k1" diff --git a/core/services/vrf/v1/integration_test.go b/core/services/vrf/v1/integration_test.go index b7e6be43183..a7dca56776f 100644 --- a/core/services/vrf/v1/integration_test.go +++ b/core/services/vrf/v1/integration_test.go @@ -2,7 +2,6 @@ package v1_test import ( "encoding/hex" - "fmt" "math/big" "strings" "testing" @@ -46,7 +45,7 @@ func TestIntegration_VRF_JPV2(t *testing.T) { for _, tt := range tests { test := tt t.Run(test.name, func(t *testing.T) { - config, _ := heavyweight.FullTestDBV2(t, fmt.Sprintf("vrf_jpv2_%v", test.eip1559), func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].GasEstimator.EIP1559DynamicFees = &test.eip1559 c.EVM[0].ChainID = (*utils.Big)(testutils.SimulatedChainID) }) @@ -129,7 +128,7 @@ func TestIntegration_VRF_JPV2(t *testing.T) { func TestIntegration_VRF_WithBHS(t *testing.T) { t.Parallel() - config, _ := heavyweight.FullTestDBV2(t, "vrf_with_bhs", func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true) c.EVM[0].BlockBackfillDepth = ptr[uint32](500) c.Feature.LogPoller = ptr(true) diff --git a/core/services/vrf/v1/listener_v1.go b/core/services/vrf/v1/listener_v1.go index 03b92bc15cb..494847797fa 100644 --- a/core/services/vrf/v1/listener_v1.go +++ b/core/services/vrf/v1/listener_v1.go @@ -3,20 +3,23 @@ package v1 import ( "context" "encoding/hex" + "errors" "fmt" "math/big" + "strings" "sync" "time" + "github.com/avast/retry-go/v4" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" heaps "github.com/theodesp/go-heaps" "github.com/theodesp/go-heaps/pairing" - httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_coordinator_interface" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/recovery" @@ -43,26 +46,24 @@ type request struct { } type Listener struct { - utils.StartStopOnce - - Cfg vrfcommon.Config - FeeCfg vrfcommon.FeeConfig - L logger.SugaredLogger - LogBroadcaster log.Broadcaster - Coordinator *solidity_vrf_coordinator_interface.VRFCoordinator - PipelineRunner pipeline.Runner - Job job.Job - Q pg.Q - HeadBroadcaster httypes.HeadBroadcasterRegistry - Txm txmgr.TxManager - GethKs vrfcommon.GethKeyStore - MailMon *utils.MailboxMonitor - ReqLogs *utils.Mailbox[log.Broadcast] - ChStop utils.StopChan - WaitOnStop chan struct{} - NewHead chan struct{} - LatestHead uint64 - LatestHeadMu sync.RWMutex + services.StateMachine + + Cfg vrfcommon.Config + FeeCfg vrfcommon.FeeConfig + L logger.SugaredLogger + Coordinator *solidity_vrf_coordinator_interface.VRFCoordinator + PipelineRunner pipeline.Runner + Job job.Job + Q pg.Q + GethKs vrfcommon.GethKeyStore + MailMon *utils.MailboxMonitor + ReqLogs *utils.Mailbox[log.Broadcast] + ChStop services.StopChan + WaitOnStop chan struct{} + NewHead chan struct{} + LatestHead uint64 + LatestHeadMu sync.RWMutex + Chain legacyevm.Chain // We can keep these pending logs in memory because we // only mark them confirmed once we send a corresponding fulfillment transaction. @@ -109,11 +110,11 @@ func (lsn *Listener) getLatestHead() uint64 { } // Start complies with job.Service -func (lsn *Listener) Start(context.Context) error { +func (lsn *Listener) Start(ctx context.Context) error { return lsn.StartOnce("VRFListener", func() error { - spec := job.LoadEnvConfigVarsVRF(lsn.Cfg, *lsn.Job.VRFSpec) + spec := job.LoadDefaultVRFPollPeriod(*lsn.Job.VRFSpec) - unsubscribeLogs := lsn.LogBroadcaster.Register(lsn, log.ListenerOpts{ + unsubscribeLogs := lsn.Chain.LogBroadcaster().Register(lsn, log.ListenerOpts{ Contract: lsn.Coordinator.Address(), ParseLog: lsn.Coordinator.ParseLog, LogsWithTopics: map[common.Hash][][]log.Topic{ @@ -135,10 +136,19 @@ func (lsn *Listener) Start(context.Context) error { }) // Subscribe to the head broadcaster for handling // per request conf requirements. - latestHead, unsubscribeHeadBroadcaster := lsn.HeadBroadcaster.Subscribe(lsn) + latestHead, unsubscribeHeadBroadcaster := lsn.Chain.HeadBroadcaster().Subscribe(lsn) if latestHead != nil { lsn.setLatestHead(latestHead) } + + // Populate the response count map + lsn.RespCountMu.Lock() + defer lsn.RespCountMu.Unlock() + respCount, err := lsn.GetStartingResponseCountsV1(ctx) + if err != nil { + return err + } + lsn.ResponseCount = respCount go lsn.RunLogListener([]func(){unsubscribeLogs}, spec.MinIncomingConfirmations) go lsn.RunHeadListener(unsubscribeHeadBroadcaster) @@ -147,6 +157,48 @@ func (lsn *Listener) Start(context.Context) error { }) } +func (lsn *Listener) GetStartingResponseCountsV1(ctx context.Context) (respCount map[[32]byte]uint64, err error) { + respCounts := make(map[[32]byte]uint64) + var latestBlockNum *big.Int + // Retry client call for LatestBlockHeight if fails + // Want to avoid failing startup due to potential faulty RPC call + err = retry.Do(func() error { + latestBlockNum, err = lsn.Chain.Client().LatestBlockHeight(ctx) + return err + }, retry.Attempts(10), retry.Delay(500*time.Millisecond)) + if err != nil { + return nil, err + } + if latestBlockNum == nil { + return nil, errors.New("LatestBlockHeight return nil block num") + } + confirmedBlockNum := latestBlockNum.Int64() - int64(lsn.Chain.Config().EVM().FinalityDepth()) + // Only check as far back as the evm finality depth for completed transactions. + var counts []vrfcommon.RespCountEntry + counts, err = vrfcommon.GetRespCounts(ctx, lsn.Chain.TxManager(), lsn.Chain.Client().ConfiguredChainID(), confirmedBlockNum) + if err != nil { + // Continue with an empty map, do not block job on this. + lsn.L.Errorw("Unable to read previous confirmed fulfillments", "err", err) + return respCounts, nil + } + + for _, c := range counts { + // Remove the quotes from the json + req := strings.Replace(c.RequestID, `"`, ``, 2) + // Remove the 0x prefix + b, err := hex.DecodeString(req[2:]) + if err != nil { + lsn.L.Errorw("Unable to read fulfillment", "err", err, "reqID", c.RequestID) + continue + } + var reqID [32]byte + copy(reqID[:], b) + respCounts[reqID] = uint64(c.Count) + } + + return respCounts, nil +} + // Removes and returns all the confirmed logs from // the pending queue. func (lsn *Listener) extractConfirmedLogs() []request { @@ -313,7 +365,7 @@ func (lsn *Listener) handleLog(lb log.Broadcast, minConfs uint32) { } func (lsn *Listener) shouldProcessLog(lb log.Broadcast) bool { - consumed, err := lsn.LogBroadcaster.WasAlreadyConsumed(lb) + consumed, err := lsn.Chain.LogBroadcaster().WasAlreadyConsumed(lb) if err != nil { lsn.L.Errorw("Could not determine if log was already consumed", "error", err, "txHash", lb.RawLog().TxHash) // Do not process, let lb resend it as a retry mechanism. @@ -323,7 +375,7 @@ func (lsn *Listener) shouldProcessLog(lb log.Broadcast) bool { } func (lsn *Listener) markLogAsConsumed(lb log.Broadcast) { - err := lsn.LogBroadcaster.MarkConsumed(lb) + err := lsn.Chain.LogBroadcaster().MarkConsumed(lb) lsn.L.ErrorIf(err, fmt.Sprintf("Unable to mark log %v as consumed", lb.String())) } @@ -431,7 +483,7 @@ func (lsn *Listener) ProcessRequest(ctx context.Context, req request) bool { // The VRF pipeline has no async tasks, so we don't need to check for `incomplete` if _, err = lsn.PipelineRunner.Run(ctx, run, lggr, true, func(tx pg.Queryer) error { // Always mark consumed regardless of whether the proof failed or not. - if err = lsn.LogBroadcaster.MarkConsumed(req.lb, pg.WithQueryer(tx)); err != nil { + if err = lsn.Chain.LogBroadcaster().MarkConsumed(req.lb, pg.WithQueryer(tx)); err != nil { lggr.Errorw("Failed mark consumed", "err", err) } return nil diff --git a/core/services/vrf/v2/bhs_feeder_test.go b/core/services/vrf/v2/bhs_feeder_test.go index 0da28378d01..31a4ff815a9 100644 --- a/core/services/vrf/v2/bhs_feeder_test.go +++ b/core/services/vrf/v2/bhs_feeder_test.go @@ -4,13 +4,12 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrftesthelpers" "github.com/smartcontractkit/chainlink/v2/core/store/models" @@ -51,7 +50,7 @@ func TestStartHeartbeats(t *testing.T) { keys = append(keys, ownerKey, vrfKey) - config, _ := heavyweight.FullTestDBV2(t, "vrfv2_needs_blockhash_store", func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, gasLanePriceWei, keySpecificOverrides...)(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) c.Feature.LogPoller = ptr(true) @@ -63,26 +62,12 @@ func TestStartHeartbeats(t *testing.T) { heartbeatPeriod := 5 * time.Second t.Run("bhs_feeder_startheartbeats_happy_path", func(tt *testing.T) { - coordinatorAddress := uni.rootContractAddress - vrfVersion := vrfcommon.V2 - app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, keys...) require.NoError(t, app.Start(testutils.Context(t))) - var ( - v2CoordinatorAddress string - v2PlusCoordinatorAddress string - ) - - if vrfVersion == vrfcommon.V2 { - v2CoordinatorAddress = coordinatorAddress.String() - } else if vrfVersion == vrfcommon.V2Plus { - v2PlusCoordinatorAddress = coordinatorAddress.String() - } - _ = vrftesthelpers.CreateAndStartBHSJob( t, bhsKeyAddresses, app, uni.bhsContractAddress.String(), "", - v2CoordinatorAddress, v2PlusCoordinatorAddress, "", 0, 200, heartbeatPeriod, 100) + uni.rootContractAddress.String(), "", "", 0, 200, heartbeatPeriod, 100) // Ensure log poller is ready and has all logs. require.NoError(t, app.GetRelayers().LegacyEVMChains().Slice()[0].LogPoller().Ready()) @@ -97,9 +82,10 @@ func TestStartHeartbeats(t *testing.T) { t.Logf("Sleeping %.2f seconds before checking blockhash in BHS added by BHS_Heartbeats_Service\n", diff.Seconds()) time.Sleep(diff) // storeEarliest in BHS contract stores blocktip - 256 in the Blockhash Store (BHS) - // before the initTxns:260 txns sent by the loop above, 18 txns are sent by - // newVRFCoordinatorV2Universe method. block tip is initTxns + 18 - blockTip := initTxns + 18 - verifyBlockhashStored(t, uni.coordinatorV2UniverseCommon, uint64(blockTip-256)) + tipHeader, err := uni.backend.HeaderByNumber(testutils.Context(t), nil) + require.NoError(t, err) + // the storeEarliest transaction will end up in a new block, hence the + 1 below. + blockNumberStored := tipHeader.Number.Uint64() - 256 + 1 + verifyBlockhashStored(t, uni.coordinatorV2UniverseCommon, blockNumberStored) }) } diff --git a/core/services/vrf/v2/coordinator_v2x_interface.go b/core/services/vrf/v2/coordinator_v2x_interface.go index b090c4ad5af..e20500cca89 100644 --- a/core/services/vrf/v2/coordinator_v2x_interface.go +++ b/core/services/vrf/v2/coordinator_v2x_interface.go @@ -28,6 +28,7 @@ var ( type CoordinatorV2_X interface { Address() common.Address ParseRandomWordsRequested(log types.Log) (RandomWordsRequested, error) + ParseRandomWordsFulfilled(log types.Log) (RandomWordsFulfilled, error) RequestRandomWords(opts *bind.TransactOpts, keyHash [32]byte, subID *big.Int, requestConfirmations uint16, callbackGasLimit uint32, numWords uint32, payInEth bool) (*types.Transaction, error) AddConsumer(opts *bind.TransactOpts, subID *big.Int, consumer common.Address) (*types.Transaction, error) CreateSubscription(opts *bind.TransactOpts) (*types.Transaction, error) @@ -47,6 +48,10 @@ type CoordinatorV2_X interface { GetCommitment(opts *bind.CallOpts, requestID *big.Int) ([32]byte, error) Migrate(opts *bind.TransactOpts, subID *big.Int, newCoordinator common.Address) (*types.Transaction, error) FundSubscriptionWithNative(opts *bind.TransactOpts, subID *big.Int, amount *big.Int) (*types.Transaction, error) + // RandomWordsRequestedTopic returns the log topic of the RandomWordsRequested log + RandomWordsRequestedTopic() common.Hash + // RandomWordsFulfilledTopic returns the log topic of the RandomWordsFulfilled log + RandomWordsFulfilledTopic() common.Hash } type coordinatorV2 struct { @@ -61,6 +66,14 @@ func NewCoordinatorV2(c *vrf_coordinator_v2.VRFCoordinatorV2) CoordinatorV2_X { } } +func (c *coordinatorV2) RandomWordsRequestedTopic() common.Hash { + return vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested{}.Topic() +} + +func (c *coordinatorV2) RandomWordsFulfilledTopic() common.Hash { + return vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled{}.Topic() +} + func (c *coordinatorV2) Address() common.Address { return c.coordinator.Address() } @@ -73,6 +86,14 @@ func (c *coordinatorV2) ParseRandomWordsRequested(log types.Log) (RandomWordsReq return NewV2RandomWordsRequested(parsed), nil } +func (c *coordinatorV2) ParseRandomWordsFulfilled(log types.Log) (RandomWordsFulfilled, error) { + parsed, err := c.coordinator.ParseRandomWordsFulfilled(log) + if err != nil { + return nil, err + } + return NewV2RandomWordsFulfilled(parsed), nil +} + func (c *coordinatorV2) RequestRandomWords(opts *bind.TransactOpts, keyHash [32]byte, subID *big.Int, requestConfirmations uint16, callbackGasLimit uint32, numWords uint32, payInEth bool) (*types.Transaction, error) { return c.coordinator.RequestRandomWords(opts, keyHash, subID.Uint64(), requestConfirmations, callbackGasLimit, numWords) } @@ -187,6 +208,14 @@ func NewCoordinatorV2_5(c vrf_coordinator_v2_5.VRFCoordinatorV25Interface) Coord } } +func (c *coordinatorV2_5) RandomWordsRequestedTopic() common.Hash { + return vrf_coordinator_v2plus_interface.IVRFCoordinatorV2PlusInternalRandomWordsRequested{}.Topic() +} + +func (c *coordinatorV2_5) RandomWordsFulfilledTopic() common.Hash { + return vrf_coordinator_v2plus_interface.IVRFCoordinatorV2PlusInternalRandomWordsFulfilled{}.Topic() +} + func (c *coordinatorV2_5) Address() common.Address { return c.coordinator.Address() } @@ -199,6 +228,14 @@ func (c *coordinatorV2_5) ParseRandomWordsRequested(log types.Log) (RandomWordsR return NewV2_5RandomWordsRequested(parsed), nil } +func (c *coordinatorV2_5) ParseRandomWordsFulfilled(log types.Log) (RandomWordsFulfilled, error) { + parsed, err := c.coordinator.ParseRandomWordsFulfilled(log) + if err != nil { + return nil, err + } + return NewV2_5RandomWordsFulfilled(parsed), nil +} + func (c *coordinatorV2_5) RequestRandomWords(opts *bind.TransactOpts, keyHash [32]byte, subID *big.Int, requestConfirmations uint16, callbackGasLimit uint32, numWords uint32, payInEth bool) (*types.Transaction, error) { extraArgs, err := extraargs.ExtraArgsV1(payInEth) if err != nil { diff --git a/core/services/vrf/v2/integration_helpers_test.go b/core/services/vrf/v2/integration_helpers_test.go index 09c9a0ed437..03d96cadf20 100644 --- a/core/services/vrf/v2/integration_helpers_test.go +++ b/core/services/vrf/v2/integration_helpers_test.go @@ -1,7 +1,6 @@ package v2_test import ( - "fmt" "math/big" "strings" "testing" @@ -18,7 +17,7 @@ import ( "github.com/stretchr/testify/require" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" v2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" @@ -62,7 +61,7 @@ func testSingleConsumerHappyPath( key1 := cltest.MustGenerateRandomKey(t) key2 := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_singleconsumer_happypath", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), toml.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), @@ -73,6 +72,8 @@ func testSingleConsumerHappyPath( GasEstimator: toml.KeySpecificGasEstimator{PriceMax: gasLanePriceWei}, })(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, key1, key2) @@ -113,7 +114,7 @@ func testSingleConsumerHappyPath( }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) // Mine the fulfillment that was queued. - mine(t, requestID1, subID, uni.backend, db, vrfVersion) + mine(t, requestID1, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) // Assert correct state of RandomWordsFulfilled event. // In particular: @@ -133,7 +134,7 @@ func testSingleConsumerHappyPath( t.Log("runs", len(runs)) return len(runs) == 2 }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) - mine(t, requestID2, subID, uni.backend, db, vrfVersion) + mine(t, requestID2, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) // Assert correct state of RandomWordsFulfilled event. // In particular: @@ -202,12 +203,12 @@ func testMultipleConsumersNeedBHS( GasEstimator: toml.KeySpecificGasEstimator{PriceMax: gasLanePriceWei}, }) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_needs_blockhash_store", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), keySpecificOverrides...)(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) c.EVM[0].FinalityDepth = ptr[uint32](2) - c.EVM[0].LogPollInterval = models.MustNewDuration(time.Second) }) keys = append(keys, ownerKey, vrfKey) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, keys...) @@ -285,7 +286,7 @@ func testMultipleConsumersNeedBHS( return len(runs) == 1 }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) - mine(t, requestID, subID, uni.backend, db, vrfVersion) + mine(t, requestID, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) rwfe := assertRandomWordsFulfilled(t, requestID, true, coordinator, nativePayment) if len(assertions) > 0 { @@ -349,13 +350,13 @@ func testMultipleConsumersNeedTrustedBHS( uni.backend.Commit() } - config, db := heavyweight.FullTestDBV2(t, "vrfv2_needs_trusted_blockhash_store", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), keySpecificOverrides...)(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) c.EVM[0].GasEstimator.LimitDefault = ptr(uint32(5_000_000)) c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) c.EVM[0].FinalityDepth = ptr[uint32](2) - c.EVM[0].LogPollInterval = models.MustNewDuration(time.Second) }) keys = append(keys, ownerKey, vrfKey) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, keys...) @@ -446,7 +447,7 @@ func testMultipleConsumersNeedTrustedBHS( return len(runs) == 1 }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) - mine(t, requestID, subID, uni.backend, db, vrfVersion) + mine(t, requestID, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) rwfe := assertRandomWordsFulfilled(t, requestID, true, coordinator, nativePayment) if len(assertions) > 0 { @@ -531,7 +532,7 @@ func testSingleConsumerHappyPathBatchFulfillment( ) { key1 := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_singleconsumer_batch_happypath", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), toml.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), @@ -540,6 +541,8 @@ func testSingleConsumerHappyPathBatchFulfillment( c.EVM[0].GasEstimator.LimitDefault = ptr[uint32](5_000_000) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) c.EVM[0].ChainID = (*utils.Big)(testutils.SimulatedChainID) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, key1) @@ -592,7 +595,7 @@ func testSingleConsumerHappyPathBatchFulfillment( return len(runs) == numRequests }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) - mineBatch(t, reqIDs, subID, uni.backend, db, vrfVersion) + mineBatch(t, reqIDs, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) for i, requestID := range reqIDs { // Assert correct state of RandomWordsFulfilled event. @@ -635,13 +638,15 @@ func testSingleConsumerNeedsTopUp( ) { key := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(1000) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_singleconsumer_needstopup", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(1000), toml.KeySpecific{ // Gas lane. Key: ptr(key.EIP55Address), GasEstimator: toml.KeySpecificGasEstimator{PriceMax: gasLanePriceWei}, })(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, key) @@ -694,7 +699,7 @@ func testSingleConsumerNeedsTopUp( // 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.backend, db, vrfVersion) + mine(t, requestID, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) // Assert the state of the RandomWordsFulfilled event. rwfe := assertRandomWordsFulfilled(t, requestID, true, coordinator, nativePayment) @@ -739,7 +744,7 @@ func testBlockHeaderFeeder( gasLanePriceWei := assets.GWei(10) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_test_block_header_feeder", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, gasLanePriceWei, toml.KeySpecific{ // Gas lane. Key: ptr(vrfKey.EIP55Address), @@ -747,8 +752,8 @@ func testBlockHeaderFeeder( })(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) c.EVM[0].FinalityDepth = ptr[uint32](2) - c.EVM[0].LogPollInterval = models.MustNewDuration(time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, vrfKey, bhfKey) require.NoError(t, app.Start(testutils.Context(t))) @@ -818,7 +823,7 @@ func testBlockHeaderFeeder( return len(runs) == 1 }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) - mine(t, requestID, subID, uni.backend, db, vrfVersion) + mine(t, requestID, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) rwfe := assertRandomWordsFulfilled(t, requestID, true, coordinator, nativePayment) if len(assertions) > 0 { @@ -894,7 +899,7 @@ func testSingleConsumerForcedFulfillment( key1 := cltest.MustGenerateRandomKey(t) key2 := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, db := heavyweight.FullTestDBV2(t, fmt.Sprintf("vrfv2_singleconsumer_forcefulfill_%v", batchEnabled), func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), toml.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), @@ -905,6 +910,8 @@ func testSingleConsumerForcedFulfillment( GasEstimator: toml.KeySpecificGasEstimator{PriceMax: gasLanePriceWei}, })(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, key1, key2) @@ -1021,7 +1028,7 @@ func testSingleConsumerForcedFulfillment( }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) // Mine the fulfillment that was queued. - mine(t, requestID, subID, uni.backend, db, vrfVersion) + mine(t, requestID, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) // Assert correct state of RandomWordsFulfilled event. // In this particular case: @@ -1054,21 +1061,20 @@ func testSingleConsumerEIP150( vrfVersion vrfcommon.Version, nativePayment bool, ) { - callBackGasLimit := int64(2_500_000) // base callback gas. - eip150Fee := callBackGasLimit / 64 // premium needed for callWithExactGas - coordinatorFulfillmentOverhead := int64(90_000) // fixed gas used in coordinator fulfillment - gasLimit := callBackGasLimit + eip150Fee + coordinatorFulfillmentOverhead + callBackGasLimit := int64(2_500_000) // base callback gas. key1 := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, _ := heavyweight.FullTestDBV2(t, "vrfv2_singleconsumer_eip150_happypath", func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), v2.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), GasEstimator: v2.KeySpecificGasEstimator{PriceMax: gasLanePriceWei}, })(c, s) - c.EVM[0].GasEstimator.LimitDefault = ptr(uint32(gasLimit)) + c.EVM[0].GasEstimator.LimitDefault = ptr(uint32(3.5e6)) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, key1) consumer := uni.vrfConsumers[0] @@ -1129,7 +1135,7 @@ func testSingleConsumerEIP150Revert( key1 := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, _ := heavyweight.FullTestDBV2(t, "vrfv2_singleconsumer_eip150_revert", func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), v2.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), @@ -1137,6 +1143,8 @@ func testSingleConsumerEIP150Revert( })(c, s) c.EVM[0].GasEstimator.LimitDefault = ptr(uint32(gasLimit)) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, key1) consumer := uni.vrfConsumers[0] @@ -1192,7 +1200,7 @@ func testSingleConsumerBigGasCallbackSandwich( ) { key1 := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(100) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_singleconsumer_bigcallback_sandwich", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(100), v2.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), @@ -1200,6 +1208,8 @@ func testSingleConsumerBigGasCallbackSandwich( })(c, s) c.EVM[0].GasEstimator.LimitDefault = ptr[uint32](5_000_000) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, key1) consumer := uni.vrfConsumers[0] @@ -1264,7 +1274,7 @@ func testSingleConsumerBigGasCallbackSandwich( }, 3*time.Second, 1*time.Second).Should(gomega.BeTrue()) // Mine the fulfillment that was queued. - mine(t, reqIDs[1], subID, uni.backend, db, vrfVersion) + mine(t, reqIDs[1], subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) // Assert the random word was fulfilled assertRandomWordsFulfilled(t, reqIDs[1], false, uni.rootContract, nativePayment) @@ -1308,7 +1318,7 @@ func testSingleConsumerMultipleGasLanes( expensiveKey := cltest.MustGenerateRandomKey(t) cheapGasLane := assets.GWei(10) expensiveGasLane := assets.GWei(1000) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_singleconsumer_multiplegaslanes", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), v2.KeySpecific{ // Cheap gas lane. Key: ptr(cheapKey.EIP55Address), @@ -1320,6 +1330,8 @@ func testSingleConsumerMultipleGasLanes( })(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) c.EVM[0].GasEstimator.LimitDefault = ptr[uint32](5_000_000) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, cheapKey, expensiveKey) @@ -1365,7 +1377,7 @@ func testSingleConsumerMultipleGasLanes( }, testutils.WaitTimeout(t), 1*time.Second).Should(gomega.BeTrue()) // Mine the fulfillment that was queued. - mine(t, cheapRequestID, subID, uni.backend, db, vrfVersion) + mine(t, cheapRequestID, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) // Assert correct state of RandomWordsFulfilled event. assertRandomWordsFulfilled(t, cheapRequestID, true, uni.rootContract, nativePayment) @@ -1397,7 +1409,7 @@ func testSingleConsumerMultipleGasLanes( }, testutils.WaitTimeout(t), 1*time.Second).Should(gomega.BeTrue()) // Mine the fulfillment that was queued. - mine(t, expensiveRequestID, subID, uni.backend, db, vrfVersion) + mine(t, expensiveRequestID, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) // Assert correct state of RandomWordsFulfilled event. assertRandomWordsFulfilled(t, expensiveRequestID, true, uni.rootContract, nativePayment) @@ -1428,13 +1440,15 @@ func testSingleConsumerAlwaysRevertingCallbackStillFulfilled( ) { key := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_singleconsumer_alwaysrevertingcallback", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), v2.KeySpecific{ // Gas lane. Key: ptr(key.EIP55Address), GasEstimator: v2.KeySpecificGasEstimator{PriceMax: gasLanePriceWei}, })(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, key) consumer := uni.reverter @@ -1477,7 +1491,7 @@ func testSingleConsumerAlwaysRevertingCallbackStillFulfilled( }, testutils.WaitTimeout(t), 1*time.Second).Should(gomega.BeTrue()) // Mine the fulfillment that was queued. - mine(t, requestID, subID, uni.backend, db, vrfVersion) + mine(t, requestID, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) // Assert correct state of RandomWordsFulfilled event. assertRandomWordsFulfilled(t, requestID, false, uni.rootContract, nativePayment) @@ -1496,7 +1510,7 @@ func testConsumerProxyHappyPath( key1 := cltest.MustGenerateRandomKey(t) key2 := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_consumerproxy_happypath", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), v2.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), @@ -1506,6 +1520,8 @@ func testConsumerProxyHappyPath( GasEstimator: v2.KeySpecificGasEstimator{PriceMax: gasLanePriceWei}, })(c, s) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, key1, key2) consumerOwner := uni.neil @@ -1552,7 +1568,7 @@ func testConsumerProxyHappyPath( }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) // Mine the fulfillment that was queued. - mine(t, requestID1, subID, uni.backend, db, vrfVersion) + mine(t, requestID1, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) // Assert correct state of RandomWordsFulfilled event. assertRandomWordsFulfilled(t, requestID1, true, uni.rootContract, nativePayment) @@ -1576,7 +1592,7 @@ func testConsumerProxyHappyPath( t.Log("runs", len(runs)) return len(runs) == 2 }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) - mine(t, requestID2, subID, uni.backend, db, vrfVersion) + mine(t, requestID2, subID, uni.backend, db, vrfVersion, testutils.SimulatedChainID) assertRandomWordsFulfilled(t, requestID2, true, uni.rootContract, nativePayment) // Assert correct number of random words sent by coordinator. @@ -1624,12 +1640,14 @@ func testMaliciousConsumer( batchEnabled bool, vrfVersion vrfcommon.Version, ) { - config, _ := heavyweight.FullTestDBV2(t, "vrf_v2plus_integration_malicious", func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].GasEstimator.LimitDefault = ptr[uint32](2_000_000) c.EVM[0].GasEstimator.PriceMax = assets.GWei(1) c.EVM[0].GasEstimator.PriceDefault = assets.GWei(1) c.EVM[0].GasEstimator.FeeCapDefault = assets.GWei(1) c.EVM[0].ChainID = (*utils.Big)(testutils.SimulatedChainID) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) carol := uni.vrfConsumers[0] diff --git a/core/services/vrf/v2/integration_v2_plus_test.go b/core/services/vrf/v2/integration_v2_plus_test.go index 33eb9f74839..1564f0f6343 100644 --- a/core/services/vrf/v2/integration_v2_plus_test.go +++ b/core/services/vrf/v2/integration_v2_plus_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_blockhash_store" @@ -40,7 +40,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" @@ -50,6 +50,7 @@ import ( v22 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/v2" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrftesthelpers" + "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -107,6 +108,10 @@ func newVRFCoordinatorV2PlusUniverse(t *testing.T, key ethkey.KeyV2, numConsumer vrf_coordinator_v2plus_interface.IVRFCoordinatorV2PlusInternalABI)) require.NoError(t, err) backend := cltest.NewSimulatedBackend(t, genesisData, gasLimit) + blockTime := time.UnixMilli(int64(backend.Blockchain().CurrentHeader().Time)) + err = backend.AdjustTime(time.Since(blockTime) - 24*time.Hour) + require.NoError(t, err) + backend.Commit() // Deploy link linkAddress, _, linkContract, err := link_token_interface.DeployLinkToken( sergey, backend) @@ -259,6 +264,10 @@ func newVRFCoordinatorV2PlusUniverse(t *testing.T, key ethkey.KeyV2, numConsumer require.NoError(t, err, "failed to set coordinator configuration") backend.Commit() + for i := 0; i < 200; i++ { + backend.Commit() + } + return coordinatorV2PlusUniverse{ coordinatorV2UniverseCommon: coordinatorV2UniverseCommon{ vrfConsumers: vrfConsumers, @@ -456,6 +465,7 @@ func TestVRFV2PlusIntegration_SingleConsumer_HappyPath(t *testing.T) { } func TestVRFV2PlusIntegration_SingleConsumer_EOA_Request(t *testing.T) { + t.Skip("questionable value of this test") t.Parallel() ownerKey := cltest.MustGenerateRandomKey(t) uni := newVRFCoordinatorV2PlusUniverse(t, ownerKey, 1, false) @@ -471,6 +481,7 @@ func TestVRFV2PlusIntegration_SingleConsumer_EOA_Request(t *testing.T) { } func TestVRFV2PlusIntegration_SingleConsumer_EOA_Request_Batching_Enabled(t *testing.T) { + t.Skip("questionable value of this test") t.Parallel() ownerKey := cltest.MustGenerateRandomKey(t) uni := newVRFCoordinatorV2PlusUniverse(t, ownerKey, 1, false) @@ -486,9 +497,6 @@ func TestVRFV2PlusIntegration_SingleConsumer_EOA_Request_Batching_Enabled(t *tes } func TestVRFV2PlusIntegration_SingleConsumer_EIP150_HappyPath(t *testing.T) { - // See: https://smartcontract-it.atlassian.net/browse/VRF-589 - // Temporarily skipping to figure out issue with test - t.Skip() t.Parallel() ownerKey := cltest.MustGenerateRandomKey(t) uni := newVRFCoordinatorV2PlusUniverse(t, ownerKey, 1, false) @@ -1141,7 +1149,7 @@ func TestVRFV2PlusIntegration_Migration(t *testing.T) { uni := newVRFCoordinatorV2PlusUniverse(t, ownerKey, 1, false) key1 := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, db := heavyweight.FullTestDBV2(t, "vrfv2plus_migration", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), toml.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), @@ -1149,6 +1157,8 @@ func TestVRFV2PlusIntegration_Migration(t *testing.T) { })(c, s) c.EVM[0].GasEstimator.LimitDefault = ptr[uint32](5_000_000) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, uni.backend, ownerKey, key1) @@ -1200,7 +1210,7 @@ func TestVRFV2PlusIntegration_Migration(t *testing.T) { return len(runs) == 1 }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) - mine(t, requestID, subID, uni.backend, db, vrfcommon.V2Plus) + mine(t, requestID, subID, uni.backend, db, vrfcommon.V2Plus, testutils.SimulatedChainID) assertRandomWordsFulfilled(t, requestID, true, uni.rootContract, false) // Assert correct number of random words sent by coordinator. diff --git a/core/services/vrf/v2/integration_v2_test.go b/core/services/vrf/v2/integration_v2_test.go index 6dad3173072..fa95b694f98 100644 --- a/core/services/vrf/v2/integration_v2_test.go +++ b/core/services/vrf/v2/integration_v2_test.go @@ -1,7 +1,6 @@ package v2_test import ( - "context" "encoding/hex" "encoding/json" "fmt" @@ -24,19 +23,24 @@ import ( "github.com/onsi/gomega" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmlogger "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_blockhash_store" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_vrf_coordinator_v2" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/blockhash_store" @@ -57,7 +61,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -65,11 +71,11 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/services/pg/datatypes" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/services/signatures/secp256k1" - "github.com/smartcontractkit/chainlink/v2/core/services/vrf" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/proof" + v1 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/v1" v22 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/v2" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrftesthelpers" @@ -127,24 +133,14 @@ type coordinatorV2Universe struct { batchCoordinatorContractAddress common.Address } -const ( - ConfirmedEthTxesV2Query = `SELECT * FROM evm.txes - WHERE evm.txes.state = 'confirmed' - AND evm.txes.meta->>'RequestID' = $1 - AND CAST(evm.txes.meta->>'SubId' AS NUMERIC) = $2 LIMIT 1` - ConfirmedEthTxesV2PlusQuery = `SELECT * FROM evm.txes - WHERE evm.txes.state = 'confirmed' - AND evm.txes.meta->>'RequestID' = $1 - AND CAST(evm.txes.meta->>'GlobalSubId' AS NUMERIC) = $2 LIMIT 1` - ConfirmedEthTxesV2BatchQuery = ` - SELECT * FROM evm.txes - WHERE evm.txes.state = 'confirmed' - AND CAST(evm.txes.meta->>'SubId' AS NUMERIC) = $1` - ConfirmedEthTxesV2PlusBatchQuery = ` - SELECT * FROM evm.txes - WHERE evm.txes.state = 'confirmed' - AND CAST(evm.txes.meta->>'GlobalSubId' AS NUMERIC) = $1` -) +func makeTestTxm(t *testing.T, txStore txmgr.TestEvmTxStore, keyStore keystore.Master, ec *evmclimocks.Client) txmgrcommon.TxManager[*big.Int, *evmtypes.Head, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] { + _, _, evmConfig := txmgr.MakeTestConfigs(t) + txmConfig := txmgr.NewEvmTxmConfig(evmConfig) + txm := txmgr.NewEvmTxm(ec.ConfiguredChainID(), txmConfig, evmConfig.Transactions(), keyStore.Eth(), logger.TestLogger(t), nil, nil, + nil, txStore, nil, nil, nil, nil, nil) + + return txm +} func newVRFCoordinatorV2Universe(t *testing.T, key ethkey.KeyV2, numConsumers int) coordinatorV2Universe { testutils.SkipShort(t, "VRFCoordinatorV2Universe") @@ -187,6 +183,10 @@ func newVRFCoordinatorV2Universe(t *testing.T, key ethkey.KeyV2, numConsumers in vrf_coordinator_v2.VRFCoordinatorV2ABI)) require.NoError(t, err) backend := cltest.NewSimulatedBackend(t, genesisData, gasLimit) + blockTime := time.UnixMilli(int64(backend.Blockchain().CurrentHeader().Time)) + err = backend.AdjustTime(time.Since(blockTime) - 24*time.Hour) + require.NoError(t, err) + backend.Commit() // Deploy link linkAddress, _, linkContract, err := link_token_interface.DeployLinkToken( sergey, backend) @@ -425,21 +425,22 @@ func deployOldCoordinator( common.Address, *vrf_coordinator_v2.VRFCoordinatorV2, ) { + ctx := testutils.Context(t) bytecode := hexutil.MustDecode("0x60e06040523480156200001157600080fd5b506040516200608c3803806200608c8339810160408190526200003491620001b1565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000e8565b5050506001600160601b0319606093841b811660805290831b811660a052911b1660c052620001fb565b6001600160a01b038116331415620001435760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b80516001600160a01b0381168114620001ac57600080fd5b919050565b600080600060608486031215620001c757600080fd5b620001d28462000194565b9250620001e26020850162000194565b9150620001f26040850162000194565b90509250925092565b60805160601c60a05160601c60c05160601c615e2762000265600039600081816105260152613bd901526000818161061d015261402401526000818161036d01528181611599015281816125960152818161302c0152818161318201526138360152615e276000f3fe608060405234801561001057600080fd5b506004361061025b5760003560e01c80636f64f03f11610145578063ad178361116100bd578063d2f9f9a71161008c578063e72f6e3011610071578063e72f6e30146106fa578063e82ad7d41461070d578063f2fde38b1461073057600080fd5b8063d2f9f9a7146106d4578063d7ae1d30146106e757600080fd5b8063ad17836114610618578063af198b971461063f578063c3f909d41461066f578063caf70c4a146106c157600080fd5b80638da5cb5b11610114578063a21a23e4116100f9578063a21a23e4146105da578063a47c7696146105e2578063a4c0ed361461060557600080fd5b80638da5cb5b146105a95780639f87fad7146105c757600080fd5b80636f64f03f146105685780637341c10c1461057b57806379ba50971461058e578063823597401461059657600080fd5b8063356dac71116101d85780635fbbc0d2116101a757806366316d8d1161018c57806366316d8d1461050e578063689c45171461052157806369bcdb7d1461054857600080fd5b80635fbbc0d21461040057806364d51a2a1461050657600080fd5b8063356dac71146103b457806340d6bb82146103bc5780634cb48a54146103da5780635d3b1d30146103ed57600080fd5b806308821d581161022f57806315c48b841161021457806315c48b841461030e578063181f5a77146103295780631b6b6d231461036857600080fd5b806308821d58146102cf57806312b58349146102e257600080fd5b80620122911461026057806302bcc5b61461028057806304c357cb1461029557806306bfa637146102a8575b600080fd5b610268610743565b60405161027793929190615964565b60405180910390f35b61029361028e366004615792565b6107bf565b005b6102936102a33660046157ad565b61086b565b60055467ffffffffffffffff165b60405167ffffffffffffffff9091168152602001610277565b6102936102dd3660046154a3565b610a60565b6005546801000000000000000090046bffffffffffffffffffffffff165b604051908152602001610277565b61031660c881565b60405161ffff9091168152602001610277565b604080518082018252601681527f565246436f6f7264696e61746f72563220312e302e30000000000000000000006020820152905161027791906158f1565b61038f7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610277565b600a54610300565b6103c56101f481565b60405163ffffffff9091168152602001610277565b6102936103e836600461563c565b610c3f565b6103006103fb366004615516565b611036565b600c546040805163ffffffff80841682526401000000008404811660208301526801000000000000000084048116928201929092526c010000000000000000000000008304821660608201527001000000000000000000000000000000008304909116608082015262ffffff740100000000000000000000000000000000000000008304811660a0830152770100000000000000000000000000000000000000000000008304811660c08301527a0100000000000000000000000000000000000000000000000000008304811660e08301527d01000000000000000000000000000000000000000000000000000000000090920490911661010082015261012001610277565b610316606481565b61029361051c36600461545b565b611444565b61038f7f000000000000000000000000000000000000000000000000000000000000000081565b610300610556366004615779565b60009081526009602052604090205490565b6102936105763660046153a0565b6116ad565b6102936105893660046157ad565b6117f7565b610293611a85565b6102936105a4366004615792565b611b82565b60005473ffffffffffffffffffffffffffffffffffffffff1661038f565b6102936105d53660046157ad565b611d7c565b6102b66121fd565b6105f56105f0366004615792565b6123ed565b6040516102779493929190615b02565b6102936106133660046153d4565b612537565b61038f7f000000000000000000000000000000000000000000000000000000000000000081565b61065261064d366004615574565b6127a8565b6040516bffffffffffffffffffffffff9091168152602001610277565b600b546040805161ffff8316815263ffffffff6201000084048116602083015267010000000000000084048116928201929092526b010000000000000000000000909204166060820152608001610277565b6103006106cf3660046154bf565b612c6d565b6103c56106e2366004615792565b612c9d565b6102936106f53660046157ad565b612e92565b610293610708366004615385565b612ff3565b61072061071b366004615792565b613257565b6040519015158152602001610277565b61029361073e366004615385565b6134ae565b600b546007805460408051602080840282018101909252828152600094859460609461ffff8316946201000090930463ffffffff169391928391908301828280156107ad57602002820191906000526020600020905b815481526020019060010190808311610799575b50505050509050925092509250909192565b6107c76134bf565b67ffffffffffffffff811660009081526003602052604090205473ffffffffffffffffffffffffffffffffffffffff1661082d576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff811660009081526003602052604090205461086890829073ffffffffffffffffffffffffffffffffffffffff16613542565b50565b67ffffffffffffffff8216600090815260036020526040902054829073ffffffffffffffffffffffffffffffffffffffff16806108d4576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821614610940576040517fd8a3fb5200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024015b60405180910390fd5b600b546601000000000000900460ff1615610987576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff841660009081526003602052604090206001015473ffffffffffffffffffffffffffffffffffffffff848116911614610a5a5767ffffffffffffffff841660008181526003602090815260409182902060010180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff88169081179091558251338152918201527f69436ea6df009049404f564eff6622cd00522b0bd6a89efd9e52a355c4a879be91015b60405180910390a25b50505050565b610a686134bf565b604080518082018252600091610a97919084906002908390839080828437600092019190915250612c6d915050565b60008181526006602052604090205490915073ffffffffffffffffffffffffffffffffffffffff1680610af9576040517f77f5b84c00000000000000000000000000000000000000000000000000000000815260048101839052602401610937565b600082815260066020526040812080547fffffffffffffffffffffffff00000000000000000000000000000000000000001690555b600754811015610be9578260078281548110610b4c57610b4c615dbc565b90600052602060002001541415610bd7576007805460009190610b7190600190615c76565b81548110610b8157610b81615dbc565b906000526020600020015490508060078381548110610ba257610ba2615dbc565b6000918252602090912001556007805480610bbf57610bbf615d8d565b60019003818190600052602060002001600090559055505b80610be181615cba565b915050610b2e565b508073ffffffffffffffffffffffffffffffffffffffff167f72be339577868f868798bac2c93e52d6f034fef4689a9848996c14ebb7416c0d83604051610c3291815260200190565b60405180910390a2505050565b610c476134bf565b60c861ffff87161115610c9a576040517fa738697600000000000000000000000000000000000000000000000000000000815261ffff871660048201819052602482015260c86044820152606401610937565b60008213610cd7576040517f43d4cf6600000000000000000000000000000000000000000000000000000000815260048101839052602401610937565b6040805160a0808201835261ffff891680835263ffffffff89811660208086018290526000868801528a831660608088018290528b85166080988901819052600b80547fffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000001690971762010000909502949094177fffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff166701000000000000009092027fffffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffff16919091176b010000000000000000000000909302929092179093558651600c80549489015189890151938a0151978a0151968a015160c08b015160e08c01516101008d01519588167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009099169890981764010000000093881693909302929092177fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff1668010000000000000000958716959095027fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff16949094176c0100000000000000000000000098861698909802979097177fffffffffffffffffff00000000000000ffffffffffffffffffffffffffffffff1670010000000000000000000000000000000096909416959095027fffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffff16929092177401000000000000000000000000000000000000000062ffffff92831602177fffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffff1677010000000000000000000000000000000000000000000000958216959095027fffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffff16949094177a01000000000000000000000000000000000000000000000000000092851692909202919091177cffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167d0100000000000000000000000000000000000000000000000000000000009390911692909202919091178155600a84905590517fc21e3bd2e0b339d2848f0dd956947a88966c242c0c0c582a33137a5c1ceb5cb2916110269189918991899189918991906159c3565b60405180910390a1505050505050565b600b546000906601000000000000900460ff1615611080576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff851660009081526003602052604090205473ffffffffffffffffffffffffffffffffffffffff166110e6576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b33600090815260026020908152604080832067ffffffffffffffff808a1685529252909120541680611156576040517ff0019fe600000000000000000000000000000000000000000000000000000000815267ffffffffffffffff87166004820152336024820152604401610937565b600b5461ffff9081169086161080611172575060c861ffff8616115b156111c257600b546040517fa738697600000000000000000000000000000000000000000000000000000000815261ffff8088166004830152909116602482015260c86044820152606401610937565b600b5463ffffffff620100009091048116908516111561122957600b546040517ff5d7e01e00000000000000000000000000000000000000000000000000000000815263ffffffff8087166004830152620100009092049091166024820152604401610937565b6101f463ffffffff8416111561127b576040517f47386bec00000000000000000000000000000000000000000000000000000000815263ffffffff841660048201526101f46024820152604401610937565b6000611288826001615bd2565b6040805160208082018c9052338284015267ffffffffffffffff808c16606084015284166080808401919091528351808403909101815260a08301845280519082012060c083018d905260e080840182905284518085039091018152610100909301909352815191012091925060009182916040805160208101849052439181019190915267ffffffffffffffff8c16606082015263ffffffff808b166080830152891660a08201523360c0820152919350915060e001604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0018152828252805160209182012060008681526009835283902055848352820183905261ffff8a169082015263ffffffff808916606083015287166080820152339067ffffffffffffffff8b16908c907f63373d1c4696214b898952999c9aaec57dac1ee2723cec59bea6888f489a97729060a00160405180910390a45033600090815260026020908152604080832067ffffffffffffffff808d16855292529091208054919093167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009091161790915591505095945050505050565b600b546601000000000000900460ff161561148b576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600860205260409020546bffffffffffffffffffffffff808316911610156114e5576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b33600090815260086020526040812080548392906115129084906bffffffffffffffffffffffff16615c8d565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555080600560088282829054906101000a90046bffffffffffffffffffffffff166115699190615c8d565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb83836040518363ffffffff1660e01b815260040161162192919073ffffffffffffffffffffffffffffffffffffffff9290921682526bffffffffffffffffffffffff16602082015260400190565b602060405180830381600087803b15801561163b57600080fd5b505af115801561164f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061167391906154db565b6116a9576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050565b6116b56134bf565b6040805180820182526000916116e4919084906002908390839080828437600092019190915250612c6d915050565b60008181526006602052604090205490915073ffffffffffffffffffffffffffffffffffffffff1615611746576040517f4a0b8fa700000000000000000000000000000000000000000000000000000000815260048101829052602401610937565b600081815260066020908152604080832080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff88169081179091556007805460018101825594527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688909301849055518381527fe729ae16526293f74ade739043022254f1489f616295a25bf72dfb4511ed73b89101610c32565b67ffffffffffffffff8216600090815260036020526040902054829073ffffffffffffffffffffffffffffffffffffffff1680611860576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff8216146118c7576040517fd8a3fb5200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610937565b600b546601000000000000900460ff161561190e576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff841660009081526003602052604090206002015460641415611965576040517f05a48e0f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316600090815260026020908152604080832067ffffffffffffffff808916855292529091205416156119ac57610a5a565b73ffffffffffffffffffffffffffffffffffffffff8316600081815260026020818152604080842067ffffffffffffffff8a1680865290835281852080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000166001908117909155600384528286209094018054948501815585529382902090920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001685179055905192835290917f43dc749a04ac8fb825cbd514f7c0e13f13bc6f2ee66043b76629d51776cff8e09101610a51565b60015473ffffffffffffffffffffffffffffffffffffffff163314611b06576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610937565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b600b546601000000000000900460ff1615611bc9576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff811660009081526003602052604090205473ffffffffffffffffffffffffffffffffffffffff16611c2f576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff811660009081526003602052604090206001015473ffffffffffffffffffffffffffffffffffffffff163314611cd15767ffffffffffffffff8116600090815260036020526040908190206001015490517fd084e97500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610937565b67ffffffffffffffff81166000818152600360209081526040918290208054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560019093018054909316909255835173ffffffffffffffffffffffffffffffffffffffff909116808252928101919091529092917f6f1dc65165ffffedfd8e507b4a0f1fcfdada045ed11f6c26ba27cedfe87802f0910160405180910390a25050565b67ffffffffffffffff8216600090815260036020526040902054829073ffffffffffffffffffffffffffffffffffffffff1680611de5576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821614611e4c576040517fd8a3fb5200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610937565b600b546601000000000000900460ff1615611e93576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316600090815260026020908152604080832067ffffffffffffffff808916855292529091205416611f2e576040517ff0019fe600000000000000000000000000000000000000000000000000000000815267ffffffffffffffff8516600482015273ffffffffffffffffffffffffffffffffffffffff84166024820152604401610937565b67ffffffffffffffff8416600090815260036020908152604080832060020180548251818502810185019093528083529192909190830182828015611fa957602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311611f7e575b50505050509050600060018251611fc09190615c76565b905060005b825181101561215f578573ffffffffffffffffffffffffffffffffffffffff16838281518110611ff757611ff7615dbc565b602002602001015173ffffffffffffffffffffffffffffffffffffffff16141561214d57600083838151811061202f5761202f615dbc565b6020026020010151905080600360008a67ffffffffffffffff1667ffffffffffffffff168152602001908152602001600020600201838154811061207557612075615dbc565b600091825260208083209190910180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff949094169390931790925567ffffffffffffffff8a1681526003909152604090206002018054806120ef576120ef615d8d565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690550190555061215f565b8061215781615cba565b915050611fc5565b5073ffffffffffffffffffffffffffffffffffffffff8516600081815260026020908152604080832067ffffffffffffffff8b168085529083529281902080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001690555192835290917f182bff9831466789164ca77075fffd84916d35a8180ba73c27e45634549b445b91015b60405180910390a2505050505050565b600b546000906601000000000000900460ff1615612247576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005805467ffffffffffffffff1690600061226183615cf3565b82546101009290920a67ffffffffffffffff8181021990931691831602179091556005541690506000806040519080825280602002602001820160405280156122b4578160200160208202803683370190505b506040805180820182526000808252602080830182815267ffffffffffffffff888116808552600484528685209551865493516bffffffffffffffffffffffff9091167fffffffffffffffffffffffff0000000000000000000000000000000000000000948516176c010000000000000000000000009190931602919091179094558451606081018652338152808301848152818701888152958552600384529590932083518154831673ffffffffffffffffffffffffffffffffffffffff918216178255955160018201805490931696169590951790559151805194955090936123a592600285019201906150c5565b505060405133815267ffffffffffffffff841691507f464722b4166576d3dcbba877b999bc35cf911f4eaf434b7eba68fa113951d0bf9060200160405180910390a250905090565b67ffffffffffffffff81166000908152600360205260408120548190819060609073ffffffffffffffffffffffffffffffffffffffff1661245a576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff80861660009081526004602090815260408083205460038352928190208054600290910180548351818602810186019094528084526bffffffffffffffffffffffff8616966c010000000000000000000000009096049095169473ffffffffffffffffffffffffffffffffffffffff90921693909291839183018282801561252157602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff1681526001909101906020018083116124f6575b5050505050905093509350935093509193509193565b600b546601000000000000900460ff161561257e576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146125ed576040517f44b0e3c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60208114612627576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061263582840184615792565b67ffffffffffffffff811660009081526003602052604090205490915073ffffffffffffffffffffffffffffffffffffffff1661269e576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff8116600090815260046020526040812080546bffffffffffffffffffffffff16918691906126d58385615bfe565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555084600560088282829054906101000a90046bffffffffffffffffffffffff1661272c9190615bfe565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508167ffffffffffffffff167fd39ec07f4e209f627a4c427971473820dc129761ba28de8906bd56f57101d4f88287846127939190615bba565b604080519283526020830191909152016121ed565b600b546000906601000000000000900460ff16156127f2576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005a9050600080600061280687876139b5565b9250925092506000866060015163ffffffff1667ffffffffffffffff81111561283157612831615deb565b60405190808252806020026020018201604052801561285a578160200160208202803683370190505b50905060005b876060015163ffffffff168110156128ce5760408051602081018590529081018290526060016040516020818303038152906040528051906020012060001c8282815181106128b1576128b1615dbc565b6020908102919091010152806128c681615cba565b915050612860565b506000838152600960205260408082208290555181907f1fe543e300000000000000000000000000000000000000000000000000000000906129169087908690602401615ab4565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252600b80547fffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffff166601000000000000179055908a015160808b01519192506000916129e49163ffffffff169084613d04565b600b80547fffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffff1690556020808c01805167ffffffffffffffff9081166000908152600490935260408084205492518216845290922080549394506c01000000000000000000000000918290048316936001939192600c92612a68928692900416615bd2565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506000612abf8a600b600001600b9054906101000a900463ffffffff1663ffffffff16612ab985612c9d565b3a613d52565b6020808e015167ffffffffffffffff166000908152600490915260409020549091506bffffffffffffffffffffffff80831691161015612b2b576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808d015167ffffffffffffffff1660009081526004909152604081208054839290612b679084906bffffffffffffffffffffffff16615c8d565b82546101009290920a6bffffffffffffffffffffffff81810219909316918316021790915560008b81526006602090815260408083205473ffffffffffffffffffffffffffffffffffffffff1683526008909152812080548594509092612bd091859116615bfe565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550877f7dffc5ae5ee4e2e4df1651cf6ad329a73cebdb728f37ea0187b9b17e036756e4888386604051612c53939291909283526bffffffffffffffffffffffff9190911660208301521515604082015260600190565b60405180910390a299505050505050505050505b92915050565b600081604051602001612c8091906158e3565b604051602081830303815290604052805190602001209050919050565b6040805161012081018252600c5463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c010000000000000000000000008104831660608301527001000000000000000000000000000000008104909216608082015262ffffff740100000000000000000000000000000000000000008304811660a08301819052770100000000000000000000000000000000000000000000008404821660c08401527a0100000000000000000000000000000000000000000000000000008404821660e08401527d0100000000000000000000000000000000000000000000000000000000009093041661010082015260009167ffffffffffffffff841611612dbb575192915050565b8267ffffffffffffffff168160a0015162ffffff16108015612df057508060c0015162ffffff168367ffffffffffffffff1611155b15612dff576020015192915050565b8267ffffffffffffffff168160c0015162ffffff16108015612e3457508060e0015162ffffff168367ffffffffffffffff1611155b15612e43576040015192915050565b8267ffffffffffffffff168160e0015162ffffff16108015612e79575080610100015162ffffff168367ffffffffffffffff1611155b15612e88576060015192915050565b6080015192915050565b67ffffffffffffffff8216600090815260036020526040902054829073ffffffffffffffffffffffffffffffffffffffff1680612efb576040517f1f6a65b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821614612f62576040517fd8a3fb5200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610937565b600b546601000000000000900460ff1615612fa9576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612fb284613257565b15612fe9576040517fb42f66e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610a5a8484613542565b612ffb6134bf565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906370a082319060240160206040518083038186803b15801561308357600080fd5b505afa158015613097573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130bb91906154fd565b6005549091506801000000000000000090046bffffffffffffffffffffffff168181111561311f576040517fa99da3020000000000000000000000000000000000000000000000000000000081526004810182905260248101839052604401610937565b818110156132525760006131338284615c76565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152602482018390529192507f00000000000000000000000000000000000000000000000000000000000000009091169063a9059cbb90604401602060405180830381600087803b1580156131c857600080fd5b505af11580156131dc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061320091906154db565b506040805173ffffffffffffffffffffffffffffffffffffffff86168152602081018390527f59bfc682b673f8cbf945f1e454df9334834abf7dfe7f92237ca29ecb9b436600910160405180910390a1505b505050565b67ffffffffffffffff811660009081526003602090815260408083208151606081018352815473ffffffffffffffffffffffffffffffffffffffff9081168252600183015416818501526002820180548451818702810187018652818152879693958601939092919083018282801561330657602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff1681526001909101906020018083116132db575b505050505081525050905060005b8160400151518110156134a45760005b60075481101561349157600061345a6007838154811061334657613346615dbc565b90600052602060002001548560400151858151811061336757613367615dbc565b602002602001015188600260008960400151898151811061338a5761338a615dbc565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040908101600090812067ffffffffffffffff808f168352935220541660408051602080820187905273ffffffffffffffffffffffffffffffffffffffff959095168183015267ffffffffffffffff9384166060820152919092166080808301919091528251808303909101815260a08201835280519084012060c082019490945260e080820185905282518083039091018152610100909101909152805191012091565b506000818152600960205260409020549091501561347e5750600195945050505050565b508061348981615cba565b915050613324565b508061349c81615cba565b915050613314565b5060009392505050565b6134b66134bf565b61086881613e5a565b60005473ffffffffffffffffffffffffffffffffffffffff163314613540576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610937565b565b600b546601000000000000900460ff1615613589576040517fed3ba6a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff821660009081526003602090815260408083208151606081018352815473ffffffffffffffffffffffffffffffffffffffff90811682526001830154168185015260028201805484518187028101870186528181529295939486019383018282801561363457602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311613609575b5050509190925250505067ffffffffffffffff80851660009081526004602090815260408083208151808301909252546bffffffffffffffffffffffff81168083526c01000000000000000000000000909104909416918101919091529293505b83604001515181101561373b5760026000856040015183815181106136bc576136bc615dbc565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040908101600090812067ffffffffffffffff8a168252909252902080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001690558061373381615cba565b915050613695565b5067ffffffffffffffff8516600090815260036020526040812080547fffffffffffffffffffffffff00000000000000000000000000000000000000009081168255600182018054909116905590613796600283018261514f565b505067ffffffffffffffff8516600090815260046020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055600580548291906008906138069084906801000000000000000090046bffffffffffffffffffffffff16615c8d565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85836bffffffffffffffffffffffff166040518363ffffffff1660e01b81526004016138be92919073ffffffffffffffffffffffffffffffffffffffff929092168252602082015260400190565b602060405180830381600087803b1580156138d857600080fd5b505af11580156138ec573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061391091906154db565b613946576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805173ffffffffffffffffffffffffffffffffffffffff861681526bffffffffffffffffffffffff8316602082015267ffffffffffffffff8716917fe8ed5b475a5b5987aa9165e8731bb78043f39eee32ec5a1169a89e27fcd49815910160405180910390a25050505050565b60008060006139c78560000151612c6d565b60008181526006602052604090205490935073ffffffffffffffffffffffffffffffffffffffff1680613a29576040517f77f5b84c00000000000000000000000000000000000000000000000000000000815260048101859052602401610937565b6080860151604051613a48918691602001918252602082015260400190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291815281516020928301206000818152600990935291205490935080613ac5576040517f3688124a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85516020808801516040808a015160608b015160808c01519251613b3e968b96909594910195865267ffffffffffffffff948516602087015292909316604085015263ffffffff908116606085015291909116608083015273ffffffffffffffffffffffffffffffffffffffff1660a082015260c00190565b604051602081830303815290604052805190602001208114613b8c576040517fd529142c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b855167ffffffffffffffff164080613cb05786516040517fe9413d3800000000000000000000000000000000000000000000000000000000815267ffffffffffffffff90911660048201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063e9413d389060240160206040518083038186803b158015613c3057600080fd5b505afa158015613c44573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613c6891906154fd565b905080613cb05786516040517f175dadad00000000000000000000000000000000000000000000000000000000815267ffffffffffffffff9091166004820152602401610937565b6000886080015182604051602001613cd2929190918252602082015260400190565b6040516020818303038152906040528051906020012060001c9050613cf78982613f50565b9450505050509250925092565b60005a611388811015613d1657600080fd5b611388810390508460408204820311613d2e57600080fd5b50823b613d3a57600080fd5b60008083516020850160008789f190505b9392505050565b600080613d5d613fd9565b905060008113613d9c576040517f43d4cf6600000000000000000000000000000000000000000000000000000000815260048101829052602401610937565b6000815a613daa8989615bba565b613db49190615c76565b613dc686670de0b6b3a7640000615c39565b613dd09190615c39565b613dda9190615c25565b90506000613df363ffffffff871664e8d4a51000615c39565b9050613e0b816b033b2e3c9fd0803ce8000000615c76565b821115613e44576040517fe80fa38100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613e4e8183615bba565b98975050505050505050565b73ffffffffffffffffffffffffffffffffffffffff8116331415613eda576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610937565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000613f848360000151846020015185604001518660600151868860a001518960c001518a60e001518b61010001516140ed565b60038360200151604051602001613f9c929190615aa0565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101209392505050565b600b54604080517ffeaf968c0000000000000000000000000000000000000000000000000000000081529051600092670100000000000000900463ffffffff169182151591849182917f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169163feaf968c9160048083019260a0929190829003018186803b15801561407f57600080fd5b505afa158015614093573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140b791906157d7565b5094509092508491505080156140db57506140d28242615c76565b8463ffffffff16105b156140e55750600a545b949350505050565b6140f6896143c4565b61415c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f7075626c6963206b6579206973206e6f74206f6e2063757276650000000000006044820152606401610937565b614165886143c4565b6141cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f67616d6d61206973206e6f74206f6e20637572766500000000000000000000006044820152606401610937565b6141d4836143c4565b61423a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f6347616d6d615769746e657373206973206e6f74206f6e2063757276650000006044820152606401610937565b614243826143c4565b6142a9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f73486173685769746e657373206973206e6f74206f6e206375727665000000006044820152606401610937565b6142b5878a888761451f565b61431b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f6164647228632a706b2b732a6729213d5f755769746e657373000000000000006044820152606401610937565b60006143278a876146c2565b9050600061433a898b878b868989614726565b9050600061434b838d8d8a866148ae565b9050808a146143b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f696e76616c69642070726f6f66000000000000000000000000000000000000006044820152606401610937565b505050505050505050505050565b80516000907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f11614451576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f696e76616c696420782d6f7264696e61746500000000000000000000000000006044820152606401610937565b60208201517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f116144de576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f696e76616c696420792d6f7264696e61746500000000000000000000000000006044820152606401610937565b60208201517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f9080096145188360005b602002015161490c565b1492915050565b600073ffffffffffffffffffffffffffffffffffffffff821661459e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f626164207769746e6573730000000000000000000000000000000000000000006044820152606401610937565b6020840151600090600116156145b557601c6145b8565b601b5b905060007ffffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd03641418587600060200201510986517ffffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141918203925060009190890987516040805160008082526020820180845287905260ff88169282019290925260608101929092526080820183905291925060019060a0016020604051602081039080840390855afa15801561466f573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015173ffffffffffffffffffffffffffffffffffffffff9081169088161495505050505050949350505050565b6146ca61516d565b6146f7600184846040516020016146e3939291906158c2565b604051602081830303815290604052614964565b90505b614703816143c4565b612c6757805160408051602081019290925261471f91016146e3565b90506146fa565b61472e61516d565b825186517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f90819006910614156147c1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f706f696e747320696e2073756d206d7573742062652064697374696e637400006044820152606401610937565b6147cc8789886149cd565b614832576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4669727374206d756c20636865636b206661696c6564000000000000000000006044820152606401610937565b61483d8486856149cd565b6148a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f5365636f6e64206d756c20636865636b206661696c65640000000000000000006044820152606401610937565b613e4e868484614b5a565b6000600286868685876040516020016148cc96959493929190615850565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101209695505050505050565b6000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f80848509840990507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f600782089392505050565b61496c61516d565b61497582614c89565b815261498a61498582600061450e565b614cde565b6020820181905260029006600114156149c8576020810180517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f0390525b919050565b600082614a36576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f7a65726f207363616c61720000000000000000000000000000000000000000006044820152606401610937565b83516020850151600090614a4c90600290615d1b565b15614a5857601c614a5b565b601b5b905060007ffffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd03641418387096040805160008082526020820180845281905260ff86169282019290925260608101869052608081018390529192509060019060a0016020604051602081039080840390855afa158015614adb573d6000803e3d6000fd5b505050602060405103519050600086604051602001614afa919061583e565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152919052805160209091012073ffffffffffffffffffffffffffffffffffffffff92831692169190911498975050505050505050565b614b6261516d565b835160208086015185519186015160009384938493614b8393909190614d18565b919450925090507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f858209600114614c17576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f696e765a206d75737420626520696e7665727365206f66207a000000000000006044820152606401610937565b60405180604001604052807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f80614c5057614c50615d5e565b87860981526020017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8785099052979650505050505050565b805160208201205b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f81106149c857604080516020808201939093528151808203840181529082019091528051910120614c91565b6000612c67826002614d117ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f6001615bba565b901c614eae565b60008080600180827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f897ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f038808905060007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f038a0890506000614dc083838585614fa2565b9098509050614dd188828e88614ffa565b9098509050614de288828c87614ffa565b90985090506000614df58d878b85614ffa565b9098509050614e0688828686614fa2565b9098509050614e1788828e89614ffa565b9098509050818114614e9a577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f818a0998507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f82890997507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8183099650614e9e565b8196505b5050505050509450945094915050565b600080614eb961518b565b6020808252818101819052604082015260608101859052608081018490527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f60a0820152614f056151a9565b60208160c08460057ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa925082614f98576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6269674d6f64457870206661696c7572652100000000000000000000000000006044820152606401610937565b5195945050505050565b6000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8487097ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8487099097909650945050505050565b600080807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f878509905060007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f87877ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f030990507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8183087ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f86890990999098509650505050505050565b82805482825590600052602060002090810192821561513f579160200282015b8281111561513f57825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9091161782556020909201916001909101906150e5565b5061514b9291506151c7565b5090565b508054600082559060005260206000209081019061086891906151c7565b60405180604001604052806002906020820280368337509192915050565b6040518060c001604052806006906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b5b8082111561514b57600081556001016151c8565b803573ffffffffffffffffffffffffffffffffffffffff811681146149c857600080fd5b8060408101831015612c6757600080fd5b600082601f83011261522257600080fd5b6040516040810181811067ffffffffffffffff8211171561524557615245615deb565b806040525080838560408601111561525c57600080fd5b60005b600281101561527e57813583526020928301929091019060010161525f565b509195945050505050565b600060a0828403121561529b57600080fd5b60405160a0810181811067ffffffffffffffff821117156152be576152be615deb565b6040529050806152cd83615353565b81526152db60208401615353565b60208201526152ec6040840161533f565b60408201526152fd6060840161533f565b606082015261530e608084016151dc565b60808201525092915050565b803561ffff811681146149c857600080fd5b803562ffffff811681146149c857600080fd5b803563ffffffff811681146149c857600080fd5b803567ffffffffffffffff811681146149c857600080fd5b805169ffffffffffffffffffff811681146149c857600080fd5b60006020828403121561539757600080fd5b613d4b826151dc565b600080606083850312156153b357600080fd5b6153bc836151dc565b91506153cb8460208501615200565b90509250929050565b600080600080606085870312156153ea57600080fd5b6153f3856151dc565b935060208501359250604085013567ffffffffffffffff8082111561541757600080fd5b818701915087601f83011261542b57600080fd5b81358181111561543a57600080fd5b88602082850101111561544c57600080fd5b95989497505060200194505050565b6000806040838503121561546e57600080fd5b615477836151dc565b915060208301356bffffffffffffffffffffffff8116811461549857600080fd5b809150509250929050565b6000604082840312156154b557600080fd5b613d4b8383615200565b6000604082840312156154d157600080fd5b613d4b8383615211565b6000602082840312156154ed57600080fd5b81518015158114613d4b57600080fd5b60006020828403121561550f57600080fd5b5051919050565b600080600080600060a0868803121561552e57600080fd5b8535945061553e60208701615353565b935061554c6040870161531a565b925061555a6060870161533f565b91506155686080870161533f565b90509295509295909350565b60008082840361024081121561558957600080fd5b6101a08082121561559957600080fd5b6155a1615b90565b91506155ad8686615211565b82526155bc8660408701615211565b60208301526080850135604083015260a0850135606083015260c085013560808301526155eb60e086016151dc565b60a08301526101006155ff87828801615211565b60c0840152615612876101408801615211565b60e0840152610180860135818401525081935061563186828701615289565b925050509250929050565b6000806000806000808688036101c081121561565757600080fd5b6156608861531a565b965061566e6020890161533f565b955061567c6040890161533f565b945061568a6060890161533f565b935060808801359250610120807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60830112156156c557600080fd5b6156cd615b90565b91506156db60a08a0161533f565b82526156e960c08a0161533f565b60208301526156fa60e08a0161533f565b604083015261010061570d818b0161533f565b606084015261571d828b0161533f565b608084015261572f6101408b0161532c565b60a08401526157416101608b0161532c565b60c08401526157536101808b0161532c565b60e08401526157656101a08b0161532c565b818401525050809150509295509295509295565b60006020828403121561578b57600080fd5b5035919050565b6000602082840312156157a457600080fd5b613d4b82615353565b600080604083850312156157c057600080fd5b6157c983615353565b91506153cb602084016151dc565b600080600080600060a086880312156157ef57600080fd5b6157f88661536b565b94506020860151935060408601519250606086015191506155686080870161536b565b8060005b6002811015610a5a57815184526020938401939091019060010161581f565b615848818361581b565b604001919050565b868152615860602082018761581b565b61586d606082018661581b565b61587a60a082018561581b565b61588760e082018461581b565b60609190911b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166101208201526101340195945050505050565b8381526158d2602082018461581b565b606081019190915260800192915050565b60408101612c67828461581b565b600060208083528351808285015260005b8181101561591e57858101830151858201604001528201615902565b81811115615930576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006060820161ffff86168352602063ffffffff86168185015260606040850152818551808452608086019150828701935060005b818110156159b557845183529383019391830191600101615999565b509098975050505050505050565b60006101c08201905061ffff8816825263ffffffff808816602084015280871660408401528086166060840152846080840152835481811660a0850152615a1760c08501838360201c1663ffffffff169052565b615a2e60e08501838360401c1663ffffffff169052565b615a466101008501838360601c1663ffffffff169052565b615a5e6101208501838360801c1663ffffffff169052565b62ffffff60a082901c811661014086015260b882901c811661016086015260d082901c1661018085015260e81c6101a090930192909252979650505050505050565b82815260608101613d4b602083018461581b565b6000604082018483526020604081850152818551808452606086019150828701935060005b81811015615af557845183529383019391830191600101615ad9565b5090979650505050505050565b6000608082016bffffffffffffffffffffffff87168352602067ffffffffffffffff87168185015273ffffffffffffffffffffffffffffffffffffffff80871660408601526080606086015282865180855260a087019150838801945060005b81811015615b80578551841683529484019491840191600101615b62565b50909a9950505050505050505050565b604051610120810167ffffffffffffffff81118282101715615bb457615bb4615deb565b60405290565b60008219821115615bcd57615bcd615d2f565b500190565b600067ffffffffffffffff808316818516808303821115615bf557615bf5615d2f565b01949350505050565b60006bffffffffffffffffffffffff808316818516808303821115615bf557615bf5615d2f565b600082615c3457615c34615d5e565b500490565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615615c7157615c71615d2f565b500290565b600082821015615c8857615c88615d2f565b500390565b60006bffffffffffffffffffffffff83811690831681811015615cb257615cb2615d2f565b039392505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415615cec57615cec615d2f565b5060010190565b600067ffffffffffffffff80831681811415615d1157615d11615d2f565b6001019392505050565b600082615d2a57615d2a615d5e565b500690565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fdfea164736f6c6343000806000a") ctorArgs, err := utils.ABIEncode(`[{"type":"address"}, {"type":"address"}, {"type":"address"}]`, linkAddress, bhsAddress, linkEthFeed) require.NoError(t, err) bytecode = append(bytecode, ctorArgs...) - nonce, err := backend.PendingNonceAt(context.Background(), neil.From) + nonce, err := backend.PendingNonceAt(ctx, neil.From) require.NoError(t, err) - gasPrice, err := backend.SuggestGasPrice(context.Background()) + gasPrice, err := backend.SuggestGasPrice(ctx) require.NoError(t, err) unsignedTx := gethtypes.NewContractCreation(nonce, big.NewInt(0), 15e6, gasPrice, bytecode) signedTx, err := neil.Signer(neil.From, unsignedTx) require.NoError(t, err) - err = backend.SendTransaction(context.Background(), signedTx) + err = backend.SendTransaction(ctx, signedTx) require.NoError(t, err, "could not deploy old vrf coordinator to simulated blockchain") backend.Commit() - receipt, err := backend.TransactionReceipt(context.Background(), signedTx.Hash()) + receipt, err := backend.TransactionReceipt(ctx, signedTx.Hash()) require.NoError(t, err) oldRootContractAddress := receipt.ContractAddress require.NotEqual(t, common.HexToAddress("0x0"), oldRootContractAddress, "old vrf coordinator address equal to zero address, deployment failed") @@ -454,7 +455,7 @@ func sendEth(t *testing.T, key ethkey.KeyV2, ec *backends.SimulatedBackend, to c nonce, err := ec.PendingNonceAt(testutils.Context(t), key.Address) require.NoError(t, err) tx := gethtypes.NewTx(&gethtypes.DynamicFeeTx{ - ChainID: big.NewInt(1337), + ChainID: testutils.SimulatedChainID, Nonce: nonce, GasTipCap: big.NewInt(1), GasFeeCap: assets.GWei(10).ToInt(), // block base fee in sim @@ -463,7 +464,7 @@ func sendEth(t *testing.T, key ethkey.KeyV2, ec *backends.SimulatedBackend, to c Value: big.NewInt(0).Mul(big.NewInt(int64(eth)), big.NewInt(1e18)), Data: nil, }) - signedTx, err := gethtypes.SignTx(tx, gethtypes.NewLondonSigner(big.NewInt(1337)), key.ToEcdsaPrivKey()) + signedTx, err := gethtypes.SignTx(tx, gethtypes.NewLondonSigner(testutils.SimulatedChainID), key.ToEcdsaPrivKey()) require.NoError(t, err) err = ec.SendTransaction(testutils.Context(t), signedTx) require.NoError(t, err) @@ -762,32 +763,42 @@ func assertNumRandomWords( } } -func mine(t *testing.T, requestID, subID *big.Int, backend *backends.SimulatedBackend, db *sqlx.DB, vrfVersion vrfcommon.Version) bool { - var query string +func mine(t *testing.T, requestID, subID *big.Int, backend *backends.SimulatedBackend, db *sqlx.DB, vrfVersion vrfcommon.Version, chainId *big.Int) bool { + cfg := pgtest.NewQConfig(false) + txstore := txmgr.NewTxStore(db, logger.TestLogger(t), cfg) + var metaField string if vrfVersion == vrfcommon.V2Plus { - query = ConfirmedEthTxesV2PlusQuery + metaField = "GlobalSubId" } else if vrfVersion == vrfcommon.V2 { - query = ConfirmedEthTxesV2Query + metaField = "SubId" } else { t.Errorf("unsupported vrf version %s", vrfVersion) } + return gomega.NewWithT(t).Eventually(func() bool { backend.Commit() - var txs []txmgr.DbEthTx - err := db.Select(&txs, query, common.BytesToHash(requestID.Bytes()).String(), subID.String()) + txes, err := txstore.FindTxesByMetaFieldAndStates(testutils.Context(t), metaField, subID.String(), []txmgrtypes.TxState{txmgrcommon.TxConfirmed}, chainId) require.NoError(t, err) - t.Log("num txs", len(txs)) - return len(txs) == 1 + for _, tx := range txes { + meta, err := tx.GetMeta() + require.NoError(t, err) + if meta.RequestID.String() == common.BytesToHash(requestID.Bytes()).String() { + return true + } + } + return false }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) } -func mineBatch(t *testing.T, requestIDs []*big.Int, subID *big.Int, backend *backends.SimulatedBackend, db *sqlx.DB, vrfVersion vrfcommon.Version) bool { +func mineBatch(t *testing.T, requestIDs []*big.Int, subID *big.Int, backend *backends.SimulatedBackend, db *sqlx.DB, vrfVersion vrfcommon.Version, chainId *big.Int) bool { requestIDMap := map[string]bool{} - var query string + cfg := pgtest.NewQConfig(false) + txstore := txmgr.NewTxStore(db, logger.TestLogger(t), cfg) + var metaField string if vrfVersion == vrfcommon.V2Plus { - query = ConfirmedEthTxesV2PlusBatchQuery + metaField = "GlobalSubId" } else if vrfVersion == vrfcommon.V2 { - query = ConfirmedEthTxesV2BatchQuery + metaField = "SubId" } else { t.Errorf("unsupported vrf version %s", vrfVersion) } @@ -796,12 +807,10 @@ func mineBatch(t *testing.T, requestIDs []*big.Int, subID *big.Int, backend *bac } return gomega.NewWithT(t).Eventually(func() bool { backend.Commit() - var txs []txmgr.DbEthTx - require.NoError(t, db.Select(&txs, query, subID.String())) - for _, tx := range txs { - var evmTx txmgr.Tx - tx.ToTx(&evmTx) - meta, err := evmTx.GetMeta() + txes, err := txstore.FindTxesByMetaFieldAndStates(testutils.Context(t), metaField, subID.String(), []txmgrtypes.TxState{txmgrcommon.TxConfirmed}, chainId) + require.NoError(t, err) + for _, tx := range txes { + meta, err := tx.GetMeta() require.NoError(t, err) for _, requestID := range meta.RequestIDs { if _, ok := requestIDMap[requestID.String()]; ok { @@ -920,6 +929,7 @@ func TestVRFV2Integration_SingleConsumer_HappyPath(t *testing.T) { } func TestVRFV2Integration_SingleConsumer_EOA_Request(t *testing.T) { + t.Skip("questionable value of this test") t.Parallel() ownerKey := cltest.MustGenerateRandomKey(t) uni := newVRFCoordinatorV2Universe(t, ownerKey, 1) @@ -935,6 +945,7 @@ func TestVRFV2Integration_SingleConsumer_EOA_Request(t *testing.T) { } func TestVRFV2Integration_SingleConsumer_EOA_Request_Batching_Enabled(t *testing.T) { + t.Skip("questionable value of this test") t.Parallel() ownerKey := cltest.MustGenerateRandomKey(t) uni := newVRFCoordinatorV2Universe(t, ownerKey, 1) @@ -963,7 +974,7 @@ func testEoa( key1 := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, _ := heavyweight.FullTestDBV2(t, "vrfv2_singleconsumer_eoa_request", func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), toml.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), @@ -1124,7 +1135,7 @@ func TestVRFV2Integration_SingleConsumer_Wrapper(t *testing.T) { callBackGasLimit := int64(100_000) // base callback gas. key1 := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_singleconsumer_wrapper", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), toml.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), @@ -1188,7 +1199,7 @@ func TestVRFV2Integration_SingleConsumer_Wrapper(t *testing.T) { }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) // Mine the fulfillment that was queued. - mine(t, requestID, new(big.Int).SetUint64(wrapperSubID), uni.backend, db, vrfcommon.V2) + mine(t, requestID, new(big.Int).SetUint64(wrapperSubID), uni.backend, db, vrfcommon.V2, testutils.SimulatedChainID) // Assert correct state of RandomWordsFulfilled event. assertRandomWordsFulfilled(t, requestID, true, uni.rootContract, false) @@ -1204,7 +1215,7 @@ func TestVRFV2Integration_Wrapper_High_Gas(t *testing.T) { key1 := cltest.MustGenerateRandomKey(t) callBackGasLimit := int64(2_000_000) // base callback gas. gasLanePriceWei := assets.GWei(10) - config, db := heavyweight.FullTestDBV2(t, "vrfv2_wrapper_high_gas_revert", func(c *chainlink.Config, s *chainlink.Secrets) { + config, db := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, assets.GWei(10), toml.KeySpecific{ // Gas lane. Key: ptr(key1.EIP55Address), @@ -1212,6 +1223,8 @@ func TestVRFV2Integration_Wrapper_High_Gas(t *testing.T) { })(c, s) c.EVM[0].GasEstimator.LimitDefault = ptr[uint32](3_500_000) c.EVM[0].MinIncomingConfirmations = ptr[uint32](2) + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) }) ownerKey := cltest.MustGenerateRandomKey(t) uni := newVRFCoordinatorV2Universe(t, ownerKey, 1) @@ -1268,7 +1281,7 @@ func TestVRFV2Integration_Wrapper_High_Gas(t *testing.T) { }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) // Mine the fulfillment that was queued. - mine(t, requestID, new(big.Int).SetUint64(wrapperSubID), uni.backend, db, vrfcommon.V2) + mine(t, requestID, new(big.Int).SetUint64(wrapperSubID), uni.backend, db, vrfcommon.V2, testutils.SimulatedChainID) // Assert correct state of RandomWordsFulfilled event. assertRandomWordsFulfilled(t, requestID, true, uni.rootContract, false) @@ -1447,7 +1460,10 @@ func simulatedOverrides(t *testing.T, defaultGasPrice *assets.Wei, ks ...toml.Ke if defaultGasPrice != nil { c.EVM[0].GasEstimator.PriceDefault = defaultGasPrice } - c.EVM[0].GasEstimator.LimitDefault = ptr[uint32](2_000_000) + c.EVM[0].GasEstimator.LimitDefault = ptr[uint32](3_500_000) + + c.Feature.LogPoller = ptr(true) + c.EVM[0].LogPollInterval = models.MustNewDuration(1 * time.Second) c.EVM[0].HeadTracker.MaxBufferSize = ptr[uint32](100) c.EVM[0].HeadTracker.SamplingInterval = models.MustNewDuration(0) // Head sampling disabled @@ -1457,7 +1473,7 @@ func simulatedOverrides(t *testing.T, defaultGasPrice *assets.Wei, ks ...toml.Ke c.EVM[0].FinalityDepth = ptr[uint32](15) c.EVM[0].MinIncomingConfirmations = ptr[uint32](1) - c.EVM[0].MinContractPayment = assets.NewLinkFromJuels(100) + c.EVM[0].MinContractPayment = commonassets.NewLinkFromJuels(100) c.EVM[0].KeySpecific = ks } } @@ -1581,7 +1597,7 @@ func TestIntegrationVRFV2(t *testing.T) { gasPrice := assets.GWei(1) key := cltest.MustGenerateRandomKey(t) gasLanePriceWei := assets.GWei(10) - config, _ := heavyweight.FullTestDBV2(t, "vrf_v2_integration", func(c *chainlink.Config, s *chainlink.Secrets) { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { simulatedOverrides(t, gasPrice, toml.KeySpecific{ Key: &key.EIP55Address, GasEstimator: toml.KeySpecificGasEstimator{PriceMax: gasLanePriceWei}, @@ -1599,6 +1615,10 @@ func TestIntegrationVRFV2(t *testing.T) { require.Zero(t, key.Cmp(keys[0])) require.NoError(t, app.Start(testutils.Context(t))) + var chain legacyevm.Chain + chain, err = app.GetRelayers().LegacyEVMChains().Get(testutils.SimulatedChainID.String()) + require.NoError(t, err) + listenerV2 := v22.MakeTestListenerV2(chain) jbs := createVRFJobs( t, @@ -1751,11 +1771,10 @@ func TestIntegrationVRFV2(t *testing.T) { }) // We should see the response count present - chain, err := app.GetRelayers().LegacyEVMChains().Get(big.NewInt(1337).String()) require.NoError(t, err) - - q := pg.NewQ(app.GetSqlxDB(), app.Logger, app.Config.Database()) - counts := vrf.GetStartingResponseCountsV2(q, app.Logger, chain.Client().ConfiguredChainID().Uint64(), chain.Config().EVM().FinalityDepth()) + var counts map[string]uint64 + counts, err = listenerV2.GetStartingResponseCountsV2(testutils.Context(t)) + require.NoError(t, err) t.Log(counts, rf[0].RequestID().String()) assert.Equal(t, uint64(1), counts[rf[0].RequestID().String()]) } @@ -1996,20 +2015,31 @@ func TestFulfillmentCost(t *testing.T) { } func TestStartingCountsV1(t *testing.T) { - cfg, db := heavyweight.FullTestDBNoFixturesV2(t, "vrf_test_starting_counts", nil) - _, err := db.Exec(`INSERT INTO evm.heads (hash, number, parent_hash, created_at, timestamp, evm_chain_id) - VALUES ($1, 4, $2, NOW(), NOW(), 1337)`, utils.NewHash(), utils.NewHash()) - require.NoError(t, err) + cfg, db := heavyweight.FullTestDBNoFixturesV2(t, nil) lggr := logger.TestLogger(t) - q := pg.NewQ(db, lggr, cfg.Database()) - finalityDepth := 3 - counts := vrf.GetStartingResponseCountsV1(q, lggr, 1337, uint32(finalityDepth)) - assert.Equal(t, 0, len(counts)) + qCfg := pgtest.NewQConfig(false) + txStore := txmgr.NewTxStore(db, logger.TestLogger(t), qCfg) ks := keystore.NewInMemory(db, utils.FastScryptParams, lggr, cfg.Database()) + ec := evmclimocks.NewClient(t) + ec.On("ConfiguredChainID").Return(testutils.SimulatedChainID) + ec.On("LatestBlockHeight", mock.Anything).Return(big.NewInt(2), nil).Maybe() + txm := makeTestTxm(t, txStore, ks, ec) + relayExtenders := evmtest.NewChainRelayExtenders(t, evmtest.TestChainOpts{KeyStore: ks.Eth(), Client: ec, DB: db, GeneralConfig: cfg, TxManager: txm}) + legacyChains := evmrelay.NewLegacyChainsFromRelayerExtenders(relayExtenders) + chain, err := legacyChains.Get(testutils.SimulatedChainID.String()) + require.NoError(t, err) + listenerV1 := &v1.Listener{ + Chain: chain, + } + listenerV2 := v22.MakeTestListenerV2(chain) + var counts map[[32]byte]uint64 + counts, err = listenerV1.GetStartingResponseCountsV1(testutils.Context(t)) + require.NoError(t, err) + assert.Equal(t, 0, len(counts)) err = ks.Unlock(testutils.Password) require.NoError(t, err) - k, err := ks.Eth().Create(big.NewInt(1337)) + k, err := ks.Eth().Create(testutils.SimulatedChainID) require.NoError(t, err) b := time.Now() n1, n2, n3, n4 := evmtypes.Nonce(0), evmtypes.Nonce(1), evmtypes.Nonce(2), evmtypes.Nonce(3) @@ -2019,15 +2049,15 @@ func TestStartingCountsV1(t *testing.T) { } md1, err := json.Marshal(&m1) require.NoError(t, err) - md1_ := datatypes.JSON(md1) + md1SQL := sqlutil.JSON(md1) reqID2 := utils.PadByteToHash(0x11) m2 := txmgr.TxMeta{ RequestID: &reqID2, } md2, err := json.Marshal(&m2) - md2_ := datatypes.JSON(md2) + md2SQL := sqlutil.JSON(md2) require.NoError(t, err) - chainID := utils.NewBig(big.NewInt(1337)) + chainID := utils.NewBig(testutils.SimulatedChainID) confirmedTxes := []txmgr.Tx{ { Sequence: &n1, @@ -2037,7 +2067,7 @@ func TestStartingCountsV1(t *testing.T) { InitialBroadcastAt: &b, CreatedAt: b, State: txmgrcommon.TxConfirmed, - Meta: &datatypes.JSON{}, + Meta: &sqlutil.JSON{}, EncodedPayload: []byte{}, ChainID: chainID.ToInt(), }, @@ -2049,7 +2079,7 @@ func TestStartingCountsV1(t *testing.T) { InitialBroadcastAt: &b, CreatedAt: b, State: txmgrcommon.TxConfirmed, - Meta: &md1_, + Meta: &md1SQL, EncodedPayload: []byte{}, ChainID: chainID.ToInt(), }, @@ -2061,7 +2091,7 @@ func TestStartingCountsV1(t *testing.T) { InitialBroadcastAt: &b, CreatedAt: b, State: txmgrcommon.TxConfirmed, - Meta: &md2_, + Meta: &md2SQL, EncodedPayload: []byte{}, ChainID: chainID.ToInt(), }, @@ -2073,7 +2103,7 @@ func TestStartingCountsV1(t *testing.T) { InitialBroadcastAt: &b, CreatedAt: b, State: txmgrcommon.TxConfirmed, - Meta: &md2_, + Meta: &md2SQL, EncodedPayload: []byte{}, ChainID: chainID.ToInt(), }, @@ -2086,6 +2116,7 @@ func TestStartingCountsV1(t *testing.T) { RequestID: &reqID3, }) require.NoError(t, err2) + mdSQL := sqlutil.JSON(md) newNonce := evmtypes.Nonce(i + 1) unconfirmedTxes = append(unconfirmedTxes, txmgr.Tx{ Sequence: &newNonce, @@ -2095,21 +2126,18 @@ func TestStartingCountsV1(t *testing.T) { State: txmgrcommon.TxUnconfirmed, BroadcastAt: &b, InitialBroadcastAt: &b, - Meta: (*datatypes.JSON)(&md), + Meta: &mdSQL, EncodedPayload: []byte{}, ChainID: chainID.ToInt(), }) } - sql := `INSERT INTO evm.txes (nonce, from_address, to_address, encoded_payload, value, gas_limit, state, created_at, broadcast_at, initial_broadcast_at, meta, subject, evm_chain_id, min_confirmations, pipeline_task_run_id) -VALUES (:nonce, :from_address, :to_address, :encoded_payload, :value, :gas_limit, :state, :created_at, :broadcast_at, :initial_broadcast_at, :meta, :subject, :evm_chain_id, :min_confirmations, :pipeline_task_run_id);` - for _, tx := range append(confirmedTxes, unconfirmedTxes...) { - var dbEtx txmgr.DbEthTx - dbEtx.FromTx(&tx) //nolint:gosec // just copying fields - _, err = db.NamedExec(sql, &dbEtx) + txList := append(confirmedTxes, unconfirmedTxes...) + for i := range txList { + err = txStore.InsertTx(&txList[i]) require.NoError(t, err) } - // add evm.tx_attempts for confirmed + // add tx attempt for confirmed broadcastBlock := int64(1) var txAttempts []txmgr.TxAttempt for i := range confirmedTxes { @@ -2124,7 +2152,7 @@ VALUES (:nonce, :from_address, :to_address, :encoded_payload, :value, :gas_limit ChainSpecificFeeLimit: uint32(100), }) } - // add evm.tx_attempts for unconfirmed + // add tx attempt for unconfirmed for i := range unconfirmedTxes { txAttempts = append(txAttempts, txmgr.TxAttempt{ TxID: int64(i + 1 + len(confirmedTxes)), @@ -2139,41 +2167,35 @@ VALUES (:nonce, :from_address, :to_address, :encoded_payload, :value, :gas_limit for _, txAttempt := range txAttempts { t.Log("tx attempt eth tx id: ", txAttempt.TxID) } - sql = `INSERT INTO evm.tx_attempts (eth_tx_id, gas_price, signed_raw_tx, hash, state, created_at, chain_specific_gas_limit) - VALUES (:eth_tx_id, :gas_price, :signed_raw_tx, :hash, :state, :created_at, :chain_specific_gas_limit)` - for _, attempt := range txAttempts { - var dbAttempt txmgr.DbEthTxAttempt - dbAttempt.FromTxAttempt(&attempt) //nolint:gosec // just copying fields - _, err = db.NamedExec(sql, &dbAttempt) + for i := range txAttempts { + err = txStore.InsertTxAttempt(&txAttempts[i]) require.NoError(t, err) } // add evm.receipts - receipts := []txmgr.Receipt{} + receipts := []evmtypes.Receipt{} for i := 0; i < 4; i++ { - receipts = append(receipts, txmgr.Receipt{ + receipts = append(receipts, evmtypes.Receipt{ BlockHash: utils.NewHash(), TxHash: txAttempts[i].Hash, - BlockNumber: broadcastBlock, + BlockNumber: big.NewInt(broadcastBlock), TransactionIndex: 1, - Receipt: evmtypes.Receipt{}, - CreatedAt: time.Now(), }) } - sql = `INSERT INTO evm.receipts (block_hash, tx_hash, block_number, transaction_index, receipt, created_at) - VALUES (:block_hash, :tx_hash, :block_number, :transaction_index, :receipt, :created_at)` - for _, r := range receipts { - _, err2 := db.NamedExec(sql, r) - require.NoError(t, err2) + for i := range receipts { + _, err = txStore.InsertReceipt(&receipts[i]) + require.NoError(t, err) } - counts = vrf.GetStartingResponseCountsV1(q, lggr, 1337, uint32(finalityDepth)) + counts, err = listenerV1.GetStartingResponseCountsV1(testutils.Context(t)) + require.NoError(t, err) assert.Equal(t, 3, len(counts)) assert.Equal(t, uint64(1), counts[utils.PadByteToHash(0x10)]) assert.Equal(t, uint64(2), counts[utils.PadByteToHash(0x11)]) assert.Equal(t, uint64(2), counts[utils.PadByteToHash(0x12)]) - countsV2 := vrf.GetStartingResponseCountsV2(q, lggr, 1337, uint32(finalityDepth)) + countsV2, err := listenerV2.GetStartingResponseCountsV2(testutils.Context(t)) + require.NoError(t, err) t.Log(countsV2) assert.Equal(t, 3, len(countsV2)) assert.Equal(t, uint64(1), countsV2[big.NewInt(0x10).String()]) diff --git a/core/services/vrf/v2/listener_v2.go b/core/services/vrf/v2/listener_v2.go index 31e76b48fdb..5878bf54763 100644 --- a/core/services/vrf/v2/listener_v2.go +++ b/core/services/vrf/v2/listener_v2.go @@ -1,36 +1,24 @@ package v2 import ( - "cmp" "context" - "database/sql" - "fmt" - "math" + "encoding/hex" "math/big" - "slices" "strings" "sync" "time" - "github.com/ethereum/go-ethereum" + "github.com/avast/retry-go/v4" "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/google/uuid" "github.com/pkg/errors" - heaps "github.com/theodesp/go-heaps" "github.com/theodesp/go-heaps/pairing" - "go.uber.org/multierr" + "github.com/smartcontractkit/chainlink-common/pkg/services" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/aggregator_v3_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_vrf_coordinator_v2" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_vrf_coordinator_v2plus" @@ -43,18 +31,17 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" - "github.com/smartcontractkit/chainlink/v2/core/utils" - bigmath "github.com/smartcontractkit/chainlink/v2/core/utils/big_math" ) var ( - _ log.Listener = &listenerV2{} _ job.ServiceCtx = &listenerV2{} coordinatorV2ABI = evmtypes.MustGetABI(vrf_coordinator_v2.VRFCoordinatorV2ABI) coordinatorV2PlusABI = evmtypes.MustGetABI(vrf_coordinator_v2plus_interface.IVRFCoordinatorV2PlusInternalABI) batchCoordinatorV2ABI = evmtypes.MustGetABI(batch_vrf_coordinator_v2.BatchVRFCoordinatorV2ABI) batchCoordinatorV2PlusABI = evmtypes.MustGetABI(batch_vrf_coordinator_v2plus.BatchVRFCoordinatorV2PlusABI) vrfOwnerABI = evmtypes.MustGetABI(vrf_owner.VRFOwnerMetaData.ABI) + // These are the transaction states used when summing up already reserved subscription funds that are about to be used in in-flight transactions + reserveEthLinkQueryStates = []txmgrtypes.TxState{txmgrcommon.TxUnconfirmed, txmgrcommon.TxUnstarted, txmgrcommon.TxInProgress} ) const ( @@ -72,134 +59,59 @@ const ( // backoffFactor is the factor by which to increase the delay each time a request fails. backoffFactor = 1.3 - V2ReservedLinkQuery = `SELECT SUM(CAST(meta->>'MaxLink' AS NUMERIC(78, 0))) - FROM evm.txes - WHERE meta->>'MaxLink' IS NOT NULL - AND evm_chain_id = $1 - AND CAST(meta->>'SubId' AS NUMERIC) = $2 - AND state IN ('unconfirmed', 'unstarted', 'in_progress') - GROUP BY meta->>'SubId'` - - V2PlusReservedLinkQuery = `SELECT SUM(CAST(meta->>'MaxLink' AS NUMERIC(78, 0))) - FROM evm.txes - WHERE meta->>'MaxLink' IS NOT NULL - AND evm_chain_id = $1 - AND CAST(meta->>'GlobalSubId' AS NUMERIC) = $2 - AND state IN ('unconfirmed', 'unstarted', 'in_progress') - GROUP BY meta->>'GlobalSubId'` - - V2PlusReservedEthQuery = `SELECT SUM(CAST(meta->>'MaxEth' AS NUMERIC(78, 0))) - FROM evm.txes - WHERE meta->>'MaxEth' IS NOT NULL - AND evm_chain_id = $1 - AND CAST(meta->>'GlobalSubId' AS NUMERIC) = $2 - AND state IN ('unconfirmed', 'unstarted', 'in_progress') - GROUP BY meta->>'GlobalSubId'` - - CouldNotDetermineIfLogConsumedMsg = "Could not determine if log was already consumed" + txMetaFieldSubId = "SubId" + txMetaGlobalSubId = "GlobalSubId" ) -type errPossiblyInsufficientFunds struct{} - -func (errPossiblyInsufficientFunds) Error() string { - return "Simulation errored, possibly insufficient funds. Request will remain unprocessed until funds are available" -} - -type errBlockhashNotInStore struct{} - -func (errBlockhashNotInStore) Error() string { - return "Blockhash not in store" -} - func New( cfg vrfcommon.Config, feeCfg vrfcommon.FeeConfig, l logger.Logger, - ethClient evmclient.Client, + chain legacyevm.Chain, chainID *big.Int, - logBroadcaster log.Broadcaster, q pg.Q, coordinator CoordinatorV2_X, batchCoordinator batch_vrf_coordinator_v2.BatchVRFCoordinatorV2Interface, vrfOwner vrf_owner.VRFOwnerInterface, aggregator *aggregator_v3_interface.AggregatorV3Interface, - txm txmgr.TxManager, pipelineRunner pipeline.Runner, gethks keystore.Eth, job job.Job, - mailMon *utils.MailboxMonitor, - reqLogs *utils.Mailbox[log.Broadcast], reqAdded func(), - respCount map[string]uint64, - headBroadcaster httypes.HeadBroadcasterRegistry, - deduper *vrfcommon.LogDeduper, + inflightCache vrfcommon.InflightCache, + fulfillmentDeduper *vrfcommon.LogDeduper, ) job.ServiceCtx { return &listenerV2{ - cfg: cfg, - feeCfg: feeCfg, - l: logger.Sugared(l), - ethClient: ethClient, - chainID: chainID, - logBroadcaster: logBroadcaster, - txm: txm, - mailMon: mailMon, - coordinator: coordinator, - batchCoordinator: batchCoordinator, - vrfOwner: vrfOwner, - pipelineRunner: pipelineRunner, - job: job, - q: q, - gethks: gethks, - reqLogs: reqLogs, - chStop: make(chan struct{}), - reqAdded: reqAdded, - respCount: respCount, - blockNumberToReqID: pairing.New(), - headBroadcaster: headBroadcaster, - latestHeadMu: sync.RWMutex{}, - wg: &sync.WaitGroup{}, - aggregator: aggregator, - deduper: deduper, + cfg: cfg, + feeCfg: feeCfg, + l: logger.Sugared(l), + chain: chain, + chainID: chainID, + coordinator: coordinator, + batchCoordinator: batchCoordinator, + vrfOwner: vrfOwner, + pipelineRunner: pipelineRunner, + job: job, + q: q, + gethks: gethks, + chStop: make(chan struct{}), + reqAdded: reqAdded, + blockNumberToReqID: pairing.New(), + latestHeadMu: sync.RWMutex{}, + wg: &sync.WaitGroup{}, + aggregator: aggregator, + inflightCache: inflightCache, + fulfillmentLogDeduper: fulfillmentDeduper, } } -type pendingRequest struct { - confirmedAtBlock uint64 - req RandomWordsRequested - lb log.Broadcast - utcTimestamp time.Time - - // used for exponential backoff when retrying - attempts int - lastTry time.Time -} - -type vrfPipelineResult struct { - err error - // maxFee indicates how much juels (link) or wei (ether) would be paid for the VRF request - // if it were to be fulfilled at the maximum gas price (i.e gas lane gas price). - maxFee *big.Int - // fundsNeeded indicates a "minimum balance" in juels or wei that must be held in the - // subscription's account in order to fulfill the request. - fundsNeeded *big.Int - run *pipeline.Run - payload string - gasLimit uint32 - req pendingRequest - proof VRFProof - reqCommitment RequestCommitment -} - type listenerV2 struct { - utils.StartStopOnce - cfg vrfcommon.Config - feeCfg vrfcommon.FeeConfig - l logger.SugaredLogger - ethClient evmclient.Client - chainID *big.Int - logBroadcaster log.Broadcaster - txm txmgr.TxManager - mailMon *utils.MailboxMonitor + services.StateMachine + cfg vrfcommon.Config + feeCfg vrfcommon.FeeConfig + l logger.SugaredLogger + chain legacyevm.Chain + chainID *big.Int coordinator CoordinatorV2_X batchCoordinator batch_vrf_coordinator_v2.BatchVRFCoordinatorV2Interface @@ -209,26 +121,19 @@ type listenerV2 struct { job job.Job q pg.Q gethks keystore.Eth - reqLogs *utils.Mailbox[log.Broadcast] - chStop utils.StopChan - // We can keep these pending logs in memory because we - // only mark them confirmed once we send a corresponding fulfillment transaction. - // So on node restart in the middle of processing, the lb will resend them. - reqsMu sync.Mutex // Both the log listener and the request handler write to reqs - reqs []pendingRequest + chStop services.StopChan + reqAdded func() // A simple debug helper // Data structures for reorg attack protection // We want a map so we can do an O(1) count update every fulfillment log we get. - respCountMu sync.Mutex - respCount map[string]uint64 + respCount map[string]uint64 // This auxiliary heap is used when we need to purge the // respCount map - we repeatedly want to remove the minimum log. // You could use a sorted list if the completed logs arrive in order, but they may not. blockNumberToReqID *pairing.PairHeap // head tracking data structures - headBroadcaster httypes.HeadBroadcasterRegistry latestHeadMu sync.RWMutex latestHeadNumber uint64 @@ -238,8 +143,14 @@ type listenerV2 struct { // aggregator client to get link/eth feed prices from chain. Can be nil for VRF V2 plus aggregator aggregator_v3_interface.AggregatorV3InterfaceInterface - // deduper prevents processing duplicate requests from the log broadcaster. - deduper *vrfcommon.LogDeduper + // fulfillmentLogDeduper prevents re-processing fulfillment logs. + // fulfillment logs are used to increment counts in the respCount map + // and to update the blockNumberToReqID heap. + fulfillmentLogDeduper *vrfcommon.LogDeduper + + // inflightCache is a cache of in-flight requests, used to prevent + // re-processing of requests that are in-flight or already fulfilled. + inflightCache vrfcommon.InflightCache } func (lsn *listenerV2) HealthReport() map[string]error { @@ -262,7 +173,7 @@ func (lsn *listenerV2) Start(ctx context.Context) error { } if err != nil { lsn.l.Criticalw("Error getting coordinator config for gas limit check, starting anyway.", "err", err) - } else if conf.MaxGasLimit()+(GasProofVerification*2) > uint32(gasLimit) { + } else if conf.MaxGasLimit()+(GasProofVerification*2) > gasLimit { lsn.l.Criticalw("Node gas limit setting may not be high enough to fulfill all requests; it should be increased. Starting anyway.", "currentGasLimit", gasLimit, "neededGasLimit", conf.MaxGasLimit()+(GasProofVerification*2), @@ -270,1489 +181,87 @@ func (lsn *listenerV2) Start(ctx context.Context) error { "proofVerificationGas", GasProofVerification) } - spec := job.LoadEnvConfigVarsVRF(lsn.cfg, *lsn.job.VRFSpec) + spec := job.LoadDefaultVRFPollPeriod(*lsn.job.VRFSpec) - unsubscribeLogs := lsn.logBroadcaster.Register(lsn, log.ListenerOpts{ - Contract: lsn.coordinator.Address(), - ParseLog: lsn.coordinator.ParseLog, - LogsWithTopics: lsn.coordinator.LogsWithTopics(spec.PublicKey.MustHash()), - // 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, - ReplayStartedCallback: lsn.ReplayStartedCallback, - }) - - latestHead, unsubscribeHeadBroadcaster := lsn.headBroadcaster.Subscribe(lsn) - if latestHead != nil { - lsn.setLatestHead(latestHead) + var respCount map[string]uint64 + respCount, err = lsn.GetStartingResponseCountsV2(ctx) + if err != nil { + return err } + lsn.respCount = respCount - // Log listener gathers request logs + // Log listener gathers request logs and processes them lsn.wg.Add(1) go func() { - lsn.runLogListener([]func(){unsubscribeLogs, unsubscribeHeadBroadcaster}, spec.MinIncomingConfirmations, lsn.wg) + defer lsn.wg.Done() + lsn.runLogListener(spec.PollPeriod, spec.MinIncomingConfirmations) }() - // Request handler periodically computes a set of logs which can be fulfilled. - lsn.wg.Add(1) - go func() { - lsn.runRequestHandler(spec.PollPeriod, lsn.wg) - }() - - lsn.mailMon.Monitor(lsn.reqLogs, "VRFListenerV2", "RequestLogs", fmt.Sprint(lsn.job.ID)) return nil }) } -func (lsn *listenerV2) setLatestHead(head *evmtypes.Head) { - lsn.latestHeadMu.Lock() - defer lsn.latestHeadMu.Unlock() - num := uint64(head.Number) - if num > lsn.latestHeadNumber { - lsn.latestHeadNumber = num - } -} - -// OnNewLongestChain is called by the head broadcaster when a new head is available. -func (lsn *listenerV2) OnNewLongestChain(ctx context.Context, head *evmtypes.Head) { - lsn.setLatestHead(head) -} - -func (lsn *listenerV2) getLatestHead() uint64 { - lsn.latestHeadMu.RLock() - defer lsn.latestHeadMu.RUnlock() - return uint64(lsn.latestHeadNumber) -} - -// Returns all the confirmed logs from -// the pending queue by subscription -func (lsn *listenerV2) getAndRemoveConfirmedLogsBySub(latestHead uint64) map[string][]pendingRequest { - lsn.reqsMu.Lock() - defer lsn.reqsMu.Unlock() - vrfcommon.UpdateQueueSize(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version(), uniqueReqs(lsn.reqs)) - var toProcess = make(map[string][]pendingRequest) - var toKeep []pendingRequest - for i := 0; i < len(lsn.reqs); i++ { - if r := lsn.reqs[i]; lsn.ready(r, latestHead) { - toProcess[r.req.SubID().String()] = append(toProcess[r.req.SubID().String()], r) - } else { - toKeep = append(toKeep, lsn.reqs[i]) - } - } - lsn.reqs = toKeep - return toProcess -} - -func (lsn *listenerV2) ready(req pendingRequest, latestHead uint64) bool { - // Request is not eligible for fulfillment yet - if req.confirmedAtBlock > latestHead { - return false - } - - if lsn.job.VRFSpec.BackoffInitialDelay == 0 || req.attempts == 0 { - // Backoff is disabled, or this is the first try - return true - } - - return time.Now().UTC().After( - nextTry( - req.attempts, - lsn.job.VRFSpec.BackoffInitialDelay, - lsn.job.VRFSpec.BackoffMaxDelay, - req.lastTry)) -} - -func nextTry(retries int, initial, max time.Duration, last time.Time) time.Time { - expBackoffFactor := math.Pow(backoffFactor, float64(retries-1)) - - var delay time.Duration - if expBackoffFactor > float64(max/initial) { - delay = max - } else { - delay = time.Duration(float64(initial) * expBackoffFactor) - } - return last.Add(delay) -} - -// Remove all entries 10000 blocks or older -// to avoid a memory leak. -func (lsn *listenerV2) pruneConfirmedRequestCounts() { - lsn.respCountMu.Lock() - defer lsn.respCountMu.Unlock() - min := lsn.blockNumberToReqID.FindMin() - for min != nil { - m := min.(fulfilledReqV2) - if m.blockNumber > (lsn.getLatestHead() - 10000) { - break - } - delete(lsn.respCount, m.reqID) - lsn.blockNumberToReqID.DeleteMin() - min = lsn.blockNumberToReqID.FindMin() - } -} - -// 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 txm as already "spent" link or native, -// using a max link or max native 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. -// Other variables which can change the bill amount between our eth call simulation and tx execution: -// - Link/eth price fluctuation -// - Falling back to BHS -// However the likelihood is vanishingly small as -// 1) the window between simulation and tx execution is tiny. -// 2) the max gas price provides a very large buffer most of the time. -// Its easier to optimistically assume it will go though and in the rare case of a reversion -// we simply retry TODO: follow up where if we see a fulfillment revert, return log to the queue. -func (lsn *listenerV2) processPendingVRFRequests(ctx context.Context) { - confirmed := lsn.getAndRemoveConfirmedLogsBySub(lsn.getLatestHead()) - 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 { - req.attempts++ - req.lastTry = time.Now().UTC() - toKeep = append(toKeep, req) - if lsn.job.VRFSpec.BackoffInitialDelay != 0 { - lsn.l.Infow("Request failed, next retry will be delayed.", - "reqID", req.req.RequestID().String(), - "subID", req.req.SubID(), - "attempts", req.attempts, - "lastTry", req.lastTry.String(), - "nextTry", nextTry( - req.attempts, - lsn.job.VRFSpec.BackoffInitialDelay, - lsn.job.VRFSpec.BackoffMaxDelay, - req.lastTry)) - } - } else { - lsn.markLogAsConsumed(req.lb) - } - } - } - // 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.l.Infow("Finished processing pending requests", - "totalProcessed", len(processed), - "totalFailed", len(toKeep), - "total", len(lsn.reqs), - "time", time.Since(start).String()) - lsn.reqsMu.Unlock() // unlock here since len(lsn.reqs) is a read, to avoid a data race. - }() - - if len(confirmed) == 0 { - lsn.l.Infow("No pending requests ready for processing") - return - } - for subID, reqs := range confirmed { - l := lsn.l.With("subID", subID, "startTime", time.Now(), "numReqsForSub", len(reqs)) - // Get the balance of the subscription and also it's active status. - // The reason we need both is that we cannot determine if a subscription - // is active solely by it's balance, since an active subscription could legitimately - // have a zero balance. - var ( - startLinkBalance *big.Int - startEthBalance *big.Int - subIsActive bool - ) - sID, ok := new(big.Int).SetString(subID, 10) - if !ok { - l.Criticalw("Unable to convert %s to Int", subID) - continue - } - sub, err := lsn.coordinator.GetSubscription(&bind.CallOpts{ - Context: ctx}, sID) - - if err != nil { - if strings.Contains(err.Error(), "execution reverted") { - // "execution reverted" indicates that the subscription no longer exists. - // We can no longer just mark these as processed and continue, - // since it could be that the subscription was canceled while there - // were still unfulfilled requests. - // The simplest approach to handle this is to enter the processRequestsPerSub - // loop rather than create a bunch of largely duplicated code - // to handle this specific situation, since we need to run the pipeline to get - // the VRF proof, abi-encode it, etc. - l.Warnw("Subscription not found - setting start balance to zero", "subID", subID, "err", err) - startLinkBalance = big.NewInt(0) - } else { - // Most likely this is an RPC error, so we re-try later. - l.Errorw("Unable to read subscription balance", "err", err) - continue - } - } else { - // Happy path - sub is active. - startLinkBalance = sub.Balance() - if sub.Version() == vrfcommon.V2Plus { - startEthBalance = sub.NativeBalance() - } - subIsActive = true - } - - // Sort requests in ascending order by CallbackGasLimit - // so that we process the "cheapest" requests for each subscription - // first. This allows us to break out of the processing loop as early as possible - // in the event that a subscription is too underfunded to have it's - // requests processed. - slices.SortFunc(reqs, func(a, b pendingRequest) int { - return cmp.Compare(a.req.CallbackGasLimit(), b.req.CallbackGasLimit()) - }) - - p := lsn.processRequestsPerSub(ctx, sID, startLinkBalance, startEthBalance, reqs, subIsActive) - for reqID := range p { - processed[reqID] = struct{}{} - } - } - lsn.pruneConfirmedRequestCounts() -} - -// 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(q pg.Q, startBalance *big.Int, chainID uint64, subID *big.Int, vrfVersion vrfcommon.Version) (*big.Int, error) { - var ( - reservedLink string - query string - ) - if vrfVersion == vrfcommon.V2Plus { - query = V2PlusReservedLinkQuery - } else if vrfVersion == vrfcommon.V2 { - query = V2ReservedLinkQuery - } else { - return nil, errors.Errorf("unsupported vrf version %s", vrfVersion) - } - - err := q.Get(&reservedLink, query, chainID, subID.String()) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return nil, errors.Wrap(err, "getting reserved LINK") - } - - if reservedLink != "" { - reservedLinkInt, success := big.NewInt(0).SetString(reservedLink, 10) - if !success { - return nil, fmt.Errorf("converting reserved LINK %s", reservedLink) - } - - return new(big.Int).Sub(startBalance, reservedLinkInt), nil - } - - return new(big.Int).Set(startBalance), nil -} - -// MaybeSubtractReservedEth figures out how much ether 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 MaybeSubtractReservedEth(q pg.Q, startBalance *big.Int, chainID uint64, subID *big.Int, vrfVersion vrfcommon.Version) (*big.Int, error) { - var ( - reservedEther string - query string - ) - if vrfVersion == vrfcommon.V2Plus { - query = V2PlusReservedEthQuery - } else if vrfVersion == vrfcommon.V2 { - // native payment is not supported for v2, so returning 0 reserved ETH - return big.NewInt(0), nil - } else { - return nil, errors.Errorf("unsupported vrf version %s", vrfVersion) - } - err := q.Get(&reservedEther, query, chainID, subID.String()) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return nil, errors.Wrap(err, "getting reserved ether") - } - - if reservedEther != "" { - reservedEtherInt, success := big.NewInt(0).SetString(reservedEther, 10) - if !success { - return nil, fmt.Errorf("converting reserved ether %s", reservedEther) - } - - return new(big.Int).Sub(startBalance, reservedEtherInt), nil - } - - if startBalance != nil { - return new(big.Int).Set(startBalance), nil - } - return big.NewInt(0), nil -} - -type fulfilledReqV2 struct { - blockNumber uint64 - reqID string -} - -func (a fulfilledReqV2) Compare(b heaps.Item) int { - a1 := a - a2 := b.(fulfilledReqV2) - switch { - case a1.blockNumber > a2.blockNumber: - return 1 - case a1.blockNumber < a2.blockNumber: - return -1 - default: - return 0 - } -} - -func (lsn *listenerV2) processRequestsPerSubBatchHelper( - ctx context.Context, - subID *big.Int, - startBalance *big.Int, - startBalanceNoReserved *big.Int, - reqs []pendingRequest, - subIsActive bool, - nativePayment bool, -) (processed map[string]struct{}) { - start := time.Now() - processed = make(map[string]struct{}) - - // Base the max gas for a batch on the max gas limit for a single callback. - // Since the max gas limit for a single callback is usually quite large already, - // we probably don't want to exceed it too much so that we can reliably get - // batch fulfillments included, while also making sure that the biggest gas guzzler - // callbacks are included. - config, err := lsn.coordinator.GetConfig(&bind.CallOpts{ - Context: ctx, - }) - if err != nil { - lsn.l.Errorw("Couldn't get config from coordinator", "err", err) - return processed - } - - // Add very conservative upper bound estimate on verification costs. - batchMaxGas := uint32(config.MaxGasLimit() + 400_000) - - l := lsn.l.With( - "subID", subID, - "eligibleSubReqs", len(reqs), - "startBalance", startBalance.String(), - "startBalanceNoReserved", startBalanceNoReserved.String(), - "batchMaxGas", batchMaxGas, - "subIsActive", subIsActive, - "nativePayment", nativePayment, - ) - - defer func() { - l.Infow("Finished processing for sub", - "endBalance", startBalanceNoReserved.String(), - "totalProcessed", len(processed), - "totalUnique", uniqueReqs(reqs), - "time", time.Since(start).String()) - }() - - l.Infow("Processing requests for subscription with batching") - - // Check for already consumed or expired reqs - unconsumed, processedReqs := lsn.getUnconsumed(l, reqs) - for _, reqID := range processedReqs { - processed[reqID] = struct{}{} - } - - // Process requests in chunks in order to kick off as many jobs - // as configured in parallel. Then we can combine into fulfillment - // batches afterwards. - for chunkStart := 0; chunkStart < len(unconsumed); chunkStart += int(lsn.job.VRFSpec.ChunkSize) { - chunkEnd := chunkStart + int(lsn.job.VRFSpec.ChunkSize) - if chunkEnd > len(unconsumed) { - chunkEnd = len(unconsumed) - } - chunk := unconsumed[chunkStart:chunkEnd] - - var unfulfilled []pendingRequest - alreadyFulfilled, err := lsn.checkReqsFulfilled(ctx, l, chunk) - if errors.Is(err, context.Canceled) { - l.Infow("Context canceled, stopping request processing", "err", err) - return processed - } else if err != nil { - l.Errorw("Error checking for already fulfilled requests, proceeding anyway", "err", err) - } - for i, a := range alreadyFulfilled { - if a { - processed[chunk[i].req.RequestID().String()] = struct{}{} - } else { - unfulfilled = append(unfulfilled, chunk[i]) - } - } - - // All fromAddresses passed to the VRFv2 job have the same KeySpecific-MaxPrice value. - fromAddresses := lsn.fromAddresses() - maxGasPriceWei := lsn.feeCfg.PriceMaxKey(fromAddresses[0]) - - // Cases: - // 1. Never simulated: in this case, we want to observe the time until simulated - // on the utcTimestamp field of the pending request. - // 2. Simulated before: in this case, lastTry will be set to a non-zero time value, - // in which case we'd want to use that as a relative point from when we last tried - // the request. - observeRequestSimDuration(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version(), unfulfilled) - - pipelines := lsn.runPipelines(ctx, l, maxGasPriceWei, unfulfilled) - batches := newBatchFulfillments(batchMaxGas, lsn.coordinator.Version()) - outOfBalance := false - for _, p := range pipelines { - ll := l.With("reqID", p.req.req.RequestID().String(), - "txHash", p.req.req.Raw().TxHash, - "maxGasPrice", maxGasPriceWei.String(), - "fundsNeeded", p.fundsNeeded.String(), - "maxFee", p.maxFee.String(), - "gasLimit", p.gasLimit, - "attempts", p.req.attempts, - "remainingBalance", startBalanceNoReserved.String(), - "consumerAddress", p.req.req.Sender(), - "blockNumber", p.req.req.Raw().BlockNumber, - "blockHash", p.req.req.Raw().BlockHash, - ) - fromAddresses := lsn.fromAddresses() - fromAddress, err := lsn.gethks.GetRoundRobinAddress(lsn.chainID, fromAddresses...) - if err != nil { - l.Errorw("Couldn't get next from address", "err", err) - continue - } - ll = ll.With("fromAddress", fromAddress) - - if p.err != nil { - if errors.Is(p.err, errBlockhashNotInStore{}) { - // Running the blockhash store feeder in backwards mode will be required to - // resolve this. - ll.Criticalw("Pipeline error", "err", p.err) - } else { - ll.Errorw("Pipeline error", "err", p.err) - if !subIsActive { - ll.Warnw("Force-fulfilling a request with insufficient funds on a cancelled sub") - etx, err := lsn.enqueueForceFulfillment(ctx, p, fromAddress) - if err != nil { - ll.Errorw("Error enqueuing force-fulfillment, re-queueing request", "err", err) - continue - } - ll.Infow("Successfully enqueued force-fulfillment", "ethTxID", etx.ID) - processed[p.req.req.RequestID().String()] = struct{}{} - - // Need to put a continue here, otherwise the next if statement will be hit - // and we'd break out of the loop prematurely. - // If a sub is canceled, we want to force-fulfill ALL of it's pending requests - // before saying we're done with it. - continue - } - - if startBalanceNoReserved.Cmp(p.fundsNeeded) < 0 && errors.Is(p.err, errPossiblyInsufficientFunds{}) { - ll.Infow("Insufficient balance to fulfill a request based on estimate, breaking", "err", p.err) - outOfBalance = true - - // break out of this inner loop to process the currently constructed batch - break - } - - // Ensure consumer is valid, otherwise drop the request. - if !lsn.isConsumerValidAfterFinalityDepthElapsed(ctx, p.req) { - lsn.l.Infow( - "Dropping request that was made by an invalid consumer.", - "consumerAddress", p.req.req.Sender(), - "reqID", p.req.req.RequestID(), - "blockNumber", p.req.req.Raw().BlockNumber, - "blockHash", p.req.req.Raw().BlockHash, - ) - lsn.markLogAsConsumed(p.req.lb) - } - } - continue - } - - if startBalanceNoReserved.Cmp(p.maxFee) < 0 { - // Insufficient funds, have to wait for a user top up. - // Break out of the loop now and process what we are able to process - // in the constructed batches. - ll.Infow("Insufficient balance to fulfill a request, breaking") - break - } - - batches.addRun(p, fromAddress) - - startBalanceNoReserved.Sub(startBalanceNoReserved, p.maxFee) - } - - var processedRequestIDs []string - for _, batch := range batches.fulfillments { - l.Debugw("Processing batch", "batchSize", len(batch.proofs)) - p := lsn.processBatch(l, subID, startBalanceNoReserved, batchMaxGas, batch, batch.fromAddress) - processedRequestIDs = append(processedRequestIDs, p...) - } - - for _, reqID := range processedRequestIDs { - processed[reqID] = struct{}{} - } - - // outOfBalance is set to true if the current sub we are processing - // has run out of funds to process any remaining requests. After enqueueing - // this constructed batch, we break out of this outer loop in order to - // avoid unnecessarily processing the remaining requests. - if outOfBalance { - break - } - } - - return -} - -func (lsn *listenerV2) processRequestsPerSubBatch( - ctx context.Context, - subID *big.Int, - startLinkBalance *big.Int, - startEthBalance *big.Int, - reqs []pendingRequest, - subIsActive bool, -) map[string]struct{} { - var processed = make(map[string]struct{}) - startBalanceNoReserveLink, err := MaybeSubtractReservedLink( - lsn.q, startLinkBalance, lsn.chainID.Uint64(), subID, lsn.coordinator.Version()) - if err != nil { - lsn.l.Errorw("Couldn't get reserved LINK for subscription", "sub", reqs[0].req.SubID(), "err", err) - return processed - } - startBalanceNoReserveEth, err := MaybeSubtractReservedEth( - lsn.q, startEthBalance, lsn.chainID.Uint64(), subID, lsn.coordinator.Version()) - if err != nil { - lsn.l.Errorw("Couldn't get reserved ether for subscription", "sub", reqs[0].req.SubID(), "err", err) - return processed - } - - // Split the requests into native and LINK requests. - var ( - nativeRequests []pendingRequest - linkRequests []pendingRequest - ) - for _, req := range reqs { - if req.req.NativePayment() { - nativeRequests = append(nativeRequests, req) - } else { - linkRequests = append(linkRequests, req) - } - } - // process the native and link requests in parallel - var wg sync.WaitGroup - var nativeProcessed, linkProcessed map[string]struct{} - wg.Add(2) - go func() { - defer wg.Done() - nativeProcessed = lsn.processRequestsPerSubBatchHelper(ctx, subID, startEthBalance, startBalanceNoReserveEth, nativeRequests, subIsActive, true) - }() - go func() { - defer wg.Done() - linkProcessed = lsn.processRequestsPerSubBatchHelper(ctx, subID, startLinkBalance, startBalanceNoReserveLink, linkRequests, subIsActive, false) - }() - wg.Wait() - // combine the processed link and native requests into the processed map - for k, v := range nativeProcessed { - processed[k] = v - } - for k, v := range linkProcessed { - processed[k] = v - } - - return processed -} - -// enqueueForceFulfillment enqueues a forced fulfillment through the -// VRFOwner contract. It estimates gas again on the transaction due -// to the extra steps taken within VRFOwner.fulfillRandomWords. -func (lsn *listenerV2) enqueueForceFulfillment( - ctx context.Context, - p vrfPipelineResult, - fromAddress common.Address, -) (etx txmgr.Tx, err error) { - if lsn.job.VRFSpec.VRFOwnerAddress == nil { - err = errors.New("vrf owner address not set in job spec, recreate job and provide it to force-fulfill") - return - } - - if p.payload == "" { - // should probably never happen - // a critical log will be logged if this is the case in simulateFulfillment - err = errors.New("empty payload in vrfPipelineResult") - return - } - - // fulfill the request through the VRF owner - err = lsn.q.Transaction(func(tx pg.Queryer) error { - if err = lsn.logBroadcaster.MarkConsumed(p.req.lb, pg.WithQueryer(tx)); err != nil { - return err - } - - lsn.l.Infow("VRFOwner.fulfillRandomWords vs. VRFCoordinatorV2.fulfillRandomWords", - "vrf_owner.fulfillRandomWords", hexutil.Encode(vrfOwnerABI.Methods["fulfillRandomWords"].ID), - "vrf_coordinator_v2.fulfillRandomWords", hexutil.Encode(coordinatorV2ABI.Methods["fulfillRandomWords"].ID), - ) - - vrfOwnerAddress1 := lsn.vrfOwner.Address() - vrfOwnerAddressSpec := lsn.job.VRFSpec.VRFOwnerAddress.Address() - lsn.l.Infow("addresses diff", "wrapper_address", vrfOwnerAddress1, "spec_address", vrfOwnerAddressSpec) - - lsn.l.Infow("fulfillRandomWords payload", "proof", p.proof, "commitment", p.reqCommitment.Get(), "payload", p.payload) - txData := hexutil.MustDecode(p.payload) - if err != nil { - return errors.Wrap(err, "abi pack VRFOwner.fulfillRandomWords") - } - estimateGasLimit, err := lsn.ethClient.EstimateGas(ctx, ethereum.CallMsg{ - From: fromAddress, - To: &vrfOwnerAddressSpec, - Data: txData, - }) - if err != nil { - return errors.Wrap(err, "failed to estimate gas on VRFOwner.fulfillRandomWords") - } - - lsn.l.Infow("Estimated gas limit on force fulfillment", - "estimateGasLimit", estimateGasLimit, "pipelineGasLimit", p.gasLimit) - if estimateGasLimit < uint64(p.gasLimit) { - estimateGasLimit = uint64(p.gasLimit) - } - - requestID := common.BytesToHash(p.req.req.RequestID().Bytes()) - subID := p.req.req.SubID() - requestTxHash := p.req.req.Raw().TxHash - etx, err = lsn.txm.CreateTransaction(ctx, txmgr.TxRequest{ - FromAddress: fromAddress, - ToAddress: lsn.vrfOwner.Address(), - EncodedPayload: txData, - FeeLimit: uint32(estimateGasLimit), - Strategy: txmgrcommon.NewSendEveryStrategy(), - Meta: &txmgr.TxMeta{ - RequestID: &requestID, - SubID: ptr(subID.Uint64()), - RequestTxHash: &requestTxHash, - // No max link since simulation failed - }, - }) +func (lsn *listenerV2) GetStartingResponseCountsV2(ctx context.Context) (respCount map[string]uint64, err error) { + respCounts := map[string]uint64{} + var latestBlockNum *big.Int + // Retry client call for LatestBlockHeight if fails + // Want to avoid failing startup due to potential faulty RPC call + err = retry.Do(func() error { + latestBlockNum, err = lsn.chain.Client().LatestBlockHeight(ctx) return err - }) - return -} - -// For an errored pipeline run, wait until the finality depth of the chain to have elapsed, -// then check if the failing request is being called by an invalid sender. Return false if this is the case, -// otherwise true. -func (lsn *listenerV2) isConsumerValidAfterFinalityDepthElapsed(ctx context.Context, req pendingRequest) bool { - latestHead := lsn.getLatestHead() - if latestHead-req.req.Raw().BlockNumber > uint64(lsn.cfg.FinalityDepth()) { - code, err := lsn.ethClient.CodeAt(ctx, req.req.Sender(), big.NewInt(int64(latestHead))) - if err != nil { - lsn.l.Warnw("Failed to fetch contract code", "err", err) - return true // error fetching code, give the benefit of doubt to the consumer - } - if len(code) == 0 { - return false // invalid consumer - } - } - - return true // valid consumer, or finality depth has not elapsed -} - -// processRequestsPerSubHelper processes a set of pending requests for the provided sub id. -// It returns a set of request IDs that were processed. -// Note that the provided startBalanceNoReserve is the balance of the subscription -// minus any pending requests that have already been processed and not yet fulfilled onchain. -func (lsn *listenerV2) processRequestsPerSubHelper( - ctx context.Context, - subID *big.Int, - startBalance *big.Int, - startBalanceNoReserved *big.Int, - reqs []pendingRequest, - subIsActive bool, - nativePayment bool, -) (processed map[string]struct{}) { - start := time.Now() - processed = make(map[string]struct{}) - - l := lsn.l.With( - "subID", subID, - "eligibleSubReqs", len(reqs), - "startBalance", startBalance.String(), - "startBalanceNoReserved", startBalanceNoReserved.String(), - "subIsActive", subIsActive, - "nativePayment", nativePayment, - ) - - defer func() { - l.Infow("Finished processing for sub", - "endBalance", startBalanceNoReserved.String(), - "totalProcessed", len(processed), - "totalUnique", uniqueReqs(reqs), - "time", time.Since(start).String()) - }() - - l.Infow("Processing requests for subscription") - - // Check for already consumed or expired reqs - unconsumed, processedReqs := lsn.getUnconsumed(l, reqs) - for _, reqID := range processedReqs { - processed[reqID] = struct{}{} - } - - // Process requests in chunks - for chunkStart := 0; chunkStart < len(unconsumed); chunkStart += int(lsn.job.VRFSpec.ChunkSize) { - chunkEnd := chunkStart + int(lsn.job.VRFSpec.ChunkSize) - if chunkEnd > len(unconsumed) { - chunkEnd = len(unconsumed) - } - chunk := unconsumed[chunkStart:chunkEnd] - - var unfulfilled []pendingRequest - alreadyFulfilled, err := lsn.checkReqsFulfilled(ctx, l, chunk) - if errors.Is(err, context.Canceled) { - l.Infow("Context canceled, stopping request processing", "err", err) - return processed - } else if err != nil { - l.Errorw("Error checking for already fulfilled requests, proceeding anyway", "err", err) - } - for i, a := range alreadyFulfilled { - if a { - processed[chunk[i].req.RequestID().String()] = struct{}{} - } else { - unfulfilled = append(unfulfilled, chunk[i]) - } - } - - // All fromAddresses passed to the VRFv2 job have the same KeySpecific-MaxPrice value. - fromAddresses := lsn.fromAddresses() - maxGasPriceWei := lsn.feeCfg.PriceMaxKey(fromAddresses[0]) - observeRequestSimDuration(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version(), unfulfilled) - pipelines := lsn.runPipelines(ctx, l, maxGasPriceWei, unfulfilled) - for _, p := range pipelines { - ll := l.With("reqID", p.req.req.RequestID().String(), - "txHash", p.req.req.Raw().TxHash, - "maxGasPrice", maxGasPriceWei.String(), - "fundsNeeded", p.fundsNeeded.String(), - "maxFee", p.maxFee.String(), - "gasLimit", p.gasLimit, - "attempts", p.req.attempts, - "remainingBalance", startBalanceNoReserved.String(), - "consumerAddress", p.req.req.Sender(), - "blockNumber", p.req.req.Raw().BlockNumber, - "blockHash", p.req.req.Raw().BlockHash, - ) - fromAddress, err := lsn.gethks.GetRoundRobinAddress(lsn.chainID, fromAddresses...) - if err != nil { - l.Errorw("Couldn't get next from address", "err", err) - continue - } - ll = ll.With("fromAddress", fromAddress) - - if p.err != nil { - if errors.Is(p.err, errBlockhashNotInStore{}) { - // Running the blockhash store feeder in backwards mode will be required to - // resolve this. - ll.Criticalw("Pipeline error", "err", p.err) - } else { - ll.Errorw("Pipeline error", "err", p.err) - - if !subIsActive { - lsn.l.Warnw("Force-fulfilling a request with insufficient funds on a cancelled sub") - etx, err2 := lsn.enqueueForceFulfillment(ctx, p, fromAddress) - if err2 != nil { - ll.Errorw("Error enqueuing force-fulfillment, re-queueing request", "err", err2) - continue - } - ll.Infow("Enqueued force-fulfillment", "ethTxID", etx.ID) - processed[p.req.req.RequestID().String()] = struct{}{} - - // Need to put a continue here, otherwise the next if statement will be hit - // and we'd break out of the loop prematurely. - // If a sub is canceled, we want to force-fulfill ALL of it's pending requests - // before saying we're done with it. - continue - } - - if startBalanceNoReserved.Cmp(p.fundsNeeded) < 0 { - ll.Infow("Insufficient balance to fulfill a request based on estimate, returning", "err", p.err) - return processed - } - - // Ensure consumer is valid, otherwise drop the request. - if !lsn.isConsumerValidAfterFinalityDepthElapsed(ctx, p.req) { - lsn.l.Infow( - "Dropping request that was made by an invalid consumer.", - "consumerAddress", p.req.req.Sender(), - "reqID", p.req.req.RequestID(), - "blockNumber", p.req.req.Raw().BlockNumber, - "blockHash", p.req.req.Raw().BlockHash, - ) - lsn.markLogAsConsumed(p.req.lb) - } - } - continue - } - - if startBalanceNoReserved.Cmp(p.maxFee) < 0 { - // Insufficient funds, have to wait for a user top up. Leave it unprocessed for now - ll.Infow("Insufficient balance to fulfill a request, returning") - return processed - } - - ll.Infow("Enqueuing fulfillment") - var transaction txmgr.Tx - err = lsn.q.Transaction(func(tx pg.Queryer) error { - if err = lsn.pipelineRunner.InsertFinishedRun(p.run, true, pg.WithQueryer(tx)); err != nil { - return err - } - if err = lsn.logBroadcaster.MarkConsumed(p.req.lb, pg.WithQueryer(tx)); err != nil { - return err - } - - var maxLink, maxEth *string - tmp := p.maxFee.String() - if p.reqCommitment.NativePayment() { - maxEth = &tmp - } else { - maxLink = &tmp - } - var ( - txMetaSubID *uint64 - txMetaGlobalSubID *string - ) - if lsn.coordinator.Version() == vrfcommon.V2Plus { - txMetaGlobalSubID = ptr(p.req.req.SubID().String()) - } else if lsn.coordinator.Version() == vrfcommon.V2 { - txMetaSubID = ptr(p.req.req.SubID().Uint64()) - } - requestID := common.BytesToHash(p.req.req.RequestID().Bytes()) - coordinatorAddress := lsn.coordinator.Address() - requestTxHash := p.req.req.Raw().TxHash - transaction, err = lsn.txm.CreateTransaction(ctx, txmgr.TxRequest{ - FromAddress: fromAddress, - ToAddress: lsn.coordinator.Address(), - EncodedPayload: hexutil.MustDecode(p.payload), - FeeLimit: p.gasLimit, - Meta: &txmgr.TxMeta{ - RequestID: &requestID, - MaxLink: maxLink, - MaxEth: maxEth, - SubID: txMetaSubID, - GlobalSubID: txMetaGlobalSubID, - RequestTxHash: &requestTxHash, - }, - Strategy: txmgrcommon.NewSendEveryStrategy(), - Checker: txmgr.TransmitCheckerSpec{ - CheckerType: lsn.transmitCheckerType(), - VRFCoordinatorAddress: &coordinatorAddress, - VRFRequestBlockNumber: new(big.Int).SetUint64(p.req.req.Raw().BlockNumber), - }, - }) - return err - }) - if err != nil { - ll.Errorw("Error enqueuing fulfillment, requeuing request", "err", err) - continue - } - ll.Infow("Enqueued fulfillment", "ethTxID", transaction.GetID()) - - // If we successfully enqueued for the txm, subtract that balance - // And loop to attempt to enqueue another fulfillment - startBalanceNoReserved.Sub(startBalanceNoReserved, p.maxFee) - processed[p.req.req.RequestID().String()] = struct{}{} - vrfcommon.IncProcessedReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version()) - } - } - - return -} - -func (lsn *listenerV2) transmitCheckerType() txmgrtypes.TransmitCheckerType { - if lsn.coordinator.Version() == vrfcommon.V2 { - return txmgr.TransmitCheckerTypeVRFV2 - } - return txmgr.TransmitCheckerTypeVRFV2Plus -} - -func (lsn *listenerV2) processRequestsPerSub( - ctx context.Context, - subID *big.Int, - startLinkBalance *big.Int, - startEthBalance *big.Int, - reqs []pendingRequest, - subIsActive bool, -) map[string]struct{} { - if lsn.job.VRFSpec.BatchFulfillmentEnabled && lsn.batchCoordinator != nil { - return lsn.processRequestsPerSubBatch(ctx, subID, startLinkBalance, startEthBalance, reqs, subIsActive) - } - - var processed = make(map[string]struct{}) - chainId := lsn.ethClient.ConfiguredChainID() - startBalanceNoReserveLink, err := MaybeSubtractReservedLink( - lsn.q, startLinkBalance, chainId.Uint64(), subID, lsn.coordinator.Version()) + }, retry.Attempts(10), retry.Delay(500*time.Millisecond)) if err != nil { - lsn.l.Errorw("Couldn't get reserved LINK for subscription", "sub", reqs[0].req.SubID(), "err", err) - return processed + return nil, err } - startBalanceNoReserveEth, err := MaybeSubtractReservedEth( - lsn.q, startEthBalance, lsn.chainID.Uint64(), subID, lsn.coordinator.Version()) - if err != nil { - lsn.l.Errorw("Couldn't get reserved ETH for subscription", "sub", reqs[0].req.SubID(), "err", err) - return processed + if latestBlockNum == nil { + return nil, errors.New("LatestBlockHeight return nil block num") } - - // Split the requests into native and LINK requests. - var ( - nativeRequests []pendingRequest - linkRequests []pendingRequest - ) - for _, req := range reqs { - if req.req.NativePayment() { - nativeRequests = append(nativeRequests, req) - } else { - linkRequests = append(linkRequests, req) - } - } - // process the native and link requests in parallel - var ( - wg sync.WaitGroup - nativeProcessed, linkProcessed map[string]struct{} - ) - wg.Add(2) - go func() { - defer wg.Done() - nativeProcessed = lsn.processRequestsPerSubHelper( - ctx, - subID, - startEthBalance, - startBalanceNoReserveEth, - nativeRequests, - subIsActive, - true) - }() - go func() { - defer wg.Done() - linkProcessed = lsn.processRequestsPerSubHelper( - ctx, - subID, - startLinkBalance, - startBalanceNoReserveLink, - linkRequests, - subIsActive, - false) - }() - wg.Wait() - // combine the native and link processed requests into the processed map - for k, v := range nativeProcessed { - processed[k] = v - } - for k, v := range linkProcessed { - processed[k] = v - } - - return processed -} - -func (lsn *listenerV2) requestCommitmentPayload(requestID *big.Int) (payload []byte, err error) { - if lsn.coordinator.Version() == vrfcommon.V2Plus { - return coordinatorV2PlusABI.Pack("s_requestCommitments", requestID) - } else if lsn.coordinator.Version() == vrfcommon.V2 { - return coordinatorV2ABI.Pack("getCommitment", requestID) - } - return nil, errors.Errorf("unsupported coordinator version: %s", lsn.coordinator.Version()) -} - -// checkReqsFulfilled returns a bool slice the same size of the given reqs slice -// where each slice element indicates whether that request was already fulfilled -// or not. -func (lsn *listenerV2) checkReqsFulfilled(ctx context.Context, l logger.Logger, reqs []pendingRequest) ([]bool, error) { - var ( - start = time.Now() - calls = make([]rpc.BatchElem, len(reqs)) - fulfilled = make([]bool, len(reqs)) - ) - - for i, req := range reqs { - payload, err := lsn.requestCommitmentPayload(req.req.RequestID()) - if err != nil { - // This shouldn't happen - return fulfilled, errors.Wrap(err, "creating getCommitment payload") - } - - reqBlockNumber := new(big.Int).SetUint64(req.req.Raw().BlockNumber) - - // Subtract 5 since the newest block likely isn't indexed yet and will cause "header not - // found" errors. - currBlock := new(big.Int).SetUint64(lsn.getLatestHead() - 5) - m := bigmath.Max(reqBlockNumber, currBlock) - - var result string - calls[i] = rpc.BatchElem{ - Method: "eth_call", - Args: []interface{}{ - map[string]interface{}{ - "to": lsn.coordinator.Address(), - "data": hexutil.Bytes(payload), - }, - // The block at which we want to make the call - hexutil.EncodeBig(m), - }, - Result: &result, - } - } - - err := lsn.ethClient.BatchCallContext(ctx, calls) + confirmedBlockNum := latestBlockNum.Int64() - int64(lsn.chain.Config().EVM().FinalityDepth()) + // Only check as far back as the evm finality depth for completed transactions. + var counts []vrfcommon.RespCountEntry + counts, err = vrfcommon.GetRespCounts(ctx, lsn.chain.TxManager(), lsn.chainID, confirmedBlockNum) if err != nil { - return fulfilled, errors.Wrap(err, "making batch call") + // Continue with an empty map, do not block job on this. + lsn.l.Errorw("Unable to read previous confirmed fulfillments", "err", err) + return respCounts, nil } - var errs error - for i, call := range calls { - if call.Error != nil { - errs = multierr.Append(errs, fmt.Errorf("checking request %s with hash %s: %w", - reqs[i].req.RequestID().String(), reqs[i].req.Raw().TxHash.String(), call.Error)) - continue - } - - rString, ok := call.Result.(*string) - if !ok { - errs = multierr.Append(errs, - fmt.Errorf("unexpected result %+v on request %s with hash %s", - call.Result, reqs[i].req.RequestID().String(), reqs[i].req.Raw().TxHash.String())) - continue - } - result, err := hexutil.Decode(*rString) + for _, c := range counts { + // Remove the quotes from the json + req := strings.Replace(c.RequestID, `"`, ``, 2) + // Remove the 0x prefix + b, err := hex.DecodeString(req[2:]) if err != nil { - errs = multierr.Append(errs, - fmt.Errorf("decoding batch call result %+v %s request %s with hash %s: %w", - call.Result, *rString, reqs[i].req.RequestID().String(), reqs[i].req.Raw().TxHash.String(), err)) + lsn.l.Errorw("Unable to read fulfillment", "err", err, "reqID", c.RequestID) continue } - - if utils.IsEmpty(result) { - l.Infow("Request already fulfilled", - "reqID", reqs[i].req.RequestID().String(), - "attempts", reqs[i].attempts, - "txHash", reqs[i].req.Raw().TxHash) - fulfilled[i] = true - } - } - - l.Debugw("Done checking fulfillment status", - "numChecked", len(reqs), "time", time.Since(start).String()) - return fulfilled, errs -} - -func (lsn *listenerV2) runPipelines( - ctx context.Context, - l logger.Logger, - maxGasPriceWei *assets.Wei, - reqs []pendingRequest, -) []vrfPipelineResult { - var ( - start = time.Now() - results = make([]vrfPipelineResult, len(reqs)) - wg = sync.WaitGroup{} - ) - - for i, req := range reqs { - wg.Add(1) - go func(i int, req pendingRequest) { - defer wg.Done() - results[i] = lsn.simulateFulfillment(ctx, maxGasPriceWei, req, l) - }(i, req) - } - wg.Wait() - - l.Debugw("Finished running pipelines", - "count", len(reqs), "time", time.Since(start).String()) - return results -} - -func (lsn *listenerV2) estimateFee( - ctx context.Context, - req RandomWordsRequested, - maxGasPriceWei *assets.Wei, -) (*big.Int, error) { - // NativePayment() returns true if and only if the version is V2+ and the - // request was made in ETH. - if req.NativePayment() { - return EstimateFeeWei(req.CallbackGasLimit(), maxGasPriceWei.ToInt()) - } - - // In the event we are using LINK we need to estimate the fee in juels - // Don't use up too much time to get this info, it's not critical for operating vrf. - callCtx, cancel := context.WithTimeout(ctx, 1*time.Second) - defer cancel() - roundData, err := lsn.aggregator.LatestRoundData(&bind.CallOpts{Context: callCtx}) - if err != nil { - return nil, errors.Wrap(err, "get aggregator latestAnswer") - } - - return EstimateFeeJuels( - req.CallbackGasLimit(), - maxGasPriceWei.ToInt(), - roundData.Answer, - ) -} - -// 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) simulateFulfillment( - ctx context.Context, - maxGasPriceWei *assets.Wei, - req pendingRequest, - lg logger.Logger, -) vrfPipelineResult { - var ( - res = vrfPipelineResult{req: req} - err error - ) - // estimate how much funds are needed so that we can log it if the simulation fails. - res.fundsNeeded, err = lsn.estimateFee(ctx, req.req, maxGasPriceWei) - if err != nil { - // not critical, just log and continue - lg.Warnw("unable to estimate funds needed for request, continuing anyway", - "reqID", req.req.RequestID(), - "err", err) - res.fundsNeeded = big.NewInt(0) - } - - vars := pipeline.NewVarsFrom(map[string]interface{}{ - "jobSpec": map[string]interface{}{ - "databaseID": lsn.job.ID, - "externalJobID": lsn.job.ExternalJobID, - "name": lsn.job.Name.ValueOrZero(), - "publicKey": lsn.job.VRFSpec.PublicKey[:], - "maxGasPrice": maxGasPriceWei.ToInt().String(), - "evmChainID": lsn.job.VRFSpec.EVMChainID.String(), - }, - "jobRun": map[string]interface{}{ - "logBlockHash": req.req.Raw().BlockHash.Bytes(), - "logBlockNumber": req.req.Raw().BlockNumber, - "logTxHash": req.req.Raw().TxHash, - "logTopics": req.req.Raw().Topics, - "logData": req.req.Raw().Data, - }, - }) - var trrs pipeline.TaskRunResults - res.run, trrs, err = lsn.pipelineRunner.ExecuteRun(ctx, *lsn.job.PipelineSpec, vars, lg) - if err != nil { - res.err = errors.Wrap(err, "executing run") - return res - } - // The call task will fail if there are insufficient funds - if res.run.AllErrors.HasError() { - res.err = errors.WithStack(res.run.AllErrors.ToError()) - - if strings.Contains(res.err.Error(), "blockhash not found in store") { - res.err = multierr.Combine(res.err, errBlockhashNotInStore{}) - } else if strings.Contains(res.err.Error(), "execution reverted") { - // Even if the simulation fails, we want to get the - // txData for the fulfillRandomWords call, in case - // we need to force fulfill. - for _, trr := range trrs { - if trr.Task.Type() == pipeline.TaskTypeVRFV2 { - if trr.Result.Error != nil { - // error in VRF proof generation - // this means that we won't be able to force-fulfill in the event of a - // canceled sub and active requests. - // since this would be an extraordinary situation, - // we can log loudly here. - lg.Criticalw("failed to generate VRF proof", "err", trr.Result.Error) - break - } - - // extract the abi-encoded tx data to fulfillRandomWords from the VRF task. - // that's all we need in the event of a force-fulfillment. - m := trr.Result.Value.(map[string]any) - res.payload = m["output"].(string) - res.proof = FromV2Proof(m["proof"].(vrf_coordinator_v2.VRFProof)) - res.reqCommitment = NewRequestCommitment(m["requestCommitment"]) - } - } - res.err = multierr.Combine(res.err, errPossiblyInsufficientFunds{}) - } - - return res - } - finalResult := trrs.FinalResult(lg) - if len(finalResult.Values) != 1 { - res.err = errors.Errorf("unexpected number of outputs, expected 1, was %d", len(finalResult.Values)) - return res - } - - // Run succeeded, we expect a byte array representing the billing amount - b, ok := finalResult.Values[0].([]uint8) - if !ok { - res.err = errors.New("expected []uint8 final result") - return res - } - res.maxFee = utils.HexToBig(hexutil.Encode(b)[2:]) - for _, trr := range trrs { - if trr.Task.Type() == pipeline.TaskTypeVRFV2 { - m := trr.Result.Value.(map[string]interface{}) - res.payload = m["output"].(string) - res.proof = FromV2Proof(m["proof"].(vrf_coordinator_v2.VRFProof)) - res.reqCommitment = NewRequestCommitment(m["requestCommitment"]) - } - - if trr.Task.Type() == pipeline.TaskTypeVRFV2Plus { - m := trr.Result.Value.(map[string]interface{}) - res.payload = m["output"].(string) - res.proof = FromV2PlusProof(m["proof"].(vrf_coordinator_v2plus_interface.IVRFCoordinatorV2PlusInternalProof)) - res.reqCommitment = NewRequestCommitment(m["requestCommitment"]) - } - - if trr.Task.Type() == pipeline.TaskTypeEstimateGasLimit { - res.gasLimit = trr.Result.Value.(uint32) - } + bi := new(big.Int).SetBytes(b) + respCounts[bi.String()] = uint64(c.Count) } - return res + return respCounts, nil } -func (lsn *listenerV2) runRequestHandler(pollPeriod time.Duration, wg *sync.WaitGroup) { - defer wg.Done() - tick := time.NewTicker(pollPeriod) - defer tick.Stop() - ctx, cancel := lsn.chStop.NewCtx() - defer cancel() - for { - select { - case <-lsn.chStop: - return - case <-tick.C: - lsn.processPendingVRFRequests(ctx) - } - } -} - -func (lsn *listenerV2) runLogListener(unsubscribes []func(), minConfs uint32, wg *sync.WaitGroup) { - defer wg.Done() - lsn.l.Infow("Listening for run requests", - "minConfs", minConfs) - for { - select { - case <-lsn.chStop: - for _, f := range unsubscribes { - f() - } - return - case <-lsn.reqLogs.Notify(): - // Process all the logs in the queue if one is added - for { - lb, exists := lsn.reqLogs.Retrieve() - if !exists { - break - } - lsn.handleLog(lb, minConfs) - } - } - } -} - -func (lsn *listenerV2) getConfirmedAt(req RandomWordsRequested, nodeMinConfs uint32) uint64 { - lsn.respCountMu.Lock() - defer lsn.respCountMu.Unlock() - // Take the max(nodeMinConfs, requestedConfs + requestedConfsDelay). - // Add the requested confs delay if provided in the jobspec so that we avoid an edge case - // where the primary and backup VRF v2 nodes submit a proof at the same time. - minConfs := nodeMinConfs - if uint32(req.MinimumRequestConfirmations())+uint32(lsn.job.VRFSpec.RequestedConfsDelay) > nodeMinConfs { - minConfs = uint32(req.MinimumRequestConfirmations()) + uint32(lsn.job.VRFSpec.RequestedConfsDelay) - } - newConfs := uint64(minConfs) * (1 << lsn.respCount[req.RequestID().String()]) - // We cap this at 200 because solidity only supports the most recent 256 blocks - // in the contract so if it was older than that, fulfillments would start failing - // without the blockhash store feeder. We use 200 to give the node plenty of time - // to fulfill even on fast chains. - if newConfs > 200 { - newConfs = 200 - } - if lsn.respCount[req.RequestID().String()] > 0 { - lsn.l.Warnw("Duplicate request found after fulfillment, doubling incoming confirmations", - "txHash", req.Raw().TxHash, - "blockNumber", req.Raw().BlockNumber, - "blockHash", req.Raw().BlockHash, - "reqID", req.RequestID().String(), - "newConfs", newConfs) - vrfcommon.IncDupeReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version()) - } - return req.Raw().BlockNumber + newConfs -} - -func (lsn *listenerV2) handleLog(lb log.Broadcast, minConfs uint32) { - if v, ok := lb.DecodedLog().(*vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled); ok { - lsn.l.Debugw("Received fulfilled log", "reqID", v.RequestId, "success", v.Success) - consumed, err := lsn.logBroadcaster.WasAlreadyConsumed(lb) - if err != nil { - lsn.l.Errorw(CouldNotDetermineIfLogConsumedMsg, "err", err, "txHash", lb.RawLog().TxHash) - return - } else if consumed { - return - } - lsn.respCountMu.Lock() - lsn.respCount[v.RequestId.String()]++ - lsn.respCountMu.Unlock() - lsn.blockNumberToReqID.Insert(fulfilledReqV2{ - blockNumber: v.Raw.BlockNumber, - reqID: v.RequestId.String(), - }) - lsn.markLogAsConsumed(lb) - return - } - - if v, ok := lb.DecodedLog().(*vrf_coordinator_v2plus_interface.IVRFCoordinatorV2PlusInternalRandomWordsFulfilled); ok { - lsn.l.Debugw("Received fulfilled log", "reqID", v.RequestId, "success", v.Success) - consumed, err := lsn.logBroadcaster.WasAlreadyConsumed(lb) - if err != nil { - lsn.l.Errorw(CouldNotDetermineIfLogConsumedMsg, "err", err, "txHash", lb.RawLog().TxHash) - return - } else if consumed { - return - } - lsn.respCountMu.Lock() - lsn.respCount[v.RequestId.String()]++ - lsn.respCountMu.Unlock() - lsn.blockNumberToReqID.Insert(fulfilledReqV2{ - blockNumber: v.Raw.BlockNumber, - reqID: v.RequestId.String(), - }) - lsn.markLogAsConsumed(lb) - return - } - - req, err := lsn.coordinator.ParseRandomWordsRequested(lb.RawLog()) - if err != nil { - lsn.l.Errorw("Failed to parse log", "err", err, "txHash", lb.RawLog().TxHash) - consumed, err := lsn.logBroadcaster.WasAlreadyConsumed(lb) - if err != nil { - lsn.l.Errorw(CouldNotDetermineIfLogConsumedMsg, "err", err, "txHash", lb.RawLog().TxHash) - return - } else if consumed { - return - } - lsn.markLogAsConsumed(lb) - return +func (lsn *listenerV2) setLatestHead(head logpoller.LogPollerBlock) { + lsn.latestHeadMu.Lock() + defer lsn.latestHeadMu.Unlock() + num := uint64(head.BlockNumber) + if num > lsn.latestHeadNumber { + lsn.latestHeadNumber = num } - - confirmedAt := lsn.getConfirmedAt(req, minConfs) - lsn.l.Infow("VRFListenerV2: Received log request", "reqID", req.RequestID(), "confirmedAt", confirmedAt, "subID", req.SubID(), "sender", req.Sender()) - lsn.reqsMu.Lock() - lsn.reqs = append(lsn.reqs, pendingRequest{ - confirmedAtBlock: confirmedAt, - req: req, - lb: lb, - utcTimestamp: time.Now().UTC(), - }) - lsn.reqAdded() - lsn.reqsMu.Unlock() } -func (lsn *listenerV2) markLogAsConsumed(lb log.Broadcast) { - err := lsn.logBroadcaster.MarkConsumed(lb) - lsn.l.ErrorIf(err, fmt.Sprintf("Unable to mark log %v as consumed", lb.String())) +func (lsn *listenerV2) getLatestHead() uint64 { + lsn.latestHeadMu.RLock() + defer lsn.latestHeadMu.RUnlock() + return lsn.latestHeadNumber } // Close complies with job.Service func (lsn *listenerV2) Close() error { return lsn.StopOnce("VRFListenerV2", func() error { close(lsn.chStop) - // wait on the request handler, log listener, and head listener to stop + // wait on the request handler, log listener lsn.wg.Wait() - return lsn.reqLogs.Close() + return nil }) } - -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") - vrfcommon.IncDroppedReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version(), vrfcommon.ReasonMailboxSize) - } -} - -// JobID complies with log.Listener -func (lsn *listenerV2) JobID() int32 { - return lsn.job.ID -} - -// ReplayStartedCallback is called by the log broadcaster when a replay is about to start. -func (lsn *listenerV2) ReplayStartedCallback() { - // Clear the log deduper cache so that we don't incorrectly ignore logs that have been sent that - // are already in the cache. - lsn.deduper.Clear() -} - -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 len(s) -} - -// GasProofVerification is an upper limit on the gas used for verifying the VRF proof on-chain. -// It can be used to estimate the amount of LINK or native needed to fulfill a request. -const GasProofVerification uint32 = 200_000 - -// EstimateFeeJuels estimates the amount of link needed to fulfill a request -// given the callback gas limit, the gas price, and the wei per unit link. -// An error is returned if the wei per unit link provided is zero. -func EstimateFeeJuels(callbackGasLimit uint32, maxGasPriceWei, weiPerUnitLink *big.Int) (*big.Int, error) { - if weiPerUnitLink.Cmp(big.NewInt(0)) == 0 { - return nil, errors.New("wei per unit link is zero") - } - maxGasUsed := big.NewInt(int64(callbackGasLimit + GasProofVerification)) - costWei := maxGasUsed.Mul(maxGasUsed, maxGasPriceWei) - // Multiply by 1e18 first so that we don't lose a ton of digits due to truncation when we divide - // by weiPerUnitLink - numerator := costWei.Mul(costWei, big.NewInt(1e18)) - costJuels := numerator.Quo(numerator, weiPerUnitLink) - return costJuels, nil -} - -// EstimateFeeWei estimates the amount of wei needed to fulfill a request -func EstimateFeeWei(callbackGasLimit uint32, maxGasPriceWei *big.Int) (*big.Int, error) { - maxGasUsed := big.NewInt(int64(callbackGasLimit + GasProofVerification)) - costWei := maxGasUsed.Mul(maxGasUsed, maxGasPriceWei) - return costWei, nil -} - -// observeRequestSimDuration records the time between the given requests simulations or -// the time until it's first simulation, whichever is applicable. -// Cases: -// 1. Never simulated: in this case, we want to observe the time until simulated -// on the utcTimestamp field of the pending request. -// 2. Simulated before: in this case, lastTry will be set to a non-zero time value, -// in which case we'd want to use that as a relative point from when we last tried -// the request. -func observeRequestSimDuration(jobName string, extJobID uuid.UUID, vrfVersion vrfcommon.Version, pendingReqs []pendingRequest) { - now := time.Now().UTC() - for _, request := range pendingReqs { - // First time around lastTry will be zero because the request has not been - // simulated yet. It will be updated every time the request is simulated (in the event - // the request is simulated multiple times, due to it being underfunded). - if request.lastTry.IsZero() { - vrfcommon.MetricTimeUntilInitialSim. - WithLabelValues(jobName, extJobID.String(), string(vrfVersion)). - Observe(float64(now.Sub(request.utcTimestamp))) - } else { - vrfcommon.MetricTimeBetweenSims. - WithLabelValues(jobName, extJobID.String(), string(vrfVersion)). - Observe(float64(now.Sub(request.lastTry))) - } - } -} - -func ptr[T any](t T) *T { return &t } diff --git a/core/services/vrf/v2/listener_v2_helpers.go b/core/services/vrf/v2/listener_v2_helpers.go new file mode 100644 index 00000000000..b3a3675e296 --- /dev/null +++ b/core/services/vrf/v2/listener_v2_helpers.go @@ -0,0 +1,103 @@ +package v2 + +import ( + "math/big" + "strings" + "time" + + "github.com/google/uuid" + "github.com/pkg/errors" + + "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" +) + +func uniqueReqs(reqs []pendingRequest) int { + s := map[string]struct{}{} + for _, r := range reqs { + s[r.req.RequestID().String()] = struct{}{} + } + return len(s) +} + +// GasProofVerification is an upper limit on the gas used for verifying the VRF proof on-chain. +// It can be used to estimate the amount of LINK or native needed to fulfill a request. +const GasProofVerification uint32 = 200_000 + +// EstimateFeeJuels estimates the amount of link needed to fulfill a request +// given the callback gas limit, the gas price, and the wei per unit link. +// An error is returned if the wei per unit link provided is zero. +func EstimateFeeJuels(callbackGasLimit uint32, maxGasPriceWei, weiPerUnitLink *big.Int) (*big.Int, error) { + if weiPerUnitLink.Cmp(big.NewInt(0)) == 0 { + return nil, errors.New("wei per unit link is zero") + } + maxGasUsed := big.NewInt(int64(callbackGasLimit + GasProofVerification)) + costWei := maxGasUsed.Mul(maxGasUsed, maxGasPriceWei) + // Multiply by 1e18 first so that we don't lose a ton of digits due to truncation when we divide + // by weiPerUnitLink + numerator := costWei.Mul(costWei, big.NewInt(1e18)) + costJuels := numerator.Quo(numerator, weiPerUnitLink) + return costJuels, nil +} + +// EstimateFeeWei estimates the amount of wei needed to fulfill a request +func EstimateFeeWei(callbackGasLimit uint32, maxGasPriceWei *big.Int) (*big.Int, error) { + maxGasUsed := big.NewInt(int64(callbackGasLimit + GasProofVerification)) + costWei := maxGasUsed.Mul(maxGasUsed, maxGasPriceWei) + return costWei, nil +} + +// observeRequestSimDuration records the time between the given requests simulations or +// the time until it's first simulation, whichever is applicable. +// Cases: +// 1. Never simulated: in this case, we want to observe the time until simulated +// on the utcTimestamp field of the pending request. +// 2. Simulated before: in this case, lastTry will be set to a non-zero time value, +// in which case we'd want to use that as a relative point from when we last tried +// the request. +func observeRequestSimDuration(jobName string, extJobID uuid.UUID, vrfVersion vrfcommon.Version, pendingReqs []pendingRequest) { + now := time.Now().UTC() + for _, request := range pendingReqs { + // First time around lastTry will be zero because the request has not been + // simulated yet. It will be updated every time the request is simulated (in the event + // the request is simulated multiple times, due to it being underfunded). + if request.lastTry.IsZero() { + vrfcommon.MetricTimeUntilInitialSim. + WithLabelValues(jobName, extJobID.String(), string(vrfVersion)). + Observe(float64(now.Sub(request.utcTimestamp))) + } else { + vrfcommon.MetricTimeBetweenSims. + WithLabelValues(jobName, extJobID.String(), string(vrfVersion)). + Observe(float64(now.Sub(request.lastTry))) + } + } +} + +func ptr[T any](t T) *T { return &t } + +func isProofVerificationError(errMsg string) bool { + // See VRF.sol for all these messages + // NOTE: it's unclear which of these errors are impossible and which + // may actually happen, so including them all to be safe. + errMessages := []string{ + "invalid x-ordinate", + "invalid y-ordinate", + "zero scalar", + "invZ must be inverse of z", + "bad witness", + "points in sum must be distinct", + "First mul check failed", + "Second mul check failed", + "public key is not on curve", + "gamma is not on curve", + "cGammaWitness is not on curve", + "sHashWitness is not on curve", + "addr(c*pk+s*g)!=_uWitness", + "invalid proof", + } + for _, msg := range errMessages { + if strings.Contains(errMsg, msg) { + return true + } + } + return false +} diff --git a/core/services/vrf/v2/listener_v2_helpers_test.go b/core/services/vrf/v2/listener_v2_helpers_test.go index 8ba900bdc3a..401a5bcc577 100644 --- a/core/services/vrf/v2/listener_v2_helpers_test.go +++ b/core/services/vrf/v2/listener_v2_helpers_test.go @@ -6,8 +6,10 @@ import ( "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" v2 "github.com/smartcontractkit/chainlink/v2/core/services/vrf/v2" + "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" ) func TestListener_EstimateFeeJuels(t *testing.T) { @@ -29,3 +31,23 @@ func TestListener_EstimateFeeJuels(t *testing.T) { require.Nil(t, actual) require.Error(t, err) } + +func Test_TxListDeduper(t *testing.T) { + tx1 := &txmgr.Tx{ + ID: 1, + Value: *big.NewInt(0), + ChainID: big.NewInt(0), + } + tx2 := &txmgr.Tx{ + ID: 1, + Value: *big.NewInt(1), + ChainID: big.NewInt(0), + } + tx3 := &txmgr.Tx{ + ID: 2, + Value: *big.NewInt(1), + ChainID: big.NewInt(0), + } + txList := vrfcommon.DedupeTxList([]*txmgr.Tx{tx1, tx2, tx3}) + require.Equal(t, len(txList), 2) +} diff --git a/core/services/vrf/v2/listener_v2_log_listener.go b/core/services/vrf/v2/listener_v2_log_listener.go new file mode 100644 index 00000000000..b35593bd1ca --- /dev/null +++ b/core/services/vrf/v2/listener_v2_log_listener.go @@ -0,0 +1,451 @@ +package v2 + +import ( + "bytes" + "context" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "go.uber.org/multierr" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" + "github.com/smartcontractkit/chainlink/v2/core/utils/mathutil" +) + +func (lsn *listenerV2) runLogListener( + pollPeriod time.Duration, + minConfs uint32, +) { + lsn.l.Infow("Listening for run requests via log poller", + "minConfs", minConfs) + ticker := time.NewTicker(pollPeriod) + defer ticker.Stop() + var ( + lastProcessedBlock int64 + startingUp = true + ) + ctx, cancel := lsn.chStop.NewCtx() + defer cancel() + for { + select { + case <-lsn.chStop: + return + case <-ticker.C: + start := time.Now() + lsn.l.Debugw("log listener loop") + // Filter registration is idempotent, so we can just call it every time + // and retry on errors using the ticker. + err := lsn.chain.LogPoller().RegisterFilter(logpoller.Filter{ + Name: logpoller.FilterName( + "VRFListener", + "version", lsn.coordinator.Version(), + "keyhash", lsn.job.VRFSpec.PublicKey.MustHash(), + "coordinatorAddress", lsn.coordinator.Address()), + EventSigs: evmtypes.HashArray{ + lsn.coordinator.RandomWordsFulfilledTopic(), + lsn.coordinator.RandomWordsRequestedTopic(), + }, + Addresses: evmtypes.AddressArray{ + lsn.coordinator.Address(), + }, + }) + if err != nil { + lsn.l.Errorw("error registering filter in log poller, retrying", + "err", err, + "elapsed", time.Since(start)) + continue + } + + // on startup we want to initialize the last processed block + if startingUp { + lsn.l.Debugw("initializing last processed block on startup") + lastProcessedBlock, err = lsn.initializeLastProcessedBlock(ctx) + if err != nil { + lsn.l.Errorw("error initializing last processed block, retrying", + "err", err, + "elapsed", time.Since(start)) + continue + } + startingUp = false + lsn.l.Debugw("initialized last processed block", "lastProcessedBlock", lastProcessedBlock) + } + + pending, err := lsn.pollLogs(ctx, minConfs, lastProcessedBlock) + if err != nil { + lsn.l.Errorw("error polling vrf logs, retrying", + "err", err, + "elapsed", time.Since(start)) + continue + } + + // process pending requests and insert any fulfillments into the inflight cache + lsn.processPendingVRFRequests(ctx, pending) + + lastProcessedBlock, err = lsn.updateLastProcessedBlock(ctx, lastProcessedBlock) + if err != nil { + lsn.l.Errorw("error updating last processed block, continuing anyway", "err", err) + } else { + lsn.l.Debugw("updated last processed block", "lastProcessedBlock", lastProcessedBlock) + } + lsn.l.Debugw("log listener loop done", "elapsed", time.Since(start)) + } + } +} + +// initializeLastProcessedBlock returns the earliest block number that we need to +// process requests for. This is the block number of the earliest unfulfilled request +// or the latest finalized block, if there are no unfulfilled requests. +// TODO: add tests +func (lsn *listenerV2) initializeLastProcessedBlock(ctx context.Context) (lastProcessedBlock int64, err error) { + lp := lsn.chain.LogPoller() + start := time.Now() + + // will retry on error in the runLogListener loop + latestBlock, err := lp.LatestBlock() + if err != nil { + return 0, fmt.Errorf("LogPoller.LatestBlock(): %w", err) + } + fromTimestamp := time.Now().UTC().Add(-lsn.job.VRFSpec.RequestTimeout) + ll := lsn.l.With( + "latestFinalizedBlock", latestBlock.FinalizedBlockNumber, + "latestBlock", latestBlock.BlockNumber, + "fromTimestamp", fromTimestamp) + ll.Debugw("Initializing last processed block") + defer func() { + ll.Debugw("Done initializing last processed block", "elapsed", time.Since(start)) + }() + + numBlocksToReplay := numReplayBlocks(lsn.job.VRFSpec.RequestTimeout, lsn.chain.ID()) + ll.Debugw("running replay on log poller") + err = lp.Replay(ctx, mathutil.Max(latestBlock.FinalizedBlockNumber-numBlocksToReplay, 1)) + if err != nil { + return 0, fmt.Errorf("LogPoller.Replay: %w", err) + } + + // get randomness requested logs with the appropriate keyhash + // keyhash is specified in topic1 + requests, err := lp.IndexedLogsCreatedAfter( + lsn.coordinator.RandomWordsRequestedTopic(), // event sig + lsn.coordinator.Address(), // address + 1, // topic index + []common.Hash{lsn.job.VRFSpec.PublicKey.MustHash()}, // topic values + fromTimestamp, // from time + logpoller.Finalized, // confs + ) + if err != nil { + return 0, fmt.Errorf("LogPoller.LogsCreatedAfter RandomWordsRequested logs: %w", err) + } + + // fulfillments don't have keyhash indexed, we'll have to get all of them + // TODO: can we instead write a single query that joins on request id's somehow? + fulfillments, err := lp.LogsCreatedAfter( + lsn.coordinator.RandomWordsFulfilledTopic(), // event sig + lsn.coordinator.Address(), // address + fromTimestamp, // from time + logpoller.Finalized, // confs + ) + if err != nil { + return 0, fmt.Errorf("LogPoller.LogsCreatedAfter RandomWordsFulfilled logs: %w", err) + } + + unfulfilled, _, _ := lsn.getUnfulfilled(append(requests, fulfillments...), ll) + // find request block of earliest unfulfilled request + // even if this block is > latest finalized, we use latest finalized as earliest unprocessed + // because re-orgs can occur on any unfinalized block. + var earliestUnfulfilledBlock = latestBlock.FinalizedBlockNumber + for _, req := range unfulfilled { + if req.Raw().BlockNumber < uint64(earliestUnfulfilledBlock) { + earliestUnfulfilledBlock = int64(req.Raw().BlockNumber) + } + } + + return earliestUnfulfilledBlock, nil +} + +func (lsn *listenerV2) updateLastProcessedBlock(ctx context.Context, currLastProcessedBlock int64) (lastProcessedBlock int64, err error) { + lp := lsn.chain.LogPoller() + start := time.Now() + + latestBlock, err := lp.LatestBlock(pg.WithParentCtx(ctx)) + if err != nil { + lsn.l.Errorw("error getting latest block", "err", err) + return 0, fmt.Errorf("LogPoller.LatestBlock(): %w", err) + } + ll := lsn.l.With( + "currLastProcessedBlock", currLastProcessedBlock, + "latestBlock", latestBlock.BlockNumber, + "latestFinalizedBlock", latestBlock.FinalizedBlockNumber) + ll.Debugw("updating last processed block") + defer func() { + ll.Debugw("done updating last processed block", "elapsed", time.Since(start)) + }() + + logs, err := lp.LogsWithSigs( + currLastProcessedBlock, + latestBlock.FinalizedBlockNumber, + []common.Hash{lsn.coordinator.RandomWordsFulfilledTopic(), lsn.coordinator.RandomWordsRequestedTopic()}, + lsn.coordinator.Address(), + pg.WithParentCtx(ctx), + ) + if err != nil { + return currLastProcessedBlock, fmt.Errorf("LogPoller.LogsWithSigs: %w", err) + } + + unfulfilled, unfulfilledLP, _ := lsn.getUnfulfilled(logs, ll) + // find request block of earliest unfulfilled request + // even if this block is > latest finalized, we use latest finalized as earliest unprocessed + // because re-orgs can occur on any unfinalized block. + var earliestUnprocessedRequestBlock = latestBlock.FinalizedBlockNumber + for i, req := range unfulfilled { + // need to drop requests that have timed out otherwise the earliestUnprocessedRequestBlock + // will be unnecessarily far back and our queries will be slower. + if unfulfilledLP[i].CreatedAt.Before(time.Now().UTC().Add(-lsn.job.VRFSpec.RequestTimeout)) { + // request timed out, don't process + lsn.l.Debugw("request timed out, skipping", + "reqID", req.RequestID(), + ) + continue + } + if req.Raw().BlockNumber < uint64(earliestUnprocessedRequestBlock) { + earliestUnprocessedRequestBlock = int64(req.Raw().BlockNumber) + } + } + + return earliestUnprocessedRequestBlock, nil +} + +// pollLogs uses the log poller to poll for the latest VRF logs +func (lsn *listenerV2) pollLogs(ctx context.Context, minConfs uint32, lastProcessedBlock int64) (pending []pendingRequest, err error) { + start := time.Now() + lp := lsn.chain.LogPoller() + + // latest unfinalized block used on purpose to get bleeding edge logs + // we don't really have the luxury to wait for finalization on most chains + // if we want to fulfill on time. + latestBlock, err := lp.LatestBlock() + if err != nil { + return nil, fmt.Errorf("LogPoller.LatestBlock(): %w", err) + } + lsn.setLatestHead(latestBlock) + ll := lsn.l.With( + "lastProcessedBlock", lastProcessedBlock, + "minConfs", minConfs, + "latestBlock", latestBlock.BlockNumber, + "latestFinalizedBlock", latestBlock.FinalizedBlockNumber) + ll.Debugw("polling for logs") + defer func() { + ll.Debugw("done polling for logs", "elapsed", time.Since(start)) + }() + + // We don't specify confs because each request can have a different conf above + // the minimum. So we do all conf handling in getConfirmedAt. + logs, err := lp.LogsWithSigs( + lastProcessedBlock, + latestBlock.BlockNumber, + []common.Hash{lsn.coordinator.RandomWordsFulfilledTopic(), lsn.coordinator.RandomWordsRequestedTopic()}, + lsn.coordinator.Address(), + pg.WithParentCtx(ctx), + ) + if err != nil { + return nil, fmt.Errorf("LogPoller.LogsWithSigs: %w", err) + } + + unfulfilled, unfulfilledLP, fulfilled := lsn.getUnfulfilled(logs, ll) + if len(unfulfilled) > 0 { + ll.Debugw("found unfulfilled logs", "unfulfilled", len(unfulfilled)) + } else { + ll.Debugw("no unfulfilled logs found") + } + + lsn.handleFulfilled(fulfilled) + + return lsn.handleRequested(unfulfilled, unfulfilledLP, minConfs), nil +} + +func (lsn *listenerV2) getUnfulfilled(logs []logpoller.Log, ll logger.Logger) (unfulfilled []RandomWordsRequested, unfulfilledLP []logpoller.Log, fulfilled map[string]RandomWordsFulfilled) { + var ( + requested = make(map[string]RandomWordsRequested) + requestedLP = make(map[string]logpoller.Log) + errs error + expectedKeyHash = lsn.job.VRFSpec.PublicKey.MustHash() + ) + fulfilled = make(map[string]RandomWordsFulfilled) + for _, l := range logs { + if l.EventSig == lsn.coordinator.RandomWordsFulfilledTopic() { + parsed, err2 := lsn.coordinator.ParseRandomWordsFulfilled(l.ToGethLog()) + if err2 != nil { + // should never happen + errs = multierr.Append(errs, err2) + continue + } + fulfilled[parsed.RequestID().String()] = parsed + } else if l.EventSig == lsn.coordinator.RandomWordsRequestedTopic() { + parsed, err2 := lsn.coordinator.ParseRandomWordsRequested(l.ToGethLog()) + if err2 != nil { + // should never happen + errs = multierr.Append(errs, err2) + continue + } + keyHash := parsed.KeyHash() + if !bytes.Equal(keyHash[:], expectedKeyHash[:]) { + // wrong keyhash, can ignore + continue + } + requested[parsed.RequestID().String()] = parsed + requestedLP[parsed.RequestID().String()] = l + } + } + // should never happen, unsure if recoverable + // may be worth a panic + if errs != nil { + ll.Errorw("encountered parse errors", "err", errs) + } + + if len(fulfilled) > 0 || len(requested) > 0 { + ll.Infow("found logs", "fulfilled", len(fulfilled), "requested", len(requested)) + } else { + ll.Debugw("no logs found") + } + + // find unfulfilled requests by comparing requested events with the fulfilled events + for reqID, req := range requested { + if _, isFulfilled := fulfilled[reqID]; !isFulfilled { + unfulfilled = append(unfulfilled, req) + unfulfilledLP = append(unfulfilledLP, requestedLP[reqID]) + } + } + + return unfulfilled, unfulfilledLP, fulfilled +} + +func (lsn *listenerV2) getConfirmedAt(req RandomWordsRequested, nodeMinConfs uint32) uint64 { + // Take the max(nodeMinConfs, requestedConfs + requestedConfsDelay). + // Add the requested confs delay if provided in the jobspec so that we avoid an edge case + // where the primary and backup VRF v2 nodes submit a proof at the same time. + minConfs := nodeMinConfs + if uint32(req.MinimumRequestConfirmations())+uint32(lsn.job.VRFSpec.RequestedConfsDelay) > nodeMinConfs { + minConfs = uint32(req.MinimumRequestConfirmations()) + uint32(lsn.job.VRFSpec.RequestedConfsDelay) + } + newConfs := uint64(minConfs) * (1 << lsn.respCount[req.RequestID().String()]) + // We cap this at 200 because solidity only supports the most recent 256 blocks + // in the contract so if it was older than that, fulfillments would start failing + // without the blockhash store feeder. We use 200 to give the node plenty of time + // to fulfill even on fast chains. + if newConfs > 200 { + newConfs = 200 + } + if lsn.respCount[req.RequestID().String()] > 0 { + lsn.l.Warnw("Duplicate request found after fulfillment, doubling incoming confirmations", + "txHash", req.Raw().TxHash, + "blockNumber", req.Raw().BlockNumber, + "blockHash", req.Raw().BlockHash, + "reqID", req.RequestID().String(), + "newConfs", newConfs) + vrfcommon.IncDupeReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version()) + } + return req.Raw().BlockNumber + newConfs +} + +func (lsn *listenerV2) handleFulfilled(fulfilled map[string]RandomWordsFulfilled) { + for _, v := range fulfilled { + // don't process same log over again + // log key includes block number and blockhash, so on re-orgs it would return true + // and we would re-process the re-orged request. + if !lsn.fulfillmentLogDeduper.ShouldDeliver(v.Raw()) { + continue + } + lsn.l.Debugw("Received fulfilled log", "reqID", v.RequestID(), "success", v.Success()) + lsn.respCount[v.RequestID().String()]++ + lsn.blockNumberToReqID.Insert(fulfilledReqV2{ + blockNumber: v.Raw().BlockNumber, + reqID: v.RequestID().String(), + }) + } +} + +func (lsn *listenerV2) handleRequested(requested []RandomWordsRequested, requestedLP []logpoller.Log, minConfs uint32) (pendingRequests []pendingRequest) { + for i, req := range requested { + // don't process same log over again + // log key includes block number and blockhash, so on re-orgs it would return true + // and we would re-process the re-orged request. + if lsn.inflightCache.Contains(req.Raw()) { + continue + } + + confirmedAt := lsn.getConfirmedAt(req, minConfs) + lsn.l.Debugw("VRFListenerV2: Received log request", + "reqID", req.RequestID(), + "reqBlockNumber", req.Raw().BlockNumber, + "reqBlockHash", req.Raw().BlockHash, + "reqTxHash", req.Raw().TxHash, + "confirmedAt", confirmedAt, + "subID", req.SubID(), + "sender", req.Sender()) + pendingRequests = append(pendingRequests, pendingRequest{ + confirmedAtBlock: confirmedAt, + req: req, + utcTimestamp: requestedLP[i].CreatedAt.UTC(), + }) + lsn.reqAdded() + } + + return pendingRequests +} + +// numReplayBlocks returns the number of blocks to replay on startup +// given the request timeout and the chain ID. +// if the chain ID is not recognized it assumes a block time of 1 second +// and returns the number of blocks in a day. +func numReplayBlocks(requestTimeout time.Duration, chainID *big.Int) int64 { + var timeoutSeconds = int64(requestTimeout.Seconds()) + switch chainID.String() { + case "1": // eth mainnet + case "3": // eth ropsten + case "4": // eth rinkeby + case "5": // eth goerli + case "11155111": // eth sepolia + // block time is 12s + return timeoutSeconds / 12 + case "137": // polygon mainnet + case "80001": // polygon mumbai + // block time is 2s + return timeoutSeconds / 2 + case "56": // bsc mainnet + case "97": // bsc testnet + // block time is 2s + return timeoutSeconds / 2 + case "43114": // avalanche mainnet + case "43113": // avalanche fuji + // block time is 1s + return timeoutSeconds + case "250": // fantom mainnet + case "4002": // fantom testnet + // block time is 1s + return timeoutSeconds + case "42161": // arbitrum mainnet + case "421613": // arbitrum goerli + case "421614": // arbitrum sepolia + // block time is 0.25s in the worst case + return timeoutSeconds * 4 + case "10": // optimism mainnet + case "69": // optimism kovan + case "420": // optimism goerli + case "11155420": // optimism sepolia + case "8453": // base mainnet + case "84531": // base goerli + case "84532": // base sepolia + // block time is 2s + return timeoutSeconds / 2 + default: + // assume block time of 1s + return timeoutSeconds + } + // assume block time of 1s + return timeoutSeconds +} diff --git a/core/services/vrf/v2/listener_v2_log_processor.go b/core/services/vrf/v2/listener_v2_log_processor.go new file mode 100644 index 00000000000..004ab4c4905 --- /dev/null +++ b/core/services/vrf/v2/listener_v2_log_processor.go @@ -0,0 +1,1214 @@ +package v2 + +import ( + "cmp" + "context" + "database/sql" + "fmt" + "math" + "math/big" + "slices" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum" + "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/pkg/errors" + "go.uber.org/multierr" + + txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2plus_interface" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" + "github.com/smartcontractkit/chainlink/v2/core/utils" + bigmath "github.com/smartcontractkit/chainlink/v2/core/utils/big_math" +) + +// Returns all the confirmed logs from the provided pending queue by subscription +func (lsn *listenerV2) getConfirmedLogsBySub(latestHead uint64, pendingRequests []pendingRequest) map[string][]pendingRequest { + vrfcommon.UpdateQueueSize(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version(), uniqueReqs(pendingRequests)) + var toProcess = make(map[string][]pendingRequest) + for _, request := range pendingRequests { + if lsn.ready(request, latestHead) { + toProcess[request.req.SubID().String()] = append(toProcess[request.req.SubID().String()], request) + } + } + return toProcess +} + +func (lsn *listenerV2) ready(req pendingRequest, latestHead uint64) bool { + // Request is not eligible for fulfillment yet + if req.confirmedAtBlock > latestHead { + return false + } + + if lsn.job.VRFSpec.BackoffInitialDelay == 0 || req.attempts == 0 { + // Backoff is disabled, or this is the first try + return true + } + + return time.Now().UTC().After( + nextTry( + req.attempts, + lsn.job.VRFSpec.BackoffInitialDelay, + lsn.job.VRFSpec.BackoffMaxDelay, + req.lastTry)) +} + +func nextTry(retries int, initial, max time.Duration, last time.Time) time.Time { + expBackoffFactor := math.Pow(backoffFactor, float64(retries-1)) + + var delay time.Duration + if expBackoffFactor > float64(max/initial) { + delay = max + } else { + delay = time.Duration(float64(initial) * expBackoffFactor) + } + return last.Add(delay) +} + +// Remove all entries 10000 blocks or older +// to avoid a memory leak. +func (lsn *listenerV2) pruneConfirmedRequestCounts() { + min := lsn.blockNumberToReqID.FindMin() + for min != nil { + m := min.(fulfilledReqV2) + if m.blockNumber > (lsn.getLatestHead() - 10000) { + break + } + delete(lsn.respCount, m.reqID) + lsn.blockNumberToReqID.DeleteMin() + min = lsn.blockNumberToReqID.FindMin() + } +} + +// 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 txm as already "spent" link or native, +// using a max link or max native 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. +// Other variables which can change the bill amount between our eth call simulation and tx execution: +// - Link/eth price fluctuation +// - Falling back to BHS +// However the likelihood is vanishingly small as +// 1) the window between simulation and tx execution is tiny. +// 2) the max gas price provides a very large buffer most of the time. +// Its easier to optimistically assume it will go though and in the rare case of a reversion +// we simply retry TODO: follow up where if we see a fulfillment revert, return log to the queue. +func (lsn *listenerV2) processPendingVRFRequests(ctx context.Context, pendingRequests []pendingRequest) { + confirmed := lsn.getConfirmedLogsBySub(lsn.getLatestHead(), pendingRequests) + var processedMu sync.Mutex + processed := make(map[string]struct{}) + start := time.Now() + + defer func() { + for _, subReqs := range confirmed { + for _, req := range subReqs { + if _, ok := processed[req.req.RequestID().String()]; ok { + // add to the inflight cache so that we don't re-process this request + lsn.inflightCache.Add(req.req.Raw()) + } + } + } + lsn.l.Infow("Finished processing pending requests", + "totalProcessed", len(processed), + "totalFailed", len(pendingRequests)-len(processed), + "total", len(pendingRequests), + "time", time.Since(start).String(), + "inflightCacheSize", lsn.inflightCache.Size()) + }() + + if len(confirmed) == 0 { + lsn.l.Infow("No pending requests ready for processing") + return + } + for subID, reqs := range confirmed { + l := lsn.l.With("subID", subID, "startTime", time.Now(), "numReqsForSub", len(reqs)) + // Get the balance of the subscription and also it's active status. + // The reason we need both is that we cannot determine if a subscription + // is active solely by it's balance, since an active subscription could legitimately + // have a zero balance. + var ( + startLinkBalance *big.Int + startEthBalance *big.Int + subIsActive bool + ) + sID, ok := new(big.Int).SetString(subID, 10) + if !ok { + l.Criticalw("Unable to convert %s to Int", subID) + return + } + sub, err := lsn.coordinator.GetSubscription(&bind.CallOpts{ + Context: ctx}, sID) + + if err != nil { + if strings.Contains(err.Error(), "execution reverted") { + // "execution reverted" indicates that the subscription no longer exists. + // We can no longer just mark these as processed and continue, + // since it could be that the subscription was canceled while there + // were still unfulfilled requests. + // The simplest approach to handle this is to enter the processRequestsPerSub + // loop rather than create a bunch of largely duplicated code + // to handle this specific situation, since we need to run the pipeline to get + // the VRF proof, abi-encode it, etc. + l.Warnw("Subscription not found - setting start balance to zero", "subID", subID, "err", err) + startLinkBalance = big.NewInt(0) + } else { + // Most likely this is an RPC error, so we re-try later. + l.Errorw("Unable to read subscription balance", "err", err) + return + } + } else { + // Happy path - sub is active. + startLinkBalance = sub.Balance() + if sub.Version() == vrfcommon.V2Plus { + startEthBalance = sub.NativeBalance() + } + subIsActive = true + } + + // Sort requests in ascending order by CallbackGasLimit + // so that we process the "cheapest" requests for each subscription + // first. This allows us to break out of the processing loop as early as possible + // in the event that a subscription is too underfunded to have it's + // requests processed. + slices.SortFunc(reqs, func(a, b pendingRequest) int { + return cmp.Compare(a.req.CallbackGasLimit(), b.req.CallbackGasLimit()) + }) + + p := lsn.processRequestsPerSub(ctx, sID, startLinkBalance, startEthBalance, reqs, subIsActive) + processedMu.Lock() + for reqID := range p { + processed[reqID] = struct{}{} + } + processedMu.Unlock() + } + lsn.pruneConfirmedRequestCounts() +} + +// 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 (lsn *listenerV2) MaybeSubtractReservedLink(ctx context.Context, startBalance *big.Int, chainID *big.Int, subID *big.Int, vrfVersion vrfcommon.Version) (*big.Int, error) { + var metaField string + if vrfVersion == vrfcommon.V2Plus { + metaField = txMetaGlobalSubId + } else if vrfVersion == vrfcommon.V2 { + metaField = txMetaFieldSubId + } else { + return nil, errors.Errorf("unsupported vrf version %s", vrfVersion) + } + + txes, err := lsn.chain.TxManager().FindTxesByMetaFieldAndStates(ctx, metaField, subID.String(), reserveEthLinkQueryStates, chainID) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("TXM FindTxesByMetaFieldAndStates failed: %w", err) + } + + reservedLinkSum := big.NewInt(0) + // Aggregate non-null MaxLink from all txes returned + for _, tx := range txes { + var meta *txmgrtypes.TxMeta[common.Address, common.Hash] + meta, err = tx.GetMeta() + if err != nil { + return nil, fmt.Errorf("GetMeta for Tx failed: %w", err) + } + if meta != nil && meta.MaxLink != nil { + txMaxLink, success := new(big.Int).SetString(*meta.MaxLink, 10) + if !success { + return nil, fmt.Errorf("converting reserved LINK %s", *meta.MaxLink) + } + + reservedLinkSum.Add(reservedLinkSum, txMaxLink) + } + } + + return new(big.Int).Sub(startBalance, reservedLinkSum), nil +} + +// MaybeSubtractReservedEth figures out how much ether 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 (lsn *listenerV2) MaybeSubtractReservedEth(ctx context.Context, startBalance *big.Int, chainID *big.Int, subID *big.Int, vrfVersion vrfcommon.Version) (*big.Int, error) { + var metaField string + if vrfVersion == vrfcommon.V2Plus { + metaField = txMetaGlobalSubId + } else if vrfVersion == vrfcommon.V2 { + // native payment is not supported for v2, so returning 0 reserved ETH + return big.NewInt(0), nil + } else { + return nil, errors.Errorf("unsupported vrf version %s", vrfVersion) + } + txes, err := lsn.chain.TxManager().FindTxesByMetaFieldAndStates(ctx, metaField, subID.String(), reserveEthLinkQueryStates, chainID) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("TXM FindTxesByMetaFieldAndStates failed: %w", err) + } + + reservedEthSum := big.NewInt(0) + // Aggregate non-null MaxEth from all txes returned + for _, tx := range txes { + var meta *txmgrtypes.TxMeta[common.Address, common.Hash] + meta, err = tx.GetMeta() + if err != nil { + return nil, fmt.Errorf("GetMeta for Tx failed: %w", err) + } + if meta != nil && meta.MaxEth != nil { + txMaxEth, success := new(big.Int).SetString(*meta.MaxEth, 10) + if !success { + return nil, fmt.Errorf("converting reserved ETH %s", *meta.MaxEth) + } + + reservedEthSum.Add(reservedEthSum, txMaxEth) + } + } + + if startBalance != nil { + return new(big.Int).Sub(startBalance, reservedEthSum), nil + } + return big.NewInt(0), nil +} + +func (lsn *listenerV2) processRequestsPerSubBatchHelper( + ctx context.Context, + subID *big.Int, + startBalance *big.Int, + startBalanceNoReserved *big.Int, + reqs []pendingRequest, + subIsActive bool, + nativePayment bool, +) (processed map[string]struct{}) { + start := time.Now() + processed = make(map[string]struct{}) + + // Base the max gas for a batch on the max gas limit for a single callback. + // Since the max gas limit for a single callback is usually quite large already, + // we probably don't want to exceed it too much so that we can reliably get + // batch fulfillments included, while also making sure that the biggest gas guzzler + // callbacks are included. + config, err := lsn.coordinator.GetConfig(&bind.CallOpts{ + Context: ctx, + }) + if err != nil { + lsn.l.Errorw("Couldn't get config from coordinator", "err", err) + return processed + } + + // Add very conservative upper bound estimate on verification costs. + batchMaxGas := config.MaxGasLimit() + 400_000 + + l := lsn.l.With( + "subID", subID, + "eligibleSubReqs", len(reqs), + "startBalance", startBalance.String(), + "startBalanceNoReserved", startBalanceNoReserved.String(), + "batchMaxGas", batchMaxGas, + "subIsActive", subIsActive, + "nativePayment", nativePayment, + ) + + defer func() { + l.Infow("Finished processing for sub", + "endBalance", startBalanceNoReserved.String(), + "totalProcessed", len(processed), + "totalUnique", uniqueReqs(reqs), + "time", time.Since(start).String()) + }() + + l.Infow("Processing requests for subscription with batching") + + ready, expired := lsn.getReadyAndExpired(l, reqs) + for _, reqID := range expired { + processed[reqID] = struct{}{} + } + + // Process requests in chunks in order to kick off as many jobs + // as configured in parallel. Then we can combine into fulfillment + // batches afterwards. + for chunkStart := 0; chunkStart < len(ready); chunkStart += int(lsn.job.VRFSpec.ChunkSize) { + chunkEnd := chunkStart + int(lsn.job.VRFSpec.ChunkSize) + if chunkEnd > len(ready) { + chunkEnd = len(ready) + } + chunk := ready[chunkStart:chunkEnd] + + var unfulfilled []pendingRequest + alreadyFulfilled, err := lsn.checkReqsFulfilled(ctx, l, chunk) + if errors.Is(err, context.Canceled) { + l.Infow("Context canceled, stopping request processing", "err", err) + return processed + } else if err != nil { + l.Errorw("Error checking for already fulfilled requests, proceeding anyway", "err", err) + } + for i, a := range alreadyFulfilled { + if a { + processed[chunk[i].req.RequestID().String()] = struct{}{} + } else { + unfulfilled = append(unfulfilled, chunk[i]) + } + } + + // All fromAddresses passed to the VRFv2 job have the same KeySpecific-MaxPrice value. + fromAddresses := lsn.fromAddresses() + maxGasPriceWei := lsn.feeCfg.PriceMaxKey(fromAddresses[0]) + + // Cases: + // 1. Never simulated: in this case, we want to observe the time until simulated + // on the utcTimestamp field of the pending request. + // 2. Simulated before: in this case, lastTry will be set to a non-zero time value, + // in which case we'd want to use that as a relative point from when we last tried + // the request. + observeRequestSimDuration(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version(), unfulfilled) + + pipelines := lsn.runPipelines(ctx, l, maxGasPriceWei, unfulfilled) + batches := newBatchFulfillments(batchMaxGas, lsn.coordinator.Version()) + outOfBalance := false + for _, p := range pipelines { + ll := l.With("reqID", p.req.req.RequestID().String(), + "txHash", p.req.req.Raw().TxHash, + "maxGasPrice", maxGasPriceWei.String(), + "fundsNeeded", p.fundsNeeded.String(), + "maxFee", p.maxFee.String(), + "gasLimit", p.gasLimit, + "attempts", p.req.attempts, + "remainingBalance", startBalanceNoReserved.String(), + "consumerAddress", p.req.req.Sender(), + "blockNumber", p.req.req.Raw().BlockNumber, + "blockHash", p.req.req.Raw().BlockHash, + ) + fromAddresses := lsn.fromAddresses() + fromAddress, err := lsn.gethks.GetRoundRobinAddress(lsn.chainID, fromAddresses...) + if err != nil { + l.Errorw("Couldn't get next from address", "err", err) + continue + } + ll = ll.With("fromAddress", fromAddress) + + if p.err != nil { + if errors.Is(p.err, errBlockhashNotInStore{}) { + // Running the blockhash store feeder in backwards mode will be required to + // resolve this. + ll.Criticalw("Pipeline error", "err", p.err) + } else if errors.Is(p.err, errProofVerificationFailed{}) { + // This occurs when the proof reverts in the simulation + // This is almost always (if not always) due to a proof generated with an out-of-date + // blockhash + // we can simply mark as processed and move on, since we will eventually + // process the request with the right blockhash + ll.Infow("proof reverted in simulation, likely stale blockhash") + processed[p.req.req.RequestID().String()] = struct{}{} + } else { + ll.Errorw("Pipeline error", "err", p.err) + if !subIsActive { + ll.Warnw("Force-fulfilling a request with insufficient funds on a cancelled sub") + etx, err := lsn.enqueueForceFulfillment(ctx, p, fromAddress) + if err != nil { + ll.Errorw("Error enqueuing force-fulfillment, re-queueing request", "err", err) + continue + } + ll.Infow("Successfully enqueued force-fulfillment", "ethTxID", etx.ID) + processed[p.req.req.RequestID().String()] = struct{}{} + + // Need to put a continue here, otherwise the next if statement will be hit + // and we'd break out of the loop prematurely. + // If a sub is canceled, we want to force-fulfill ALL of it's pending requests + // before saying we're done with it. + continue + } + + if startBalanceNoReserved.Cmp(p.fundsNeeded) < 0 && errors.Is(p.err, errPossiblyInsufficientFunds{}) { + ll.Infow("Insufficient balance to fulfill a request based on estimate, breaking", "err", p.err) + outOfBalance = true + + // break out of this inner loop to process the currently constructed batch + break + } + + // Ensure consumer is valid, otherwise drop the request. + if !lsn.isConsumerValidAfterFinalityDepthElapsed(ctx, p.req) { + lsn.l.Infow( + "Dropping request that was made by an invalid consumer.", + "consumerAddress", p.req.req.Sender(), + "reqID", p.req.req.RequestID(), + "blockNumber", p.req.req.Raw().BlockNumber, + "blockHash", p.req.req.Raw().BlockHash, + ) + processed[p.req.req.RequestID().String()] = struct{}{} + continue + } + } + continue + } + + if startBalanceNoReserved.Cmp(p.maxFee) < 0 { + // Insufficient funds, have to wait for a user top up. + // Break out of the loop now and process what we are able to process + // in the constructed batches. + ll.Infow("Insufficient balance to fulfill a request, breaking") + break + } + + batches.addRun(p, fromAddress) + + startBalanceNoReserved.Sub(startBalanceNoReserved, p.maxFee) + } + + var processedRequestIDs []string + for _, batch := range batches.fulfillments { + l.Debugw("Processing batch", "batchSize", len(batch.proofs)) + p := lsn.processBatch(l, subID, startBalanceNoReserved, batchMaxGas, batch, batch.fromAddress) + processedRequestIDs = append(processedRequestIDs, p...) + } + + for _, reqID := range processedRequestIDs { + processed[reqID] = struct{}{} + } + + // outOfBalance is set to true if the current sub we are processing + // has run out of funds to process any remaining requests. After enqueueing + // this constructed batch, we break out of this outer loop in order to + // avoid unnecessarily processing the remaining requests. + if outOfBalance { + break + } + } + + return +} + +func (lsn *listenerV2) processRequestsPerSubBatch( + ctx context.Context, + subID *big.Int, + startLinkBalance *big.Int, + startEthBalance *big.Int, + reqs []pendingRequest, + subIsActive bool, +) map[string]struct{} { + var processed = make(map[string]struct{}) + startBalanceNoReserveLink, err := lsn.MaybeSubtractReservedLink( + ctx, startLinkBalance, lsn.chainID, subID, lsn.coordinator.Version()) + if err != nil { + lsn.l.Errorw("Couldn't get reserved LINK for subscription", "sub", reqs[0].req.SubID(), "err", err) + return processed + } + startBalanceNoReserveEth, err := lsn.MaybeSubtractReservedEth( + ctx, startEthBalance, lsn.chainID, subID, lsn.coordinator.Version()) + if err != nil { + lsn.l.Errorw("Couldn't get reserved ether for subscription", "sub", reqs[0].req.SubID(), "err", err) + return processed + } + + // Split the requests into native and LINK requests. + var ( + nativeRequests []pendingRequest + linkRequests []pendingRequest + ) + for _, req := range reqs { + if req.req.NativePayment() { + nativeRequests = append(nativeRequests, req) + } else { + linkRequests = append(linkRequests, req) + } + } + // process the native and link requests in parallel + var wg sync.WaitGroup + var nativeProcessed, linkProcessed map[string]struct{} + wg.Add(2) + go func() { + defer wg.Done() + nativeProcessed = lsn.processRequestsPerSubBatchHelper(ctx, subID, startEthBalance, startBalanceNoReserveEth, nativeRequests, subIsActive, true) + }() + go func() { + defer wg.Done() + linkProcessed = lsn.processRequestsPerSubBatchHelper(ctx, subID, startLinkBalance, startBalanceNoReserveLink, linkRequests, subIsActive, false) + }() + wg.Wait() + // combine the processed link and native requests into the processed map + for k, v := range nativeProcessed { + processed[k] = v + } + for k, v := range linkProcessed { + processed[k] = v + } + + return processed +} + +// enqueueForceFulfillment enqueues a forced fulfillment through the +// VRFOwner contract. It estimates gas again on the transaction due +// to the extra steps taken within VRFOwner.fulfillRandomWords. +func (lsn *listenerV2) enqueueForceFulfillment( + ctx context.Context, + p vrfPipelineResult, + fromAddress common.Address, +) (etx txmgr.Tx, err error) { + if lsn.job.VRFSpec.VRFOwnerAddress == nil { + err = errors.New("vrf owner address not set in job spec, recreate job and provide it to force-fulfill") + return + } + + if p.payload == "" { + // should probably never happen + // a critical log will be logged if this is the case in simulateFulfillment + err = errors.New("empty payload in vrfPipelineResult") + return + } + + // fulfill the request through the VRF owner + err = lsn.q.Transaction(func(tx pg.Queryer) error { + lsn.l.Infow("VRFOwner.fulfillRandomWords vs. VRFCoordinatorV2.fulfillRandomWords", + "vrf_owner.fulfillRandomWords", hexutil.Encode(vrfOwnerABI.Methods["fulfillRandomWords"].ID), + "vrf_coordinator_v2.fulfillRandomWords", hexutil.Encode(coordinatorV2ABI.Methods["fulfillRandomWords"].ID), + ) + + vrfOwnerAddress1 := lsn.vrfOwner.Address() + vrfOwnerAddressSpec := lsn.job.VRFSpec.VRFOwnerAddress.Address() + lsn.l.Infow("addresses diff", "wrapper_address", vrfOwnerAddress1, "spec_address", vrfOwnerAddressSpec) + + lsn.l.Infow("fulfillRandomWords payload", "proof", p.proof, "commitment", p.reqCommitment.Get(), "payload", p.payload) + txData := hexutil.MustDecode(p.payload) + if err != nil { + return fmt.Errorf("abi pack VRFOwner.fulfillRandomWords: %w", err) + } + estimateGasLimit, err := lsn.chain.Client().EstimateGas(ctx, ethereum.CallMsg{ + From: fromAddress, + To: &vrfOwnerAddressSpec, + Data: txData, + }) + if err != nil { + return fmt.Errorf("failed to estimate gas on VRFOwner.fulfillRandomWords: %w", err) + } + + lsn.l.Infow("Estimated gas limit on force fulfillment", + "estimateGasLimit", estimateGasLimit, "pipelineGasLimit", p.gasLimit) + if estimateGasLimit < uint64(p.gasLimit) { + estimateGasLimit = uint64(p.gasLimit) + } + + requestID := common.BytesToHash(p.req.req.RequestID().Bytes()) + subID := p.req.req.SubID() + requestTxHash := p.req.req.Raw().TxHash + etx, err = lsn.chain.TxManager().CreateTransaction(ctx, txmgr.TxRequest{ + FromAddress: fromAddress, + ToAddress: lsn.vrfOwner.Address(), + EncodedPayload: txData, + FeeLimit: uint32(estimateGasLimit), + Strategy: txmgrcommon.NewSendEveryStrategy(), + Meta: &txmgr.TxMeta{ + RequestID: &requestID, + SubID: ptr(subID.Uint64()), + RequestTxHash: &requestTxHash, + // No max link since simulation failed + }, + }) + return err + }) + return +} + +// For an errored pipeline run, wait until the finality depth of the chain to have elapsed, +// then check if the failing request is being called by an invalid sender. Return false if this is the case, +// otherwise true. +func (lsn *listenerV2) isConsumerValidAfterFinalityDepthElapsed(ctx context.Context, req pendingRequest) bool { + latestHead := lsn.getLatestHead() + if latestHead-req.req.Raw().BlockNumber > uint64(lsn.cfg.FinalityDepth()) { + code, err := lsn.chain.Client().CodeAt(ctx, req.req.Sender(), big.NewInt(int64(latestHead))) + if err != nil { + lsn.l.Warnw("Failed to fetch contract code", "err", err) + return true // error fetching code, give the benefit of doubt to the consumer + } + if len(code) == 0 { + return false // invalid consumer + } + } + + return true // valid consumer, or finality depth has not elapsed +} + +// processRequestsPerSubHelper processes a set of pending requests for the provided sub id. +// It returns a set of request IDs that were processed. +// Note that the provided startBalanceNoReserve is the balance of the subscription +// minus any pending requests that have already been processed and not yet fulfilled onchain. +func (lsn *listenerV2) processRequestsPerSubHelper( + ctx context.Context, + subID *big.Int, + startBalance *big.Int, + startBalanceNoReserved *big.Int, + reqs []pendingRequest, + subIsActive bool, + nativePayment bool, +) (processed map[string]struct{}) { + start := time.Now() + processed = make(map[string]struct{}) + + l := lsn.l.With( + "subID", subID, + "eligibleSubReqs", len(reqs), + "startBalance", startBalance.String(), + "startBalanceNoReserved", startBalanceNoReserved.String(), + "subIsActive", subIsActive, + "nativePayment", nativePayment, + ) + + defer func() { + l.Infow("Finished processing for sub", + "endBalance", startBalanceNoReserved.String(), + "totalProcessed", len(processed), + "totalUnique", uniqueReqs(reqs), + "time", time.Since(start).String()) + }() + + l.Infow("Processing requests for subscription") + + ready, expired := lsn.getReadyAndExpired(l, reqs) + for _, reqID := range expired { + processed[reqID] = struct{}{} + } + + // Process requests in chunks + for chunkStart := 0; chunkStart < len(ready); chunkStart += int(lsn.job.VRFSpec.ChunkSize) { + chunkEnd := chunkStart + int(lsn.job.VRFSpec.ChunkSize) + if chunkEnd > len(ready) { + chunkEnd = len(ready) + } + chunk := ready[chunkStart:chunkEnd] + + var unfulfilled []pendingRequest + alreadyFulfilled, err := lsn.checkReqsFulfilled(ctx, l, chunk) + if errors.Is(err, context.Canceled) { + l.Infow("Context canceled, stopping request processing", "err", err) + return processed + } else if err != nil { + l.Errorw("Error checking for already fulfilled requests, proceeding anyway", "err", err) + } + for i, a := range alreadyFulfilled { + if a { + processed[chunk[i].req.RequestID().String()] = struct{}{} + } else { + unfulfilled = append(unfulfilled, chunk[i]) + } + } + + // All fromAddresses passed to the VRFv2 job have the same KeySpecific-MaxPrice value. + fromAddresses := lsn.fromAddresses() + maxGasPriceWei := lsn.feeCfg.PriceMaxKey(fromAddresses[0]) + observeRequestSimDuration(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version(), unfulfilled) + pipelines := lsn.runPipelines(ctx, l, maxGasPriceWei, unfulfilled) + for _, p := range pipelines { + ll := l.With("reqID", p.req.req.RequestID().String(), + "txHash", p.req.req.Raw().TxHash, + "maxGasPrice", maxGasPriceWei.String(), + "fundsNeeded", p.fundsNeeded.String(), + "maxFee", p.maxFee.String(), + "gasLimit", p.gasLimit, + "attempts", p.req.attempts, + "remainingBalance", startBalanceNoReserved.String(), + "consumerAddress", p.req.req.Sender(), + "blockNumber", p.req.req.Raw().BlockNumber, + "blockHash", p.req.req.Raw().BlockHash, + ) + fromAddress, err := lsn.gethks.GetRoundRobinAddress(lsn.chainID, fromAddresses...) + if err != nil { + l.Errorw("Couldn't get next from address", "err", err) + continue + } + ll = ll.With("fromAddress", fromAddress) + + if p.err != nil { + if errors.Is(p.err, errBlockhashNotInStore{}) { + // Running the blockhash store feeder in backwards mode will be required to + // resolve this. + ll.Criticalw("Pipeline error", "err", p.err) + } else if errors.Is(p.err, errProofVerificationFailed{}) { + // This occurs when the proof reverts in the simulation + // This is almost always (if not always) due to a proof generated with an out-of-date + // blockhash + // we can simply mark as processed and move on, since we will eventually + // process the request with the right blockhash + ll.Infow("proof reverted in simulation, likely stale blockhash") + processed[p.req.req.RequestID().String()] = struct{}{} + } else { + ll.Errorw("Pipeline error", "err", p.err) + + if !subIsActive { + lsn.l.Warnw("Force-fulfilling a request with insufficient funds on a cancelled sub") + etx, err2 := lsn.enqueueForceFulfillment(ctx, p, fromAddress) + if err2 != nil { + ll.Errorw("Error enqueuing force-fulfillment, re-queueing request", "err", err2) + continue + } + ll.Infow("Enqueued force-fulfillment", "ethTxID", etx.ID) + processed[p.req.req.RequestID().String()] = struct{}{} + + // Need to put a continue here, otherwise the next if statement will be hit + // and we'd break out of the loop prematurely. + // If a sub is canceled, we want to force-fulfill ALL of it's pending requests + // before saying we're done with it. + continue + } + + if startBalanceNoReserved.Cmp(p.fundsNeeded) < 0 { + ll.Infow("Insufficient balance to fulfill a request based on estimate, returning", "err", p.err) + return processed + } + + // Ensure consumer is valid, otherwise drop the request. + if !lsn.isConsumerValidAfterFinalityDepthElapsed(ctx, p.req) { + lsn.l.Infow( + "Dropping request that was made by an invalid consumer.", + "consumerAddress", p.req.req.Sender(), + "reqID", p.req.req.RequestID(), + "blockNumber", p.req.req.Raw().BlockNumber, + "blockHash", p.req.req.Raw().BlockHash, + ) + processed[p.req.req.RequestID().String()] = struct{}{} + continue + } + } + continue + } + + if startBalanceNoReserved.Cmp(p.maxFee) < 0 { + // Insufficient funds, have to wait for a user top up. Leave it unprocessed for now + ll.Infow("Insufficient balance to fulfill a request, returning") + return processed + } + + ll.Infow("Enqueuing fulfillment") + var transaction txmgr.Tx + err = lsn.q.Transaction(func(tx pg.Queryer) error { + if err = lsn.pipelineRunner.InsertFinishedRun(p.run, true, pg.WithQueryer(tx)); err != nil { + return err + } + + var maxLink, maxEth *string + tmp := p.maxFee.String() + if p.reqCommitment.NativePayment() { + maxEth = &tmp + } else { + maxLink = &tmp + } + var ( + txMetaSubID *uint64 + txMetaGlobalSubID *string + ) + if lsn.coordinator.Version() == vrfcommon.V2Plus { + txMetaGlobalSubID = ptr(p.req.req.SubID().String()) + } else if lsn.coordinator.Version() == vrfcommon.V2 { + txMetaSubID = ptr(p.req.req.SubID().Uint64()) + } + requestID := common.BytesToHash(p.req.req.RequestID().Bytes()) + coordinatorAddress := lsn.coordinator.Address() + requestTxHash := p.req.req.Raw().TxHash + transaction, err = lsn.chain.TxManager().CreateTransaction(ctx, txmgr.TxRequest{ + FromAddress: fromAddress, + ToAddress: lsn.coordinator.Address(), + EncodedPayload: hexutil.MustDecode(p.payload), + FeeLimit: p.gasLimit, + Meta: &txmgr.TxMeta{ + RequestID: &requestID, + MaxLink: maxLink, + MaxEth: maxEth, + SubID: txMetaSubID, + GlobalSubID: txMetaGlobalSubID, + RequestTxHash: &requestTxHash, + }, + Strategy: txmgrcommon.NewSendEveryStrategy(), + Checker: txmgr.TransmitCheckerSpec{ + CheckerType: lsn.transmitCheckerType(), + VRFCoordinatorAddress: &coordinatorAddress, + VRFRequestBlockNumber: new(big.Int).SetUint64(p.req.req.Raw().BlockNumber), + }, + }) + return err + }) + if err != nil { + ll.Errorw("Error enqueuing fulfillment, requeuing request", "err", err) + continue + } + ll.Infow("Enqueued fulfillment", "ethTxID", transaction.GetID()) + + // If we successfully enqueued for the txm, subtract that balance + // And loop to attempt to enqueue another fulfillment + startBalanceNoReserved.Sub(startBalanceNoReserved, p.maxFee) + processed[p.req.req.RequestID().String()] = struct{}{} + vrfcommon.IncProcessedReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, lsn.coordinator.Version()) + } + } + + return +} + +func (lsn *listenerV2) transmitCheckerType() txmgrtypes.TransmitCheckerType { + if lsn.coordinator.Version() == vrfcommon.V2 { + return txmgr.TransmitCheckerTypeVRFV2 + } + return txmgr.TransmitCheckerTypeVRFV2Plus +} + +func (lsn *listenerV2) processRequestsPerSub( + ctx context.Context, + subID *big.Int, + startLinkBalance *big.Int, + startEthBalance *big.Int, + reqs []pendingRequest, + subIsActive bool, +) map[string]struct{} { + if lsn.job.VRFSpec.BatchFulfillmentEnabled && lsn.batchCoordinator != nil { + return lsn.processRequestsPerSubBatch(ctx, subID, startLinkBalance, startEthBalance, reqs, subIsActive) + } + + var processed = make(map[string]struct{}) + chainId := lsn.chain.Client().ConfiguredChainID() + startBalanceNoReserveLink, err := lsn.MaybeSubtractReservedLink( + ctx, startLinkBalance, chainId, subID, lsn.coordinator.Version()) + if err != nil { + lsn.l.Errorw("Couldn't get reserved LINK for subscription", "sub", reqs[0].req.SubID(), "err", err) + return processed + } + startBalanceNoReserveEth, err := lsn.MaybeSubtractReservedEth( + ctx, startEthBalance, lsn.chainID, subID, lsn.coordinator.Version()) + if err != nil { + lsn.l.Errorw("Couldn't get reserved ETH for subscription", "sub", reqs[0].req.SubID(), "err", err) + return processed + } + + // Split the requests into native and LINK requests. + var ( + nativeRequests []pendingRequest + linkRequests []pendingRequest + ) + for _, req := range reqs { + if req.req.NativePayment() { + if !lsn.inflightCache.Contains(req.req.Raw()) { + nativeRequests = append(nativeRequests, req) + } else { + lsn.l.Debugw("Skipping native request because it is already inflight", + "reqID", req.req.RequestID()) + } + } else { + if !lsn.inflightCache.Contains(req.req.Raw()) { + linkRequests = append(linkRequests, req) + } else { + lsn.l.Debugw("Skipping link request because it is already inflight", + "reqID", req.req.RequestID()) + } + } + } + // process the native and link requests in parallel + var ( + wg sync.WaitGroup + nativeProcessed, linkProcessed map[string]struct{} + ) + wg.Add(2) + go func() { + defer wg.Done() + nativeProcessed = lsn.processRequestsPerSubHelper( + ctx, + subID, + startEthBalance, + startBalanceNoReserveEth, + nativeRequests, + subIsActive, + true) + }() + go func() { + defer wg.Done() + linkProcessed = lsn.processRequestsPerSubHelper( + ctx, + subID, + startLinkBalance, + startBalanceNoReserveLink, + linkRequests, + subIsActive, + false) + }() + wg.Wait() + // combine the native and link processed requests into the processed map + for k, v := range nativeProcessed { + processed[k] = v + } + for k, v := range linkProcessed { + processed[k] = v + } + + return processed +} + +func (lsn *listenerV2) requestCommitmentPayload(requestID *big.Int) (payload []byte, err error) { + if lsn.coordinator.Version() == vrfcommon.V2Plus { + return coordinatorV2PlusABI.Pack("s_requestCommitments", requestID) + } else if lsn.coordinator.Version() == vrfcommon.V2 { + return coordinatorV2ABI.Pack("getCommitment", requestID) + } + return nil, errors.Errorf("unsupported coordinator version: %s", lsn.coordinator.Version()) +} + +// checkReqsFulfilled returns a bool slice the same size of the given reqs slice +// where each slice element indicates whether that request was already fulfilled +// or not. +func (lsn *listenerV2) checkReqsFulfilled(ctx context.Context, l logger.Logger, reqs []pendingRequest) ([]bool, error) { + var ( + start = time.Now() + calls = make([]rpc.BatchElem, len(reqs)) + fulfilled = make([]bool, len(reqs)) + ) + + for i, req := range reqs { + payload, err := lsn.requestCommitmentPayload(req.req.RequestID()) + if err != nil { + // This shouldn't happen + return fulfilled, fmt.Errorf("creating getCommitment payload: %w", err) + } + + reqBlockNumber := new(big.Int).SetUint64(req.req.Raw().BlockNumber) + + // Subtract 5 since the newest block likely isn't indexed yet and will cause "header not + // found" errors. + currBlock := new(big.Int).SetUint64(lsn.getLatestHead() - 5) + m := bigmath.Max(reqBlockNumber, currBlock) + + var result string + calls[i] = rpc.BatchElem{ + Method: "eth_call", + Args: []interface{}{ + map[string]interface{}{ + "to": lsn.coordinator.Address(), + "data": hexutil.Bytes(payload), + }, + // The block at which we want to make the call + hexutil.EncodeBig(m), + }, + Result: &result, + } + } + + err := lsn.chain.Client().BatchCallContext(ctx, calls) + if err != nil { + return fulfilled, fmt.Errorf("making batch call: %w", err) + } + + var errs error + for i, call := range calls { + if call.Error != nil { + errs = multierr.Append(errs, fmt.Errorf("checking request %s with hash %s: %w", + reqs[i].req.RequestID().String(), reqs[i].req.Raw().TxHash.String(), call.Error)) + continue + } + + rString, ok := call.Result.(*string) + if !ok { + errs = multierr.Append(errs, + fmt.Errorf("unexpected result %+v on request %s with hash %s", + call.Result, reqs[i].req.RequestID().String(), reqs[i].req.Raw().TxHash.String())) + continue + } + result, err := hexutil.Decode(*rString) + if err != nil { + errs = multierr.Append(errs, + fmt.Errorf("decoding batch call result %+v %s request %s with hash %s: %w", + call.Result, *rString, reqs[i].req.RequestID().String(), reqs[i].req.Raw().TxHash.String(), err)) + continue + } + + if utils.IsEmpty(result) { + l.Infow("Request already fulfilled", + "reqID", reqs[i].req.RequestID().String(), + "attempts", reqs[i].attempts, + "txHash", reqs[i].req.Raw().TxHash) + fulfilled[i] = true + } + } + + l.Debugw("Done checking fulfillment status", + "numChecked", len(reqs), "time", time.Since(start).String()) + return fulfilled, errs +} + +func (lsn *listenerV2) runPipelines( + ctx context.Context, + l logger.Logger, + maxGasPriceWei *assets.Wei, + reqs []pendingRequest, +) []vrfPipelineResult { + var ( + start = time.Now() + results = make([]vrfPipelineResult, len(reqs)) + wg = sync.WaitGroup{} + ) + + for i, req := range reqs { + wg.Add(1) + go func(i int, req pendingRequest) { + defer wg.Done() + results[i] = lsn.simulateFulfillment(ctx, maxGasPriceWei, req, l) + }(i, req) + } + wg.Wait() + + l.Debugw("Finished running pipelines", + "count", len(reqs), "time", time.Since(start).String()) + return results +} + +func (lsn *listenerV2) estimateFee( + ctx context.Context, + req RandomWordsRequested, + maxGasPriceWei *assets.Wei, +) (*big.Int, error) { + // NativePayment() returns true if and only if the version is V2+ and the + // request was made in ETH. + if req.NativePayment() { + return EstimateFeeWei(req.CallbackGasLimit(), maxGasPriceWei.ToInt()) + } + + // In the event we are using LINK we need to estimate the fee in juels + // Don't use up too much time to get this info, it's not critical for operating vrf. + callCtx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + roundData, err := lsn.aggregator.LatestRoundData(&bind.CallOpts{Context: callCtx}) + if err != nil { + return nil, fmt.Errorf("get aggregator latestAnswer: %w", err) + } + + return EstimateFeeJuels( + req.CallbackGasLimit(), + maxGasPriceWei.ToInt(), + roundData.Answer, + ) +} + +// 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) simulateFulfillment( + ctx context.Context, + maxGasPriceWei *assets.Wei, + req pendingRequest, + lg logger.Logger, +) vrfPipelineResult { + var ( + res = vrfPipelineResult{req: req} + err error + ) + // estimate how much funds are needed so that we can log it if the simulation fails. + res.fundsNeeded, err = lsn.estimateFee(ctx, req.req, maxGasPriceWei) + if err != nil { + // not critical, just log and continue + lg.Warnw("unable to estimate funds needed for request, continuing anyway", + "reqID", req.req.RequestID(), + "err", err) + res.fundsNeeded = big.NewInt(0) + } + + vars := pipeline.NewVarsFrom(map[string]interface{}{ + "jobSpec": map[string]interface{}{ + "databaseID": lsn.job.ID, + "externalJobID": lsn.job.ExternalJobID, + "name": lsn.job.Name.ValueOrZero(), + "publicKey": lsn.job.VRFSpec.PublicKey[:], + "maxGasPrice": maxGasPriceWei.ToInt().String(), + "evmChainID": lsn.job.VRFSpec.EVMChainID.String(), + }, + "jobRun": map[string]interface{}{ + "logBlockHash": req.req.Raw().BlockHash.Bytes(), + "logBlockNumber": req.req.Raw().BlockNumber, + "logTxHash": req.req.Raw().TxHash, + "logTopics": req.req.Raw().Topics, + "logData": req.req.Raw().Data, + }, + }) + var trrs pipeline.TaskRunResults + res.run, trrs, err = lsn.pipelineRunner.ExecuteRun(ctx, *lsn.job.PipelineSpec, vars, lg) + if err != nil { + res.err = fmt.Errorf("executing run: %w", err) + return res + } + // The call task will fail if there are insufficient funds + if res.run.AllErrors.HasError() { + res.err = errors.WithStack(res.run.AllErrors.ToError()) + + if strings.Contains(res.err.Error(), "blockhash not found in store") { + res.err = multierr.Combine(res.err, errBlockhashNotInStore{}) + } else if isProofVerificationError(res.err.Error()) { + res.err = multierr.Combine(res.err, errProofVerificationFailed{}) + } else if strings.Contains(res.err.Error(), "execution reverted") { + // Even if the simulation fails, we want to get the + // txData for the fulfillRandomWords call, in case + // we need to force fulfill. + for _, trr := range trrs { + if trr.Task.Type() == pipeline.TaskTypeVRFV2 { + if trr.Result.Error != nil { + // error in VRF proof generation + // this means that we won't be able to force-fulfill in the event of a + // canceled sub and active requests. + // since this would be an extraordinary situation, + // we can log loudly here. + lg.Criticalw("failed to generate VRF proof", "err", trr.Result.Error) + break + } + + // extract the abi-encoded tx data to fulfillRandomWords from the VRF task. + // that's all we need in the event of a force-fulfillment. + m := trr.Result.Value.(map[string]any) + res.payload = m["output"].(string) + res.proof = FromV2Proof(m["proof"].(vrf_coordinator_v2.VRFProof)) + res.reqCommitment = NewRequestCommitment(m["requestCommitment"]) + } + } + res.err = multierr.Combine(res.err, errPossiblyInsufficientFunds{}) + } + + return res + } + finalResult := trrs.FinalResult(lg) + if len(finalResult.Values) != 1 { + res.err = errors.Errorf("unexpected number of outputs, expected 1, was %d", len(finalResult.Values)) + return res + } + + // Run succeeded, we expect a byte array representing the billing amount + b, ok := finalResult.Values[0].([]uint8) + if !ok { + res.err = errors.New("expected []uint8 final result") + return res + } + res.maxFee = utils.HexToBig(hexutil.Encode(b)[2:]) + for _, trr := range trrs { + if trr.Task.Type() == pipeline.TaskTypeVRFV2 { + m := trr.Result.Value.(map[string]interface{}) + res.payload = m["output"].(string) + res.proof = FromV2Proof(m["proof"].(vrf_coordinator_v2.VRFProof)) + res.reqCommitment = NewRequestCommitment(m["requestCommitment"]) + } + + if trr.Task.Type() == pipeline.TaskTypeVRFV2Plus { + m := trr.Result.Value.(map[string]interface{}) + res.payload = m["output"].(string) + res.proof = FromV2PlusProof(m["proof"].(vrf_coordinator_v2plus_interface.IVRFCoordinatorV2PlusInternalProof)) + res.reqCommitment = NewRequestCommitment(m["requestCommitment"]) + } + + if trr.Task.Type() == pipeline.TaskTypeEstimateGasLimit { + res.gasLimit = trr.Result.Value.(uint32) + } + } + return res +} + +func (lsn *listenerV2) fromAddresses() []common.Address { + var addresses []common.Address + for _, a := range lsn.job.VRFSpec.FromAddresses { + addresses = append(addresses, a.Address()) + } + return addresses +} diff --git a/core/services/vrf/v2/listener_v2_test.go b/core/services/vrf/v2/listener_v2_test.go index 70d5b8154e0..6192db95dfe 100644 --- a/core/services/vrf/v2/listener_v2_test.go +++ b/core/services/vrf/v2/listener_v2_test.go @@ -1,8 +1,8 @@ package v2 import ( + "encoding/json" "math/big" - "sync" "testing" "time" @@ -11,41 +11,42 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/theodesp/go-heaps/pairing" - - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2plus_interface" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" + evmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm/mocks" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" + clnull "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" - "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/testdata/testspecs" "github.com/smartcontractkit/chainlink/v2/core/utils" ) -const ( - addEthTxQuery = `INSERT INTO evm.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 - ) - RETURNING "txes".*` - - addConfirmedEthTxQuery = `INSERT INTO evm.txes (nonce, broadcast_at, initial_broadcast_at, error, 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, NOW(), NOW(), NULL, $2, $3, $4, $5, $6, 'confirmed', NOW(), $7, $8, $9, $10, $11 - ) - RETURNING "txes".*` -) +func makeTestTxm(t *testing.T, txStore txmgr.TestEvmTxStore, keyStore keystore.Master) txmgrcommon.TxManager[*big.Int, *evmtypes.Head, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] { + _, _, evmConfig := txmgr.MakeTestConfigs(t) + ec := evmtest.NewEthClientMockWithDefaultChain(t) + txmConfig := txmgr.NewEvmTxmConfig(evmConfig) + txm := txmgr.NewEvmTxm(ec.ConfiguredChainID(), txmConfig, evmConfig.Transactions(), keyStore.Eth(), logger.TestLogger(t), nil, nil, + nil, txStore, nil, nil, nil, nil, nil) + + return txm +} + +func MakeTestListenerV2(chain legacyevm.Chain) *listenerV2 { + return &listenerV2{chainID: chain.Client().ConfiguredChainID(), chain: chain} +} func txMetaSubIDs(t *testing.T, vrfVersion vrfcommon.Version, subID *big.Int) (*uint64, *string) { var ( @@ -62,89 +63,118 @@ func txMetaSubIDs(t *testing.T, vrfVersion vrfcommon.Version, subID *big.Int) (* return txMetaSubID, txMetaGlobalSubID } -func addEthTx(t *testing.T, db *sqlx.DB, from common.Address, state txmgrtypes.TxState, maxLink string, subID *big.Int, reqTxHash common.Hash, vrfVersion vrfcommon.Version) { +func addEthTx(t *testing.T, txStore txmgr.TestEvmTxStore, from common.Address, state txmgrtypes.TxState, maxLink string, subID *big.Int, reqTxHash common.Hash, vrfVersion vrfcommon.Version) { txMetaSubID, txMetaGlobalSubID := txMetaSubIDs(t, vrfVersion, subID) - _, err := db.Exec(addEthTxQuery, - from, // from - from, // to - []byte(`blah`), // payload - 0, // value - 0, // limit - state, - txmgr.TxMeta{ - MaxLink: &maxLink, - SubID: txMetaSubID, - GlobalSubID: txMetaGlobalSubID, - RequestTxHash: &reqTxHash, - }, - uuid.NullUUID{}, - 1337, - 0, // confs - nil) + b, err := json.Marshal(txmgr.TxMeta{ + MaxLink: &maxLink, + SubID: txMetaSubID, + GlobalSubID: txMetaGlobalSubID, + RequestTxHash: &reqTxHash, + }) + require.NoError(t, err) + meta := sqlutil.JSON(b) + tx := &txmgr.Tx{ + FromAddress: from, + ToAddress: from, + EncodedPayload: []byte(`blah`), + Value: *big.NewInt(0), + FeeLimit: 0, + State: state, + Meta: &meta, + Subject: uuid.NullUUID{}, + ChainID: testutils.SimulatedChainID, + MinConfirmations: clnull.Uint32{Uint32: 0}, + PipelineTaskRunID: uuid.NullUUID{}, + } + err = txStore.InsertTx(tx) require.NoError(t, err) } -func addConfirmedEthTx(t *testing.T, db *sqlx.DB, from common.Address, maxLink string, subID *big.Int, nonce uint64, vrfVersion vrfcommon.Version) { +func addConfirmedEthTx(t *testing.T, txStore txmgr.TestEvmTxStore, from common.Address, maxLink string, subID *big.Int, nonce evmtypes.Nonce, vrfVersion vrfcommon.Version) { txMetaSubID, txMetaGlobalSubID := txMetaSubIDs(t, vrfVersion, subID) - _, err := db.Exec(addConfirmedEthTxQuery, - nonce, // nonce - from, // from - from, // to - []byte(`blah`), // payload - 0, // value - 0, // limit - txmgr.TxMeta{ - MaxLink: &maxLink, - SubID: txMetaSubID, - GlobalSubID: txMetaGlobalSubID, - }, - uuid.NullUUID{}, - 1337, - 0, // confs - nil) + b, err := json.Marshal(txmgr.TxMeta{ + MaxLink: &maxLink, + SubID: txMetaSubID, + GlobalSubID: txMetaGlobalSubID, + }) + require.NoError(t, err) + meta := sqlutil.JSON(b) + now := time.Now() + + tx := &txmgr.Tx{ + Sequence: &nonce, + FromAddress: from, + ToAddress: from, + EncodedPayload: []byte(`blah`), + Value: *big.NewInt(0), + FeeLimit: 0, + State: txmgrcommon.TxConfirmed, + Meta: &meta, + Subject: uuid.NullUUID{}, + ChainID: testutils.SimulatedChainID, + MinConfirmations: clnull.Uint32{Uint32: 0}, + PipelineTaskRunID: uuid.NullUUID{}, + BroadcastAt: &now, + InitialBroadcastAt: &now, + } + err = txStore.InsertTx(tx) require.NoError(t, err) } -func addEthTxNativePayment(t *testing.T, db *sqlx.DB, from common.Address, state txmgrtypes.TxState, maxNative string, subID *big.Int, reqTxHash common.Hash, vrfVersion vrfcommon.Version) { +func addEthTxNativePayment(t *testing.T, txStore txmgr.TestEvmTxStore, from common.Address, state txmgrtypes.TxState, maxNative string, subID *big.Int, reqTxHash common.Hash, vrfVersion vrfcommon.Version) { txMetaSubID, txMetaGlobalSubID := txMetaSubIDs(t, vrfVersion, subID) - _, err := db.Exec(addEthTxQuery, - from, // from - from, // to - []byte(`blah`), // payload - 0, // value - 0, // limit - state, - txmgr.TxMeta{ - MaxEth: &maxNative, - SubID: txMetaSubID, - GlobalSubID: txMetaGlobalSubID, - RequestTxHash: &reqTxHash, - }, - uuid.NullUUID{}, - 1337, - 0, // confs - nil) + b, err := json.Marshal(txmgr.TxMeta{ + MaxEth: &maxNative, + SubID: txMetaSubID, + GlobalSubID: txMetaGlobalSubID, + RequestTxHash: &reqTxHash, + }) + require.NoError(t, err) + meta := sqlutil.JSON(b) + tx := &txmgr.Tx{ + FromAddress: from, + ToAddress: from, + EncodedPayload: []byte(`blah`), + Value: *big.NewInt(0), + FeeLimit: 0, + State: state, + Meta: &meta, + Subject: uuid.NullUUID{}, + ChainID: testutils.SimulatedChainID, + MinConfirmations: clnull.Uint32{Uint32: 0}, + PipelineTaskRunID: uuid.NullUUID{}, + } + err = txStore.InsertTx(tx) require.NoError(t, err) } -func addConfirmedEthTxNativePayment(t *testing.T, db *sqlx.DB, from common.Address, maxNative string, subID *big.Int, nonce uint64, vrfVersion vrfcommon.Version) { +func addConfirmedEthTxNativePayment(t *testing.T, txStore txmgr.TestEvmTxStore, from common.Address, maxNative string, subID *big.Int, nonce evmtypes.Nonce, vrfVersion vrfcommon.Version) { txMetaSubID, txMetaGlobalSubID := txMetaSubIDs(t, vrfVersion, subID) - _, err := db.Exec(addConfirmedEthTxQuery, - nonce, // nonce - from, // from - from, // to - []byte(`blah`), // payload - 0, // value - 0, // limit - txmgr.TxMeta{ - MaxEth: &maxNative, - SubID: txMetaSubID, - GlobalSubID: txMetaGlobalSubID, - }, - uuid.NullUUID{}, - 1337, - 0, // confs - nil) + b, err := json.Marshal(txmgr.TxMeta{ + MaxEth: &maxNative, + SubID: txMetaSubID, + GlobalSubID: txMetaGlobalSubID, + }) + require.NoError(t, err) + meta := sqlutil.JSON(b) + now := time.Now() + tx := &txmgr.Tx{ + Sequence: &nonce, + FromAddress: from, + ToAddress: from, + EncodedPayload: []byte(`blah`), + Value: *big.NewInt(0), + FeeLimit: 0, + State: txmgrcommon.TxConfirmed, + Meta: &meta, + Subject: uuid.NullUUID{}, + ChainID: testutils.SimulatedChainID, + MinConfirmations: clnull.Uint32{Uint32: 0}, + PipelineTaskRunID: uuid.NullUUID{}, + BroadcastAt: &now, + InitialBroadcastAt: &now, + } + err = txStore.InsertTx(tx) require.NoError(t, err) } @@ -152,57 +182,72 @@ func testMaybeSubtractReservedLink(t *testing.T, vrfVersion vrfcommon.Version) { db := pgtest.NewSqlxDB(t) lggr := logger.TestLogger(t) cfg := pgtest.NewQConfig(false) - q := pg.NewQ(db, lggr, cfg) ks := keystore.NewInMemory(db, utils.FastScryptParams, lggr, cfg) require.NoError(t, ks.Unlock("blah")) - chainID := uint64(1337) - k, err := ks.Eth().Create(big.NewInt(int64(chainID))) + chainID := testutils.SimulatedChainID + k, err := ks.Eth().Create(chainID) require.NoError(t, err) subID := new(big.Int).SetUint64(1) reqTxHash := common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8") + j, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{ + RequestedConfsDelay: 10, + }).Toml()) + require.NoError(t, err) + txstore := txmgr.NewTxStore(db, lggr, cfg) + txm := makeTestTxm(t, txstore, ks) + chain := evmmocks.NewChain(t) + chain.On("TxManager").Return(txm) + listener := &listenerV2{ + respCount: map[string]uint64{}, + job: j, + chain: chain, + } + + ctx := testutils.Context(t) + // Insert an unstarted eth tx with link metadata - addEthTx(t, db, k.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) - start, err := MaybeSubtractReservedLink(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addEthTx(t, txstore, k.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) + start, err := listener.MaybeSubtractReservedLink(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) assert.Equal(t, "90000", start.String()) // A confirmed tx should not affect the starting balance - addConfirmedEthTx(t, db, k.Address, "10000", subID, 1, vrfVersion) - start, err = MaybeSubtractReservedLink(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addConfirmedEthTx(t, txstore, k.Address, "10000", subID, 1, vrfVersion) + start, err = listener.MaybeSubtractReservedLink(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) assert.Equal(t, "90000", start.String()) // An unconfirmed tx _should_ affect the starting balance. - addEthTx(t, db, k.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) - start, err = MaybeSubtractReservedLink(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addEthTx(t, txstore, k.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) + start, err = listener.MaybeSubtractReservedLink(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) assert.Equal(t, "80000", start.String()) // One subscriber's reserved link should not affect other subscribers prospective balance. otherSubID := new(big.Int).SetUint64(2) require.NoError(t, err) - addEthTx(t, db, k.Address, txmgrcommon.TxUnstarted, "10000", otherSubID, reqTxHash, vrfVersion) - start, err = MaybeSubtractReservedLink(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addEthTx(t, txstore, k.Address, txmgrcommon.TxUnstarted, "10000", otherSubID, reqTxHash, vrfVersion) + start, err = listener.MaybeSubtractReservedLink(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) require.Equal(t, "80000", start.String()) // One key's data should not affect other keys' data in the case of different subscribers. - k2, err := ks.Eth().Create(big.NewInt(1337)) + k2, err := ks.Eth().Create(testutils.SimulatedChainID) require.NoError(t, err) anotherSubID := new(big.Int).SetUint64(3) - addEthTx(t, db, k2.Address, txmgrcommon.TxUnstarted, "10000", anotherSubID, reqTxHash, vrfVersion) - start, err = MaybeSubtractReservedLink(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addEthTx(t, txstore, k2.Address, txmgrcommon.TxUnstarted, "10000", anotherSubID, reqTxHash, vrfVersion) + start, err = listener.MaybeSubtractReservedLink(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) 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, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) - start, err = MaybeSubtractReservedLink(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addEthTx(t, txstore, k2.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) + start, err = listener.MaybeSubtractReservedLink(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) require.Equal(t, "70000", start.String()) } @@ -219,57 +264,73 @@ func testMaybeSubtractReservedNative(t *testing.T, vrfVersion vrfcommon.Version) db := pgtest.NewSqlxDB(t) lggr := logger.TestLogger(t) cfg := pgtest.NewQConfig(false) - q := pg.NewQ(db, lggr, cfg) ks := keystore.NewInMemory(db, utils.FastScryptParams, lggr, cfg) require.NoError(t, ks.Unlock("blah")) - chainID := uint64(1337) - k, err := ks.Eth().Create(big.NewInt(int64(chainID))) + chainID := testutils.SimulatedChainID + k, err := ks.Eth().Create(chainID) require.NoError(t, err) subID := new(big.Int).SetUint64(1) reqTxHash := common.HexToHash("0xc524fafafcaec40652b1f84fca09c231185437d008d195fccf2f51e64b7062f8") + j, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{ + RequestedConfsDelay: 10, + }).Toml()) + require.NoError(t, err) + txstore := txmgr.NewTxStore(db, logger.TestLogger(t), cfg) + txm := makeTestTxm(t, txstore, ks) + require.NoError(t, err) + chain := evmmocks.NewChain(t) + chain.On("TxManager").Return(txm) + listener := &listenerV2{ + respCount: map[string]uint64{}, + job: j, + chain: chain, + } + + ctx := testutils.Context(t) + // Insert an unstarted eth tx with native metadata - addEthTxNativePayment(t, db, k.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) - start, err := MaybeSubtractReservedEth(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addEthTxNativePayment(t, txstore, k.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) + start, err := listener.MaybeSubtractReservedEth(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) assert.Equal(t, "90000", start.String()) // A confirmed tx should not affect the starting balance - addConfirmedEthTxNativePayment(t, db, k.Address, "10000", subID, 1, vrfVersion) - start, err = MaybeSubtractReservedEth(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addConfirmedEthTxNativePayment(t, txstore, k.Address, "10000", subID, 1, vrfVersion) + start, err = listener.MaybeSubtractReservedEth(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) assert.Equal(t, "90000", start.String()) // An unconfirmed tx _should_ affect the starting balance. - addEthTxNativePayment(t, db, k.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) - start, err = MaybeSubtractReservedEth(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addEthTxNativePayment(t, txstore, k.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) + start, err = listener.MaybeSubtractReservedEth(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) assert.Equal(t, "80000", start.String()) // One subscriber's reserved native should not affect other subscribers prospective balance. otherSubID := new(big.Int).SetUint64(2) require.NoError(t, err) - addEthTxNativePayment(t, db, k.Address, txmgrcommon.TxUnstarted, "10000", otherSubID, reqTxHash, vrfVersion) - start, err = MaybeSubtractReservedEth(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addEthTxNativePayment(t, txstore, k.Address, txmgrcommon.TxUnstarted, "10000", otherSubID, reqTxHash, vrfVersion) + start, err = listener.MaybeSubtractReservedEth(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) require.Equal(t, "80000", start.String()) // One key's data should not affect other keys' data in the case of different subscribers. - k2, err := ks.Eth().Create(big.NewInt(1337)) + k2, err := ks.Eth().Create(testutils.SimulatedChainID) require.NoError(t, err) anotherSubID := new(big.Int).SetUint64(3) - addEthTxNativePayment(t, db, k2.Address, txmgrcommon.TxUnstarted, "10000", anotherSubID, reqTxHash, vrfVersion) - start, err = MaybeSubtractReservedEth(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addEthTxNativePayment(t, txstore, k2.Address, txmgrcommon.TxUnstarted, "10000", anotherSubID, reqTxHash, vrfVersion) + start, err = listener.MaybeSubtractReservedEth(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) require.Equal(t, "80000", start.String()) // A subscriber's balance is deducted with the native reserved across multiple keys, // i.e, gas lanes. - addEthTxNativePayment(t, db, k2.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) - start, err = MaybeSubtractReservedEth(q, big.NewInt(100_000), chainID, subID, vrfVersion) + addEthTxNativePayment(t, txstore, k2.Address, txmgrcommon.TxUnstarted, "10000", subID, reqTxHash, vrfVersion) + start, err = listener.MaybeSubtractReservedEth(ctx, big.NewInt(100_000), chainID, subID, vrfVersion) require.NoError(t, err) require.Equal(t, "70000", start.String()) } @@ -282,13 +343,26 @@ func TestMaybeSubtractReservedNativeV2(t *testing.T) { db := pgtest.NewSqlxDB(t) lggr := logger.TestLogger(t) cfg := pgtest.NewQConfig(false) - q := pg.NewQ(db, lggr, cfg) ks := keystore.NewInMemory(db, utils.FastScryptParams, lggr, cfg) require.NoError(t, ks.Unlock("blah")) - chainID := uint64(1337) + chainID := testutils.SimulatedChainID subID := new(big.Int).SetUint64(1) + + j, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{ + RequestedConfsDelay: 10, + }).Toml()) + require.NoError(t, err) + txstore := txmgr.NewTxStore(db, logger.TestLogger(t), cfg) + txm := makeTestTxm(t, txstore, ks) + chain := evmmocks.NewChain(t) + chain.On("TxManager").Return(txm).Maybe() + listener := &listenerV2{ + respCount: map[string]uint64{}, + job: j, + chain: chain, + } // returns error because native payment is not supported for V2 - start, err := MaybeSubtractReservedEth(q, big.NewInt(100_000), chainID, subID, vrfcommon.V2) + start, err := listener.MaybeSubtractReservedEth(testutils.Context(t), big.NewInt(100_000), chainID, subID, vrfcommon.V2) require.NoError(t, err) assert.Equal(t, big.NewInt(0), start) } @@ -423,73 +497,3 @@ func TestListener_Backoff(t *testing.T) { }) } } - -func TestListener_handleLog(tt *testing.T) { - lb := mocks.NewBroadcaster(tt) - chainID := int64(2) - minConfs := uint32(3) - blockNumber := uint64(5) - requestID := int64(6) - tt.Run("v2", func(t *testing.T) { - j, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{ - VRFVersion: vrfcommon.V2, - RequestedConfsDelay: 10, - FromAddresses: []string{"0xF2982b7Ef6E3D8BB738f8Ea20502229781f6Ad97"}, - }).Toml()) - require.NoError(t, err) - fulfilledLog := vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled{ - RequestId: big.NewInt(requestID), - Raw: types.Log{BlockNumber: blockNumber}, - } - log := log.NewLogBroadcast(types.Log{}, *big.NewInt(chainID), &fulfilledLog) - lb.On("WasAlreadyConsumed", log).Return(false, nil).Once() - lb.On("MarkConsumed", log).Return(nil).Once() - defer lb.AssertExpectations(t) - listener := &listenerV2{ - respCount: map[string]uint64{}, - job: j, - blockNumberToReqID: pairing.New(), - latestHeadMu: sync.RWMutex{}, - logBroadcaster: lb, - l: logger.TestLogger(t), - } - listener.handleLog(log, minConfs) - require.Equal(t, listener.respCount[fulfilledLog.RequestId.String()], uint64(1)) - req, ok := listener.blockNumberToReqID.FindMin().(fulfilledReqV2) - require.True(t, ok) - require.Equal(t, req.blockNumber, blockNumber) - require.Equal(t, req.reqID, "6") - }) - - tt.Run("v2 plus", func(t *testing.T) { - j, err := vrfcommon.ValidatedVRFSpec(testspecs.GenerateVRFSpec(testspecs.VRFSpecParams{ - VRFVersion: vrfcommon.V2Plus, - RequestedConfsDelay: 10, - FromAddresses: []string{"0xF2982b7Ef6E3D8BB738f8Ea20502229781f6Ad97"}, - }).Toml()) - require.NoError(t, err) - fulfilledLog := vrf_coordinator_v2plus_interface.IVRFCoordinatorV2PlusInternalRandomWordsFulfilled{ - RequestId: big.NewInt(requestID), - Raw: types.Log{BlockNumber: blockNumber}, - } - log := log.NewLogBroadcast(types.Log{}, *big.NewInt(chainID), &fulfilledLog) - lb.On("WasAlreadyConsumed", log).Return(false, nil).Once() - lb.On("MarkConsumed", log).Return(nil).Once() - defer lb.AssertExpectations(t) - listener := &listenerV2{ - respCount: map[string]uint64{}, - job: j, - blockNumberToReqID: pairing.New(), - latestHeadMu: sync.RWMutex{}, - logBroadcaster: lb, - l: logger.TestLogger(t), - } - listener.handleLog(log, minConfs) - require.Equal(t, listener.respCount[fulfilledLog.RequestId.String()], uint64(1)) - req, ok := listener.blockNumberToReqID.FindMin().(fulfilledReqV2) - require.True(t, ok) - require.Equal(t, req.blockNumber, blockNumber) - require.Equal(t, req.reqID, "6") - }) - -} diff --git a/core/services/vrf/v2/listener_v2_types.go b/core/services/vrf/v2/listener_v2_types.go index e0596abcd1a..b3cbbff4713 100644 --- a/core/services/vrf/v2/listener_v2_types.go +++ b/core/services/vrf/v2/listener_v2_types.go @@ -1,14 +1,14 @@ package v2 import ( + "fmt" "math/big" "time" "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" + heaps "github.com/theodesp/go-heaps" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" @@ -16,6 +16,68 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" ) +type errPossiblyInsufficientFunds struct{} + +func (errPossiblyInsufficientFunds) Error() string { + return "Simulation errored, possibly insufficient funds. Request will remain unprocessed until funds are available" +} + +type errBlockhashNotInStore struct{} + +func (errBlockhashNotInStore) Error() string { + return "Blockhash not in store" +} + +type errProofVerificationFailed struct{} + +func (errProofVerificationFailed) Error() string { + return "Proof verification failed" +} + +type fulfilledReqV2 struct { + blockNumber uint64 + reqID string +} + +func (a fulfilledReqV2) Compare(b heaps.Item) int { + a1 := a + a2 := b.(fulfilledReqV2) + switch { + case a1.blockNumber > a2.blockNumber: + return 1 + case a1.blockNumber < a2.blockNumber: + return -1 + default: + return 0 + } +} + +type pendingRequest struct { + confirmedAtBlock uint64 + req RandomWordsRequested + utcTimestamp time.Time + + // used for exponential backoff when retrying + attempts int + lastTry time.Time +} + +type vrfPipelineResult struct { + err error + // maxFee indicates how much juels (link) or wei (ether) would be paid for the VRF request + // if it were to be fulfilled at the maximum gas price (i.e gas lane gas price). + maxFee *big.Int + // fundsNeeded indicates a "minimum balance" in juels or wei that must be held in the + // subscription's account in order to fulfill the request. + fundsNeeded *big.Int + run *pipeline.Run + payload string + gasLimit uint32 + req pendingRequest + proof VRFProof + reqCommitment RequestCommitment +} + // batchFulfillment contains all the information needed in order to // perform a batch fulfillment operation on the batch VRF coordinator. type batchFulfillment struct { @@ -24,7 +86,6 @@ type batchFulfillment struct { totalGasLimit uint32 runs []*pipeline.Run reqIDs []*big.Int - lbs []log.Broadcast maxFees []*big.Int txHashes []common.Hash fromAddress common.Address @@ -46,9 +107,6 @@ func newBatchFulfillment(result vrfPipelineResult, fromAddress common.Address, v reqIDs: []*big.Int{ result.req.req.RequestID(), }, - lbs: []log.Broadcast{ - result.req.lb, - }, maxFees: []*big.Int{ result.maxFee, }, @@ -97,7 +155,6 @@ func (b *batchFulfillments) addRun(result vrfPipelineResult, fromAddress common. currBatch.totalGasLimit += result.gasLimit currBatch.runs = append(currBatch.runs, result.run) currBatch.reqIDs = append(currBatch.reqIDs, result.req.req.RequestID()) - currBatch.lbs = append(currBatch.lbs, result.req.lb) currBatch.maxFees = append(currBatch.maxFees, result.maxFee) currBatch.txHashes = append(currBatch.txHashes, result.req.req.Raw().TxHash) } @@ -167,21 +224,19 @@ func (lsn *listenerV2) processBatch( var ethTX txmgr.Tx err = lsn.q.Transaction(func(tx pg.Queryer) error { if err = lsn.pipelineRunner.InsertFinishedRuns(batch.runs, true, pg.WithQueryer(tx)); err != nil { - return errors.Wrap(err, "inserting finished pipeline runs") - } - - if err = lsn.logBroadcaster.MarkManyConsumed(batch.lbs, pg.WithQueryer(tx)); err != nil { - return errors.Wrap(err, "mark logs consumed") + return fmt.Errorf("inserting finished pipeline runs: %w", err) } maxLink, maxEth := accumulateMaxLinkAndMaxEth(batch) - txHashes := []common.Hash{} + var ( + txHashes []common.Hash + reqIDHashes []common.Hash + ) copy(txHashes, batch.txHashes) - reqIDHashes := []common.Hash{} for _, reqID := range batch.reqIDs { reqIDHashes = append(reqIDHashes, common.BytesToHash(reqID.Bytes())) } - ethTX, err = lsn.txm.CreateTransaction(ctx, txmgr.TxRequest{ + ethTX, err = lsn.chain.TxManager().CreateTransaction(ctx, txmgr.TxRequest{ FromAddress: fromAddress, ToAddress: lsn.batchCoordinator.Address(), EncodedPayload: payload, @@ -196,8 +251,11 @@ func (lsn *listenerV2) processBatch( RequestTxHashes: txHashes, }, }) + if err != nil { + return fmt.Errorf("create batch fulfillment eth transaction: %w", err) + } - return errors.Wrap(err, "create batch fulfillment eth transaction") + return nil }) if err != nil { ll.Errorw("Error enqueuing batch fulfillments, requeuing requests", "err", err) @@ -217,35 +275,21 @@ func (lsn *listenerV2) processBatch( return } -// getUnconsumed returns the requests in the given slice that are not expired -// and not marked consumed in the log broadcaster. -func (lsn *listenerV2) getUnconsumed(l logger.Logger, reqs []pendingRequest) (unconsumed []pendingRequest, processed []string) { +// getReadyAndExpired filters out requests that are expired from the given pendingRequest slice +// and returns requests that are ready for processing. +func (lsn *listenerV2) getReadyAndExpired(l logger.Logger, reqs []pendingRequest) (ready []pendingRequest, expired []string) { for _, req := range reqs { // Check if we can ignore the request due to its age. if time.Now().UTC().Sub(req.utcTimestamp) >= lsn.job.VRFSpec.RequestTimeout { l.Infow("Request too old, dropping it", "reqID", req.req.RequestID().String(), "txHash", req.req.Raw().TxHash) - lsn.markLogAsConsumed(req.lb) - processed = append(processed, req.req.RequestID().String()) + expired = append(expired, req.req.RequestID().String()) vrfcommon.IncDroppedReqs(lsn.job.Name.ValueOrZero(), lsn.job.ExternalJobID, vrfcommon.V2, vrfcommon.ReasonAge) continue } - - // 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 for now, retry on next iteration. - l.Errorw("Could not determine if log was already consumed", - "reqID", req.req.RequestID().String(), - "txHash", req.req.Raw().TxHash, - "error", err) - } else if consumed { - processed = append(processed, req.req.RequestID().String()) - } else { - unconsumed = append(unconsumed, req) - } + // we always check if the requests are already fulfilled prior to trying to fulfill them again + ready = append(ready, req) } return } diff --git a/core/services/vrf/v2/listener_v2_types_test.go b/core/services/vrf/v2/listener_v2_types_test.go index 5391e18589b..c66270210b9 100644 --- a/core/services/vrf/v2/listener_v2_types_test.go +++ b/core/services/vrf/v2/listener_v2_types_test.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log/mocks" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2_5" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" @@ -30,7 +29,6 @@ func Test_BatchFulfillments_AddRun(t *testing.T) { TxHash: common.HexToHash("0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65"), }, }), - lb: mocks.NewBroadcast(t), }, run: pipeline.NewRun(pipeline.Spec{}, pipeline.Vars{}), }, fromAddress) @@ -49,7 +47,6 @@ func Test_BatchFulfillments_AddRun(t *testing.T) { TxHash: common.HexToHash("0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65"), }, }), - lb: mocks.NewBroadcast(t), }, run: pipeline.NewRun(pipeline.Spec{}, pipeline.Vars{}), }, fromAddress) @@ -70,7 +67,6 @@ func Test_BatchFulfillments_AddRun_V2Plus(t *testing.T) { TxHash: common.HexToHash("0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65"), }, }), - lb: mocks.NewBroadcast(t), }, run: pipeline.NewRun(pipeline.Spec{}, pipeline.Vars{}), }, fromAddress) @@ -89,7 +85,6 @@ func Test_BatchFulfillments_AddRun_V2Plus(t *testing.T) { TxHash: common.HexToHash("0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65"), }, }), - lb: mocks.NewBroadcast(t), }, run: pipeline.NewRun(pipeline.Spec{}, pipeline.Vars{}), }, fromAddress) diff --git a/core/services/vrf/vrfcommon/inflight_cache.go b/core/services/vrf/vrfcommon/inflight_cache.go new file mode 100644 index 00000000000..e5bd0e8870c --- /dev/null +++ b/core/services/vrf/vrfcommon/inflight_cache.go @@ -0,0 +1,85 @@ +package vrfcommon + +import ( + "sync" + + "github.com/ethereum/go-ethereum/core/types" +) + +type InflightCache interface { + Add(lg types.Log) + Contains(lg types.Log) bool + Size() int +} + +var _ InflightCache = (*inflightCache)(nil) + +const cachePruneInterval = 1000 + +type inflightCache struct { + // cache stores the logs whose fulfillments are currently in flight or already fulfilled. + cache 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 + + // mu synchronizes access to the delivered map. + mu sync.RWMutex +} + +func NewInflightCache(lookback int) InflightCache { + return &inflightCache{ + cache: make(map[logKey]struct{}), + lookback: lookback, + mu: sync.RWMutex{}, + } +} + +func (c *inflightCache) Size() int { + c.mu.RLock() + defer c.mu.RUnlock() + return len(c.cache) +} + +func (c *inflightCache) Add(lg types.Log) { + c.mu.Lock() + defer c.mu.Unlock() // unlock in the last defer, so that we hold the lock when pruning. + defer c.prune(lg.BlockNumber) + + c.cache[logKey{ + blockHash: lg.BlockHash, + blockNumber: lg.BlockNumber, + logIndex: lg.Index, + }] = struct{}{} +} + +func (c *inflightCache) Contains(lg types.Log) bool { + c.mu.RLock() + defer c.mu.RUnlock() + + _, ok := c.cache[logKey{ + blockHash: lg.BlockHash, + blockNumber: lg.BlockNumber, + logIndex: lg.Index, + }] + return ok +} + +func (c *inflightCache) prune(logBlock uint64) { + // Only prune every pruneInterval blocks + if int(logBlock)-int(c.lastPruneHeight) < cachePruneInterval { + return + } + + for key := range c.cache { + if int(key.blockNumber) < int(logBlock)-c.lookback { + delete(c.cache, key) + } + } + + c.lastPruneHeight = logBlock +} diff --git a/core/services/vrf/vrfcommon/log_dedupe_test.go b/core/services/vrf/vrfcommon/log_dedupe_test.go index 757842a3f65..f606b95e2a4 100644 --- a/core/services/vrf/vrfcommon/log_dedupe_test.go +++ b/core/services/vrf/vrfcommon/log_dedupe_test.go @@ -30,6 +30,22 @@ func TestLogDeduper(t *testing.T) { }, results: []bool{true, false}, }, + { + name: "different block number", + logs: []types.Log{ + { + BlockNumber: 10, + BlockHash: common.Hash{0x1}, + Index: 3, + }, + { + BlockNumber: 11, + BlockHash: common.Hash{0x2}, + Index: 3, + }, + }, + results: []bool{true, true}, + }, { name: "same block number different hash", logs: []types.Log{ @@ -128,11 +144,11 @@ func TestLogDeduper(t *testing.T) { Index: 11, }, { - BlockNumber: 115, + BlockNumber: 1015, BlockHash: common.Hash{0x1, 0x1, 0x5}, Index: 0, }, - // Now the logs at blocks 10 and 11 should be pruned, and therefor redelivered. + // Now the logs at blocks 10 and 11 should be pruned, and therefore redelivered. // The log at block 115 should not be redelivered. { BlockNumber: 10, @@ -145,7 +161,7 @@ func TestLogDeduper(t *testing.T) { Index: 11, }, { - BlockNumber: 115, + BlockNumber: 1015, BlockHash: common.Hash{0x1, 0x1, 0x5}, Index: 0, }, diff --git a/core/services/vrf/vrfcommon/types.go b/core/services/vrf/vrfcommon/types.go index 03de4eb562f..175b362b5aa 100644 --- a/core/services/vrf/vrfcommon/types.go +++ b/core/services/vrf/vrfcommon/types.go @@ -5,7 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" ) diff --git a/core/services/vrf/vrfcommon/utils.go b/core/services/vrf/vrfcommon/utils.go new file mode 100644 index 00000000000..f9cc012d8fb --- /dev/null +++ b/core/services/vrf/vrfcommon/utils.go @@ -0,0 +1,78 @@ +package vrfcommon + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + + txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" +) + +type RespCountEntry struct { + RequestID string + Count int +} + +func GetRespCounts(ctx context.Context, txm txmgr.TxManager, chainID *big.Int, confirmedBlockNum int64) ( + []RespCountEntry, + error, +) { + counts := []RespCountEntry{} + metaField := "RequestID" + states := []txmgrtypes.TxState{txmgrcommon.TxUnconfirmed, txmgrcommon.TxUnstarted, txmgrcommon.TxInProgress} + // Search for txes with a non-null meta field in the provided states + unconfirmedTxes, err := txm.FindTxesWithMetaFieldByStates(ctx, metaField, states, chainID) + if err != nil { + return nil, errors.Wrap(err, "getRespCounts failed due to error in FindTxesWithMetaFieldByStates") + } + // Fetch completed transactions only as far back as the given confirmedBlockNum. This avoids + // a table scan of the whole table, which could be large if it is unpruned. + var confirmedTxes []*txmgr.Tx + confirmedTxes, err = txm.FindTxesWithMetaFieldByReceiptBlockNum(ctx, metaField, confirmedBlockNum, chainID) + if err != nil { + return nil, errors.Wrap(err, "getRespCounts failed due to error in FindTxesWithMetaFieldByReceiptBlockNum") + } + txes := DedupeTxList(append(unconfirmedTxes, confirmedTxes...)) + respCountMap := make(map[string]int) + // Consolidate the number of txes for each meta RequestID + for _, tx := range txes { + var meta *txmgrtypes.TxMeta[common.Address, common.Hash] + meta, err = tx.GetMeta() + if err != nil { + return nil, errors.Wrap(err, "getRespCounts failed parsing tx meta field") + } + if meta != nil && meta.RequestID != nil { + requestId := meta.RequestID.String() + if _, exists := respCountMap[requestId]; !exists { + respCountMap[requestId] = 0 + } + respCountMap[requestId]++ + } + } + + // Parse response count map into output + for key, value := range respCountMap { + respCountEntry := RespCountEntry{ + RequestID: key, + Count: value, + } + counts = append(counts, respCountEntry) + } + return counts, nil +} + +func DedupeTxList(txes []*txmgr.Tx) []*txmgr.Tx { + txIdMap := make(map[string]bool) + dedupedTxes := []*txmgr.Tx{} + for _, tx := range txes { + if _, found := txIdMap[tx.GetID()]; !found { + txIdMap[tx.GetID()] = true + dedupedTxes = append(dedupedTxes, tx) + } + } + return dedupedTxes +} diff --git a/core/services/vrf/vrfcommon/validate.go b/core/services/vrf/vrfcommon/validate.go index d417336562c..07e8676ddb7 100644 --- a/core/services/vrf/vrfcommon/validate.go +++ b/core/services/vrf/vrfcommon/validate.go @@ -9,7 +9,7 @@ import ( "github.com/pelletier/go-toml" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/signatures/secp256k1" diff --git a/core/services/vrf/vrfcommon/validate_test.go b/core/services/vrf/vrfcommon/validate_test.go index 03d544f8189..efb1f22216f 100644 --- a/core/services/vrf/vrfcommon/validate_test.go +++ b/core/services/vrf/vrfcommon/validate_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/services/job" ) diff --git a/core/services/vrf/vrftesthelpers/helpers.go b/core/services/vrf/vrftesthelpers/helpers.go index f36fb1ea027..77d3f33a653 100644 --- a/core/services/vrf/vrftesthelpers/helpers.go +++ b/core/services/vrf/vrftesthelpers/helpers.go @@ -17,7 +17,7 @@ import ( "github.com/shopspring/decimal" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/blockhash_store" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_consumer_interface" @@ -67,7 +67,7 @@ func CreateAndStartBHSJob( TrustedBlockhashStoreAddress: trustedBlockhashStoreAddress, TrustedBlockhashStoreBatchSize: trustedBlockhashStoreBatchSize, PollPeriod: time.Second, - RunTimeout: 100 * time.Millisecond, + RunTimeout: 10 * time.Second, EVMChainID: 1337, FromAddresses: fromAddresses, }) diff --git a/core/services/webhook/authorizer_test.go b/core/services/webhook/authorizer_test.go index 19dbf381408..b6eb2feaccb 100644 --- a/core/services/webhook/authorizer_test.go +++ b/core/services/webhook/authorizer_test.go @@ -3,7 +3,7 @@ package webhook_test import ( "testing" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" diff --git a/core/services/webhook/delegate.go b/core/services/webhook/delegate.go index f5a8d553f23..237245b81c9 100644 --- a/core/services/webhook/delegate.go +++ b/core/services/webhook/delegate.go @@ -8,11 +8,12 @@ import ( "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) type ( @@ -111,7 +112,7 @@ func newWebhookJobRunner(runner pipeline.Runner, lggr logger.Logger) *webhookJob type registeredJob struct { job.Job - chRemove utils.StopChan + chRemove services.StopChan } func (r *webhookJobRunner) addSpec(spec job.Job) error { diff --git a/core/services/webhook/external_initiator_manager.go b/core/services/webhook/external_initiator_manager.go index 2e881ec42d6..01edf82b114 100644 --- a/core/services/webhook/external_initiator_manager.go +++ b/core/services/webhook/external_initiator_manager.go @@ -10,7 +10,7 @@ import ( "github.com/lib/pq" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/logger" diff --git a/core/sessions/authentication.go b/core/sessions/authentication.go new file mode 100644 index 00000000000..0f0dda3bf33 --- /dev/null +++ b/core/sessions/authentication.go @@ -0,0 +1,66 @@ +package sessions + +import ( + "errors" + "fmt" + + "github.com/smartcontractkit/chainlink/v2/core/auth" + "github.com/smartcontractkit/chainlink/v2/core/bridges" +) + +// Application config constant options +type AuthenticationProviderName string + +const ( + LocalAuth AuthenticationProviderName = "local" + LDAPAuth AuthenticationProviderName = "ldap" +) + +// ErrUserSessionExpired defines the error triggered when the user session has expired +var ErrUserSessionExpired = errors.New("session missing or expired, please login again") + +// ErrNotSupported defines the error where interface functionality doesn't align with the underlying Auth Provider +var ErrNotSupported = fmt.Errorf("functionality not supported with current authentication provider: %w", errors.ErrUnsupported) + +// ErrEmptySessionID captures the empty case error message +var ErrEmptySessionID = errors.New("session ID cannot be empty") + +//go:generate mockery --quiet --name BasicAdminUsersORM --output ./mocks/ --case=underscore + +// BasicAdminUsersORM is the interface that defines the functionality required for supporting basic admin functionality +// adjacent to the identity provider authentication provider implementation. It is currently implemented by the local +// users/sessions ORM containing local admin CLI actions. This is separate from the AuthenticationProvider, +// as local admin management (ie initial core node setup, initial admin user creation), is always +// required no matter what the pluggable AuthenticationProvider implementation is. +type BasicAdminUsersORM interface { + ListUsers() ([]User, error) + CreateUser(user *User) error + FindUser(email string) (User, error) +} + +//go:generate mockery --quiet --name AuthenticationProvider --output ./mocks/ --case=underscore + +// AuthenticationProvider is an interface that abstracts the required application calls to a user management backend +// Currently localauth (users table DB) or LDAP server (readonly) +type AuthenticationProvider interface { + FindUser(email string) (User, error) + FindUserByAPIToken(apiToken string) (User, error) + ListUsers() ([]User, error) + AuthorizedUserWithSession(sessionID string) (User, error) + DeleteUser(email string) error + DeleteUserSession(sessionID string) error + CreateSession(sr SessionRequest) (string, error) + ClearNonCurrentSessions(sessionID string) error + CreateUser(user *User) error + UpdateRole(email, newRole string) (User, error) + SetAuthToken(user *User, token *auth.Token) error + CreateAndSetAuthToken(user *User) (*auth.Token, error) + DeleteAuthToken(user *User) error + SetPassword(user *User, newPassword string) error + TestPassword(email, password string) error + Sessions(offset, limit int) ([]Session, error) + GetUserWebAuthn(email string) ([]WebAuthn, error) + SaveWebAuthn(token *WebAuthn) error + + FindExternalInitiator(eia *auth.Token) (initiator *bridges.ExternalInitiator, err error) +} diff --git a/core/sessions/ldapauth/client.go b/core/sessions/ldapauth/client.go new file mode 100644 index 00000000000..bb259f8c9a2 --- /dev/null +++ b/core/sessions/ldapauth/client.go @@ -0,0 +1,47 @@ +package ldapauth + +import ( + "fmt" + + "github.com/go-ldap/ldap/v3" + + "github.com/smartcontractkit/chainlink/v2/core/config" +) + +type ldapClient struct { + config config.LDAP +} + +//go:generate mockery --quiet --name LDAPClient --output ./mocks/ --case=underscore + +// Wrapper for creating a handle to a *ldap.Conn/LDAPConn interface +type LDAPClient interface { + CreateEphemeralConnection() (LDAPConn, error) +} + +//go:generate mockery --quiet --name LDAPConn --output ./mocks/ --case=underscore + +// Wrapper for ldap connection and mock testing, implemented by *ldap.Conn +type LDAPConn interface { + Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) + Bind(username string, password string) error + Close() (err error) +} + +func newLDAPClient(config config.LDAP) LDAPClient { + return &ldapClient{config} +} + +// CreateEphemeralConnection returns a valid, active LDAP connection for upstream Search and Bind queries +func (l *ldapClient) CreateEphemeralConnection() (LDAPConn, error) { + conn, err := ldap.DialURL(l.config.ServerAddress()) + if err != nil { + return nil, fmt.Errorf("failed to Dial LDAP Server: %w", err) + } + // Root level root user auth with credentials provided from config + bindStr := l.config.BaseUserAttr() + "=" + l.config.ReadOnlyUserLogin() + "," + l.config.BaseDN() + if err := conn.Bind(bindStr, l.config.ReadOnlyUserPass()); err != nil { + return nil, fmt.Errorf("unable to login as initial root LDAP user: %w", err) + } + return conn, nil +} diff --git a/core/sessions/ldapauth/helpers_test.go b/core/sessions/ldapauth/helpers_test.go new file mode 100644 index 00000000000..3566ea84380 --- /dev/null +++ b/core/sessions/ldapauth/helpers_test.go @@ -0,0 +1,131 @@ +package ldapauth + +import ( + "time" + + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/logger/audit" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/store/models" +) + +// Returns an instantiated ldapAuthenticator struct without validation for testing +func NewTestLDAPAuthenticator( + db *sqlx.DB, + pgCfg pg.QConfig, + ldapCfg config.LDAP, + dev bool, + lggr logger.Logger, + auditLogger audit.AuditLogger, +) (*ldapAuthenticator, error) { + namedLogger := lggr.Named("LDAPAuthenticationProvider") + ldapAuth := ldapAuthenticator{ + q: pg.NewQ(db, namedLogger, pgCfg), + ldapClient: newLDAPClient(ldapCfg), + config: ldapCfg, + lggr: lggr.Named("LDAPAuthenticationProvider"), + auditLogger: auditLogger, + } + + return &ldapAuth, nil +} + +// Default server group name mappings for test config and mocked ldap search results +const ( + NodeAdminsGroupCN = "NodeAdmins" + NodeEditorsGroupCN = "NodeEditors" + NodeRunnersGroupCN = "NodeRunners" + NodeReadOnlyGroupCN = "NodeReadOnly" +) + +// Implement a setter function within the _test file so that the ldapauth_test module can set the unexported field with a mock +func (l *ldapAuthenticator) SetLDAPClient(newClient LDAPClient) { + l.ldapClient = newClient +} + +// Implements config.LDAP +type TestConfig struct { +} + +func (t *TestConfig) ServerAddress() string { + return "ldaps://MOCK" +} + +func (t *TestConfig) ReadOnlyUserLogin() string { + return "mock-readonly" +} + +func (t *TestConfig) ReadOnlyUserPass() string { + return "mock-password" +} + +func (t *TestConfig) ServerTLS() bool { + return false +} + +func (t *TestConfig) SessionTimeout() models.Duration { + return models.MustMakeDuration(time.Duration(0)) +} + +func (t *TestConfig) QueryTimeout() time.Duration { + return time.Duration(0) +} + +func (t *TestConfig) UserAPITokenDuration() models.Duration { + return models.MustMakeDuration(time.Duration(0)) +} + +func (t *TestConfig) BaseUserAttr() string { + return "uid" +} + +func (t *TestConfig) BaseDN() string { + return "dc=custom,dc=example,dc=com" +} + +func (t *TestConfig) UsersDN() string { + return "ou=users" +} + +func (t *TestConfig) GroupsDN() string { + return "ou=groups" +} + +func (t *TestConfig) ActiveAttribute() string { + return "organizationalStatus" +} + +func (t *TestConfig) ActiveAttributeAllowedValue() string { + return "ACTIVE" +} + +func (t *TestConfig) AdminUserGroupCN() string { + return NodeAdminsGroupCN +} + +func (t *TestConfig) EditUserGroupCN() string { + return NodeEditorsGroupCN +} + +func (t *TestConfig) RunUserGroupCN() string { + return NodeRunnersGroupCN +} + +func (t *TestConfig) ReadUserGroupCN() string { + return NodeReadOnlyGroupCN +} + +func (t *TestConfig) UserApiTokenEnabled() bool { + return true +} + +func (t *TestConfig) UpstreamSyncInterval() models.Duration { + return models.MustMakeDuration(time.Duration(0)) +} + +func (t *TestConfig) UpstreamSyncRateLimit() models.Duration { + return models.MustMakeDuration(time.Duration(0)) +} diff --git a/core/sessions/ldapauth/ldap.go b/core/sessions/ldapauth/ldap.go new file mode 100644 index 00000000000..04f6fbfbbb6 --- /dev/null +++ b/core/sessions/ldapauth/ldap.go @@ -0,0 +1,858 @@ +/* +The LDAP authentication package forwards the credentials in the user session request +for authentication with a configured upstream LDAP server + +This package relies on the two following local database tables: + + ldap_sessions: Upon successful LDAP response, creates a keyed local copy of the user email + ldap_user_api_tokens: User created API tokens, tied to the node, storing user email. + +Note: user can have only one API token at a time, and token expiration is enforced + +User session and roles are cached and revalidated with the upstream service at the interval defined in +the local LDAP config through the Application.sessionReaper implementation in reaper.go. + +Changes to the upstream identity server will propagate through and update local tables (web sessions, API tokens) +by either removing the entries or updating the roles. This sync happens for every auth endpoint hit, and +via the defined sync interval. One goroutine is created to coordinate the sync timing in the New function + +This implementation is read only; user mutation actions such as Delete are not supported. + +MFA is supported via the remote LDAP server implementation. Sufficient request time out should accommodate +for a blocking auth call while the user responds to a potential push notification callback. +*/ +package ldapauth + +import ( + "crypto/subtle" + "database/sql" + "errors" + "fmt" + "strings" + "time" + + "github.com/go-ldap/ldap/v3" + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink/v2/core/auth" + "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/logger/audit" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/sessions" + "github.com/smartcontractkit/chainlink/v2/core/utils" + "github.com/smartcontractkit/chainlink/v2/core/utils/mathutil" +) + +const ( + UniqueMemberAttribute = "uniqueMember" +) + +var ErrUserNotInUpstream = errors.New("LDAP query returned no matching users") +var ErrUserNoLDAPGroups = errors.New("user present in directory, but matching no role groups assigned") + +type ldapAuthenticator struct { + q pg.Q + ldapClient LDAPClient + config config.LDAP + lggr logger.Logger + auditLogger audit.AuditLogger +} + +// ldapAuthenticator implements sessions.AuthenticationProvider interface +var _ sessions.AuthenticationProvider = (*ldapAuthenticator)(nil) + +func NewLDAPAuthenticator( + db *sqlx.DB, + pgCfg pg.QConfig, + ldapCfg config.LDAP, + dev bool, + lggr logger.Logger, + auditLogger audit.AuditLogger, +) (*ldapAuthenticator, error) { + namedLogger := lggr.Named("LDAPAuthenticationProvider") + + // If not chainlink dev and not tls, error + if !dev && !ldapCfg.ServerTLS() { + return nil, errors.New("LDAP Authentication driver requires TLS when running in Production mode") + } + + // Ensure all RBAC role mappings to LDAP Groups are defined, and required fields populated, or error on startup + if ldapCfg.AdminUserGroupCN() == "" || ldapCfg.EditUserGroupCN() == "" || + ldapCfg.RunUserGroupCN() == "" || ldapCfg.ReadUserGroupCN() == "" { + return nil, errors.New("LDAP Group mapping from server group name for all local RBAC role required. Set group names for `_UserGroupCN` fields") + } + if ldapCfg.ServerAddress() == "" { + return nil, errors.New("LDAP ServerAddress config required") + } + if ldapCfg.ReadOnlyUserLogin() == "" { + return nil, errors.New("LDAP ReadOnlyUserLogin config required") + } + + ldapAuth := ldapAuthenticator{ + q: pg.NewQ(db, namedLogger, pgCfg), + ldapClient: newLDAPClient(ldapCfg), + config: ldapCfg, + lggr: lggr.Named("LDAPAuthenticationProvider"), + auditLogger: auditLogger, + } + + // Single override of library defined global + ldap.DefaultTimeout = ldapCfg.QueryTimeout() + + // Test initial connection and credentials + lggr.Infof("Attempting initial connection to configured LDAP server with bind as API user") + conn, err := ldapAuth.ldapClient.CreateEphemeralConnection() + if err != nil { + return nil, fmt.Errorf("unable to establish connection to LDAP server with provided URL and credentials: %w", err) + } + conn.Close() + + // Store LDAP connection config for auth/new connection per request instead of persisted connection with reconnect + return &ldapAuth, nil +} + +// FindUser will attempt to return an LDAP user with mapped role by email. +func (l *ldapAuthenticator) FindUser(email string) (sessions.User, error) { + email = strings.ToLower(email) + foundUser := sessions.User{} + + // First check for the supported local admin users table + var foundLocalAdminUser sessions.User + checkErr := l.q.Transaction(func(tx pg.Queryer) error { + sql := "SELECT * FROM users WHERE lower(email) = lower($1)" + return tx.Get(&foundLocalAdminUser, sql, email) + }) + if checkErr != nil { + // If error is not nil, there was either an issue or no local users found + if !errors.Is(checkErr, sql.ErrNoRows) { + // If the error is not that no local user was found, log and exit + l.lggr.Errorf("error searching users table: %v", checkErr) + return sessions.User{}, errors.New("error Finding user") + } + } else { + // Error was nil, local user found. Return + return foundLocalAdminUser, nil + } + + // First query for user "is active" property if defined + usersActive, err := l.validateUsersActive([]string{email}) + if err != nil { + if errors.Is(err, ErrUserNotInUpstream) { + return foundUser, ErrUserNotInUpstream + } + l.lggr.Errorf("error in validateUsers call: %v", err) + return foundUser, errors.New("error running query to validate user active") + } + if !usersActive[0] { + return foundUser, errors.New("user not active") + } + + conn, err := l.ldapClient.CreateEphemeralConnection() + if err != nil { + l.lggr.Errorf("error in LDAP dial: ", err) + return foundUser, errors.New("unable to establish connection to LDAP server with provided URL and credentials") + } + defer conn.Close() + + // User email and role are the only upstream data that needs queried for. + // List query user groups using the provided email, on success is a list of group the uniquemember belongs to + // data is readily available + escapedEmail := ldap.EscapeFilter(email) + searchBaseDN := fmt.Sprintf("%s, %s", l.config.GroupsDN(), l.config.BaseDN()) + filterQuery := fmt.Sprintf("(&(uniquemember=%s=%s,%s,%s))", l.config.BaseUserAttr(), escapedEmail, l.config.UsersDN(), l.config.BaseDN()) + searchRequest := ldap.NewSearchRequest( + searchBaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, + 0, int(l.config.QueryTimeout().Seconds()), false, + filterQuery, + []string{"cn"}, + nil, + ) + + // Query the server + result, err := conn.Search(searchRequest) + if err != nil { + l.lggr.Errorf("error searching users in LDAP query: %v", err) + return foundUser, errors.New("error searching users in LDAP directory") + } + + if len(result.Entries) == 0 { + // Provided email is not present in upstream LDAP server, local admin CLI auth is supported + // So query and check the users table as well before failing + if err = l.q.Transaction(func(tx pg.Queryer) error { + var localUserRole sessions.UserRole + if err = tx.Get(&localUserRole, "SELECT role FROM users WHERE email = $1", email); err != nil { + return err + } + foundUser = sessions.User{ + Email: email, + Role: localUserRole, + } + return nil + }); err != nil { + // Above query for local user unsuccessful, return error + l.lggr.Warnf("No local users table user found with email %s", email) + return foundUser, errors.New("no users found with provided email") + } + + // If the above query to the local users table was successful, return that local user's role + return foundUser, nil + } + + // Populate found user by email and role based on matched group names + userRole, err := l.groupSearchResultsToUserRole(result.Entries) + if err != nil { + l.lggr.Warnf("User '%s' found but no matching assigned groups in LDAP to assume role", email) + return sessions.User{}, err + } + + // Convert search result to sessions.User type with required fields + foundUser = sessions.User{ + Email: email, + Role: userRole, + } + + return foundUser, nil +} + +// FindUserByAPIToken retrieves a possible stored user and role from the ldap_user_api_tokens table store +func (l *ldapAuthenticator) FindUserByAPIToken(apiToken string) (sessions.User, error) { + if !l.config.UserApiTokenEnabled() { + return sessions.User{}, errors.New("API token is not enabled ") + } + + var foundUser sessions.User + err := l.q.Transaction(func(tx pg.Queryer) error { + // Query the ldap user API token table for given token, user role and email are cached so + // no further upstream LDAP query is performed, sessions and tokens are synced against the upstream server + // via the UpstreamSyncInterval config and reaper.go sync implementation + var foundUserToken struct { + UserEmail string + UserRole sessions.UserRole + Valid bool + } + if err := tx.Get(&foundUserToken, + "SELECT user_email, user_role, created_at + $2 >= now() as valid FROM ldap_user_api_tokens WHERE token_key = $1", + apiToken, l.config.UserAPITokenDuration().Duration(), + ); err != nil { + return err + } + if !foundUserToken.Valid { + return sessions.ErrUserSessionExpired + } + foundUser = sessions.User{ + Email: foundUserToken.UserEmail, + Role: foundUserToken.UserRole, + } + return nil + }) + if err != nil { + if errors.Is(err, sessions.ErrUserSessionExpired) { + // API Token expired, purge + if _, execErr := l.q.Exec("DELETE FROM ldap_user_api_tokens WHERE token_key = $1", apiToken); err != nil { + l.lggr.Errorf("error purging stale ldap API token session: %v", execErr) + } + } + return sessions.User{}, err + } + return foundUser, nil +} + +// ListUsers will load and return all active users in applicable LDAP groups, extended with local admin users as well +func (l *ldapAuthenticator) ListUsers() ([]sessions.User, error) { + // For each defined role/group, query for the list of group members to gather the full list of possible users + users := []sessions.User{} + var err error + + conn, err := l.ldapClient.CreateEphemeralConnection() + if err != nil { + l.lggr.Errorf("error in LDAP dial: ", err) + return users, errors.New("unable to establish connection to LDAP server with provided URL and credentials") + } + defer conn.Close() + + // Query for list of uniqueMember IDs present in Admin group + adminUsers, err := l.ldapGroupMembersListToUser(conn, l.config.AdminUserGroupCN(), sessions.UserRoleAdmin) + if err != nil { + l.lggr.Errorf("error in ldapGroupMembersListToUser: ", err) + return users, errors.New("unable to list group users") + } + // Query for list of uniqueMember IDs present in Edit group + editUsers, err := l.ldapGroupMembersListToUser(conn, l.config.EditUserGroupCN(), sessions.UserRoleEdit) + if err != nil { + l.lggr.Errorf("error in ldapGroupMembersListToUser: ", err) + return users, errors.New("unable to list group users") + } + // Query for list of uniqueMember IDs present in Run group + runUsers, err := l.ldapGroupMembersListToUser(conn, l.config.RunUserGroupCN(), sessions.UserRoleRun) + if err != nil { + l.lggr.Errorf("error in ldapGroupMembersListToUser: ", err) + return users, errors.New("unable to list group users") + } + // Query for list of uniqueMember IDs present in Read group + readUsers, err := l.ldapGroupMembersListToUser(conn, l.config.ReadUserGroupCN(), sessions.UserRoleView) + if err != nil { + l.lggr.Errorf("error in ldapGroupMembersListToUser: ", err) + return users, errors.New("unable to list group users") + } + + // Aggregate full list + users = append(users, adminUsers...) + users = append(users, editUsers...) + users = append(users, runUsers...) + users = append(users, readUsers...) + + // Dedupe preserving order of highest role + uniqueRef := make(map[string]struct{}) + dedupedUsers := []sessions.User{} + for _, user := range users { + if _, ok := uniqueRef[user.Email]; !ok { + uniqueRef[user.Email] = struct{}{} + dedupedUsers = append(dedupedUsers, user) + } + } + + // If no active attribute to check is defined, user simple being assigned the group is enough, return full list + if l.config.ActiveAttribute() == "" { + return dedupedUsers, nil + } + + // Now optionally validate that all uniqueMembers are active in the org/LDAP server + emails := []string{} + for _, user := range dedupedUsers { + emails = append(emails, user.Email) + } + activeUsers, err := l.validateUsersActive(emails) + if err != nil { + l.lggr.Errorf("error validating supplied user list: ", err) + return users, errors.New("error validating supplied user list") + } + + // Filter non active users + returnUsers := []sessions.User{} + for i, active := range activeUsers { + if active { + returnUsers = append(returnUsers, dedupedUsers[i]) + } + } + + // Extend with local admin users + var localAdminUsers []sessions.User + if err := l.q.Transaction(func(tx pg.Queryer) error { + sql := "SELECT * FROM users ORDER BY email ASC;" + return tx.Select(&localAdminUsers, sql) + }); err != nil { + l.lggr.Errorf("error extending upstream LDAP users with local admin users in users table: ", err) + } else { + returnUsers = append(returnUsers, localAdminUsers...) + } + + return returnUsers, nil +} + +// ldapGroupMembersListToUser queries the LDAP server given a conn for a list of uniqueMember who are part of the parameterized group +func (l *ldapAuthenticator) ldapGroupMembersListToUser(conn LDAPConn, groupNameCN string, roleToAssign sessions.UserRole) ([]sessions.User, error) { + users, err := ldapGroupMembersListToUser( + conn, groupNameCN, roleToAssign, l.config.GroupsDN(), + l.config.BaseDN(), l.config.QueryTimeout(), + l.lggr, + ) + if err != nil { + l.lggr.Errorf("error listing members of group (%s): %v", groupNameCN, err) + return users, errors.New("error searching group members in LDAP directory") + } + return users, nil +} + +// AuthorizedUserWithSession will return the API user associated with the Session ID if it +// exists and hasn't expired, and update session's LastUsed field. The state of the upstream LDAP server +// is polled and synced at the defined interval via a SleeperTask +func (l *ldapAuthenticator) AuthorizedUserWithSession(sessionID string) (sessions.User, error) { + if len(sessionID) == 0 { + return sessions.User{}, errors.New("session ID cannot be empty") + } + var foundUser sessions.User + err := l.q.Transaction(func(tx pg.Queryer) error { + // Query the ldap_sessions table for given session ID, user role and email are cached so + // no further upstream LDAP query is performed + var foundSession struct { + UserEmail string + UserRole sessions.UserRole + Valid bool + } + if err := tx.Get(&foundSession, + "SELECT user_email, user_role, created_at + $2 >= now() as valid FROM ldap_sessions WHERE id = $1", + sessionID, l.config.SessionTimeout().Duration(), + ); err != nil { + return sessions.ErrUserSessionExpired + } + if !foundSession.Valid { + // Sessions expired, purge + return sessions.ErrUserSessionExpired + } + foundUser = sessions.User{ + Email: foundSession.UserEmail, + Role: foundSession.UserRole, + } + return nil + }) + if err != nil { + if errors.Is(err, sessions.ErrUserSessionExpired) { + if _, execErr := l.q.Exec("DELETE FROM ldap_sessions WHERE id = $1", sessionID); err != nil { + l.lggr.Errorf("error purging stale ldap session: %v", execErr) + } + } + return sessions.User{}, err + } + return foundUser, nil +} + +// DeleteUser is not supported for read only LDAP +func (l *ldapAuthenticator) DeleteUser(email string) error { + return sessions.ErrNotSupported +} + +// DeleteUserSession removes an ldapSession table entry by ID +func (l *ldapAuthenticator) DeleteUserSession(sessionID string) error { + _, err := l.q.Exec("DELETE FROM ldap_sessions WHERE id = $1", sessionID) + return err +} + +// GetUserWebAuthn returns an empty stub, MFA token prompt is handled either by the upstream +// server blocking callback, or an error code to pass a OTP +func (l *ldapAuthenticator) GetUserWebAuthn(email string) ([]sessions.WebAuthn, error) { + return []sessions.WebAuthn{}, nil +} + +// CreateSession will forward the session request credentials to the +// LDAP server, querying for a user + role response if username and +// password match. The API call is blocking with timeout, so a sufficient timeout +// should allow the user to respond to potential MFA push notifications +func (l *ldapAuthenticator) CreateSession(sr sessions.SessionRequest) (string, error) { + conn, err := l.ldapClient.CreateEphemeralConnection() + if err != nil { + return "", errors.New("unable to establish connection to LDAP server with provided URL and credentials") + } + defer conn.Close() + + var returnErr error + + // Attempt to LDAP Bind with user provided credentials + escapedEmail := ldap.EscapeFilter(strings.ToLower(sr.Email)) + searchBaseDN := fmt.Sprintf("%s=%s,%s,%s", l.config.BaseUserAttr(), escapedEmail, l.config.UsersDN(), l.config.BaseDN()) + if err = conn.Bind(searchBaseDN, sr.Password); err != nil { + l.lggr.Infof("Error binding user authentication request in LDAP Bind: %v", err) + returnErr = errors.New("unable to log in with LDAP server. Check credentials") + } + + // Bind was successful meaning user and credentials are present in LDAP directory + // Reuse FindUser functionality to fetch user roles used to create ldap_session entry + // with cached user email and role + foundUser, err := l.FindUser(escapedEmail) + if err != nil { + l.lggr.Infof("Successful user login, but error querying for user groups: user: %s, error %v", escapedEmail, err) + returnErr = errors.New("log in successful, but no assigned groups to assume role") + } + + isLocalUser := false + if returnErr != nil { + // Unable to log in against LDAP server, attempt fallback local auth with credentials, case of local CLI Admin account + // Successful local user sessions can not be managed by the upstream server and have expiration handled by the reaper sync module + foundUser, returnErr = l.localLoginFallback(sr) + isLocalUser = true + } + + // If err is still populated, return + if returnErr != nil { + return "", returnErr + } + + l.lggr.Infof("Successful LDAP login request for user %s - %s", sr.Email, foundUser.Role) + + // Save session, user, and role to database. Given a session ID for future queries, the LDAP server will not be queried + // Sessions are set to expire after the duration + creation date elapsed, and are synced on an interval against the upstream + // LDAP server + session := sessions.NewSession() + _, err = l.q.Exec( + "INSERT INTO ldap_sessions (id, user_email, user_role, localauth_user, created_at) VALUES ($1, $2, $3, $4, now())", + session.ID, + strings.ToLower(sr.Email), + foundUser.Role, + isLocalUser, + ) + if err != nil { + l.lggr.Errorf("unable to create new session in ldap_sessions table %v", err) + return "", fmt.Errorf("error creating local LDAP session: %w", err) + } + + l.auditLogger.Audit(audit.AuthLoginSuccessNo2FA, map[string]interface{}{"email": sr.Email}) + + return session.ID, nil +} + +// ClearNonCurrentSessions removes all ldap_sessions but the id passed in. +func (l *ldapAuthenticator) ClearNonCurrentSessions(sessionID string) error { + _, err := l.q.Exec("DELETE FROM ldap_sessions where id != $1", sessionID) + return err +} + +// CreateUser is not supported for read only LDAP +func (l *ldapAuthenticator) CreateUser(user *sessions.User) error { + return sessions.ErrNotSupported +} + +// UpdateRole is not supported for read only LDAP +func (l *ldapAuthenticator) UpdateRole(email, newRole string) (sessions.User, error) { + return sessions.User{}, sessions.ErrNotSupported +} + +// SetPassword for remote users is not supported via the read only LDAP implementation, however change password +// in the context of updating a local admin user's password is required +func (l *ldapAuthenticator) SetPassword(user *sessions.User, newPassword string) error { + // Ensure specified user is part of the local admins user table + var localAdminUser sessions.User + if err := l.q.Transaction(func(tx pg.Queryer) error { + sql := "SELECT * FROM users WHERE lower(email) = lower($1)" + return tx.Get(&localAdminUser, sql, user.Email) + }); err != nil { + l.lggr.Infof("Can not change password, local user with email not found in users table: %s, err: %v", user.Email, err) + return sessions.ErrNotSupported + } + + // User is local admin, save new password + hashedPassword, err := utils.HashPassword(newPassword) + if err != nil { + return err + } + if err := l.q.Transaction(func(tx pg.Queryer) error { + sql := "UPDATE users SET hashed_password = $1, updated_at = now() WHERE email = $2 RETURNING *" + return tx.Get(user, sql, hashedPassword, user.Email) + }); err != nil { + l.lggr.Errorf("unable to set password for user: %s, err: %v", user.Email, err) + return errors.New("unable to save password") + } + return nil +} + +// TestPassword tests if an LDAP login bind can be performed with provided credentials, returns nil if success +func (l *ldapAuthenticator) TestPassword(email string, password string) error { + conn, err := l.ldapClient.CreateEphemeralConnection() + if err != nil { + return errors.New("unable to establish connection to LDAP server with provided URL and credentials") + } + defer conn.Close() + + // Attempt to LDAP Bind with user provided credentials + escapedEmail := ldap.EscapeFilter(strings.ToLower(email)) + searchBaseDN := fmt.Sprintf("%s=%s,%s,%s", l.config.BaseUserAttr(), escapedEmail, l.config.UsersDN(), l.config.BaseDN()) + err = conn.Bind(searchBaseDN, password) + if err == nil { + return nil + } + l.lggr.Infof("Error binding user authentication request in TestPassword call LDAP Bind: %v", err) + + // Fall back to test local users table in case of supported local CLI users as well + var hashedPassword string + if err := l.q.Get(&hashedPassword, "SELECT hashed_password FROM users WHERE lower(email) = lower($1)", email); err != nil { + return errors.New("invalid credentials") + } + if !utils.CheckPasswordHash(password, hashedPassword) { + return errors.New("invalid credentials") + } + + return nil +} + +// CreateAndSetAuthToken generates a new credential token with the user role +func (l *ldapAuthenticator) CreateAndSetAuthToken(user *sessions.User) (*auth.Token, error) { + newToken := auth.NewToken() + + err := l.SetAuthToken(user, newToken) + if err != nil { + return nil, err + } + + return newToken, nil +} + +// SetAuthToken updates the user to use the given Authentication Token. +func (l *ldapAuthenticator) SetAuthToken(user *sessions.User, token *auth.Token) error { + if !l.config.UserApiTokenEnabled() { + return errors.New("API token is not enabled ") + } + + salt := utils.NewSecret(utils.DefaultSecretSize) + hashedSecret, err := auth.HashedSecret(token, salt) + if err != nil { + return fmt.Errorf("LDAPAuth SetAuthToken hashed secret error: %w", err) + } + + err = l.q.Transaction(func(tx pg.Queryer) error { + // Is this user a local CLI Admin or upstream LDAP user? + // Check presence in local users table. Set localauth_user column true if present. + // This flag omits the session/token from being purged by the sync daemon/reaper.go + isLocalCLIAdmin := false + err = l.q.QueryRow("SELECT EXISTS (SELECT 1 FROM users WHERE email = $1)", user.Email).Scan(&isLocalCLIAdmin) + if err != nil { + return fmt.Errorf("error checking user presence in users table: %w", err) + } + + // Remove any existing API tokens + if _, err = l.q.Exec("DELETE FROM ldap_user_api_tokens WHERE user_email = $1", user.Email); err != nil { + return fmt.Errorf("error executing DELETE FROM ldap_user_api_tokens: %w", err) + } + // Create new API token for user + _, err = l.q.Exec( + "INSERT INTO ldap_user_api_tokens (user_email, user_role, localauth_user, token_key, token_salt, token_hashed_secret, created_at) VALUES ($1, $2, $3, $4, $5, $6, now())", + user.Email, + user.Role, + isLocalCLIAdmin, + token.AccessKey, + salt, + hashedSecret, + ) + if err != nil { + return fmt.Errorf("failed insert into ldap_user_api_tokens: %w", err) + } + return nil + }) + if err != nil { + return errors.New("error creating API token") + } + + l.auditLogger.Audit(audit.APITokenCreated, map[string]interface{}{"user": user.Email}) + return nil +} + +// DeleteAuthToken clears and disables the users Authentication Token. +func (l *ldapAuthenticator) DeleteAuthToken(user *sessions.User) error { + _, err := l.q.Exec("DELETE FROM ldap_user_api_tokens WHERE email = $1") + return err +} + +// SaveWebAuthn is not supported for read only LDAP +func (l *ldapAuthenticator) SaveWebAuthn(token *sessions.WebAuthn) error { + return sessions.ErrNotSupported +} + +// Sessions returns all sessions limited by the parameters. +func (l *ldapAuthenticator) Sessions(offset, limit int) ([]sessions.Session, error) { + var sessions []sessions.Session + sql := `SELECT * FROM ldap_sessions ORDER BY created_at, id LIMIT $1 OFFSET $2;` + if err := l.q.Select(&sessions, sql, limit, offset); err != nil { + return sessions, nil + } + return sessions, nil +} + +// FindExternalInitiator supports the 'Run' role external intiator header auth functionality +func (l *ldapAuthenticator) FindExternalInitiator(eia *auth.Token) (*bridges.ExternalInitiator, error) { + exi := &bridges.ExternalInitiator{} + err := l.q.Get(exi, `SELECT * FROM external_initiators WHERE access_key = $1`, eia.AccessKey) + return exi, err +} + +// localLoginFallback tests the credentials provided against the 'local' authentication method +// This covers the case of local CLI API calls requiring local login separate from the LDAP server +func (l *ldapAuthenticator) localLoginFallback(sr sessions.SessionRequest) (sessions.User, error) { + var user sessions.User + sql := "SELECT * FROM users WHERE lower(email) = lower($1)" + err := l.q.Get(&user, sql, sr.Email) + if err != nil { + return user, err + } + if !constantTimeEmailCompare(strings.ToLower(sr.Email), strings.ToLower(user.Email)) { + l.auditLogger.Audit(audit.AuthLoginFailedEmail, map[string]interface{}{"email": sr.Email}) + return user, errors.New("invalid email") + } + + if !utils.CheckPasswordHash(sr.Password, user.HashedPassword) { + l.auditLogger.Audit(audit.AuthLoginFailedPassword, map[string]interface{}{"email": sr.Email}) + return user, errors.New("invalid password") + } + + return user, nil +} + +// validateUsersActive performs an additional LDAP server query for the supplied emails, checking the +// returned user data for an 'active' property defined optionally in the config. +// Returns same length bool 'valid' array, indexed by sorted email +func (l *ldapAuthenticator) validateUsersActive(emails []string) ([]bool, error) { + validUsers := make([]bool, len(emails)) + // If active attribute to check is not defined in config, skip + if l.config.ActiveAttribute() == "" { + // fill with valids + for i := range emails { + validUsers[i] = true + } + return validUsers, nil + } + + conn, err := l.ldapClient.CreateEphemeralConnection() + if err != nil { + l.lggr.Errorf("error in LDAP dial: ", err) + return validUsers, errors.New("unable to establish connection to LDAP server with provided URL and credentials") + } + defer conn.Close() + + // Build the full email list query to pull all 'isActive' information for each user specified in one query + filterQuery := "(|" + for _, email := range emails { + escapedEmail := ldap.EscapeFilter(email) + filterQuery = fmt.Sprintf("%s(%s=%s)", filterQuery, l.config.BaseUserAttr(), escapedEmail) + } + filterQuery = fmt.Sprintf("(&%s))", filterQuery) + searchBaseDN := fmt.Sprintf("%s,%s", l.config.UsersDN(), l.config.BaseDN()) + searchRequest := ldap.NewSearchRequest( + searchBaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, + 0, int(l.config.QueryTimeout().Seconds()), false, + filterQuery, + []string{l.config.BaseUserAttr(), l.config.ActiveAttribute()}, + nil, + ) + // Query LDAP server for the ActiveAttribute property of each specified user + results, err := conn.Search(searchRequest) + if err != nil { + l.lggr.Errorf("error searching user in LDAP query: %v", err) + return validUsers, errors.New("error searching users in LDAP directory") + } + + // Ensure user response entries + if len(results.Entries) == 0 { + return validUsers, ErrUserNotInUpstream + } + + // Pull expected ActiveAttribute value from list of string possible values + // keyed on email for final step to return flag bool list where order is preserved + emailToActiveMap := make(map[string]bool) + for _, result := range results.Entries { + isActiveAttribute := result.GetAttributeValue(l.config.ActiveAttribute()) + uidAttribute := result.GetAttributeValue(l.config.BaseUserAttr()) + emailToActiveMap[uidAttribute] = isActiveAttribute == l.config.ActiveAttributeAllowedValue() + } + for i, email := range emails { + active, ok := emailToActiveMap[email] + if ok && active { + validUsers[i] = true + } + } + + return validUsers, nil +} + +// ldapGroupMembersListToUser queries the LDAP server given a conn for a list of uniqueMember who are part of the parameterized group. Reused by sync.go +func ldapGroupMembersListToUser( + conn LDAPConn, + groupNameCN string, + roleToAssign sessions.UserRole, + groupsDN string, + baseDN string, + queryTimeout time.Duration, + lggr logger.Logger, +) ([]sessions.User, error) { + users := []sessions.User{} + // Prepare and query the GroupsDN for the specified group name + searchBaseDN := fmt.Sprintf("%s, %s", groupsDN, baseDN) + filterQuery := fmt.Sprintf("(&(cn=%s))", groupNameCN) + searchRequest := ldap.NewSearchRequest( + searchBaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, + 0, int(queryTimeout.Seconds()), false, + filterQuery, + []string{UniqueMemberAttribute}, + nil, + ) + result, err := conn.Search(searchRequest) + if err != nil { + lggr.Errorf("error searching group members in LDAP query: %v", err) + return users, errors.New("error searching group members in LDAP directory") + } + + // The result.Entry query response here is for the 'group' type of LDAP resource. The result should be a single entry, containing + // a single Attribute named 'uniqueMember' containing a list of string Values. These Values are strings that should be returned in + // the format "uid=test.user@example.com,ou=users,dc=example,dc=com". The 'uid' is then manually parsed here as the library does + // not expose the functionality + if len(result.Entries) != 1 { + lggr.Errorf("unexpected length of query results for group user members, expected one got %d", len(result.Entries)) + return users, errors.New("error searching group members in LDAP directory") + } + + // Get string list of members from 'uniqueMember' attribute + uniqueMemberValues := result.Entries[0].GetAttributeValues(UniqueMemberAttribute) + for _, uniqueMemberEntry := range uniqueMemberValues { + parts := strings.Split(uniqueMemberEntry, ",") // Split attribute value on comma (uid, ou, dc parts) + uidComponent := "" + for _, part := range parts { // Iterate parts for "uid=" + if strings.HasPrefix(part, "uid=") { + uidComponent = part + break + } + } + if uidComponent == "" { + lggr.Errorf("unexpected LDAP group query response for unique members - expected list of LDAP Values for uniqueMember containing LDAP strings in format uid=test.user@example.com,ou=users,dc=example,dc=com. Got %s", uniqueMemberEntry) + continue + } + // Map each user email to the sessions.User struct + userEmail := strings.TrimPrefix(uidComponent, "uid=") + users = append(users, sessions.User{ + Email: userEmail, + Role: roleToAssign, + }) + } + return users, nil +} + +// groupSearchResultsToUserRole takes a list of LDAP group search result entries and returns the associated +// internal user role based on the group name mappings defined in the configuration +func (l *ldapAuthenticator) groupSearchResultsToUserRole(ldapGroups []*ldap.Entry) (sessions.UserRole, error) { + return GroupSearchResultsToUserRole( + ldapGroups, + l.config.AdminUserGroupCN(), + l.config.EditUserGroupCN(), + l.config.RunUserGroupCN(), + l.config.ReadUserGroupCN(), + ) +} + +func GroupSearchResultsToUserRole(ldapGroups []*ldap.Entry, adminCN string, editCN string, runCN string, readCN string) (sessions.UserRole, error) { + // If defined Admin group name is present in groups search result, return UserRoleAdmin + for _, group := range ldapGroups { + if group.GetAttributeValue("cn") == adminCN { + return sessions.UserRoleAdmin, nil + } + } + // Check edit role + for _, group := range ldapGroups { + if group.GetAttributeValue("cn") == editCN { + return sessions.UserRoleEdit, nil + } + } + // Check run role + for _, group := range ldapGroups { + if group.GetAttributeValue("cn") == runCN { + return sessions.UserRoleRun, nil + } + } + // Check view role + for _, group := range ldapGroups { + if group.GetAttributeValue("cn") == readCN { + return sessions.UserRoleView, nil + } + } + // No role group found, error + return sessions.UserRoleView, ErrUserNoLDAPGroups +} + +const constantTimeEmailLength = 256 + +func constantTimeEmailCompare(left, right string) bool { + length := mathutil.Max(constantTimeEmailLength, len(left), len(right)) + leftBytes := make([]byte, length) + rightBytes := make([]byte, length) + copy(leftBytes, left) + copy(rightBytes, right) + return subtle.ConstantTimeCompare(leftBytes, rightBytes) == 1 +} diff --git a/core/sessions/ldapauth/ldap_test.go b/core/sessions/ldapauth/ldap_test.go new file mode 100644 index 00000000000..c85e0db831e --- /dev/null +++ b/core/sessions/ldapauth/ldap_test.go @@ -0,0 +1,639 @@ +package ldapauth_test + +import ( + "errors" + "fmt" + "testing" + "time" + + "github.com/go-ldap/ldap/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/logger/audit" + "github.com/smartcontractkit/chainlink/v2/core/sessions" + "github.com/smartcontractkit/chainlink/v2/core/sessions/ldapauth" + "github.com/smartcontractkit/chainlink/v2/core/sessions/ldapauth/mocks" +) + +// Setup LDAP Auth authenticator +func setupAuthenticationProvider(t *testing.T, ldapClient ldapauth.LDAPClient) (*sqlx.DB, sessions.AuthenticationProvider) { + t.Helper() + + cfg := ldapauth.TestConfig{} + db := pgtest.NewSqlxDB(t) + ldapAuthProvider, err := ldapauth.NewTestLDAPAuthenticator(db, pgtest.NewQConfig(true), &cfg, true, logger.TestLogger(t), &audit.AuditLoggerService{}) + if err != nil { + t.Fatalf("Error constructing NewTestLDAPAuthenticator: %v\n", err) + } + + // Override the LDAPClient responsible for returning the *ldap.Conn struct with Mock + ldapAuthProvider.SetLDAPClient(ldapClient) + return db, ldapAuthProvider +} + +func TestORM_FindUser_Empty(t *testing.T) { + t.Parallel() + + mockLdapClient := mocks.NewLDAPClient(t) + mockLdapConnProvider := mocks.NewLDAPConn(t) + mockLdapClient.On("CreateEphemeralConnection").Return(mockLdapConnProvider, nil) + mockLdapConnProvider.On("Close").Return(nil) + + // Initilaize LDAP Authentication Provider with mock client + _, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + // User not in upstream, return no entry + expectedResults := ldap.SearchResult{} + + // On search performed for validateUsersActive + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&expectedResults, nil) + + // Not in upstream, no local admin users, expect error + _, err := ldapAuthProvider.FindUser("unknown-user") + require.ErrorContains(t, err, "LDAP query returned no matching users") +} + +func TestORM_FindUser_NoGroups(t *testing.T) { + t.Parallel() + + mockLdapClient := mocks.NewLDAPClient(t) + mockLdapConnProvider := mocks.NewLDAPConn(t) + mockLdapClient.On("CreateEphemeralConnection").Return(mockLdapConnProvider, nil) + mockLdapConnProvider.On("Close").Return(nil) + + // Initilaize LDAP Authentication Provider with mock client + _, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + // User present in Upstream but no groups assigned + user1 := cltest.MustRandomUser(t) + expectedResults := ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: "cn=User One,ou=Users,dc=example,dc=com", + Attributes: []*ldap.EntryAttribute{ + { + Name: "organizationalStatus", + Values: []string{"ACTIVE"}, + }, + { + Name: "uid", + Values: []string{user1.Email}, + }, + }, + }, + }, + } + + // On search performed for validateUsersActive + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&expectedResults, nil) + + // No Groups, expect error + _, err := ldapAuthProvider.FindUser(user1.Email) + require.ErrorContains(t, err, "user present in directory, but matching no role groups assigned") +} + +func TestORM_FindUser_NotActive(t *testing.T) { + t.Parallel() + + mockLdapClient := mocks.NewLDAPClient(t) + mockLdapConnProvider := mocks.NewLDAPConn(t) + mockLdapClient.On("CreateEphemeralConnection").Return(mockLdapConnProvider, nil) + mockLdapConnProvider.On("Close").Return(nil) + + // Initilaize LDAP Authentication Provider with mock client + _, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + // User present in Upstream but not active + user1 := cltest.MustRandomUser(t) + expectedResults := ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: "cn=User One,ou=Users,dc=example,dc=com", + Attributes: []*ldap.EntryAttribute{ + { + Name: "organizationalStatus", + Values: []string{"INACTIVE"}, + }, + { + Name: "uid", + Values: []string{user1.Email}, + }, + }, + }, + }, + } + + // On search performed for validateUsersActive + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&expectedResults, nil) + + // User not active, expect error + _, err := ldapAuthProvider.FindUser(user1.Email) + require.ErrorContains(t, err, "user not active") +} + +func TestORM_FindUser_Single(t *testing.T) { + t.Parallel() + + mockLdapClient := mocks.NewLDAPClient(t) + mockLdapConnProvider := mocks.NewLDAPConn(t) + mockLdapClient.On("CreateEphemeralConnection").Return(mockLdapConnProvider, nil) + mockLdapConnProvider.On("Close").Return(nil) + + // Initilaize LDAP Authentication Provider with mock client + _, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + // User present and valid + user1 := cltest.MustRandomUser(t) + expectedResults := ldap.SearchResult{ // Users query + Entries: []*ldap.Entry{ + { + DN: "cn=User One,ou=Users,dc=example,dc=com", + Attributes: []*ldap.EntryAttribute{ + { + Name: "organizationalStatus", + Values: []string{"ACTIVE"}, + }, + { + Name: "uid", + Values: []string{user1.Email}, + }, + }, + }, + }, + } + expectedGroupResults := ldap.SearchResult{ // Groups query + Entries: []*ldap.Entry{ + { + DN: "cn=NodeEditors,ou=Users,dc=example,dc=com", + Attributes: []*ldap.EntryAttribute{ + { + Name: "cn", + Values: []string{"NodeEditors"}, + }, + }, + }, + }, + } + + // On search performed for validateUsersActive + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&expectedResults, nil).Once() + + // Second call on user groups search + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&expectedGroupResults, nil).Once() + + // User active, and has editor group. Expect success + user, err := ldapAuthProvider.FindUser(user1.Email) + require.NoError(t, err) + require.Equal(t, user1.Email, user.Email) + require.Equal(t, sessions.UserRoleEdit, user.Role) +} + +func TestORM_FindUser_FallbackMatchLocalAdmin(t *testing.T) { + t.Parallel() + + // Initilaize LDAP Authentication Provider with mock client + mockLdapClient := mocks.NewLDAPClient(t) + _, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + // Not in upstream, but utilize text fixture admin user presence in test DB. Succeed + user, err := ldapAuthProvider.FindUser(cltest.APIEmailAdmin) + require.NoError(t, err) + require.Equal(t, cltest.APIEmailAdmin, user.Email) + require.Equal(t, sessions.UserRoleAdmin, user.Role) +} + +func TestORM_FindUserByAPIToken_Success(t *testing.T) { + // Initilaize LDAP Authentication Provider with mock client + mockLdapClient := mocks.NewLDAPClient(t) + db, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + // Ensure valid tokens return a user with role + testEmail := "test@test.com" + apiToken := "example" + _, err := db.Exec("INSERT INTO ldap_user_api_tokens values ($1, 'edit', false, $2, '', '', now())", testEmail, apiToken) + require.NoError(t, err) + + // Found user by API token in specific ldap_user_api_tokens table + user, err := ldapAuthProvider.FindUserByAPIToken(apiToken) + require.NoError(t, err) + require.Equal(t, testEmail, user.Email) + require.Equal(t, sessions.UserRoleEdit, user.Role) +} + +func TestORM_FindUserByAPIToken_Expired(t *testing.T) { + cfg := ldapauth.TestConfig{} + + // Initilaize LDAP Authentication Provider with mock client + mockLdapClient := mocks.NewLDAPClient(t) + db, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + // Ensure valid tokens return a user with role + testEmail := "test@test.com" + apiToken := "example" + expiredTime := time.Now().Add(-cfg.UserAPITokenDuration().Duration()) + _, err := db.Exec("INSERT INTO ldap_user_api_tokens values ($1, 'edit', false, $2, '', '', $3)", testEmail, apiToken, expiredTime) + require.NoError(t, err) + + // Token found, but expired. Expect error + _, err = ldapAuthProvider.FindUserByAPIToken(apiToken) + require.Equal(t, sessions.ErrUserSessionExpired, err) +} + +func TestORM_ListUsers_Full(t *testing.T) { + t.Parallel() + + mockLdapClient := mocks.NewLDAPClient(t) + mockLdapConnProvider := mocks.NewLDAPConn(t) + mockLdapClient.On("CreateEphemeralConnection").Return(mockLdapConnProvider, nil) + mockLdapConnProvider.On("Close").Return(nil) + + // Initilaize LDAP Authentication Provider with mock client + _, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + user1 := cltest.MustRandomUser(t) + user2 := cltest.MustRandomUser(t) + user3 := cltest.MustRandomUser(t) + user4 := cltest.MustRandomUser(t) + user5 := cltest.MustRandomUser(t) + user6 := cltest.MustRandomUser(t) + + // LDAP Group queries per role - admin + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: fmt.Sprintf("cn=%s,ou=Groups,dc=example,dc=com", ldapauth.NodeAdminsGroupCN), + Attributes: []*ldap.EntryAttribute{ + { + Name: ldapauth.UniqueMemberAttribute, + Values: []string{ + fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", user1.Email), + fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", user2.Email), + }, + }, + }, + }, + }, + }, nil).Once() + // LDAP Group queries per role - edit + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: fmt.Sprintf("cn=%s,ou=Groups,dc=example,dc=com", ldapauth.NodeEditorsGroupCN), + Attributes: []*ldap.EntryAttribute{ + { + Name: ldapauth.UniqueMemberAttribute, + Values: []string{ + fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", user3.Email), + }, + }, + }, + }, + }, + }, nil).Once() + // LDAP Group queries per role - run + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: "cn=NodeRunners,ou=Groups,dc=example,dc=com", + Attributes: []*ldap.EntryAttribute{ + { + Name: ldapauth.UniqueMemberAttribute, + Values: []string{ + fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", user4.Email), + fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", user4.Email), // Test deduped + fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", user5.Email), + }, + }, + }, + }, + }, + }, nil).Once() + // LDAP Group queries per role - view + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: "cn=NodeReadOnly,ou=Groups,dc=example,dc=com", + Attributes: []*ldap.EntryAttribute{ + { + Name: ldapauth.UniqueMemberAttribute, + Values: []string{ + fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", user6.Email), + }, + }, + }, + }, + }, + }, nil).Once() + // Lastly followed by IsActive lookup + type userActivePair struct { + email string + active string + } + emailsActive := []userActivePair{ + {user1.Email, "ACTIVE"}, + {user2.Email, "INACTIVE"}, + {user3.Email, "ACTIVE"}, + {user4.Email, "ACTIVE"}, + {user5.Email, "INACTIVE"}, + {user6.Email, "ACTIVE"}, + } + listUpstreamUsersQuery := ldap.SearchResult{} + for _, upstreamUser := range emailsActive { + listUpstreamUsersQuery.Entries = append(listUpstreamUsersQuery.Entries, &ldap.Entry{ + DN: "cn=User,ou=Users,dc=example,dc=com", + Attributes: []*ldap.EntryAttribute{ + { + Name: "organizationalStatus", + Values: []string{upstreamUser.active}, + }, + { + Name: "uid", + Values: []string{upstreamUser.email}, + }, + }, + }, + ) + } + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&listUpstreamUsersQuery, nil).Once() + + // Asserts 'uid=' parsing log in ldapGroupMembersListToUser + // Expected full list of users above, including local admin user, excluding 'inactive' and duplicate users + users, err := ldapAuthProvider.ListUsers() + require.NoError(t, err) + require.Equal(t, users[0].Email, user1.Email) + require.Equal(t, users[0].Role, sessions.UserRoleAdmin) + require.Equal(t, users[1].Email, user3.Email) // User 2 inactive + require.Equal(t, users[1].Role, sessions.UserRoleEdit) + require.Equal(t, users[2].Email, user4.Email) + require.Equal(t, users[2].Role, sessions.UserRoleRun) + require.Equal(t, users[3].Email, user6.Email) // User 5 inactive + require.Equal(t, users[3].Role, sessions.UserRoleView) + require.Equal(t, users[4].Email, cltest.APIEmailAdmin) // Text fixture user is local admin included as well + require.Equal(t, users[4].Role, sessions.UserRoleAdmin) +} + +func TestORM_CreateSession_UpstreamBind(t *testing.T) { + t.Parallel() + + mockLdapClient := mocks.NewLDAPClient(t) + mockLdapConnProvider := mocks.NewLDAPConn(t) + mockLdapClient.On("CreateEphemeralConnection").Return(mockLdapConnProvider, nil) + mockLdapConnProvider.On("Close").Return(nil) + + // Initilaize LDAP Authentication Provider with mock client + _, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + // Upsream user present + user1 := cltest.MustRandomUser(t) + expectedResults := ldap.SearchResult{ // Users query + Entries: []*ldap.Entry{ + { + DN: "cn=User One,ou=Users,dc=example,dc=com", + Attributes: []*ldap.EntryAttribute{ + { + Name: "organizationalStatus", + Values: []string{"ACTIVE"}, + }, + { + Name: "uid", + Values: []string{user1.Email}, + }, + }, + }, + }, + } + expectedGroupResults := ldap.SearchResult{ // Groups query + Entries: []*ldap.Entry{ + { + DN: "cn=NodeEditors,ou=Users,dc=example,dc=com", + Attributes: []*ldap.EntryAttribute{ + { + Name: "cn", + Values: []string{"NodeEditors"}, + }, + }, + }, + }, + } + + // On search performed for validateUsersActive + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&expectedResults, nil).Once() + + // Second call on user groups search + mockLdapConnProvider.On("Search", mock.AnythingOfType("*ldap.SearchRequest")).Return(&expectedGroupResults, nil).Once() + + // User active, and has editor group. Expect success + mockLdapConnProvider.On("Bind", mock.Anything, cltest.Password).Return(nil) + sessionRequest := sessions.SessionRequest{ + Email: user1.Email, + Password: cltest.Password, + } + + _, err := ldapAuthProvider.CreateSession(sessionRequest) + require.NoError(t, err) +} + +func TestORM_CreateSession_LocalAdminFallbackLogin(t *testing.T) { + t.Parallel() + + mockLdapClient := mocks.NewLDAPClient(t) + mockLdapConnProvider := mocks.NewLDAPConn(t) + mockLdapClient.On("CreateEphemeralConnection").Return(mockLdapConnProvider, nil) + mockLdapConnProvider.On("Close").Return(nil) + + // Initilaize LDAP Authentication Provider with mock client + _, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + // Fail the bind to trigger 'localLoginFallback' - local admin users should still be able to login + // regardless of whether the authentication provider is remote or not + mockLdapConnProvider.On("Bind", mock.Anything, cltest.Password).Return(errors.New("unable to login via LDAP server")).Once() + + // User active, and has editor group. Expect success + sessionRequest := sessions.SessionRequest{ + Email: cltest.APIEmailAdmin, + Password: cltest.Password, + } + + _, err := ldapAuthProvider.CreateSession(sessionRequest) + require.NoError(t, err) + + // Finally, assert login failing altogether + // User active, and has editor group. Expect success + mockLdapConnProvider.On("Bind", mock.Anything, "incorrect-password").Return(errors.New("unable to login via LDAP server")).Once() + sessionRequest = sessions.SessionRequest{ + Email: cltest.APIEmailAdmin, + Password: "incorrect-password", + } + + _, err = ldapAuthProvider.CreateSession(sessionRequest) + require.ErrorContains(t, err, "invalid password") +} + +func TestORM_SetPassword_LocalAdminFallbackLogin(t *testing.T) { + t.Parallel() + + mockLdapClient := mocks.NewLDAPClient(t) + mockLdapConnProvider := mocks.NewLDAPConn(t) + mockLdapClient.On("CreateEphemeralConnection").Return(mockLdapConnProvider, nil) + mockLdapConnProvider.On("Close").Return(nil) + + // Initilaize LDAP Authentication Provider with mock client + _, ldapAuthProvider := setupAuthenticationProvider(t, mockLdapClient) + + // Fail the bind to trigger 'localLoginFallback' - local admin users should still be able to login + // regardless of whether the authentication provider is remote or not + mockLdapConnProvider.On("Bind", mock.Anything, cltest.Password).Return(errors.New("unable to login via LDAP server")).Once() + + // User active, and has editor group. Expect success + sessionRequest := sessions.SessionRequest{ + Email: cltest.APIEmailAdmin, + Password: cltest.Password, + } + + _, err := ldapAuthProvider.CreateSession(sessionRequest) + require.NoError(t, err) + + // Finally, assert login failing altogether + // User active, and has editor group. Expect success + mockLdapConnProvider.On("Bind", mock.Anything, "incorrect-password").Return(errors.New("unable to login via LDAP server")).Once() + sessionRequest = sessions.SessionRequest{ + Email: cltest.APIEmailAdmin, + Password: "incorrect-password", + } + + _, err = ldapAuthProvider.CreateSession(sessionRequest) + require.ErrorContains(t, err, "invalid password") +} + +func TestORM_MapSearchGroups(t *testing.T) { + t.Parallel() + + cfg := ldapauth.TestConfig{} + + tests := []struct { + name string + groupsQuerySearchResult []*ldap.Entry + wantMappedRole sessions.UserRole + wantErr error + }{ + { + "user in admin group only", + []*ldap.Entry{ + { + DN: fmt.Sprintf("cn=%s,ou=Groups,dc=example,dc=com", ldapauth.NodeAdminsGroupCN), + Attributes: []*ldap.EntryAttribute{ + { + Name: "cn", + Values: []string{ldapauth.NodeAdminsGroupCN}, + }, + }, + }, + }, + sessions.UserRoleAdmin, + nil, + }, + { + "user in edit group", + []*ldap.Entry{ + { + DN: fmt.Sprintf("cn=%s,ou=Groups,dc=example,dc=com", ldapauth.NodeEditorsGroupCN), + Attributes: []*ldap.EntryAttribute{ + { + Name: "cn", + Values: []string{ldapauth.NodeEditorsGroupCN}, + }, + }, + }, + }, + sessions.UserRoleEdit, + nil, + }, + { + "user in run group", + []*ldap.Entry{ + { + DN: fmt.Sprintf("cn=%s,ou=Groups,dc=example,dc=com", ldapauth.NodeRunnersGroupCN), + Attributes: []*ldap.EntryAttribute{ + { + Name: "cn", + Values: []string{ldapauth.NodeRunnersGroupCN}, + }, + }, + }, + }, + sessions.UserRoleRun, + nil, + }, + { + "user in view role", + []*ldap.Entry{ + { + DN: fmt.Sprintf("cn=%s,ou=Groups,dc=example,dc=com", ldapauth.NodeReadOnlyGroupCN), + Attributes: []*ldap.EntryAttribute{ + { + Name: "cn", + Values: []string{ldapauth.NodeReadOnlyGroupCN}, + }, + }, + }, + }, + sessions.UserRoleView, + nil, + }, + { + "user in none", + []*ldap.Entry{}, + sessions.UserRole(""), // ignored, error case + ldapauth.ErrUserNoLDAPGroups, + }, + { + "user in run and view", + []*ldap.Entry{ + { + DN: fmt.Sprintf("cn=%s,ou=Groups,dc=example,dc=com", ldapauth.NodeRunnersGroupCN), + Attributes: []*ldap.EntryAttribute{ + { + Name: "cn", + Values: []string{ldapauth.NodeRunnersGroupCN}, + }, + }, + }, + { + DN: fmt.Sprintf("cn=%s,ou=Groups,dc=example,dc=com", ldapauth.NodeReadOnlyGroupCN), + Attributes: []*ldap.EntryAttribute{ + { + Name: "cn", + Values: []string{ldapauth.NodeReadOnlyGroupCN}, + }, + }, + }, + }, + sessions.UserRoleRun, // Take highest role + nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + role, err := ldapauth.GroupSearchResultsToUserRole( + test.groupsQuerySearchResult, + cfg.AdminUserGroupCN(), + cfg.EditUserGroupCN(), + cfg.RunUserGroupCN(), + cfg.ReadUserGroupCN(), + ) + if test.wantErr != nil { + assert.Equal(t, test.wantErr, err) + } else { + assert.Equal(t, test.wantMappedRole, role) + } + }) + } +} diff --git a/core/sessions/ldapauth/mocks/ldap_client.go b/core/sessions/ldapauth/mocks/ldap_client.go new file mode 100644 index 00000000000..7a44778dcaa --- /dev/null +++ b/core/sessions/ldapauth/mocks/ldap_client.go @@ -0,0 +1,53 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package mocks + +import ( + ldapauth "github.com/smartcontractkit/chainlink/v2/core/sessions/ldapauth" + mock "github.com/stretchr/testify/mock" +) + +// LDAPClient is an autogenerated mock type for the LDAPClient type +type LDAPClient struct { + mock.Mock +} + +// CreateEphemeralConnection provides a mock function with given fields: +func (_m *LDAPClient) CreateEphemeralConnection() (ldapauth.LDAPConn, error) { + ret := _m.Called() + + var r0 ldapauth.LDAPConn + var r1 error + if rf, ok := ret.Get(0).(func() (ldapauth.LDAPConn, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() ldapauth.LDAPConn); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ldapauth.LDAPConn) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewLDAPClient creates a new instance of LDAPClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewLDAPClient(t interface { + mock.TestingT + Cleanup(func()) +}) *LDAPClient { + mock := &LDAPClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/sessions/ldapauth/mocks/ldap_conn.go b/core/sessions/ldapauth/mocks/ldap_conn.go new file mode 100644 index 00000000000..c05fb6c4fa6 --- /dev/null +++ b/core/sessions/ldapauth/mocks/ldap_conn.go @@ -0,0 +1,82 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package mocks + +import ( + ldap "github.com/go-ldap/ldap/v3" + + mock "github.com/stretchr/testify/mock" +) + +// LDAPConn is an autogenerated mock type for the LDAPConn type +type LDAPConn struct { + mock.Mock +} + +// Bind provides a mock function with given fields: username, password +func (_m *LDAPConn) Bind(username string, password string) error { + ret := _m.Called(username, password) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(username, password) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Close provides a mock function with given fields: +func (_m *LDAPConn) 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 +} + +// Search provides a mock function with given fields: searchRequest +func (_m *LDAPConn) Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) { + ret := _m.Called(searchRequest) + + var r0 *ldap.SearchResult + var r1 error + if rf, ok := ret.Get(0).(func(*ldap.SearchRequest) (*ldap.SearchResult, error)); ok { + return rf(searchRequest) + } + if rf, ok := ret.Get(0).(func(*ldap.SearchRequest) *ldap.SearchResult); ok { + r0 = rf(searchRequest) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ldap.SearchResult) + } + } + + if rf, ok := ret.Get(1).(func(*ldap.SearchRequest) error); ok { + r1 = rf(searchRequest) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewLDAPConn creates a new instance of LDAPConn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewLDAPConn(t interface { + mock.TestingT + Cleanup(func()) +}) *LDAPConn { + mock := &LDAPConn{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/sessions/ldapauth/sync.go b/core/sessions/ldapauth/sync.go new file mode 100644 index 00000000000..67f101b62a4 --- /dev/null +++ b/core/sessions/ldapauth/sync.go @@ -0,0 +1,343 @@ +package ldapauth + +import ( + "errors" + "fmt" + "time" + + "github.com/go-ldap/ldap/v3" + "github.com/jmoiron/sqlx" + "github.com/lib/pq" + + "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/sessions" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +type LDAPServerStateSyncer struct { + q pg.Q + ldapClient LDAPClient + config config.LDAP + lggr logger.Logger + nextSyncTime time.Time +} + +// NewLDAPServerStateSync creates a reaper that cleans stale sessions from the store. +func NewLDAPServerStateSync( + db *sqlx.DB, + pgCfg pg.QConfig, + config config.LDAP, + lggr logger.Logger, +) utils.SleeperTask { + namedLogger := lggr.Named("LDAPServerStateSync") + serverSync := LDAPServerStateSyncer{ + q: pg.NewQ(db, namedLogger, pgCfg), + ldapClient: newLDAPClient(config), + config: config, + lggr: namedLogger, + nextSyncTime: time.Time{}, + } + // If enabled, start a background task that calls the Sync/Work function on an + // interval without needing an auth event to trigger it + // Use IsInstant to check 0 value to omit functionality. + if !config.UpstreamSyncInterval().IsInstant() { + lggr.Info("LDAP Config UpstreamSyncInterval is non-zero, sync functionality will be called on a timer, respecting the UpstreamSyncRateLimit value") + serverSync.StartWorkOnTimer() + } else { + // Ensure upstream server state is synced on startup manually if interval check not set + serverSync.Work() + } + + // Start background Sync call task reactive to auth related events + serverSyncSleeperTask := utils.NewSleeperTask(&serverSync) + return serverSyncSleeperTask +} + +func (ldSync *LDAPServerStateSyncer) Name() string { + return "LDAPServerStateSync" +} + +func (ldSync *LDAPServerStateSyncer) StartWorkOnTimer() { + time.AfterFunc(ldSync.config.UpstreamSyncInterval().Duration(), ldSync.StartWorkOnTimer) + ldSync.Work() +} + +func (ldSync *LDAPServerStateSyncer) Work() { + // Purge expired ldap_sessions and ldap_user_api_tokens + recordCreationStaleThreshold := ldSync.config.SessionTimeout().Before(time.Now()) + err := ldSync.deleteStaleSessions(recordCreationStaleThreshold) + if err != nil { + ldSync.lggr.Error("unable to expire local LDAP sessions: ", err) + } + recordCreationStaleThreshold = ldSync.config.UserAPITokenDuration().Before(time.Now()) + err = ldSync.deleteStaleAPITokens(recordCreationStaleThreshold) + if err != nil { + ldSync.lggr.Error("unable to expire user API tokens: ", err) + } + + // Optional rate limiting check to limit the amount of upstream LDAP server queries performed + if !ldSync.config.UpstreamSyncRateLimit().IsInstant() { + if !time.Now().After(ldSync.nextSyncTime) { + return + } + + // Enough time has elapsed to sync again, store the time for when next sync is allowed and begin sync + ldSync.nextSyncTime = time.Now().Add(ldSync.config.UpstreamSyncRateLimit().Duration()) + } + + ldSync.lggr.Info("Begin Upstream LDAP provider state sync after checking time against config UpstreamSyncInterval and UpstreamSyncRateLimit") + + // For each defined role/group, query for the list of group members to gather the full list of possible users + users := []sessions.User{} + + conn, err := ldSync.ldapClient.CreateEphemeralConnection() + if err != nil { + ldSync.lggr.Errorf("Failed to Dial LDAP Server", err) + return + } + // Root level root user auth with credentials provided from config + bindStr := ldSync.config.BaseUserAttr() + "=" + ldSync.config.ReadOnlyUserLogin() + "," + ldSync.config.BaseDN() + if err = conn.Bind(bindStr, ldSync.config.ReadOnlyUserPass()); err != nil { + ldSync.lggr.Errorf("Unable to login as initial root LDAP user", err) + } + defer conn.Close() + + // Query for list of uniqueMember IDs present in Admin group + adminUsers, err := ldSync.ldapGroupMembersListToUser(conn, ldSync.config.AdminUserGroupCN(), sessions.UserRoleAdmin) + if err != nil { + ldSync.lggr.Errorf("Error in ldapGroupMembersListToUser: ", err) + return + } + // Query for list of uniqueMember IDs present in Edit group + editUsers, err := ldSync.ldapGroupMembersListToUser(conn, ldSync.config.EditUserGroupCN(), sessions.UserRoleEdit) + if err != nil { + ldSync.lggr.Errorf("Error in ldapGroupMembersListToUser: ", err) + return + } + // Query for list of uniqueMember IDs present in Edit group + runUsers, err := ldSync.ldapGroupMembersListToUser(conn, ldSync.config.RunUserGroupCN(), sessions.UserRoleRun) + if err != nil { + ldSync.lggr.Errorf("Error in ldapGroupMembersListToUser: ", err) + return + } + // Query for list of uniqueMember IDs present in Edit group + readUsers, err := ldSync.ldapGroupMembersListToUser(conn, ldSync.config.ReadUserGroupCN(), sessions.UserRoleView) + if err != nil { + ldSync.lggr.Errorf("Error in ldapGroupMembersListToUser: ", err) + return + } + + users = append(users, adminUsers...) + users = append(users, editUsers...) + users = append(users, runUsers...) + users = append(users, readUsers...) + + // Dedupe preserving order of highest role (sorted) + // Preserve members as a map for future lookup + upstreamUserStateMap := make(map[string]sessions.User) + dedupedEmails := []string{} + for _, user := range users { + if _, ok := upstreamUserStateMap[user.Email]; !ok { + upstreamUserStateMap[user.Email] = user + dedupedEmails = append(dedupedEmails, user.Email) + } + } + + // For each unique user in list of active sessions, check for 'Is Active' propery if defined in the config. Some LDAP providers + // list group members that are no longer marked as active + usersActiveFlags, err := ldSync.validateUsersActive(dedupedEmails, conn) + if err != nil { + ldSync.lggr.Errorf("Error validating supplied user list: ", err) + } + // Remove users in the upstreamUserStateMap source of truth who are part of groups but marked as deactivated/no-active + for i, active := range usersActiveFlags { + if !active { + delete(upstreamUserStateMap, dedupedEmails[i]) + } + } + + // upstreamUserStateMap is now the most up to date source of truth + // Now sync database sessions and roles with new data + err = ldSync.q.Transaction(func(tx pg.Queryer) error { + // First, purge users present in the local ldap_sessions table but not in the upstream server + type LDAPSession struct { + UserEmail string + UserRole sessions.UserRole + } + var existingSessions []LDAPSession + if err = tx.Select(&existingSessions, "SELECT user_email, user_role FROM ldap_sessions WHERE localauth_user = false"); err != nil { + return fmt.Errorf("unable to query ldap_sessions table: %w", err) + } + var existingAPITokens []LDAPSession + if err = tx.Select(&existingAPITokens, "SELECT user_email, user_role FROM ldap_user_api_tokens WHERE localauth_user = false"); err != nil { + return fmt.Errorf("unable to query ldap_user_api_tokens table: %w", err) + } + + // Create existing sessions and API tokens lookup map for later + existingSessionsMap := make(map[string]LDAPSession) + for _, sess := range existingSessions { + existingSessionsMap[sess.UserEmail] = sess + } + existingAPITokensMap := make(map[string]LDAPSession) + for _, sess := range existingAPITokens { + existingAPITokensMap[sess.UserEmail] = sess + } + + // Populate list of session emails present in the local session table but not in the upstream state + emailsToPurge := []interface{}{} + for _, ldapSession := range existingSessions { + if _, ok := upstreamUserStateMap[ldapSession.UserEmail]; !ok { + emailsToPurge = append(emailsToPurge, ldapSession.UserEmail) + } + } + // Likewise for API Tokens table + apiTokenEmailsToPurge := []interface{}{} + for _, ldapSession := range existingAPITokens { + if _, ok := upstreamUserStateMap[ldapSession.UserEmail]; !ok { + apiTokenEmailsToPurge = append(apiTokenEmailsToPurge, ldapSession.UserEmail) + } + } + + // Remove any active sessions this user may have + if len(emailsToPurge) > 0 { + _, err = ldSync.q.Exec("DELETE FROM ldap_sessions WHERE user_email = ANY($1)", pq.Array(emailsToPurge)) + if err != nil { + return err + } + } + + // Remove any active API tokens this user may have + if len(apiTokenEmailsToPurge) > 0 { + _, err = ldSync.q.Exec("DELETE FROM ldap_user_api_tokens WHERE user_email = ANY($1)", pq.Array(apiTokenEmailsToPurge)) + if err != nil { + return err + } + } + + // For each user session row, update role to match state of user map from upstream source + queryWhenClause := "" + emailValues := []interface{}{} + // Prepare CASE WHEN query statement with parameterized argument $n placeholders and matching role based on index + for email, user := range upstreamUserStateMap { + // Only build on SET CASE statement per local session and API token role, not for each upstream user value + _, sessionOk := existingSessionsMap[email] + _, tokenOk := existingAPITokensMap[email] + if !sessionOk && !tokenOk { + continue + } + emailValues = append(emailValues, email) + queryWhenClause += fmt.Sprintf("WHEN user_email = $%d THEN '%s' ", len(emailValues), user.Role) + } + + // If there are remaining user entries to update + if len(emailValues) != 0 { + // Set new role state for all rows in single Exec + query := fmt.Sprintf("UPDATE ldap_sessions SET user_role = CASE %s ELSE user_role END", queryWhenClause) + _, err = ldSync.q.Exec(query, emailValues...) + if err != nil { + return err + } + + // Update role of API tokens as well + query = fmt.Sprintf("UPDATE ldap_user_api_tokens SET user_role = CASE %s ELSE user_role END", queryWhenClause) + _, err = ldSync.q.Exec(query, emailValues...) + if err != nil { + return err + } + } + + ldSync.lggr.Info("local ldap_sessions and ldap_user_api_tokens table successfully synced with upstream LDAP state") + return nil + }) + if err != nil { + ldSync.lggr.Errorf("Error syncing local database state: ", err) + } + ldSync.lggr.Info("Upstream LDAP sync complete") +} + +// deleteStaleSessions deletes all ldap_sessions before the passed time. +func (ldSync *LDAPServerStateSyncer) deleteStaleSessions(before time.Time) error { + _, err := ldSync.q.Exec("DELETE FROM ldap_sessions WHERE created_at < $1", before) + return err +} + +// deleteStaleAPITokens deletes all ldap_user_api_tokens before the passed time. +func (ldSync *LDAPServerStateSyncer) deleteStaleAPITokens(before time.Time) error { + _, err := ldSync.q.Exec("DELETE FROM ldap_user_api_tokens WHERE created_at < $1", before) + return err +} + +// ldapGroupMembersListToUser queries the LDAP server given a conn for a list of uniqueMember who are part of the parameterized group +func (ldSync *LDAPServerStateSyncer) ldapGroupMembersListToUser(conn LDAPConn, groupNameCN string, roleToAssign sessions.UserRole) ([]sessions.User, error) { + users, err := ldapGroupMembersListToUser( + conn, groupNameCN, roleToAssign, ldSync.config.GroupsDN(), + ldSync.config.BaseDN(), ldSync.config.QueryTimeout(), + ldSync.lggr, + ) + if err != nil { + ldSync.lggr.Errorf("Error listing members of group (%s): %v", groupNameCN, err) + return users, errors.New("error searching group members in LDAP directory") + } + return users, nil +} + +// validateUsersActive performs an additional LDAP server query for the supplied emails, checking the +// returned user data for an 'active' property defined optionally in the config. +// Returns same length bool 'valid' array, order preserved +func (ldSync *LDAPServerStateSyncer) validateUsersActive(emails []string, conn LDAPConn) ([]bool, error) { + validUsers := make([]bool, len(emails)) + // If active attribute to check is not defined in config, skip + if ldSync.config.ActiveAttribute() == "" { + // pre fill with valids + for i := range emails { + validUsers[i] = true + } + return validUsers, nil + } + + // Build the full email list query to pull all 'isActive' information for each user specified in one query + filterQuery := "(|" + for _, email := range emails { + escapedEmail := ldap.EscapeFilter(email) + filterQuery = fmt.Sprintf("%s(%s=%s)", filterQuery, ldSync.config.BaseUserAttr(), escapedEmail) + } + filterQuery = fmt.Sprintf("(&%s))", filterQuery) + searchBaseDN := fmt.Sprintf("%s,%s", ldSync.config.UsersDN(), ldSync.config.BaseDN()) + searchRequest := ldap.NewSearchRequest( + searchBaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, + 0, int(ldSync.config.QueryTimeout().Seconds()), false, + filterQuery, + []string{ldSync.config.BaseUserAttr(), ldSync.config.ActiveAttribute()}, + nil, + ) + // Query LDAP server for the ActiveAttribute property of each specified user + results, err := conn.Search(searchRequest) + if err != nil { + ldSync.lggr.Errorf("Error searching user in LDAP query: %v", err) + return validUsers, errors.New("error searching users in LDAP directory") + } + // Ensure user response entries + if len(results.Entries) == 0 { + return validUsers, errors.New("no users matching email query") + } + + // Pull expected ActiveAttribute value from list of string possible values + // keyed on email for final step to return flag bool list where order is preserved + emailToActiveMap := make(map[string]bool) + for _, result := range results.Entries { + isActiveAttribute := result.GetAttributeValue(ldSync.config.ActiveAttribute()) + uidAttribute := result.GetAttributeValue(ldSync.config.BaseUserAttr()) + emailToActiveMap[uidAttribute] = isActiveAttribute == ldSync.config.ActiveAttributeAllowedValue() + } + for i, email := range emails { + active, ok := emailToActiveMap[email] + if ok && active { + validUsers[i] = true + } + } + + return validUsers, nil +} diff --git a/core/sessions/orm.go b/core/sessions/localauth/orm.go similarity index 79% rename from core/sessions/orm.go rename to core/sessions/localauth/orm.go index eaac211f242..090dc468a62 100644 --- a/core/sessions/orm.go +++ b/core/sessions/localauth/orm.go @@ -1,4 +1,4 @@ -package sessions +package localauth import ( "crypto/subtle" @@ -6,42 +6,19 @@ import ( "strings" "time" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "github.com/smartcontractkit/sqlx" "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/sessions" "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/v2/core/utils/mathutil" ) -//go:generate mockery --quiet --name ORM --output ./mocks/ --case=underscore - -type ORM interface { - FindUser(email string) (User, error) - FindUserByAPIToken(apiToken string) (User, error) - ListUsers() ([]User, error) - AuthorizedUserWithSession(sessionID string) (User, error) - DeleteUser(email string) error - DeleteUserSession(sessionID string) error - CreateSession(sr SessionRequest) (string, error) - ClearNonCurrentSessions(sessionID string) error - CreateUser(user *User) error - UpdateRole(email, newRole string) (User, error) - SetAuthToken(user *User, token *auth.Token) error - CreateAndSetAuthToken(user *User) (*auth.Token, error) - DeleteAuthToken(user *User) error - SetPassword(user *User, newPassword string) error - Sessions(offset, limit int) ([]Session, error) - GetUserWebAuthn(email string) ([]WebAuthn, error) - SaveWebAuthn(token *WebAuthn) error - - FindExternalInitiator(eia *auth.Token) (initiator *bridges.ExternalInitiator, err error) -} - type orm struct { q pg.Q sessionDuration time.Duration @@ -49,38 +26,40 @@ type orm struct { auditLogger audit.AuditLogger } -var _ ORM = (*orm)(nil) +// orm implements sessions.AuthenticationProvider and sessions.BasicAdminUsersORM interfaces +var _ sessions.AuthenticationProvider = (*orm)(nil) +var _ sessions.BasicAdminUsersORM = (*orm)(nil) -func NewORM(db *sqlx.DB, sd time.Duration, lggr logger.Logger, cfg pg.QConfig, auditLogger audit.AuditLogger) ORM { - lggr = lggr.Named("SessionsORM") +func NewORM(db *sqlx.DB, sd time.Duration, lggr logger.Logger, cfg pg.QConfig, auditLogger audit.AuditLogger) sessions.AuthenticationProvider { + namedLogger := lggr.Named("LocalAuthAuthenticationProviderORM") return &orm{ - q: pg.NewQ(db, lggr, cfg), + q: pg.NewQ(db, namedLogger, cfg), sessionDuration: sd, - lggr: lggr, + lggr: lggr.Named("LocalAuthAuthenticationProviderORM"), auditLogger: auditLogger, } } // FindUser will attempt to return an API user by email. -func (o *orm) FindUser(email string) (User, error) { +func (o *orm) FindUser(email string) (sessions.User, error) { return o.findUser(email) } // FindUserByAPIToken will attempt to return an API user via the user's table token_key column. -func (o *orm) FindUserByAPIToken(apiToken string) (user User, err error) { +func (o *orm) FindUserByAPIToken(apiToken string) (user sessions.User, err error) { sql := "SELECT * FROM users WHERE token_key = $1" err = o.q.Get(&user, sql, apiToken) return } -func (o *orm) findUser(email string) (user User, err error) { +func (o *orm) findUser(email string) (user sessions.User, err error) { sql := "SELECT * FROM users WHERE lower(email) = lower($1)" err = o.q.Get(&user, sql, email) return } // ListUsers will load and return all user rows from the db. -func (o *orm) ListUsers() (users []User, err error) { +func (o *orm) ListUsers() (users []sessions.User, err error) { sql := "SELECT * FROM users ORDER BY email ASC;" err = o.q.Select(&users, sql) return @@ -100,31 +79,27 @@ func (o *orm) updateSessionLastUsed(sessionID string) error { return o.q.ExecQ("UPDATE sessions SET last_used = now() WHERE id = $1", sessionID) } -// ErrUserSessionExpired defines the error triggered when the user session has expired -var ( - ErrUserSessionExpired = errors.New("user session missing or expired, please login again") - ErrEmptySessionID = errors.New("session ID cannot be empty") -) - // AuthorizedUserWithSession will return the API user associated with the Session ID if it // exists and hasn't expired, and update session's LastUsed field. -func (o *orm) AuthorizedUserWithSession(sessionID string) (user User, err error) { +// AuthorizedUserWithSession will return the API user associated with the Session ID if it +// exists and hasn't expired, and update session's LastUsed field. +func (o *orm) AuthorizedUserWithSession(sessionID string) (user sessions.User, err error) { if len(sessionID) == 0 { - return User{}, ErrEmptySessionID + return sessions.User{}, sessions.ErrEmptySessionID } email, err := o.findValidSession(sessionID) if err != nil { - return User{}, ErrUserSessionExpired + return sessions.User{}, sessions.ErrUserSessionExpired } user, err = o.findUser(email) if err != nil { - return User{}, ErrUserSessionExpired + return sessions.User{}, sessions.ErrUserSessionExpired } if err := o.updateSessionLastUsed(sessionID); err != nil { - return User{}, err + return sessions.User{}, err } return user, nil @@ -151,8 +126,8 @@ func (o *orm) DeleteUserSession(sessionID string) error { // tokens for the user. This list must be used when logging in (for obvious reasons) but // must also be used for registration to prevent the user from enrolling the same hardware // token multiple times. -func (o *orm) GetUserWebAuthn(email string) ([]WebAuthn, error) { - var uwas []WebAuthn +func (o *orm) GetUserWebAuthn(email string) ([]sessions.WebAuthn, error) { + var uwas []sessions.WebAuthn err := o.q.Select(&uwas, "SELECT email, public_key_data FROM web_authns WHERE LOWER(email) = $1", strings.ToLower(email)) if err != nil { return uwas, err @@ -165,7 +140,7 @@ func (o *orm) GetUserWebAuthn(email string) ([]WebAuthn, error) { // CreateSession will check the password in the SessionRequest against // the hashed API User password in the db. Also will check WebAuthn if it's // enabled for that user. -func (o *orm) CreateSession(sr SessionRequest) (string, error) { +func (o *orm) CreateSession(sr sessions.SessionRequest) (string, error) { user, err := o.FindUser(sr.Email) if err != nil { return "", err @@ -196,7 +171,7 @@ func (o *orm) CreateSession(sr SessionRequest) (string, error) { // No webauthn tokens registered for the current user, so normal authentication is now complete if len(uwas) == 0 { lggr.Infof("No MFA for user. Creating Session") - session := NewSession() + session := sessions.NewSession() _, err = o.q.Exec("INSERT INTO sessions (id, email, last_used, created_at) VALUES ($1, $2, now(), now())", session.ID, user.Email) o.auditLogger.Audit(audit.AuthLoginSuccessNo2FA, map[string]interface{}{"email": sr.Email}) return session.ID, err @@ -207,7 +182,7 @@ func (o *orm) CreateSession(sr SessionRequest) (string, error) { // data in the next round trip request (tap key to include webauthn data on the login page) if sr.WebAuthnData == "" { lggr.Warnf("Attempted login to MFA user. Generating challenge for user.") - options, webauthnError := BeginWebAuthnLogin(user, uwas, sr) + options, webauthnError := sessions.BeginWebAuthnLogin(user, uwas, sr) if webauthnError != nil { lggr.Errorf("Could not begin WebAuthn verification: %v", webauthnError) return "", errors.New("MFA Error") @@ -225,7 +200,7 @@ func (o *orm) CreateSession(sr SessionRequest) (string, error) { // The user is at the final stage of logging in with MFA. We have an // attestation back from the user, we now need to verify that it is // correct. - err = FinishWebAuthnLogin(user, uwas, sr) + err = sessions.FinishWebAuthnLogin(user, uwas, sr) if err != nil { // The user does have WebAuthn enabled but failed the check @@ -236,7 +211,7 @@ func (o *orm) CreateSession(sr SessionRequest) (string, error) { lggr.Infof("User passed MFA authentication and login will proceed") // This is a success so we can create the sessions - session := NewSession() + session := sessions.NewSession() _, err = o.q.Exec("INSERT INTO sessions (id, email, last_used, created_at) VALUES ($1, $2, now(), now())", session.ID, user.Email) if err != nil { return "", err @@ -271,14 +246,14 @@ func (o *orm) ClearNonCurrentSessions(sessionID string) error { } // CreateUser creates a new API user -func (o *orm) CreateUser(user *User) error { +func (o *orm) CreateUser(user *sessions.User) error { sql := "INSERT INTO users (email, hashed_password, role, created_at, updated_at) VALUES ($1, $2, $3, now(), now()) RETURNING *" return o.q.Get(user, sql, strings.ToLower(user.Email), user.HashedPassword, user.Role) } // UpdateRole overwrites role field of the user specified by email. -func (o *orm) UpdateRole(email, newRole string) (User, error) { - var userToEdit User +func (o *orm) UpdateRole(email, newRole string) (sessions.User, error) { + var userToEdit sessions.User if newRole == "" { return userToEdit, errors.New("user role must be specified") @@ -291,7 +266,7 @@ func (o *orm) UpdateRole(email, newRole string) (User, error) { } // Patch validated role - userRole, err := GetUserRole(newRole) + userRole, err := sessions.GetUserRole(newRole) if err != nil { return err } @@ -316,7 +291,7 @@ func (o *orm) UpdateRole(email, newRole string) (User, error) { } // SetAuthToken updates the user to use the given Authentication Token. -func (o *orm) SetPassword(user *User, newPassword string) error { +func (o *orm) SetPassword(user *sessions.User, newPassword string) error { hashedPassword, err := utils.HashPassword(newPassword) if err != nil { return err @@ -325,7 +300,19 @@ func (o *orm) SetPassword(user *User, newPassword string) error { return o.q.Get(user, sql, hashedPassword, user.Email) } -func (o *orm) CreateAndSetAuthToken(user *User) (*auth.Token, error) { +// TestPassword checks plaintext user provided password with hashed database password, returns nil if matched +func (o *orm) TestPassword(email string, password string) error { + var hashedPassword string + if err := o.q.Get(&hashedPassword, "SELECT hashed_password FROM users WHERE lower(email) = lower($1)", email); err != nil { + return errors.New("no matching user for provided email") + } + if !utils.CheckPasswordHash(password, hashedPassword) { + return errors.New("passwords don't match") + } + return nil +} + +func (o *orm) CreateAndSetAuthToken(user *sessions.User) (*auth.Token, error) { newToken := auth.NewToken() err := o.SetAuthToken(user, newToken) @@ -337,7 +324,7 @@ func (o *orm) CreateAndSetAuthToken(user *User) (*auth.Token, error) { } // SetAuthToken updates the user to use the given Authentication Token. -func (o *orm) SetAuthToken(user *User, token *auth.Token) error { +func (o *orm) SetAuthToken(user *sessions.User, token *auth.Token) error { salt := utils.NewSecret(utils.DefaultSecretSize) hashedSecret, err := auth.HashedSecret(token, salt) if err != nil { @@ -348,20 +335,20 @@ func (o *orm) SetAuthToken(user *User, token *auth.Token) error { } // DeleteAuthToken clears and disables the users Authentication Token. -func (o *orm) DeleteAuthToken(user *User) error { +func (o *orm) DeleteAuthToken(user *sessions.User) error { sql := "UPDATE users SET token_salt = '', token_key = '', token_hashed_secret = '', updated_at = now() WHERE email = $1 RETURNING *" return o.q.Get(user, sql, user.Email) } // SaveWebAuthn saves new WebAuthn token information. -func (o *orm) SaveWebAuthn(token *WebAuthn) error { +func (o *orm) SaveWebAuthn(token *sessions.WebAuthn) error { sql := "INSERT INTO web_authns (email, public_key_data) VALUES ($1, $2)" _, err := o.q.Exec(sql, token.Email, token.PublicKeyData) return err } // Sessions returns all sessions limited by the parameters. -func (o *orm) Sessions(offset, limit int) (sessions []Session, err error) { +func (o *orm) Sessions(offset, limit int) (sessions []sessions.Session, err error) { sql := `SELECT * FROM sessions ORDER BY created_at, id LIMIT $1 OFFSET $2;` if err = o.q.Select(&sessions, sql, limit, offset); err != nil { return diff --git a/core/sessions/orm_test.go b/core/sessions/localauth/orm_test.go similarity index 94% rename from core/sessions/orm_test.go rename to core/sessions/localauth/orm_test.go index 5decb823086..c2e155de282 100644 --- a/core/sessions/orm_test.go +++ b/core/sessions/localauth/orm_test.go @@ -1,4 +1,4 @@ -package sessions_test +package localauth_test import ( "encoding/json" @@ -7,10 +7,11 @@ import ( "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" @@ -18,14 +19,15 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/sessions" + "github.com/smartcontractkit/chainlink/v2/core/sessions/localauth" "github.com/smartcontractkit/chainlink/v2/core/utils" ) -func setupORM(t *testing.T) (*sqlx.DB, sessions.ORM) { +func setupORM(t *testing.T) (*sqlx.DB, sessions.AuthenticationProvider) { t.Helper() db := pgtest.NewSqlxDB(t) - orm := sessions.NewORM(db, time.Minute, logger.TestLogger(t), pgtest.NewQConfig(true), &audit.AuditLoggerService{}) + orm := localauth.NewORM(db, time.Minute, logger.TestLogger(t), pgtest.NewQConfig(true), &audit.AuditLoggerService{}) return db, orm } @@ -66,7 +68,7 @@ func TestORM_AuthorizedUserWithSession(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { db := pgtest.NewSqlxDB(t) - orm := sessions.NewORM(db, test.sessionDuration, logger.TestLogger(t), pgtest.NewQConfig(true), &audit.AuditLoggerService{}) + orm := localauth.NewORM(db, test.sessionDuration, logger.TestLogger(t), pgtest.NewQConfig(true), &audit.AuditLoggerService{}) user := cltest.MustRandomUser(t) require.NoError(t, orm.CreateUser(&user)) diff --git a/core/sessions/reaper.go b/core/sessions/localauth/reaper.go similarity index 98% rename from core/sessions/reaper.go rename to core/sessions/localauth/reaper.go index c4f0ed6796c..77d1b1abef2 100644 --- a/core/sessions/reaper.go +++ b/core/sessions/localauth/reaper.go @@ -1,4 +1,4 @@ -package sessions +package localauth import ( "database/sql" diff --git a/core/sessions/reaper_test.go b/core/sessions/localauth/reaper_test.go similarity index 69% rename from core/sessions/reaper_test.go rename to core/sessions/localauth/reaper_test.go index a96c3822ef5..43a263d0321 100644 --- a/core/sessions/reaper_test.go +++ b/core/sessions/localauth/reaper_test.go @@ -1,4 +1,4 @@ -package sessions_test +package localauth_test import ( "testing" @@ -9,8 +9,10 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/sessions" + "github.com/smartcontractkit/chainlink/v2/core/sessions/localauth" "github.com/smartcontractkit/chainlink/v2/core/store/models" + "github.com/onsi/gomega" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -31,10 +33,9 @@ func TestSessionReaper_ReapSessions(t *testing.T) { db := pgtest.NewSqlxDB(t) config := sessionReaperConfig{} lggr := logger.TestLogger(t) - orm := sessions.NewORM(db, config.SessionTimeout().Duration(), lggr, pgtest.NewQConfig(true), audit.NoopLogger) - - r := sessions.NewSessionReaper(db.DB, config, lggr) + orm := localauth.NewORM(db, config.SessionTimeout().Duration(), lggr, pgtest.NewQConfig(true), audit.NoopLogger) + r := localauth.NewSessionReaper(db.DB, config, lggr) t.Cleanup(func() { assert.NoError(t, r.Stop()) }) @@ -53,31 +54,28 @@ func TestSessionReaper_ReapSessions(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - user := cltest.MustRandomUser(t) - require.NoError(t, orm.CreateUser(&user)) - - session := sessions.NewSession() - session.Email = user.Email - - _, err := db.Exec("INSERT INTO sessions (last_used, email, id, created_at) VALUES ($1, $2, $3, now())", test.lastUsed, user.Email, test.name) - require.NoError(t, err) - t.Cleanup(func() { - _, err2 := db.Exec("DELETE FROM sessions where email = $1", user.Email) + _, err2 := db.Exec("DELETE FROM sessions where email = $1", cltest.APIEmailAdmin) require.NoError(t, err2) }) + _, err := db.Exec("INSERT INTO sessions (last_used, email, id, created_at) VALUES ($1, $2, $3, now())", test.lastUsed, cltest.APIEmailAdmin, test.name) + require.NoError(t, err) + r.WakeUp() - <-r.(interface { - WorkDone() <-chan struct{} - }).WorkDone() - sessions, err := orm.Sessions(0, 10) - assert.NoError(t, err) if test.wantReap { - assert.Len(t, sessions, 0) + gomega.NewWithT(t).Eventually(func() []sessions.Session { + sessions, err := orm.Sessions(0, 10) + assert.NoError(t, err) + return sessions + }).Should(gomega.HaveLen(0)) } else { - assert.Len(t, sessions, 1) + gomega.NewWithT(t).Consistently(func() []sessions.Session { + sessions, err := orm.Sessions(0, 10) + assert.NoError(t, err) + return sessions + }).Should(gomega.HaveLen(1)) } }) } diff --git a/core/sessions/mocks/orm.go b/core/sessions/mocks/authentication_provider.go similarity index 75% rename from core/sessions/mocks/orm.go rename to core/sessions/mocks/authentication_provider.go index 5699b9f8892..d6e33d11e45 100644 --- a/core/sessions/mocks/orm.go +++ b/core/sessions/mocks/authentication_provider.go @@ -11,13 +11,13 @@ import ( sessions "github.com/smartcontractkit/chainlink/v2/core/sessions" ) -// ORM is an autogenerated mock type for the ORM type -type ORM struct { +// AuthenticationProvider is an autogenerated mock type for the AuthenticationProvider type +type AuthenticationProvider struct { mock.Mock } // AuthorizedUserWithSession provides a mock function with given fields: sessionID -func (_m *ORM) AuthorizedUserWithSession(sessionID string) (sessions.User, error) { +func (_m *AuthenticationProvider) AuthorizedUserWithSession(sessionID string) (sessions.User, error) { ret := _m.Called(sessionID) var r0 sessions.User @@ -41,7 +41,7 @@ func (_m *ORM) AuthorizedUserWithSession(sessionID string) (sessions.User, error } // ClearNonCurrentSessions provides a mock function with given fields: sessionID -func (_m *ORM) ClearNonCurrentSessions(sessionID string) error { +func (_m *AuthenticationProvider) ClearNonCurrentSessions(sessionID string) error { ret := _m.Called(sessionID) var r0 error @@ -55,7 +55,7 @@ func (_m *ORM) ClearNonCurrentSessions(sessionID string) error { } // CreateAndSetAuthToken provides a mock function with given fields: user -func (_m *ORM) CreateAndSetAuthToken(user *sessions.User) (*auth.Token, error) { +func (_m *AuthenticationProvider) CreateAndSetAuthToken(user *sessions.User) (*auth.Token, error) { ret := _m.Called(user) var r0 *auth.Token @@ -81,7 +81,7 @@ func (_m *ORM) CreateAndSetAuthToken(user *sessions.User) (*auth.Token, error) { } // CreateSession provides a mock function with given fields: sr -func (_m *ORM) CreateSession(sr sessions.SessionRequest) (string, error) { +func (_m *AuthenticationProvider) CreateSession(sr sessions.SessionRequest) (string, error) { ret := _m.Called(sr) var r0 string @@ -105,7 +105,7 @@ func (_m *ORM) CreateSession(sr sessions.SessionRequest) (string, error) { } // CreateUser provides a mock function with given fields: user -func (_m *ORM) CreateUser(user *sessions.User) error { +func (_m *AuthenticationProvider) CreateUser(user *sessions.User) error { ret := _m.Called(user) var r0 error @@ -119,7 +119,7 @@ func (_m *ORM) CreateUser(user *sessions.User) error { } // DeleteAuthToken provides a mock function with given fields: user -func (_m *ORM) DeleteAuthToken(user *sessions.User) error { +func (_m *AuthenticationProvider) DeleteAuthToken(user *sessions.User) error { ret := _m.Called(user) var r0 error @@ -133,7 +133,7 @@ func (_m *ORM) DeleteAuthToken(user *sessions.User) error { } // DeleteUser provides a mock function with given fields: email -func (_m *ORM) DeleteUser(email string) error { +func (_m *AuthenticationProvider) DeleteUser(email string) error { ret := _m.Called(email) var r0 error @@ -147,7 +147,7 @@ func (_m *ORM) DeleteUser(email string) error { } // DeleteUserSession provides a mock function with given fields: sessionID -func (_m *ORM) DeleteUserSession(sessionID string) error { +func (_m *AuthenticationProvider) DeleteUserSession(sessionID string) error { ret := _m.Called(sessionID) var r0 error @@ -161,7 +161,7 @@ func (_m *ORM) DeleteUserSession(sessionID string) error { } // FindExternalInitiator provides a mock function with given fields: eia -func (_m *ORM) FindExternalInitiator(eia *auth.Token) (*bridges.ExternalInitiator, error) { +func (_m *AuthenticationProvider) FindExternalInitiator(eia *auth.Token) (*bridges.ExternalInitiator, error) { ret := _m.Called(eia) var r0 *bridges.ExternalInitiator @@ -187,7 +187,7 @@ func (_m *ORM) FindExternalInitiator(eia *auth.Token) (*bridges.ExternalInitiato } // FindUser provides a mock function with given fields: email -func (_m *ORM) FindUser(email string) (sessions.User, error) { +func (_m *AuthenticationProvider) FindUser(email string) (sessions.User, error) { ret := _m.Called(email) var r0 sessions.User @@ -211,7 +211,7 @@ func (_m *ORM) FindUser(email string) (sessions.User, error) { } // FindUserByAPIToken provides a mock function with given fields: apiToken -func (_m *ORM) FindUserByAPIToken(apiToken string) (sessions.User, error) { +func (_m *AuthenticationProvider) FindUserByAPIToken(apiToken string) (sessions.User, error) { ret := _m.Called(apiToken) var r0 sessions.User @@ -235,7 +235,7 @@ func (_m *ORM) FindUserByAPIToken(apiToken string) (sessions.User, error) { } // GetUserWebAuthn provides a mock function with given fields: email -func (_m *ORM) GetUserWebAuthn(email string) ([]sessions.WebAuthn, error) { +func (_m *AuthenticationProvider) GetUserWebAuthn(email string) ([]sessions.WebAuthn, error) { ret := _m.Called(email) var r0 []sessions.WebAuthn @@ -261,7 +261,7 @@ func (_m *ORM) GetUserWebAuthn(email string) ([]sessions.WebAuthn, error) { } // ListUsers provides a mock function with given fields: -func (_m *ORM) ListUsers() ([]sessions.User, error) { +func (_m *AuthenticationProvider) ListUsers() ([]sessions.User, error) { ret := _m.Called() var r0 []sessions.User @@ -287,7 +287,7 @@ func (_m *ORM) ListUsers() ([]sessions.User, error) { } // SaveWebAuthn provides a mock function with given fields: token -func (_m *ORM) SaveWebAuthn(token *sessions.WebAuthn) error { +func (_m *AuthenticationProvider) SaveWebAuthn(token *sessions.WebAuthn) error { ret := _m.Called(token) var r0 error @@ -301,7 +301,7 @@ func (_m *ORM) SaveWebAuthn(token *sessions.WebAuthn) error { } // Sessions provides a mock function with given fields: offset, limit -func (_m *ORM) Sessions(offset int, limit int) ([]sessions.Session, error) { +func (_m *AuthenticationProvider) Sessions(offset int, limit int) ([]sessions.Session, error) { ret := _m.Called(offset, limit) var r0 []sessions.Session @@ -327,7 +327,7 @@ func (_m *ORM) Sessions(offset int, limit int) ([]sessions.Session, error) { } // SetAuthToken provides a mock function with given fields: user, token -func (_m *ORM) SetAuthToken(user *sessions.User, token *auth.Token) error { +func (_m *AuthenticationProvider) SetAuthToken(user *sessions.User, token *auth.Token) error { ret := _m.Called(user, token) var r0 error @@ -341,7 +341,7 @@ func (_m *ORM) SetAuthToken(user *sessions.User, token *auth.Token) error { } // SetPassword provides a mock function with given fields: user, newPassword -func (_m *ORM) SetPassword(user *sessions.User, newPassword string) error { +func (_m *AuthenticationProvider) SetPassword(user *sessions.User, newPassword string) error { ret := _m.Called(user, newPassword) var r0 error @@ -354,8 +354,22 @@ func (_m *ORM) SetPassword(user *sessions.User, newPassword string) error { return r0 } +// TestPassword provides a mock function with given fields: email, password +func (_m *AuthenticationProvider) TestPassword(email string, password string) error { + ret := _m.Called(email, password) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(email, password) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // UpdateRole provides a mock function with given fields: email, newRole -func (_m *ORM) UpdateRole(email string, newRole string) (sessions.User, error) { +func (_m *AuthenticationProvider) UpdateRole(email string, newRole string) (sessions.User, error) { ret := _m.Called(email, newRole) var r0 sessions.User @@ -378,13 +392,13 @@ func (_m *ORM) UpdateRole(email string, newRole string) (sessions.User, error) { return r0, r1 } -// NewORM creates a new instance of ORM. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewAuthenticationProvider creates a new instance of AuthenticationProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewORM(t interface { +func NewAuthenticationProvider(t interface { mock.TestingT Cleanup(func()) -}) *ORM { - mock := &ORM{} +}) *AuthenticationProvider { + mock := &AuthenticationProvider{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/core/sessions/mocks/basic_admin_users_orm.go b/core/sessions/mocks/basic_admin_users_orm.go new file mode 100644 index 00000000000..845e2d8880e --- /dev/null +++ b/core/sessions/mocks/basic_admin_users_orm.go @@ -0,0 +1,91 @@ +// Code generated by mockery v2.35.4. DO NOT EDIT. + +package mocks + +import ( + sessions "github.com/smartcontractkit/chainlink/v2/core/sessions" + mock "github.com/stretchr/testify/mock" +) + +// BasicAdminUsersORM is an autogenerated mock type for the BasicAdminUsersORM type +type BasicAdminUsersORM struct { + mock.Mock +} + +// CreateUser provides a mock function with given fields: user +func (_m *BasicAdminUsersORM) CreateUser(user *sessions.User) error { + ret := _m.Called(user) + + var r0 error + if rf, ok := ret.Get(0).(func(*sessions.User) error); ok { + r0 = rf(user) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// FindUser provides a mock function with given fields: email +func (_m *BasicAdminUsersORM) FindUser(email string) (sessions.User, error) { + ret := _m.Called(email) + + var r0 sessions.User + var r1 error + if rf, ok := ret.Get(0).(func(string) (sessions.User, error)); ok { + return rf(email) + } + if rf, ok := ret.Get(0).(func(string) sessions.User); ok { + r0 = rf(email) + } else { + r0 = ret.Get(0).(sessions.User) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(email) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListUsers provides a mock function with given fields: +func (_m *BasicAdminUsersORM) ListUsers() ([]sessions.User, error) { + ret := _m.Called() + + var r0 []sessions.User + var r1 error + if rf, ok := ret.Get(0).(func() ([]sessions.User, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []sessions.User); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]sessions.User) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewBasicAdminUsersORM creates a new instance of BasicAdminUsersORM. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewBasicAdminUsersORM(t interface { + mock.TestingT + Cleanup(func()) +}) *BasicAdminUsersORM { + mock := &BasicAdminUsersORM{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/sessions/session.go b/core/sessions/session.go new file mode 100644 index 00000000000..90964596e9a --- /dev/null +++ b/core/sessions/session.go @@ -0,0 +1,74 @@ +package sessions + +import ( + "crypto/subtle" + "time" + + "github.com/pkg/errors" + "gopkg.in/guregu/null.v4" + + "github.com/smartcontractkit/chainlink/v2/core/auth" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +// SessionRequest encapsulates the fields needed to generate a new SessionID, +// including the hashed password. +type SessionRequest struct { + Email string `json:"email"` + Password string `json:"password"` + WebAuthnData string `json:"webauthndata"` + WebAuthnConfig WebAuthnConfiguration + SessionStore *WebAuthnSessionStore +} + +// Session holds the unique id for the authenticated session. +type Session struct { + ID string `json:"id"` + Email string `json:"email"` + LastUsed time.Time `json:"lastUsed"` + CreatedAt time.Time `json:"createdAt"` +} + +// NewSession returns a session instance with ID set to a random ID and +// LastUsed to now. +func NewSession() Session { + return Session{ + ID: utils.NewBytes32ID(), + LastUsed: time.Now(), + } +} + +// Changeauth.TokenRequest is sent when updating a User's authentication token. +type ChangeAuthTokenRequest struct { + Password string `json:"password"` +} + +// GenerateAuthToken randomly generates and sets the users Authentication +// Token. +func (u *User) GenerateAuthToken() (*auth.Token, error) { + token := auth.NewToken() + return token, u.SetAuthToken(token) +} + +// SetAuthToken updates the user to use the given Authentication Token. +func (u *User) SetAuthToken(token *auth.Token) error { + salt := utils.NewSecret(utils.DefaultSecretSize) + hashedSecret, err := auth.HashedSecret(token, salt) + if err != nil { + return errors.Wrap(err, "user") + } + u.TokenSalt = null.StringFrom(salt) + u.TokenKey = null.StringFrom(token.AccessKey) + u.TokenHashedSecret = null.StringFrom(hashedSecret) + return nil +} + +// AuthenticateUserByToken returns true on successful authentication of the +// user against the given Authentication Token. +func AuthenticateUserByToken(token *auth.Token, user *User) (bool, error) { + hashedSecret, err := auth.HashedSecret(token, user.TokenSalt.ValueOrZero()) + if err != nil { + return false, err + } + return subtle.ConstantTimeCompare([]byte(hashedSecret), []byte(user.TokenHashedSecret.ValueOrZero())) == 1, nil +} diff --git a/core/sessions/user.go b/core/sessions/user.go index a1208744323..f2e4827b922 100644 --- a/core/sessions/user.go +++ b/core/sessions/user.go @@ -1,7 +1,6 @@ package sessions import ( - "crypto/subtle" "fmt" "net/mail" "time" @@ -9,7 +8,6 @@ import ( "github.com/pkg/errors" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -108,65 +106,3 @@ func GetUserRole(role string) (UserRole, error) { ) return UserRole(""), errors.New(errStr) } - -// SessionRequest encapsulates the fields needed to generate a new SessionID, -// including the hashed password. -type SessionRequest struct { - Email string `json:"email"` - Password string `json:"password"` - WebAuthnData string `json:"webauthndata"` - WebAuthnConfig WebAuthnConfiguration - SessionStore *WebAuthnSessionStore -} - -// Session holds the unique id for the authenticated session. -type Session struct { - ID string `json:"id"` - Email string `json:"email"` - LastUsed time.Time `json:"lastUsed"` - CreatedAt time.Time `json:"createdAt"` -} - -// NewSession returns a session instance with ID set to a random ID and -// LastUsed to now. -func NewSession() Session { - return Session{ - ID: utils.NewBytes32ID(), - LastUsed: time.Now(), - } -} - -// Changeauth.TokenRequest is sent when updating a User's authentication token. -type ChangeAuthTokenRequest struct { - Password string `json:"password"` -} - -// GenerateAuthToken randomly generates and sets the users Authentication -// Token. -func (u *User) GenerateAuthToken() (*auth.Token, error) { - token := auth.NewToken() - return token, u.SetAuthToken(token) -} - -// SetAuthToken updates the user to use the given Authentication Token. -func (u *User) SetAuthToken(token *auth.Token) error { - salt := utils.NewSecret(utils.DefaultSecretSize) - hashedSecret, err := auth.HashedSecret(token, salt) - if err != nil { - return errors.Wrap(err, "user") - } - u.TokenSalt = null.StringFrom(salt) - u.TokenKey = null.StringFrom(token.AccessKey) - u.TokenHashedSecret = null.StringFrom(hashedSecret) - return nil -} - -// AuthenticateUserByToken returns true on successful authentication of the -// user against the given Authentication Token. -func AuthenticateUserByToken(token *auth.Token, user *User) (bool, error) { - hashedSecret, err := auth.HashedSecret(token, user.TokenSalt.ValueOrZero()) - if err != nil { - return false, err - } - return subtle.ConstantTimeCompare([]byte(hashedSecret), []byte(user.TokenHashedSecret.ValueOrZero())) == 1, nil -} diff --git a/core/sessions/webauthn.go b/core/sessions/webauthn.go index 0dd8242dc8a..89e7758bc5b 100644 --- a/core/sessions/webauthn.go +++ b/core/sessions/webauthn.go @@ -9,8 +9,8 @@ import ( "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" + sqlxTypes "github.com/jmoiron/sqlx/types" "github.com/pkg/errors" - sqlxTypes "github.com/smartcontractkit/sqlx/types" ) // WebAuthn holds the credentials for API user. @@ -279,7 +279,7 @@ func (store *WebAuthnSessionStore) GetWebauthnSession(key string) (data webauthn return } -func AddCredentialToUser(o ORM, email string, credential *webauthn.Credential) error { +func AddCredentialToUser(ap AuthenticationProvider, email string, credential *webauthn.Credential) error { credj, err := json.Marshal(credential) if err != nil { return err @@ -289,5 +289,5 @@ func AddCredentialToUser(o ORM, email string, credential *webauthn.Credential) e Email: email, PublicKeyData: sqlxTypes.JSONText(credj), } - return o.SaveWebAuthn(&token) + return ap.SaveWebAuthn(&token) } diff --git a/core/sessions/webauthn_test.go b/core/sessions/webauthn_test.go index b3c1ecf7897..9c055d9c794 100644 --- a/core/sessions/webauthn_test.go +++ b/core/sessions/webauthn_test.go @@ -7,7 +7,7 @@ import ( "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" - sqlxTypes "github.com/smartcontractkit/sqlx/types" + sqlxTypes "github.com/jmoiron/sqlx/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/core/store/migrate/migrate.go b/core/store/migrate/migrate.go index 69cf9a78247..04da4c48e92 100644 --- a/core/store/migrate/migrate.go +++ b/core/store/migrate/migrate.go @@ -9,9 +9,9 @@ import ( "strconv" "strings" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" "github.com/pressly/goose/v3" - "github.com/smartcontractkit/sqlx" "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/v2/core/config/env" @@ -36,22 +36,22 @@ func init() { } // Ensure we migrated from v1 migrations to goose_migrations -func ensureMigrated(db *sql.DB, lggr logger.Logger) { +func ensureMigrated(ctx context.Context, db *sql.DB, lggr logger.Logger) error { sqlxDB := pg.WrapDbWithSqlx(db) var names []string - err := sqlxDB.Select(&names, `SELECT id FROM migrations`) + err := sqlxDB.SelectContext(ctx, &names, `SELECT id FROM migrations`) if err != nil { // already migrated - return + return nil } - err = pg.SqlTransaction(context.Background(), db, lggr, func(tx *sqlx.Tx) error { + err = pg.SqlTransaction(ctx, db, lggr, func(tx *sqlx.Tx) error { // ensure that no legacy job specs are present: we _must_ bail out early if // so because otherwise we run the risk of dropping working jobs if the // user has not read the release notes return migrations.CheckNoLegacyJobs(tx.Tx) }) if err != nil { - panic(err) + return err } // Look for the squashed migration. If not present, the db needs to be migrated on an earlier release first @@ -62,18 +62,18 @@ func ensureMigrated(db *sql.DB, lggr logger.Logger) { } } if !found { - panic("Database state is too old. Need to migrate to chainlink version 0.9.10 first before upgrading to this version. This upgrade is NOT REVERSIBLE, so it is STRONGLY RECOMMENDED that you take a database backup before continuing.") + return errors.New("database state is too old. Need to migrate to chainlink version 0.9.10 first before upgrading to this version. This upgrade is NOT REVERSIBLE, so it is STRONGLY RECOMMENDED that you take a database backup before continuing") } // ensure a goose migrations table exists with it's initial v0 if _, err = goose.GetDBVersion(db); err != nil { - panic(err) + return err } // insert records for existing migrations //nolint sql := fmt.Sprintf(`INSERT INTO %s (version_id, is_applied) VALUES ($1, true);`, goose.TableName()) - err = pg.SqlTransaction(context.Background(), db, lggr, func(tx *sqlx.Tx) error { + return pg.SqlTransaction(ctx, db, lggr, func(tx *sqlx.Tx) error { for _, name := range names { var id int64 // the first migration doesn't follow the naming convention @@ -100,33 +100,38 @@ func ensureMigrated(db *sql.DB, lggr logger.Logger) { _, err = db.Exec("DROP TABLE migrations;") return err }) - if err != nil { - panic(err) - } } -func Migrate(db *sql.DB, lggr logger.Logger) error { - ensureMigrated(db, lggr) +func Migrate(ctx context.Context, db *sql.DB, lggr logger.Logger) error { + if err := ensureMigrated(ctx, db, lggr); err != nil { + return err + } // WithAllowMissing is necessary when upgrading from 0.10.14 since it // includes out-of-order migrations return goose.Up(db, MIGRATIONS_DIR, goose.WithAllowMissing()) } -func Rollback(db *sql.DB, lggr logger.Logger, version null.Int) error { - ensureMigrated(db, lggr) +func Rollback(ctx context.Context, db *sql.DB, lggr logger.Logger, version null.Int) error { + if err := ensureMigrated(ctx, db, lggr); err != nil { + return err + } if version.Valid { return goose.DownTo(db, MIGRATIONS_DIR, version.Int64) } return goose.Down(db, MIGRATIONS_DIR) } -func Current(db *sql.DB, lggr logger.Logger) (int64, error) { - ensureMigrated(db, lggr) +func Current(ctx context.Context, db *sql.DB, lggr logger.Logger) (int64, error) { + if err := ensureMigrated(ctx, db, lggr); err != nil { + return -1, err + } return goose.EnsureDBVersion(db) } -func Status(db *sql.DB, lggr logger.Logger) error { - ensureMigrated(db, lggr) +func Status(ctx context.Context, db *sql.DB, lggr logger.Logger) error { + if err := ensureMigrated(ctx, db, lggr); err != nil { + return err + } return goose.Status(db, MIGRATIONS_DIR) } diff --git a/core/store/migrate/migrate_test.go b/core/store/migrate/migrate_test.go index 7a1e38fb030..43ddd41d56f 100644 --- a/core/store/migrate/migrate_test.go +++ b/core/store/migrate/migrate_test.go @@ -12,14 +12,14 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -73,7 +73,7 @@ func getOCR2Spec100() OffchainReporting2OracleSpec100 { } func TestMigrate_0100_BootstrapConfigs(t *testing.T) { - cfg, db := heavyweight.FullTestDBEmptyV2(t, migrationDir, nil) + cfg, db := heavyweight.FullTestDBEmptyV2(t, nil) lggr := logger.TestLogger(t) err := goose.UpTo(db.DB, migrationDir, 99) require.NoError(t, err) @@ -342,7 +342,7 @@ ON jobs.offchainreporting2_oracle_spec_id = ocr2.id` } func TestMigrate_101_GenericOCR2(t *testing.T) { - _, db := heavyweight.FullTestDBEmptyV2(t, migrationDir, nil) + _, db := heavyweight.FullTestDBEmptyV2(t, nil) err := goose.UpTo(db.DB, migrationDir, 100) require.NoError(t, err) @@ -391,25 +391,26 @@ func TestMigrate_101_GenericOCR2(t *testing.T) { } func TestMigrate(t *testing.T) { + ctx := testutils.Context(t) lggr := logger.TestLogger(t) - _, db := heavyweight.FullTestDBEmptyV2(t, migrationDir, nil) + _, db := heavyweight.FullTestDBEmptyV2(t, nil) err := goose.UpTo(db.DB, migrationDir, 100) require.NoError(t, err) - err = migrate.Status(db.DB, lggr) + err = migrate.Status(ctx, db.DB, lggr) require.NoError(t, err) - ver, err := migrate.Current(db.DB, lggr) + ver, err := migrate.Current(ctx, db.DB, lggr) require.NoError(t, err) require.Equal(t, int64(100), ver) - err = migrate.Migrate(db.DB, lggr) + err = migrate.Migrate(ctx, db.DB, lggr) require.NoError(t, err) - err = migrate.Rollback(db.DB, lggr, null.IntFrom(99)) + err = migrate.Rollback(ctx, db.DB, lggr, null.IntFrom(99)) require.NoError(t, err) - ver, err = migrate.Current(db.DB, lggr) + ver, err = migrate.Current(ctx, db.DB, lggr) require.NoError(t, err) require.Equal(t, int64(99), ver) } @@ -443,7 +444,7 @@ func TestSetMigrationENVVars(t *testing.T) { } func TestDatabaseBackFillWithMigration202(t *testing.T) { - _, db := heavyweight.FullTestDBEmptyV2(t, migrationDir, nil) + _, db := heavyweight.FullTestDBEmptyV2(t, nil) err := goose.UpTo(db.DB, migrationDir, 201) require.NoError(t, err) @@ -523,7 +524,7 @@ func BenchmarkBackfillingRecordsWithMigration202(b *testing.B) { maxLogsSize := 100_000 // Disable Goose logging for benchmarking goose.SetLogger(goose.NopLogger()) - _, db := heavyweight.FullTestDBEmptyV2(b, migrationDir, nil) + _, db := heavyweight.FullTestDBEmptyV2(b, nil) err := goose.UpTo(db.DB, migrationDir, previousMigration) require.NoError(b, err) diff --git a/core/store/migrate/migrations/0036_external_job_id.go b/core/store/migrate/migrations/0036_external_job_id.go index 82f26206d88..fc9ec08ec60 100644 --- a/core/store/migrate/migrations/0036_external_job_id.go +++ b/core/store/migrate/migrations/0036_external_job_id.go @@ -6,8 +6,8 @@ import ( "fmt" "github.com/google/uuid" + "github.com/jmoiron/sqlx" "github.com/pressly/goose/v3" - "github.com/smartcontractkit/sqlx" ) func init() { @@ -45,7 +45,7 @@ func Up36(ctx context.Context, tx *sql.Tx) error { // Update all jobs to have an external_job_id. // We do this to avoid using the uuid postgres extension. var jobIDs []int32 - txx := sqlx.NewTx(tx, "postgres") + txx := sqlx.Tx{Tx: tx} if err := txx.SelectContext(ctx, &jobIDs, "SELECT id FROM jobs"); err != nil { return err } diff --git a/core/store/migrate/migrations/0205_add_feed_id_to_mercury_transmit_requests.sql b/core/store/migrate/migrations/0205_add_feed_id_to_mercury_transmit_requests.sql new file mode 100644 index 00000000000..04cf5a2571d --- /dev/null +++ b/core/store/migrate/migrations/0205_add_feed_id_to_mercury_transmit_requests.sql @@ -0,0 +1,14 @@ +-- +goose Up +ALTER TABLE mercury_transmit_requests ADD COLUMN feed_id BYTEA CHECK (feed_id IS NULL OR octet_length(feed_id) = 32); +DROP INDEX idx_mercury_transmission_requests_epoch_round; +CREATE INDEX idx_mercury_transmission_requests_job_id_epoch_round ON mercury_transmit_requests (job_id, epoch DESC, round DESC); +CREATE INDEX idx_mercury_transmit_requests_job_id ON mercury_transmit_requests (job_id); +CREATE INDEX idx_mercury_transmit_requests_feed_id ON mercury_transmit_requests (feed_id); +CREATE INDEX idx_mercury_feed_latest_reports_job_id ON feed_latest_reports (job_id); + +-- +goose Down +ALTER TABLE mercury_transmit_requests DROP COLUMN feed_id; +DROP INDEX idx_mercury_transmit_requests_job_id; +DROP INDEX idx_mercury_feed_latest_reports_job_id; +CREATE INDEX idx_mercury_transmission_requests_epoch_round ON mercury_transmit_requests (epoch DESC, round DESC); +DROP INDEX idx_mercury_transmission_requests_job_id_epoch_round; diff --git a/core/store/migrate/migrations/0206_remove_tx_insert_trigger.sql b/core/store/migrate/migrations/0206_remove_tx_insert_trigger.sql new file mode 100644 index 00000000000..94b2e4aa8a6 --- /dev/null +++ b/core/store/migrate/migrations/0206_remove_tx_insert_trigger.sql @@ -0,0 +1,18 @@ +-- +goose Up +DROP TRIGGER IF EXISTS notify_tx_insertion on evm.txes; +DROP FUNCTION IF EXISTS evm.notifyethtxinsertion(); + + +-- +goose Down +-- +goose StatementBegin +CREATE OR REPLACE FUNCTION evm.notifytxinsertion() RETURNS trigger + LANGUAGE plpgsql + AS $$ + BEGIN + PERFORM pg_notify('evm.insert_on_txes'::text, encode(NEW.from_address, 'hex')); + RETURN NULL; + END + $$; + +CREATE TRIGGER notify_tx_insertion AFTER INSERT ON evm.txes FOR EACH ROW EXECUTE PROCEDURE evm.notifytxinsertion(); +-- +goose StatementEnd \ No newline at end of file diff --git a/core/store/migrate/migrations/0207_drop_insert_on_terra_msg.sql b/core/store/migrate/migrations/0207_drop_insert_on_terra_msg.sql new file mode 100644 index 00000000000..f4ae4b98e2c --- /dev/null +++ b/core/store/migrate/migrations/0207_drop_insert_on_terra_msg.sql @@ -0,0 +1,20 @@ +-- +goose Up + +-- +goose StatementBegin +DROP TRIGGER IF EXISTS insert_on_terra_msg ON PUBLIC.cosmos_msgs; +DROP FUNCTION IF EXISTS PUBLIC.notify_terra_msg_insert; +-- +goose StatementEnd + +-- +goose Down + +-- +goose StatementBegin +CREATE FUNCTION notify_terra_msg_insert() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + PERFORM pg_notify('insert_on_terra_msg'::text, NOW()::text); + RETURN NULL; +END +$$; +CREATE TRIGGER notify_terra_msg_insertion AFTER INSERT ON cosmos_msgs FOR EACH STATEMENT EXECUTE PROCEDURE notify_terra_msg_insert(); +-- +goose StatementEnd diff --git a/core/store/migrate/migrations/0208_create_ldap_sessions_table.sql b/core/store/migrate/migrations/0208_create_ldap_sessions_table.sql new file mode 100644 index 00000000000..f788cdab076 --- /dev/null +++ b/core/store/migrate/migrations/0208_create_ldap_sessions_table.sql @@ -0,0 +1,22 @@ +-- +goose Up +CREATE TABLE IF NOT EXISTS ldap_sessions ( + id text PRIMARY KEY, + user_email text NOT NULL, + user_role user_roles, + localauth_user BOOLEAN, + created_at timestamp with time zone NOT NULL +); + +CREATE TABLE IF NOT EXISTS ldap_user_api_tokens ( + user_email text PRIMARY KEY, + user_role user_roles, + localauth_user BOOLEAN, + token_key text UNIQUE NOT NULL, + token_salt text NOT NULL, + token_hashed_secret text NOT NULL, + created_at timestamp with time zone NOT NULL +); + +-- +goose Down +DROP TABLE ldap_sessions; +DROP TABLE ldap_user_api_tokens; diff --git a/core/store/migrate/migrations/0209_add_resume_pipeline_task_flags_to_evm_txes.sql b/core/store/migrate/migrations/0209_add_resume_pipeline_task_flags_to_evm_txes.sql new file mode 100644 index 00000000000..dbe7e91b9f6 --- /dev/null +++ b/core/store/migrate/migrations/0209_add_resume_pipeline_task_flags_to_evm_txes.sql @@ -0,0 +1,15 @@ +-- +goose Up +ALTER TABLE evm.txes ADD COLUMN "signal_callback" BOOL DEFAULT FALSE; +ALTER TABLE evm.txes ADD COLUMN "callback_completed" BOOL DEFAULT FALSE; + +UPDATE evm.txes +SET signal_callback = TRUE AND callback_completed = FALSE +WHERE evm.txes.pipeline_task_run_id IN ( + SELECT pipeline_task_runs.id FROM pipeline_task_runs + INNER JOIN pipeline_runs ON pipeline_runs.id = pipeline_task_runs.pipeline_run_id + WHERE pipeline_runs.state = 'suspended' +); + +-- +goose Down +ALTER TABLE evm.txes DROP COLUMN "signal_callback"; +ALTER TABLE evm.txes DROP COLUMN "callback_completed"; diff --git a/core/store/migrate/migrations/0210_remove_evm_key_states_fk_constraint.sql b/core/store/migrate/migrations/0210_remove_evm_key_states_fk_constraint.sql new file mode 100644 index 00000000000..119de9d260e --- /dev/null +++ b/core/store/migrate/migrations/0210_remove_evm_key_states_fk_constraint.sql @@ -0,0 +1,4 @@ +-- +goose Up +ALTER TABLE evm.txes DROP CONSTRAINT eth_txes_evm_chain_id_from_address_fkey; +-- +goose Down +ALTER TABLE evm.txes ADD CONSTRAINT eth_txes_evm_chain_id_from_address_fkey FOREIGN KEY (evm_chain_id, from_address) REFERENCES evm.key_states(evm_chain_id, address) ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE NOT VALID; \ No newline at end of file diff --git a/core/store/migrate/migrations/0211_log_poller_word_indexes.sql b/core/store/migrate/migrations/0211_log_poller_word_indexes.sql new file mode 100644 index 00000000000..3d2e8bf8a4e --- /dev/null +++ b/core/store/migrate/migrations/0211_log_poller_word_indexes.sql @@ -0,0 +1,6 @@ +-- +goose Up +CREATE INDEX evm_logs_idx_data_word_four ON evm.logs (substring(data from 97 for 32)); + + +-- +goose Down +DROP INDEX IF EXISTS evm.evm_logs_idx_data_word_four; diff --git a/core/store/models/common.go b/core/store/models/common.go index fc7f7762046..10f391861e1 100644 --- a/core/store/models/common.go +++ b/core/store/models/common.go @@ -17,7 +17,7 @@ import ( "github.com/tidwall/gjson" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/testdata/testspecs/v2_specs.go b/core/testdata/testspecs/v2_specs.go index beda7354566..0ecb85f1e49 100644 --- a/core/testdata/testspecs/v2_specs.go +++ b/core/testdata/testspecs/v2_specs.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" "github.com/smartcontractkit/chainlink/v2/core/services/webhook" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -247,6 +247,7 @@ type VRFSpecParams struct { BackoffInitialDelay time.Duration BackoffMaxDelay time.Duration GasLanePrice *assets.Wei + PollPeriod time.Duration } type VRFSpec struct { @@ -283,6 +284,10 @@ func GenerateVRFSpec(params VRFSpecParams) VRFSpec { if params.VRFOwnerAddress != "" && vrfVersion == vrfcommon.V2 { vrfOwnerAddress = params.VRFOwnerAddress } + pollPeriod := 5 * time.Second + if params.PollPeriod > 0 && (vrfVersion == vrfcommon.V2 || vrfVersion == vrfcommon.V2Plus) { + pollPeriod = params.PollPeriod + } batchFulfillmentGasMultiplier := 1.0 if params.BatchFulfillmentGasMultiplier >= 1.0 { batchFulfillmentGasMultiplier = params.BatchFulfillmentGasMultiplier @@ -406,6 +411,7 @@ chunkSize = %d backoffInitialDelay = "%s" backoffMaxDelay = "%s" gasLanePrice = "%s" +pollPeriod = "%s" observationSource = """ %s """ @@ -414,7 +420,8 @@ observationSource = """ jobID, name, coordinatorAddress, params.EVMChainID, batchCoordinatorAddress, params.BatchFulfillmentEnabled, strconv.FormatFloat(batchFulfillmentGasMultiplier, 'f', 2, 64), confirmations, params.RequestedConfsDelay, requestTimeout.String(), publicKey, chunkSize, - params.BackoffInitialDelay.String(), params.BackoffMaxDelay.String(), gasLanePrice.String(), observationSource) + params.BackoffInitialDelay.String(), params.BackoffMaxDelay.String(), gasLanePrice.String(), + pollPeriod.String(), observationSource) if len(params.FromAddresses) != 0 { var addresses []string for _, address := range params.FromAddresses { @@ -443,6 +450,7 @@ observationSource = """ BackoffMaxDelay: params.BackoffMaxDelay, VRFOwnerAddress: vrfOwnerAddress, VRFVersion: vrfVersion, + PollPeriod: pollPeriod, }, toml: toml} } diff --git a/core/utils/big.go b/core/utils/big.go index 6bdb95c1217..69fab223de7 100644 --- a/core/utils/big.go +++ b/core/utils/big.go @@ -8,7 +8,8 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" - bigmath "github.com/smartcontractkit/chainlink/v2/core/utils/big_math" + bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" + "github.com/smartcontractkit/chainlink-common/pkg/utils/bytes" ) const base10 = 10 @@ -80,7 +81,7 @@ func (b Big) MarshalJSON() ([]byte, error) { // UnmarshalText implements encoding.TextUnmarshaler. func (b *Big) UnmarshalText(input []byte) error { - input = RemoveQuotes(input) + input = bytes.TrimQuotes(input) str := string(input) if HasHexPrefix(str) { decoded, err := hexutil.DecodeBig(str) diff --git a/core/utils/config/validate.go b/core/utils/config/validate.go index 32cb94b5205..5fbae24ad53 100644 --- a/core/utils/config/validate.go +++ b/core/utils/config/validate.go @@ -9,7 +9,7 @@ import ( "github.com/Masterminds/semver/v3" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink-relay/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/utils/hash.go b/core/utils/hash.go index bcae823770e..b0a32454e2f 100644 --- a/core/utils/hash.go +++ b/core/utils/hash.go @@ -8,12 +8,32 @@ import ( "github.com/pkg/errors" ) +const HashLength = 32 + // Hash is a simplified version of go-ethereum's common.Hash to avoid // go-ethereum dependency // It represents a 32 byte fixed size array that marshals/unmarshals assuming a // 0x prefix type Hash [32]byte +// BytesToHash sets b to hash. +// If b is larger than len(h), b will be cropped from the left. +func BytesToHash(b []byte) Hash { + var h Hash + h.SetBytes(b) + return h +} + +// SetBytes sets the hash to the value of b. +// If b is larger than len(h), b will be cropped from the left. +func (h *Hash) SetBytes(b []byte) { + if len(b) > len(h) { + b = b[len(b)-HashLength:] + } + + copy(h[HashLength-len(b):], b) +} + // Hex converts a hash to a hex string. func (h Hash) Hex() string { return fmt.Sprintf("0x%s", hex.EncodeToString(h[:])) } diff --git a/core/utils/helpers_test.go b/core/utils/helpers_test.go deleted file mode 100644 index d317994cde1..00000000000 --- a/core/utils/helpers_test.go +++ /dev/null @@ -1,5 +0,0 @@ -package utils - -func (once *StartStopOnce) LoadState() StartStopOnceState { - return StartStopOnceState(once.state.Load()) -} diff --git a/core/utils/lazy.go b/core/utils/lazy.go deleted file mode 100644 index 43e84808159..00000000000 --- a/core/utils/lazy.go +++ /dev/null @@ -1,36 +0,0 @@ -package utils - -import ( - "sync" -) - -type LazyLoad[T any] struct { - f func() (T, error) - state T - ok bool - lock sync.Mutex -} - -func NewLazyLoad[T any](f func() (T, error)) *LazyLoad[T] { - return &LazyLoad[T]{ - f: f, - } -} - -func (l *LazyLoad[T]) Get() (out T, err error) { - l.lock.Lock() - defer l.lock.Unlock() - - if l.ok { - return l.state, nil - } - l.state, err = l.f() - l.ok = err == nil - return l.state, err -} - -func (l *LazyLoad[T]) Reset() { - l.lock.Lock() - defer l.lock.Unlock() - l.ok = false -} diff --git a/core/utils/lazy_test.go b/core/utils/lazy_test.go deleted file mode 100644 index 88d02eea7f2..00000000000 --- a/core/utils/lazy_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package utils - -import ( - "sync" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestLazyLoad(t *testing.T) { - var clientwg sync.WaitGroup - - tc := func() (int, error) { - clientwg.Done() - return 10, nil - } - - // Get should only request a client once, use cached afterward - t.Run("get", func(t *testing.T) { - clientwg.Add(1) // expect one call to get client - c := NewLazyLoad(tc) - rw, err := c.Get() - assert.NoError(t, err) - assert.NotNil(t, rw) - assert.NotNil(t, c.state) - - // used cached client - rw, err = c.Get() - assert.NoError(t, err) - assert.NotNil(t, rw) - clientwg.Wait() - }) - - // Clear removes the cached client, should refetch - t.Run("clear", func(t *testing.T) { - clientwg.Add(2) // expect two calls to get client - - c := NewLazyLoad(tc) - rw, err := c.Get() - assert.NotNil(t, rw) - assert.NoError(t, err) - - c.Reset() - - rw, err = c.Get() - assert.NotNil(t, rw) - assert.NoError(t, err) - clientwg.Wait() - }) - - // Race checks a race condition of Getting and Clearing a new client - t.Run("race", func(t *testing.T) { - clientwg.Add(1) // expect one call to get client - - c := NewLazyLoad(tc) - var wg sync.WaitGroup - wg.Add(2) - go func() { - rw, err := c.Get() - assert.NoError(t, err) - assert.NotNil(t, rw) - wg.Done() - }() - go func() { - c.Reset() - wg.Done() - }() - wg.Wait() - clientwg.Wait() - }) -} diff --git a/core/utils/mailbox_prom.go b/core/utils/mailbox_prom.go index 0291a51d2c4..33cbb2357b1 100644 --- a/core/utils/mailbox_prom.go +++ b/core/utils/mailbox_prom.go @@ -9,6 +9,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/smartcontractkit/chainlink-common/pkg/services" ) var mailboxLoad = promauto.NewGaugeVec(prometheus.GaugeOpts{ @@ -21,7 +23,7 @@ var mailboxLoad = promauto.NewGaugeVec(prometheus.GaugeOpts{ const mailboxPromInterval = 5 * time.Second type MailboxMonitor struct { - StartStopOnce + services.StateMachine appID string mailboxes sync.Map diff --git a/core/utils/sleeper_task.go b/core/utils/sleeper_task.go index 0b45507a82f..d84457e9325 100644 --- a/core/utils/sleeper_task.go +++ b/core/utils/sleeper_task.go @@ -3,6 +3,8 @@ package utils import ( "fmt" "time" + + "github.com/smartcontractkit/chainlink-common/pkg/services" ) // SleeperTask represents a task that waits in the background to process some work. @@ -19,12 +21,12 @@ type Worker interface { } type sleeperTask struct { + services.StateMachine worker Worker chQueue chan struct{} chStop chan struct{} chDone chan struct{} chWorkDone chan struct{} - StartStopOnce } // NewSleeperTask takes a worker and returns a SleeperTask. @@ -76,13 +78,14 @@ func (s *sleeperTask) WakeUpIfStarted() { // WakeUp wakes up the sleeper task, asking it to execute its Worker. func (s *sleeperTask) WakeUp() { - if s.StartStopOnce.State() == StartStopOnce_Stopped { + if !s.IfStarted(func() { + select { + case s.chQueue <- struct{}{}: + default: + } + }) { panic("cannot wake up stopped sleeper task") } - select { - case s.chQueue <- struct{}{}: - default: - } } func (s *sleeperTask) workDone() { diff --git a/core/utils/thread_control.go b/core/utils/thread_control.go index 8f7fff42496..52cda82797a 100644 --- a/core/utils/thread_control.go +++ b/core/utils/thread_control.go @@ -3,6 +3,8 @@ package utils import ( "context" "sync" + + "github.com/smartcontractkit/chainlink-common/pkg/services" ) var _ ThreadControl = &threadControl{} @@ -25,7 +27,7 @@ func NewThreadControl() *threadControl { type threadControl struct { threadsWG sync.WaitGroup - stop StopChan + stop services.StopChan } func (tc *threadControl) Go(fn func(context.Context)) { diff --git a/core/utils/utils.go b/core/utils/utils.go index d96546b3e19..0df280775b5 100644 --- a/core/utils/utils.go +++ b/core/utils/utils.go @@ -9,7 +9,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "math" "math/big" mrand "math/rand" @@ -31,6 +30,8 @@ import ( "github.com/robfig/cron/v3" "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/sha3" + + "github.com/smartcontractkit/chainlink-common/pkg/services" ) const ( @@ -38,9 +39,6 @@ const ( DefaultSecretSize = 48 // EVMWordByteLen the length of an EVM Word Byte EVMWordByteLen = 32 - - // defaultErrorBufferCap is the default cap on the errors an error buffer can store at any time - defaultErrorBufferCap = 50 ) // ZeroAddress is an address of all zeroes, otherwise in Ethereum as @@ -306,22 +304,6 @@ func Sha256(in string) (string, error) { return hex.EncodeToString(hasher.Sum(nil)), nil } -// IsQuoted checks if the first and last characters are either " or '. -func IsQuoted(input []byte) bool { - return len(input) >= 2 && - ((input[0] == '"' && input[len(input)-1] == '"') || - (input[0] == '\'' && input[len(input)-1] == '\'')) -} - -// RemoveQuotes removes the first and last character if they are both either -// " or ', otherwise it is a noop. -func RemoveQuotes(input []byte) []byte { - if IsQuoted(input) { - return input[1 : len(input)-1] - } - return input -} - // EIP55CapitalizedAddress returns true iff possibleAddressString has the correct // capitalization for an Ethereum address, per EIP 55 func EIP55CapitalizedAddress(possibleAddressString string) bool { @@ -416,68 +398,28 @@ func WaitGroupChan(wg *sync.WaitGroup) <-chan struct{} { } // WithCloseChan wraps a context so that it is canceled if the passed in channel is closed. -// Deprecated: Call StopChan.Ctx directly +// Deprecated: Call [services.StopChan.Ctx] directly func WithCloseChan(parentCtx context.Context, chStop chan struct{}) (context.Context, context.CancelFunc) { - return StopChan(chStop).Ctx(parentCtx) + return services.StopChan(chStop).Ctx(parentCtx) } // ContextFromChan creates a context that finishes when the provided channel receives or is closed. -// Deprecated: Call StopChan.NewCtx directly. +// Deprecated: Call [services.StopChan.NewCtx] directly. func ContextFromChan(chStop chan struct{}) (context.Context, context.CancelFunc) { - return StopChan(chStop).NewCtx() + return services.StopChan(chStop).NewCtx() } // ContextFromChanWithTimeout creates a context with a timeout that finishes when the provided channel receives or is closed. -// Deprecated: Call StopChan.CtxCancel directly +// Deprecated: Call [services.StopChan.CtxCancel] directly func ContextFromChanWithTimeout(chStop chan struct{}, timeout time.Duration) (context.Context, context.CancelFunc) { - return StopChan(chStop).CtxCancel(context.WithTimeout(context.Background(), timeout)) -} - -// A StopChan signals when some work should stop. -type StopChan chan struct{} - -// NewCtx returns a background [context.Context] that is cancelled when StopChan is closed. -func (s StopChan) NewCtx() (context.Context, context.CancelFunc) { - return StopRChan((<-chan struct{})(s)).NewCtx() + return services.StopChan(chStop).CtxCancel(context.WithTimeout(context.Background(), timeout)) } -// Ctx cancels a [context.Context] when StopChan is closed. -func (s StopChan) Ctx(ctx context.Context) (context.Context, context.CancelFunc) { - return StopRChan((<-chan struct{})(s)).Ctx(ctx) -} +// Deprecated: use services.StopChan +type StopChan = services.StopChan -// CtxCancel cancels a [context.Context] when StopChan is closed. -// Returns ctx and cancel unmodified, for convenience. -func (s StopChan) CtxCancel(ctx context.Context, cancel context.CancelFunc) (context.Context, context.CancelFunc) { - return StopRChan((<-chan struct{})(s)).CtxCancel(ctx, cancel) -} - -// A StopRChan signals when some work should stop. -// This version is receive-only. -type StopRChan <-chan struct{} - -// NewCtx returns a background [context.Context] that is cancelled when StopChan is closed. -func (s StopRChan) NewCtx() (context.Context, context.CancelFunc) { - return s.Ctx(context.Background()) -} - -// Ctx cancels a [context.Context] when StopChan is closed. -func (s StopRChan) Ctx(ctx context.Context) (context.Context, context.CancelFunc) { - return s.CtxCancel(context.WithCancel(ctx)) -} - -// CtxCancel cancels a [context.Context] when StopChan is closed. -// Returns ctx and cancel unmodified, for convenience. -func (s StopRChan) CtxCancel(ctx context.Context, cancel context.CancelFunc) (context.Context, context.CancelFunc) { - go func() { - select { - case <-s: - cancel() - case <-ctx.Done(): - } - }() - return ctx, cancel -} +// Deprecated: use services.StopRChan +type StopRChan = services.StopRChan // DependentAwaiter contains Dependent funcs type DependentAwaiter interface { @@ -819,14 +761,6 @@ func EVMBytesToUint64(buf []byte) uint64 { return result } -type errNotStarted struct { - state StartStopOnceState -} - -func (e *errNotStarted) Error() string { - return fmt.Sprintf("service is %q, not started", e.state) -} - var ( ErrAlreadyStopped = errors.New("already stopped") ErrCannotStopUnstarted = errors.New("cannot stop unstarted service") @@ -834,186 +768,7 @@ var ( // StartStopOnce contains a StartStopOnceState integer // Deprecated: use services.StateMachine -type StartStopOnce struct { - state atomic.Int32 - sync.RWMutex // lock is held during startup/shutdown, RLock is held while executing functions dependent on a particular state - - // SvcErrBuffer is an ErrorBuffer that let service owners track critical errors happening in the service. - // - // SvcErrBuffer.SetCap(int) Overrides buffer limit from defaultErrorBufferCap - // SvcErrBuffer.Append(error) Appends an error to the buffer - // SvcErrBuffer.Flush() error returns all tracked errors as a single joined error - SvcErrBuffer ErrorBuffer -} - -// StartStopOnceState holds the state for StartStopOnce -type StartStopOnceState int32 - -// nolint -const ( - StartStopOnce_Unstarted StartStopOnceState = iota - StartStopOnce_Started - StartStopOnce_Starting - StartStopOnce_StartFailed - StartStopOnce_Stopping - StartStopOnce_Stopped - StartStopOnce_StopFailed -) - -func (s StartStopOnceState) String() string { - switch s { - case StartStopOnce_Unstarted: - return "Unstarted" - case StartStopOnce_Started: - return "Started" - case StartStopOnce_Starting: - return "Starting" - case StartStopOnce_StartFailed: - return "StartFailed" - case StartStopOnce_Stopping: - return "Stopping" - case StartStopOnce_Stopped: - return "Stopped" - case StartStopOnce_StopFailed: - return "StopFailed" - 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 - // concurrent StartOnce() calls return immediately. - success := once.state.CompareAndSwap(int32(StartStopOnce_Unstarted), int32(StartStopOnce_Starting)) - - if !success { - return pkgerrors.Errorf("%v has already been started once; state=%v", name, StartStopOnceState(once.state.Load())) - } - - once.Lock() - defer once.Unlock() - - // Setting cap before calling startup fn in case of crits in startup - once.SvcErrBuffer.SetCap(defaultErrorBufferCap) - err := fn() - - if err == nil { - success = once.state.CompareAndSwap(int32(StartStopOnce_Starting), int32(StartStopOnce_Started)) - } else { - success = once.state.CompareAndSwap(int32(StartStopOnce_Starting), int32(StartStopOnce_StartFailed)) - } - - if !success { - // SAFETY: If this is reached, something must be very wrong: once.state - // was tampered with outside of the lock. - panic(fmt.Sprintf("%v entered unreachable state, unable to set state to started", name)) - } - - return err -} - -// StopOnce sets the state to Stopped -func (once *StartStopOnce) StopOnce(name string, fn func() error) error { - // SAFETY: We hold the lock here so that Stop blocks until StartOnce - // executes. This ensures that a very fast call to Stop will wait for the - // code to finish starting up before teardown. - once.Lock() - defer once.Unlock() - - success := once.state.CompareAndSwap(int32(StartStopOnce_Started), int32(StartStopOnce_Stopping)) - - if !success { - state := once.state.Load() - switch state { - case int32(StartStopOnce_Stopped): - return pkgerrors.Wrapf(ErrAlreadyStopped, "%s has already been stopped", name) - case int32(StartStopOnce_Unstarted): - return pkgerrors.Wrapf(ErrCannotStopUnstarted, "%s has not been started", name) - default: - return pkgerrors.Errorf("%v cannot be stopped from this state; state=%v", name, StartStopOnceState(state)) - } - } - - err := fn() - - if err == nil { - success = once.state.CompareAndSwap(int32(StartStopOnce_Stopping), int32(StartStopOnce_Stopped)) - } else { - success = once.state.CompareAndSwap(int32(StartStopOnce_Stopping), int32(StartStopOnce_StopFailed)) - } - - if !success { - // SAFETY: If this is reached, something must be very wrong: once.state - // was tampered with outside of the lock. - panic(fmt.Sprintf("%v entered unreachable state, unable to set state to stopped", name)) - } - - return err -} - -// State retrieves the current state -func (once *StartStopOnce) State() StartStopOnceState { - state := once.state.Load() - return StartStopOnceState(state) -} - -// IfStarted runs the func and returns true only if started, otherwise returns false -func (once *StartStopOnce) IfStarted(f func()) (ok bool) { - once.RLock() - defer once.RUnlock() - - state := once.state.Load() - - if StartStopOnceState(state) == StartStopOnce_Started { - f() - return true - } - return false -} - -// IfNotStopped runs the func and returns true if in any state other than Stopped -func (once *StartStopOnce) IfNotStopped(f func()) (ok bool) { - once.RLock() - defer once.RUnlock() - - state := once.state.Load() - - if StartStopOnceState(state) == StartStopOnce_Stopped { - return false - } - f() - return true -} - -// Ready returns ErrNotStarted if the state is not started. -func (once *StartStopOnce) Ready() error { - state := once.State() - if state == StartStopOnce_Started { - return nil - } - 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 { - state := once.State() - if state == StartStopOnce_Started { - return once.SvcErrBuffer.Flush() - } - return &errNotStarted{state: state} -} - -// EnsureClosed closes the io.Closer, returning nil if it was already -// closed or not started yet -func EnsureClosed(c io.Closer) error { - err := c.Close() - if errors.Is(err, ErrAlreadyStopped) || errors.Is(err, ErrCannotStopUnstarted) { - return nil - } - return err -} +type StartStopOnce = services.StateMachine // WithJitter adds +/- 10% to a duration func WithJitter(d time.Duration) time.Duration { diff --git a/core/utils/utils_example_test.go b/core/utils/utils_example_test.go deleted file mode 100644 index 0ca9d0be88d..00000000000 --- a/core/utils/utils_example_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package utils - -import ( - "context" - "sync" - "time" -) - -func ExampleStopChan() { - stopCh := make(StopChan) - - work := func(context.Context) {} - - a := func(ctx context.Context, done func()) { - defer done() - ctx, cancel := stopCh.Ctx(ctx) - defer cancel() - work(ctx) - } - - b := func(ctx context.Context, done func()) { - defer done() - ctx, cancel := stopCh.CtxCancel(context.WithTimeout(ctx, time.Second)) - defer cancel() - work(ctx) - } - - c := func(ctx context.Context, done func()) { - defer done() - ctx, cancel := stopCh.CtxCancel(context.WithDeadline(ctx, time.Now().Add(5*time.Second))) - defer cancel() - work(ctx) - } - - ctx, cancel := stopCh.NewCtx() - defer cancel() - - var wg sync.WaitGroup - wg.Add(3) - go a(ctx, wg.Done) - go b(ctx, wg.Done) - go c(ctx, wg.Done) - - time.AfterFunc(time.Second, func() { close(stopCh) }) - - wg.Wait() - // Output: -} diff --git a/core/utils/utils_test.go b/core/utils/utils_test.go index 5d728d14f4a..e11c01a8e4f 100644 --- a/core/utils/utils_test.go +++ b/core/utils/utils_test.go @@ -374,79 +374,6 @@ func Test_WithJitter(t *testing.T) { } } -func Test_StartStopOnce_StopWaitsForStartToFinish(t *testing.T) { - t.Parallel() - - once := utils.StartStopOnce{} - - ch := make(chan int, 3) - - ready := make(chan bool) - - go func() { - assert.NoError(t, once.StartOnce("slow service", func() (err error) { - ch <- 1 - ready <- true - <-time.After(time.Millisecond * 500) // wait for StopOnce to happen - ch <- 2 - - return nil - })) - }() - - go func() { - <-ready // try stopping halfway through startup - assert.NoError(t, once.StopOnce("slow service", func() (err error) { - ch <- 3 - - return nil - })) - }() - - require.Equal(t, 1, <-ch) - require.Equal(t, 2, <-ch) - require.Equal(t, 3, <-ch) -} - -func Test_StartStopOnce_MultipleStartNoBlock(t *testing.T) { - t.Parallel() - - once := utils.StartStopOnce{} - - ch := make(chan int, 3) - - ready := make(chan bool) - next := make(chan bool) - - go func() { - ch <- 1 - assert.NoError(t, once.StartOnce("slow service", func() (err error) { - ready <- true - <-next // continue after the other StartOnce call fails - - return nil - })) - <-next - ch <- 2 - - }() - - go func() { - <-ready // try starting halfway through startup - assert.Error(t, once.StartOnce("slow service", func() (err error) { - return nil - })) - next <- true - ch <- 3 - next <- true - - }() - - require.Equal(t, 1, <-ch) - require.Equal(t, 3, <-ch) // 3 arrives before 2 because it returns immediately - require.Equal(t, 2, <-ch) -} - func TestAllEqual(t *testing.T) { t.Parallel() @@ -724,111 +651,6 @@ func TestContextFromChanWithTimeout(t *testing.T) { }) } -func TestStartStopOnceState_String(t *testing.T) { - t.Parallel() - - assert.Equal(t, "Unstarted", utils.StartStopOnce_Unstarted.String()) - assert.Equal(t, "Started", utils.StartStopOnce_Started.String()) - assert.Equal(t, "Starting", utils.StartStopOnce_Starting.String()) - assert.Equal(t, "Stopping", utils.StartStopOnce_Stopping.String()) - assert.Equal(t, "Stopped", utils.StartStopOnce_Stopped.String()) - assert.Equal(t, "unrecognized state: 123", utils.StartStopOnceState(123).String()) -} - -func TestStartStopOnce(t *testing.T) { - t.Parallel() - - var callsCount atomic.Int32 - incCount := func() { - callsCount.Add(1) - } - - var s utils.StartStopOnce - ok := s.IfStarted(incCount) - assert.False(t, ok) - ok = s.IfNotStopped(incCount) - assert.True(t, ok) - assert.Equal(t, int32(1), callsCount.Load()) - - err := s.StartOnce("foo", func() error { return nil }) - assert.NoError(t, err) - - assert.True(t, s.IfStarted(incCount)) - assert.Equal(t, int32(2), callsCount.Load()) - - err = s.StopOnce("foo", func() error { return nil }) - assert.NoError(t, err) - ok = s.IfNotStopped(incCount) - assert.False(t, ok) - assert.Equal(t, int32(2), callsCount.Load()) -} - -func TestStartStopOnce_StartErrors(t *testing.T) { - var s utils.StartStopOnce - - err := s.StartOnce("foo", func() error { return errors.New("foo") }) - assert.Error(t, err) - - var callsCount atomic.Int32 - incCount := func() { - callsCount.Add(1) - } - - assert.False(t, s.IfStarted(incCount)) - assert.Equal(t, int32(0), callsCount.Load()) - - err = s.StartOnce("foo", func() error { return nil }) - require.Error(t, err) - assert.Contains(t, err.Error(), "foo has already been started once") - err = s.StopOnce("foo", func() error { return nil }) - require.Error(t, err) - assert.Contains(t, err.Error(), "foo cannot be stopped from this state; state=StartFailed") - - assert.Equal(t, utils.StartStopOnce_StartFailed, s.LoadState()) -} - -func TestStartStopOnce_StopErrors(t *testing.T) { - var s utils.StartStopOnce - - err := s.StartOnce("foo", func() error { return nil }) - require.NoError(t, err) - - var callsCount atomic.Int32 - incCount := func() { - callsCount.Add(1) - } - - err = s.StopOnce("foo", func() error { return errors.New("explodey mcsplode") }) - assert.Error(t, err) - - assert.False(t, s.IfStarted(incCount)) - assert.Equal(t, int32(0), callsCount.Load()) - assert.True(t, s.IfNotStopped(incCount)) - assert.Equal(t, int32(1), callsCount.Load()) - - err = s.StartOnce("foo", func() error { return nil }) - require.Error(t, err) - assert.Contains(t, err.Error(), "foo has already been started once") - err = s.StopOnce("foo", func() error { return nil }) - require.Error(t, err) - assert.Contains(t, err.Error(), "foo cannot be stopped from this state; state=StopFailed") - - assert.Equal(t, utils.StartStopOnce_StopFailed, s.LoadState()) -} - -func TestStartStopOnce_Ready_Healthy(t *testing.T) { - t.Parallel() - - var s utils.StartStopOnce - assert.Error(t, s.Ready()) - assert.Error(t, s.Healthy()) - - err := s.StartOnce("foo", func() error { return nil }) - assert.NoError(t, err) - assert.NoError(t, s.Ready()) - assert.NoError(t, s.Healthy()) -} - func TestLeftPadBitString(t *testing.T) { t.Parallel() diff --git a/core/web/assets/9f6d832ef97e8493764e.svg b/core/web/assets/9f6d832ef97e8493764e.svg new file mode 100644 index 00000000000..e35f1df284c --- /dev/null +++ b/core/web/assets/9f6d832ef97e8493764e.svg @@ -0,0 +1 @@ + diff --git a/core/web/assets/9f6d832ef97e8493764e.svg.gz b/core/web/assets/9f6d832ef97e8493764e.svg.gz new file mode 100644 index 00000000000..44cd870b0bb Binary files /dev/null and b/core/web/assets/9f6d832ef97e8493764e.svg.gz differ diff --git a/core/web/assets/ba8bbf16ebf8e1d05bef.svg b/core/web/assets/ba8bbf16ebf8e1d05bef.svg new file mode 100644 index 00000000000..3ca546a8aaa --- /dev/null +++ b/core/web/assets/ba8bbf16ebf8e1d05bef.svg @@ -0,0 +1 @@ +Artboard 1 \ No newline at end of file diff --git a/core/web/assets/ba8bbf16ebf8e1d05bef.svg.gz b/core/web/assets/ba8bbf16ebf8e1d05bef.svg.gz new file mode 100644 index 00000000000..7e4f235f1b9 Binary files /dev/null and b/core/web/assets/ba8bbf16ebf8e1d05bef.svg.gz differ diff --git a/core/web/assets/index.html b/core/web/assets/index.html new file mode 100644 index 00000000000..601453b4eac --- /dev/null +++ b/core/web/assets/index.html @@ -0,0 +1 @@ +Operator UIChainlink
\ No newline at end of file diff --git a/core/web/assets/index.html.gz b/core/web/assets/index.html.gz new file mode 100644 index 00000000000..24cc3068f6e Binary files /dev/null and b/core/web/assets/index.html.gz differ diff --git a/core/web/assets/main.b0b6f79f7f4a94560e37.js b/core/web/assets/main.b0b6f79f7f4a94560e37.js new file mode 100644 index 00000000000..6c9f23d1cca --- /dev/null +++ b/core/web/assets/main.b0b6f79f7f4a94560e37.js @@ -0,0 +1,187 @@ +(()=>{var __webpack_modules__={23564(e,t,n){"use strict";n.d(t,{Jh:()=>u,ZT:()=>i,_T:()=>o,ev:()=>c,mG:()=>s,pi:()=>a});/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ var r=function(e,t){return(r=Object.setPrototypeOf||({__proto__:[]})instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function i(e,t){if("function"!=typeof t&&null!==t)throw TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var a=function(){return(a=Object.assign||function(e){for(var t,n=1,r=arguments.length;nt.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,r=Object.getOwnPropertySymbols(e);it.indexOf(r[i])&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]]);return n}function s(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||(n=Promise))(function(n,a){function o(e){try{u(r.next(e))}catch(t){a(t)}}function s(e){try{u(r.throw(e))}catch(t){a(t)}}function u(e){e.done?n(e.value):i(e.value).then(o,s)}u((r=r.apply(e,t||[])).next())})}function u(e,t){var n,r,i,a,o={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return a={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function s(e){return function(t){return u([e,t])}}function u(a){if(n)throw TypeError("Generator is already executing.");for(;o;)try{if(n=1,r&&(i=2&a[0]?r.return:a[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,a[1])).done)return i;switch(r=0,i&&(a=[2&a[0],i.value]),a[0]){case 0:case 1:i=a;break;case 4:return o.label++,{value:a[1],done:!1};case 5:o.label++,r=a[1],a=[0];continue;case 7:a=o.ops.pop(),o.trys.pop();continue;default:if(!(i=(i=o.trys).length>0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}e.exports=i,e.exports.default=e.exports,e.exports.__esModule=!0},37316(e){function t(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}e.exports=t,e.exports.default=e.exports,e.exports.__esModule=!0},78585(e,t,n){var r=n(50008).default,i=n(81506);function a(e,t){return t&&("object"===r(t)||"function"==typeof t)?t:i(e)}e.exports=a,e.exports.default=e.exports,e.exports.__esModule=!0},99489(e){function t(n,r){return e.exports=t=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},e.exports.default=e.exports,e.exports.__esModule=!0,t(n,r)}e.exports=t,e.exports.default=e.exports,e.exports.__esModule=!0},319(e,t,n){var r=n(23646),i=n(46860),a=n(60379),o=n(98206);function s(e){return r(e)||i(e)||a(e)||o()}e.exports=s,e.exports.default=e.exports,e.exports.__esModule=!0},50008(e){function t(n){return"function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?(e.exports=t=function(e){return typeof e},e.exports.default=e.exports,e.exports.__esModule=!0):(e.exports=t=function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.default=e.exports,e.exports.__esModule=!0),t(n)}e.exports=t,e.exports.default=e.exports,e.exports.__esModule=!0},60379(e,t,n){var r=n(67228);function i(e,t){if(e){if("string"==typeof e)return r(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return r(e,t)}}e.exports=i,e.exports.default=e.exports,e.exports.__esModule=!0},98925(e,t,n){"use strict";let r=n(98633),i=n.g.Date;class a extends i{constructor(e){super(e),this.isDate=!0}toISOString(){return`${this.getUTCFullYear()}-${r(2,this.getUTCMonth()+1)}-${r(2,this.getUTCDate())}`}}e.exports=e=>{let t=new a(e);if(!isNaN(t))return t;throw TypeError("Invalid Datetime")}},86595(e,t,n){"use strict";let r=n(98633);class i extends Date{constructor(e){super(e+"Z"),this.isFloating=!0}toISOString(){let e=`${this.getUTCFullYear()}-${r(2,this.getUTCMonth()+1)}-${r(2,this.getUTCDate())}`,t=`${r(2,this.getUTCHours())}:${r(2,this.getUTCMinutes())}:${r(2,this.getUTCSeconds())}.${r(3,this.getUTCMilliseconds())}`;return`${e}T${t}`}}e.exports=e=>{let t=new i(e);if(!isNaN(t))return t;throw TypeError("Invalid Datetime")}},76114(e){"use strict";e.exports=e=>{let t=new Date(e);if(!isNaN(t))return t;throw TypeError("Invalid Datetime")}},99439(e,t,n){"use strict";let r=n(98633);class i extends Date{constructor(e){super(`0000-01-01T${e}Z`),this.isTime=!0}toISOString(){return`${r(2,this.getUTCHours())}:${r(2,this.getUTCMinutes())}:${r(2,this.getUTCSeconds())}.${r(3,this.getUTCMilliseconds())}`}}e.exports=e=>{let t=new i(e);if(!isNaN(t))return t;throw TypeError("Invalid Datetime")}},98633(e){"use strict";e.exports=(e,t)=>{for(t=String(t);t.length{let t=new TomlError(e.message);return t.code=e.code,t.wrapped=e,t},module.exports.TomlError=TomlError;let createDateTime=__webpack_require__(76114),createDateTimeFloat=__webpack_require__(86595),createDate=__webpack_require__(98925),createTime=__webpack_require__(99439),CTRL_I=9,CTRL_J=10,CTRL_M=13,CTRL_CHAR_BOUNDARY=31,CHAR_SP=32,CHAR_QUOT=34,CHAR_NUM=35,CHAR_APOS=39,CHAR_PLUS=43,CHAR_COMMA=44,CHAR_HYPHEN=45,CHAR_PERIOD=46,CHAR_0=48,CHAR_1=49,CHAR_7=55,CHAR_9=57,CHAR_COLON=58,CHAR_EQUALS=61,CHAR_A=65,CHAR_E=69,CHAR_F=70,CHAR_T=84,CHAR_U=85,CHAR_Z=90,CHAR_LOWBAR=95,CHAR_a=97,CHAR_b=98,CHAR_e=101,CHAR_f=102,CHAR_i=105,CHAR_l=108,CHAR_n=110,CHAR_o=111,CHAR_r=114,CHAR_s=115,CHAR_t=116,CHAR_u=117,CHAR_x=120,CHAR_z=122,CHAR_LCUB=123,CHAR_RCUB=125,CHAR_LSQB=91,CHAR_BSOL=92,CHAR_RSQB=93,CHAR_DEL=127,SURROGATE_FIRST=55296,SURROGATE_LAST=57343,escapes={[CHAR_b]:"\b",[CHAR_t]:" ",[CHAR_n]:"\n",[CHAR_f]:"\f",[CHAR_r]:"\r",[CHAR_QUOT]:'"',[CHAR_BSOL]:"\\"};function isDigit(e){return e>=CHAR_0&&e<=CHAR_9}function isHexit(e){return e>=CHAR_A&&e<=CHAR_F||e>=CHAR_a&&e<=CHAR_f||e>=CHAR_0&&e<=CHAR_9}function isBit(e){return e===CHAR_1||e===CHAR_0}function isOctit(e){return e>=CHAR_0&&e<=CHAR_7}function isAlphaNumQuoteHyphen(e){return e>=CHAR_A&&e<=CHAR_Z||e>=CHAR_a&&e<=CHAR_z||e>=CHAR_0&&e<=CHAR_9||e===CHAR_APOS||e===CHAR_QUOT||e===CHAR_LOWBAR||e===CHAR_HYPHEN}function isAlphaNumHyphen(e){return e>=CHAR_A&&e<=CHAR_Z||e>=CHAR_a&&e<=CHAR_z||e>=CHAR_0&&e<=CHAR_9||e===CHAR_LOWBAR||e===CHAR_HYPHEN}let _type=Symbol("type"),_declared=Symbol("declared"),hasOwnProperty=Object.prototype.hasOwnProperty,defineProperty=Object.defineProperty,descriptor={configurable:!0,enumerable:!0,writable:!0,value:void 0};function hasKey(e,t){return!!hasOwnProperty.call(e,t)||("__proto__"===t&&defineProperty(e,"__proto__",descriptor),!1)}let INLINE_TABLE=Symbol("inline-table");function InlineTable(){return Object.defineProperties({},{[_type]:{value:INLINE_TABLE}})}function isInlineTable(e){return null!==e&&"object"==typeof e&&e[_type]===INLINE_TABLE}let TABLE=Symbol("table");function Table(){return Object.defineProperties({},{[_type]:{value:TABLE},[_declared]:{value:!1,writable:!0}})}function isTable(e){return null!==e&&"object"==typeof e&&e[_type]===TABLE}let _contentType=Symbol("content-type"),INLINE_LIST=Symbol("inline-list");function InlineList(e){return Object.defineProperties([],{[_type]:{value:INLINE_LIST},[_contentType]:{value:e}})}function isInlineList(e){return null!==e&&"object"==typeof e&&e[_type]===INLINE_LIST}let LIST=Symbol("list");function List(){return Object.defineProperties([],{[_type]:{value:LIST}})}function isList(e){return null!==e&&"object"==typeof e&&e[_type]===LIST}let _custom;try{let utilInspect=eval("require('util').inspect");_custom=utilInspect.custom}catch(_){}let _inspect=_custom||"inspect";class BoxedBigInt{constructor(e){try{this.value=__webpack_require__.g.BigInt.asIntN(64,e)}catch(t){this.value=null}Object.defineProperty(this,_type,{value:INTEGER})}isNaN(){return null===this.value}toString(){return String(this.value)}[_inspect](){return`[BigInt: ${this.toString()}]}`}valueOf(){return this.value}}let INTEGER=Symbol("integer");function Integer(e){let t=Number(e);return(Object.is(t,-0)&&(t=0),__webpack_require__.g.BigInt&&!Number.isSafeInteger(t))?new BoxedBigInt(e):Object.defineProperties(new Number(t),{isNaN:{value:function(){return isNaN(this)}},[_type]:{value:INTEGER},[_inspect]:{value:()=>`[Integer: ${e}]`}})}function isInteger(e){return null!==e&&"object"==typeof e&&e[_type]===INTEGER}let FLOAT=Symbol("float");function Float(e){return Object.defineProperties(new Number(e),{[_type]:{value:FLOAT},[_inspect]:{value:()=>`[Float: ${e}]`}})}function isFloat(e){return null!==e&&"object"==typeof e&&e[_type]===FLOAT}function tomlType(e){let t=typeof e;if("object"===t){if(null===e)return"null";if(e instanceof Date)return"datetime";if(_type in e)switch(e[_type]){case INLINE_TABLE:return"inline-table";case INLINE_LIST:return"inline-list";case TABLE:return"table";case LIST:return"list";case FLOAT:return"float";case INTEGER:return"integer"}}return t}function makeParserClass(e){class t extends e{constructor(){super(),this.ctx=this.obj=Table()}atEndOfWord(){return this.char===CHAR_NUM||this.char===CTRL_I||this.char===CHAR_SP||this.atEndOfLine()}atEndOfLine(){return this.char===e.END||this.char===CTRL_J||this.char===CTRL_M}parseStart(){if(this.char===e.END)return null;if(this.char===CHAR_LSQB)return this.call(this.parseTableOrList);if(this.char===CHAR_NUM)return this.call(this.parseComment);if(this.char===CTRL_J||this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M)return null;if(isAlphaNumQuoteHyphen(this.char))return this.callNow(this.parseAssignStatement);else throw this.error(new TomlError(`Unknown character "${this.char}"`))}parseWhitespaceToEOL(){if(this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M)return null;if(this.char===CHAR_NUM)return this.goto(this.parseComment);if(this.char===e.END||this.char===CTRL_J)return this.return();throw this.error(new TomlError("Unexpected character, expected only whitespace or comments till end of line"))}parseAssignStatement(){return this.callNow(this.parseAssign,this.recordAssignStatement)}recordAssignStatement(e){let t=this.ctx,n=e.key.pop();for(let r of e.key){if(hasKey(t,r)&&!isTable(t[r]))throw this.error(new TomlError("Can't redefine existing key"));t=t[r]=t[r]||Table()}if(hasKey(t,n))throw this.error(new TomlError("Can't redefine existing key"));return t[_declared]=!0,isInteger(e.value)||isFloat(e.value)?t[n]=e.value.valueOf():t[n]=e.value,this.goto(this.parseWhitespaceToEOL)}parseAssign(){return this.callNow(this.parseKeyword,this.recordAssignKeyword)}recordAssignKeyword(e){return this.state.resultTable?this.state.resultTable.push(e):this.state.resultTable=[e],this.goto(this.parseAssignKeywordPreDot)}parseAssignKeywordPreDot(){return this.char===CHAR_PERIOD?this.next(this.parseAssignKeywordPostDot):this.char!==CHAR_SP&&this.char!==CTRL_I?this.goto(this.parseAssignEqual):void 0}parseAssignKeywordPostDot(){if(this.char!==CHAR_SP&&this.char!==CTRL_I)return this.callNow(this.parseKeyword,this.recordAssignKeyword)}parseAssignEqual(){if(this.char===CHAR_EQUALS)return this.next(this.parseAssignPreValue);throw this.error(new TomlError('Invalid character, expected "="'))}parseAssignPreValue(){return this.char===CHAR_SP||this.char===CTRL_I?null:this.callNow(this.parseValue,this.recordAssignValue)}recordAssignValue(e){return this.returnNow({key:this.state.resultTable,value:e})}parseComment(){do{if(this.char===e.END||this.char===CTRL_J)return this.return();if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I)throw this.errorControlCharIn("comments")}while(this.nextChar())}parseTableOrList(){if(this.char!==CHAR_LSQB)return this.goto(this.parseTable);this.next(this.parseList)}parseTable(){return this.ctx=this.obj,this.goto(this.parseTableNext)}parseTableNext(){return this.char===CHAR_SP||this.char===CTRL_I?null:this.callNow(this.parseKeyword,this.parseTableMore)}parseTableMore(e){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===CHAR_RSQB){if(hasKey(this.ctx,e)&&(!isTable(this.ctx[e])||this.ctx[e][_declared]))throw this.error(new TomlError("Can't redefine existing key"));return this.ctx=this.ctx[e]=this.ctx[e]||Table(),this.ctx[_declared]=!0,this.next(this.parseWhitespaceToEOL)}if(this.char===CHAR_PERIOD){if(hasKey(this.ctx,e)){if(isTable(this.ctx[e]))this.ctx=this.ctx[e];else if(isList(this.ctx[e]))this.ctx=this.ctx[e][this.ctx[e].length-1];else throw this.error(new TomlError("Can't redefine existing key"))}else this.ctx=this.ctx[e]=Table();return this.next(this.parseTableNext)}throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"))}parseList(){return this.ctx=this.obj,this.goto(this.parseListNext)}parseListNext(){return this.char===CHAR_SP||this.char===CTRL_I?null:this.callNow(this.parseKeyword,this.parseListMore)}parseListMore(e){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===CHAR_RSQB){if(hasKey(this.ctx,e)||(this.ctx[e]=List()),isInlineList(this.ctx[e]))throw this.error(new TomlError("Can't extend an inline array"));if(isList(this.ctx[e])){let t=Table();this.ctx[e].push(t),this.ctx=t}else throw this.error(new TomlError("Can't redefine an existing key"));return this.next(this.parseListEnd)}if(this.char===CHAR_PERIOD){if(hasKey(this.ctx,e)){if(isInlineList(this.ctx[e]))throw this.error(new TomlError("Can't extend an inline array"));if(isInlineTable(this.ctx[e]))throw this.error(new TomlError("Can't extend an inline table"));else if(isList(this.ctx[e]))this.ctx=this.ctx[e][this.ctx[e].length-1];else if(isTable(this.ctx[e]))this.ctx=this.ctx[e];else throw this.error(new TomlError("Can't redefine an existing key"))}else this.ctx=this.ctx[e]=Table();return this.next(this.parseListNext)}throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"))}parseListEnd(e){if(this.char===CHAR_RSQB)return this.next(this.parseWhitespaceToEOL);throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"))}parseValue(){if(this.char===e.END)throw this.error(new TomlError("Key without value"));if(this.char===CHAR_QUOT)return this.next(this.parseDoubleString);if(this.char===CHAR_APOS)return this.next(this.parseSingleString);if(this.char===CHAR_HYPHEN||this.char===CHAR_PLUS)return this.goto(this.parseNumberSign);if(this.char===CHAR_i)return this.next(this.parseInf);if(this.char===CHAR_n)return this.next(this.parseNan);if(isDigit(this.char))return this.goto(this.parseNumberOrDateTime);else if(this.char===CHAR_t||this.char===CHAR_f)return this.goto(this.parseBoolean);else if(this.char===CHAR_LSQB)return this.call(this.parseInlineList,this.recordValue);else if(this.char===CHAR_LCUB)return this.call(this.parseInlineTable,this.recordValue);else throw this.error(new TomlError("Unexpected character, expecting string, number, datetime, boolean, inline array or inline table"))}recordValue(e){return this.returnNow(e)}parseInf(){if(this.char===CHAR_n)return this.next(this.parseInf2);throw this.error(new TomlError('Unexpected character, expected "inf", "+inf" or "-inf"'))}parseInf2(){if(this.char===CHAR_f)return"-"===this.state.buf?this.return(-1/0):this.return(1/0);throw this.error(new TomlError('Unexpected character, expected "inf", "+inf" or "-inf"'))}parseNan(){if(this.char===CHAR_a)return this.next(this.parseNan2);throw this.error(new TomlError('Unexpected character, expected "nan"'))}parseNan2(){if(this.char===CHAR_n)return this.return(NaN);throw this.error(new TomlError('Unexpected character, expected "nan"'))}parseKeyword(){return this.char===CHAR_QUOT?this.next(this.parseBasicString):this.char===CHAR_APOS?this.next(this.parseLiteralString):this.goto(this.parseBareKey)}parseBareKey(){do{if(this.char===e.END)throw this.error(new TomlError("Key ended without value"));if(isAlphaNumHyphen(this.char))this.consume();else if(0!==this.state.buf.length)return this.returnNow();else throw this.error(new TomlError("Empty bare keys are not allowed"))}while(this.nextChar())}parseSingleString(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiStringMaybe):this.goto(this.parseLiteralString)}parseLiteralString(){do{if(this.char===CHAR_APOS)return this.return();if(this.atEndOfLine())throw this.error(new TomlError("Unterminated string"));if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I)throw this.errorControlCharIn("strings");else this.consume()}while(this.nextChar())}parseLiteralMultiStringMaybe(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiString):this.returnNow()}parseLiteralMultiString(){return this.char===CTRL_M?null:this.char===CTRL_J?this.next(this.parseLiteralMultiStringContent):this.goto(this.parseLiteralMultiStringContent)}parseLiteralMultiStringContent(){do{if(this.char===CHAR_APOS)return this.next(this.parseLiteralMultiEnd);if(this.char===e.END)throw this.error(new TomlError("Unterminated multi-line string"));if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I&&this.char!==CTRL_J&&this.char!==CTRL_M)throw this.errorControlCharIn("strings");else this.consume()}while(this.nextChar())}parseLiteralMultiEnd(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiEnd2):(this.state.buf+="'",this.goto(this.parseLiteralMultiStringContent))}parseLiteralMultiEnd2(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiEnd3):(this.state.buf+="''",this.goto(this.parseLiteralMultiStringContent))}parseLiteralMultiEnd3(){return this.char===CHAR_APOS?(this.state.buf+="'",this.next(this.parseLiteralMultiEnd4)):this.returnNow()}parseLiteralMultiEnd4(){return this.char===CHAR_APOS?(this.state.buf+="'",this.return()):this.returnNow()}parseDoubleString(){return this.char===CHAR_QUOT?this.next(this.parseMultiStringMaybe):this.goto(this.parseBasicString)}parseBasicString(){do{if(this.char===CHAR_BSOL)return this.call(this.parseEscape,this.recordEscapeReplacement);if(this.char===CHAR_QUOT)return this.return();if(this.atEndOfLine())throw this.error(new TomlError("Unterminated string"));else if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I)throw this.errorControlCharIn("strings");else this.consume()}while(this.nextChar())}recordEscapeReplacement(e){return this.state.buf+=e,this.goto(this.parseBasicString)}parseMultiStringMaybe(){return this.char===CHAR_QUOT?this.next(this.parseMultiString):this.returnNow()}parseMultiString(){return this.char===CTRL_M?null:this.char===CTRL_J?this.next(this.parseMultiStringContent):this.goto(this.parseMultiStringContent)}parseMultiStringContent(){do{if(this.char===CHAR_BSOL)return this.call(this.parseMultiEscape,this.recordMultiEscapeReplacement);if(this.char===CHAR_QUOT)return this.next(this.parseMultiEnd);if(this.char===e.END)throw this.error(new TomlError("Unterminated multi-line string"));else if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I&&this.char!==CTRL_J&&this.char!==CTRL_M)throw this.errorControlCharIn("strings");else this.consume()}while(this.nextChar())}errorControlCharIn(e){let t="\\u00";return this.char<16&&(t+="0"),t+=this.char.toString(16),this.error(new TomlError(`Control characters (codes < 0x1f and 0x7f) are not allowed in ${e}, use ${t} instead`))}recordMultiEscapeReplacement(e){return this.state.buf+=e,this.goto(this.parseMultiStringContent)}parseMultiEnd(){return this.char===CHAR_QUOT?this.next(this.parseMultiEnd2):(this.state.buf+='"',this.goto(this.parseMultiStringContent))}parseMultiEnd2(){return this.char===CHAR_QUOT?this.next(this.parseMultiEnd3):(this.state.buf+='""',this.goto(this.parseMultiStringContent))}parseMultiEnd3(){return this.char===CHAR_QUOT?(this.state.buf+='"',this.next(this.parseMultiEnd4)):this.returnNow()}parseMultiEnd4(){return this.char===CHAR_QUOT?(this.state.buf+='"',this.return()):this.returnNow()}parseMultiEscape(){return this.char===CTRL_M||this.char===CTRL_J?this.next(this.parseMultiTrim):this.char===CHAR_SP||this.char===CTRL_I?this.next(this.parsePreMultiTrim):this.goto(this.parseEscape)}parsePreMultiTrim(){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===CTRL_M||this.char===CTRL_J)return this.next(this.parseMultiTrim);throw this.error(new TomlError("Can't escape whitespace"))}parseMultiTrim(){return this.char===CTRL_J||this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M?null:this.returnNow()}parseEscape(){if(this.char in escapes)return this.return(escapes[this.char]);if(this.char===CHAR_u)return this.call(this.parseSmallUnicode,this.parseUnicodeReturn);if(this.char===CHAR_U)return this.call(this.parseLargeUnicode,this.parseUnicodeReturn);throw this.error(new TomlError("Unknown escape character: "+this.char))}parseUnicodeReturn(e){try{let t=parseInt(e,16);if(t>=SURROGATE_FIRST&&t<=SURROGATE_LAST)throw this.error(new TomlError("Invalid unicode, character in range 0xD800 - 0xDFFF is reserved"));return this.returnNow(String.fromCodePoint(t))}catch(n){throw this.error(TomlError.wrap(n))}}parseSmallUnicode(){if(isHexit(this.char)){if(this.consume(),this.state.buf.length>=4)return this.return()}else throw this.error(new TomlError("Invalid character in unicode sequence, expected hex"))}parseLargeUnicode(){if(isHexit(this.char)){if(this.consume(),this.state.buf.length>=8)return this.return()}else throw this.error(new TomlError("Invalid character in unicode sequence, expected hex"))}parseNumberSign(){return this.consume(),this.next(this.parseMaybeSignedInfOrNan)}parseMaybeSignedInfOrNan(){return this.char===CHAR_i?this.next(this.parseInf):this.char===CHAR_n?this.next(this.parseNan):this.callNow(this.parseNoUnder,this.parseNumberIntegerStart)}parseNumberIntegerStart(){return this.char===CHAR_0?(this.consume(),this.next(this.parseNumberIntegerExponentOrDecimal)):this.goto(this.parseNumberInteger)}parseNumberIntegerExponentOrDecimal(){return this.char===CHAR_PERIOD?(this.consume(),this.call(this.parseNoUnder,this.parseNumberFloat)):this.char===CHAR_E||this.char===CHAR_e?(this.consume(),this.next(this.parseNumberExponentSign)):this.returnNow(Integer(this.state.buf))}parseNumberInteger(){if(isDigit(this.char))this.consume();else{if(this.char===CHAR_LOWBAR)return this.call(this.parseNoUnder);if(this.char===CHAR_E||this.char===CHAR_e)return this.consume(),this.next(this.parseNumberExponentSign);if(this.char===CHAR_PERIOD)return this.consume(),this.call(this.parseNoUnder,this.parseNumberFloat);let e=Integer(this.state.buf);if(!e.isNaN())return this.returnNow(e);throw this.error(new TomlError("Invalid number"))}}parseNoUnder(){if(this.char===CHAR_LOWBAR||this.char===CHAR_PERIOD||this.char===CHAR_E||this.char===CHAR_e)throw this.error(new TomlError("Unexpected character, expected digit"));if(this.atEndOfWord())throw this.error(new TomlError("Incomplete number"));return this.returnNow()}parseNoUnderHexOctBinLiteral(){if(this.char===CHAR_LOWBAR||this.char===CHAR_PERIOD)throw this.error(new TomlError("Unexpected character, expected digit"));if(this.atEndOfWord())throw this.error(new TomlError("Incomplete number"));return this.returnNow()}parseNumberFloat(){if(this.char===CHAR_LOWBAR)return this.call(this.parseNoUnder,this.parseNumberFloat);if(isDigit(this.char))this.consume();else if(this.char===CHAR_E||this.char===CHAR_e)return this.consume(),this.next(this.parseNumberExponentSign);else return this.returnNow(Float(this.state.buf))}parseNumberExponentSign(){if(isDigit(this.char))return this.goto(this.parseNumberExponent);if(this.char===CHAR_HYPHEN||this.char===CHAR_PLUS)this.consume(),this.call(this.parseNoUnder,this.parseNumberExponent);else throw this.error(new TomlError("Unexpected character, expected -, + or digit"))}parseNumberExponent(){if(isDigit(this.char))this.consume();else if(this.char===CHAR_LOWBAR)return this.call(this.parseNoUnder);else return this.returnNow(Float(this.state.buf))}parseNumberOrDateTime(){return this.char===CHAR_0?(this.consume(),this.next(this.parseNumberBaseOrDateTime)):this.goto(this.parseNumberOrDateTimeOnly)}parseNumberOrDateTimeOnly(){if(this.char===CHAR_LOWBAR)return this.call(this.parseNoUnder,this.parseNumberInteger);if(isDigit(this.char))this.consume(),this.state.buf.length>4&&this.next(this.parseNumberInteger);else if(this.char===CHAR_E||this.char===CHAR_e)return this.consume(),this.next(this.parseNumberExponentSign);else if(this.char===CHAR_PERIOD)return this.consume(),this.call(this.parseNoUnder,this.parseNumberFloat);else if(this.char===CHAR_HYPHEN)return this.goto(this.parseDateTime);else if(this.char===CHAR_COLON)return this.goto(this.parseOnlyTimeHour);else return this.returnNow(Integer(this.state.buf))}parseDateTimeOnly(){if(this.state.buf.length<4){if(isDigit(this.char))return this.consume();if(this.char===CHAR_COLON)return this.goto(this.parseOnlyTimeHour);throw this.error(new TomlError("Expected digit while parsing year part of a date"))}if(this.char===CHAR_HYPHEN)return this.goto(this.parseDateTime);throw this.error(new TomlError("Expected hyphen (-) while parsing year part of date"))}parseNumberBaseOrDateTime(){if(this.char===CHAR_b)return this.consume(),this.call(this.parseNoUnderHexOctBinLiteral,this.parseIntegerBin);if(this.char===CHAR_o)return this.consume(),this.call(this.parseNoUnderHexOctBinLiteral,this.parseIntegerOct);if(this.char===CHAR_x)return this.consume(),this.call(this.parseNoUnderHexOctBinLiteral,this.parseIntegerHex);if(this.char===CHAR_PERIOD)return this.goto(this.parseNumberInteger);if(isDigit(this.char))return this.goto(this.parseDateTimeOnly);else return this.returnNow(Integer(this.state.buf))}parseIntegerHex(){if(isHexit(this.char))this.consume();else{if(this.char===CHAR_LOWBAR)return this.call(this.parseNoUnderHexOctBinLiteral);let e=Integer(this.state.buf);if(!e.isNaN())return this.returnNow(e);throw this.error(new TomlError("Invalid number"))}}parseIntegerOct(){if(isOctit(this.char))this.consume();else{if(this.char===CHAR_LOWBAR)return this.call(this.parseNoUnderHexOctBinLiteral);let e=Integer(this.state.buf);if(!e.isNaN())return this.returnNow(e);throw this.error(new TomlError("Invalid number"))}}parseIntegerBin(){if(isBit(this.char))this.consume();else{if(this.char===CHAR_LOWBAR)return this.call(this.parseNoUnderHexOctBinLiteral);let e=Integer(this.state.buf);if(!e.isNaN())return this.returnNow(e);throw this.error(new TomlError("Invalid number"))}}parseDateTime(){if(this.state.buf.length<4)throw this.error(new TomlError("Years less than 1000 must be zero padded to four characters"));return this.state.result=this.state.buf,this.state.buf="",this.next(this.parseDateMonth)}parseDateMonth(){if(this.char===CHAR_HYPHEN){if(this.state.buf.length<2)throw this.error(new TomlError("Months less than 10 must be zero padded to two characters"));return this.state.result+="-"+this.state.buf,this.state.buf="",this.next(this.parseDateDay)}if(isDigit(this.char))this.consume();else throw this.error(new TomlError("Incomplete datetime"))}parseDateDay(){if(this.char===CHAR_T||this.char===CHAR_SP){if(this.state.buf.length<2)throw this.error(new TomlError("Days less than 10 must be zero padded to two characters"));return this.state.result+="-"+this.state.buf,this.state.buf="",this.next(this.parseStartTimeHour)}if(this.atEndOfWord())return this.returnNow(createDate(this.state.result+"-"+this.state.buf));if(isDigit(this.char))this.consume();else throw this.error(new TomlError("Incomplete datetime"))}parseStartTimeHour(){return this.atEndOfWord()?this.returnNow(createDate(this.state.result)):this.goto(this.parseTimeHour)}parseTimeHour(){if(this.char===CHAR_COLON){if(this.state.buf.length<2)throw this.error(new TomlError("Hours less than 10 must be zero padded to two characters"));return this.state.result+="T"+this.state.buf,this.state.buf="",this.next(this.parseTimeMin)}if(isDigit(this.char))this.consume();else throw this.error(new TomlError("Incomplete datetime"))}parseTimeMin(){if(this.state.buf.length<2&&isDigit(this.char))this.consume();else if(2===this.state.buf.length&&this.char===CHAR_COLON)return this.state.result+=":"+this.state.buf,this.state.buf="",this.next(this.parseTimeSec);else throw this.error(new TomlError("Incomplete datetime"))}parseTimeSec(){if(isDigit(this.char)){if(this.consume(),2===this.state.buf.length)return this.state.result+=":"+this.state.buf,this.state.buf="",this.next(this.parseTimeZoneOrFraction)}else throw this.error(new TomlError("Incomplete datetime"))}parseOnlyTimeHour(){if(this.char===CHAR_COLON){if(this.state.buf.length<2)throw this.error(new TomlError("Hours less than 10 must be zero padded to two characters"));return this.state.result=this.state.buf,this.state.buf="",this.next(this.parseOnlyTimeMin)}throw this.error(new TomlError("Incomplete time"))}parseOnlyTimeMin(){if(this.state.buf.length<2&&isDigit(this.char))this.consume();else if(2===this.state.buf.length&&this.char===CHAR_COLON)return this.state.result+=":"+this.state.buf,this.state.buf="",this.next(this.parseOnlyTimeSec);else throw this.error(new TomlError("Incomplete time"))}parseOnlyTimeSec(){if(isDigit(this.char)){if(this.consume(),2===this.state.buf.length)return this.next(this.parseOnlyTimeFractionMaybe)}else throw this.error(new TomlError("Incomplete time"))}parseOnlyTimeFractionMaybe(){if(this.state.result+=":"+this.state.buf,this.char!==CHAR_PERIOD)return this.return(createTime(this.state.result));this.state.buf="",this.next(this.parseOnlyTimeFraction)}parseOnlyTimeFraction(){if(isDigit(this.char))this.consume();else if(this.atEndOfWord()){if(0===this.state.buf.length)throw this.error(new TomlError("Expected digit in milliseconds"));return this.returnNow(createTime(this.state.result+"."+this.state.buf))}else throw this.error(new TomlError("Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z"))}parseTimeZoneOrFraction(){if(this.char===CHAR_PERIOD)this.consume(),this.next(this.parseDateTimeFraction);else if(this.char===CHAR_HYPHEN||this.char===CHAR_PLUS)this.consume(),this.next(this.parseTimeZoneHour);else if(this.char===CHAR_Z)return this.consume(),this.return(createDateTime(this.state.result+this.state.buf));else if(this.atEndOfWord())return this.returnNow(createDateTimeFloat(this.state.result+this.state.buf));else throw this.error(new TomlError("Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z"))}parseDateTimeFraction(){if(isDigit(this.char))this.consume();else if(1===this.state.buf.length)throw this.error(new TomlError("Expected digit in milliseconds"));else if(this.char===CHAR_HYPHEN||this.char===CHAR_PLUS)this.consume(),this.next(this.parseTimeZoneHour);else if(this.char===CHAR_Z)return this.consume(),this.return(createDateTime(this.state.result+this.state.buf));else if(this.atEndOfWord())return this.returnNow(createDateTimeFloat(this.state.result+this.state.buf));else throw this.error(new TomlError("Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z"))}parseTimeZoneHour(){if(isDigit(this.char)){if(this.consume(),/\d\d$/.test(this.state.buf))return this.next(this.parseTimeZoneSep)}else throw this.error(new TomlError("Unexpected character in datetime, expected digit"))}parseTimeZoneSep(){if(this.char===CHAR_COLON)this.consume(),this.next(this.parseTimeZoneMin);else throw this.error(new TomlError("Unexpected character in datetime, expected colon"))}parseTimeZoneMin(){if(isDigit(this.char)){if(this.consume(),/\d\d$/.test(this.state.buf))return this.return(createDateTime(this.state.result+this.state.buf))}else throw this.error(new TomlError("Unexpected character in datetime, expected digit"))}parseBoolean(){return this.char===CHAR_t?(this.consume(),this.next(this.parseTrue_r)):this.char===CHAR_f?(this.consume(),this.next(this.parseFalse_a)):void 0}parseTrue_r(){if(this.char===CHAR_r)return this.consume(),this.next(this.parseTrue_u);throw this.error(new TomlError("Invalid boolean, expected true or false"))}parseTrue_u(){if(this.char===CHAR_u)return this.consume(),this.next(this.parseTrue_e);throw this.error(new TomlError("Invalid boolean, expected true or false"))}parseTrue_e(){if(this.char===CHAR_e)return this.return(!0);throw this.error(new TomlError("Invalid boolean, expected true or false"))}parseFalse_a(){if(this.char===CHAR_a)return this.consume(),this.next(this.parseFalse_l);throw this.error(new TomlError("Invalid boolean, expected true or false"))}parseFalse_l(){if(this.char===CHAR_l)return this.consume(),this.next(this.parseFalse_s);throw this.error(new TomlError("Invalid boolean, expected true or false"))}parseFalse_s(){if(this.char===CHAR_s)return this.consume(),this.next(this.parseFalse_e);throw this.error(new TomlError("Invalid boolean, expected true or false"))}parseFalse_e(){if(this.char===CHAR_e)return this.return(!1);throw this.error(new TomlError("Invalid boolean, expected true or false"))}parseInlineList(){if(this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M||this.char===CTRL_J)return null;if(this.char===e.END)throw this.error(new TomlError("Unterminated inline array"));return this.char===CHAR_NUM?this.call(this.parseComment):this.char===CHAR_RSQB?this.return(this.state.resultArr||InlineList()):this.callNow(this.parseValue,this.recordInlineListValue)}recordInlineListValue(e){return this.state.resultArr||(this.state.resultArr=InlineList(tomlType(e))),isFloat(e)||isInteger(e)?this.state.resultArr.push(e.valueOf()):this.state.resultArr.push(e),this.goto(this.parseInlineListNext)}parseInlineListNext(){if(this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M||this.char===CTRL_J)return null;if(this.char===CHAR_NUM)return this.call(this.parseComment);if(this.char===CHAR_COMMA)return this.next(this.parseInlineList);if(this.char===CHAR_RSQB)return this.goto(this.parseInlineList);throw this.error(new TomlError("Invalid character, expected whitespace, comma (,) or close bracket (])"))}parseInlineTable(){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===e.END||this.char===CHAR_NUM||this.char===CTRL_J||this.char===CTRL_M)throw this.error(new TomlError("Unterminated inline array"));return this.char===CHAR_RCUB?this.return(this.state.resultTable||InlineTable()):(this.state.resultTable||(this.state.resultTable=InlineTable()),this.callNow(this.parseAssign,this.recordInlineTableValue))}recordInlineTableValue(e){let t=this.state.resultTable,n=e.key.pop();for(let r of e.key){if(hasKey(t,r)&&(!isTable(t[r])||t[r][_declared]))throw this.error(new TomlError("Can't redefine existing key"));t=t[r]=t[r]||Table()}if(hasKey(t,n))throw this.error(new TomlError("Can't redefine existing key"));return isInteger(e.value)||isFloat(e.value)?t[n]=e.value.valueOf():t[n]=e.value,this.goto(this.parseInlineTableNext)}parseInlineTableNext(){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===e.END||this.char===CHAR_NUM||this.char===CTRL_J||this.char===CTRL_M)throw this.error(new TomlError("Unterminated inline array"));if(this.char===CHAR_COMMA)return this.next(this.parseInlineTablePostComma);if(this.char===CHAR_RCUB)return this.goto(this.parseInlineTable);throw this.error(new TomlError("Invalid character, expected whitespace, comma (,) or close bracket (])"))}parseInlineTablePostComma(){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===e.END||this.char===CHAR_NUM||this.char===CTRL_J||this.char===CTRL_M)throw this.error(new TomlError("Unterminated inline array"));if(this.char===CHAR_COMMA)throw this.error(new TomlError("Empty elements in inline tables are not permitted"));if(this.char!==CHAR_RCUB)return this.goto(this.parseInlineTable);throw this.error(new TomlError("Trailing commas in inline tables are not permitted"))}}return t}},90560(e,t,n){"use strict";e.exports=a;let r=n(8676),i=n(22418);function a(e,t){t||(t={});let n=0,a=t.blocksize||40960,o=new r;return new Promise((e,t)=>{setImmediate(s,n,a,e,t)});function s(t,n,r,a){if(t>=e.length)try{return r(o.finish())}catch(u){return a(i(u,e))}try{o.parse(e.slice(t,t+n)),setImmediate(s,t+n,n,r,a)}catch(c){a(i(c,e))}}}},22418(e){"use strict";function t(e,t){if(null==e.pos||null==e.line)return e;let n=e.message;if(n+=` at row ${e.line+1}, col ${e.col+1}, pos ${e.pos}: +`,t&&t.split){let r=t.split(/\n/),i=String(Math.min(r.length,e.line+3)).length,a=" ";for(;a.length "+r[o]+"\n",n+=a+" ";for(let u=0;u{let i,a=!1,o=!1;function s(){if(a=!0,!i)try{n(t.finish())}catch(e){r(e)}}function u(e){o=!0,r(e)}function c(){i=!0;let n;for(;null!==(n=e.read());)try{t.parse(n)}catch(r){return u(r)}if(i=!1,a)return s();o||e.once("readable",c)}e.once("end",s),e.once("error",u),c()})}function s(){let e=new i;return new r.Transform({objectMode:!0,transform(t,n,r){try{e.parse(t.toString(n))}catch(i){this.emit("error",i)}r()},flush(t){try{this.push(e.finish())}catch(n){this.emit("error",n)}t()}})}},56530(e,t,n){"use strict";e.exports=a;let r=n(8676),i=n(22418);function a(e){n.g.Buffer&&n.g.Buffer.isBuffer(e)&&(e=e.toString("utf8"));let t=new r;try{return t.parse(e),t.finish()}catch(a){throw i(a,e)}}},83512(e,t,n){"use strict";e.exports=n(56530),e.exports.async=n(90560),e.exports.stream=n(6435),e.exports.prettyError=n(22418)},36921(e){"use strict";function t(e){if(null===e)throw n("null");if(void 0===e)throw n("undefined");if("object"!=typeof e)throw n(typeof e);if("function"==typeof e.toJSON&&(e=e.toJSON()),null==e)return null;let t=u(e);if("table"!==t)throw n(t);return o("","",e)}function n(e){return Error("Can only stringify objects, not "+e)}function r(e){return Object.keys(e).filter(t=>s(e[t]))}function i(e){return Object.keys(e).filter(t=>!s(e[t]))}function a(e){let t=Array.isArray(e)?[]:Object.prototype.hasOwnProperty.call(e,"__proto__")?{["__proto__"]:void 0}:{};for(let n of Object.keys(e))!e[n]||"function"!=typeof e[n].toJSON||"toISOString"in e[n]?t[n]=e[n]:t[n]=e[n].toJSON();return t}function o(e,t,n){let o,s;o=r(n=a(n)),s=i(n);let l=[],f=t||"";o.forEach(e=>{var t=u(n[e]);"undefined"!==t&&"null"!==t&&l.push(f+c(e)+" = "+b(n[e],!0))}),l.length>0&&l.push("");let d=e&&o.length>0?t+" ":"";return s.forEach(t=>{l.push(S(e,d,t,n[t]))}),l.join("\n")}function s(e){switch(u(e)){case"undefined":case"null":case"integer":case"nan":case"float":case"boolean":case"string":case"datetime":return!0;case"array":return 0===e.length||"table"!==u(e[0]);case"table":return 0===Object.keys(e).length;default:return!1}}function u(e){if(void 0===e)return"undefined";if(null===e)return"null";if("bigint"==typeof e||Number.isInteger(e)&&!Object.is(e,-0))return"integer";if("number"==typeof e)return"float";if("boolean"==typeof e)return"boolean";else if("string"==typeof e)return"string";else if("toISOString"in e)return isNaN(e)?"undefined":"datetime";else if(Array.isArray(e))return"array";else return"table"}function c(e){let t=String(e);return/^[-A-Za-z0-9_]+$/.test(t)?t:l(t)}function l(e){return'"'+h(e).replace(/"/g,'\\"')+'"'}function f(e){return"'"+e+"'"}function d(e,t){for(;t.length"\\u"+d(4,e.codePointAt(0).toString(16)))}function p(e){let t=e.split(/\n/).map(e=>h(e).replace(/"(?="")/g,'\\"')).join("\n");return'"'===t.slice(-1)&&(t+="\\\n"),'"""\n'+t+'"""'}function b(e,t){let n=u(e);return"string"===n&&(t&&/\n/.test(e)?n="string-multiline":!/[\b\t\n\f\r']/.test(e)&&/"/.test(e)&&(n="string-literal")),m(e,n)}function m(e,t){switch(t||(t=u(e)),t){case"string-multiline":return p(e);case"string":return l(e);case"string-literal":return f(e);case"integer":return g(e);case"float":return v(e);case"boolean":return y(e);case"datetime":return w(e);case"array":return _(e.filter(e=>"null"!==u(e)&&"undefined"!==u(e)&&"nan"!==u(e)));case"table":return E(e);default:throw n(t)}}function g(e){return String(e).replace(/\B(?=(\d{3})+(?!\d))/g,"_")}function v(e){if(e===1/0)return"inf";if(e===-1/0)return"-inf";if(Object.is(e,NaN))return"nan";if(Object.is(e,-0))return"-0.0";let[t,n]=String(e).split(".");return g(t)+"."+n}function y(e){return String(e)}function w(e){return e.toISOString()}function _(e){e=a(e);let t="[",n=e.map(e=>m(e));return n.join(", ").length>60||/\n/.test(n)?t+="\n "+n.join(",\n ")+"\n":t+=" "+n.join(", ")+(n.length>0?" ":""),t+"]"}function E(e){e=a(e);let t=[];return Object.keys(e).forEach(n=>{t.push(c(n)+" = "+b(e[n],!1))}),"{ "+t.join(", ")+(t.length>0?" ":"")+"}"}function S(e,t,r,i){let a=u(i);if("array"===a)return k(e,t,r,i);if("table"===a)return x(e,t,r,i);throw n(a)}function k(e,t,r,i){i=a(i);let s=u(i[0]);if("table"!==s)throw n(s);let l=e+c(r),f="";return i.forEach(e=>{f.length>0&&(f+="\n"),f+=t+"[["+l+"]]\n",f+=o(l+".",t,e)}),f}function x(e,t,n,i){let a=e+c(n),s="";return r(i).length>0&&(s+=t+"["+a+"]\n"),s+o(a+".",t,i)}e.exports=t,e.exports.value=m},5022(e,t,n){"use strict";t.parse=n(83512),t.stringify=n(36921)},46515(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184)),c=r(n(78252)),l=n(98741),f=r(n(68821)),d=function(e){var t="light"===e.palette.type?e.palette.grey[100]:e.palette.grey[900];return{root:{display:"flex",flexDirection:"column",width:"100%",boxSizing:"border-box",zIndex:e.zIndex.appBar,flexShrink:0},positionFixed:{position:"fixed",top:0,left:"auto",right:0},positionAbsolute:{position:"absolute",top:0,left:"auto",right:0},positionSticky:{position:"sticky",top:0,left:"auto",right:0},positionStatic:{position:"static"},positionRelative:{position:"relative"},colorDefault:{backgroundColor:t,color:e.palette.getContrastText(t)},colorPrimary:{backgroundColor:e.palette.primary.main,color:e.palette.primary.contrastText},colorSecondary:{backgroundColor:e.palette.secondary.main,color:e.palette.secondary.contrastText}}};function h(e){var t,n=e.children,r=e.classes,c=e.className,d=e.color,h=e.position,p=(0,o.default)(e,["children","classes","className","color","position"]),b=(0,u.default)(r.root,r["position".concat((0,l.capitalize)(h))],(t={},(0,a.default)(t,r["color".concat((0,l.capitalize)(d))],"inherit"!==d),(0,a.default)(t,"mui-fixed","fixed"===h),t),c);return s.default.createElement(f.default,(0,i.default)({square:!0,component:"header",elevation:4,className:b},p),n)}t.styles=d,h.defaultProps={color:"primary",position:"fixed"};var p=(0,c.default)(d,{name:"MuiAppBar"})(h);t.default=p},95880(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(46515))},68477(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(59713)),a=r(n(67154)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184));n(55252);var c=r(n(78252)),l=function(e){return{root:{position:"relative",display:"flex",alignItems:"center",justifyContent:"center",flexShrink:0,width:40,height:40,fontFamily:e.typography.fontFamily,fontSize:e.typography.pxToRem(20),borderRadius:"50%",overflow:"hidden",userSelect:"none"},colorDefault:{color:e.palette.background.default,backgroundColor:"light"===e.palette.type?e.palette.grey[400]:e.palette.grey[600]},img:{width:"100%",height:"100%",textAlign:"center",objectFit:"cover"}}};function f(e){var t=e.alt,n=e.children,r=e.childrenClassName,c=e.classes,l=e.className,f=e.component,d=e.imgProps,h=e.sizes,p=e.src,b=e.srcSet,m=(0,o.default)(e,["alt","children","childrenClassName","classes","className","component","imgProps","sizes","src","srcSet"]),g=null,v=p||b;return g=v?s.default.createElement("img",(0,a.default)({alt:t,src:p,srcSet:b,sizes:h,className:c.img},d)):r&&s.default.isValidElement(n)?s.default.cloneElement(n,{className:(0,u.default)(r,n.props.className)}):n,s.default.createElement(f,(0,a.default)({className:(0,u.default)(c.root,c.system,(0,i.default)({},c.colorDefault,!v),l)},m),g)}t.styles=l,f.defaultProps={component:"div"};var d=(0,c.default)(l,{name:"MuiAvatar"})(f);t.default=d},90338(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(68477))},9211(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184)),c=r(n(78252)),l=r(n(46408)),f={root:{zIndex:-1,position:"fixed",right:0,bottom:0,top:0,left:0,backgroundColor:"rgba(0, 0, 0, 0.5)",WebkitTapHighlightColor:"transparent",touchAction:"none"},invisible:{backgroundColor:"transparent"}};function d(e){var t=e.classes,n=e.className,r=e.invisible,c=e.open,f=e.transitionDuration,d=(0,o.default)(e,["classes","className","invisible","open","transitionDuration"]);return s.default.createElement(l.default,(0,i.default)({in:c,timeout:f},d),s.default.createElement("div",{className:(0,u.default)(t.root,(0,a.default)({},t.invisible,r),n),"aria-hidden":"true"}))}t.styles=f,d.defaultProps={invisible:!1};var h=(0,c.default)(f,{name:"MuiBackdrop"})(d);t.default=h},14983(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(9211))},84732(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184));n(55252);var c=r(n(78252)),l=n(98741),f=10,d=function(e){return{root:{position:"relative",display:"inline-flex",verticalAlign:"middle"},badge:{display:"flex",flexDirection:"row",flexWrap:"wrap",justifyContent:"center",alignContent:"center",alignItems:"center",position:"absolute",top:0,right:0,boxSizing:"border-box",fontFamily:e.typography.fontFamily,fontWeight:e.typography.fontWeightMedium,fontSize:e.typography.pxToRem(12),minWidth:2*f,padding:"0 4px",height:2*f,borderRadius:f,backgroundColor:e.palette.color,color:e.palette.textColor,zIndex:1,transform:"scale(1) translate(50%, -50%)",transformOrigin:"100% 0%",transition:e.transitions.create("transform",{easing:e.transitions.easing.easeInOut,duration:e.transitions.duration.enteringScreen})},colorPrimary:{backgroundColor:e.palette.primary.main,color:e.palette.primary.contrastText},colorSecondary:{backgroundColor:e.palette.secondary.main,color:e.palette.secondary.contrastText},colorError:{backgroundColor:e.palette.error.main,color:e.palette.error.contrastText},invisible:{transition:e.transitions.create("transform",{easing:e.transitions.easing.easeInOut,duration:e.transitions.duration.leavingScreen}),transform:"scale(0) translate(50%, -50%)",transformOrigin:"100% 0%"},dot:{height:6,minWidth:6,padding:0}}};function h(e){var t,n=e.badgeContent,r=e.children,c=e.classes,f=e.className,d=e.color,h=e.component,p=e.invisible,b=e.showZero,m=e.max,g=e.variant,v=(0,o.default)(e,["badgeContent","children","classes","className","color","component","invisible","showZero","max","variant"]),y=p;null!=p||0!==Number(n)||b||(y=!0);var w=(0,u.default)(c.badge,(t={},(0,a.default)(t,c["color".concat((0,l.capitalize)(d))],"default"!==d),(0,a.default)(t,c.invisible,y),(0,a.default)(t,c.dot,"dot"===g),t)),_="";return"dot"!==g&&(_=n>m?"".concat(m,"+"):n),s.default.createElement(h,(0,i.default)({className:(0,u.default)(c.root,f)},v),r,s.default.createElement("span",{className:w},_))}t.styles=d,h.defaultProps={color:"default",component:"span",max:99,showZero:!1,variant:"standard"};var p=(0,c.default)(d,{name:"MuiBadge"})(h);t.default=p},70398(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(84732))},21783(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(59713)),a=r(n(6479)),o=r(n(67154)),s=r(n(67294));r(n(45697));var u=r(n(94184));n(55252);var c=r(n(78252)),l=n(59114),f=r(n(16070)),d=n(98741),h=function(e){return{root:(0,o.default)({lineHeight:1.75},e.typography.button,{boxSizing:"border-box",minWidth:64,padding:"6px 16px",borderRadius:e.shape.borderRadius,color:e.palette.text.primary,transition:e.transitions.create(["background-color","box-shadow","border"],{duration:e.transitions.duration.short}),"&:hover":{textDecoration:"none",backgroundColor:(0,l.fade)(e.palette.text.primary,e.palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:"transparent"},"&$disabled":{backgroundColor:"transparent"}},"&$disabled":{color:e.palette.action.disabled}}),label:{width:"100%",display:"inherit",alignItems:"inherit",justifyContent:"inherit"},text:{padding:"6px 8px"},textPrimary:{color:e.palette.primary.main,"&:hover":{backgroundColor:(0,l.fade)(e.palette.primary.main,e.palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:"transparent"}}},textSecondary:{color:e.palette.secondary.main,"&:hover":{backgroundColor:(0,l.fade)(e.palette.secondary.main,e.palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:"transparent"}}},flat:{},flatPrimary:{},flatSecondary:{},outlined:{padding:"5px 16px",border:"1px solid ".concat("light"===e.palette.type?"rgba(0, 0, 0, 0.23)":"rgba(255, 255, 255, 0.23)"),"&$disabled":{border:"1px solid ".concat(e.palette.action.disabled)}},outlinedPrimary:{color:e.palette.primary.main,border:"1px solid ".concat((0,l.fade)(e.palette.primary.main,.5)),"&:hover":{border:"1px solid ".concat(e.palette.primary.main),backgroundColor:(0,l.fade)(e.palette.primary.main,e.palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:"transparent"}}},outlinedSecondary:{color:e.palette.secondary.main,border:"1px solid ".concat((0,l.fade)(e.palette.secondary.main,.5)),"&:hover":{border:"1px solid ".concat(e.palette.secondary.main),backgroundColor:(0,l.fade)(e.palette.secondary.main,e.palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:"transparent"}},"&$disabled":{border:"1px solid ".concat(e.palette.action.disabled)}},contained:{color:e.palette.getContrastText(e.palette.grey[300]),backgroundColor:e.palette.grey[300],boxShadow:e.shadows[2],"&$focusVisible":{boxShadow:e.shadows[6]},"&:active":{boxShadow:e.shadows[8]},"&$disabled":{color:e.palette.action.disabled,boxShadow:e.shadows[0],backgroundColor:e.palette.action.disabledBackground},"&:hover":{backgroundColor:e.palette.grey.A100,"@media (hover: none)":{backgroundColor:e.palette.grey[300]},"&$disabled":{backgroundColor:e.palette.action.disabledBackground}}},containedPrimary:{color:e.palette.primary.contrastText,backgroundColor:e.palette.primary.main,"&:hover":{backgroundColor:e.palette.primary.dark,"@media (hover: none)":{backgroundColor:e.palette.primary.main}}},containedSecondary:{color:e.palette.secondary.contrastText,backgroundColor:e.palette.secondary.main,"&:hover":{backgroundColor:e.palette.secondary.dark,"@media (hover: none)":{backgroundColor:e.palette.secondary.main}}},raised:{},raisedPrimary:{},raisedSecondary:{},fab:{borderRadius:"50%",padding:0,minWidth:0,width:56,height:56,boxShadow:e.shadows[6],"&:active":{boxShadow:e.shadows[12]}},extendedFab:{borderRadius:24,padding:"0 16px",width:"auto",minWidth:48,height:48},focusVisible:{},disabled:{},colorInherit:{color:"inherit",borderColor:"currentColor"},mini:{width:40,height:40},sizeSmall:{padding:"4px 8px",minWidth:64,fontSize:e.typography.pxToRem(13)},sizeLarge:{padding:"8px 24px",fontSize:e.typography.pxToRem(15)},fullWidth:{width:"100%"}}};function p(e){var t,n=e.children,r=e.classes,c=e.className,l=e.color,h=e.disabled,p=e.disableFocusRipple,b=e.focusVisibleClassName,m=e.fullWidth,g=e.mini,v=e.size,y=e.variant,w=(0,a.default)(e,["children","classes","className","color","disabled","disableFocusRipple","focusVisibleClassName","fullWidth","mini","size","variant"]),_="fab"===y||"extendedFab"===y,E="contained"===y||"raised"===y,S="text"===y||"flat"===y,k=(0,u.default)(r.root,(t={},(0,i.default)(t,r.fab,_),(0,i.default)(t,r.mini,_&&g),(0,i.default)(t,r.extendedFab,"extendedFab"===y),(0,i.default)(t,r.text,S),(0,i.default)(t,r.textPrimary,S&&"primary"===l),(0,i.default)(t,r.textSecondary,S&&"secondary"===l),(0,i.default)(t,r.flat,S),(0,i.default)(t,r.flatPrimary,S&&"primary"===l),(0,i.default)(t,r.flatSecondary,S&&"secondary"===l),(0,i.default)(t,r.contained,E||_),(0,i.default)(t,r.containedPrimary,(E||_)&&"primary"===l),(0,i.default)(t,r.containedSecondary,(E||_)&&"secondary"===l),(0,i.default)(t,r.raised,E||_),(0,i.default)(t,r.raisedPrimary,(E||_)&&"primary"===l),(0,i.default)(t,r.raisedSecondary,(E||_)&&"secondary"===l),(0,i.default)(t,r.outlined,"outlined"===y),(0,i.default)(t,r.outlinedPrimary,"outlined"===y&&"primary"===l),(0,i.default)(t,r.outlinedSecondary,"outlined"===y&&"secondary"===l),(0,i.default)(t,r["size".concat((0,d.capitalize)(v))],"medium"!==v),(0,i.default)(t,r.disabled,h),(0,i.default)(t,r.fullWidth,m),(0,i.default)(t,r.colorInherit,"inherit"===l),t),c);return s.default.createElement(f.default,(0,o.default)({className:k,disabled:h,focusRipple:!p,focusVisibleClassName:(0,u.default)(r.focusVisible,b)},w),s.default.createElement("span",{className:r.label},n))}t.styles=h,p.defaultProps={color:"default",component:"button",disabled:!1,disableFocusRipple:!1,fullWidth:!1,mini:!1,size:"medium",type:"button",variant:"text"};var b=(0,c.default)(h,{name:"MuiButton"})(p);t.default=b},83638(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(21783))},74610(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(34575)),u=r(n(93913)),c=r(n(78585)),l=r(n(29754)),f=r(n(2205)),d=r(n(81506)),h=r(n(67294));r(n(45697));var p=r(n(73935)),b=r(n(94184));n(55252);var m=r(n(62614)),g=r(n(78252)),v=r(n(78582)),y=n(32252),w=r(n(65406)),_=r(n(83673)),E={root:{display:"inline-flex",alignItems:"center",justifyContent:"center",position:"relative",WebkitTapHighlightColor:"transparent",backgroundColor:"transparent",outline:"none",border:0,margin:0,borderRadius:0,padding:0,cursor:"pointer",userSelect:"none",verticalAlign:"middle","-moz-appearance":"none","-webkit-appearance":"none",textDecoration:"none",color:"inherit","&::-moz-focus-inner":{borderStyle:"none"},"&$disabled":{pointerEvents:"none",cursor:"default"}},disabled:{},focusVisible:{}};t.styles=E;var S=function(e){function t(){(0,s.default)(this,t);for(var e,n,r=arguments.length,i=Array(r),a=0;a0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},s=arguments.length>2?arguments[2]:void 0,u=o.pulsate,c=void 0!==u&&u,l=o.center,f=void 0===l?n.props.center||o.pulsate:l,h=o.fakeElement,b=void 0!==h&&h;if("mousedown"===a.type&&n.ignoringMouseDown){n.ignoringMouseDown=!1;return}"touchstart"===a.type&&(n.ignoringMouseDown=!0);var m=b?null:p.default.findDOMNode((0,d.default)((0,d.default)(n))),g=m?m.getBoundingClientRect():{width:0,height:0,left:0,top:0};if(!f&&(0!==a.clientX||0!==a.clientY)&&(a.clientX||a.touches)){var v=a.clientX?a.clientX:a.touches[0].clientX,y=a.clientY?a.clientY:a.touches[0].clientY;t=Math.round(v-g.left),r=Math.round(y-g.top)}else t=Math.round(g.width/2),r=Math.round(g.height/2);if(f)(i=Math.sqrt((2*Math.pow(g.width,2)+Math.pow(g.height,2))/3))%2==0&&(i+=1);else{i=Math.sqrt(Math.pow(2*Math.max(Math.abs((m?m.clientWidth:0)-t),t)+2,2)+Math.pow(2*Math.max(Math.abs((m?m.clientHeight:0)-r),r)+2,2))}a.touches?(n.startTimerCommit=function(){n.startCommit({pulsate:c,rippleX:t,rippleY:r,rippleSize:i,cb:s})},n.startTimer=setTimeout(function(){n.startTimerCommit&&(n.startTimerCommit(),n.startTimerCommit=null)},w)):n.startCommit({pulsate:c,rippleX:t,rippleY:r,rippleSize:i,cb:s})},n.startCommit=function(e){var t=e.pulsate,r=e.rippleX,i=e.rippleY,a=e.rippleSize,s=e.cb;n.setState(function(e){return{nextKey:e.nextKey+1,ripples:[].concat((0,o.default)(e.ripples),[h.default.createElement(v.default,{key:e.nextKey,classes:n.props.classes,timeout:{exit:y,enter:y},pulsate:t,rippleX:r,rippleY:i,rippleSize:a})])}},s)},n.stop=function(e,t){clearTimeout(n.startTimer);var r=n.state.ripples;if("touchend"===e.type&&n.startTimerCommit){e.persist(),n.startTimerCommit(),n.startTimerCommit=null,n.startTimer=setTimeout(function(){n.stop(e,t)});return}n.startTimerCommit=null,r&&r.length&&n.setState({ripples:r.slice(1)},t)},n}return(0,f.default)(t,e),(0,u.default)(t,[{key:"componentWillUnmount",value:function(){clearTimeout(this.startTimer)}},{key:"render",value:function(){var e=this.props,t=(e.center,e.classes),n=e.className,r=(0,a.default)(e,["center","classes","className"]);return h.default.createElement(b.default,(0,i.default)({component:"span",enter:!0,exit:!0,className:(0,m.default)(t.root,n)},r),this.state.ripples)}}]),t}(h.default.PureComponent);E.defaultProps={center:!1};var S=(0,g.default)(_,{flip:!1,name:"MuiTouchRipple"})(E);t.default=S},83673(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n=function(e,t,n,r){return function(i){r&&r.call(e,i);var a=!1;return i.defaultPrevented&&(a=!0),e.props.disableTouchRipple&&"Blur"!==t&&(a=!0),!a&&e.ripple&&e.ripple[n](i),"function"==typeof e.props["on".concat(t)]&&e.props["on".concat(t)](i),!0}};"undefined"==typeof window&&(n=function(){return function(){}});var r=n;t.default=r},32252(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.detectFocusVisible=s,t.listenForFocusKeys=f,r(n(42473));var i=r(n(16143)),a={focusKeyPressed:!1,keyUpEventTimeout:-1};function o(e){for(var t=e.activeElement;t&&t.shadowRoot&&t.shadowRoot.activeElement;)t=t.shadowRoot.activeElement;return t}function s(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1;e.focusVisibleTimeout=setTimeout(function(){var u=(0,i.default)(t),c=o(u);a.focusKeyPressed&&(c===t||t.contains(c))?n():r-1}var l=function(e){c(e)&&(a.focusKeyPressed=!0,clearTimeout(a.keyUpEventTimeout),a.keyUpEventTimeout=setTimeout(function(){a.focusKeyPressed=!1},500))};function f(e){e.addEventListener("keyup",l)}},16070(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(74610))},46003(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(67294));r(n(45697));var s=r(n(94184)),u=r(n(68821)),c=r(n(78252)),l={root:{overflow:"hidden"}};function f(e){var t=e.classes,n=e.className,r=e.raised,c=(0,a.default)(e,["classes","className","raised"]);return o.default.createElement(u.default,(0,i.default)({className:(0,s.default)(t.root,n),elevation:r?8:1},c))}t.styles=l,f.defaultProps={raised:!1};var d=(0,c.default)(l,{name:"MuiCard"})(f);t.default=d},82204(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(46003))},5780(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(67294));r(n(45697));var s=r(n(94184));n(55252);var u=r(n(78252)),c={root:{padding:16,"&:last-child":{paddingBottom:24}}};function l(e){var t=e.classes,n=e.className,r=e.component,u=(0,a.default)(e,["classes","className","component"]);return o.default.createElement(r,(0,i.default)({className:(0,s.default)(t.root,n)},u))}t.styles=c,l.defaultProps={component:"div"};var f=(0,u.default)(c,{name:"MuiCardContent"})(l);t.default=f},30060(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(5780))},50704(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(67294));r(n(45697));var s=r(n(94184));n(55252);var u=r(n(78252)),c=r(n(71426)),l={root:{display:"flex",alignItems:"center",padding:16},avatar:{flex:"0 0 auto",marginRight:16},action:{flex:"0 0 auto",alignSelf:"flex-start",marginTop:-8,marginRight:-8},content:{flex:"1 1 auto"},title:{},subheader:{}};function f(e){var t=e.action,n=e.avatar,r=e.classes,u=e.className,l=e.component,f=e.disableTypography,d=e.subheader,h=e.subheaderTypographyProps,p=e.title,b=e.titleTypographyProps,m=(0,a.default)(e,["action","avatar","classes","className","component","disableTypography","subheader","subheaderTypographyProps","title","titleTypographyProps"]),g=p;null==g||g.type===c.default||f||(g=o.default.createElement(c.default,(0,i.default)({variant:n?"body2":"headline",internalDeprecatedVariant:!0,className:r.title,component:"span"},b),g));var v=d;return null==v||v.type===c.default||f||(v=o.default.createElement(c.default,(0,i.default)({variant:n?"body2":"body1",className:r.subheader,color:"textSecondary",component:"span"},h),v)),o.default.createElement(l,(0,i.default)({className:(0,s.default)(r.root,u)},m),n&&o.default.createElement("div",{className:r.avatar},n),o.default.createElement("div",{className:r.content},g,v),t&&o.default.createElement("div",{className:r.action},t))}t.styles=l,f.defaultProps={component:"div",disableTypography:!1};var d=(0,u.default)(l,{name:"MuiCardHeader"})(f);t.default=d},52658(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(50704))},82811(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184)),c=r(n(85609)),l=r(n(42159)),f=r(n(41549)),d=r(n(61486)),h=n(98741),p=r(n(78252)),b=function(e){return{root:{color:e.palette.text.secondary},checked:{},disabled:{},indeterminate:{},colorPrimary:{"&$checked":{color:e.palette.primary.main},"&$disabled":{color:e.palette.action.disabled}},colorSecondary:{"&$checked":{color:e.palette.secondary.main},"&$disabled":{color:e.palette.action.disabled}}}};function m(e){var t=e.checkedIcon,n=e.classes,r=e.className,l=e.color,f=e.icon,d=e.indeterminate,p=e.indeterminateIcon,b=e.inputProps,m=(0,o.default)(e,["checkedIcon","classes","className","color","icon","indeterminate","indeterminateIcon","inputProps"]);return s.default.createElement(c.default,(0,i.default)({type:"checkbox",checkedIcon:d?p:t,className:(0,u.default)((0,a.default)({},n.indeterminate,d),r),classes:{root:(0,u.default)(n.root,n["color".concat((0,h.capitalize)(l))]),checked:n.checked,disabled:n.disabled},inputProps:(0,i.default)({"data-indeterminate":d},b),icon:d?p:f},m))}t.styles=b,m.defaultProps={checkedIcon:s.default.createElement(f.default,null),color:"secondary",icon:s.default.createElement(l.default,null),indeterminate:!1,indeterminateIcon:s.default.createElement(d.default,null)};var g=(0,p.default)(b,{name:"MuiCheckbox"})(m);t.default=g},71209(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(82811))},16444(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(34575)),u=r(n(93913)),c=r(n(78585)),l=r(n(29754)),f=r(n(2205)),d=r(n(67294));r(n(45697));var h=r(n(94184));r(n(42473)),n(55252);var p=r(n(99781)),b=r(n(78252)),m=n(59114);r(n(21677));var g=n(98741);n(68477);var v=function(e){var t=32,n="light"===e.palette.type?e.palette.grey[300]:e.palette.grey[700],r=(0,m.fade)(e.palette.text.primary,.26);return{root:{fontFamily:e.typography.fontFamily,fontSize:e.typography.pxToRem(13),display:"inline-flex",alignItems:"center",justifyContent:"center",height:t,color:e.palette.getContrastText(n),backgroundColor:n,borderRadius:t/2,whiteSpace:"nowrap",transition:e.transitions.create(["background-color","box-shadow"]),cursor:"default",outline:"none",textDecoration:"none",border:"none",padding:0,verticalAlign:"middle",boxSizing:"border-box"},colorPrimary:{backgroundColor:e.palette.primary.main,color:e.palette.primary.contrastText},colorSecondary:{backgroundColor:e.palette.secondary.main,color:e.palette.secondary.contrastText},clickable:{WebkitTapHighlightColor:"transparent",cursor:"pointer","&:hover, &:focus":{backgroundColor:(0,m.emphasize)(n,.08)},"&:active":{boxShadow:e.shadows[1],backgroundColor:(0,m.emphasize)(n,.12)}},clickableColorPrimary:{"&:hover, &:focus":{backgroundColor:(0,m.emphasize)(e.palette.primary.main,.08)},"&:active":{backgroundColor:(0,m.emphasize)(e.palette.primary.main,.12)}},clickableColorSecondary:{"&:hover, &:focus":{backgroundColor:(0,m.emphasize)(e.palette.secondary.main,.08)},"&:active":{backgroundColor:(0,m.emphasize)(e.palette.secondary.main,.12)}},deletable:{"&:focus":{backgroundColor:(0,m.emphasize)(n,.08)}},deletableColorPrimary:{"&:focus":{backgroundColor:(0,m.emphasize)(e.palette.primary.main,.2)}},deletableColorSecondary:{"&:focus":{backgroundColor:(0,m.emphasize)(e.palette.secondary.main,.2)}},outlined:{backgroundColor:"transparent",border:"1px solid ".concat("light"===e.palette.type?"rgba(0, 0, 0, 0.23)":"rgba(255, 255, 255, 0.23)"),"$clickable&:hover, $clickable&:focus, $deletable&:focus":{backgroundColor:(0,m.fade)(e.palette.text.primary,e.palette.action.hoverOpacity)},"& $avatar":{marginLeft:-1}},outlinedPrimary:{color:e.palette.primary.main,border:"1px solid ".concat(e.palette.primary.main),"$clickable&:hover, $clickable&:focus, $deletable&:focus":{backgroundColor:(0,m.fade)(e.palette.primary.main,e.palette.action.hoverOpacity)}},outlinedSecondary:{color:e.palette.secondary.main,border:"1px solid ".concat(e.palette.secondary.main),"$clickable&:hover, $clickable&:focus, $deletable&:focus":{backgroundColor:(0,m.fade)(e.palette.secondary.main,e.palette.action.hoverOpacity)}},avatar:{marginRight:-4,width:t,height:t,color:"light"===e.palette.type?e.palette.grey[700]:e.palette.grey[300],fontSize:e.typography.pxToRem(16)},avatarColorPrimary:{color:e.palette.primary.contrastText,backgroundColor:e.palette.primary.dark},avatarColorSecondary:{color:e.palette.secondary.contrastText,backgroundColor:e.palette.secondary.dark},avatarChildren:{width:19,height:19},icon:{color:"light"===e.palette.type?e.palette.grey[700]:e.palette.grey[300],marginLeft:4,marginRight:-8},iconColorPrimary:{color:"inherit"},iconColorSecondary:{color:"inherit"},label:{display:"flex",alignItems:"center",paddingLeft:12,paddingRight:12,userSelect:"none",whiteSpace:"nowrap",cursor:"inherit"},deleteIcon:{WebkitTapHighlightColor:"transparent",color:r,cursor:"pointer",height:"auto",margin:"0 4px 0 -8px","&:hover":{color:(0,m.fade)(r,.4)}},deleteIconColorPrimary:{color:(0,m.fade)(e.palette.primary.contrastText,.7),"&:hover, &:active":{color:e.palette.primary.contrastText}},deleteIconColorSecondary:{color:(0,m.fade)(e.palette.secondary.contrastText,.7),"&:hover, &:active":{color:e.palette.secondary.contrastText}},deleteIconOutlinedColorPrimary:{color:(0,m.fade)(e.palette.primary.main,.7),"&:hover, &:active":{color:e.palette.primary.main}},deleteIconOutlinedColorSecondary:{color:(0,m.fade)(e.palette.secondary.main,.7),"&:hover, &:active":{color:e.palette.secondary.main}}}};t.styles=v;var y=function(e){function t(){(0,s.default)(this,t);for(var e,n,r=arguments.length,i=Array(r),a=0;a :last-child":{paddingRight:32},"&$expanded":{margin:"20px 0"}},expandIcon:{position:"absolute",top:"50%",right:8,transform:"translateY(-50%) rotate(0deg)",transition:e.transitions.create("transform",t),"&:hover":{backgroundColor:"transparent"},"&$expanded":{transform:"translateY(-50%) rotate(180deg)"}}}};t.styles=g;var v=function(e){function t(){(0,s.default)(this,t);for(var e,n,r=arguments.length,i=Array(r),a=0;a $item":{padding:e/2}})}),n}var b=function(e){return(0,o.default)({container:{boxSizing:"border-box",display:"flex",flexWrap:"wrap",width:"100%"},item:{boxSizing:"border-box",margin:"0"},zeroMinWidth:{minWidth:0},"direction-xs-column":{flexDirection:"column"},"direction-xs-column-reverse":{flexDirection:"column-reverse"},"direction-xs-row-reverse":{flexDirection:"row-reverse"},"wrap-xs-nowrap":{flexWrap:"nowrap"},"wrap-xs-wrap-reverse":{flexWrap:"wrap-reverse"},"align-items-xs-center":{alignItems:"center"},"align-items-xs-flex-start":{alignItems:"flex-start"},"align-items-xs-flex-end":{alignItems:"flex-end"},"align-items-xs-baseline":{alignItems:"baseline"},"align-content-xs-center":{alignContent:"center"},"align-content-xs-flex-start":{alignContent:"flex-start"},"align-content-xs-flex-end":{alignContent:"flex-end"},"align-content-xs-space-between":{alignContent:"space-between"},"align-content-xs-space-around":{alignContent:"space-around"},"justify-xs-center":{justifyContent:"center"},"justify-xs-flex-end":{justifyContent:"flex-end"},"justify-xs-space-between":{justifyContent:"space-between"},"justify-xs-space-around":{justifyContent:"space-around"},"justify-xs-space-evenly":{justifyContent:"space-evenly"}},p(e,"xs"),l.keys.reduce(function(t,n){return h(t,e,n),t},{}))};function m(e){var t,n=e.alignContent,r=e.alignItems,c=e.classes,l=e.className,f=e.component,d=e.container,h=e.direction,p=e.item,b=e.justify,g=e.lg,v=e.md,y=e.sm,w=e.spacing,_=e.wrap,E=e.xl,S=e.xs,k=e.zeroMinWidth,x=(0,a.default)(e,["alignContent","alignItems","classes","className","component","container","direction","item","justify","lg","md","sm","spacing","wrap","xl","xs","zeroMinWidth"]),T=(0,u.default)((t={},(0,i.default)(t,c.container,d),(0,i.default)(t,c.item,p),(0,i.default)(t,c.zeroMinWidth,k),(0,i.default)(t,c["spacing-xs-".concat(String(w))],d&&0!==w),(0,i.default)(t,c["direction-xs-".concat(String(h))],h!==m.defaultProps.direction),(0,i.default)(t,c["wrap-xs-".concat(String(_))],_!==m.defaultProps.wrap),(0,i.default)(t,c["align-items-xs-".concat(String(r))],r!==m.defaultProps.alignItems),(0,i.default)(t,c["align-content-xs-".concat(String(n))],n!==m.defaultProps.alignContent),(0,i.default)(t,c["justify-xs-".concat(String(b))],b!==m.defaultProps.justify),(0,i.default)(t,c["grid-xs-".concat(String(S))],!1!==S),(0,i.default)(t,c["grid-sm-".concat(String(y))],!1!==y),(0,i.default)(t,c["grid-md-".concat(String(v))],!1!==v),(0,i.default)(t,c["grid-lg-".concat(String(g))],!1!==g),(0,i.default)(t,c["grid-xl-".concat(String(E))],!1!==E),t),l);return s.default.createElement(f,(0,o.default)({className:T},x))}t.styles=b,m.defaultProps={alignContent:"stretch",alignItems:"stretch",component:"div",container:!1,direction:"row",item:!1,justify:"flex-start",lg:!1,md:!1,sm:!1,spacing:0,wrap:"wrap",xl:!1,xs:!1,zeroMinWidth:!1};var g,v=(0,c.default)(b,{name:"MuiGrid"})(m);t.default=v},97779(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(27973))},57205(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(67294));r(n(45697));var d=r(n(60644)),h=r(n(82313)),p=n(41929);function b(e){return"scale(".concat(e,", ").concat(Math.pow(e,2),")")}var m={entering:{opacity:1,transform:b(1)},entered:{opacity:1,transform:"".concat(b(1)," translateZ(0)")}},g=function(e){function t(){(0,o.default)(this,t);for(var e,n,r=arguments.length,i=Array(r),a=0;a=Number(e.rows)&&(n=Math.min(Number(e.rowsMax)*t,n)),n=Math.max(n,t),Math.abs(this.state.height-n)>1&&this.setState({height:n}))}}},{key:"render",value:function(){var e=this.props,t=e.classes,n=e.className,r=e.defaultValue,o=(e.onChange,e.rows),s=(e.rowsMax,e.style),u=(e.textareaRef,e.value),c=(0,a.default)(e,["classes","className","defaultValue","onChange","rows","rowsMax","style","textareaRef","value"]);return f.default.createElement("div",{className:t.root},f.default.createElement(p.default,{target:"window",onResize:this.handleResize}),f.default.createElement("textarea",{"aria-hidden":"true",className:(0,d.default)(t.textarea,t.shadow),readOnly:!0,ref:this.handleRefSinglelineShadow,rows:"1",tabIndex:-1,value:""}),f.default.createElement("textarea",{"aria-hidden":"true",className:(0,d.default)(t.textarea,t.shadow),defaultValue:r,readOnly:!0,ref:this.handleRefShadow,rows:o,tabIndex:-1,value:u}),f.default.createElement("textarea",(0,i.default)({rows:o,className:(0,d.default)(t.textarea,n),defaultValue:r,value:u,onChange:this.handleChange,ref:this.handleRefInput,style:(0,i.default)({height:this.state.height},s)},c)))}}]),t}(f.default.Component);y.defaultProps={rows:1};var w=(0,b.default)(v,{name:"MuiPrivateTextarea"})(y);t.default=w},67598(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(62010))},78586(e,t){"use strict";function n(e){return null!=e&&!(Array.isArray(e)&&0===e.length)}function r(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return e&&(n(e.value)&&""!==e.value||t&&n(e.defaultValue)&&""!==e.defaultValue)}function i(e){return e.startAdornment}Object.defineProperty(t,"__esModule",{value:!0}),t.hasValue=n,t.isFilled=r,t.isAdornedStart=i},56030(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184)),c=r(n(58189)),l=r(n(52598)),f=r(n(78252)),d=r(n(69645)),h=function(e){return{root:{transformOrigin:"top left"},focused:{},disabled:{},error:{},required:{},formControl:{position:"absolute",left:0,top:0,transform:"translate(0, 24px) scale(1)"},marginDense:{transform:"translate(0, 21px) scale(1)"},shrink:{transform:"translate(0, 1.5px) scale(0.75)",transformOrigin:"top left"},animated:{transition:e.transitions.create(["color","transform"],{duration:e.transitions.duration.shorter,easing:e.transitions.easing.easeOut})},filled:{zIndex:1,pointerEvents:"none",transform:"translate(12px, 20px) scale(1)","&$marginDense":{transform:"translate(12px, 17px) scale(1)"},"&$shrink":{transform:"translate(12px, 10px) scale(0.75)","&$marginDense":{transform:"translate(12px, 7px) scale(0.75)"}}},outlined:{zIndex:1,pointerEvents:"none",transform:"translate(14px, 20px) scale(1)","&$marginDense":{transform:"translate(14px, 17px) scale(1)"},"&$shrink":{transform:"translate(14px, -6px) scale(0.75)"}}}};function p(e){var t,n=e.children,r=e.classes,l=e.className,f=e.disableAnimation,h=e.FormLabelClasses,p=(e.margin,e.muiFormControl),b=e.shrink,m=(e.variant,(0,o.default)(e,["children","classes","className","disableAnimation","FormLabelClasses","margin","muiFormControl","shrink","variant"])),g=b;void 0===g&&p&&(g=p.filled||p.focused||p.adornedStart);var v=(0,c.default)({props:e,muiFormControl:p,states:["margin","variant"]}),y=(0,u.default)(r.root,(t={},(0,a.default)(t,r.formControl,p),(0,a.default)(t,r.animated,!f),(0,a.default)(t,r.shrink,g),(0,a.default)(t,r.marginDense,"dense"===v.margin),(0,a.default)(t,r.filled,"filled"===v.variant),(0,a.default)(t,r.outlined,"outlined"===v.variant),t),l);return s.default.createElement(d.default,(0,i.default)({"data-shrink":g,className:y,classes:(0,i.default)({focused:r.focused,disabled:r.disabled,error:r.error,required:r.required},h)},m),n)}t.styles=h,p.defaultProps={disableAnimation:!1};var b=(0,f.default)(h,{name:"MuiInputLabel"})((0,l.default)(p));t.default=b},23153(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(56030))},46616(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184));r(n(42473));var c=r(n(78252)),l=n(59114),f=4,d=function(e){return{root:{position:"relative",overflow:"hidden",height:4},colorPrimary:{backgroundColor:(0,l.lighten)(e.palette.primary.light,.6)},colorSecondary:{backgroundColor:(0,l.lighten)(e.palette.secondary.light,.4)},determinate:{},indeterminate:{},buffer:{backgroundColor:"transparent"},query:{transform:"rotate(180deg)"},dashed:{position:"absolute",marginTop:0,height:"100%",width:"100%",animation:"buffer 3s infinite linear",animationName:"$buffer"},dashedColorPrimary:{backgroundImage:"radial-gradient(".concat((0,l.lighten)(e.palette.primary.light,.6)," 0%, ").concat((0,l.lighten)(e.palette.primary.light,.6)," 16%, transparent 42%)"),backgroundSize:"10px 10px",backgroundPosition:"0px -23px"},dashedColorSecondary:{backgroundImage:"radial-gradient(".concat((0,l.lighten)(e.palette.secondary.light,.4)," 0%, ").concat((0,l.lighten)(e.palette.secondary.light,.6)," 16%, transparent 42%)"),backgroundSize:"10px 10px",backgroundPosition:"0px -23px"},bar:{width:"100%",position:"absolute",left:0,bottom:0,top:0,transition:"transform 0.2s linear",transformOrigin:"left"},barColorPrimary:{backgroundColor:e.palette.primary.main},barColorSecondary:{backgroundColor:e.palette.secondary.main},bar1Indeterminate:{width:"auto",animation:"mui-indeterminate1 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite",animationName:"$mui-indeterminate1"},bar1Determinate:{transition:"transform .".concat(f,"s linear")},bar1Buffer:{zIndex:1,transition:"transform .".concat(f,"s linear")},bar2Indeterminate:{width:"auto",animation:"mui-indeterminate2 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite",animationName:"$mui-indeterminate2",animationDelay:"1.15s"},bar2Buffer:{transition:"transform .".concat(f,"s linear")},"@keyframes mui-indeterminate1":{"0%":{left:"-35%",right:"100%"},"60%":{left:"100%",right:"-90%"},"100%":{left:"100%",right:"-90%"}},"@keyframes mui-indeterminate2":{"0%":{left:"-200%",right:"100%"},"60%":{left:"107%",right:"-8%"},"100%":{left:"107%",right:"-8%"}},"@keyframes buffer":{"0%":{opacity:1,backgroundPosition:"0px -23px"},"50%":{opacity:0,backgroundPosition:"0px -23px"},"100%":{opacity:1,backgroundPosition:"-200px -23px"}}}};function h(e){var t,n,r,c,l=e.classes,f=e.className,d=e.color,h=e.value,p=e.valueBuffer,b=e.variant,m=(0,o.default)(e,["classes","className","color","value","valueBuffer","variant"]),g=(0,u.default)(l.root,(t={},(0,a.default)(t,l.colorPrimary,"primary"===d),(0,a.default)(t,l.colorSecondary,"secondary"===d),(0,a.default)(t,l.determinate,"determinate"===b),(0,a.default)(t,l.indeterminate,"indeterminate"===b),(0,a.default)(t,l.buffer,"buffer"===b),(0,a.default)(t,l.query,"query"===b),t),f),v=(0,u.default)(l.dashed,(n={},(0,a.default)(n,l.dashedColorPrimary,"primary"===d),(0,a.default)(n,l.dashedColorSecondary,"secondary"===d),n)),y=(0,u.default)(l.bar,(r={},(0,a.default)(r,l.barColorPrimary,"primary"===d),(0,a.default)(r,l.barColorSecondary,"secondary"===d),(0,a.default)(r,l.bar1Indeterminate,"indeterminate"===b||"query"===b),(0,a.default)(r,l.bar1Determinate,"determinate"===b),(0,a.default)(r,l.bar1Buffer,"buffer"===b),r)),w=(0,u.default)(l.bar,(c={},(0,a.default)(c,l.barColorPrimary,"primary"===d&&"buffer"!==b),(0,a.default)(c,l.colorPrimary,"primary"===d&&"buffer"===b),(0,a.default)(c,l.barColorSecondary,"secondary"===d&&"buffer"!==b),(0,a.default)(c,l.colorSecondary,"secondary"===d&&"buffer"===b),(0,a.default)(c,l.bar2Indeterminate,"indeterminate"===b||"query"===b),(0,a.default)(c,l.bar2Buffer,"buffer"===b),c)),_={},E={bar1:{},bar2:{}};return("determinate"===b||"buffer"===b)&&void 0!==h&&(_["aria-valuenow"]=Math.round(h),E.bar1.transform="scaleX(".concat(h/100,")")),"buffer"===b&&void 0!==p&&(E.bar2.transform="scaleX(".concat((p||0)/100,")")),s.default.createElement("div",(0,i.default)({className:g,role:"progressbar"},_,m),"buffer"===b?s.default.createElement("div",{className:v}):null,s.default.createElement("div",{className:y,style:E.bar1}),"determinate"===b?null:s.default.createElement("div",{className:w,style:E.bar2}))}t.styles=d,h.defaultProps={color:"primary",variant:"indeterminate"};var p=(0,c.default)(d,{name:"MuiLinearProgress"})(h);t.default=p},79424(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(46616))},74080(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184));n(55252);var c=r(n(78252)),l=r(n(47457)),f={root:{listStyle:"none",margin:0,padding:0,position:"relative"},padding:{paddingTop:8,paddingBottom:8},dense:{paddingTop:4,paddingBottom:4},subheader:{paddingTop:0}};function d(e){var t,n=e.children,r=e.classes,c=e.className,f=e.component,d=e.dense,h=e.disablePadding,p=e.subheader,b=(0,o.default)(e,["children","classes","className","component","dense","disablePadding","subheader"]);return s.default.createElement(f,(0,i.default)({className:(0,u.default)(r.root,(t={},(0,a.default)(t,r.dense,d&&!h),(0,a.default)(t,r.padding,!h),(0,a.default)(t,r.subheader,p),t),c)},b),s.default.createElement(l.default.Provider,{value:{dense:d}},p,n))}t.styles=f,d.defaultProps={component:"ul",dense:!1,disablePadding:!1};var h=(0,c.default)(f,{name:"MuiList"})(d);t.default=h},47457(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67294)).default.createContext({});t.default=i},3022(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(74080))},29936(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184));n(55252);var c=r(n(78252)),l=r(n(16070)),f=n(44370),d=r(n(671)),h=function(e){return{root:{display:"flex",justifyContent:"flex-start",alignItems:"center",position:"relative",textDecoration:"none",width:"100%",boxSizing:"border-box",textAlign:"left",paddingTop:11,paddingBottom:11,"&$selected, &$selected:hover, &$selected:focus":{backgroundColor:e.palette.action.selected}},container:{position:"relative"},focusVisible:{},default:{},dense:{paddingTop:8,paddingBottom:8},alignItemsFlexStart:{alignItems:"flex-start"},disabled:{opacity:.5},divider:{borderBottom:"1px solid ".concat(e.palette.divider),backgroundClip:"padding-box"},gutters:{paddingLeft:16,paddingRight:16},button:{transition:e.transitions.create("background-color",{duration:e.transitions.duration.shortest}),"&:hover":{textDecoration:"none",backgroundColor:e.palette.action.hover,"@media (hover: none)":{backgroundColor:"transparent"}},"&:focus":{backgroundColor:e.palette.action.hover}},secondaryAction:{paddingRight:32},selected:{}}};function p(e){var t=e.alignItems,n=e.button,r=e.children,c=e.classes,h=e.className,p=e.component,b=e.ContainerComponent,m=e.ContainerProps,g=(m=void 0===m?{}:m).className,v=(0,o.default)(m,["className"]),y=e.dense,w=e.disabled,_=e.disableGutters,E=e.divider,S=e.focusVisibleClassName,k=e.selected,x=(0,o.default)(e,["alignItems","button","children","classes","className","component","ContainerComponent","ContainerProps","dense","disabled","disableGutters","divider","focusVisibleClassName","selected"]);return s.default.createElement(d.default,{dense:y,alignItems:t},function(e){var o,d=e.dense,m=s.default.Children.toArray(r),y=m.some(function(e){return(0,f.isMuiElement)(e,["ListItemAvatar"])}),T=m.length&&(0,f.isMuiElement)(m[m.length-1],["ListItemSecondaryAction"]),M=(0,u.default)(c.root,c.default,(o={},(0,a.default)(o,c.dense,d||y),(0,a.default)(o,c.gutters,!_),(0,a.default)(o,c.divider,E),(0,a.default)(o,c.disabled,w),(0,a.default)(o,c.button,n),(0,a.default)(o,c.alignItemsFlexStart,"flex-start"===t),(0,a.default)(o,c.secondaryAction,T),(0,a.default)(o,c.selected,k),o),h),O=(0,i.default)({className:M,disabled:w},x),A=p||"li";return(n&&(O.component=p||"div",O.focusVisibleClassName=(0,u.default)(c.focusVisible,S),A=l.default),T)?(A=O.component||p?A:"div","li"===b&&("li"===A?A="div":"li"===O.component&&(O.component="div")),s.default.createElement(b,(0,i.default)({className:(0,u.default)(c.container,g)},v),s.default.createElement(A,O,m),m.pop())):s.default.createElement(A,O,m)})}t.styles=h,p.defaultProps={alignItems:"center",button:!1,ContainerComponent:"li",dense:!1,disabled:!1,disableGutters:!1,divider:!1,selected:!1};var b=(0,c.default)(h,{name:"MuiListItem"})(p);t.default=b},671(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67294));r(n(45697));var a=r(n(47457));function o(e){var t=e.alignItems,n=e.children,r=e.dense;return i.default.createElement(a.default.Consumer,null,function(e){var o={dense:r||e.dense||!1,alignItems:t};return i.default.createElement(a.default.Provider,{value:o},n(o))})}var s=o;t.default=s},60323(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(29936))},69394(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(67294));r(n(45697));var s=r(n(94184)),u=r(n(78252)),c=function(e){return{root:{marginRight:16,color:e.palette.action.active,flexShrink:0,display:"inline-flex"}}};function l(e){var t=e.children,n=e.classes,r=e.className,u=(0,a.default)(e,["children","classes","className"]);return o.default.createElement("div",(0,i.default)({className:(0,s.default)(n.root,r)},u),t)}t.styles=c;var f=(0,u.default)(c,{name:"MuiListItemIcon"})(l);t.default=f},11186(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(69394))},73390(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184)),c=r(n(78252)),l=r(n(71426)),f=r(n(47457)),d=function(e){return{root:{flex:"1 1 auto",minWidth:0,padding:"0 16px","&:first-child":{paddingLeft:0}},inset:{"&:first-child":{paddingLeft:56}},dense:{fontSize:e.typography.pxToRem(13)},primary:{"&$textDense":{fontSize:"inherit"}},secondary:{"&$textDense":{fontSize:"inherit"}},textDense:{}}};function h(e){var t=e.children,n=e.classes,r=e.className,c=e.disableTypography,d=e.inset,h=e.primary,p=e.primaryTypographyProps,b=e.secondary,m=e.secondaryTypographyProps,g=e.theme,v=(0,o.default)(e,["children","classes","className","disableTypography","inset","primary","primaryTypographyProps","secondary","secondaryTypographyProps","theme"]);return s.default.createElement(f.default.Consumer,null,function(e){var o,f=e.dense,y=null!=h?h:t;null==y||y.type===l.default||c||(y=s.default.createElement(l.default,(0,i.default)({variant:g.typography.useNextVariants?"body1":"subheading",className:(0,u.default)(n.primary,(0,a.default)({},n.textDense,f)),component:"span"},p),y));var w=b;return null==w||w.type===l.default||c||(w=s.default.createElement(l.default,(0,i.default)({className:(0,u.default)(n.secondary,(0,a.default)({},n.textDense,f)),color:"textSecondary"},m),w)),s.default.createElement("div",(0,i.default)({className:(0,u.default)(n.root,(o={},(0,a.default)(o,n.dense,f),(0,a.default)(o,n.inset,d),o),r)},v),y,w)})}t.styles=d,h.defaultProps={disableTypography:!1,inset:!1};var p=(0,c.default)(d,{name:"MuiListItemText",withTheme:!0})(h);t.default=p},87591(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(73390))},95890(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(67294));r(n(45697));var d=r(n(73935)),h=r(n(44825)),p=r(n(78252)),b=r(n(50810)),m=r(n(34980)),g={vertical:"top",horizontal:"right"},v={vertical:"top",horizontal:"left"},y={paper:{maxHeight:"calc(100% - 96px)",WebkitOverflowScrolling:"touch"}};t.styles=y;var w=function(e){function t(){(0,o.default)(this,t);for(var e,n,r=arguments.length,i=Array(r),a=0;a=0?t.children[e].focus():t.firstChild.focus())}},{key:"resetTabIndex",value:function(){for(var e=this.listRef,t=(0,h.default)(e).activeElement,n=[],r=0;r0&&void 0!==arguments[0]?arguments[0]:{};(0,i.default)(this,e);var n=t.hideSiblingNodes,r=void 0===n||n,a=t.handleContainerOverflow,o=void 0===a||a;this.hideSiblingNodes=r,this.handleContainerOverflow=o,this.modals=[],this.data=[]}return(0,a.default)(e,[{key:"add",value:function(e,t){var n=this.modals.indexOf(e);if(-1!==n)return n;n=this.modals.length,this.modals.push(e),e.modalRef&&(0,l.ariaHidden)(e.modalRef,!1),this.hideSiblingNodes&&(0,l.ariaHiddenSiblings)(t,e.mountNode,e.modalRef,!0);var r=f(this.data,function(e){return e.container===t});if(-1!==r)return this.data[r].modals.push(e),n;var i={modals:[e],container:t,overflowing:(0,c.default)(t),prevPaddings:[]};return this.data.push(i),n}},{key:"mount",value:function(e){var t=f(this.data,function(t){return -1!==t.modals.indexOf(e)}),n=this.data[t];!n.style&&this.handleContainerOverflow&&h(n)}},{key:"remove",value:function(e){var t=this.modals.indexOf(e);if(-1===t)return t;var n=f(this.data,function(t){return -1!==t.modals.indexOf(e)}),r=this.data[n];if(r.modals.splice(r.modals.indexOf(e),1),this.modals.splice(t,1),0===r.modals.length)this.handleContainerOverflow&&p(r),e.modalRef&&(0,l.ariaHidden)(e.modalRef,!0),this.hideSiblingNodes&&(0,l.ariaHiddenSiblings)(r.container,e.mountNode,e.modalRef,!1),this.data.splice(n,1);else if(this.hideSiblingNodes){var i=r.modals[r.modals.length-1];i.modalRef&&(0,l.ariaHidden)(i.modalRef,!1)}return t}},{key:"isTopModal",value:function(e){return!!this.modals.length&&this.modals[this.modals.length-1]===e}}]),e}();t.default=b},55536(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}}),Object.defineProperty(t,"ModalManager",{enumerable:!0,get:function(){return a.default}});var i=r(n(58228)),a=r(n(2158))},16575(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.isBody=s,t.default=u;var i=r(n(7624)),a=r(n(16143)),o=r(n(62614));function s(e){return e&&"body"===e.tagName.toLowerCase()}function u(e){var t=(0,a.default)(e),n=(0,o.default)(t);if(!(0,i.default)(t)&&!s(e))return e.scrollHeight>e.clientHeight;var r=n.getComputedStyle(t.body),u=parseInt(r.getPropertyValue("margin-left"),10),c=parseInt(r.getPropertyValue("margin-right"),10);return u+t.body.clientWidth+c0?.75*r+8:0;return s.default.createElement("fieldset",(0,a.default)({"aria-hidden":!0,style:(0,a.default)((0,i.default)({},"padding".concat((0,l.capitalize)(p)),8+(c?0:b/2)),f),className:(0,u.default)(t.root,n)},h),s.default.createElement("legend",{className:t.legend,style:{width:c?b:.01}},s.default.createElement("span",{dangerouslySetInnerHTML:{__html:"​"}})))}t.styles=f;var h=(0,c.withStyles)(f,{name:"MuiPrivateNotchedOutline",withTheme:!0})(d);t.default=h},96405(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(67294));r(n(45697));var s=r(n(94184));n(55252);var u=r(n(67598)),c=r(n(21142)),l=r(n(78252)),f=function(e){var t="light"===e.palette.type?"rgba(0, 0, 0, 0.23)":"rgba(255, 255, 255, 0.23)";return{root:{position:"relative","& $notchedOutline":{borderColor:t},"&:hover:not($disabled):not($focused):not($error) $notchedOutline":{borderColor:e.palette.text.primary,"@media (hover: none)":{borderColor:t}},"&$focused $notchedOutline":{borderColor:e.palette.primary.main,borderWidth:2},"&$error $notchedOutline":{borderColor:e.palette.error.main},"&$disabled $notchedOutline":{borderColor:e.palette.action.disabled}},focused:{},disabled:{},adornedStart:{paddingLeft:14},adornedEnd:{paddingRight:14},error:{},multiline:{padding:"18.5px 14px",boxSizing:"border-box"},notchedOutline:{},input:{padding:"18.5px 14px"},inputMarginDense:{paddingTop:15,paddingBottom:15},inputMultiline:{padding:0},inputAdornedStart:{paddingLeft:0},inputAdornedEnd:{paddingRight:0}}};function d(e){var t=e.classes,n=e.labelWidth,r=e.notched,l=(0,a.default)(e,["classes","labelWidth","notched"]);return o.default.createElement(u.default,(0,i.default)({renderPrefix:function(e){return o.default.createElement(c.default,{className:t.notchedOutline,labelWidth:n,notched:void 0!==r?r:Boolean(e.startAdornment||e.filled||e.focused)})},classes:(0,i.default)({},t,{root:(0,s.default)(t.root,t.underline),notchedOutline:null})},l))}t.styles=f,u.default.defaultProps={fullWidth:!1,inputComponent:"input",multiline:!1,type:"text"},d.muiName="Input";var h=(0,l.default)(f,{name:"MuiOutlinedInput"})(d);t.default=h},59537(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(96405))},30083(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(59713)),a=r(n(6479)),o=r(n(67154)),s=r(n(67294));r(n(45697));var u=r(n(94184));r(n(42473)),n(55252);var c=r(n(78252)),l=function(e){var t={};return e.shadows.forEach(function(e,n){t["elevation".concat(n)]={boxShadow:e}}),(0,o.default)({root:{backgroundColor:e.palette.background.paper},rounded:{borderRadius:e.shape.borderRadius}},t)};function f(e){var t=e.classes,n=e.className,r=e.component,c=e.square,l=e.elevation,f=(0,a.default)(e,["classes","className","component","square","elevation"]),d=(0,u.default)(t.root,t["elevation".concat(l)],(0,i.default)({},t.rounded,!c),n);return s.default.createElement(r,(0,o.default)({className:d},f))}t.styles=l,f.defaultProps={component:"div",elevation:2,square:!1};var d=(0,c.default)(l,{name:"MuiPaper"})(f);t.default=d},68821(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(30083))},64224(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(67294));r(n(45697));var d=r(n(73935));r(n(42473));var h=r(n(20296)),p=r(n(96421));n(55252);var b=r(n(16143)),m=r(n(62614)),g=n(98741),v=r(n(78252)),y=r(n(55536)),w=r(n(261)),_=r(n(68821));function E(e,t){var n=0;return"number"==typeof t?n=t:"center"===t?n=e.height/2:"bottom"===t&&(n=e.height),n}function S(e,t){var n=0;return"number"==typeof t?n=t:"center"===t?n=e.width/2:"right"===t&&(n=e.width),n}function k(e){return[e.horizontal,e.vertical].map(function(e){return"number"==typeof e?"".concat(e,"px"):e}).join(" ")}function x(e,t){for(var n=t,r=0;n&&n!==e;)r+=(n=n.parentNode).scrollTop;return r}function T(e){return"function"==typeof e?e():e}var M={paper:{position:"absolute",overflowY:"auto",overflowX:"hidden",minWidth:16,minHeight:16,maxWidth:"calc(100% - 32px)",maxHeight:"calc(100% - 32px)",outline:"none"}};t.styles=M;var O=function(e){function t(){var e;return(0,o.default)(this,t),(e=(0,u.default)(this,(0,c.default)(t).call(this))).handleGetOffsetTop=E,e.handleGetOffsetLeft=S,e.componentWillUnmount=function(){e.handleResize.clear()},e.setPositioningStyles=function(t){var n=e.getPositioningStyle(t);null!==n.top&&(t.style.top=n.top),null!==n.left&&(t.style.left=n.left),t.style.transformOrigin=n.transformOrigin},e.getPositioningStyle=function(t){var n=e.props,r=n.anchorEl,i=n.anchorReference,a=n.marginThreshold,o=e.getContentAnchorOffset(t),s={width:t.offsetWidth,height:t.offsetHeight},u=e.getTransformOrigin(s,o);if("none"===i)return{top:null,left:null,transformOrigin:k(u)};var c=e.getAnchorOffset(o),l=c.top-u.vertical,f=c.left-u.horizontal,d=l+s.height,h=f+s.width,p=(0,m.default)(T(r)),b=p.innerHeight-a,g=p.innerWidth-a;if(lb){var y=d-b;l-=y,u.vertical+=y}if(fg){var _=h-g;f-=_,u.horizontal+=_}return{top:"".concat(l,"px"),left:"".concat(f,"px"),transformOrigin:k(u)}},e.handleEntering=function(t){e.props.onEntering&&e.props.onEntering(t),e.setPositioningStyles(t)},"undefined"!=typeof window&&(e.handleResize=(0,h.default)(function(){e.props.open&&e.setPositioningStyles(e.paperRef)},166)),e}return(0,l.default)(t,e),(0,s.default)(t,[{key:"componentDidMount",value:function(){this.props.action&&this.props.action({updatePosition:this.handleResize})}},{key:"getAnchorOffset",value:function(e){var t=this.props,n=t.anchorEl,r=t.anchorOrigin,i=t.anchorReference,a=t.anchorPosition;if("anchorPosition"===i)return a;var o=(T(n)||(0,b.default)(this.paperRef).body).getBoundingClientRect(),s=0===e?r.vertical:"center";return{top:o.top+this.handleGetOffsetTop(o,s),left:o.left+this.handleGetOffsetLeft(o,r.horizontal)}}},{key:"getContentAnchorOffset",value:function(e){var t=this.props,n=t.getContentAnchorEl,r=t.anchorReference,i=0;if(n&&"anchorEl"===r){var a=n(e);if(a&&e.contains(a)){var o=x(e,a);i=a.offsetTop+a.clientHeight/2-o||0}}return i}},{key:"getTransformOrigin",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=this.props.transformOrigin;return{vertical:this.handleGetOffsetTop(e,n.vertical)+t,horizontal:this.handleGetOffsetLeft(e,n.horizontal)}}},{key:"render",value:function(){var e=this,t=this.props,n=(t.action,t.anchorEl),r=(t.anchorOrigin,t.anchorPosition,t.anchorReference,t.children),o=t.classes,s=t.container,u=t.elevation,c=(t.getContentAnchorEl,t.marginThreshold,t.ModalClasses),l=t.onEnter,h=t.onEntered,m=(t.onEntering,t.onExit),v=t.onExited,w=t.onExiting,E=t.open,S=t.PaperProps,k=t.role,x=(t.transformOrigin,t.TransitionComponent),M=t.transitionDuration,O=t.TransitionProps,A=void 0===O?{}:O,L=(0,a.default)(t,["action","anchorEl","anchorOrigin","anchorPosition","anchorReference","children","classes","container","elevation","getContentAnchorEl","marginThreshold","ModalClasses","onEnter","onEntered","onEntering","onExit","onExited","onExiting","open","PaperProps","role","transformOrigin","TransitionComponent","transitionDuration","TransitionProps"]),C=M;"auto"!==M||x.muiSupportAuto||(C=void 0);var I=s||(n?(0,b.default)(T(n)).body:void 0);return f.default.createElement(y.default,(0,i.default)({classes:c,container:I,open:E,BackdropProps:{invisible:!0}},L),f.default.createElement(x,(0,i.default)({appear:!0,in:E,onEnter:l,onEntered:h,onExit:m,onExited:v,onExiting:w,role:k,timeout:C},A,{onEntering:(0,g.createChainedFunction)(this.handleEntering,A.onEntering)}),f.default.createElement(_.default,(0,i.default)({className:o.paper,elevation:u,ref:function(t){e.paperRef=d.default.findDOMNode(t)}},S),f.default.createElement(p.default,{target:"window",onResize:this.handleResize}),r)))}}]),t}(f.default.Component);O.defaultProps={anchorReference:"anchorEl",anchorOrigin:{vertical:"top",horizontal:"left"},elevation:8,marginThreshold:16,transformOrigin:{vertical:"top",horizontal:"left"},TransitionComponent:w.default,transitionDuration:"auto"};var A=(0,v.default)(M,{name:"MuiPopover"})(O);t.default=A},50810(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(64224))},24693(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(6479)),a=r(n(67154)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(81506)),d=r(n(67294)),h=r(n(73935));r(n(45697));var p=r(n(28981)),b=r(n(25649));function m(e){if("rtl"!==("undefined"!=typeof window&&document.body.getAttribute("dir")||"ltr"))return e;switch(e){case"bottom-end":return"bottom-start";case"bottom-start":return"bottom-end";case"top-end":return"top-start";case"top-start":return"top-end";default:return e}}function g(e){return"function"==typeof e?e():e}var v=function(e){function t(e){var n;return(0,o.default)(this,t),(n=(0,u.default)(this,(0,c.default)(t).call(this))).handleOpen=function(){var e=n.props,t=e.anchorEl,r=e.modifiers,i=e.open,o=e.placement,s=e.popperOptions,u=void 0===s?{}:s,c=e.disablePortal,l=h.default.findDOMNode((0,f.default)((0,f.default)(n)));l&&t&&i&&(n.popper&&(n.popper.destroy(),n.popper=null),n.popper=new p.default(g(t),l,(0,a.default)({placement:m(o)},u,{modifiers:(0,a.default)({},c?{}:{preventOverflow:{boundariesElement:"window"}},r,u.modifiers),onCreate:n.handlePopperUpdate,onUpdate:n.handlePopperUpdate})))},n.handlePopperUpdate=function(e){e.placement!==n.state.placement&&n.setState({placement:e.placement})},n.handleExited=function(){n.setState({exited:!0}),n.handleClose()},n.handleClose=function(){n.popper&&(n.popper.destroy(),n.popper=null)},n.state={exited:!e.open},n}return(0,l.default)(t,e),(0,s.default)(t,[{key:"componentDidUpdate",value:function(e){e.open===this.props.open||this.props.open||this.props.transition||this.handleClose(),(e.open!==this.props.open||e.anchorEl!==this.props.anchorEl||e.popperOptions!==this.props.popperOptions||e.modifiers!==this.props.modifiers||e.disablePortal!==this.props.disablePortal||e.placement!==this.props.placement)&&this.handleOpen()}},{key:"componentWillUnmount",value:function(){this.handleClose()}},{key:"render",value:function(){var e=this.props,t=(e.anchorEl,e.children),n=e.container,r=e.disablePortal,o=e.keepMounted,s=(e.modifiers,e.open),u=e.placement,c=(e.popperOptions,e.transition),l=(0,i.default)(e,["anchorEl","children","container","disablePortal","keepMounted","modifiers","open","placement","popperOptions","transition"]),f=this.state,h=f.exited,p=f.placement;if(!o&&!s&&(!c||h))return null;var g={placement:p||m(u)};return c&&(g.TransitionProps={in:s,onExited:this.handleExited}),d.default.createElement(b.default,{onRendered:this.handleOpen,disablePortal:r,container:n},d.default.createElement("div",(0,a.default)({role:"tooltip",style:{position:"absolute"}},l),"function"==typeof t?t(g):t))}}],[{key:"getDerivedStateFromProps",value:function(e){return e.open?{exited:!1}:e.transition?null:{exited:!0}}}]),t}(d.default.Component);v.defaultProps={disablePortal:!1,placement:"bottom",transition:!1};var y=v;t.default=y},60111(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(24693))},92261(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(34575)),a=r(n(93913)),o=r(n(78585)),s=r(n(29754)),u=r(n(2205)),c=r(n(67294)),l=r(n(73935));r(n(45697));var f=r(n(16143));function d(e,t){return e="function"==typeof e?e():e,l.default.findDOMNode(e)||t}function h(e){return(0,f.default)(l.default.findDOMNode(e))}n(55252);var p=function(e){function t(){(0,i.default)(this,t);for(var e,n,r=arguments.length,a=Array(r),u=0;u1;n.state.labelWrapped!==e&&n.setState({labelWrapped:e})}},n}return(0,c.default)(t,e),(0,o.default)(t,[{key:"componentDidMount",value:function(){this.checkTextWrap()}},{key:"componentDidUpdate",value:function(e,t){this.state.labelWrapped===t.labelWrapped&&this.checkTextWrap()}},{key:"render",value:function(){var e,t,n=this,r=this.props,a=r.classes,o=r.className,s=r.disabled,u=r.fullWidth,c=r.icon,p=r.indicator,g=r.label,v=(r.onChange,r.selected),y=r.textColor,w=(r.value,(0,i.default)(r,["classes","className","disabled","fullWidth","icon","indicator","label","onChange","selected","textColor","value"]));return void 0!==g&&(e=d.default.createElement("span",{className:a.labelContainer},d.default.createElement("span",{className:(0,h.default)(a.label,(0,l.default)({},a.labelWrapped,this.state.labelWrapped)),ref:function(e){n.labelRef=e}},g))),d.default.createElement(b.default,(0,f.default)({focusRipple:!0,className:(0,h.default)(a.root,a["textColor".concat((0,m.capitalize)(y))],(t={},(0,l.default)(t,a.disabled,s),(0,l.default)(t,a.selected,v),(0,l.default)(t,a.labelIcon,c&&e),(0,l.default)(t,a.fullWidth,u),t),o),role:"tab","aria-selected":v,disabled:s},w,{onClick:this.handleChange}),d.default.createElement("span",{className:a.wrapper},c,e),p)}}]),t}(d.default.Component);v.defaultProps={disabled:!1,textColor:"inherit"};var y=(0,p.default)(g,{name:"MuiTab"})(v);t.default=y},75759(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(70201))},7575(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(67294));r(n(45697));var d=r(n(94184));n(55252);var h=r(n(78252)),p=r(n(82577)),b=function(e){return{root:{display:"table",fontFamily:e.typography.fontFamily,width:"100%",borderCollapse:"collapse",borderSpacing:0}}};t.styles=b;var m=function(e){function t(){(0,o.default)(this,t);for(var e,n,r=arguments.length,i=Array(r),a=0;ai&&n(null,i)}},{key:"render",value:function(){var e,t=this.props,n=t.ActionsComponent,r=t.backIconButtonProps,o=t.classes,s=t.colSpan,u=t.component,c=t.count,l=t.labelDisplayedRows,d=t.labelRowsPerPage,y=t.nextIconButtonProps,w=t.onChangePage,_=t.onChangeRowsPerPage,E=t.page,S=t.rowsPerPage,k=t.rowsPerPageOptions,x=t.SelectProps,T=void 0===x?{}:x,M=(0,a.default)(t,["ActionsComponent","backIconButtonProps","classes","colSpan","component","count","labelDisplayedRows","labelRowsPerPage","nextIconButtonProps","onChangePage","onChangeRowsPerPage","page","rowsPerPage","rowsPerPageOptions","SelectProps"]);(u===m.default||"td"===u)&&(e=s||1e3);var O=T.native?"option":p.default;return f.default.createElement(u,(0,i.default)({className:o.root,colSpan:e},M),f.default.createElement(g.default,{className:o.toolbar},f.default.createElement("div",{className:o.spacer}),k.length>1&&f.default.createElement(v.default,{color:"inherit",variant:"caption",className:o.caption},d),k.length>1&&f.default.createElement(b.default,(0,i.default)({classes:{root:o.selectRoot,select:o.select,icon:o.selectIcon},input:f.default.createElement(h.default,{className:o.input}),value:S,onChange:_},T),k.map(function(e){return f.default.createElement(O,{className:o.menuItem,key:e,value:e},e)})),f.default.createElement(v.default,{color:"inherit",variant:"caption",className:o.caption},l({from:0===c?0:E*S+1,to:Math.min(c,(E+1)*S),count:c,page:E})),f.default.createElement(n,{className:o.actions,backIconButtonProps:r,count:c,nextIconButtonProps:y,onChangePage:w,page:E,rowsPerPage:S})))}}]),t}(f.default.Component);_.defaultProps={ActionsComponent:y.default,component:m.default,labelDisplayedRows:function(e){var t=e.from,n=e.to,r=e.count;return"".concat(t,"-").concat(n," of ").concat(r)},labelRowsPerPage:"Rows per page:",rowsPerPageOptions:[10,25,50,100]};var E=(0,d.default)(w,{name:"MuiTablePagination"})(_);t.default=E},32844(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(67294));r(n(45697));var d=r(n(86861)),h=r(n(43836)),p=r(n(82313)),b=r(n(81701)),m=f.default.createElement(h.default,null),g=f.default.createElement(d.default,null),v=f.default.createElement(d.default,null),y=f.default.createElement(h.default,null),w=function(e){function t(){(0,o.default)(this,t);for(var e,n,r=arguments.length,i=Array(r),a=0;a=Math.ceil(n/s)-1,color:"inherit"},r),"rtl"===u.direction?v:y))}}]),t}(f.default.Component),_=(0,p.default)()(w);t.default=_},18217(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(71744))},86424(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184));n(55252);var c=r(n(78252)),l=r(n(27628)),f=function(e){return{root:{color:"inherit",display:"table-row",height:48,verticalAlign:"middle",outline:"none","&$selected":{backgroundColor:"light"===e.palette.type?"rgba(0, 0, 0, 0.04)":"rgba(255, 255, 255, 0.08)"},"&$hover:hover":{backgroundColor:"light"===e.palette.type?"rgba(0, 0, 0, 0.07)":"rgba(255, 255, 255, 0.14)"}},selected:{},hover:{},head:{height:56},footer:{height:56}}};function d(e){var t=e.classes,n=e.className,r=e.component,c=e.hover,f=e.selected,d=(0,o.default)(e,["classes","className","component","hover","selected"]);return s.default.createElement(l.default.Consumer,null,function(e){var o,l=(0,u.default)(t.root,(o={},(0,a.default)(o,t.head,e&&"head"===e.variant),(0,a.default)(o,t.footer,e&&"footer"===e.variant),(0,a.default)(o,t.hover,c),(0,a.default)(o,t.selected,f),o),n);return s.default.createElement(r,(0,i.default)({className:l},d))})}t.styles=f,d.defaultProps={component:"tr",hover:!1,selected:!1};var h=(0,c.default)(f,{name:"MuiTableRow"})(d);t.default=h},17175(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(86424))},28550(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(34575)),a=r(n(93913)),o=r(n(78585)),s=r(n(29754)),u=r(n(2205)),c=r(n(67294));r(n(45697));var l,f=r(n(96421)),d=r(n(20296)),h={width:90,height:90,position:"absolute",top:-9e3,overflow:"scroll",msOverflowStyle:"scrollbar"},p=function(e){function t(){var e;return(0,i.default)(this,t),(e=(0,o.default)(this,(0,s.default)(t).call(this))).handleRef=function(t){e.nodeRef=t},e.setMeasurements=function(){var t=e.nodeRef;t&&(e.scrollbarHeight=t.offsetHeight-t.clientHeight)},"undefined"!=typeof window&&(e.handleResize=(0,d.default)(function(){var t=e.scrollbarHeight;e.setMeasurements(),t!==e.scrollbarHeight&&e.props.onChange(e.scrollbarHeight)},166)),e}return(0,u.default)(t,e),(0,a.default)(t,[{key:"componentDidMount",value:function(){this.setMeasurements(),this.props.onChange(this.scrollbarHeight)}},{key:"componentWillUnmount",value:function(){this.handleResize.clear()}},{key:"render",value:function(){return c.default.createElement(c.default.Fragment,null,c.default.createElement(f.default,{target:"window",onResize:this.handleResize}),c.default.createElement("div",{style:h,ref:this.handleRef}))}}]),t}(c.default.Component);t.default=p},12417(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(67294));r(n(45697));var s=r(n(94184)),u=r(n(78252)),c=n(98741),l=function(e){return{root:{position:"absolute",height:2,bottom:0,width:"100%",transition:e.transitions.create()},colorPrimary:{backgroundColor:e.palette.primary.main},colorSecondary:{backgroundColor:e.palette.secondary.main}}};function f(e){var t=e.classes,n=e.className,r=e.color,u=(0,a.default)(e,["classes","className","color"]);return o.default.createElement("span",(0,i.default)({className:(0,s.default)(t.root,t["color".concat((0,c.capitalize)(r))],n)},u))}t.styles=l;var d=(0,u.default)(l,{name:"MuiPrivateTabIndicator"})(f);t.default=d},69583(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(67294));r(n(45697));var s=r(n(94184)),u=r(n(86861)),c=r(n(43836)),l=r(n(78252)),f=r(n(16070)),d={root:{color:"inherit",width:56,flexShrink:0}};t.styles=d;var h=o.default.createElement(u.default,null),p=o.default.createElement(c.default,null);function b(e){var t=e.classes,n=e.className,r=e.direction,u=e.onClick,c=e.visible,l=(0,a.default)(e,["classes","className","direction","onClick","visible"]),d=(0,s.default)(t.root,n);return c?o.default.createElement(f.default,(0,i.default)({className:d,onClick:u,tabIndex:-1},l),"left"===r?h:p):o.default.createElement("div",{className:d})}b.defaultProps={visible:!0};var m=(0,l.default)(d,{name:"MuiPrivateTabScrollButton"})(b);t.default=m},89172(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(59713)),d=r(n(67294));r(n(45697)),r(n(42473));var h=r(n(94184)),p=r(n(96421)),b=r(n(20296)),m=n(46417);n(55252);var g=r(n(13329)),v=r(n(28550)),y=r(n(78252)),w=r(n(12417)),_=r(n(69583));r(n(346));var E=function(e){return{root:{overflow:"hidden",minHeight:48,WebkitOverflowScrolling:"touch"},flexContainer:{display:"flex"},centered:{justifyContent:"center"},scroller:{position:"relative",display:"inline-block",flex:"1 1 auto",whiteSpace:"nowrap"},fixed:{overflowX:"hidden",width:"100%"},scrollable:{overflowX:"scroll"},scrollButtons:{},scrollButtonsAuto:(0,f.default)({},e.breakpoints.down("xs"),{display:"none"}),indicator:{}}};t.styles=E;var S=function(e){function t(){var e;return(0,o.default)(this,t),(e=(0,u.default)(this,(0,c.default)(t).call(this))).state={indicatorStyle:{},scrollerStyle:{marginBottom:0},showLeftScroll:!1,showRightScroll:!1,mounted:!1},e.getConditionalElements=function(){var t=e.props,n=t.classes,r=t.scrollable,i=t.ScrollButtonComponent,a=t.scrollButtons,o=t.theme,s=t.variant,u={},c="scrollable"===s||r;u.scrollbarSizeListener=c?d.default.createElement(v.default,{onChange:e.handleScrollbarSizeChange}):null;var l=c&&("auto"===a||"on"===a);return u.scrollButtonLeft=l?d.default.createElement(i,{direction:o&&"rtl"===o.direction?"right":"left",onClick:e.handleLeftScrollClick,visible:e.state.showLeftScroll,className:(0,h.default)(n.scrollButtons,(0,f.default)({},n.scrollButtonsAuto,"auto"===a))}):null,u.scrollButtonRight=l?d.default.createElement(i,{direction:o&&"rtl"===o.direction?"left":"right",onClick:e.handleRightScrollClick,visible:e.state.showRightScroll,className:(0,h.default)(n.scrollButtons,(0,f.default)({},n.scrollButtonsAuto,"auto"===a))}):null,u},e.getTabsMeta=function(t,n){if(e.tabsRef){var r,i,a=e.tabsRef.getBoundingClientRect();r={clientWidth:e.tabsRef.clientWidth,scrollLeft:e.tabsRef.scrollLeft,scrollLeftNormalized:(0,m.getNormalizedScrollLeft)(e.tabsRef,n),scrollWidth:e.tabsRef.scrollWidth,left:a.left,right:a.right}}if(e.tabsRef&&!1!==t){var o=e.tabsRef.children[0].children;if(o.length>0){var s=o[e.valueToIndex.get(t)];i=s?s.getBoundingClientRect():null}}return{tabsMeta:r,tabMeta:i}},e.handleLeftScrollClick=function(){e.moveTabsScroll(-e.tabsRef.clientWidth)},e.handleRightScrollClick=function(){e.moveTabsScroll(e.tabsRef.clientWidth)},e.handleScrollbarSizeChange=function(t){e.setState({scrollerStyle:{marginBottom:-t}})},e.moveTabsScroll=function(t){var n=e.props.theme,r="rtl"===n.direction?-1:1,i=e.tabsRef.scrollLeft+t*r,a="rtl"===n.direction&&"reverse"===(0,m.detectScrollType)()?-1:1;e.scroll(a*i)},e.scrollSelectedIntoView=function(){var t=e.props,n=t.theme,r=t.value,i=e.getTabsMeta(r,n.direction),a=i.tabsMeta,o=i.tabMeta;if(o&&a){if(o.lefta.right){var u=a.scrollLeft+(o.right-a.right);e.scroll(u)}}},e.scroll=function(t){(0,g.default)("scrollLeft",e.tabsRef,t)},e.updateScrollButtonState=function(){var t=e.props,n=t.scrollable,r=t.scrollButtons,i=t.theme;if(("scrollable"===t.variant||n)&&"off"!==r){var a=e.tabsRef,o=a.scrollWidth,s=a.clientWidth,u=(0,m.getNormalizedScrollLeft)(e.tabsRef,i.direction),c="rtl"===i.direction?o>s+u:u>0,l="rtl"===i.direction?u>0:o>s+u;(c!==e.state.showLeftScroll||l!==e.state.showRightScroll)&&e.setState({showLeftScroll:c,showRightScroll:l})}},"undefined"!=typeof window&&(e.handleResize=(0,b.default)(function(){e.updateIndicatorState(e.props),e.updateScrollButtonState()},166),e.handleTabsScroll=(0,b.default)(function(){e.updateScrollButtonState()},166)),e}return(0,l.default)(t,e),(0,s.default)(t,[{key:"componentDidMount",value:function(){this.setState({mounted:!0}),this.updateIndicatorState(this.props),this.updateScrollButtonState(),this.props.action&&this.props.action({updateIndicator:this.handleResize})}},{key:"componentDidUpdate",value:function(e,t){this.updateIndicatorState(this.props),this.updateScrollButtonState(),this.state.indicatorStyle!==t.indicatorStyle&&this.scrollSelectedIntoView()}},{key:"componentWillUnmount",value:function(){this.handleResize.clear(),this.handleTabsScroll.clear()}},{key:"updateIndicatorState",value:function(e){var t=e.theme,n=e.value,r=this.getTabsMeta(n,t.direction),i=r.tabsMeta,a=r.tabMeta,o=0;if(a&&i){var s="rtl"===t.direction?i.scrollLeftNormalized+i.clientWidth-i.scrollWidth:i.scrollLeft;o=Math.round(a.left-i.left+s)}var u={left:o,width:a?Math.round(a.width):0};u.left===this.state.indicatorStyle.left&&u.width===this.state.indicatorStyle.width||isNaN(u.left)||isNaN(u.width)||this.setState({indicatorStyle:u})}},{key:"render",value:function(){var e,t=this,n=this.props,r=(n.action,n.centered),o=n.children,s=n.classes,u=n.className,c=n.component,l=n.fullWidth,b=void 0!==l&&l,m=n.indicatorColor,g=n.onChange,v=n.scrollable,y=void 0!==v&&v,_=(n.ScrollButtonComponent,n.scrollButtons,n.TabIndicatorProps),E=void 0===_?{}:_,S=n.textColor,k=(n.theme,n.value),x=n.variant,T=(0,a.default)(n,["action","centered","children","classes","className","component","fullWidth","indicatorColor","onChange","scrollable","ScrollButtonComponent","scrollButtons","TabIndicatorProps","textColor","theme","value","variant"]),M="scrollable"===x||y,O=(0,h.default)(s.root,u),A=(0,h.default)(s.flexContainer,(0,f.default)({},s.centered,r&&!M)),L=(0,h.default)(s.scroller,(e={},(0,f.default)(e,s.fixed,!M),(0,f.default)(e,s.scrollable,M),e)),C=d.default.createElement(w.default,(0,i.default)({className:s.indicator,color:m},E,{style:(0,i.default)({},this.state.indicatorStyle,E.style)}));this.valueToIndex=new Map;var I=0,D=d.default.Children.map(o,function(e){if(!d.default.isValidElement(e))return null;var n=void 0===e.props.value?I:e.props.value;t.valueToIndex.set(n,I);var r=n===k;return I+=1,d.default.cloneElement(e,{fullWidth:"fullWidth"===x||b,indicator:r&&!t.state.mounted&&C,selected:r,onChange:g,textColor:S,value:n})}),N=this.getConditionalElements();return d.default.createElement(c,(0,i.default)({className:O},T),d.default.createElement(p.default,{target:"window",onResize:this.handleResize}),N.scrollbarSizeListener,d.default.createElement("div",{className:s.flexContainer},N.scrollButtonLeft,d.default.createElement("div",{className:L,style:this.state.scrollerStyle,ref:function(e){t.tabsRef=e},role:"tablist",onScroll:this.handleTabsScroll},d.default.createElement("div",{className:A},D),this.state.mounted&&C),N.scrollButtonRight))}}]),t}(d.default.Component);S.defaultProps={centered:!1,component:"div",indicatorColor:"secondary",ScrollButtonComponent:_.default,scrollButtons:"auto",textColor:"inherit",variant:"standard"};var k=(0,y.default)(E,{name:"MuiTabs",withTheme:!0})(S);t.default=k},12794(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(89172))},78592(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(67294)),d=r(n(73935));r(n(42473)),r(n(45697));var h=r(n(54846)),p=r(n(1402)),b=r(n(59537)),m=r(n(23153)),g=r(n(85461)),v=r(n(76023)),y=r(n(11970)),w={standard:h.default,filled:p.default,outlined:b.default},_=function(e){function t(e){var n;return(0,o.default)(this,t),(n=(0,u.default)(this,(0,c.default)(t).call(this,e))).labelRef=f.default.createRef(),n}return(0,l.default)(t,e),(0,s.default)(t,[{key:"componentDidMount",value:function(){"outlined"===this.props.variant&&(this.labelNode=d.default.findDOMNode(this.labelRef.current),this.forceUpdate())}},{key:"render",value:function(){var e=this.props,t=e.autoComplete,n=e.autoFocus,r=e.children,o=e.className,s=e.defaultValue,u=e.error,c=e.FormHelperTextProps,l=e.fullWidth,d=e.helperText,h=e.id,p=e.InputLabelProps,b=e.inputProps,_=e.InputProps,E=e.inputRef,S=e.label,k=e.multiline,x=e.name,T=e.onBlur,M=e.onChange,O=e.onFocus,A=e.placeholder,L=e.required,C=e.rows,I=e.rowsMax,D=e.select,N=e.SelectProps,P=e.type,R=e.value,j=e.variant,F=(0,a.default)(e,["autoComplete","autoFocus","children","className","defaultValue","error","FormHelperTextProps","fullWidth","helperText","id","InputLabelProps","inputProps","InputProps","inputRef","label","multiline","name","onBlur","onChange","onFocus","placeholder","required","rows","rowsMax","select","SelectProps","type","value","variant"]),Y={};"outlined"===j&&(p&&void 0!==p.shrink&&(Y.notched=p.shrink),Y.labelWidth=this.labelNode&&this.labelNode.offsetWidth||0);var B=d&&h?"".concat(h,"-helper-text"):void 0,U=w[j],H=f.default.createElement(U,(0,i.default)({"aria-describedby":B,autoComplete:t,autoFocus:n,defaultValue:s,fullWidth:l,multiline:k,name:x,rows:C,rowsMax:I,type:P,value:R,id:h,inputRef:E,onBlur:T,onChange:M,onFocus:O,placeholder:A,inputProps:b},Y,_));return f.default.createElement(g.default,(0,i.default)({className:o,error:u,fullWidth:l,required:L,variant:j},F),S&&f.default.createElement(m.default,(0,i.default)({htmlFor:h,ref:this.labelRef},p),S),D?f.default.createElement(y.default,(0,i.default)({"aria-describedby":B,value:R,input:H},N),r):H,d&&f.default.createElement(v.default,(0,i.default)({id:B},c),d))}}]),t}(f.default.Component);_.defaultProps={required:!1,select:!1,variant:"standard"};var E=_;t.default=E},60520(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(78592))},48596(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184)),c=r(n(78252)),l=function(e){return{root:{position:"relative",display:"flex",alignItems:"center"},gutters:e.mixins.gutters(),regular:e.mixins.toolbar,dense:{minHeight:48}}};function f(e){var t=e.children,n=e.classes,r=e.className,c=e.disableGutters,l=e.variant,f=(0,o.default)(e,["children","classes","className","disableGutters","variant"]),d=(0,u.default)(n.root,n[l],(0,a.default)({},n.gutters,!c),r);return s.default.createElement("div",(0,i.default)({className:d},f),t)}t.styles=l,f.defaultProps={disableGutters:!1,variant:"regular"};var d=(0,c.default)(l,{name:"MuiToolbar"})(f);t.default=d},28902(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(48596))},83065(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(59713)),d=r(n(67294));r(n(45697)),r(n(42473));var h=r(n(94184));n(55252);var p=r(n(39737)),b=r(n(78252)),m=n(98741),g=r(n(261)),v=r(n(60111)),y=function(e){return{popper:{zIndex:e.zIndex.tooltip,opacity:.9,pointerEvents:"none"},popperInteractive:{pointerEvents:"auto"},tooltip:{backgroundColor:e.palette.grey[700],borderRadius:e.shape.borderRadius,color:e.palette.common.white,fontFamily:e.typography.fontFamily,padding:"4px 8px",fontSize:e.typography.pxToRem(10),lineHeight:"".concat(e.typography.round(1.4),"em"),maxWidth:300},touch:{padding:"8px 16px",fontSize:e.typography.pxToRem(14),lineHeight:"".concat(e.typography.round(16/14),"em")},tooltipPlacementLeft:(0,f.default)({transformOrigin:"right center",margin:"0 24px "},e.breakpoints.up("sm"),{margin:"0 14px"}),tooltipPlacementRight:(0,f.default)({transformOrigin:"left center",margin:"0 24px"},e.breakpoints.up("sm"),{margin:"0 14px"}),tooltipPlacementTop:(0,f.default)({transformOrigin:"center bottom",margin:"24px 0"},e.breakpoints.up("sm"),{margin:"14px 0"}),tooltipPlacementBottom:(0,f.default)({transformOrigin:"center top",margin:"24px 0"},e.breakpoints.up("sm"),{margin:"14px 0"})}};t.styles=y;var w=function(e){function t(e){var n;return(0,o.default)(this,t),(n=(0,u.default)(this,(0,c.default)(t).call(this))).ignoreNonTouchEvents=!1,n.onRootRef=function(e){n.childrenRef=e},n.handleFocus=function(e){n.childrenRef||(n.childrenRef=e.currentTarget),n.handleEnter(e);var t=n.props.children.props;t.onFocus&&t.onFocus(e)},n.handleEnter=function(e){var t=n.props,r=t.children,i=t.enterDelay,a=r.props;"mouseover"===e.type&&a.onMouseOver&&a.onMouseOver(e),(!n.ignoreNonTouchEvents||"touchstart"===e.type)&&(n.childrenRef.setAttribute("title",""),clearTimeout(n.enterTimer),clearTimeout(n.leaveTimer),i?(e.persist(),n.enterTimer=setTimeout(function(){n.handleOpen(e)},i)):n.handleOpen(e))},n.handleOpen=function(e){n.isControlled||n.state.open||n.setState({open:!0}),n.props.onOpen&&n.props.onOpen(e)},n.handleLeave=function(e){var t=n.props,r=t.children,i=t.leaveDelay,a=r.props;"blur"===e.type&&a.onBlur&&a.onBlur(e),"mouseleave"===e.type&&a.onMouseLeave&&a.onMouseLeave(e),clearTimeout(n.enterTimer),clearTimeout(n.leaveTimer),i?(e.persist(),n.leaveTimer=setTimeout(function(){n.handleClose(e)},i)):n.handleClose(e)},n.handleClose=function(e){n.isControlled||n.setState({open:!1}),n.props.onClose&&n.props.onClose(e),clearTimeout(n.closeTimer),n.closeTimer=setTimeout(function(){n.ignoreNonTouchEvents=!1},n.props.theme.transitions.duration.shortest)},n.handleTouchStart=function(e){n.ignoreNonTouchEvents=!0;var t=n.props,r=t.children,i=t.enterTouchDelay;r.props.onTouchStart&&r.props.onTouchStart(e),clearTimeout(n.leaveTimer),clearTimeout(n.closeTimer),clearTimeout(n.touchTimer),e.persist(),n.touchTimer=setTimeout(function(){n.handleEnter(e)},i)},n.handleTouchEnd=function(e){var t=n.props,r=t.children,i=t.leaveTouchDelay;r.props.onTouchEnd&&r.props.onTouchEnd(e),clearTimeout(n.touchTimer),clearTimeout(n.leaveTimer),e.persist(),n.leaveTimer=setTimeout(function(){n.handleClose(e)},i)},n.isControlled=null!=e.open,n.state={open:null},n.isControlled||(n.state.open=!1),n}return(0,l.default)(t,e),(0,s.default)(t,[{key:"componentDidMount",value:function(){this.defaultId="mui-tooltip-".concat(Math.round(1e5*Math.random())),this.props.open&&this.forceUpdate()}},{key:"componentWillUnmount",value:function(){clearTimeout(this.closeTimer),clearTimeout(this.enterTimer),clearTimeout(this.focusTimer),clearTimeout(this.leaveTimer),clearTimeout(this.touchTimer)}},{key:"render",value:function(){var e=this,t=this.props,n=t.children,r=t.classes,o=t.disableFocusListener,s=t.disableHoverListener,u=t.disableTouchListener,c=(t.enterDelay,t.enterTouchDelay,t.id),l=t.interactive,b=(t.leaveDelay,t.leaveTouchDelay,t.onClose,t.onOpen,t.open),g=t.placement,y=t.PopperProps,w=t.theme,_=t.title,E=t.TransitionComponent,S=t.TransitionProps,k=(0,a.default)(t,["children","classes","disableFocusListener","disableHoverListener","disableTouchListener","enterDelay","enterTouchDelay","id","interactive","leaveDelay","leaveTouchDelay","onClose","onOpen","open","placement","PopperProps","theme","title","TransitionComponent","TransitionProps"]),x=this.isControlled?b:this.state.open;""===_&&(x=!1);var T=!x&&!s,M=(0,i.default)({"aria-describedby":x?c||this.defaultId:null,title:T&&"string"==typeof _?_:null},k,n.props,{className:(0,h.default)(k.className,n.props.className)});u||(M.onTouchStart=this.handleTouchStart,M.onTouchEnd=this.handleTouchEnd),s||(M.onMouseOver=this.handleEnter,M.onMouseLeave=this.handleLeave),o||(M.onFocus=this.handleFocus,M.onBlur=this.handleLeave);var O=l?{onMouseOver:M.onMouseOver,onMouseLeave:M.onMouseLeave,onFocus:M.onFocus,onBlur:M.onBlur}:{};return d.default.createElement(d.default.Fragment,null,d.default.createElement(p.default,{rootRef:this.onRootRef},d.default.cloneElement(n,M)),d.default.createElement(v.default,(0,i.default)({className:(0,h.default)(r.popper,(0,f.default)({},r.popperInteractive,l)),placement:g,anchorEl:this.childrenRef,open:x,id:M["aria-describedby"],transition:!0},O,y),function(t){var n=t.placement,a=t.TransitionProps;return d.default.createElement(E,(0,i.default)({timeout:w.transitions.duration.shorter},a,S),d.default.createElement("div",{className:(0,h.default)(r.tooltip,(0,f.default)({},r.touch,e.ignoreNonTouchEvents),r["tooltipPlacement".concat((0,m.capitalize)(n.split("-")[0]))])},_))}))}}]),t}(d.default.Component);w.defaultProps={disableFocusListener:!1,disableHoverListener:!1,disableTouchListener:!1,enterDelay:0,enterTouchDelay:1e3,interactive:!1,leaveDelay:0,leaveTouchDelay:1500,placement:"bottom",TransitionComponent:g.default};var _=(0,b.default)(y,{name:"MuiTooltip",withTheme:!0})(w);t.default=_},31657(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"Z",{enumerable:!0,get:function(){return a.default}});var a=i(n(83065))},49476(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(67294));r(n(45697));var u=r(n(94184));n(55252);var c=r(n(78252)),l=n(98741),f=function(e){return{root:{display:"block",margin:0},display4:e.typography.display4,display3:e.typography.display3,display2:e.typography.display2,display1:e.typography.display1,headline:e.typography.headline,title:e.typography.title,subheading:e.typography.subheading,body2:e.typography.body2,body1:e.typography.body1,caption:e.typography.caption,button:e.typography.button,h1:e.typography.h1,h2:e.typography.h2,h3:e.typography.h3,h4:e.typography.h4,h5:e.typography.h5,h6:e.typography.h6,subtitle1:e.typography.subtitle1,subtitle2:e.typography.subtitle2,overline:e.typography.overline,srOnly:{position:"absolute",height:1,width:1,overflow:"hidden"},alignLeft:{textAlign:"left"},alignCenter:{textAlign:"center"},alignRight:{textAlign:"right"},alignJustify:{textAlign:"justify"},noWrap:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},gutterBottom:{marginBottom:"0.35em"},paragraph:{marginBottom:16},colorInherit:{color:"inherit"},colorPrimary:{color:e.palette.primary.main},colorSecondary:{color:e.palette.secondary.main},colorTextPrimary:{color:e.palette.text.primary},colorTextSecondary:{color:e.palette.text.secondary},colorError:{color:e.palette.error.main},inline:{display:"inline"}}};t.styles=f;var d={display4:"h1",display3:"h2",display2:"h3",display1:"h4",headline:"h5",title:"h6",subheading:"subtitle1"};function h(e,t){var n=e.typography,r=t;return r||(r=n.useNextVariants?"body2":"body1"),n.useNextVariants&&(r=d[r]||r),r}var p={h1:"h1",h2:"h2",h3:"h3",h4:"h4",h5:"h5",h6:"h6",subtitle1:"h6",subtitle2:"h6",body1:"p",body2:"p",display4:"h1",display3:"h1",display2:"h1",display1:"h1",headline:"h1",title:"h2",subheading:"h3"};function b(e){var t,n=e.align,r=e.classes,c=e.className,f=e.color,d=e.component,b=e.gutterBottom,m=e.headlineMapping,g=e.inline,v=(e.internalDeprecatedVariant,e.noWrap),y=e.paragraph,w=e.theme,_=e.variant,E=(0,o.default)(e,["align","classes","className","color","component","gutterBottom","headlineMapping","inline","internalDeprecatedVariant","noWrap","paragraph","theme","variant"]),S=h(w,_),k=(0,u.default)(r.root,(t={},(0,a.default)(t,r[S],"inherit"!==S),(0,a.default)(t,r["color".concat((0,l.capitalize)(f))],"default"!==f),(0,a.default)(t,r.noWrap,v),(0,a.default)(t,r.gutterBottom,b),(0,a.default)(t,r.paragraph,y),(0,a.default)(t,r["align".concat((0,l.capitalize)(n))],"inherit"!==n),(0,a.default)(t,r.inline,g),t),c),x=d||(y?"p":m[S]||p[S])||"span";return s.default.createElement(x,(0,i.default)({className:k},E))}b.defaultProps={align:"inherit",color:"default",gutterBottom:!1,headlineMapping:p,inline:!1,noWrap:!1,paragraph:!1};var m=(0,c.default)(f,{name:"MuiTypography",withTheme:!0})(b);t.default=m},71426(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i.default}});var i=r(n(49476))},8070(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#fff8e1",100:"#ffecb3",200:"#ffe082",300:"#ffd54f",400:"#ffca28",500:"#ffc107",600:"#ffb300",700:"#ffa000",800:"#ff8f00",900:"#ff6f00",A100:"#ffe57f",A200:"#ffd740",A400:"#ffc400",A700:"#ffab00"};t.default=n},63259(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#e3f2fd",100:"#bbdefb",200:"#90caf9",300:"#64b5f6",400:"#42a5f5",500:"#2196f3",600:"#1e88e5",700:"#1976d2",800:"#1565c0",900:"#0d47a1",A100:"#82b1ff",A200:"#448aff",A400:"#2979ff",A700:"#2962ff"};t.default=n},38236(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#eceff1",100:"#cfd8dc",200:"#b0bec5",300:"#90a4ae",400:"#78909c",500:"#607d8b",600:"#546e7a",700:"#455a64",800:"#37474f",900:"#263238",A100:"#cfd8dc",A200:"#b0bec5",A400:"#78909c",A700:"#455a64"};t.default=n},60169(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#efebe9",100:"#d7ccc8",200:"#bcaaa4",300:"#a1887f",400:"#8d6e63",500:"#795548",600:"#6d4c41",700:"#5d4037",800:"#4e342e",900:"#3e2723",A100:"#d7ccc8",A200:"#bcaaa4",A400:"#8d6e63",A700:"#5d4037"};t.default=n},515(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={black:"#000",white:"#fff"};t.default=n},57646(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#e0f7fa",100:"#b2ebf2",200:"#80deea",300:"#4dd0e1",400:"#26c6da",500:"#00bcd4",600:"#00acc1",700:"#0097a7",800:"#00838f",900:"#006064",A100:"#84ffff",A200:"#18ffff",A400:"#00e5ff",A700:"#00b8d4"};t.default=n},50173(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#fbe9e7",100:"#ffccbc",200:"#ffab91",300:"#ff8a65",400:"#ff7043",500:"#ff5722",600:"#f4511e",700:"#e64a19",800:"#d84315",900:"#bf360c",A100:"#ff9e80",A200:"#ff6e40",A400:"#ff3d00",A700:"#dd2c00"};t.default=n},45018(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#ede7f6",100:"#d1c4e9",200:"#b39ddb",300:"#9575cd",400:"#7e57c2",500:"#673ab7",600:"#5e35b1",700:"#512da8",800:"#4527a0",900:"#311b92",A100:"#b388ff",A200:"#7c4dff",A400:"#651fff",A700:"#6200ea"};t.default=n},47559(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#e8f5e9",100:"#c8e6c9",200:"#a5d6a7",300:"#81c784",400:"#66bb6a",500:"#4caf50",600:"#43a047",700:"#388e3c",800:"#2e7d32",900:"#1b5e20",A100:"#b9f6ca",A200:"#69f0ae",A400:"#00e676",A700:"#00c853"};t.default=n},70167(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#fafafa",100:"#f5f5f5",200:"#eeeeee",300:"#e0e0e0",400:"#bdbdbd",500:"#9e9e9e",600:"#757575",700:"#616161",800:"#424242",900:"#212121",A100:"#d5d5d5",A200:"#aaaaaa",A400:"#303030",A700:"#616161"};t.default=n},19350(e,t,n){"use strict";var r,i=n(95318);r={value:!0},Object.defineProperty(t,"y0",{enumerable:!0,get:function(){return a.default}}),r={enumerable:!0,get:function(){return o.default}},r={enumerable:!0,get:function(){return s.default}},r={enumerable:!0,get:function(){return u.default}},r={enumerable:!0,get:function(){return c.default}},r={enumerable:!0,get:function(){return l.default}},r={enumerable:!0,get:function(){return f.default}},r={enumerable:!0,get:function(){return d.default}},r={enumerable:!0,get:function(){return h.default}},r={enumerable:!0,get:function(){return p.default}},Object.defineProperty(t,"ek",{enumerable:!0,get:function(){return b.default}}),r={enumerable:!0,get:function(){return m.default}},r={enumerable:!0,get:function(){return g.default}},r={enumerable:!0,get:function(){return v.default}},r={enumerable:!0,get:function(){return y.default}},r={enumerable:!0,get:function(){return w.default}},r={enumerable:!0,get:function(){return _.default}},r={enumerable:!0,get:function(){return E.default}},Object.defineProperty(t,"BA",{enumerable:!0,get:function(){return S.default}}),r={enumerable:!0,get:function(){return k.default}};var a=i(n(515)),o=i(n(83165)),s=i(n(124)),u=i(n(18118)),c=i(n(45018)),l=i(n(78768)),f=i(n(63259)),d=i(n(4923)),h=i(n(57646)),p=i(n(91605)),b=i(n(47559)),m=i(n(40192)),g=i(n(98567)),v=i(n(74578)),y=i(n(8070)),w=i(n(36594)),_=i(n(50173)),E=i(n(60169)),S=i(n(70167)),k=i(n(38236))},78768(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#e8eaf6",100:"#c5cae9",200:"#9fa8da",300:"#7986cb",400:"#5c6bc0",500:"#3f51b5",600:"#3949ab",700:"#303f9f",800:"#283593",900:"#1a237e",A100:"#8c9eff",A200:"#536dfe",A400:"#3d5afe",A700:"#304ffe"};t.default=n},4923(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#e1f5fe",100:"#b3e5fc",200:"#81d4fa",300:"#4fc3f7",400:"#29b6f6",500:"#03a9f4",600:"#039be5",700:"#0288d1",800:"#0277bd",900:"#01579b",A100:"#80d8ff",A200:"#40c4ff",A400:"#00b0ff",A700:"#0091ea"};t.default=n},40192(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#f1f8e9",100:"#dcedc8",200:"#c5e1a5",300:"#aed581",400:"#9ccc65",500:"#8bc34a",600:"#7cb342",700:"#689f38",800:"#558b2f",900:"#33691e",A100:"#ccff90",A200:"#b2ff59",A400:"#76ff03",A700:"#64dd17"};t.default=n},98567(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#f9fbe7",100:"#f0f4c3",200:"#e6ee9c",300:"#dce775",400:"#d4e157",500:"#cddc39",600:"#c0ca33",700:"#afb42b",800:"#9e9d24",900:"#827717",A100:"#f4ff81",A200:"#eeff41",A400:"#c6ff00",A700:"#aeea00"};t.default=n},36594(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#fff3e0",100:"#ffe0b2",200:"#ffcc80",300:"#ffb74d",400:"#ffa726",500:"#ff9800",600:"#fb8c00",700:"#f57c00",800:"#ef6c00",900:"#e65100",A100:"#ffd180",A200:"#ffab40",A400:"#ff9100",A700:"#ff6d00"};t.default=n},124(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#fce4ec",100:"#f8bbd0",200:"#f48fb1",300:"#f06292",400:"#ec407a",500:"#e91e63",600:"#d81b60",700:"#c2185b",800:"#ad1457",900:"#880e4f",A100:"#ff80ab",A200:"#ff4081",A400:"#f50057",A700:"#c51162"};t.default=n},18118(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#f3e5f5",100:"#e1bee7",200:"#ce93d8",300:"#ba68c8",400:"#ab47bc",500:"#9c27b0",600:"#8e24aa",700:"#7b1fa2",800:"#6a1b9a",900:"#4a148c",A100:"#ea80fc",A200:"#e040fb",A400:"#d500f9",A700:"#aa00ff"};t.default=n},83165(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#ffebee",100:"#ffcdd2",200:"#ef9a9a",300:"#e57373",400:"#ef5350",500:"#f44336",600:"#e53935",700:"#d32f2f",800:"#c62828",900:"#b71c1c",A100:"#ff8a80",A200:"#ff5252",A400:"#ff1744",A700:"#d50000"};t.default=n},91605(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#e0f2f1",100:"#b2dfdb",200:"#80cbc4",300:"#4db6ac",400:"#26a69a",500:"#009688",600:"#00897b",700:"#00796b",800:"#00695c",900:"#004d40",A100:"#a7ffeb",A200:"#64ffda",A400:"#1de9b6",A700:"#00bfa5"};t.default=n},74578(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={50:"#fffde7",100:"#fff9c4",200:"#fff59d",300:"#fff176",400:"#ffee58",500:"#ffeb3b",600:"#fdd835",700:"#fbc02d",800:"#f9a825",900:"#f57f17",A100:"#ffff8d",A200:"#ffff00",A400:"#ffea00",A700:"#ffd600"};t.default=n},85609(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.styles=void 0;var i=r(n(67154)),a=r(n(59713)),o=r(n(6479)),s=r(n(34575)),u=r(n(93913)),c=r(n(78585)),l=r(n(29754)),f=r(n(2205)),d=r(n(67294));r(n(45697));var h=r(n(94184)),p=r(n(52598)),b=r(n(78252)),m=r(n(81701)),g={root:{display:"inline-flex",alignItems:"center",transition:"none","&:hover":{backgroundColor:"transparent"}},checked:{},disabled:{},input:{cursor:"inherit",position:"absolute",opacity:0,width:"100%",height:"100%",top:0,left:0,margin:0,padding:0}};t.styles=g;var v=function(e){function t(e){var n;return(0,s.default)(this,t),(n=(0,c.default)(this,(0,l.default)(t).call(this))).handleFocus=function(e){n.props.onFocus&&n.props.onFocus(e);var t=n.props.muiFormControl;t&&t.onFocus&&t.onFocus(e)},n.handleBlur=function(e){n.props.onBlur&&n.props.onBlur(e);var t=n.props.muiFormControl;t&&t.onBlur&&t.onBlur(e)},n.handleInputChange=function(e){var t=e.target.checked;n.isControlled||n.setState({checked:t}),n.props.onChange&&n.props.onChange(e,t)},n.isControlled=null!=e.checked,n.state={},n.isControlled||(n.state.checked=void 0!==e.defaultChecked&&e.defaultChecked),n}return(0,f.default)(t,e),(0,u.default)(t,[{key:"render",value:function(){var e,t=this.props,n=t.autoFocus,r=t.checked,s=t.checkedIcon,u=t.classes,c=t.className,l=t.defaultChecked,f=t.disabled,p=t.icon,b=t.id,g=t.inputProps,v=t.inputRef,y=t.muiFormControl,w=t.name,_=(t.onBlur,t.onChange,t.onFocus,t.readOnly),E=t.required,S=t.tabIndex,k=t.type,x=t.value,T=(0,o.default)(t,["autoFocus","checked","checkedIcon","classes","className","defaultChecked","disabled","icon","id","inputProps","inputRef","muiFormControl","name","onBlur","onChange","onFocus","readOnly","required","tabIndex","type","value"]),M=f;y&&void 0===M&&(M=y.disabled);var O=this.isControlled?r:this.state.checked,A="checkbox"===k||"radio"===k;return d.default.createElement(m.default,(0,i.default)({component:"span",className:(0,h.default)(u.root,(e={},(0,a.default)(e,u.checked,O),(0,a.default)(e,u.disabled,M),e),c),disabled:M,tabIndex:null,role:void 0,onFocus:this.handleFocus,onBlur:this.handleBlur},T),O?s:p,d.default.createElement("input",(0,i.default)({autoFocus:n,checked:r,defaultChecked:l,className:u.input,disabled:M,id:A&&b,name:w,onChange:this.handleInputChange,readOnly:_,ref:v,required:E,tabIndex:S,type:k,value:x},g)))}}]),t}(d.default.Component),y=(0,b.default)(g,{name:"MuiPrivateSwitchBase"})((0,p.default)(v));t.default=y},13329(e,t){"use strict";function n(e){return(1+Math.sin(Math.PI*e-Math.PI/2))/2}function r(e,t,r){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},a=arguments.length>4&&void 0!==arguments[4]?arguments[4]:function(){},o=i.ease,s=void 0===o?n:o,u=i.duration,c=void 0===u?300:u,l=null,f=t[e],d=!1,h=function(){d=!0},p=function n(i){if(d){a(Error("Animation cancelled"));return}null===l&&(l=i);var o=Math.min(1,(i-l)/c);if(t[e]=s(o)*(r-f)+f,o>=1){requestAnimationFrame(function(){a(null)});return}requestAnimationFrame(n)};return f===r?(a(Error("Element already at target position")),h):(requestAnimationFrame(p),h)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r;t.default=i},74622(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67294)),a=r(n(46949)),o=r(n(40577)),s=i.default.createElement("path",{d:"M7 10l5 5 5-5z"}),u=function(e){return i.default.createElement(o.default,e,s)};(u=(0,a.default)(u)).muiName="SvgIcon";var c=u;t.default=c},99781(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67294)),a=r(n(46949)),o=r(n(40577)),s=i.default.createElement("path",{d:"M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"}),u=function(e){return i.default.createElement(o.default,e,s)};(u=(0,a.default)(u)).muiName="SvgIcon";var c=u;t.default=c},41549(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67294)),a=r(n(46949)),o=r(n(40577)),s=i.default.createElement("path",{d:"M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"}),u=function(e){return i.default.createElement(o.default,e,s)};(u=(0,a.default)(u)).muiName="SvgIcon";var c=u;t.default=c},42159(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67294)),a=r(n(46949)),o=r(n(40577)),s=i.default.createElement("path",{d:"M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"}),u=function(e){return i.default.createElement(o.default,e,s)};(u=(0,a.default)(u)).muiName="SvgIcon";var c=u;t.default=c},61486(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67294)),a=r(n(46949)),o=r(n(40577)),s=i.default.createElement("path",{d:"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10H7v-2h10v2z"}),u=function(e){return i.default.createElement(o.default,e,s)};(u=(0,a.default)(u)).muiName="SvgIcon";var c=u;t.default=c},86861(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67294)),a=r(n(46949)),o=r(n(40577)),s=i.default.createElement("path",{d:"M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"}),u=function(e){return i.default.createElement(o.default,e,s)};(u=(0,a.default)(u)).muiName="SvgIcon";var c=u;t.default=c},43836(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67294)),a=r(n(46949)),o=r(n(40577)),s=i.default.createElement("path",{d:"M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"}),u=function(e){return i.default.createElement(o.default,e,s)};(u=(0,a.default)(u)).muiName="SvgIcon";var c=u;t.default=c},93078(e,t,n){"use strict";/*! + * is-plain-object + * + * Copyright (c) 2014-2017, Jon Schlinkert. + * Released under the MIT License. + */ var r=n(47798);function i(e){return!0===r(e)&&"[object Object]"===Object.prototype.toString.call(e)}e.exports=function(e){var t,n;return!1!==i(e)&&"function"==typeof(t=e.constructor)&&!1!==i(n=t.prototype)&&!1!==n.hasOwnProperty("isPrototypeOf")}},72366(e,t,n){"use strict";var r=n(20862),i=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.MuiThemeProviderOld=void 0;var a=i(n(67154)),o=i(n(59713)),s=i(n(34575)),u=i(n(93913)),c=i(n(78585)),l=i(n(29754)),f=i(n(2205)),d=i(n(67294)),h=i(n(45697));i(n(42473));var p=i(n(43890)),b=n(55252),m=r(n(51067)),g=function(e){function t(e,n){var r;return(0,s.default)(this,t),(r=(0,c.default)(this,(0,l.default)(t).call(this))).broadcast=(0,p.default)(),r.outerTheme=m.default.initial(n),r.broadcast.setState(r.mergeOuterLocalTheme(e.theme)),r}return(0,f.default)(t,e),(0,u.default)(t,[{key:"getChildContext",value:function(){var e,t=this.props,n=t.disableStylesGeneration,r=t.sheetsCache,i=t.sheetsManager,a=this.context.muiThemeProviderOptions||{};return void 0!==n&&(a.disableStylesGeneration=n),void 0!==r&&(a.sheetsCache=r),void 0!==i&&(a.sheetsManager=i),e={},(0,o.default)(e,m.CHANNEL,this.broadcast),(0,o.default)(e,"muiThemeProviderOptions",a),e}},{key:"componentDidMount",value:function(){var e=this;this.unsubscribeId=m.default.subscribe(this.context,function(t){e.outerTheme=t,e.broadcast.setState(e.mergeOuterLocalTheme(e.props.theme))})}},{key:"componentDidUpdate",value:function(e){this.props.theme!==e.theme&&this.broadcast.setState(this.mergeOuterLocalTheme(this.props.theme))}},{key:"componentWillUnmount",value:function(){null!==this.unsubscribeId&&m.default.unsubscribe(this.context,this.unsubscribeId)}},{key:"mergeOuterLocalTheme",value:function(e){return"function"==typeof e?e(this.outerTheme):this.outerTheme?(0,a.default)({},this.outerTheme,e):e}},{key:"render",value:function(){return this.props.children}}]),t}(d.default.Component);t.MuiThemeProviderOld=g,g.childContextTypes=(0,a.default)({},m.default.contextTypes,{muiThemeProviderOptions:h.default.object}),g.contextTypes=(0,a.default)({},m.default.contextTypes,{muiThemeProviderOptions:h.default.object}),b.ponyfillGlobal.__MUI_STYLES__||(b.ponyfillGlobal.__MUI_STYLES__={}),b.ponyfillGlobal.__MUI_STYLES__.MuiThemeProvider||(b.ponyfillGlobal.__MUI_STYLES__.MuiThemeProvider=g);var v=b.ponyfillGlobal.__MUI_STYLES__.MuiThemeProvider;t.default=v},59114(e,t,n){"use strict";var r=n(95318);function i(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;return en?n:e}function a(e){e=e.substr(1);var t=RegExp(".{1,".concat(e.length/3,"}"),"g"),n=e.match(t);return n&&1===n[0].length&&(n=n.map(function(e){return e+e})),n?"rgb(".concat(n.map(function(e){return parseInt(e,16)}).join(", "),")"):""}function o(e){if(0===e.indexOf("#"))return e;function t(e){var t=e.toString(16);return 1===t.length?"0".concat(t):t}var n=s(e).values;return n=n.map(function(e){return t(e)}),"#".concat(n.join(""))}function s(e){if("#"===e.charAt(0))return s(a(e));var t=e.indexOf("("),n=e.substring(0,t),r=e.substring(t+1,e.length-1).split(",");return r=r.map(function(e){return parseFloat(e)}),{type:n,values:r}}function u(e){var t=e.type,n=e.values;return -1!==t.indexOf("rgb")&&(n=n.map(function(e,t){return t<3?parseInt(e,10):e})),-1!==t.indexOf("hsl")&&(n[1]="".concat(n[1],"%"),n[2]="".concat(n[2],"%")),"".concat(e.type,"(").concat(n.join(", "),")")}function c(e,t){var n=l(e),r=l(t);return(Math.max(n,r)+.05)/(Math.min(n,r)+.05)}function l(e){var t=s(e);if(-1!==t.type.indexOf("rgb")){var n=t.values.map(function(e){return(e/=255)<=.03928?e/12.92:Math.pow((e+.055)/1.055,2.4)});return Number((.2126*n[0]+.7152*n[1]+.0722*n[2]).toFixed(3))}return t.values[2]/100}function f(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:.15;return l(e)>.5?h(e,t):p(e,t)}function d(e,t){return e?(e=s(e),t=i(t),("rgb"===e.type||"hsl"===e.type)&&(e.type+="a"),e.values[3]=t,u(e)):e}function h(e,t){if(!e)return e;if(e=s(e),t=i(t),-1!==e.type.indexOf("hsl"))e.values[2]*=1-t;else if(-1!==e.type.indexOf("rgb"))for(var n=0;n<3;n+=1)e.values[n]*=1-t;return u(e)}function p(e,t){if(!e)return e;if(e=s(e),t=i(t),-1!==e.type.indexOf("hsl"))e.values[2]+=(100-e.values[2])*t;else if(-1!==e.type.indexOf("rgb"))for(var n=0;n<3;n+=1)e.values[n]+=(255-e.values[n])*t;return u(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.convertHexToRGB=a,t.rgbToHex=o,t.decomposeColor=s,t.recomposeColor=u,t.getContrastRatio=c,t.getLuminance=l,t.emphasize=f,t.fade=d,t.darken=h,t.lighten=p,r(n(42473))},94811(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=s,t.keys=void 0;var i=r(n(67154)),a=r(n(6479)),o=["xs","sm","md","lg","xl"];function s(e){var t=e.values,n=void 0===t?{xs:0,sm:600,md:960,lg:1280,xl:1920}:t,r=e.unit,s=void 0===r?"px":r,u=e.step,c=void 0===u?5:u,l=(0,a.default)(e,["values","unit","step"]);function f(e){var t="number"==typeof n[e]?n[e]:e;return"@media (min-width:".concat(t).concat(s,")")}function d(e){var t=o.indexOf(e)+1,r=n[o[t]];if(t===o.length)return f("xs");var i="number"==typeof r&&t>0?r:e;return"@media (max-width:".concat(i-c/100).concat(s,")")}function h(e,t){var r=o.indexOf(t)+1;return r===o.length?f(e):"@media (min-width:".concat(n[e]).concat(s,") and ")+"(max-width:".concat(n[o[r]]-c/100).concat(s,")")}function p(e){return h(e,e)}function b(e){return n[e]}return(0,i.default)({keys:o,values:n,up:f,down:d,between:h,only:p,width:b},l)}t.keys=o},20237(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=o,r(n(42473));var i=/([[\].#*$><+~=|^:(),"'`\s])/g;function a(e){var t;return String(e).replace(i,"-")}function o(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.dangerouslyUseGlobalCSS,n=void 0!==t&&t,r=e.productionPrefix,i=void 0===r?"jss":r,o=e.seed,s=void 0===o?"":o,u=0;return function(e,t){return(u+=1,n&&t&&t.options.name)?"".concat(a(t.options.name),"-").concat(e.key):"".concat(i).concat(s).concat(u)}}},40226(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=r(n(59713)),a=r(n(67154));function o(e,t,n){var r;return(0,a.default)({gutters:function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(0,a.default)({paddingLeft:2*t.unit,paddingRight:2*t.unit},n,(0,i.default)({},e.up("sm"),(0,a.default)({paddingLeft:3*t.unit,paddingRight:3*t.unit},n[e.up("sm")])))},toolbar:(r={minHeight:56},(0,i.default)(r,"".concat(e.up("xs")," and (orientation: landscape)"),{minHeight:48}),(0,i.default)(r,e.up("sm"),{minHeight:64}),r)},n)}},71615(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,r(n(59713));var i=r(n(67154)),a=r(n(6479)),o=r(n(94863)),s=r(n(93078));r(n(42473));var u=r(n(94811)),c=r(n(40226)),l=r(n(21091)),f=r(n(45184)),d=r(n(80743)),h=r(n(59591)),p=r(n(5324)),b=r(n(15406)),m=r(n(88676));function g(){var e,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.breakpoints,r=void 0===n?{}:n,g=t.mixins,v=void 0===g?{}:g,y=t.palette,w=void 0===y?{}:y,_=t.shadows,E=t.spacing,S=void 0===E?{}:E,k=t.typography,x=void 0===k?{}:k,T=(0,a.default)(t,["breakpoints","mixins","palette","shadows","spacing","typography"]),M=(0,l.default)(w),O=(0,u.default)(r),A=(0,i.default)({},p.default,S);return(0,i.default)({breakpoints:O,direction:"ltr",mixins:(0,c.default)(O,A,v),overrides:{},palette:M,props:{},shadows:_||d.default,typography:(0,f.default)(M,x)},(0,o.default)({shape:h.default,spacing:A,transitions:b.default,zIndex:m.default},T,{isMergeableObject:s.default}))}var v=g;t.default=v},21091(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=m,t.dark=t.light=void 0;var i=r(n(67154)),a=r(n(6479));r(n(42473));var o=r(n(94863)),s=r(n(78768)),u=r(n(124)),c=r(n(70167)),l=r(n(83165)),f=r(n(515)),d=n(59114),h={text:{primary:"rgba(0, 0, 0, 0.87)",secondary:"rgba(0, 0, 0, 0.54)",disabled:"rgba(0, 0, 0, 0.38)",hint:"rgba(0, 0, 0, 0.38)"},divider:"rgba(0, 0, 0, 0.12)",background:{paper:f.default.white,default:c.default[50]},action:{active:"rgba(0, 0, 0, 0.54)",hover:"rgba(0, 0, 0, 0.08)",hoverOpacity:.08,selected:"rgba(0, 0, 0, 0.14)",disabled:"rgba(0, 0, 0, 0.26)",disabledBackground:"rgba(0, 0, 0, 0.12)"}};t.light=h;var p={text:{primary:f.default.white,secondary:"rgba(255, 255, 255, 0.7)",disabled:"rgba(255, 255, 255, 0.5)",hint:"rgba(255, 255, 255, 0.5)",icon:"rgba(255, 255, 255, 0.5)"},divider:"rgba(255, 255, 255, 0.12)",background:{paper:c.default[800],default:"#303030"},action:{active:f.default.white,hover:"rgba(255, 255, 255, 0.1)",hoverOpacity:.1,selected:"rgba(255, 255, 255, 0.2)",disabled:"rgba(255, 255, 255, 0.3)",disabledBackground:"rgba(255, 255, 255, 0.12)"}};function b(e,t,n,r){e[t]||(e.hasOwnProperty(n)?e[t]=e[n]:"light"===t?e.light=(0,d.lighten)(e.main,r):"dark"===t&&(e.dark=(0,d.darken)(e.main,1.5*r)))}function m(e){var t=e.primary,n=void 0===t?{light:s.default[300],main:s.default[500],dark:s.default[700]}:t,r=e.secondary,m=void 0===r?{light:u.default.A200,main:u.default.A400,dark:u.default.A700}:r,g=e.error,v=void 0===g?{light:l.default[300],main:l.default[500],dark:l.default[700]}:g,y=e.type,w=void 0===y?"light":y,_=e.contrastThreshold,E=void 0===_?3:_,S=e.tonalOffset,k=void 0===S?.2:S,x=(0,a.default)(e,["primary","secondary","error","type","contrastThreshold","tonalOffset"]);function T(e){var t;return(0,d.getContrastRatio)(e,p.text.primary)>=E?p.text.primary:h.text.primary}function M(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:500,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:300,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:700;return!e.main&&e[t]&&(e.main=e[t]),b(e,"light",n,k),b(e,"dark",r,k),e.contrastText||(e.contrastText=T(e.main)),e}M(n),M(m,"A400","A200","A700"),M(v);var O={dark:p,light:h};return(0,o.default)((0,i.default)({common:f.default,type:w,primary:n,secondary:m,error:v,grey:c.default,contrastThreshold:E,getContrastText:T,augmentColor:M,tonalOffset:k},O[w]),x,{clone:!1})}t.dark=p},16059(e,t){"use strict";function n(e){return e}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},45184(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=f;var i=r(n(67154)),a=r(n(6479)),o=r(n(94863));r(n(42473));var s=n(55252);function u(e){return Math.round(1e5*e)/1e5}var c={textTransform:"uppercase"},l='"Roboto", "Helvetica", "Arial", sans-serif';function f(e,t){var n="function"==typeof t?t(e):t,r=n.fontFamily,f=void 0===r?l:r,d=n.fontSize,h=void 0===d?14:d,p=n.fontWeightLight,b=void 0===p?300:p,m=n.fontWeightRegular,g=void 0===m?400:m,v=n.fontWeightMedium,y=void 0===v?500:v,w=n.htmlFontSize,_=void 0===w?16:w,E=n.useNextVariants,S=void 0===E?Boolean(s.ponyfillGlobal.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__):E,k=(n.suppressWarning,n.allVariants),x=(0,a.default)(n,["fontFamily","fontSize","fontWeightLight","fontWeightRegular","fontWeightMedium","htmlFontSize","useNextVariants","suppressWarning","allVariants"]),T=h/14,M=function(e){return"".concat(e/_*T,"rem")},O=function(t,n,r,a,o){return(0,i.default)({color:e.text.primary,fontFamily:f,fontWeight:t,fontSize:M(n),lineHeight:r},f===l?{letterSpacing:"".concat(u(a/n),"em")}:{},o,k)},A={h1:O(b,96,1,-1.5),h2:O(b,60,1,-.5),h3:O(g,48,1.04,0),h4:O(g,34,1.17,.25),h5:O(g,24,1.33,0),h6:O(y,20,1.6,.15),subtitle1:O(g,16,1.75,.15),subtitle2:O(y,14,1.57,.1),body1Next:O(g,16,1.5,.15),body2Next:O(g,14,1.5,.15),buttonNext:O(y,14,1.75,.4,c),captionNext:O(g,12,1.66,.4),overline:O(g,12,2.66,1,c)},L={display4:(0,i.default)({fontSize:M(112),fontWeight:b,fontFamily:f,letterSpacing:"-.04em",lineHeight:"".concat(u(128/112),"em"),marginLeft:"-.04em",color:e.text.secondary},k),display3:(0,i.default)({fontSize:M(56),fontWeight:g,fontFamily:f,letterSpacing:"-.02em",lineHeight:"".concat(u(73/56),"em"),marginLeft:"-.02em",color:e.text.secondary},k),display2:(0,i.default)({fontSize:M(45),fontWeight:g,fontFamily:f,lineHeight:"".concat(u(51/45),"em"),marginLeft:"-.02em",color:e.text.secondary},k),display1:(0,i.default)({fontSize:M(34),fontWeight:g,fontFamily:f,lineHeight:"".concat(u(41/34),"em"),color:e.text.secondary},k),headline:(0,i.default)({fontSize:M(24),fontWeight:g,fontFamily:f,lineHeight:"".concat(u(32.5/24),"em"),color:e.text.primary},k),title:(0,i.default)({fontSize:M(21),fontWeight:y,fontFamily:f,lineHeight:"".concat(u(24.5/21),"em"),color:e.text.primary},k),subheading:(0,i.default)({fontSize:M(16),fontWeight:g,fontFamily:f,lineHeight:"".concat(u(1.5),"em"),color:e.text.primary},k),body2:(0,i.default)({fontSize:M(14),fontWeight:y,fontFamily:f,lineHeight:"".concat(u(24/14),"em"),color:e.text.primary},k),body1:(0,i.default)({fontSize:M(14),fontWeight:g,fontFamily:f,lineHeight:"".concat(u(20.5/14),"em"),color:e.text.primary},k),caption:(0,i.default)({fontSize:M(12),fontWeight:g,fontFamily:f,lineHeight:"".concat(u(1.375),"em"),color:e.text.secondary},k),button:(0,i.default)({fontSize:M(14),textTransform:"uppercase",fontWeight:y,fontFamily:f,color:e.text.primary},k)};return(0,o.default)((0,i.default)({pxToRem:M,round:u,fontFamily:f,fontSize:h,fontWeightLight:b,fontWeightRegular:g,fontWeightMedium:y},L,A,S?{body1:A.body1Next,body2:A.body2Next,button:A.buttonNext,caption:A.captionNext}:{},{useNextVariants:S}),x,{clone:!1})}},42458(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67154));r(n(50008)),r(n(42473));var a=r(n(94863));function o(e,t){return t}function s(e){var t="function"==typeof e;function n(n,r){var s=t?e(n):e;if(!r||!n.overrides||!n.overrides[r])return s;var u=n.overrides[r],c=(0,i.default)({},s);return Object.keys(u).forEach(function(e){c[e]=(0,a.default)(c[e],u[e],{arrayMerge:o})}),c}return{create:n,options:{},themingEnabled:t}}var u=s;t.default=u},58057(e,t){"use strict";function n(e){var t,n=e.theme,r=e.name,i=e.props;if(!n.props||!r||!n.props[r])return i;var a=n.props[r];for(t in a)void 0===i[t]&&(i[t]=a[t]);return i}Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=n;t.default=r},32316(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createGenerateClassName",{enumerable:!0,get:function(){return i.default}}),Object.defineProperty(t,"createMuiTheme",{enumerable:!0,get:function(){return a.default}}),Object.defineProperty(t,"jssPreset",{enumerable:!0,get:function(){return o.default}}),Object.defineProperty(t,"MuiThemeProvider",{enumerable:!0,get:function(){return s.default}}),Object.defineProperty(t,"createStyles",{enumerable:!0,get:function(){return u.default}}),Object.defineProperty(t,"withStyles",{enumerable:!0,get:function(){return c.default}}),Object.defineProperty(t,"withTheme",{enumerable:!0,get:function(){return l.default}});var i=r(n(20237)),a=r(n(71615)),o=r(n(9399)),s=r(n(72366)),u=r(n(16059)),c=r(n(78252)),l=r(n(82313))},9399(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(29059)),a=r(n(28752)),o=r(n(35828)),s=r(n(50462)),u=r(n(65926)),c=r(n(89347));function l(){return{plugins:[(0,i.default)(),(0,a.default)(),(0,o.default)(),(0,s.default)(),"undefined"==typeof window?null:(0,u.default)(),(0,c.default)()]}}var f=l;t.default=f},35199(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i=r(n(67154));function a(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.baseClasses,n=e.newClasses;if(e.Component,!n)return t;var r=(0,i.default)({},t);return Object.keys(n).forEach(function(e){n[e]&&(r[e]="".concat(t[e]," ").concat(n[e]))}),r}r(n(42473)),n(55252);var o=a;t.default=o},88693(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={set:function(e,t,n,r){var i=e.get(t);i||(i=new Map,e.set(t,i)),i.set(n,r)},get:function(e,t,n){var r=e.get(t);return r?r.get(n):void 0},delete:function(e,t,n){e.get(t).delete(n)}};t.default=n},31898(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={jss:"64a55d578f856d258dc345b094a2a2b3",sheetsRegistry:"d4bd0baacbc52bbd48bbb9eb24344ecd",sheetOptions:"6fc570d6bd61383819d0f9e7407c452d"};t.default=n},80743(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n=.2,r=.14,i=.12;function a(){return["".concat(arguments.length<=0?void 0:arguments[0],"px ").concat(arguments.length<=1?void 0:arguments[1],"px ").concat(arguments.length<=2?void 0:arguments[2],"px ").concat(arguments.length<=3?void 0:arguments[3],"px rgba(0,0,0,").concat(n,")"),"".concat(arguments.length<=4?void 0:arguments[4],"px ").concat(arguments.length<=5?void 0:arguments[5],"px ").concat(arguments.length<=6?void 0:arguments[6],"px ").concat(arguments.length<=7?void 0:arguments[7],"px rgba(0,0,0,").concat(r,")"),"".concat(arguments.length<=8?void 0:arguments[8],"px ").concat(arguments.length<=9?void 0:arguments[9],"px ").concat(arguments.length<=10?void 0:arguments[10],"px ").concat(arguments.length<=11?void 0:arguments[11],"px rgba(0,0,0,").concat(i,")")].join(",")}var o=["none",a(0,1,3,0,0,1,1,0,0,2,1,-1),a(0,1,5,0,0,2,2,0,0,3,1,-2),a(0,1,8,0,0,3,4,0,0,3,3,-2),a(0,2,4,-1,0,4,5,0,0,1,10,0),a(0,3,5,-1,0,5,8,0,0,1,14,0),a(0,3,5,-1,0,6,10,0,0,1,18,0),a(0,4,5,-2,0,7,10,1,0,2,16,1),a(0,5,5,-3,0,8,10,1,0,3,14,2),a(0,5,6,-3,0,9,12,1,0,3,16,2),a(0,6,6,-3,0,10,14,1,0,4,18,3),a(0,6,7,-4,0,11,15,1,0,4,20,3),a(0,7,8,-4,0,12,17,2,0,5,22,4),a(0,7,8,-4,0,13,19,2,0,5,24,4),a(0,7,9,-4,0,14,21,2,0,5,26,4),a(0,8,9,-5,0,15,22,2,0,6,28,5),a(0,8,10,-5,0,16,24,2,0,6,30,5),a(0,8,11,-5,0,17,26,2,0,6,32,5),a(0,9,11,-5,0,18,28,2,0,7,34,6),a(0,9,12,-6,0,19,29,2,0,7,36,6),a(0,10,13,-6,0,20,31,3,0,8,38,7),a(0,10,13,-6,0,21,33,3,0,8,40,7),a(0,10,14,-6,0,22,35,3,0,8,42,7),a(0,11,14,-7,0,23,36,3,0,9,44,8),a(0,11,15,-7,0,24,38,3,0,9,46,8)];t.default=o},59591(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={borderRadius:4};t.default=n},5324(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={unit:8};t.default=n},51067(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.CHANNEL=void 0;var i=r(n(59713)),a="__THEMING__";t.CHANNEL=a;var o={contextTypes:(0,i.default)({},a,function(){}),initial:function(e){return e[a]?e[a].getState():null},subscribe:function(e,t){return e[a]?e[a].subscribe(t):null},unsubscribe:function(e,t){e[a]&&e[a].unsubscribe(t)}};t.default=o},15406(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.isNumber=t.isString=t.formatMs=t.duration=t.easing=void 0;var i=r(n(6479));r(n(42473));var a={easeInOut:"cubic-bezier(0.4, 0, 0.2, 1)",easeOut:"cubic-bezier(0.0, 0, 0.2, 1)",easeIn:"cubic-bezier(0.4, 0, 1, 1)",sharp:"cubic-bezier(0.4, 0, 0.6, 1)"};t.easing=a;var o={shortest:150,shorter:200,short:250,standard:300,complex:375,enteringScreen:225,leavingScreen:195};t.duration=o;var s=function(e){return"".concat(Math.round(e),"ms")};t.formatMs=s;var u=function(e){return"string"==typeof e};t.isString=u;var c=function(e){return!isNaN(parseFloat(e))};t.isNumber=c;var l={easing:a,duration:o,create:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["all"],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.duration,r=void 0===n?o.standard:n,u=t.easing,c=void 0===u?a.easeInOut:u,l=t.delay,f=void 0===l?0:l;return(0,i.default)(t,["duration","easing","delay"]),(Array.isArray(e)?e:[e]).map(function(e){return"".concat(e," ").concat("string"==typeof r?r:s(r)," ").concat(c," ").concat("string"==typeof f?f:s(f))}).join(",")},getAutoHeightDuration:function(e){if(!e)return 0;var t=e/36;return Math.round((4+15*Math.pow(t,.25)+t/5)*10)}};t.default=l},78252(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.sheetsManager=void 0;var i=r(n(59713)),a=r(n(67154)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(6479)),d=r(n(67294)),h=r(n(45697));r(n(42473));var p=r(n(8679)),b=n(55252),m=n(55690),g=r(n(31898)),v=r(n(9399)),y=r(n(35199)),w=r(n(88693)),_=r(n(71615)),E=r(n(51067)),S=r(n(20237)),k=r(n(42458)),x=r(n(58057)),T=(0,m.create)((0,v.default)()),M=(0,S.default)(),O=-1e11,A=new Map;t.sheetsManager=A;var L={},C=(0,_.default)({typography:{suppressWarning:!0}}),I=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return function(n){var r,b=t.withTheme,m=void 0!==b&&b,v=t.flip,_=void 0===v?null:v,S=t.name,I=(0,f.default)(t,["withTheme","flip","name"]),D=(0,k.default)(e),N=D.themingEnabled||"string"==typeof S||m;O+=1,D.options.index=O;var P=function(e){function t(e,n){(0,o.default)(this,t),(r=(0,u.default)(this,(0,c.default)(t).call(this,e,n))).jss=n[g.default.jss]||T,r.sheetsManager=A,r.unsubscribeId=null;var r,i=n.muiThemeProviderOptions;return i&&(i.sheetsManager&&(r.sheetsManager=i.sheetsManager),r.sheetsCache=i.sheetsCache,r.disableStylesGeneration=i.disableStylesGeneration),r.stylesCreatorSaved=D,r.sheetOptions=(0,a.default)({generateClassName:M},n[g.default.sheetOptions]),r.theme=N?E.default.initial(n)||C:L,r.attach(r.theme),r.cacheClasses={value:null,lastProp:null,lastJSS:{}},r}return(0,l.default)(t,e),(0,s.default)(t,[{key:"componentDidMount",value:function(){var e=this;N&&(this.unsubscribeId=E.default.subscribe(this.context,function(t){var n=e.theme;e.theme=t,e.attach(e.theme),e.setState({},function(){e.detach(n)})}))}},{key:"componentDidUpdate",value:function(){this.stylesCreatorSaved}},{key:"componentWillUnmount",value:function(){this.detach(this.theme),null!==this.unsubscribeId&&E.default.unsubscribe(this.context,this.unsubscribeId)}},{key:"getClasses",value:function(){if(this.disableStylesGeneration)return this.props.classes||{};var e=!1,t=w.default.get(this.sheetsManager,this.stylesCreatorSaved,this.theme);return t.sheet.classes!==this.cacheClasses.lastJSS&&(this.cacheClasses.lastJSS=t.sheet.classes,e=!0),this.props.classes!==this.cacheClasses.lastProp&&(this.cacheClasses.lastProp=this.props.classes,e=!0),e&&(this.cacheClasses.value=(0,y.default)({baseClasses:this.cacheClasses.lastJSS,newClasses:this.props.classes,Component:n})),this.cacheClasses.value}},{key:"attach",value:function(e){if(!this.disableStylesGeneration){var t=this.stylesCreatorSaved,n=w.default.get(this.sheetsManager,t,e);if(n||(n={refs:0,sheet:null},w.default.set(this.sheetsManager,t,e,n)),0===n.refs){this.sheetsCache&&(r=w.default.get(this.sheetsCache,t,e)),!r&&((r=this.createSheet(e)).attach(),this.sheetsCache&&w.default.set(this.sheetsCache,t,e,r)),n.sheet=r;var r,i=this.context[g.default.sheetsRegistry];i&&i.add(r)}n.refs+=1}}},{key:"createSheet",value:function(e){var t=this.stylesCreatorSaved.create(e,S),r=S;return this.jss.createStyleSheet(t,(0,a.default)({meta:r,classNamePrefix:r,flip:"boolean"==typeof _?_:"rtl"===e.direction,link:!1},this.sheetOptions,this.stylesCreatorSaved.options,{name:S||n.displayName},I))}},{key:"detach",value:function(e){if(!this.disableStylesGeneration){var t=w.default.get(this.sheetsManager,this.stylesCreatorSaved,e);if(t.refs-=1,0===t.refs){w.default.delete(this.sheetsManager,this.stylesCreatorSaved,e),this.jss.removeStyleSheet(t.sheet);var n=this.context[g.default.sheetsRegistry];n&&n.remove(t.sheet)}}}},{key:"render",value:function(){var e=this.props,t=(e.classes,e.innerRef),r=(0,f.default)(e,["classes","innerRef"]),i=(0,x.default)({theme:this.theme,name:S,props:r});return m&&!i.theme&&(i.theme=this.theme),d.default.createElement(n,(0,a.default)({},i,{classes:this.getClasses(),ref:t}))}}]),t}(d.default.Component);return P.contextTypes=(0,a.default)((r={muiThemeProviderOptions:h.default.object},(0,i.default)(r,g.default.jss,h.default.object),(0,i.default)(r,g.default.sheetOptions,h.default.object),(0,i.default)(r,g.default.sheetsRegistry,h.default.object),r),N?E.default.contextTypes:{}),(0,p.default)(P,n),P}};b.ponyfillGlobal.__MUI_STYLES__||(b.ponyfillGlobal.__MUI_STYLES__={}),b.ponyfillGlobal.__MUI_STYLES__.withStyles||(b.ponyfillGlobal.__MUI_STYLES__.withStyles=I);var D=function(e,t){return b.ponyfillGlobal.__MUI_STYLES__.withStyles(e,(0,a.default)({defaultTheme:C},t))};t.default=D},82313(e,t,n){"use strict";var r,i=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=i(n(67154)),o=i(n(6479)),s=i(n(34575)),u=i(n(93913)),c=i(n(78585)),l=i(n(29754)),f=i(n(2205)),d=i(n(67294));i(n(45697));var h=i(n(8679)),p=n(55252),b=i(n(71615)),m=i(n(51067));function g(){return r||(r=(0,b.default)({typography:{suppressWarning:!0}}))}var v=function(){return function(e){var t=function(t){function n(e,t){var r;return(0,s.default)(this,n),(r=(0,c.default)(this,(0,l.default)(n).call(this))).state={theme:m.default.initial(t)||g()},r}return(0,f.default)(n,t),(0,u.default)(n,[{key:"componentDidMount",value:function(){var e=this;this.unsubscribeId=m.default.subscribe(this.context,function(t){e.setState({theme:t})})}},{key:"componentWillUnmount",value:function(){null!==this.unsubscribeId&&m.default.unsubscribe(this.context,this.unsubscribeId)}},{key:"render",value:function(){var t=this.props,n=t.innerRef,r=(0,o.default)(t,["innerRef"]);return d.default.createElement(e,(0,a.default)({theme:this.state.theme,ref:n},r))}}]),n}(d.default.Component);return t.contextTypes=m.default.contextTypes,(0,h.default)(t,e),t}};p.ponyfillGlobal.__MUI_STYLES__||(p.ponyfillGlobal.__MUI_STYLES__={}),p.ponyfillGlobal.__MUI_STYLES__.withTheme||(p.ponyfillGlobal.__MUI_STYLES__.withTheme=v);var y=p.ponyfillGlobal.__MUI_STYLES__.withTheme;t.default=y},88676(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={mobileStepper:1e3,appBar:1100,drawer:1200,modal:1300,snackbar:1400,tooltip:1500};t.default=n},41929(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getTransitionProps=r,t.reflow=void 0;var n=function(e){return e.scrollTop};function r(e,t){var n=e.timeout,r=e.style,i=void 0===r?{}:r;return{duration:i.transitionDuration||"number"==typeof n?n:n[t.mode],delay:i.transitionDelay}}t.reflow=n},346(e,t){"use strict";function n(e,t){return function(){return null}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=n;t.default=r},98741(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.capitalize=a,t.contains=o,t.findIndex=s,t.find=u,t.createChainedFunction=c;var i=r(n(50008));function a(e){return e.charAt(0).toUpperCase()+e.slice(1)}function o(e,t){return Object.keys(t).every(function(n){return e.hasOwnProperty(n)&&e[n]===t[n]})}function s(e,t){for(var n=(0,i.default)(t),r=0;r-1?e[n]:void 0}function c(){for(var e=arguments.length,t=Array(e),n=0;n1&&void 0!==arguments[1]?arguments[1]:window,n=(0,i.default)(e);return n.defaultView||n.parentView||t}var o=a;t.default=o},44370(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.cloneElementWithClassName=o,t.cloneChildrenWithClassName=s,t.isMuiElement=u,t.setRef=c;var i=r(n(67294)),a=r(n(94184));function o(e,t){return i.default.cloneElement(e,{className:(0,a.default)(e.props.className,t)})}function s(e,t){return i.default.Children.map(e,function(e){return i.default.isValidElement(e)&&o(e,t)})}function u(e,t){return i.default.isValidElement(e)&&-1!==t.indexOf(e.type.muiName)}function c(e,t){"function"==typeof e?e(t):e&&(e.current=t)}},47348(e,t){"use strict";function n(e){return function(){return null}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=n;t.default=r},21677(e,t){"use strict";function n(e,t,n,r,i){return null}Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=n;t.default=r},78290(e,t,n){"use strict";var r=n(20862);Object.defineProperty(t,"__esModule",{value:!0});var i={};Object.defineProperty(t,"default",{enumerable:!0,get:function(){return a.default}});var a=r(n(88446));Object.keys(a).forEach(function(e){"default"!==e&&"__esModule"!==e&&(Object.prototype.hasOwnProperty.call(i,e)||Object.defineProperty(t,e,{enumerable:!0,get:function(){return a[e]}}))})},88446(e,t,n){"use strict";var r=n(95318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.isWidthDown=t.isWidthUp=void 0;var i=r(n(67154)),a=r(n(6479)),o=r(n(34575)),s=r(n(93913)),u=r(n(78585)),c=r(n(29754)),l=r(n(2205)),f=r(n(67294));r(n(45697));var d=r(n(96421)),h=r(n(20296));n(55252);var p=r(n(8679)),b=r(n(82313)),m=n(94811),g=r(n(58057)),v=function(e,t){var n=!(arguments.length>2)||void 0===arguments[2]||arguments[2];return n?m.keys.indexOf(e)<=m.keys.indexOf(t):m.keys.indexOf(e)2)||void 0===arguments[2]||arguments[2];return n?m.keys.indexOf(t)<=m.keys.indexOf(e):m.keys.indexOf(t)0&&void 0!==arguments[0]?arguments[0]:{};return function(t){var n=e.withTheme,r=void 0!==n&&n,v=e.noSSR,y=void 0!==v&&v,w=e.initialWidth,_=e.resizeInterval,E=void 0===_?166:_,S=function(e){function n(e){var t;return(0,o.default)(this,n),(t=(0,u.default)(this,(0,c.default)(n).call(this,e))).state={width:y?t.getWidth():void 0},"undefined"!=typeof window&&(t.handleResize=(0,h.default)(function(){var e=t.getWidth();e!==t.state.width&&t.setState({width:e})},E)),t}return(0,l.default)(n,e),(0,s.default)(n,[{key:"componentDidMount",value:function(){var e=this.getWidth();e!==this.state.width&&this.setState({width:e})}},{key:"componentWillUnmount",value:function(){this.handleResize.clear()}},{key:"getWidth",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:window.innerWidth,t=this.props.theme.breakpoints,n=null,r=1;null===n&&ri.Z,componentPropType:()=>r.Z,exactProp:()=>a.ZP,getDisplayName:()=>o.ZP,ponyfillGlobal:()=>s.Z});var r=n(78728),i=n(5477),a=n(43781),o=n(25189),s=n(34712);/** @license Material-UI v3.0.0-alpha.3 + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ },34712(e,t){"use strict";n={value:!0},t.Z=void 0;var n,r="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();t.Z=r},82152(e,t,n){"use strict";n.d(t,{D:()=>u});var r=Object.prototype,i=r.toString,a=r.hasOwnProperty,o=Function.prototype.toString,s=new Map;function u(e,t){try{return c(e,t)}finally{s.clear()}}function c(e,t){if(e===t)return!0;var n=i.call(e),r=i.call(t);if(n!==r)return!1;switch(n){case"[object Array]":if(e.length!==t.length)break;case"[object Object]":if(p(e,t))return!0;var s=l(e),u=l(t),f=s.length;if(f!==u.length)break;for(var b=0;b=0&&e.indexOf(t,n)===n}function p(e,t){var n=s.get(e);if(n){if(n.has(t))return!0}else s.set(e,n=new Set);return n.add(t),!1}},79742(e,t){"use strict";t.byteLength=c,t.toByteArray=f,t.fromByteArray=p;for(var n=[],r=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",o=0,s=a.length;o0)throw Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");-1===n&&(n=t);var r=n===t?0:4-n%4;return[n,r]}function c(e){var t=u(e),n=t[0],r=t[1];return(n+r)*3/4-r}function l(e,t,n){return(t+n)*3/4-n}function f(e){var t,n,a=u(e),o=a[0],s=a[1],c=new i(l(e,o,s)),f=0,d=s>0?o-4:o;for(n=0;n>16&255,c[f++]=t>>8&255,c[f++]=255&t;return 2===s&&(t=r[e.charCodeAt(n)]<<2|r[e.charCodeAt(n+1)]>>4,c[f++]=255&t),1===s&&(t=r[e.charCodeAt(n)]<<10|r[e.charCodeAt(n+1)]<<4|r[e.charCodeAt(n+2)]>>2,c[f++]=t>>8&255,c[f++]=255&t),c}function d(e){return n[e>>18&63]+n[e>>12&63]+n[e>>6&63]+n[63&e]}function h(e,t,n){for(var r,i=[],a=t;au?u:s+o));return 1===i?a.push(n[(t=e[r-1])>>2]+n[t<<4&63]+"=="):2===i&&a.push(n[(t=(e[r-2]<<8)+e[r-1])>>10]+n[t>>4&63]+n[t<<2&63]+"="),a.join("")}r["-".charCodeAt(0)]=62,r["_".charCodeAt(0)]=63},44431:function(e,t,n){var r;!function(i){"use strict";var a,o=/^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i,s=Math.ceil,u=Math.floor,c="[BigNumber Error] ",l=c+"Number primitive has more than 15 significant digits: ",f=1e14,d=14,h=9007199254740991,p=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],b=1e7,m=1e9;function g(e){var t,n,r,i,a,x,T,M,O,A,L=$.prototype={constructor:$,toString:null,valueOf:null},C=new $(1),I=20,D=4,N=-7,P=21,R=-1e7,j=1e7,F=!1,Y=1,B=0,U={prefix:"",groupSize:3,secondaryGroupSize:0,groupSeparator:",",decimalSeparator:".",fractionGroupSize:0,fractionGroupSeparator:"\xa0",suffix:""},H="0123456789abcdefghijklmnopqrstuvwxyz";function $(e,t){var n,r,i,a,s,c,f,p,b=this;if(!(b instanceof $))return new $(e,t);if(null==t){if(e&&!0===e._isBigNumber){b.s=e.s,!e.c||e.e>j?b.c=b.e=null:e.e=10;s/=10,a++);a>j?b.c=b.e=null:(b.e=a,b.c=[e]);return}p=String(e)}else{if(!o.test(p=String(e)))return A(b,p,c);b.s=45==p.charCodeAt(0)?(p=p.slice(1),-1):1}(a=p.indexOf("."))>-1&&(p=p.replace(".","")),(s=p.search(/e/i))>0?(a<0&&(a=s),a+=+p.slice(s+1),p=p.substring(0,s)):a<0&&(a=p.length)}else{if(_(t,2,H.length,"Base"),10==t)return b=new $(e),K(b,I+b.e+1,D);if(p=String(e),c="number"==typeof e){if(0*e!=0)return A(b,p,c,t);if(b.s=1/e<0?(p=p.slice(1),-1):1,$.DEBUG&&p.replace(/^0\.0*|\./,"").length>15)throw Error(l+e)}else b.s=45===p.charCodeAt(0)?(p=p.slice(1),-1):1;for(n=H.slice(0,t),a=s=0,f=p.length;sn.indexOf(r=p.charAt(s))){if("."==r){if(s>a){a=f;continue}}else if(!i&&(p==p.toUpperCase()&&(p=p.toLowerCase())||p==p.toLowerCase()&&(p=p.toUpperCase()))){i=!0,s=-1,a=0;continue}return A(b,String(e),c,t)}c=!1,(a=(p=O(p,t,10,b.s)).indexOf("."))>-1?p=p.replace(".",""):a=p.length}for(s=0;48===p.charCodeAt(s);s++);for(f=p.length;48===p.charCodeAt(--f););if(p=p.slice(s,++f)){if(f-=s,c&&$.DEBUG&&f>15&&(e>h||e!==u(e)))throw Error(l+b.s*e);if((a=a-s-1)>j)b.c=b.e=null;else if(a=P)?S(u,o):k(u,o,"0");else if(a=(e=K(new $(e),t,n)).e,s=(u=y(e.c)).length,1==r||2==r&&(t<=a||a<=N)){for(;ss){if(--t>0)for(u+=".";t--;u+="0");}else if((t+=a-s)>0)for(a+1==s&&(u+=".");t--;u+="0");return e.s<0&&i?"-"+u:u}function G(e,t){for(var n,r=1,i=new $(e[0]);r=10;i/=10,r++);return(n=r+n*d-1)>j?e.c=e.e=null:n=10;c/=10,i++);if((a=t-i)<0)a+=d,o=t,b=(l=m[h=0])/g[i-o-1]%10|0;else if((h=s((a+1)/d))>=m.length){if(r){for(;m.length<=h;m.push(0));l=b=0,i=1,a%=d,o=a-d+1}else break out}else{for(i=1,l=c=m[h];c>=10;c/=10,i++);a%=d,b=(o=a-d+i)<0?0:l/g[i-o-1]%10|0}if(r=r||t<0||null!=m[h+1]||(o<0?l:l%g[i-o-1]),r=n<4?(b||r)&&(0==n||n==(e.s<0?3:2)):b>5||5==b&&(4==n||r||6==n&&(a>0?o>0?l/g[i-o]:0:m[h-1])%10&1||n==(e.s<0?8:7)),t<1||!m[0])return m.length=0,r?(t-=e.e+1,m[0]=g[(d-t%d)%d],e.e=-t||0):m[0]=e.e=0,e;if(0==a?(m.length=h,c=1,h--):(m.length=h+1,c=g[d-a],m[h]=o>0?u(l/g[i-o]%g[o])*c:0),r)for(;;){if(0==h){for(a=1,o=m[0];o>=10;o/=10,a++);for(o=m[0]+=c,c=1;o>=10;o/=10,c++);a!=c&&(e.e++,m[0]==f&&(m[0]=1));break}if(m[h]+=c,m[h]!=f)break;m[h--]=0,c=1}for(a=m.length;0===m[--a];m.pop());}e.e>j?e.c=e.e=null:e.e=P?S(t,n):k(t,n,"0"),e.s<0?"-"+t:t)}return $.clone=g,$.ROUND_UP=0,$.ROUND_DOWN=1,$.ROUND_CEIL=2,$.ROUND_FLOOR=3,$.ROUND_HALF_UP=4,$.ROUND_HALF_DOWN=5,$.ROUND_HALF_EVEN=6,$.ROUND_HALF_CEIL=7,$.ROUND_HALF_FLOOR=8,$.EUCLID=9,$.config=$.set=function(e){var t,n;if(null!=e){if("object"==typeof e){if(e.hasOwnProperty(t="DECIMAL_PLACES")&&(_(n=e[t],0,m,t),I=n),e.hasOwnProperty(t="ROUNDING_MODE")&&(_(n=e[t],0,8,t),D=n),e.hasOwnProperty(t="EXPONENTIAL_AT")&&((n=e[t])&&n.pop?(_(n[0],-m,0,t),_(n[1],0,m,t),N=n[0],P=n[1]):(_(n,-m,m,t),N=-(P=n<0?-n:n))),e.hasOwnProperty(t="RANGE")){if((n=e[t])&&n.pop)_(n[0],-m,-1,t),_(n[1],1,m,t),R=n[0],j=n[1];else if(_(n,-m,m,t),n)R=-(j=n<0?-n:n);else throw Error(c+t+" cannot be zero: "+n)}if(e.hasOwnProperty(t="CRYPTO")){if(!!(n=e[t])===n){if(n){if("undefined"!=typeof crypto&&crypto&&(crypto.getRandomValues||crypto.randomBytes))F=n;else throw F=!n,Error(c+"crypto unavailable")}else F=n}else throw Error(c+t+" not true or false: "+n)}if(e.hasOwnProperty(t="MODULO_MODE")&&(_(n=e[t],0,9,t),Y=n),e.hasOwnProperty(t="POW_PRECISION")&&(_(n=e[t],0,m,t),B=n),e.hasOwnProperty(t="FORMAT")){if("object"==typeof(n=e[t]))U=n;else throw Error(c+t+" not an object: "+n)}if(e.hasOwnProperty(t="ALPHABET")){if("string"!=typeof(n=e[t])||/^.?$|[+\-.\s]|(.).*\1/.test(n))throw Error(c+t+" invalid: "+n);H=n}}else throw Error(c+"Object expected: "+e)}return{DECIMAL_PLACES:I,ROUNDING_MODE:D,EXPONENTIAL_AT:[N,P],RANGE:[R,j],CRYPTO:F,MODULO_MODE:Y,POW_PRECISION:B,FORMAT:U,ALPHABET:H}},$.isBigNumber=function(e){if(!e||!0!==e._isBigNumber)return!1;if(!$.DEBUG)return!0;var t,n,r=e.c,i=e.e,a=e.s;out:if("[object Array]"==({}).toString.call(r)){if((1===a||-1===a)&&i>=-m&&i<=m&&i===u(i)){if(0===r[0]){if(0===i&&1===r.length)return!0;break out}if((t=(i+1)%d)<1&&(t+=d),String(r[0]).length==t){for(t=0;t=f||n!==u(n))break out;if(0!==n)return!0}}}else if(null===r&&null===i&&(null===a||1===a||-1===a))return!0;throw Error(c+"Invalid BigNumber: "+e)},$.maximum=$.max=function(){return G(arguments,L.lt)},$.minimum=$.min=function(){return G(arguments,L.gt)},$.random=(n=Math.random()*(t=9007199254740992)&2097151?function(){return u(Math.random()*t)}:function(){return(1073741824*Math.random()|0)*8388608+(8388608*Math.random()|0)},function(e){var t,r,i,a,o,l=0,f=[],h=new $(C);if(null==e?e=I:_(e,0,m),a=s(e/d),F){if(crypto.getRandomValues){for(t=crypto.getRandomValues(new Uint32Array(a*=2));l>>11))>=9e15?(r=crypto.getRandomValues(new Uint32Array(2)),t[l]=r[0],t[l+1]=r[1]):(f.push(o%1e14),l+=2);l=a/2}else if(crypto.randomBytes){for(t=crypto.randomBytes(a*=7);l=9e15?crypto.randomBytes(7).copy(t,l):(f.push(o%1e14),l+=7);l=a/7}else throw F=!1,Error(c+"crypto unavailable")}if(!F)for(;l=10;o/=10,l++);ln-1&&(null==o[i+1]&&(o[i+1]=0),o[i+1]+=o[i]/n|0,o[i]%=n)}return o.reverse()}return function(n,r,i,a,o){var s,u,c,l,f,d,h,p,b=n.indexOf("."),m=I,g=D;for(b>=0&&(l=B,B=0,n=n.replace(".",""),d=(p=new $(r)).pow(n.length-b),B=l,p.c=t(k(y(d.c),d.e,"0"),10,i,e),p.e=p.c.length),c=l=(h=t(n,r,i,o?(s=H,e):(s=e,H))).length;0==h[--l];h.pop());if(!h[0])return s.charAt(0);if(b<0?--c:(d.c=h,d.e=c,d.s=a,h=(d=M(d,p,m,g,i)).c,f=d.r,c=d.e),b=h[u=c+m+1],l=i/2,f=f||u<0||null!=h[u+1],f=g<4?(null!=b||f)&&(0==g||g==(d.s<0?3:2)):b>l||b==l&&(4==g||f||6==g&&1&h[u-1]||g==(d.s<0?8:7)),u<1||!h[0])n=f?k(s.charAt(1),-m,s.charAt(0)):s.charAt(0);else{if(h.length=u,f)for(--i;++h[--u]>i;)h[u]=0,u||(++c,h=[1].concat(h));for(l=h.length;!h[--l];);for(b=0,n="";b<=l;n+=s.charAt(h[b++]));n=k(n,c,s.charAt(0))}return n}}(),M=function(){function e(e,t,n){var r,i,a,o,s=0,u=e.length,c=t%b,l=t/b|0;for(e=e.slice();u--;)r=l*(a=e[u]%b)+(o=e[u]/b|0)*c,s=((i=c*a+r%b*b+s)/n|0)+(r/b|0)+l*o,e[u]=i%n;return s&&(e=[s].concat(e)),e}function t(e,t,n,r){var i,a;if(n!=r)a=n>r?1:-1;else for(i=a=0;it[i]?1:-1;break}return a}function n(e,t,n,r){for(var i=0;n--;)e[n]-=i,i=e[n]1;e.splice(0,1));}return function(r,i,a,o,s){var c,l,h,p,b,m,g,y,w,_,E,S,k,x,T,M,O,A=r.s==i.s?1:-1,L=r.c,C=i.c;if(!L||!L[0]||!C||!C[0])return new $(r.s&&i.s&&(L?!C||L[0]!=C[0]:C)?L&&0==L[0]||!C?0*A:A/0:NaN);for(w=(y=new $(A)).c=[],A=a+(l=r.e-i.e)+1,s||(s=f,l=v(r.e/d)-v(i.e/d),A=A/d|0),h=0;C[h]==(L[h]||0);h++);if(C[h]>(L[h]||0)&&l--,A<0)w.push(1),p=!0;else{for(x=L.length,M=C.length,h=0,A+=2,(b=u(s/(C[0]+1)))>1&&(C=e(C,b,s),L=e(L,b,s),M=C.length,x=L.length),k=M,E=(_=L.slice(0,M)).length;E=s/2&&T++;do{if(b=0,(c=t(C,_,M,E))<0){if(S=_[0],M!=E&&(S=S*s+(_[1]||0)),(b=u(S/T))>1)for(b>=s&&(b=s-1),g=(m=e(C,b,s)).length,E=_.length;1==t(m,_,g,E);)b--,n(m,Mt(C,_,M,E);)b++,n(_,M=10;A/=10,h++);K(y,a+(y.e=h+l*d-1)+1,o,p)}else y.e=l,y.r=+p;return y}}(),A=(r=/^(-?)0([xbo])(?=\w[\w.]*$)/i,i=/^([^.]+)\.$/,a=/^\.([^.]+)$/,x=/^-?(Infinity|NaN)$/,T=/^\s*\+(?=[\w.])|^\s+|\s+$/g,function(e,t,n,o){var s,u=n?t:t.replace(T,"");if(x.test(u))e.s=isNaN(u)?null:u<0?-1:1;else{if(!n&&(u=u.replace(r,function(e,t,n){return s="x"==(n=n.toLowerCase())?16:"b"==n?2:8,o&&o!=s?e:t}),o&&(s=o,u=u.replace(i,"$1").replace(a,"0.$1")),t!=u))return new $(u,s);if($.DEBUG)throw Error(c+"Not a"+(o?" base "+o:"")+" number: "+t);e.s=null}e.c=e.e=null}),L.absoluteValue=L.abs=function(){var e=new $(this);return e.s<0&&(e.s=1),e},L.comparedTo=function(e,t){return w(this,new $(e,t))},L.decimalPlaces=L.dp=function(e,t){var n,r,i,a=this;if(null!=e)return _(e,0,m),null==t?t=D:_(t,0,8),K(new $(a),e+a.e+1,t);if(!(n=a.c))return null;if(r=((i=n.length-1)-v(this.e/d))*d,i=n[i])for(;i%10==0;i/=10,r--);return r<0&&(r=0),r},L.dividedBy=L.div=function(e,t){return M(this,new $(e,t),I,D)},L.dividedToIntegerBy=L.idiv=function(e,t){return M(this,new $(e,t),0,1)},L.exponentiatedBy=L.pow=function(e,t){var n,r,i,a,o,l,f,h,p,b=this;if((e=new $(e)).c&&!e.isInteger())throw Error(c+"Exponent not an integer: "+V(e));if(null!=t&&(t=new $(t)),l=e.e>14,!b.c||!b.c[0]||1==b.c[0]&&!b.e&&1==b.c.length||!e.c||!e.c[0])return p=new $(Math.pow(+V(b),l?2-E(e):+V(e))),t?p.mod(t):p;if(f=e.s<0,t){if(t.c?!t.c[0]:!t.s)return new $(NaN);(r=!f&&b.isInteger()&&t.isInteger())&&(b=b.mod(t))}else{if(e.e>9&&(b.e>0||b.e<-1||(0==b.e?b.c[0]>1||l&&b.c[1]>=24e7:b.c[0]<8e13||l&&b.c[0]<=9999975e7)))return a=(b.s<0&&E(e),-0),b.e>-1&&(a=1/a),new $(f?1/a:a);B&&(a=s(B/d+2))}for(l?(n=new $(.5),f&&(e.s=1),h=E(e)):h=(i=Math.abs(+V(e)))%2,p=new $(C);;){if(h){if(!(p=p.times(b)).c)break;a?p.c.length>a&&(p.c.length=a):r&&(p=p.mod(t))}if(i){if(0===(i=u(i/2)))break;h=i%2}else if(K(e=e.times(n),e.e+1,1),e.e>14)h=E(e);else{if(0==(i=+V(e)))break;h=i%2}b=b.times(b),a?b.c&&b.c.length>a&&(b.c.length=a):r&&(b=b.mod(t))}return r?p:(f&&(p=C.div(p)),t?p.mod(t):a?K(p,B,D,o):p)},L.integerValue=function(e){var t=new $(this);return null==e?e=D:_(e,0,8),K(t,t.e+1,e)},L.isEqualTo=L.eq=function(e,t){return 0===w(this,new $(e,t))},L.isFinite=function(){return!!this.c},L.isGreaterThan=L.gt=function(e,t){return w(this,new $(e,t))>0},L.isGreaterThanOrEqualTo=L.gte=function(e,t){return 1===(t=w(this,new $(e,t)))||0===t},L.isInteger=function(){return!!this.c&&v(this.e/d)>this.c.length-2},L.isLessThan=L.lt=function(e,t){return 0>w(this,new $(e,t))},L.isLessThanOrEqualTo=L.lte=function(e,t){return -1===(t=w(this,new $(e,t)))||0===t},L.isNaN=function(){return!this.s},L.isNegative=function(){return this.s<0},L.isPositive=function(){return this.s>0},L.isZero=function(){return!!this.c&&0==this.c[0]},L.minus=function(e,t){var n,r,i,a,o=this,s=o.s;if(t=(e=new $(e,t)).s,!s||!t)return new $(NaN);if(s!=t)return e.s=-t,o.plus(e);var u=o.e/d,c=e.e/d,l=o.c,h=e.c;if(!u||!c){if(!l||!h)return l?(e.s=-t,e):new $(h?o:NaN);if(!l[0]||!h[0])return h[0]?(e.s=-t,e):new $(l[0]?o:-0)}if(u=v(u),c=v(c),l=l.slice(),s=u-c){for((a=s<0)?(s=-s,i=l):(c=u,i=h),i.reverse(),t=s;t--;i.push(0));i.reverse()}else for(r=(a=(s=l.length)<(t=h.length))?s:t,s=t=0;t0)for(;t--;l[n++]=0);for(t=f-1;r>s;){if(l[--r]=0;){for(n=0,p=S[i]%w,m=S[i]/w|0,a=i+(o=u);a>i;)s=m*(c=E[--o]%w)+(l=E[o]/w|0)*p,n=((c=p*c+s%w*w+g[a]+n)/y|0)+(s/w|0)+m*l,g[a--]=c%y;g[a]=n}return n?++r:g.splice(0,1),W(e,g,r)},L.negated=function(){var e=new $(this);return e.s=-e.s||null,e},L.plus=function(e,t){var n,r=this,i=r.s;if(t=(e=new $(e,t)).s,!i||!t)return new $(NaN);if(i!=t)return e.s=-t,r.minus(e);var a=r.e/d,o=e.e/d,s=r.c,u=e.c;if(!a||!o){if(!s||!u)return new $(i/0);if(!s[0]||!u[0])return u[0]?e:new $(s[0]?r:0*i)}if(a=v(a),o=v(o),s=s.slice(),i=a-o){for(i>0?(o=a,n=u):(i=-i,n=s),n.reverse();i--;n.push(0));n.reverse()}for((i=s.length)-(t=u.length)<0&&(n=u,u=s,s=n,t=i),i=0;t;)i=(s[--t]=s[t]+u[t]+i)/f|0,s[t]=f===s[t]?0:s[t]%f;return i&&(s=[i].concat(s),++o),W(e,s,o)},L.precision=L.sd=function(e,t){var n,r,i,a=this;if(null!=e&&!!e!==e)return _(e,1,m),null==t?t=D:_(t,0,8),K(new $(a),e,t);if(!(n=a.c))return null;if(r=(i=n.length-1)*d+1,i=n[i]){for(;i%10==0;i/=10,r--);for(i=n[0];i>=10;i/=10,r++);}return e&&a.e+1>r&&(r=a.e+1),r},L.shiftedBy=function(e){return _(e,-h,h),this.times("1e"+e)},L.squareRoot=L.sqrt=function(){var e,t,n,r,i,a=this,o=a.c,s=a.s,u=a.e,c=I+4,l=new $("0.5");if(1!==s||!o||!o[0])return new $(!s||s<0&&(!o||o[0])?NaN:o?a:1/0);if(0==(s=Math.sqrt(+V(a)))||s==1/0?(((t=y(o)).length+u)%2==0&&(t+="0"),s=Math.sqrt(+t),u=v((u+1)/2)-(u<0||u%2),t=s==1/0?"5e"+u:(t=s.toExponential()).slice(0,t.indexOf("e")+1)+u,n=new $(t)):n=new $(s+""),n.c[0]){for((s=(u=n.e)+c)<3&&(s=0);;)if(i=n,n=l.times(i.plus(M(a,i,c,1))),y(i.c).slice(0,s)===(t=y(n.c)).slice(0,s)){if(n.e0&&b>0){for(a=b%s||s,f=p.substr(0,a);a0&&(f+=l+p.slice(a)),h&&(f="-"+f)}r=d?f+(n.decimalSeparator||"")+((u=+n.fractionGroupSize)?d.replace(RegExp("\\d{"+u+"}\\B","g"),"$&"+(n.fractionGroupSeparator||"")):d):f}return(n.prefix||"")+r+(n.suffix||"")},L.toFraction=function(e){var t,n,r,i,a,o,s,u,l,f,h,b,m=this,g=m.c;if(null!=e&&(!(s=new $(e)).isInteger()&&(s.c||1!==s.s)||s.lt(C)))throw Error(c+"Argument "+(s.isInteger()?"out of range: ":"not an integer: ")+V(s));if(!g)return new $(m);for(t=new $(C),l=n=new $(C),r=u=new $(C),b=y(g),a=t.e=b.length-m.e-1,t.c[0]=p[(o=a%d)<0?d+o:o],e=!e||s.comparedTo(t)>0?a>0?t:l:s,o=j,j=1/0,s=new $(b),u.c[0]=0;f=M(s,t,0,1),1!=(i=n.plus(f.times(r))).comparedTo(e);)n=r,r=i,l=u.plus(f.times(i=l)),u=i,t=s.minus(f.times(i=t)),s=i;return i=M(e.minus(n),r,0,1),u=u.plus(i.times(l)),n=n.plus(i.times(r)),u.s=l.s=m.s,a*=2,h=1>M(l,r,a,D).minus(m).abs().comparedTo(M(u,n,a,D).minus(m).abs())?[l,r]:[u,n],j=o,h},L.toNumber=function(){return+V(this)},L.toPrecision=function(e,t){return null!=e&&_(e,1,m),z(this,e,t,2)},L.toString=function(e){var t,n=this,r=n.s,i=n.e;return null===i?r?(t="Infinity",r<0&&(t="-"+t)):t="NaN":(null==e?t=i<=N||i>=P?S(y(n.c),i):k(y(n.c),i,"0"):10===e?(n=K(new $(n),I+i+1,D),t=k(y(n.c),n.e,"0")):(_(e,2,H.length,"Base"),t=O(k(y(n.c),i,"0"),10,e,r,!0)),r<0&&n.c[0]&&(t="-"+t)),t},L.valueOf=L.toJSON=function(){return V(this)},L._isBigNumber=!0,null!=e&&$.set(e),$}function v(e){var t=0|e;return e>0||e===t?t:t-1}function y(e){for(var t,n,r=1,i=e.length,a=e[0]+"";rc^n?1:-1;for(o=0,s=(u=i.length)<(c=a.length)?u:c;oa[o]^n?1:-1;return u==c?0:u>c^n?1:-1}function _(e,t,n,r){if(en||e!==u(e))throw Error(c+(r||"Argument")+("number"==typeof e?en?" out of range: ":" not an integer: ":" not a primitive number: ")+String(e))}function E(e){var t=e.c.length-1;return v(e.e/d)==t&&e.c[t]%2!=0}function S(e,t){return(e.length>1?e.charAt(0)+"."+e.slice(1):e)+(t<0?"e":"e+")+t}function k(e,t,n){var r,i;if(t<0){for(i=n+".";++t;i+=n);e=i+e}else if(r=e.length,++t>r){for(i=n,t-=r;--t;i+=n);e+=i}else ti});let i=r},48764(e,t,n){"use strict";/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ var r=n(79742),i=n(80645),a="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;t.Buffer=c,t.SlowBuffer=w,t.INSPECT_MAX_BYTES=50;var o=2147483647;function s(){try{var e=new Uint8Array(1),t={foo:function(){return 42}};return Object.setPrototypeOf(t,Uint8Array.prototype),Object.setPrototypeOf(e,t),42===e.foo()}catch(n){return!1}}function u(e){if(e>o)throw RangeError('The value "'+e+'" is invalid for option "size"');var t=new Uint8Array(e);return Object.setPrototypeOf(t,c.prototype),t}function c(e,t,n){if("number"==typeof e){if("string"==typeof t)throw TypeError('The "string" argument must be of type string. Received type number');return h(e)}return l(e,t,n)}function l(e,t,n){if("string"==typeof e)return p(e,t);if(ArrayBuffer.isView(e))return m(e);if(null==e)throw TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(X(e,ArrayBuffer)||e&&X(e.buffer,ArrayBuffer)||"undefined"!=typeof SharedArrayBuffer&&(X(e,SharedArrayBuffer)||e&&X(e.buffer,SharedArrayBuffer)))return g(e,t,n);if("number"==typeof e)throw TypeError('The "value" argument must not be of type number. Received type number');var r=e.valueOf&&e.valueOf();if(null!=r&&r!==e)return c.from(r,t,n);var i=v(e);if(i)return i;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof e[Symbol.toPrimitive])return c.from(e[Symbol.toPrimitive]("string"),t,n);throw TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e)}function f(e){if("number"!=typeof e)throw TypeError('"size" argument must be of type number');if(e<0)throw RangeError('The value "'+e+'" is invalid for option "size"')}function d(e,t,n){return(f(e),e<=0)?u(e):void 0!==t?"string"==typeof n?u(e).fill(t,n):u(e).fill(t):u(e)}function h(e){return f(e),u(e<0?0:0|y(e))}function p(e,t){if(("string"!=typeof t||""===t)&&(t="utf8"),!c.isEncoding(t))throw TypeError("Unknown encoding: "+t);var n=0|_(e,t),r=u(n),i=r.write(e,t);return i!==n&&(r=r.slice(0,i)),r}function b(e){for(var t=e.length<0?0:0|y(e.length),n=u(t),r=0;r=o)throw RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+o.toString(16)+" bytes");return 0|e}function w(e){return+e!=e&&(e=0),c.alloc(+e)}function _(e,t){if(c.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||X(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);var n=e.length,r=arguments.length>2&&!0===arguments[2];if(!r&&0===n)return 0;for(var i=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":return W(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return q(e).length;default:if(i)return r?-1:W(e).length;t=(""+t).toLowerCase(),i=!0}}function E(e,t,n){var r=!1;if((void 0===t||t<0)&&(t=0),t>this.length||((void 0===n||n>this.length)&&(n=this.length),n<=0||(n>>>=0)<=(t>>>=0)))return"";for(e||(e="utf8");;)switch(e){case"hex":return j(this,t,n);case"utf8":case"utf-8":return I(this,t,n);case"ascii":return P(this,t,n);case"latin1":case"binary":return R(this,t,n);case"base64":return C(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return F(this,t,n);default:if(r)throw TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function S(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function k(e,t,n,r,i){if(0===e.length)return -1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),J(n=+n)&&(n=i?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(i)return -1;n=e.length-1}else if(n<0){if(!i)return -1;n=0}if("string"==typeof t&&(t=c.from(t,r)),c.isBuffer(t))return 0===t.length?-1:x(e,t,n,r,i);if("number"==typeof t)return(t&=255,"function"==typeof Uint8Array.prototype.indexOf)?i?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):x(e,[t],n,r,i);throw TypeError("val must be string, number or Buffer")}function x(e,t,n,r,i){var a,o=1,s=e.length,u=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return -1;o=2,s/=2,u/=2,n/=2}function c(e,t){return 1===o?e[t]:e.readUInt16BE(t*o)}if(i){var l=-1;for(a=n;as&&(n=s-u),a=n;a>=0;a--){for(var f=!0,d=0;di&&(r=i):r=i;var a=t.length;r>a/2&&(r=a/2);for(var o=0;o239?4:c>223?3:c>191?2:1;if(i+f<=n)switch(f){case 1:c<128&&(l=c);break;case 2:(192&(a=e[i+1]))==128&&(u=(31&c)<<6|63&a)>127&&(l=u);break;case 3:a=e[i+1],o=e[i+2],(192&a)==128&&(192&o)==128&&(u=(15&c)<<12|(63&a)<<6|63&o)>2047&&(u<55296||u>57343)&&(l=u);break;case 4:a=e[i+1],o=e[i+2],s=e[i+3],(192&a)==128&&(192&o)==128&&(192&s)==128&&(u=(15&c)<<18|(63&a)<<12|(63&o)<<6|63&s)>65535&&u<1114112&&(l=u)}null===l?(l=65533,f=1):l>65535&&(l-=65536,r.push(l>>>10&1023|55296),l=56320|1023&l),r.push(l),i+=f}return N(r)}t.kMaxLength=o,c.TYPED_ARRAY_SUPPORT=s(),c.TYPED_ARRAY_SUPPORT||"undefined"==typeof console||"function"!=typeof console.error||console.error("This browser lacks typed array (Uint8Array) support which is required by `buffer` v5.x. Use `buffer` v4.x if you require old browser support."),Object.defineProperty(c.prototype,"parent",{enumerable:!0,get:function(){if(c.isBuffer(this))return this.buffer}}),Object.defineProperty(c.prototype,"offset",{enumerable:!0,get:function(){if(c.isBuffer(this))return this.byteOffset}}),c.poolSize=8192,c.from=function(e,t,n){return l(e,t,n)},Object.setPrototypeOf(c.prototype,Uint8Array.prototype),Object.setPrototypeOf(c,Uint8Array),c.alloc=function(e,t,n){return d(e,t,n)},c.allocUnsafe=function(e){return h(e)},c.allocUnsafeSlow=function(e){return h(e)},c.isBuffer=function(e){return null!=e&&!0===e._isBuffer&&e!==c.prototype},c.compare=function(e,t){if(X(e,Uint8Array)&&(e=c.from(e,e.offset,e.byteLength)),X(t,Uint8Array)&&(t=c.from(t,t.offset,t.byteLength)),!c.isBuffer(e)||!c.isBuffer(t))throw TypeError('The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array');if(e===t)return 0;for(var n=e.length,r=t.length,i=0,a=Math.min(n,r);ir.length?c.from(a).copy(r,i):Uint8Array.prototype.set.call(r,a,i);else if(c.isBuffer(a))a.copy(r,i);else throw TypeError('"list" argument must be an Array of Buffers');i+=a.length}return r},c.byteLength=_,c.prototype._isBuffer=!0,c.prototype.swap16=function(){var e=this.length;if(e%2!=0)throw RangeError("Buffer size must be a multiple of 16-bits");for(var t=0;tn&&(e+=" ... "),""},a&&(c.prototype[a]=c.prototype.inspect),c.prototype.compare=function(e,t,n,r,i){if(X(e,Uint8Array)&&(e=c.from(e,e.offset,e.byteLength)),!c.isBuffer(e))throw TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===i&&(i=this.length),t<0||n>e.length||r<0||i>this.length)throw RangeError("out of range index");if(r>=i&&t>=n)return 0;if(r>=i)return -1;if(t>=n)return 1;if(t>>>=0,n>>>=0,r>>>=0,i>>>=0,this===e)return 0;for(var a=i-r,o=n-t,s=Math.min(a,o),u=this.slice(r,i),l=e.slice(t,n),f=0;f>>=0,isFinite(n)?(n>>>=0,void 0===r&&(r="utf8")):(r=n,n=void 0);else throw Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");var i=this.length-t;if((void 0===n||n>i)&&(n=i),e.length>0&&(n<0||t<0)||t>this.length)throw RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var a=!1;;)switch(r){case"hex":return T(this,e,t,n);case"utf8":case"utf-8":return M(this,e,t,n);case"ascii":case"latin1":case"binary":return O(this,e,t,n);case"base64":return A(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return L(this,e,t,n);default:if(a)throw TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),a=!0}},c.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var D=4096;function N(e){var t=e.length;if(t<=D)return String.fromCharCode.apply(String,e);for(var n="",r=0;rr)&&(n=r);for(var i="",a=t;an)throw RangeError("Trying to access beyond buffer length")}function B(e,t,n,r,i,a){if(!c.isBuffer(e))throw TypeError('"buffer" argument must be a Buffer instance');if(t>i||te.length)throw RangeError("Index out of range")}function U(e,t,n,r,i,a){if(n+r>e.length||n<0)throw RangeError("Index out of range")}function H(e,t,n,r,a){return t=+t,n>>>=0,a||U(e,t,n,4,34028234663852886e22,-34028234663852886e22),i.write(e,t,n,r,23,4),n+4}function $(e,t,n,r,a){return t=+t,n>>>=0,a||U(e,t,n,8,17976931348623157e292,-17976931348623157e292),i.write(e,t,n,r,52,8),n+8}c.prototype.slice=function(e,t){var n=this.length;e=~~e,t=void 0===t?n:~~t,e<0?(e+=n)<0&&(e=0):e>n&&(e=n),t<0?(t+=n)<0&&(t=0):t>n&&(t=n),t>>=0,t>>>=0,n||Y(e,t,this.length);for(var r=this[e],i=1,a=0;++a>>=0,t>>>=0,n||Y(e,t,this.length);for(var r=this[e+--t],i=1;t>0&&(i*=256);)r+=this[e+--t]*i;return r},c.prototype.readUint8=c.prototype.readUInt8=function(e,t){return e>>>=0,t||Y(e,1,this.length),this[e]},c.prototype.readUint16LE=c.prototype.readUInt16LE=function(e,t){return e>>>=0,t||Y(e,2,this.length),this[e]|this[e+1]<<8},c.prototype.readUint16BE=c.prototype.readUInt16BE=function(e,t){return e>>>=0,t||Y(e,2,this.length),this[e]<<8|this[e+1]},c.prototype.readUint32LE=c.prototype.readUInt32LE=function(e,t){return e>>>=0,t||Y(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},c.prototype.readUint32BE=c.prototype.readUInt32BE=function(e,t){return e>>>=0,t||Y(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},c.prototype.readIntLE=function(e,t,n){e>>>=0,t>>>=0,n||Y(e,t,this.length);for(var r=this[e],i=1,a=0;++a=(i*=128)&&(r-=Math.pow(2,8*t)),r},c.prototype.readIntBE=function(e,t,n){e>>>=0,t>>>=0,n||Y(e,t,this.length);for(var r=t,i=1,a=this[e+--r];r>0&&(i*=256);)a+=this[e+--r]*i;return a>=(i*=128)&&(a-=Math.pow(2,8*t)),a},c.prototype.readInt8=function(e,t){return(e>>>=0,t||Y(e,1,this.length),128&this[e])?-((255-this[e]+1)*1):this[e]},c.prototype.readInt16LE=function(e,t){e>>>=0,t||Y(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt16BE=function(e,t){e>>>=0,t||Y(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt32LE=function(e,t){return e>>>=0,t||Y(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},c.prototype.readInt32BE=function(e,t){return e>>>=0,t||Y(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},c.prototype.readFloatLE=function(e,t){return e>>>=0,t||Y(e,4,this.length),i.read(this,e,!0,23,4)},c.prototype.readFloatBE=function(e,t){return e>>>=0,t||Y(e,4,this.length),i.read(this,e,!1,23,4)},c.prototype.readDoubleLE=function(e,t){return e>>>=0,t||Y(e,8,this.length),i.read(this,e,!0,52,8)},c.prototype.readDoubleBE=function(e,t){return e>>>=0,t||Y(e,8,this.length),i.read(this,e,!1,52,8)},c.prototype.writeUintLE=c.prototype.writeUIntLE=function(e,t,n,r){if(e=+e,t>>>=0,n>>>=0,!r){var i=Math.pow(2,8*n)-1;B(this,e,t,n,i,0)}var a=1,o=0;for(this[t]=255&e;++o>>=0,n>>>=0,!r){var i=Math.pow(2,8*n)-1;B(this,e,t,n,i,0)}var a=n-1,o=1;for(this[t+a]=255&e;--a>=0&&(o*=256);)this[t+a]=e/o&255;return t+n},c.prototype.writeUint8=c.prototype.writeUInt8=function(e,t,n){return e=+e,t>>>=0,n||B(this,e,t,1,255,0),this[t]=255&e,t+1},c.prototype.writeUint16LE=c.prototype.writeUInt16LE=function(e,t,n){return e=+e,t>>>=0,n||B(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},c.prototype.writeUint16BE=c.prototype.writeUInt16BE=function(e,t,n){return e=+e,t>>>=0,n||B(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},c.prototype.writeUint32LE=c.prototype.writeUInt32LE=function(e,t,n){return e=+e,t>>>=0,n||B(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},c.prototype.writeUint32BE=c.prototype.writeUInt32BE=function(e,t,n){return e=+e,t>>>=0,n||B(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},c.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t>>>=0,!r){var i=Math.pow(2,8*n-1);B(this,e,t,n,i-1,-i)}var a=0,o=1,s=0;for(this[t]=255&e;++a>0)-s&255;return t+n},c.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t>>>=0,!r){var i=Math.pow(2,8*n-1);B(this,e,t,n,i-1,-i)}var a=n-1,o=1,s=0;for(this[t+a]=255&e;--a>=0&&(o*=256);)e<0&&0===s&&0!==this[t+a+1]&&(s=1),this[t+a]=(e/o>>0)-s&255;return t+n},c.prototype.writeInt8=function(e,t,n){return e=+e,t>>>=0,n||B(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},c.prototype.writeInt16LE=function(e,t,n){return e=+e,t>>>=0,n||B(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},c.prototype.writeInt16BE=function(e,t,n){return e=+e,t>>>=0,n||B(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},c.prototype.writeInt32LE=function(e,t,n){return e=+e,t>>>=0,n||B(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},c.prototype.writeInt32BE=function(e,t,n){return e=+e,t>>>=0,n||B(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},c.prototype.writeFloatLE=function(e,t,n){return H(this,e,t,!0,n)},c.prototype.writeFloatBE=function(e,t,n){return H(this,e,t,!1,n)},c.prototype.writeDoubleLE=function(e,t,n){return $(this,e,t,!0,n)},c.prototype.writeDoubleBE=function(e,t,n){return $(this,e,t,!1,n)},c.prototype.copy=function(e,t,n,r){if(!c.isBuffer(e))throw TypeError("argument should be a Buffer");if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw RangeError("Index out of range");if(r<0)throw RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(i=t;i55295&&n<57344){if(!i){if(n>56319||o+1===r){(t-=3)>-1&&a.push(239,191,189);continue}i=n;continue}if(n<56320){(t-=3)>-1&&a.push(239,191,189),i=n;continue}n=(i-55296<<10|n-56320)+65536}else i&&(t-=3)>-1&&a.push(239,191,189);if(i=null,n<128){if((t-=1)<0)break;a.push(n)}else if(n<2048){if((t-=2)<0)break;a.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;a.push(n>>12|224,n>>6&63|128,63&n|128)}else if(n<1114112){if((t-=4)<0)break;a.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}else throw Error("Invalid code point")}return a}function K(e){for(var t=[],n=0;n>8,a.push(i=n%256),a.push(r);return a}function q(e){return r.toByteArray(G(e))}function Z(e,t,n,r){for(var i=0;i=t.length)&&!(i>=e.length);++i)t[i+n]=e[i];return i}function X(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function J(e){return e!=e}var Q=function(){for(var e="0123456789abcdef",t=Array(256),n=0;n<16;++n)for(var r=16*n,i=0;i<16;++i)t[r+i]=e[n]+e[i];return t}()},94184(e,t){var n,r; /*! + Copyright (c) 2018 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames +*/ !function(){"use strict";var i={}.hasOwnProperty;function a(){for(var e=[],t=0;t>8&255]},F=function(e){return[255&e,e>>8&255,e>>16&255,e>>24&255]},Y=function(e){return e[3]<<24|e[2]<<16|e[1]<<8|e[0]},B=function(e){return N(e,23,4)},U=function(e){return N(e,52,8)},H=function(e,t){g(e[x],t,{get:function(){return _(this)[t]}})},$=function(e,t,n,r){var i=d(n),a=_(e);if(i+t>a.byteLength)throw D(M);var o=_(a.buffer).bytes,s=i+a.byteOffset,u=o.slice(s,s+t);return r?u:u.reverse()},z=function(e,t,n,r,i,a){var o=d(n),s=_(e);if(o+t>s.byteLength)throw D(M);for(var u=_(s.buffer).bytes,c=o+s.byteOffset,l=r(+i),f=0;fV;)(G=K[V++])in A||o(A,G,O[G]);W.constructor=A}b&&p(C)!==I&&b(C,I);var q=new L(new A(2)),Z=C.setInt8;q.setInt8(0,2147483648),q.setInt8(1,2147483649),(q.getInt8(0)||!q.getInt8(1))&&s(C,{setInt8:function(e,t){Z.call(this,e,t<<24>>24)},setUint8:function(e,t){Z.call(this,e,t<<24>>24)}},{unsafe:!0})}else A=function(e){c(this,A,S);var t=d(e);E(this,{bytes:v.call(Array(t),0),byteLength:t}),i||(this.byteLength=t)},L=function(e,t,n){c(this,L,k),c(e,A,k);var r=_(e).byteLength,a=l(t);if(a<0||a>r)throw D("Wrong offset");if(n=void 0===n?r-a:f(n),a+n>r)throw D(T);E(this,{buffer:e,byteLength:n,byteOffset:a}),i||(this.buffer=e,this.byteLength=n,this.byteOffset=a)},i&&(H(A,"byteLength"),H(L,"buffer"),H(L,"byteLength"),H(L,"byteOffset")),s(L[x],{getInt8:function(e){return $(this,1,e)[0]<<24>>24},getUint8:function(e){return $(this,1,e)[0]},getInt16:function(e){var t=$(this,2,e,arguments.length>1?arguments[1]:void 0);return(t[1]<<8|t[0])<<16>>16},getUint16:function(e){var t=$(this,2,e,arguments.length>1?arguments[1]:void 0);return t[1]<<8|t[0]},getInt32:function(e){return Y($(this,4,e,arguments.length>1?arguments[1]:void 0))},getUint32:function(e){return Y($(this,4,e,arguments.length>1?arguments[1]:void 0))>>>0},getFloat32:function(e){return P($(this,4,e,arguments.length>1?arguments[1]:void 0),23)},getFloat64:function(e){return P($(this,8,e,arguments.length>1?arguments[1]:void 0),52)},setInt8:function(e,t){z(this,1,e,R,t)},setUint8:function(e,t){z(this,1,e,R,t)},setInt16:function(e,t){z(this,2,e,j,t,arguments.length>2?arguments[2]:void 0)},setUint16:function(e,t){z(this,2,e,j,t,arguments.length>2?arguments[2]:void 0)},setInt32:function(e,t){z(this,4,e,F,t,arguments.length>2?arguments[2]:void 0)},setUint32:function(e,t){z(this,4,e,F,t,arguments.length>2?arguments[2]:void 0)},setFloat32:function(e,t){z(this,4,e,B,t,arguments.length>2?arguments[2]:void 0)},setFloat64:function(e,t){z(this,8,e,U,t,arguments.length>2?arguments[2]:void 0)}});y(A,S),y(L,k),e.exports={ArrayBuffer:A,DataView:L}},1048(e,t,n){"use strict";var r=n(47908),i=n(51400),a=n(17466),o=Math.min;e.exports=[].copyWithin||function(e,t){var n=r(this),s=a(n.length),u=i(e,s),c=i(t,s),l=arguments.length>2?arguments[2]:void 0,f=o((void 0===l?s:i(l,s))-c,s-u),d=1;for(c0;)c in n?n[u]=n[c]:delete n[u],u+=d,c+=d;return n}},21285(e,t,n){"use strict";var r=n(47908),i=n(51400),a=n(17466);e.exports=function(e){for(var t=r(this),n=a(t.length),o=arguments.length,s=i(o>1?arguments[1]:void 0,n),u=o>2?arguments[2]:void 0,c=void 0===u?n:i(u,n);c>s;)t[s++]=e;return t}},18533(e,t,n){"use strict";var r=n(42092).forEach,i=n(9341)("forEach");e.exports=i?[].forEach:function(e){return r(this,e,arguments.length>1?arguments[1]:void 0)}},97745(e){e.exports=function(e,t){for(var n=0,r=t.length,i=new e(r);r>n;)i[n]=t[n++];return i}},48457(e,t,n){"use strict";var r=n(49974),i=n(47908),a=n(53411),o=n(97659),s=n(17466),u=n(86135),c=n(18554),l=n(71246);e.exports=function(e){var t,n,f,d,h,p,b=i(e),m="function"==typeof this?this:Array,g=arguments.length,v=g>1?arguments[1]:void 0,y=void 0!==v,w=l(b),_=0;if(y&&(v=r(v,g>2?arguments[2]:void 0,2)),void 0==w||m==Array&&o(w))for(t=s(b.length),n=new m(t);t>_;_++)p=y?v(b[_],_):b[_],u(n,_,p);else for(h=(d=c(b,w)).next,n=new m;!(f=h.call(d)).done;_++)p=y?a(d,v,[f.value,_],!0):f.value,u(n,_,p);return n.length=_,n}},61386(e,t,n){var r=n(49974),i=n(68361),a=n(47908),o=n(17466),s=n(34948),u=n(70030),c=n(97745),l=[].push;e.exports=function(e,t,n,f){for(var d,h,p,b=a(e),m=i(b),g=r(t,n,3),v=u(null),y=o(m.length),w=0;y>w;w++)(h=s(g(p=m[w],w,b)))in v?l.call(v[h],p):v[h]=[p];if(f&&(d=f(b))!==Array)for(h in v)v[h]=c(d,v[h]);return v}},41318(e,t,n){var r=n(45656),i=n(17466),a=n(51400),o=function(e){return function(t,n,o){var s,u=r(t),c=i(u.length),l=a(o,c);if(e&&n!=n){for(;c>l;)if((s=u[l++])!=s)return!0}else for(;c>l;l++)if((e||l in u)&&u[l]===n)return e||l||0;return!e&&-1}};e.exports={includes:o(!0),indexOf:o(!1)}},9671(e,t,n){var r=n(49974),i=n(68361),a=n(47908),o=n(17466),s=function(e){var t=1==e;return function(n,s,u){for(var c,l,f=a(n),d=i(f),h=r(s,u,3),p=o(d.length);p-- >0;)if(l=h(c=d[p],p,f))switch(e){case 0:return c;case 1:return p}return t?-1:void 0}};e.exports={findLast:s(0),findLastIndex:s(1)}},42092(e,t,n){var r=n(49974),i=n(68361),a=n(47908),o=n(17466),s=n(65417),u=[].push,c=function(e){var t=1==e,n=2==e,c=3==e,l=4==e,f=6==e,d=7==e,h=5==e||f;return function(p,b,m,g){for(var v,y,w=a(p),_=i(w),E=r(b,m,3),S=o(_.length),k=0,x=g||s,T=t?x(p,S):n||d?x(p,0):void 0;S>k;k++)if((h||k in _)&&(y=E(v=_[k],k,w),e)){if(t)T[k]=y;else if(y)switch(e){case 3:return!0;case 5:return v;case 6:return k;case 2:u.call(T,v)}else switch(e){case 4:return!1;case 7:u.call(T,v)}}return f?-1:c||l?l:T}};e.exports={forEach:c(0),map:c(1),filter:c(2),some:c(3),every:c(4),find:c(5),findIndex:c(6),filterReject:c(7)}},86583(e,t,n){"use strict";var r=n(45656),i=n(99958),a=n(17466),o=n(9341),s=Math.min,u=[].lastIndexOf,c=!!u&&1/[1].lastIndexOf(1,-0)<0,l=o("lastIndexOf"),f=c||!l;e.exports=f?function(e){if(c)return u.apply(this,arguments)||0;var t=r(this),n=a(t.length),o=n-1;for(arguments.length>1&&(o=s(o,i(arguments[1]))),o<0&&(o=n+o);o>=0;o--)if(o in t&&t[o]===e)return o||0;return -1}:u},81194(e,t,n){var r=n(47293),i=n(5112),a=n(7392),o=i("species");e.exports=function(e){return a>=51||!r(function(){var t=[];return(t.constructor={})[o]=function(){return{foo:1}},1!==t[e](Boolean).foo})}},9341(e,t,n){"use strict";var r=n(47293);e.exports=function(e,t){var n=[][e];return!!n&&r(function(){n.call(null,t||function(){throw 1},1)})}},53671(e,t,n){var r=n(13099),i=n(47908),a=n(68361),o=n(17466),s=function(e){return function(t,n,s,u){r(n);var c=i(t),l=a(c),f=o(c.length),d=e?f-1:0,h=e?-1:1;if(s<2)for(;;){if(d in l){u=l[d],d+=h;break}if(d+=h,e?d<0:f<=d)throw TypeError("Reduce of empty array with no initial value")}for(;e?d>=0:f>d;d+=h)d in l&&(u=n(u,l[d],d,c));return u}};e.exports={left:s(!1),right:s(!0)}},94362(e){var t=Math.floor,n=function(e,a){var o=e.length,s=t(o/2);return o<8?r(e,a):i(n(e.slice(0,s),a),n(e.slice(s),a),a)},r=function(e,t){for(var n,r,i=e.length,a=1;a0;)e[r]=e[--r];r!==a++&&(e[r]=n)}return e},i=function(e,t,n){for(var r=e.length,i=t.length,a=0,o=0,s=[];a=n(e[a],t[o])?e[a++]:t[o++]):s.push(a1?arguments[1]:void 0;return(r(this),(t=void 0!==c)&&r(c),void 0==e)?new this:(n=[],t?(o=0,s=i(c,u>2?arguments[2]:void 0,2),a(e,function(e){n.push(s(e,o++))})):a(e,n.push,{that:n}),new this(n))}},82044(e){"use strict";e.exports=function(){for(var e=arguments.length,t=Array(e);e--;)t[e]=arguments[e];return new this(t)}},95631(e,t,n){"use strict";var r=n(3070).f,i=n(70030),a=n(12248),o=n(49974),s=n(25787),u=n(20408),c=n(70654),l=n(96340),f=n(19781),d=n(62423).fastKey,h=n(29909),p=h.set,b=h.getterFor;e.exports={getConstructor:function(e,t,n,c){var l=e(function(e,r){s(e,l,t),p(e,{type:t,index:i(null),first:void 0,last:void 0,size:0}),f||(e.size=0),void 0!=r&&u(r,e[c],{that:e,AS_ENTRIES:n})}),h=b(t),m=function(e,t,n){var r,i,a=h(e),o=g(e,t);return o?o.value=n:(a.last=o={index:i=d(t,!0),key:t,value:n,previous:r=a.last,next:void 0,removed:!1},a.first||(a.first=o),r&&(r.next=o),f?a.size++:e.size++,"F"!==i&&(a.index[i]=o)),e},g=function(e,t){var n,r=h(e),i=d(t);if("F"!==i)return r.index[i];for(n=r.first;n;n=n.next)if(n.key==t)return n};return a(l.prototype,{clear:function(){for(var e=this,t=h(e),n=t.index,r=t.first;r;)r.removed=!0,r.previous&&(r.previous=r.previous.next=void 0),delete n[r.index],r=r.next;t.first=t.last=void 0,f?t.size=0:e.size=0},delete:function(e){var t=this,n=h(t),r=g(t,e);if(r){var i=r.next,a=r.previous;delete n.index[r.index],r.removed=!0,a&&(a.next=i),i&&(i.previous=a),n.first==r&&(n.first=i),n.last==r&&(n.last=a),f?n.size--:t.size--}return!!r},forEach:function(e){for(var t,n=h(this),r=o(e,arguments.length>1?arguments[1]:void 0,3);t=t?t.next:n.first;)for(r(t.value,t.key,this);t&&t.removed;)t=t.previous},has:function(e){return!!g(this,e)}}),a(l.prototype,n?{get:function(e){var t=g(this,e);return t&&t.value},set:function(e,t){return m(this,0===e?0:e,t)}}:{add:function(e){return m(this,e=0===e?0:e,e)}}),f&&r(l.prototype,"size",{get:function(){return h(this).size}}),l},setStrong:function(e,t,n){var r=t+" Iterator",i=b(t),a=b(r);c(e,t,function(e,t){p(this,{type:r,target:e,state:i(e),kind:t,last:void 0})},function(){for(var e=a(this),t=e.kind,n=e.last;n&&n.removed;)n=n.previous;return e.target&&(e.last=n=n?n.next:e.state.first)?"keys"==t?{value:n.key,done:!1}:"values"==t?{value:n.value,done:!1}:{value:[n.key,n.value],done:!1}:(e.target=void 0,{value:void 0,done:!0})},n?"entries":"values",!n,!0),l(t)}}},29320(e,t,n){"use strict";var r=n(12248),i=n(62423).getWeakData,a=n(19670),o=n(70111),s=n(25787),u=n(20408),c=n(42092),l=n(86656),f=n(29909),d=f.set,h=f.getterFor,p=c.find,b=c.findIndex,m=0,g=function(e){return e.frozen||(e.frozen=new v)},v=function(){this.entries=[]},y=function(e,t){return p(e.entries,function(e){return e[0]===t})};v.prototype={get:function(e){var t=y(this,e);if(t)return t[1]},has:function(e){return!!y(this,e)},set:function(e,t){var n=y(this,e);n?n[1]=t:this.entries.push([e,t])},delete:function(e){var t=b(this.entries,function(t){return t[0]===e});return~t&&this.entries.splice(t,1),!!~t}},e.exports={getConstructor:function(e,t,n,c){var f=e(function(e,r){s(e,f,t),d(e,{type:t,id:m++,frozen:void 0}),void 0!=r&&u(r,e[c],{that:e,AS_ENTRIES:n})}),p=h(t),b=function(e,t,n){var r=p(e),o=i(a(t),!0);return!0===o?g(r).set(t,n):o[r.id]=n,e};return r(f.prototype,{delete:function(e){var t=p(this);if(!o(e))return!1;var n=i(e);return!0===n?g(t).delete(e):n&&l(n,t.id)&&delete n[t.id]},has:function(e){var t=p(this);if(!o(e))return!1;var n=i(e);return!0===n?g(t).has(e):n&&l(n,t.id)}}),r(f.prototype,n?{get:function(e){var t=p(this);if(o(e)){var n=i(e);return!0===n?g(t).get(e):n?n[t.id]:void 0}},set:function(e,t){return b(this,e,t)}}:{add:function(e){return b(this,e,!0)}}),f}}},77710(e,t,n){"use strict";var r=n(82109),i=n(17854),a=n(54705),o=n(31320),s=n(62423),u=n(20408),c=n(25787),l=n(70111),f=n(47293),d=n(17072),h=n(58003),p=n(79587);e.exports=function(e,t,n){var b=-1!==e.indexOf("Map"),m=-1!==e.indexOf("Weak"),g=b?"set":"add",v=i[e],y=v&&v.prototype,w=v,_={},E=function(e){var t=y[e];o(y,e,"add"==e?function(e){return t.call(this,0===e?0:e),this}:"delete"==e?function(e){return(!m||!!l(e))&&t.call(this,0===e?0:e)}:"get"==e?function(e){return m&&!l(e)?void 0:t.call(this,0===e?0:e)}:"has"==e?function(e){return(!m||!!l(e))&&t.call(this,0===e?0:e)}:function(e,n){return t.call(this,0===e?0:e,n),this})};if(a(e,"function"!=typeof v||!(m||y.forEach&&!f(function(){new v().entries().next()}))))w=n.getConstructor(t,e,b,g),s.enable();else if(a(e,!0)){var S=new w,k=S[g](m?{}:-0,1)!=S,x=f(function(){S.has(1)}),T=d(function(e){new v(e)}),M=!m&&f(function(){for(var e=new v,t=5;t--;)e[g](t,t);return!e.has(-0)});T||((w=t(function(t,n){c(t,w,e);var r=p(new v,t,w);return void 0!=n&&u(n,r[g],{that:r,AS_ENTRIES:b}),r})).prototype=y,y.constructor=w),(x||M)&&(E("delete"),E("has"),b&&E("get")),(M||k)&&E(g),m&&y.clear&&delete y.clear}return _[e]=w,r({global:!0,forced:w!=v},_),h(w,e),m||n.setStrong(w,e,b),w}},10313(e,t,n){var r=n(51532),i=n(4129),a=n(70030),o=n(70111),s=function(){this.object=null,this.symbol=null,this.primitives=null,this.objectsByIndex=a(null)};s.prototype.get=function(e,t){return this[e]||(this[e]=t())},s.prototype.next=function(e,t,n){var a=n?this.objectsByIndex[e]||(this.objectsByIndex[e]=new i):this.primitives||(this.primitives=new r),o=a.get(t);return o||a.set(t,o=new s),o};var u=new s;e.exports=function(){var e,t,n=u,r=arguments.length;for(e=0;e"+s+""}},24994(e,t,n){"use strict";var r=n(13383).IteratorPrototype,i=n(70030),a=n(79114),o=n(58003),s=n(97497),u=function(){return this};e.exports=function(e,t,n){var c=t+" Iterator";return e.prototype=i(r,{next:a(1,n)}),o(e,c,!1,!0),s[c]=u,e}},68880(e,t,n){var r=n(19781),i=n(3070),a=n(79114);e.exports=r?function(e,t,n){return i.f(e,t,a(1,n))}:function(e,t,n){return e[t]=n,e}},79114(e){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},86135(e,t,n){"use strict";var r=n(34948),i=n(3070),a=n(79114);e.exports=function(e,t,n){var o=r(t);o in e?i.f(e,o,a(0,n)):e[o]=n}},85573(e,t,n){"use strict";var r=n(47293),i=n(76650).start,a=Math.abs,o=Date.prototype,s=o.getTime,u=o.toISOString;e.exports=r(function(){return"0385-07-25T07:06:39.999Z"!=u.call(new Date(-5e13-1))})||!r(function(){u.call(new Date(NaN))})?function(){if(!isFinite(s.call(this)))throw RangeError("Invalid time value");var e=this,t=e.getUTCFullYear(),n=e.getUTCMilliseconds(),r=t<0?"-":t>9999?"+":"";return r+i(a(t),r?6:4,0)+"-"+i(e.getUTCMonth()+1,2,0)+"-"+i(e.getUTCDate(),2,0)+"T"+i(e.getUTCHours(),2,0)+":"+i(e.getUTCMinutes(),2,0)+":"+i(e.getUTCSeconds(),2,0)+"."+i(n,3,0)+"Z"}:u},38709(e,t,n){"use strict";var r=n(19670),i=n(92140);e.exports=function(e){if(r(this),"string"===e||"default"===e)e="string";else if("number"!==e)throw TypeError("Incorrect hint");return i(this,e)}},70654(e,t,n){"use strict";var r=n(82109),i=n(24994),a=n(79518),o=n(27674),s=n(58003),u=n(68880),c=n(31320),l=n(5112),f=n(31913),d=n(97497),h=n(13383),p=h.IteratorPrototype,b=h.BUGGY_SAFARI_ITERATORS,m=l("iterator"),g="keys",v="values",y="entries",w=function(){return this};e.exports=function(e,t,n,l,h,_,E){i(n,t,l);var S,k,x,T=function(e){if(e===h&&C)return C;if(!b&&e in A)return A[e];switch(e){case g:case v:case y:return function(){return new n(this,e)}}return function(){return new n(this)}},M=t+" Iterator",O=!1,A=e.prototype,L=A[m]||A["@@iterator"]||h&&A[h],C=!b&&L||T(h),I="Array"==t&&A.entries||L;if(I&&(S=a(I.call(new e)),p!==Object.prototype&&S.next&&(f||a(S)===p||(o?o(S,p):"function"!=typeof S[m]&&u(S,m,w)),s(S,M,!0,!0),f&&(d[M]=w))),h==v&&L&&L.name!==v&&(O=!0,C=function(){return L.call(this)}),(!f||E)&&A[m]!==C&&u(A,m,C),d[t]=C,h){if(k={values:T(v),keys:_?C:T(g),entries:T(y)},E)for(x in k)!b&&!O&&x in A||c(A,x,k[x]);else r({target:t,proto:!0,forced:b||O},k)}return k}},97235(e,t,n){var r=n(40857),i=n(86656),a=n(6061),o=n(3070).f;e.exports=function(e){var t=r.Symbol||(r.Symbol={});i(t,e)||o(t,e,{value:a.f(e)})}},19781(e,t,n){var r=n(47293);e.exports=!r(function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})},80317(e,t,n){var r=n(17854),i=n(70111),a=r.document,o=i(a)&&i(a.createElement);e.exports=function(e){return o?a.createElement(e):{}}},48324(e){e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},68886(e,t,n){var r=n(88113).match(/firefox\/(\d+)/i);e.exports=!!r&&+r[1]},7871(e){e.exports="object"==typeof window},30256(e,t,n){var r=n(88113);e.exports=/MSIE|Trident/.test(r)},71528(e,t,n){var r=n(88113),i=n(17854);e.exports=/ipad|iphone|ipod/i.test(r)&&void 0!==i.Pebble},6833(e,t,n){var r=n(88113);e.exports=/(?:ipad|iphone|ipod).*applewebkit/i.test(r)},35268(e,t,n){var r=n(84326),i=n(17854);e.exports="process"==r(i.process)},71036(e,t,n){var r=n(88113);e.exports=/web0s(?!.*chrome)/i.test(r)},88113(e,t,n){var r=n(35005);e.exports=r("navigator","userAgent")||""},7392(e,t,n){var r,i,a=n(17854),o=n(88113),s=a.process,u=a.Deno,c=s&&s.versions||u&&u.version,l=c&&c.v8;l?i=(r=l.split("."))[0]<4?1:r[0]+r[1]:o&&(!(r=o.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=o.match(/Chrome\/(\d+)/))&&(i=r[1]),e.exports=i&&+i},98008(e,t,n){var r=n(88113).match(/AppleWebKit\/(\d+)\./);e.exports=!!r&&+r[1]},80748(e){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},82109(e,t,n){var r=n(17854),i=n(31236).f,a=n(68880),o=n(31320),s=n(83505),u=n(99920),c=n(54705);e.exports=function(e,t){var n,l,f,d,h,p,b=e.target,m=e.global,g=e.stat;if(l=m?r:g?r[b]||s(b,{}):(r[b]||{}).prototype)for(f in t){if(h=t[f],d=e.noTargetGet?(p=i(l,f))&&p.value:l[f],!(n=c(m?f:b+(g?".":"#")+f,e.forced))&&void 0!==d){if(typeof h==typeof d)continue;u(h,d)}(e.sham||d&&d.sham)&&a(h,"sham",!0),o(l,f,h,e)}}},47293(e){e.exports=function(e){try{return!!e()}catch(t){return!0}}},27007(e,t,n){"use strict";n(74916);var r=n(31320),i=n(22261),a=n(47293),o=n(5112),s=n(68880),u=o("species"),c=RegExp.prototype;e.exports=function(e,t,n,l){var f=o(e),d=!a(function(){var t={};return t[f]=function(){return 7},7!=""[e](t)}),h=d&&!a(function(){var t=!1,n=/a/;return"split"===e&&((n={}).constructor={},n.constructor[u]=function(){return n},n.flags="",n[f]=/./[f]),n.exec=function(){return t=!0,null},n[f](""),!t});if(!d||!h||n){var p=/./[f],b=t(f,""[e],function(e,t,n,r,a){var o=t.exec;return o===i||o===c.exec?d&&!a?{done:!0,value:p.call(t,n,r)}:{done:!0,value:e.call(n,t,r)}:{done:!1}});r(String.prototype,e,b[0]),r(c,f,b[1])}l&&s(c[f],"sham",!0)}},6790(e,t,n){"use strict";var r=n(43157),i=n(17466),a=n(49974),o=function(e,t,n,s,u,c,l,f){for(var d,h=u,p=0,b=!!l&&a(l,f,3);p0&&r(d))h=o(e,t,d,i(d.length),h,c-1)-1;else{if(h>=9007199254740991)throw TypeError("Exceed the acceptable array length");e[h]=d}h++}p++}return h};e.exports=o},76677(e,t,n){var r=n(47293);e.exports=!r(function(){return Object.isExtensible(Object.preventExtensions({}))})},49974(e,t,n){var r=n(13099);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 0:return function(){return e.call(t)};case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,i){return e.call(t,n,r,i)}}return function(){return e.apply(t,arguments)}}},27065(e,t,n){"use strict";var r=n(13099),i=n(70111),a=[].slice,o={},s=function(e,t,n){if(!(t in o)){for(var r=[],i=0;i]*>)/g,s=/\$([$&'`]|\d{1,2})/g;e.exports=function(e,t,n,u,c,l){var f=n+e.length,d=u.length,h=s;return void 0!==c&&(c=r(c),h=o),a.call(l,h,function(r,a){var o;switch(a.charAt(0)){case"$":return"$";case"&":return e;case"`":return t.slice(0,n);case"'":return t.slice(f);case"<":o=c[a.slice(1,-1)];break;default:var s=+a;if(0===s)return r;if(s>d){var l=i(s/10);if(0===l)return r;if(l<=d)return void 0===u[l-1]?a.charAt(1):u[l-1]+a.charAt(1);return r}o=u[s-1]}return void 0===o?"":o})}},17854(e,t,n){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},86656(e,t,n){var r=n(47908),i={}.hasOwnProperty;e.exports=Object.hasOwn||function(e,t){return i.call(r(e),t)}},3501(e){e.exports={}},842(e,t,n){var r=n(17854);e.exports=function(e,t){var n=r.console;n&&n.error&&(1===arguments.length?n.error(e):n.error(e,t))}},60490(e,t,n){var r=n(35005);e.exports=r("document","documentElement")},64664(e,t,n){var r=n(19781),i=n(47293),a=n(80317);e.exports=!r&&!i(function(){return 7!=Object.defineProperty(a("div"),"a",{get:function(){return 7}}).a})},11179(e){var t=Math.abs,n=Math.pow,r=Math.floor,i=Math.log,a=Math.LN2,o=function(e,o,s){var u,c,l,f=Array(s),d=8*s-o-1,h=(1<>1,b=23===o?n(2,-24)-n(2,-77):0,m=e<0||0===e&&1/e<0?1:0,g=0;for((e=t(e))!=e||e===1/0?(c=e!=e?1:0,u=h):(u=r(i(e)/a),e*(l=n(2,-u))<1&&(u--,l*=2),u+p>=1?e+=b/l:e+=b*n(2,1-p),e*l>=2&&(u++,l/=2),u+p>=h?(c=0,u=h):u+p>=1?(c=(e*l-1)*n(2,o),u+=p):(c=e*n(2,p-1)*n(2,o),u=0));o>=8;f[g++]=255&c,c/=256,o-=8);for(u=u<0;f[g++]=255&u,u/=256,d-=8);return f[--g]|=128*m,f},s=function(e,t){var r,i=e.length,a=8*i-t-1,o=(1<>1,u=a-7,c=i-1,l=e[c--],f=127&l;for(l>>=7;u>0;f=256*f+e[c],c--,u-=8);for(r=f&(1<<-u)-1,f>>=-u,u+=t;u>0;r=256*r+e[c],c--,u-=8);if(0===f)f=1-s;else{if(f===o)return r?NaN:l?-1/0:1/0;r+=n(2,t),f-=s}return(l?-1:1)*r*n(2,f-t)};e.exports={pack:o,unpack:s}},68361(e,t,n){var r=n(47293),i=n(84326),a="".split;e.exports=r(function(){return!Object("z").propertyIsEnumerable(0)})?function(e){return"String"==i(e)?a.call(e,""):Object(e)}:Object},79587(e,t,n){var r=n(70111),i=n(27674);e.exports=function(e,t,n){var a,o;return i&&"function"==typeof(a=t.constructor)&&a!==n&&r(o=a.prototype)&&o!==n.prototype&&i(e,o),e}},42788(e,t,n){var r=n(5465),i=Function.toString;"function"!=typeof r.inspectSource&&(r.inspectSource=function(e){return i.call(e)}),e.exports=r.inspectSource},62423(e,t,n){var r=n(82109),i=n(3501),a=n(70111),o=n(86656),s=n(3070).f,u=n(8006),c=n(1156),l=n(69711),f=n(76677),d=!1,h=l("meta"),p=0,b=Object.isExtensible||function(){return!0},m=function(e){s(e,h,{value:{objectID:"O"+p++,weakData:{}}})},g=function(e,t){if(!a(e))return"symbol"==typeof e?e:("string"==typeof e?"S":"P")+e;if(!o(e,h)){if(!b(e))return"F";if(!t)return"E";m(e)}return e[h].objectID},v=function(e,t){if(!o(e,h)){if(!b(e))return!0;if(!t)return!1;m(e)}return e[h].weakData},y=function(e){return f&&d&&b(e)&&!o(e,h)&&m(e),e},w=function(){_.enable=function(){},d=!0;var e=u.f,t=[].splice,n={};n[h]=1,e(n).length&&(u.f=function(n){for(var r=e(n),i=0,a=r.length;ih;h++)if((b=k(e[h]))&&b instanceof l)return b;return new l(!1)}f=s(e,d)}for(m=f.next;!(g=m.call(f)).done;){try{b=k(g.value)}catch(x){c(f,"throw",x)}if("object"==typeof b&&b&&b instanceof l)return b}return new l(!1)}},99212(e,t,n){var r=n(19670);e.exports=function(e,t,n){var i,a;r(e);try{if(void 0===(i=e.return)){if("throw"===t)throw n;return n}i=i.call(e)}catch(o){a=!0,i=o}if("throw"===t)throw n;if(a)throw i;return r(i),n}},54956(e,t,n){"use strict";var r=n(40857),i=n(13099),a=n(19670),o=n(70030),s=n(68880),u=n(12248),c=n(5112),l=n(29909),f=l.set,d=l.get,h=c("toStringTag");e.exports=function(e,t){var n=function(e){e.next=i(e.iterator.next),e.done=!1,e.ignoreArg=!t,f(this,e)};return n.prototype=u(o(r.Iterator.prototype),{next:function(n){var r=d(this),i=arguments.length?[r.ignoreArg?void 0:n]:t?[]:[void 0];r.ignoreArg=!1;var a=r.done?void 0:e.call(r,i);return{done:r.done,value:a}},return:function(e){var t=d(this).iterator;t.done=!0;var n=t.return;return{done:!0,value:void 0===n?e:a(n.call(t,e)).value}},throw:function(e){var t=d(this).iterator;t.done=!0;var n=t.throw;if(void 0===n)throw e;return n.call(t,e)}}),t||s(n.prototype,h,"Generator"),n}},13383(e,t,n){"use strict";var r,i,a,o=n(47293),s=n(79518),u=n(68880),c=n(86656),l=n(5112),f=n(31913),d=l("iterator"),h=!1,p=function(){return this};[].keys&&("next"in(a=[].keys())?(i=s(s(a)))!==Object.prototype&&(r=i):h=!0);var b=void 0==r||o(function(){var e={};return r[d].call(e)!==e});b&&(r={}),f&&!b||c(r,d)||u(r,d,p),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:h}},97497(e){e.exports={}},37502(e,t,n){"use strict";var r=n(19670);e.exports=function(e,t){var n=r(this),i=n.has(e)&&"update"in t?t.update(n.get(e),e,n):t.insert(e,n);return n.set(e,i),i}},8154(e,t,n){"use strict";var r=n(19670);e.exports=function(e,t){var n,i=r(this),a=arguments.length>2?arguments[2]:void 0;if("function"!=typeof t&&"function"!=typeof a)throw TypeError("At least one callback required");return i.has(e)?(n=i.get(e),"function"==typeof t&&(n=t(n),i.set(e,n))):"function"==typeof a&&(n=a(),i.set(e,n)),n}},66736(e){var t=Math.expm1,n=Math.exp;e.exports=!t||t(10)>22025.465794806718||22025.465794806718>t(10)||-.00000000000000002!=t(-.00000000000000002)?function(e){return 0==(e=+e)?e:e>-.000001&&e<1e-6?e+e*e/2:n(e)-1}:t},26130(e,t,n){var r=n(64310),i=Math.abs,a=Math.pow,o=a(2,-52),s=a(2,-23),u=a(2,127)*(2-s),c=a(2,-126),l=function(e){return e+1/o-1/o};e.exports=Math.fround||function(e){var t,n,a=i(e),f=r(e);return au||n!=n?f*(1/0):f*n}},26513(e){var t=Math.log;e.exports=Math.log1p||function(e){return(e=+e)>-.00000001&&e<1e-8?e-e*e/2:t(1+e)}},47103(e){e.exports=Math.scale||function(e,t,n,r,i){return 0===arguments.length||e!=e||t!=t||n!=n||r!=r||i!=i?NaN:e===1/0||e===-1/0?e:(e-t)*(i-r)/(n-t)+r}},64310(e){e.exports=Math.sign||function(e){return 0==(e=+e)||e!=e?e:e<0?-1:1}},95948(e,t,n){var r,i,a,o,s,u,c,l,f=n(17854),d=n(31236).f,h=n(20261).set,p=n(6833),b=n(71528),m=n(71036),g=n(35268),v=f.MutationObserver||f.WebKitMutationObserver,y=f.document,w=f.process,_=f.Promise,E=d(f,"queueMicrotask"),S=E&&E.value;S||(r=function(){var e,t;for(g&&(e=w.domain)&&e.exit();i;){t=i.fn,i=i.next;try{t()}catch(n){throw i?o():a=void 0,n}}a=void 0,e&&e.enter()},p||g||m||!v||!y?!b&&_&&_.resolve?((c=_.resolve(void 0)).constructor=_,l=c.then,o=function(){l.call(c,r)}):o=g?function(){w.nextTick(r)}:function(){h.call(f,r)}:(s=!0,u=y.createTextNode(""),new v(r).observe(u,{characterData:!0}),o=function(){u.data=s=!s})),e.exports=S||function(e){var t={fn:e,next:void 0};a&&(a.next=t),i||(i=t,o()),a=t}},13366(e,t,n){var r=n(17854);e.exports=r.Promise},30133(e,t,n){var r=n(7392),i=n(47293);e.exports=!!Object.getOwnPropertySymbols&&!i(function(){var e=Symbol();return!String(e)||!(Object(e) instanceof Symbol)||!Symbol.sham&&r&&r<41})},590(e,t,n){var r=n(47293),i=n(5112),a=n(31913),o=i("iterator");e.exports=!r(function(){var e=new URL("b?a=1&b=2&c=3","http://a"),t=e.searchParams,n="";return e.pathname="c%20d",t.forEach(function(e,r){t.delete("b"),n+=r+e}),a&&!e.toJSON||!t.sort||"http://a/c%20d?a=1&c=3"!==e.href||"3"!==t.get("c")||"a=1"!==String(new URLSearchParams("?a=1"))||!t[o]||"a"!==new URL("https://a@b").username||"b"!==new URLSearchParams(new URLSearchParams("a=b")).get("a")||"xn--e1aybc"!==new URL("http://тест").host||"#%D0%B1"!==new URL("http://a#б").hash||"a1c3"!==n||"x"!==new URL("http://x",void 0).host})},68536(e,t,n){var r=n(17854),i=n(42788),a=r.WeakMap;e.exports="function"==typeof a&&/native code/.test(i(a))},78523(e,t,n){"use strict";var r=n(13099),i=function(e){var t,n;this.promise=new e(function(e,r){if(void 0!==t||void 0!==n)throw TypeError("Bad Promise constructor");t=e,n=r}),this.resolve=r(t),this.reject=r(n)};e.exports.f=function(e){return new i(e)}},3929(e,t,n){var r=n(47850);e.exports=function(e){if(r(e))throw TypeError("The method doesn't accept regular expressions");return e}},77023(e,t,n){var r=n(17854).isFinite;e.exports=Number.isFinite||function(e){return"number"==typeof e&&r(e)}},2814(e,t,n){var r=n(17854),i=n(41340),a=n(53111).trim,o=n(81361),s=r.parseFloat,u=1/s(o+"-0")!=-1/0;e.exports=u?function(e){var t=a(i(e)),n=s(t);return 0===n&&"-"==t.charAt(0)?-0:n}:s},83009(e,t,n){var r=n(17854),i=n(41340),a=n(53111).trim,o=n(81361),s=r.parseInt,u=/^[+-]?0[Xx]/,c=8!==s(o+"08")||22!==s(o+"0x16");e.exports=c?function(e,t){var n=a(i(e));return s(n,t>>>0||(u.test(n)?16:10))}:s},80430(e,t,n){"use strict";var r=n(29909),i=n(24994),a=n(70111),o=n(36048),s=n(19781),u="Incorrect Number.range arguments",c="NumericRangeIterator",l=r.set,f=r.getterFor(c),d=i(function(e,t,n,r,i,o){if(typeof e!=r||t!==1/0&&t!==-1/0&&typeof t!=r)throw TypeError(u);if(e===1/0||e===-1/0)throw RangeError(u);var f,d=t>e,h=!1;if(void 0===n)f=void 0;else if(a(n))f=n.step,h=!!n.inclusive;else if(typeof n==r)f=n;else throw TypeError(u);if(null==f&&(f=d?o:-o),typeof f!=r)throw TypeError(u);if(f===1/0||f===-1/0||f===i&&e!==t)throw RangeError(u);var p=e!=e||t!=t||f!=f||t>e!=f>i;l(this,{type:c,start:e,end:t,step:f,inclusiveEnd:h,hitsEnd:p,currentCount:i,zero:i}),s||(this.start=e,this.end=t,this.step=f,this.inclusive=h)},c,function(){var e,t=f(this);if(t.hitsEnd)return{value:void 0,done:!0};var n=t.start,r=t.end,i=n+t.step*t.currentCount++;i===r&&(t.hitsEnd=!0);var a=t.inclusiveEnd;return(e=r>n?a?i>r:i>=r:a?r>i:r>=i)?{value:void 0,done:t.hitsEnd=!0}:{value:i,done:!1}}),h=function(e){return{get:e,set:function(){},configurable:!0,enumerable:!1}};s&&o(d.prototype,{start:h(function(){return f(this).start}),end:h(function(){return f(this).end}),inclusive:h(function(){return f(this).inclusiveEnd}),step:h(function(){return f(this).step})}),e.exports=d},21574(e,t,n){"use strict";var r=n(19781),i=n(47293),a=n(81956),o=n(25181),s=n(55296),u=n(47908),c=n(68361),l=Object.assign,f=Object.defineProperty;e.exports=!l||i(function(){if(r&&1!==l({b:1},l(f({},"a",{enumerable:!0,get:function(){f(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},n=Symbol(),i="abcdefghijklmnopqrst";return e[n]=7,i.split("").forEach(function(e){t[e]=e}),7!=l({},e)[n]||a(l({},t)).join("")!=i})?function(e,t){for(var n=u(e),i=arguments.length,l=1,f=o.f,d=s.f;i>l;)for(var h,p=c(arguments[l++]),b=f?a(p).concat(f(p)):a(p),m=b.length,g=0;m>g;)h=b[g++],(!r||d.call(p,h))&&(n[h]=p[h]);return n}:l},70030(e,t,n){var r,i=n(19670),a=n(36048),o=n(80748),s=n(3501),u=n(60490),c=n(80317),l=n(6200),f=">",d="<",h="prototype",p="script",b=l("IE_PROTO"),m=function(){},g=function(e){return d+p+f+e+d+"/"+p+f},v=function(e){e.write(g("")),e.close();var t=e.parentWindow.Object;return e=null,t},y=function(){var e,t=c("iframe"),n="java"+p+":";return t.style.display="none",u.appendChild(t),t.src=String(n),(e=t.contentWindow.document).open(),e.write(g("document.F=Object")),e.close(),e.F},w=function(){try{r=new ActiveXObject("htmlfile")}catch(e){}w="undefined"!=typeof document?document.domain&&r?v(r):y():v(r);for(var t=o.length;t--;)delete w[h][o[t]];return w()};s[b]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(m[h]=i(e),n=new m,m[h]=null,n[b]=e):n=w(),void 0===t?n:a(n,t)}},36048(e,t,n){var r=n(19781),i=n(3070),a=n(19670),o=n(81956);e.exports=r?Object.defineProperties:function(e,t){a(e);for(var n,r=o(t),s=r.length,u=0;s>u;)i.f(e,n=r[u++],t[n]);return e}},3070(e,t,n){var r=n(19781),i=n(64664),a=n(19670),o=n(34948),s=Object.defineProperty;t.f=r?s:function(e,t,n){if(a(e),t=o(t),a(n),i)try{return s(e,t,n)}catch(r){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},31236(e,t,n){var r=n(19781),i=n(55296),a=n(79114),o=n(45656),s=n(34948),u=n(86656),c=n(64664),l=Object.getOwnPropertyDescriptor;t.f=r?l:function(e,t){if(e=o(e),t=s(t),c)try{return l(e,t)}catch(n){}if(u(e,t))return a(!i.f.call(e,t),e[t])}},1156(e,t,n){var r=n(45656),i=n(8006).f,a={}.toString,o="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],s=function(e){try{return i(e)}catch(t){return o.slice()}};e.exports.f=function(e){return o&&"[object Window]"==a.call(e)?s(e):i(r(e))}},8006(e,t,n){var r=n(16324),i=n(80748).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,i)}},25181(e,t){t.f=Object.getOwnPropertySymbols},79518(e,t,n){var r=n(86656),i=n(47908),a=n(6200),o=n(49920),s=a("IE_PROTO"),u=Object.prototype;e.exports=o?Object.getPrototypeOf:function(e){return(e=i(e),r(e,s))?e[s]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?u:null}},60996(e,t,n){"use strict";var r=n(29909),i=n(24994),a=n(86656),o=n(81956),s=n(47908),u="Object Iterator",c=r.set,l=r.getterFor(u);e.exports=i(function(e,t){var n=s(e);c(this,{type:u,mode:t,object:n,keys:o(n),index:0})},"Object",function(){for(var e=l(this),t=e.keys;;){if(null===t||e.index>=t.length)return e.object=e.keys=null,{value:void 0,done:!0};var n=t[e.index++],r=e.object;if(a(r,n)){switch(e.mode){case"keys":return{value:n,done:!1};case"values":return{value:r[n],done:!1}}return{value:[n,r[n]],done:!1}}}})},16324(e,t,n){var r=n(86656),i=n(45656),a=n(41318).indexOf,o=n(3501);e.exports=function(e,t){var n,s=i(e),u=0,c=[];for(n in s)!r(o,n)&&r(s,n)&&c.push(n);for(;t.length>u;)r(s,n=t[u++])&&(~a(c,n)||c.push(n));return c}},81956(e,t,n){var r=n(16324),i=n(80748);e.exports=Object.keys||function(e){return r(e,i)}},55296(e,t){"use strict";var n={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,i=r&&!n.call({1:2},1);t.f=i?function(e){var t=r(this,e);return!!t&&t.enumerable}:n},69026(e,t,n){"use strict";var r=n(31913),i=n(17854),a=n(47293),o=n(98008);e.exports=r||!a(function(){if(!o||!(o<535)){var e=Math.random();__defineSetter__.call(null,e,function(){}),delete i[e]}})},27674(e,t,n){var r=n(19670),i=n(96077);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{(e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(n,[]),t=n instanceof Array}catch(a){}return function(n,a){return r(n),i(a),t?e.call(n,a):n.__proto__=a,n}}():void 0)},44699(e,t,n){var r=n(19781),i=n(81956),a=n(45656),o=n(55296).f,s=function(e){return function(t){for(var n,s=a(t),u=i(s),c=u.length,l=0,f=[];c>l;)n=u[l++],(!r||o.call(s,n))&&f.push(e?[n,s[n]]:s[n]);return f}};e.exports={entries:s(!0),values:s(!1)}},90288(e,t,n){"use strict";var r=n(51694),i=n(70648);e.exports=r?({}).toString:function(){return"[object "+i(this)+"]"}},92140(e,t,n){var r=n(70111);e.exports=function(e,t){var n,i;if("string"===t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e))||"function"==typeof(n=e.valueOf)&&!r(i=n.call(e))||"string"!==t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;throw TypeError("Can't convert object to primitive value")}},53887(e,t,n){var r=n(35005),i=n(8006),a=n(25181),o=n(19670);e.exports=r("Reflect","ownKeys")||function(e){var t=i.f(o(e)),n=a.f;return n?t.concat(n(e)):t}},40857(e,t,n){var r=n(17854);e.exports=r},12534(e){e.exports=function(e){try{return{error:!1,value:e()}}catch(t){return{error:!0,value:t}}}},69478(e,t,n){var r=n(19670),i=n(70111),a=n(78523);e.exports=function(e,t){if(r(e),i(t)&&t.constructor===e)return t;var n=a.f(e);return(0,n.resolve)(t),n.promise}},12248(e,t,n){var r=n(31320);e.exports=function(e,t,n){for(var i in t)r(e,i,t[i],n);return e}},31320(e,t,n){var r=n(17854),i=n(68880),a=n(86656),o=n(83505),s=n(42788),u=n(29909),c=u.get,l=u.enforce,f=String(String).split("String");(e.exports=function(e,t,n,s){var u,c=!!s&&!!s.unsafe,d=!!s&&!!s.enumerable,h=!!s&&!!s.noTargetGet;if("function"!=typeof n||("string"!=typeof t||a(n,"name")||i(n,"name",t),(u=l(n)).source||(u.source=f.join("string"==typeof t?t:""))),e===r){d?e[t]=n:o(t,n);return}c?!h&&e[t]&&(d=!0):delete e[t],d?e[t]=n:i(e,t,n)})(Function.prototype,"toString",function(){return"function"==typeof this&&c(this).source||s(this)})},38845(e,t,n){var r=n(51532),i=n(4129),a=n(72309)("metadata"),o=a.store||(a.store=new i),s=function(e,t,n){var i=o.get(e);if(!i){if(!n)return;o.set(e,i=new r)}var a=i.get(t);if(!a){if(!n)return;i.set(t,a=new r)}return a},u=function(e,t,n){var r=s(t,n,!1);return void 0!==r&&r.has(e)},c=function(e,t,n){var r=s(t,n,!1);return void 0===r?void 0:r.get(e)},l=function(e,t,n,r){s(n,r,!0).set(e,t)},f=function(e,t){var n=s(e,t,!1),r=[];return n&&n.forEach(function(e,t){r.push(t)}),r},d=function(e){return void 0===e||"symbol"==typeof e?e:String(e)};e.exports={store:o,getMap:s,has:u,get:c,set:l,keys:f,toKey:d}},97651(e,t,n){var r=n(84326),i=n(22261);e.exports=function(e,t){var n=e.exec;if("function"==typeof n){var a=n.call(e,t);if("object"!=typeof a)throw TypeError("RegExp exec method returned something other than an Object or null");return a}if("RegExp"!==r(e))throw TypeError("RegExp#exec called on incompatible receiver");return i.call(e,t)}},22261(e,t,n){"use strict";var r,i,a=n(41340),o=n(67066),s=n(52999),u=n(72309),c=n(70030),l=n(29909).get,f=n(9441),d=n(38173),h=RegExp.prototype.exec,p=u("native-string-replace",String.prototype.replace),b=h,m=(r=/a/,i=/b*/g,h.call(r,"a"),h.call(i,"a"),0!==r.lastIndex||0!==i.lastIndex),g=s.UNSUPPORTED_Y||s.BROKEN_CARET,v=void 0!==/()??/.exec("")[1];(m||v||g||f||d)&&(b=function(e){var t,n,r,i,s,u,f,d=this,y=l(d),w=a(e),_=y.raw;if(_)return _.lastIndex=d.lastIndex,t=b.call(_,w),d.lastIndex=_.lastIndex,t;var E=y.groups,S=g&&d.sticky,k=o.call(d),x=d.source,T=0,M=w;if(S&&(-1===(k=k.replace("y","")).indexOf("g")&&(k+="g"),M=w.slice(d.lastIndex),d.lastIndex>0&&(!d.multiline||d.multiline&&"\n"!==w.charAt(d.lastIndex-1))&&(x="(?: "+x+")",M=" "+M,T++),n=RegExp("^(?:"+x+")",k)),v&&(n=RegExp("^"+x+"$(?!\\s)",k)),m&&(r=d.lastIndex),i=h.call(S?n:d,M),S?i?(i.input=i.input.slice(T),i[0]=i[0].slice(T),i.index=d.lastIndex,d.lastIndex+=i[0].length):d.lastIndex=0:m&&i&&(d.lastIndex=d.global?i.index+i[0].length:r),v&&i&&i.length>1&&p.call(i[0],n,function(){for(s=1;sb)","g");return"b"!==e.exec("b").groups.a||"bc"!=="b".replace(e,"$c")})},84488(e){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},46465(e){e.exports=function(e,t){return e===t||e!=e&&t!=t}},81150(e){e.exports=Object.is||function(e,t){return e===t?0!==e||1/e==1/t:e!=e&&t!=t}},83505(e,t,n){var r=n(17854);e.exports=function(e,t){try{Object.defineProperty(r,e,{value:t,configurable:!0,writable:!0})}catch(n){r[e]=t}return t}},96340(e,t,n){"use strict";var r=n(35005),i=n(3070),a=n(5112),o=n(19781),s=a("species");e.exports=function(e){var t=r(e),n=i.f;o&&t&&!t[s]&&n(t,s,{configurable:!0,get:function(){return this}})}},58003(e,t,n){var r=n(3070).f,i=n(86656),a=n(5112)("toStringTag");e.exports=function(e,t,n){e&&!i(e=n?e:e.prototype,a)&&r(e,a,{configurable:!0,value:t})}},6200(e,t,n){var r=n(72309),i=n(69711),a=r("keys");e.exports=function(e){return a[e]||(a[e]=i(e))}},5465(e,t,n){var r=n(17854),i=n(83505),a="__core-js_shared__",o=r[a]||i(a,{});e.exports=o},72309(e,t,n){var r=n(31913),i=n(5465);(e.exports=function(e,t){return i[e]||(i[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.17.0",mode:r?"pure":"global",copyright:"\xa9 2021 Denis Pushkarev (zloirock.ru)"})},36707(e,t,n){var r=n(19670),i=n(13099),a=n(5112)("species");e.exports=function(e,t){var n,o=r(e).constructor;return void 0===o||void 0==(n=r(o)[a])?t:i(n)}},43429(e,t,n){var r=n(47293);e.exports=function(e){return r(function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3})}},28710(e,t,n){var r=n(99958),i=n(41340),a=n(84488),o=function(e){return function(t,n){var o,s,u=i(a(t)),c=r(n),l=u.length;return c<0||c>=l?e?"":void 0:(o=u.charCodeAt(c))<55296||o>56319||c+1===l||(s=u.charCodeAt(c+1))<56320||s>57343?e?u.charAt(c):o:e?u.slice(c,c+2):(o-55296<<10)+(s-56320)+65536}};e.exports={codeAt:o(!1),charAt:o(!0)}},54986(e,t,n){var r=n(88113);e.exports=/Version\/10(?:\.\d+){1,2}(?: [\w./]+)?(?: Mobile\/\w+)? Safari\//.test(r)},76650(e,t,n){var r=n(17466),i=n(41340),a=n(38415),o=n(84488),s=Math.ceil,u=function(e){return function(t,n,u){var c,l,f=i(o(t)),d=f.length,h=void 0===u?" ":i(u),p=r(n);return p<=d||""==h?f:(c=p-d,(l=a.call(h,s(c/h.length))).length>c&&(l=l.slice(0,c)),e?f+l:l+f)}};e.exports={start:u(!1),end:u(!0)}},33197(e){"use strict";var t=2147483647,n=36,r=1,i=26,a=38,o=700,s=72,u=128,c="-",l=/[^\0-\u007E]/,f=/[.\u3002\uFF0E\uFF61]/g,d="Overflow: input needs wider integers to process",h=n-r,p=Math.floor,b=String.fromCharCode,m=function(e){for(var t=[],n=0,r=e.length;n=55296&&i<=56319&&n>1,e+=p(e/t);e>h*i>>1;s+=n)e=p(e/h);return p(s+(h+1)*e/(e+a))},y=function(e){var a,o,l=[],f=(e=m(e)).length,h=u,y=0,w=s;for(a=0;a=h&&op((t-y)/k))throw RangeError(d);for(y+=(S-h)*k,h=S,a=0;at)throw RangeError(d);if(o==h){for(var x=y,T=n;;T+=n){var M=T<=w?r:T>=w+i?i:T-w;if(x0;(o>>>=1)&&(t+=t))1&o&&(n+=t);return n}},76091(e,t,n){var r=n(47293),i=n(81361),a="​\x85᠎";e.exports=function(e){return r(function(){return!!i[e]()||a[e]()!=a||i[e].name!==e})}},53111(e,t,n){var r=n(84488),i=n(41340),a="["+n(81361)+"]",o=RegExp("^"+a+a+"*"),s=RegExp(a+a+"*$"),u=function(e){return function(t){var n=i(r(t));return 1&e&&(n=n.replace(o,"")),2&e&&(n=n.replace(s,"")),n}};e.exports={start:u(1),end:u(2),trim:u(3)}},20261(e,t,n){var r,i,a,o,s=n(17854),u=n(47293),c=n(49974),l=n(60490),f=n(80317),d=n(6833),h=n(35268),p=s.setImmediate,b=s.clearImmediate,m=s.process,g=s.MessageChannel,v=s.Dispatch,y=0,w={},_="onreadystatechange";try{r=s.location}catch(E){}var S=function(e){if(w.hasOwnProperty(e)){var t=w[e];delete w[e],t()}},k=function(e){return function(){S(e)}},x=function(e){S(e.data)},T=function(e){s.postMessage(String(e),r.protocol+"//"+r.host)};p&&b||(p=function(e){for(var t=[],n=arguments.length,r=1;n>r;)t.push(arguments[r++]);return w[++y]=function(){("function"==typeof e?e:Function(e)).apply(void 0,t)},i(y),y},b=function(e){delete w[e]},h?i=function(e){m.nextTick(k(e))}:v&&v.now?i=function(e){v.now(k(e))}:g&&!d?(o=(a=new g).port2,a.port1.onmessage=x,i=c(o.postMessage,o,1)):s.addEventListener&&"function"==typeof postMessage&&!s.importScripts&&r&&"file:"!==r.protocol&&!u(T)?(i=T,s.addEventListener("message",x,!1)):i=_ in f("script")?function(e){l.appendChild(f("script"))[_]=function(){l.removeChild(this),S(e)}}:function(e){setTimeout(k(e),0)}),e.exports={set:p,clear:b}},50863(e,t,n){var r=n(84326);e.exports=function(e){if("number"!=typeof e&&"Number"!=r(e))throw TypeError("Incorrect invocation");return+e}},51400(e,t,n){var r=n(99958),i=Math.max,a=Math.min;e.exports=function(e,t){var n=r(e);return n<0?i(n+t,0):a(n,t)}},57067(e,t,n){var r=n(99958),i=n(17466);e.exports=function(e){if(void 0===e)return 0;var t=r(e),n=i(t);if(t!==n)throw RangeError("Wrong length or index");return n}},45656(e,t,n){var r=n(68361),i=n(84488);e.exports=function(e){return r(i(e))}},99958(e){var t=Math.ceil,n=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?n:t)(e)}},17466(e,t,n){var r=n(99958),i=Math.min;e.exports=function(e){return e>0?i(r(e),9007199254740991):0}},47908(e,t,n){var r=n(84488);e.exports=function(e){return Object(r(e))}},84590(e,t,n){var r=n(73002);e.exports=function(e,t){var n=r(e);if(n%t)throw RangeError("Wrong offset");return n}},73002(e,t,n){var r=n(99958);e.exports=function(e){var t=r(e);if(t<0)throw RangeError("The argument can't be less than 0");return t}},57593(e,t,n){var r=n(70111),i=n(52190),a=n(92140),o=n(5112)("toPrimitive");e.exports=function(e,t){if(!r(e)||i(e))return e;var n,s=e[o];if(void 0!==s){if(void 0===t&&(t="default"),!r(n=s.call(e,t))||i(n))return n;throw TypeError("Can't convert object to primitive value")}return void 0===t&&(t="number"),a(e,t)}},34948(e,t,n){var r=n(57593),i=n(52190);e.exports=function(e){var t=r(e,"string");return i(t)?t:String(t)}},51694(e,t,n){var r=n(5112)("toStringTag"),i={};i[r]="z",e.exports="[object z]"===String(i)},41340(e,t,n){var r=n(52190);e.exports=function(e){if(r(e))throw TypeError("Cannot convert a Symbol value to a string");return String(e)}},19843(e,t,n){"use strict";var r=n(82109),i=n(17854),a=n(19781),o=n(63832),s=n(90260),u=n(13331),c=n(25787),l=n(79114),f=n(68880),d=n(18730),h=n(17466),p=n(57067),b=n(84590),m=n(34948),g=n(86656),v=n(70648),y=n(70111),w=n(52190),_=n(70030),E=n(27674),S=n(8006).f,k=n(97321),x=n(42092).forEach,T=n(96340),M=n(3070),O=n(31236),A=n(29909),L=n(79587),C=A.get,I=A.set,D=M.f,N=O.f,P=Math.round,R=i.RangeError,j=u.ArrayBuffer,F=u.DataView,Y=s.NATIVE_ARRAY_BUFFER_VIEWS,B=s.TYPED_ARRAY_CONSTRUCTOR,U=s.TYPED_ARRAY_TAG,H=s.TypedArray,$=s.TypedArrayPrototype,z=s.aTypedArrayConstructor,G=s.isTypedArray,W="BYTES_PER_ELEMENT",K="Wrong length",V=function(e,t){for(var n=0,r=t.length,i=new(z(e))(r);r>n;)i[n]=t[n++];return i},q=function(e,t){D(e,t,{get:function(){return C(this)[t]}})},Z=function(e){var t;return e instanceof j||"ArrayBuffer"==(t=v(e))||"SharedArrayBuffer"==t},X=function(e,t){return G(e)&&!w(t)&&t in e&&d(+t)&&t>=0},J=function(e,t){return t=m(t),X(e,t)?l(2,e[t]):N(e,t)},Q=function(e,t,n){return(t=m(t),X(e,t)&&y(n)&&g(n,"value")&&!g(n,"get")&&!g(n,"set")&&!n.configurable&&(!g(n,"writable")||n.writable)&&(!g(n,"enumerable")||n.enumerable))?(e[t]=n.value,e):D(e,t,n)};a?(Y||(O.f=J,M.f=Q,q($,"buffer"),q($,"byteOffset"),q($,"byteLength"),q($,"length")),r({target:"Object",stat:!0,forced:!Y},{getOwnPropertyDescriptor:J,defineProperty:Q}),e.exports=function(e,t,n){var a=e.match(/\d+$/)[0]/8,s=e+(n?"Clamped":"")+"Array",u="get"+e,l="set"+e,d=i[s],m=d,g=m&&m.prototype,v={},w=function(e,t){var n=C(e);return n.view[u](t*a+n.byteOffset,!0)},M=function(e,t,r){var i=C(e);n&&(r=(r=P(r))<0?0:r>255?255:255&r),i.view[l](t*a+i.byteOffset,r,!0)},O=function(e,t){D(e,t,{get:function(){return w(this,t)},set:function(e){return M(this,t,e)},enumerable:!0})};Y?o&&(m=t(function(e,t,n,r){return c(e,m,s),L(y(t)?Z(t)?void 0!==r?new d(t,b(n,a),r):void 0!==n?new d(t,b(n,a)):new d(t):G(t)?V(m,t):k.call(m,t):new d(p(t)),e,m)}),E&&E(m,H),x(S(d),function(e){e in m||f(m,e,d[e])}),m.prototype=g):(m=t(function(e,t,n,r){c(e,m,s);var i,o,u,l=0,f=0;if(y(t)){if(Z(t)){i=t,f=b(n,a);var d=t.byteLength;if(void 0===r){if(d%a||(o=d-f)<0)throw R(K)}else if((o=h(r)*a)+f>d)throw R(K);u=o/a}else if(G(t))return V(m,t);else return k.call(m,t)}else o=(u=p(t))*a,i=new j(o);for(I(e,{buffer:i,byteOffset:f,byteLength:o,length:u,view:new F(i)});l1?arguments[1]:void 0,g=void 0!==m,v=o(p);if(void 0!=v&&!s(v))for(h=(d=a(p,v)).next,p=[];!(f=h.call(d)).done;)p.push(f.value);for(g&&b>2&&(m=u(m,arguments[2],2)),n=i(p.length),l=new(c(this))(n),t=0;n>t;t++)l[t]=g?m(p[t],t):p[t];return l}},66304(e,t,n){var r=n(90260),i=n(36707),a=r.TYPED_ARRAY_CONSTRUCTOR,o=r.aTypedArrayConstructor;e.exports=function(e){return o(i(e,e[a]))}},69711(e){var t=0,n=Math.random();e.exports=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++t+n).toString(36)}},43307(e,t,n){var r=n(30133);e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},6061(e,t,n){var r=n(5112);t.f=r},5112(e,t,n){var r=n(17854),i=n(72309),a=n(86656),o=n(69711),s=n(30133),u=n(43307),c=i("wks"),l=r.Symbol,f=u?l:l&&l.withoutSetter||o;e.exports=function(e){return a(c,e)&&(s||"string"==typeof c[e])||(s&&a(l,e)?c[e]=l[e]:c[e]=f("Symbol."+e)),c[e]}},81361(e){e.exports=" \n\v\f\r \xa0               \u2028\u2029\uFEFF"},9170(e,t,n){"use strict";var r=n(82109),i=n(79518),a=n(27674),o=n(70030),s=n(68880),u=n(79114),c=n(20408),l=n(41340),f=function(e,t){var n=this;if(!(n instanceof f))return new f(e,t);a&&(n=a(Error(void 0),i(n))),void 0!==t&&s(n,"message",l(t));var r=[];return c(e,r.push,{that:r}),s(n,"errors",r),n};f.prototype=o(Error.prototype,{constructor:u(5,f),message:u(5,""),name:u(5,"AggregateError")}),r({global:!0},{AggregateError:f})},18264(e,t,n){"use strict";var r=n(82109),i=n(17854),a=n(13331),o=n(96340),s="ArrayBuffer",u=a[s];r({global:!0,forced:i[s]!==u},{ArrayBuffer:u}),o(s)},76938(e,t,n){var r=n(82109),i=n(90260);r({target:"ArrayBuffer",stat:!0,forced:!i.NATIVE_ARRAY_BUFFER_VIEWS},{isView:i.isView})},39575(e,t,n){"use strict";var r=n(82109),i=n(47293),a=n(13331),o=n(19670),s=n(51400),u=n(17466),c=n(36707),l=a.ArrayBuffer,f=a.DataView,d=l.prototype.slice,h=i(function(){return!new l(2).slice(1,void 0).byteLength});r({target:"ArrayBuffer",proto:!0,unsafe:!0,forced:h},{slice:function(e,t){if(void 0!==d&&void 0===t)return d.call(o(this),e);for(var n=o(this).byteLength,r=s(e,n),i=s(void 0===t?n:t,n),a=new(c(this,l))(u(i-r)),h=new f(this),p=new f(a),b=0;r=0?r:n+r;return s<0||s>=n?void 0:t[s]}}),s("at")},92222(e,t,n){"use strict";var r=n(82109),i=n(47293),a=n(43157),o=n(70111),s=n(47908),u=n(17466),c=n(86135),l=n(65417),f=n(81194),d=n(5112),h=n(7392),p=d("isConcatSpreadable"),b=9007199254740991,m="Maximum allowed index exceeded",g=h>=51||!i(function(){var e=[];return e[p]=!1,e.concat()[0]!==e}),v=f("concat"),y=function(e){if(!o(e))return!1;var t=e[p];return void 0!==t?!!t:a(e)};r({target:"Array",proto:!0,forced:!g||!v},{concat:function(e){var t,n,r,i,a,o=s(this),f=l(o,0),d=0;for(t=-1,r=arguments.length;tb)throw TypeError(m);for(n=0;n=b)throw TypeError(m);c(f,d++,a)}return f.length=d,f}})},50545(e,t,n){var r=n(82109),i=n(1048),a=n(51223);r({target:"Array",proto:!0},{copyWithin:i}),a("copyWithin")},26541(e,t,n){"use strict";var r=n(82109),i=n(42092).every,a=n(9341)("every");r({target:"Array",proto:!0,forced:!a},{every:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}})},43290(e,t,n){var r=n(82109),i=n(21285),a=n(51223);r({target:"Array",proto:!0},{fill:i}),a("fill")},57327(e,t,n){"use strict";var r=n(82109),i=n(42092).filter,a=n(81194)("filter");r({target:"Array",proto:!0,forced:!a},{filter:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}})},34553(e,t,n){"use strict";var r=n(82109),i=n(42092).findIndex,a=n(51223),o="findIndex",s=!0;o in[]&&[,][o](function(){s=!1}),r({target:"Array",proto:!0,forced:s},{findIndex:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}}),a(o)},69826(e,t,n){"use strict";var r=n(82109),i=n(42092).find,a=n(51223),o="find",s=!0;o in[]&&[,][o](function(){s=!1}),r({target:"Array",proto:!0,forced:s},{find:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}}),a(o)},86535(e,t,n){"use strict";var r=n(82109),i=n(6790),a=n(47908),o=n(17466),s=n(13099),u=n(65417);r({target:"Array",proto:!0},{flatMap:function(e){var t,n=a(this),r=o(n.length);return s(e),(t=u(n,0)).length=i(t,n,n,r,0,1,e,arguments.length>1?arguments[1]:void 0),t}})},84944(e,t,n){"use strict";var r=n(82109),i=n(6790),a=n(47908),o=n(17466),s=n(99958),u=n(65417);r({target:"Array",proto:!0},{flat:function(){var e=arguments.length?arguments[0]:void 0,t=a(this),n=o(t.length),r=u(t,0);return r.length=i(r,t,t,n,0,void 0===e?1:s(e)),r}})},89554(e,t,n){"use strict";var r=n(82109),i=n(18533);r({target:"Array",proto:!0,forced:[].forEach!=i},{forEach:i})},91038(e,t,n){var r=n(82109),i=n(48457),a=!n(17072)(function(e){Array.from(e)});r({target:"Array",stat:!0,forced:a},{from:i})},26699(e,t,n){"use strict";var r=n(82109),i=n(41318).includes,a=n(51223);r({target:"Array",proto:!0},{includes:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}}),a("includes")},82772(e,t,n){"use strict";var r=n(82109),i=n(41318).indexOf,a=n(9341),o=[].indexOf,s=!!o&&1/[1].indexOf(1,-0)<0,u=a("indexOf");r({target:"Array",proto:!0,forced:s||!u},{indexOf:function(e){return s?o.apply(this,arguments)||0:i(this,e,arguments.length>1?arguments[1]:void 0)}})},79753(e,t,n){var r=n(82109),i=n(43157);r({target:"Array",stat:!0},{isArray:i})},66992(e,t,n){"use strict";var r=n(45656),i=n(51223),a=n(97497),o=n(29909),s=n(70654),u="Array Iterator",c=o.set,l=o.getterFor(u);e.exports=s(Array,"Array",function(e,t){c(this,{type:u,target:r(e),index:0,kind:t})},function(){var e=l(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:t[r],done:!1}:{value:[r,t[r]],done:!1}},"values"),a.Arguments=a.Array,i("keys"),i("values"),i("entries")},69600(e,t,n){"use strict";var r=n(82109),i=n(68361),a=n(45656),o=n(9341),s=[].join,u=i!=Object,c=o("join",",");r({target:"Array",proto:!0,forced:u||!c},{join:function(e){return s.call(a(this),void 0===e?",":e)}})},94986(e,t,n){var r=n(82109),i=n(86583);r({target:"Array",proto:!0,forced:i!==[].lastIndexOf},{lastIndexOf:i})},21249(e,t,n){"use strict";var r=n(82109),i=n(42092).map,a=n(81194)("map");r({target:"Array",proto:!0,forced:!a},{map:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}})},26572(e,t,n){"use strict";var r=n(82109),i=n(47293),a=n(86135),o=i(function(){function e(){}return!(Array.of.call(e) instanceof e)});r({target:"Array",stat:!0,forced:o},{of:function(){for(var e=0,t=arguments.length,n=new("function"==typeof this?this:Array)(t);t>e;)a(n,e,arguments[e++]);return n.length=t,n}})},96644(e,t,n){"use strict";var r=n(82109),i=n(53671).right,a=n(9341),o=n(7392),s=n(35268),u=a("reduceRight"),c=!s&&o>79&&o<83;r({target:"Array",proto:!0,forced:!u||c},{reduceRight:function(e){return i(this,e,arguments.length,arguments.length>1?arguments[1]:void 0)}})},85827(e,t,n){"use strict";var r=n(82109),i=n(53671).left,a=n(9341),o=n(7392),s=n(35268),u=a("reduce"),c=!s&&o>79&&o<83;r({target:"Array",proto:!0,forced:!u||c},{reduce:function(e){return i(this,e,arguments.length,arguments.length>1?arguments[1]:void 0)}})},65069(e,t,n){"use strict";var r=n(82109),i=n(43157),a=[].reverse,o=[1,2];r({target:"Array",proto:!0,forced:String(o)===String(o.reverse())},{reverse:function(){return i(this)&&(this.length=this.length),a.call(this)}})},47042(e,t,n){"use strict";var r=n(82109),i=n(70111),a=n(43157),o=n(51400),s=n(17466),u=n(45656),c=n(86135),l=n(5112),f=n(81194)("slice"),d=l("species"),h=[].slice,p=Math.max;r({target:"Array",proto:!0,forced:!f},{slice:function(e,t){var n,r,l,f=u(this),b=s(f.length),m=o(e,b),g=o(void 0===t?b:t,b);if(a(f)&&("function"==typeof(n=f.constructor)&&(n===Array||a(n.prototype))?n=void 0:i(n)&&null===(n=n[d])&&(n=void 0),n===Array||void 0===n))return h.call(f,m,g);for(l=0,r=new(void 0===n?Array:n)(p(g-m,0));m1?arguments[1]:void 0)}})},2707(e,t,n){"use strict";var r=n(82109),i=n(13099),a=n(47908),o=n(17466),s=n(41340),u=n(47293),c=n(94362),l=n(9341),f=n(68886),d=n(30256),h=n(7392),p=n(98008),b=[],m=b.sort,g=u(function(){b.sort(void 0)}),v=u(function(){b.sort(null)}),y=l("sort"),w=!u(function(){if(h)return h<70;if(!f||!(f>3)){if(d)return!0;if(p)return p<603;var e,t,n,r,i="";for(e=65;e<76;e++){switch(t=String.fromCharCode(e),e){case 66:case 69:case 70:case 72:n=3;break;case 68:case 71:n=4;break;default:n=2}for(r=0;r<47;r++)b.push({k:t+r,v:n})}for(b.sort(function(e,t){return t.v-e.v}),r=0;rs(n)?1:-1}};r({target:"Array",proto:!0,forced:_},{sort:function(e){void 0!==e&&i(e);var t,n,r=a(this);if(w)return void 0===e?m.call(r):m.call(r,e);var s=[],u=o(r.length);for(n=0;nh)throw TypeError(p);for(b=0,l=u(v,r);by-r+n;b--)delete v[b-1]}else if(n>r)for(b=y-r;b>w;b--)m=b+r-1,g=b+n-1,m in v?v[g]=v[m]:delete v[g];for(b=0;b94906265.62425156?s(e)+c:a(e-1+u(e-1)*u(e+1))}})},82376(e,t,n){var r=n(82109),i=Math.asinh,a=Math.log,o=Math.sqrt;function s(e){return isFinite(e=+e)&&0!=e?e<0?-s(-e):a(e+o(e*e+1)):e}r({target:"Math",stat:!0,forced:!(i&&1/i(0)>0)},{asinh:s})},73181(e,t,n){var r=n(82109),i=Math.atanh,a=Math.log;r({target:"Math",stat:!0,forced:!(i&&1/i(-0)<0)},{atanh:function(e){return 0==(e=+e)?e:a((1+e)/(1-e))/2}})},23484(e,t,n){var r=n(82109),i=n(64310),a=Math.abs,o=Math.pow;r({target:"Math",stat:!0},{cbrt:function(e){return i(e=+e)*o(a(e),1/3)}})},2388(e,t,n){var r=n(82109),i=Math.floor,a=Math.log,o=Math.LOG2E;r({target:"Math",stat:!0},{clz32:function(e){return(e>>>=0)?31-i(a(e+.5)*o):32}})},88621(e,t,n){var r=n(82109),i=n(66736),a=Math.cosh,o=Math.abs,s=Math.E;r({target:"Math",stat:!0,forced:!a||a(710)===1/0},{cosh:function(e){var t=i(o(e)-1)+1;return(t+1/(t*s*s))*(s/2)}})},60403(e,t,n){var r=n(82109),i=n(66736);r({target:"Math",stat:!0,forced:i!=Math.expm1},{expm1:i})},84755(e,t,n){var r=n(82109),i=n(26130);r({target:"Math",stat:!0},{fround:i})},25438(e,t,n){var r=n(82109),i=Math.hypot,a=Math.abs,o=Math.sqrt,s=!!i&&i(1/0,NaN)!==1/0;r({target:"Math",stat:!0,forced:s},{hypot:function(e,t){for(var n,r,i=0,s=0,u=arguments.length,c=0;s0?i+=(r=n/c)*r:i+=n;return c===1/0?1/0:c*o(i)}})},90332(e,t,n){var r=n(82109),i=n(47293),a=Math.imul,o=i(function(){return -5!=a(4294967295,5)||2!=a.length});r({target:"Math",stat:!0,forced:o},{imul:function(e,t){var n=65535,r=+e,i=+t,a=n&r,o=n&i;return 0|a*o+((n&r>>>16)*o+a*(n&i>>>16)<<16>>>0)}})},40658(e,t,n){var r=n(82109),i=Math.log,a=Math.LOG10E;r({target:"Math",stat:!0},{log10:function(e){return i(e)*a}})},40197(e,t,n){var r=n(82109),i=n(26513);r({target:"Math",stat:!0},{log1p:i})},44914(e,t,n){var r=n(82109),i=Math.log,a=Math.LN2;r({target:"Math",stat:!0},{log2:function(e){return i(e)/a}})},52420(e,t,n){var r=n(82109),i=n(64310);r({target:"Math",stat:!0},{sign:i})},60160(e,t,n){var r=n(82109),i=n(47293),a=n(66736),o=Math.abs,s=Math.exp,u=Math.E,c=i(function(){return -.00000000000000002!=Math.sinh(-.00000000000000002)});r({target:"Math",stat:!0,forced:c},{sinh:function(e){return 1>o(e=+e)?(a(e)-a(-e))/2:(s(e-1)-s(-e-1))*(u/2)}})},60970(e,t,n){var r=n(82109),i=n(66736),a=Math.exp;r({target:"Math",stat:!0},{tanh:function(e){var t=i(e=+e),n=i(-e);return t==1/0?1:n==1/0?-1:(t-n)/(a(e)+a(-e))}})},10408(e,t,n){n(58003)(Math,"Math",!0)},73689(e,t,n){var r=n(82109),i=Math.ceil,a=Math.floor;r({target:"Math",stat:!0},{trunc:function(e){return(e>0?a:i)(e)}})},9653(e,t,n){"use strict";var r=n(19781),i=n(17854),a=n(54705),o=n(31320),s=n(86656),u=n(84326),c=n(79587),l=n(52190),f=n(57593),d=n(47293),h=n(70030),p=n(8006).f,b=n(31236).f,m=n(3070).f,g=n(53111).trim,v="Number",y=i[v],w=y.prototype,_=u(h(w))==v,E=function(e){if(l(e))throw TypeError("Cannot convert a Symbol value to a number");var t,n,r,i,a,o,s,u,c=f(e,"number");if("string"==typeof c&&c.length>2){if(43===(t=(c=g(c)).charCodeAt(0))||45===t){if(88===(n=c.charCodeAt(2))||120===n)return NaN}else if(48===t){switch(c.charCodeAt(1)){case 66:case 98:r=2,i=49;break;case 79:case 111:r=8,i=55;break;default:return+c}for(s=0,o=(a=c.slice(2)).length;si)return NaN;return parseInt(a,r)}}return+c};if(a(v,!y(" 0o1")||!y("0b1")||y("+0x1"))){for(var S,k=function(e){var t=arguments.length<1?0:e,n=this;return n instanceof k&&(_?d(function(){w.valueOf.call(n)}):u(n)!=v)?c(new y(E(t)),n,k):E(t)},x=r?p(y):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger,fromString,range".split(","),T=0;x.length>T;T++)s(y,S=x[T])&&!s(k,S)&&m(k,S,b(y,S));k.prototype=w,w.constructor=k,o(i,v,k)}},93299(e,t,n){n(82109)({target:"Number",stat:!0},{EPSILON:2220446049250313e-31})},35192(e,t,n){var r=n(82109),i=n(77023);r({target:"Number",stat:!0},{isFinite:i})},33161(e,t,n){var r=n(82109),i=n(18730);r({target:"Number",stat:!0},{isInteger:i})},44048(e,t,n){n(82109)({target:"Number",stat:!0},{isNaN:function(e){return e!=e}})},78285(e,t,n){var r=n(82109),i=n(18730),a=Math.abs;r({target:"Number",stat:!0},{isSafeInteger:function(e){return i(e)&&9007199254740991>=a(e)}})},44363(e,t,n){n(82109)({target:"Number",stat:!0},{MAX_SAFE_INTEGER:9007199254740991})},55994(e,t,n){n(82109)({target:"Number",stat:!0},{MIN_SAFE_INTEGER:-9007199254740991})},61874(e,t,n){var r=n(82109),i=n(2814);r({target:"Number",stat:!0,forced:Number.parseFloat!=i},{parseFloat:i})},9494(e,t,n){var r=n(82109),i=n(83009);r({target:"Number",stat:!0,forced:Number.parseInt!=i},{parseInt:i})},56977(e,t,n){"use strict";var r=n(82109),i=n(99958),a=n(50863),o=n(38415),s=n(47293),u=1..toFixed,c=Math.floor,l=function(e,t,n){return 0===t?n:t%2==1?l(e,t-1,n*e):l(e*e,t/2,n)},f=function(e){for(var t=0,n=e;n>=4096;)t+=12,n/=4096;for(;n>=2;)t+=1,n/=2;return t},d=function(e,t,n){for(var r=-1,i=n;++r<6;)i+=t*e[r],e[r]=i%1e7,i=c(i/1e7)},h=function(e,t){for(var n=6,r=0;--n>=0;)r+=e[n],e[n]=c(r/t),r=r%t*1e7},p=function(e){for(var t=6,n="";--t>=0;)if(""!==n||0===t||0!==e[t]){var r=String(e[t]);n=""===n?r:n+o.call("0",7-r.length)+r}return n},b=!!u||!s(function(){u.call({})});r({target:"Number",proto:!0,forced:b},{toFixed:function(e){var t,n,r,s,u=a(this),c=i(e),b=[0,0,0,0,0,0],m="",g="0";if(c<0||c>20)throw RangeError("Incorrect fraction digits");if(u!=u)return"NaN";if(u<=-1e21||u>=1e21)return String(u);if(u<0&&(m="-",u=-u),u>1e-21){if(n=(t=f(u*l(2,69,1))-69)<0?u*l(2,-t,1):u/l(2,t,1),n*=4503599627370496,(t=52-t)>0){for(d(b,0,n),r=c;r>=7;)d(b,1e7,0),r-=7;for(d(b,l(10,r,1),0),r=t-1;r>=23;)h(b,8388608),r-=23;h(b,1<0?m+((s=g.length)<=c?"0."+o.call("0",c-s)+g:g.slice(0,s-c)+"."+g.slice(s-c)):m+g}})},55147(e,t,n){"use strict";var r=n(82109),i=n(47293),a=n(50863),o=1..toPrecision,s=i(function(){return"1"!==o.call(1,void 0)})||!i(function(){o.call({})});r({target:"Number",proto:!0,forced:s},{toPrecision:function(e){return void 0===e?o.call(a(this)):o.call(a(this),e)}})},19601(e,t,n){var r=n(82109),i=n(21574);r({target:"Object",stat:!0,forced:Object.assign!==i},{assign:i})},78011(e,t,n){var r=n(82109),i=n(19781),a=n(70030);r({target:"Object",stat:!0,sham:!i},{create:a})},59595(e,t,n){"use strict";var r=n(82109),i=n(19781),a=n(69026),o=n(47908),s=n(13099),u=n(3070);i&&r({target:"Object",proto:!0,forced:a},{__defineGetter__:function(e,t){u.f(o(this),e,{get:s(t),enumerable:!0,configurable:!0})}})},33321(e,t,n){var r=n(82109),i=n(19781),a=n(36048);r({target:"Object",stat:!0,forced:!i,sham:!i},{defineProperties:a})},69070(e,t,n){var r=n(82109),i=n(19781),a=n(3070);r({target:"Object",stat:!0,forced:!i,sham:!i},{defineProperty:a.f})},35500(e,t,n){"use strict";var r=n(82109),i=n(19781),a=n(69026),o=n(47908),s=n(13099),u=n(3070);i&&r({target:"Object",proto:!0,forced:a},{__defineSetter__:function(e,t){u.f(o(this),e,{set:s(t),enumerable:!0,configurable:!0})}})},69720(e,t,n){var r=n(82109),i=n(44699).entries;r({target:"Object",stat:!0},{entries:function(e){return i(e)}})},43371(e,t,n){var r=n(82109),i=n(76677),a=n(47293),o=n(70111),s=n(62423).onFreeze,u=Object.freeze,c=a(function(){u(1)});r({target:"Object",stat:!0,forced:c,sham:!i},{freeze:function(e){return u&&o(e)?u(s(e)):e}})},38559(e,t,n){var r=n(82109),i=n(20408),a=n(86135);r({target:"Object",stat:!0},{fromEntries:function(e){var t={};return i(e,function(e,n){a(t,e,n)},{AS_ENTRIES:!0}),t}})},38880(e,t,n){var r=n(82109),i=n(47293),a=n(45656),o=n(31236).f,s=n(19781),u=i(function(){o(1)}),c=!s||u;r({target:"Object",stat:!0,forced:c,sham:!s},{getOwnPropertyDescriptor:function(e,t){return o(a(e),t)}})},49337(e,t,n){var r=n(82109),i=n(19781),a=n(53887),o=n(45656),s=n(31236),u=n(86135);r({target:"Object",stat:!0,sham:!i},{getOwnPropertyDescriptors:function(e){for(var t,n,r=o(e),i=s.f,c=a(r),l={},f=0;c.length>f;)void 0!==(n=i(r,t=c[f++]))&&u(l,t,n);return l}})},36210(e,t,n){var r=n(82109),i=n(47293),a=n(1156).f,o=i(function(){return!Object.getOwnPropertyNames(1)});r({target:"Object",stat:!0,forced:o},{getOwnPropertyNames:a})},30489(e,t,n){var r=n(82109),i=n(47293),a=n(47908),o=n(79518),s=n(49920),u=i(function(){o(1)});r({target:"Object",stat:!0,forced:u,sham:!s},{getPrototypeOf:function(e){return o(a(e))}})},46314(e,t,n){var r=n(82109),i=n(86656);r({target:"Object",stat:!0},{hasOwn:i})},41825(e,t,n){var r=n(82109),i=n(47293),a=n(70111),o=Object.isExtensible,s=i(function(){o(1)});r({target:"Object",stat:!0,forced:s},{isExtensible:function(e){return!!a(e)&&(!o||o(e))}})},98410(e,t,n){var r=n(82109),i=n(47293),a=n(70111),o=Object.isFrozen,s=i(function(){o(1)});r({target:"Object",stat:!0,forced:s},{isFrozen:function(e){return!a(e)||!!o&&o(e)}})},72200(e,t,n){var r=n(82109),i=n(47293),a=n(70111),o=Object.isSealed,s=i(function(){o(1)});r({target:"Object",stat:!0,forced:s},{isSealed:function(e){return!a(e)||!!o&&o(e)}})},43304(e,t,n){var r=n(82109),i=n(81150);r({target:"Object",stat:!0},{is:i})},47941(e,t,n){var r=n(82109),i=n(47908),a=n(81956),o=n(47293)(function(){a(1)});r({target:"Object",stat:!0,forced:o},{keys:function(e){return a(i(e))}})},94869(e,t,n){"use strict";var r=n(82109),i=n(19781),a=n(69026),o=n(47908),s=n(34948),u=n(79518),c=n(31236).f;i&&r({target:"Object",proto:!0,forced:a},{__lookupGetter__:function(e){var t,n=o(this),r=s(e);do if(t=c(n,r))return t.get;while(n=u(n))}})},33952(e,t,n){"use strict";var r=n(82109),i=n(19781),a=n(69026),o=n(47908),s=n(34948),u=n(79518),c=n(31236).f;i&&r({target:"Object",proto:!0,forced:a},{__lookupSetter__:function(e){var t,n=o(this),r=s(e);do if(t=c(n,r))return t.set;while(n=u(n))}})},57227(e,t,n){var r=n(82109),i=n(70111),a=n(62423).onFreeze,o=n(76677),s=n(47293),u=Object.preventExtensions,c=s(function(){u(1)});r({target:"Object",stat:!0,forced:c,sham:!o},{preventExtensions:function(e){return u&&i(e)?u(a(e)):e}})},60514(e,t,n){var r=n(82109),i=n(70111),a=n(62423).onFreeze,o=n(76677),s=n(47293),u=Object.seal,c=s(function(){u(1)});r({target:"Object",stat:!0,forced:c,sham:!o},{seal:function(e){return u&&i(e)?u(a(e)):e}})},68304(e,t,n){var r=n(82109),i=n(27674);r({target:"Object",stat:!0},{setPrototypeOf:i})},41539(e,t,n){var r=n(51694),i=n(31320),a=n(90288);r||i(Object.prototype,"toString",a,{unsafe:!0})},26833(e,t,n){var r=n(82109),i=n(44699).values;r({target:"Object",stat:!0},{values:function(e){return i(e)}})},54678(e,t,n){var r=n(82109),i=n(2814);r({global:!0,forced:parseFloat!=i},{parseFloat:i})},91058(e,t,n){var r=n(82109),i=n(83009);r({global:!0,forced:parseInt!=i},{parseInt:i})},17922(e,t,n){"use strict";var r=n(82109),i=n(13099),a=n(78523),o=n(12534),s=n(20408);r({target:"Promise",stat:!0},{allSettled:function(e){var t=this,n=a.f(t),r=n.resolve,u=n.reject,c=o(function(){var n=i(t.resolve),a=[],o=0,u=1;s(e,function(e){var i=o++,s=!1;a.push(void 0),u++,n.call(t,e).then(function(e){!s&&(s=!0,a[i]={status:"fulfilled",value:e},--u||r(a))},function(e){!s&&(s=!0,a[i]={status:"rejected",reason:e},--u||r(a))})}),--u||r(a)});return c.error&&u(c.value),n.promise}})},34668(e,t,n){"use strict";var r=n(82109),i=n(13099),a=n(35005),o=n(78523),s=n(12534),u=n(20408),c="No one promise resolved";r({target:"Promise",stat:!0},{any:function(e){var t=this,n=o.f(t),r=n.resolve,l=n.reject,f=s(function(){var n=i(t.resolve),o=[],s=0,f=1,d=!1;u(e,function(e){var i=s++,u=!1;o.push(void 0),f++,n.call(t,e).then(function(e){u||d||(d=!0,r(e))},function(e){!u&&!d&&(u=!0,o[i]=e,--f||l(new(a("AggregateError"))(o,c)))})}),--f||l(new(a("AggregateError"))(o,c))});return f.error&&l(f.value),n.promise}})},17727(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(13366),o=n(47293),s=n(35005),u=n(36707),c=n(69478),l=n(31320),f=!!a&&o(function(){a.prototype.finally.call({then:function(){}},function(){})});if(r({target:"Promise",proto:!0,real:!0,forced:f},{finally:function(e){var t=u(this,s("Promise")),n="function"==typeof e;return this.then(n?function(n){return c(t,e()).then(function(){return n})}:e,n?function(n){return c(t,e()).then(function(){throw n})}:e)}}),!i&&"function"==typeof a){var d=s("Promise").prototype.finally;a.prototype.finally!==d&&l(a.prototype,"finally",d,{unsafe:!0})}},88674(e,t,n){"use strict";var r,i,a,o,s=n(82109),u=n(31913),c=n(17854),l=n(35005),f=n(13366),d=n(31320),h=n(12248),p=n(27674),b=n(58003),m=n(96340),g=n(70111),v=n(13099),y=n(25787),w=n(42788),_=n(20408),E=n(17072),S=n(36707),k=n(20261).set,x=n(95948),T=n(69478),M=n(842),O=n(78523),A=n(12534),L=n(29909),C=n(54705),I=n(5112),D=n(7871),N=n(35268),P=n(7392),R=I("species"),j="Promise",F=L.get,Y=L.set,B=L.getterFor(j),U=f&&f.prototype,H=f,$=U,z=c.TypeError,G=c.document,W=c.process,K=O.f,V=K,q=!!(G&&G.createEvent&&c.dispatchEvent),Z="function"==typeof PromiseRejectionEvent,X="unhandledrejection",J="rejectionhandled",Q=0,ee=1,et=2,en=1,er=2,ei=!1,ea=C(j,function(){var e=w(H),t=e!==String(H);if(!t&&66===P||u&&!$.finally)return!0;if(P>=51&&/native code/.test(e))return!1;var n=new H(function(e){e(1)}),r=function(e){e(function(){},function(){})};return(n.constructor={})[R]=r,!(ei=n.then(function(){}) instanceof r)||!t&&D&&!Z}),eo=ea||!E(function(e){H.all(e).catch(function(){})}),es=function(e){var t;return!!g(e)&&"function"==typeof(t=e.then)&&t},eu=function(e,t){if(!e.notified){e.notified=!0;var n=e.reactions;x(function(){for(var r=e.value,i=e.state==ee,a=0;n.length>a;){var o,s,u,c=n[a++],l=i?c.ok:c.fail,f=c.resolve,d=c.reject,h=c.domain;try{l?(i||(e.rejection===er&&ed(e),e.rejection=en),!0===l?o=r:(h&&h.enter(),o=l(r),h&&(h.exit(),u=!0)),o===c.promise?d(z("Promise-chain cycle")):(s=es(o))?s.call(o,f,d):f(o)):d(r)}catch(p){h&&!u&&h.exit(),d(p)}}e.reactions=[],e.notified=!1,t&&!e.rejection&&el(e)})}},ec=function(e,t,n){var r,i;q?((r=G.createEvent("Event")).promise=t,r.reason=n,r.initEvent(e,!1,!0),c.dispatchEvent(r)):r={promise:t,reason:n},!Z&&(i=c["on"+e])?i(r):e===X&&M("Unhandled promise rejection",n)},el=function(e){k.call(c,function(){var t,n=e.facade,r=e.value;if(ef(e)&&(t=A(function(){N?W.emit("unhandledRejection",r,n):ec(X,n,r)}),e.rejection=N||ef(e)?er:en,t.error))throw t.value})},ef=function(e){return e.rejection!==en&&!e.parent},ed=function(e){k.call(c,function(){var t=e.facade;N?W.emit("rejectionHandled",t):ec(J,t,e.value)})},eh=function(e,t,n){return function(r){e(t,r,n)}},ep=function(e,t,n){e.done||(e.done=!0,n&&(e=n),e.value=t,e.state=et,eu(e,!0))},eb=function(e,t,n){if(!e.done){e.done=!0,n&&(e=n);try{if(e.facade===t)throw z("Promise can't be resolved itself");var r=es(t);r?x(function(){var n={done:!1};try{r.call(t,eh(eb,n,e),eh(ep,n,e))}catch(i){ep(n,i,e)}}):(e.value=t,e.state=ee,eu(e,!1))}catch(i){ep({done:!1},i,e)}}};if(ea&&($=(H=function(e){y(this,H,j),v(e),r.call(this);var t=F(this);try{e(eh(eb,t),eh(ep,t))}catch(n){ep(t,n)}}).prototype,(r=function(e){Y(this,{type:j,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:Q,value:void 0})}).prototype=h($,{then:function(e,t){var n=B(this),r=K(S(this,H));return r.ok="function"!=typeof e||e,r.fail="function"==typeof t&&t,r.domain=N?W.domain:void 0,n.parent=!0,n.reactions.push(r),n.state!=Q&&eu(n,!1),r.promise},catch:function(e){return this.then(void 0,e)}}),i=function(){var e=new r,t=F(e);this.promise=e,this.resolve=eh(eb,t),this.reject=eh(ep,t)},O.f=K=function(e){return e===H||e===a?new i(e):V(e)},!u&&"function"==typeof f&&U!==Object.prototype)){o=U.then,ei||(d(U,"then",function(e,t){var n=this;return new H(function(e,t){o.call(n,e,t)}).then(e,t)},{unsafe:!0}),d(U,"catch",$.catch,{unsafe:!0}));try{delete U.constructor}catch(em){}p&&p(U,$)}s({global:!0,wrap:!0,forced:ea},{Promise:H}),b(H,j,!1,!0),m(j),a=l(j),s({target:j,stat:!0,forced:ea},{reject:function(e){var t=K(this);return t.reject.call(void 0,e),t.promise}}),s({target:j,stat:!0,forced:u||ea},{resolve:function(e){return T(u&&this===a?H:this,e)}}),s({target:j,stat:!0,forced:eo},{all:function(e){var t=this,n=K(t),r=n.resolve,i=n.reject,a=A(function(){var n=v(t.resolve),a=[],o=0,s=1;_(e,function(e){var u=o++,c=!1;a.push(void 0),s++,n.call(t,e).then(function(e){!c&&(c=!0,a[u]=e,--s||r(a))},i)}),--s||r(a)});return a.error&&i(a.value),n.promise},race:function(e){var t=this,n=K(t),r=n.reject,i=A(function(){var i=v(t.resolve);_(e,function(e){i.call(t,e).then(n.resolve,r)})});return i.error&&r(i.value),n.promise}})},36535(e,t,n){var r=n(82109),i=n(35005),a=n(13099),o=n(19670),s=n(47293),u=i("Reflect","apply"),c=Function.apply,l=!s(function(){u(function(){})});r({target:"Reflect",stat:!0,forced:l},{apply:function(e,t,n){return a(e),o(n),u?u(e,t,n):c.call(e,t,n)}})},12419(e,t,n){var r=n(82109),i=n(35005),a=n(13099),o=n(19670),s=n(70111),u=n(70030),c=n(27065),l=n(47293),f=i("Reflect","construct"),d=l(function(){function e(){}return!(f(function(){},[],e) instanceof e)}),h=!l(function(){f(function(){})}),p=d||h;r({target:"Reflect",stat:!0,forced:p,sham:p},{construct:function(e,t){a(e),o(t);var n=arguments.length<3?e:a(arguments[2]);if(h&&!d)return f(e,t,n);if(e==n){switch(t.length){case 0:return new e;case 1:return new e(t[0]);case 2:return new e(t[0],t[1]);case 3:return new e(t[0],t[1],t[2]);case 4:return new e(t[0],t[1],t[2],t[3])}var r=[null];return r.push.apply(r,t),new(c.apply(e,r))}var i=n.prototype,l=u(s(i)?i:Object.prototype),p=Function.apply.call(e,l,t);return s(p)?p:l}})},69596(e,t,n){var r=n(82109),i=n(19781),a=n(19670),o=n(34948),s=n(3070),u=n(47293)(function(){Reflect.defineProperty(s.f({},1,{value:1}),1,{value:2})});r({target:"Reflect",stat:!0,forced:u,sham:!i},{defineProperty:function(e,t,n){a(e);var r=o(t);a(n);try{return s.f(e,r,n),!0}catch(i){return!1}}})},52586(e,t,n){var r=n(82109),i=n(19670),a=n(31236).f;r({target:"Reflect",stat:!0},{deleteProperty:function(e,t){var n=a(i(e),t);return(!n||!!n.configurable)&&delete e[t]}})},95683(e,t,n){var r=n(82109),i=n(19781),a=n(19670),o=n(31236);r({target:"Reflect",stat:!0,sham:!i},{getOwnPropertyDescriptor:function(e,t){return o.f(a(e),t)}})},39361(e,t,n){var r=n(82109),i=n(19670),a=n(79518),o=n(49920);r({target:"Reflect",stat:!0,sham:!o},{getPrototypeOf:function(e){return a(i(e))}})},74819(e,t,n){var r=n(82109),i=n(70111),a=n(19670),o=n(45032),s=n(31236),u=n(79518);function c(e,t){var n,r,l=arguments.length<3?e:arguments[2];return a(e)===l?e[t]:(n=s.f(e,t))?o(n)?n.value:void 0===n.get?void 0:n.get.call(l):i(r=u(e))?c(r,t,l):void 0}r({target:"Reflect",stat:!0},{get:c})},51037(e,t,n){n(82109)({target:"Reflect",stat:!0},{has:function(e,t){return t in e}})},5898(e,t,n){var r=n(82109),i=n(19670),a=Object.isExtensible;r({target:"Reflect",stat:!0},{isExtensible:function(e){return i(e),!a||a(e)}})},67556(e,t,n){var r=n(82109),i=n(53887);r({target:"Reflect",stat:!0},{ownKeys:i})},14361(e,t,n){var r=n(82109),i=n(35005),a=n(19670),o=n(76677);r({target:"Reflect",stat:!0,sham:!o},{preventExtensions:function(e){a(e);try{var t=i("Object","preventExtensions");return t&&t(e),!0}catch(n){return!1}}})},39532(e,t,n){var r=n(82109),i=n(19670),a=n(96077),o=n(27674);o&&r({target:"Reflect",stat:!0},{setPrototypeOf:function(e,t){i(e),a(t);try{return o(e,t),!0}catch(n){return!1}}})},83593(e,t,n){var r=n(82109),i=n(19670),a=n(70111),o=n(45032),s=n(47293),u=n(3070),c=n(31236),l=n(79518),f=n(79114);function d(e,t,n){var r,s,h,p=arguments.length<4?e:arguments[3],b=c.f(i(e),t);if(!b){if(a(s=l(e)))return d(s,t,n,p);b=f(0)}if(o(b)){if(!1===b.writable||!a(p))return!1;if(r=c.f(p,t)){if(r.get||r.set||!1===r.writable)return!1;r.value=n,u.f(p,t,r)}else u.f(p,t,f(0,n))}else{if(void 0===(h=b.set))return!1;h.call(p,n)}return!0}var h=s(function(){var e=function(){},t=u.f(new e,"a",{configurable:!0});return!1!==Reflect.set(e.prototype,"a",1,t)});r({target:"Reflect",stat:!0,forced:h},{set:d})},81299(e,t,n){var r=n(82109),i=n(17854),a=n(58003);r({global:!0},{Reflect:{}}),a(i.Reflect,"Reflect",!0)},24603(e,t,n){var r=n(19781),i=n(17854),a=n(54705),o=n(79587),s=n(68880),u=n(3070).f,c=n(8006).f,l=n(47850),f=n(41340),d=n(67066),h=n(52999),p=n(31320),b=n(47293),m=n(86656),g=n(29909).enforce,v=n(96340),y=n(5112),w=n(9441),_=n(38173),E=y("match"),S=i.RegExp,k=S.prototype,x=/^\?<[^\s\d!#%&*+<=>@^][^\s!#%&*+<=>@^]*>/,T=/a/g,M=/a/g,O=new S(T)!==T,A=h.UNSUPPORTED_Y,L=r&&(!O||A||w||_||b(function(){return M[E]=!1,S(T)!=T||S(M)==M||"/a/i"!=S(T,"i")})),C=function(e){for(var t,n=e.length,r=0,i="",a=!1;r<=n;r++){if("\\"===(t=e.charAt(r))){i+=t+e.charAt(++r);continue}a||"."!==t?("["===t?a=!0:"]"===t&&(a=!1),i+=t):i+="[\\s\\S]"}return i},I=function(e){for(var t,n=e.length,r=0,i="",a=[],o={},s=!1,u=!1,c=0,l="";r<=n;r++){if("\\"===(t=e.charAt(r)))t+=e.charAt(++r);else if("]"===t)s=!1;else if(!s)switch(!0){case"["===t:s=!0;break;case"("===t:x.test(e.slice(r+1))&&(r+=2,u=!0),i+=t,c++;continue;case">"===t&&u:if(""===l||m(o,l))throw SyntaxError("Invalid capture group name");o[l]=!0,a.push([l,c]),u=!1,l="";continue}u?l+=t:i+=t}return[i,a]};if(a("RegExp",L)){for(var D=function(e,t){var n,r,i,a,u,c,h=this instanceof D,p=l(e),b=void 0===t,m=[],v=e;if(!h&&p&&b&&e.constructor===D)return e;if((p||e instanceof D)&&(e=e.source,b&&(t=("flags"in v)?v.flags:d.call(v))),e=void 0===e?"":f(e),t=void 0===t?"":f(t),v=e,w&&("dotAll"in T)&&(r=!!t&&t.indexOf("s")>-1)&&(t=t.replace(/s/g,"")),n=t,A&&("sticky"in T)&&(i=!!t&&t.indexOf("y")>-1)&&(t=t.replace(/y/g,"")),_&&(e=(a=I(e))[0],m=a[1]),u=o(S(e,t),h?this:k,D),(r||i||m.length)&&(c=g(u),r&&(c.dotAll=!0,c.raw=D(C(e),n)),i&&(c.sticky=!0),m.length&&(c.groups=m)),e!==v)try{s(u,"source",""===v?"(?:)":v)}catch(y){}return u},N=function(e){(e in D)||u(D,e,{configurable:!0,get:function(){return S[e]},set:function(t){S[e]=t}})},P=c(S),R=0;P.length>R;)N(P[R++]);k.constructor=D,D.prototype=k,p(i,"RegExp",D)}v("RegExp")},28450(e,t,n){var r=n(19781),i=n(9441),a=n(3070).f,o=n(29909).get,s=RegExp.prototype;r&&i&&a(s,"dotAll",{configurable:!0,get:function(){if(this!==s){if(this instanceof RegExp)return!!o(this).dotAll;throw TypeError("Incompatible receiver, RegExp required")}}})},74916(e,t,n){"use strict";var r=n(82109),i=n(22261);r({target:"RegExp",proto:!0,forced:/./.exec!==i},{exec:i})},92087(e,t,n){var r=n(19781),i=n(3070),a=n(67066),o=n(47293);r&&o(function(){return"sy"!==Object.getOwnPropertyDescriptor(RegExp.prototype,"flags").get.call({dotAll:!0,sticky:!0})})&&i.f(RegExp.prototype,"flags",{configurable:!0,get:a})},88386(e,t,n){var r=n(19781),i=n(52999).UNSUPPORTED_Y,a=n(3070).f,o=n(29909).get,s=RegExp.prototype;r&&i&&a(s,"sticky",{configurable:!0,get:function(){if(this!==s){if(this instanceof RegExp)return!!o(this).sticky;throw TypeError("Incompatible receiver, RegExp required")}}})},77601(e,t,n){"use strict";n(74916);var r,i,a=n(82109),o=n(70111),s=(r=!1,(i=/[ac]/).exec=function(){return r=!0,/./.exec.apply(this,arguments)},!0===i.test("abc")&&r),u=/./.test;a({target:"RegExp",proto:!0,forced:!s},{test:function(e){if("function"!=typeof this.exec)return u.call(this,e);var t=this.exec(e);if(null!==t&&!o(t))throw Error("RegExp exec method returned something other than an Object or null");return!!t}})},39714(e,t,n){"use strict";var r=n(31320),i=n(19670),a=n(41340),o=n(47293),s=n(67066),u="toString",c=RegExp.prototype,l=c[u],f=o(function(){return"/a/b"!=l.call({source:"a",flags:"b"})}),d=l.name!=u;(f||d)&&r(RegExp.prototype,u,function(){var e=i(this),t=a(e.source),n=e.flags,r=a(void 0===n&&e instanceof RegExp&&!("flags"in c)?s.call(e):n);return"/"+t+"/"+r},{unsafe:!0})},70189(e,t,n){"use strict";var r=n(77710),i=n(95631);e.exports=r("Set",function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}},i)},15218(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("anchor")},{anchor:function(e){return i(this,"a","name",e)}})},24506(e,t,n){"use strict";var r=n(82109),i=n(84488),a=n(99958),o=n(17466),s=n(41340),u=n(47293)(function(){return"\uD842"!=="𠮷".at(0)});r({target:"String",proto:!0,forced:u},{at:function(e){var t=s(i(this)),n=o(t.length),r=a(e),u=r>=0?r:n+r;return u<0||u>=n?void 0:t.charAt(u)}})},74475(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("big")},{big:function(){return i(this,"big","","")}})},57929(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("blink")},{blink:function(){return i(this,"blink","","")}})},50915(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("bold")},{bold:function(){return i(this,"b","","")}})},79841(e,t,n){"use strict";var r=n(82109),i=n(28710).codeAt;r({target:"String",proto:!0},{codePointAt:function(e){return i(this,e)}})},27852(e,t,n){"use strict";var r,i=n(82109),a=n(31236).f,o=n(17466),s=n(41340),u=n(3929),c=n(84488),l=n(84964),f=n(31913),d="".endsWith,h=Math.min,p=l("endsWith"),b=!f&&!p&&!!(r=a(String.prototype,"endsWith"))&&!r.writable;i({target:"String",proto:!0,forced:!b&&!p},{endsWith:function(e){var t=s(c(this));u(e);var n=arguments.length>1?arguments[1]:void 0,r=o(t.length),i=void 0===n?r:h(o(n),r),a=s(e);return d?d.call(t,a,i):t.slice(i-a.length,i)===a}})},29253(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("fixed")},{fixed:function(){return i(this,"tt","","")}})},42125(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("fontcolor")},{fontcolor:function(e){return i(this,"font","color",e)}})},78830(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("fontsize")},{fontsize:function(e){return i(this,"font","size",e)}})},94953(e,t,n){var r=n(82109),i=n(51400),a=String.fromCharCode,o=String.fromCodePoint;r({target:"String",stat:!0,forced:!!o&&1!=o.length},{fromCodePoint:function(e){for(var t,n=[],r=arguments.length,o=0;r>o;){if(t=+arguments[o++],i(t,1114111)!==t)throw RangeError(t+" is not a valid code point");n.push(t<65536?a(t):a(((t-=65536)>>10)+55296,t%1024+56320))}return n.join("")}})},32023(e,t,n){"use strict";var r=n(82109),i=n(3929),a=n(84488),o=n(41340),s=n(84964);r({target:"String",proto:!0,forced:!s("includes")},{includes:function(e){return!!~o(a(this)).indexOf(o(i(e)),arguments.length>1?arguments[1]:void 0)}})},58734(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("italics")},{italics:function(){return i(this,"i","","")}})},78783(e,t,n){"use strict";var r=n(28710).charAt,i=n(41340),a=n(29909),o=n(70654),s="String Iterator",u=a.set,c=a.getterFor(s);o(String,"String",function(e){u(this,{type:s,string:i(e),index:0})},function(){var e,t=c(this),n=t.string,i=t.index;return i>=n.length?{value:void 0,done:!0}:(e=r(n,i),t.index+=e.length,{value:e,done:!1})})},29254(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("link")},{link:function(e){return i(this,"a","href",e)}})},76373(e,t,n){"use strict";var r=n(82109),i=n(24994),a=n(84488),o=n(17466),s=n(41340),u=n(13099),c=n(19670),l=n(84326),f=n(47850),d=n(67066),h=n(68880),p=n(47293),b=n(5112),m=n(36707),g=n(31530),v=n(29909),y=n(31913),w=b("matchAll"),_="RegExp String",E=_+" Iterator",S=v.set,k=v.getterFor(E),x=RegExp.prototype,T=x.exec,M="".matchAll,O=!!M&&!p(function(){"a".matchAll(/./)}),A=function(e,t){var n,r=e.exec;if("function"==typeof r){if("object"!=typeof(n=r.call(e,t)))throw TypeError("Incorrect exec result");return n}return T.call(e,t)},L=i(function(e,t,n,r){S(this,{type:E,regexp:e,string:t,global:n,unicode:r,done:!1})},_,function(){var e=k(this);if(e.done)return{value:void 0,done:!0};var t=e.regexp,n=e.string,r=A(t,n);return null===r?{value:void 0,done:e.done=!0}:e.global?(""===s(r[0])&&(t.lastIndex=g(n,o(t.lastIndex),e.unicode)),{value:r,done:!1}):(e.done=!0,{value:r,done:!1})}),C=function(e){var t,n,r,i,a,u,l=c(this),f=s(e);return t=m(l,RegExp),void 0===(n=l.flags)&&l instanceof RegExp&&!("flags"in x)&&(n=d.call(l)),r=void 0===n?"":s(n),i=new t(t===RegExp?l.source:l,r),a=!!~r.indexOf("g"),u=!!~r.indexOf("u"),i.lastIndex=o(l.lastIndex),new L(i,f,a,u)};r({target:"String",proto:!0,forced:O},{matchAll:function(e){var t,n,r,i,o=a(this);if(null!=e){if(f(e)&&!~(t=s(a("flags"in x?e.flags:d.call(e)))).indexOf("g"))throw TypeError("`.matchAll` does not allow non-global regexes");if(O)return M.apply(o,arguments);if(void 0===(r=e[w])&&y&&"RegExp"==l(e)&&(r=C),null!=r)return u(r).call(e,o)}else if(O)return M.apply(o,arguments);return n=s(o),i=RegExp(e,"g"),y?C.call(i,n):i[w](n)}}),y||w in x||h(x,w,C)},4723(e,t,n){"use strict";var r=n(27007),i=n(19670),a=n(17466),o=n(41340),s=n(84488),u=n(31530),c=n(97651);r("match",function(e,t,n){return[function(t){var n=s(this),r=void 0==t?void 0:t[e];return void 0!==r?r.call(t,n):RegExp(t)[e](o(n))},function(e){var r,s=i(this),l=o(e),f=n(t,s,l);if(f.done)return f.value;if(!s.global)return c(s,l);var d=s.unicode;s.lastIndex=0;for(var h=[],p=0;null!==(r=c(s,l));){var b=o(r[0]);h[p]=b,""===b&&(s.lastIndex=u(l,a(s.lastIndex),d)),p++}return 0===p?null:h}]})},66528(e,t,n){"use strict";var r=n(82109),i=n(76650).end,a=n(54986);r({target:"String",proto:!0,forced:a},{padEnd:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}})},83112(e,t,n){"use strict";var r=n(82109),i=n(76650).start,a=n(54986);r({target:"String",proto:!0,forced:a},{padStart:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}})},38992(e,t,n){var r=n(82109),i=n(45656),a=n(17466),o=n(41340);r({target:"String",stat:!0},{raw:function(e){for(var t=i(e.raw),n=a(t.length),r=arguments.length,s=[],u=0;n>u;)s.push(o(t[u++])),ue.length?-1:""===t?n:e.indexOf(t,n)};r({target:"String",proto:!0},{replaceAll:function(e,t){var n,r,c,b,m,g,v,y,w,_=i(this),E=0,S=0,k="";if(null!=e){if((n=a(e))&&!~(r=o(i("flags"in d?e.flags:s.call(e)))).indexOf("g"))throw TypeError("`.replaceAll` does not allow non-global regexes");if(void 0!==(c=e[f]))return c.call(e,_,t);if(l&&n)return o(_).replace(e,t)}for(b=o(_),m=o(e),(g="function"==typeof t)||(t=o(t)),y=h(1,v=m.length),E=p(b,m,0);-1!==E;)w=g?o(t(m,E,b)):u(m,b,E,[],void 0,t),k+=b.slice(S,E)+w,S=E+v,E=p(b,m,E+y);return S")});r("replace",function(e,t,n){var r=v?"$":"$0";return[function(e,n){var r=c(this),i=void 0==e?void 0:e[h];return void 0!==i?i.call(e,r,n):t.call(u(r),e,n)},function(e,i){var c=a(this),h=u(e);if("string"==typeof i&&-1===i.indexOf(r)&&-1===i.indexOf("$<")){var g=n(t,c,h,i);if(g.done)return g.value}var v="function"==typeof i;v||(i=u(i));var y=c.global;if(y){var w=c.unicode;c.lastIndex=0}for(var _=[];;){var E=d(c,h);if(null===E||(_.push(E),!y))break;""===u(E[0])&&(c.lastIndex=l(h,s(c.lastIndex),w))}for(var S="",k=0,x=0;x<_.length;x++){for(var T=u((E=_[x])[0]),M=p(b(o(E.index),h.length),0),O=[],A=1;A=k&&(S+=h.slice(k,M)+I,k=M+T.length)}return S+h.slice(k)}]},!y||!g||v)},64765(e,t,n){"use strict";var r=n(27007),i=n(19670),a=n(84488),o=n(81150),s=n(41340),u=n(97651);r("search",function(e,t,n){return[function(t){var n=a(this),r=void 0==t?void 0:t[e];return void 0!==r?r.call(t,n):RegExp(t)[e](s(n))},function(e){var r=i(this),a=s(e),c=n(t,r,a);if(c.done)return c.value;var l=r.lastIndex;o(l,0)||(r.lastIndex=0);var f=u(r,a);return o(r.lastIndex,l)||(r.lastIndex=l),null===f?-1:f.index}]})},37268(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("small")},{small:function(){return i(this,"small","","")}})},23123(e,t,n){"use strict";var r=n(27007),i=n(47850),a=n(19670),o=n(84488),s=n(36707),u=n(31530),c=n(17466),l=n(41340),f=n(97651),d=n(22261),h=n(52999),p=n(47293),b=h.UNSUPPORTED_Y,m=[].push,g=Math.min,v=4294967295,y=!p(function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var n="ab".split(e);return 2!==n.length||"a"!==n[0]||"b"!==n[1]});r("split",function(e,t,n){var r;return r="c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length?function(e,n){var r,a,s,u=l(o(this)),c=void 0===n?v:n>>>0;if(0===c)return[];if(void 0===e)return[u];if(!i(e))return t.call(u,e,c);for(var f=[],h=(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.unicode?"u":"")+(e.sticky?"y":""),p=0,b=RegExp(e.source,h+"g");(r=d.call(b,u))&&(!((a=b.lastIndex)>p)||(f.push(u.slice(p,r.index)),r.length>1&&r.index=c)));)b.lastIndex===r.index&&b.lastIndex++;return p===u.length?(s||!b.test(""))&&f.push(""):f.push(u.slice(p)),f.length>c?f.slice(0,c):f}:"0".split(void 0,0).length?function(e,n){return void 0===e&&0===n?[]:t.call(this,e,n)}:t,[function(t,n){var i=o(this),a=void 0==t?void 0:t[e];return void 0!==a?a.call(t,i,n):r.call(l(i),t,n)},function(e,i){var o=a(this),d=l(e),h=n(r,o,d,i,r!==t);if(h.done)return h.value;var p=s(o,RegExp),m=o.unicode,y=(o.ignoreCase?"i":"")+(o.multiline?"m":"")+(o.unicode?"u":"")+(b?"g":"y"),w=new p(b?"^(?:"+o.source+")":o,y),_=void 0===i?v:i>>>0;if(0===_)return[];if(0===d.length)return null===f(w,d)?[d]:[];for(var E=0,S=0,k=[];S1?arguments[1]:void 0,t.length)),r=s(e);return d?d.call(t,r,n):t.slice(n,n+r.length)===r}})},7397(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("strike")},{strike:function(){return i(this,"strike","","")}})},60086(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("sub")},{sub:function(){return i(this,"sub","","")}})},83650(e,t,n){"use strict";var r=n(82109),i=n(84488),a=n(99958),o=n(41340),s="".slice,u=Math.max,c=Math.min;r({target:"String",proto:!0},{substr:function(e,t){var n,r,l=o(i(this)),f=l.length,d=a(e);return(d===1/0&&(d=0),d<0&&(d=u(f+d,0)),(n=void 0===t?f:a(t))<=0||n===1/0)?"":(r=c(d+n,f),d>=r?"":s.call(l,d,r))}})},80623(e,t,n){"use strict";var r=n(82109),i=n(14230),a=n(43429);r({target:"String",proto:!0,forced:a("sup")},{sup:function(){return i(this,"sup","","")}})},48702(e,t,n){"use strict";var r=n(82109),i=n(53111).end,a=n(76091)("trimEnd"),o=a?function(){return i(this)}:"".trimEnd;r({target:"String",proto:!0,forced:a},{trimEnd:o,trimRight:o})},55674(e,t,n){"use strict";var r=n(82109),i=n(53111).start,a=n(76091)("trimStart"),o=a?function(){return i(this)}:"".trimStart;r({target:"String",proto:!0,forced:a},{trimStart:o,trimLeft:o})},73210(e,t,n){"use strict";var r=n(82109),i=n(53111).trim,a=n(76091);r({target:"String",proto:!0,forced:a("trim")},{trim:function(){return i(this)}})},72443(e,t,n){n(97235)("asyncIterator")},41817(e,t,n){"use strict";var r=n(82109),i=n(19781),a=n(17854),o=n(86656),s=n(70111),u=n(3070).f,c=n(99920),l=a.Symbol;if(i&&"function"==typeof l&&(!("description"in l.prototype)||void 0!==l().description)){var f={},d=function(){var e=arguments.length<1||void 0===arguments[0]?void 0:String(arguments[0]),t=this instanceof d?new l(e):void 0===e?l():l(e);return""===e&&(f[t]=!0),t};c(d,l);var h=d.prototype=l.prototype;h.constructor=d;var p=h.toString,b="Symbol(test)"==String(l("test")),m=/^Symbol\((.*)\)[^)]+$/;u(h,"description",{configurable:!0,get:function(){var e=s(this)?this.valueOf():this,t=p.call(e);if(o(f,e))return"";var n=b?t.slice(7,-1):t.replace(m,"$1");return""===n?void 0:n}}),r({global:!0,forced:!0},{Symbol:d})}},92401(e,t,n){n(97235)("hasInstance")},8722(e,t,n){n(97235)("isConcatSpreadable")},32165(e,t,n){n(97235)("iterator")},82526(e,t,n){"use strict";var r=n(82109),i=n(17854),a=n(35005),o=n(31913),s=n(19781),u=n(30133),c=n(47293),l=n(86656),f=n(43157),d=n(70111),h=n(52190),p=n(19670),b=n(47908),m=n(45656),g=n(34948),v=n(41340),y=n(79114),w=n(70030),_=n(81956),E=n(8006),S=n(1156),k=n(25181),x=n(31236),T=n(3070),M=n(55296),O=n(68880),A=n(31320),L=n(72309),C=n(6200),I=n(3501),D=n(69711),N=n(5112),P=n(6061),R=n(97235),j=n(58003),F=n(29909),Y=n(42092).forEach,B=C("hidden"),U="Symbol",H="prototype",$=N("toPrimitive"),z=F.set,G=F.getterFor(U),W=Object[H],K=i.Symbol,V=a("JSON","stringify"),q=x.f,Z=T.f,X=S.f,J=M.f,Q=L("symbols"),ee=L("op-symbols"),et=L("string-to-symbol-registry"),en=L("symbol-to-string-registry"),er=L("wks"),ei=i.QObject,ea=!ei||!ei[H]||!ei[H].findChild,eo=s&&c(function(){return 7!=w(Z({},"a",{get:function(){return Z(this,"a",{value:7}).a}})).a})?function(e,t,n){var r=q(W,t);r&&delete W[t],Z(e,t,n),r&&e!==W&&Z(W,t,r)}:Z,es=function(e,t){var n=Q[e]=w(K[H]);return z(n,{type:U,tag:e,description:t}),s||(n.description=t),n},eu=function(e,t,n){e===W&&eu(ee,t,n),p(e);var r=g(t);return(p(n),l(Q,r))?(n.enumerable?(l(e,B)&&e[B][r]&&(e[B][r]=!1),n=w(n,{enumerable:y(0,!1)})):(l(e,B)||Z(e,B,y(1,{})),e[B][r]=!0),eo(e,r,n)):Z(e,r,n)},ec=function(e,t){p(e);var n=m(t),r=_(n).concat(ep(n));return Y(r,function(t){(!s||ef.call(n,t))&&eu(e,t,n[t])}),e},el=function(e,t){return void 0===t?w(e):ec(w(e),t)},ef=function(e){var t=g(e),n=J.call(this,t);return(!(this===W&&l(Q,t))||!!l(ee,t))&&(!(n||!l(this,t)||!l(Q,t)||l(this,B)&&this[B][t])||n)},ed=function(e,t){var n=m(e),r=g(t);if(!(n===W&&l(Q,r))||l(ee,r)){var i=q(n,r);return i&&l(Q,r)&&!(l(n,B)&&n[B][r])&&(i.enumerable=!0),i}},eh=function(e){var t=X(m(e)),n=[];return Y(t,function(e){l(Q,e)||l(I,e)||n.push(e)}),n},ep=function(e){var t=e===W,n=X(t?ee:m(e)),r=[];return Y(n,function(e){l(Q,e)&&(!t||l(W,e))&&r.push(Q[e])}),r};if(u||(A((K=function(){if(this instanceof K)throw TypeError("Symbol is not a constructor");var e=arguments.length&&void 0!==arguments[0]?v(arguments[0]):void 0,t=D(e),n=function(e){this===W&&n.call(ee,e),l(this,B)&&l(this[B],t)&&(this[B][t]=!1),eo(this,t,y(1,e))};return s&&ea&&eo(W,t,{configurable:!0,set:n}),es(t,e)})[H],"toString",function(){return G(this).tag}),A(K,"withoutSetter",function(e){return es(D(e),e)}),M.f=ef,T.f=eu,x.f=ed,E.f=S.f=eh,k.f=ep,P.f=function(e){return es(N(e),e)},s&&(Z(K[H],"description",{configurable:!0,get:function(){return G(this).description}}),o||A(W,"propertyIsEnumerable",ef,{unsafe:!0}))),r({global:!0,wrap:!0,forced:!u,sham:!u},{Symbol:K}),Y(_(er),function(e){R(e)}),r({target:U,stat:!0,forced:!u},{for:function(e){var t=v(e);if(l(et,t))return et[t];var n=K(t);return et[t]=n,en[n]=t,n},keyFor:function(e){if(!h(e))throw TypeError(e+" is not a symbol");if(l(en,e))return en[e]},useSetter:function(){ea=!0},useSimple:function(){ea=!1}}),r({target:"Object",stat:!0,forced:!u,sham:!s},{create:el,defineProperty:eu,defineProperties:ec,getOwnPropertyDescriptor:ed}),r({target:"Object",stat:!0,forced:!u},{getOwnPropertyNames:eh,getOwnPropertySymbols:ep}),r({target:"Object",stat:!0,forced:c(function(){k.f(1)})},{getOwnPropertySymbols:function(e){return k.f(b(e))}}),V){var eb=!u||c(function(){var e=K();return"[null]"!=V([e])||"{}"!=V({a:e})||"{}"!=V(Object(e))});r({target:"JSON",stat:!0,forced:eb},{stringify:function(e,t,n){for(var r,i=[e],a=1;arguments.length>a;)i.push(arguments[a++]);if(r=t,!(!d(t)&&void 0===e||h(e)))return f(t)||(t=function(e,t){if("function"==typeof r&&(t=r.call(this,e,t)),!h(t))return t}),i[1]=t,V.apply(null,i)}})}K[H][$]||O(K[H],$,K[H].valueOf),j(K,U),I[B]=!0},16066(e,t,n){n(97235)("matchAll")},69007(e,t,n){n(97235)("match")},83510(e,t,n){n(97235)("replace")},41840(e,t,n){n(97235)("search")},6982(e,t,n){n(97235)("species")},32159(e,t,n){n(97235)("split")},96649(e,t,n){n(97235)("toPrimitive")},39341(e,t,n){n(97235)("toStringTag")},60543(e,t,n){n(97235)("unscopables")},48675(e,t,n){"use strict";var r=n(90260),i=n(17466),a=n(99958),o=r.aTypedArray;(0,r.exportTypedArrayMethod)("at",function(e){var t=o(this),n=i(t.length),r=a(e),s=r>=0?r:n+r;return s<0||s>=n?void 0:t[s]})},92990(e,t,n){"use strict";var r=n(90260),i=n(1048),a=r.aTypedArray;(0,r.exportTypedArrayMethod)("copyWithin",function(e,t){return i.call(a(this),e,t,arguments.length>2?arguments[2]:void 0)})},18927(e,t,n){"use strict";var r=n(90260),i=n(42092).every,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("every",function(e){return i(a(this),e,arguments.length>1?arguments[1]:void 0)})},33105(e,t,n){"use strict";var r=n(90260),i=n(21285),a=r.aTypedArray;(0,r.exportTypedArrayMethod)("fill",function(e){return i.apply(a(this),arguments)})},35035(e,t,n){"use strict";var r=n(90260),i=n(42092).filter,a=n(43074),o=r.aTypedArray;(0,r.exportTypedArrayMethod)("filter",function(e){var t=i(o(this),e,arguments.length>1?arguments[1]:void 0);return a(this,t)})},7174(e,t,n){"use strict";var r=n(90260),i=n(42092).findIndex,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("findIndex",function(e){return i(a(this),e,arguments.length>1?arguments[1]:void 0)})},74345(e,t,n){"use strict";var r=n(90260),i=n(42092).find,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("find",function(e){return i(a(this),e,arguments.length>1?arguments[1]:void 0)})},44197(e,t,n){n(19843)("Float32",function(e){return function(t,n,r){return e(this,t,n,r)}})},76495(e,t,n){n(19843)("Float64",function(e){return function(t,n,r){return e(this,t,n,r)}})},32846(e,t,n){"use strict";var r=n(90260),i=n(42092).forEach,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("forEach",function(e){i(a(this),e,arguments.length>1?arguments[1]:void 0)})},98145(e,t,n){"use strict";var r=n(63832),i=n(90260).exportTypedArrayStaticMethod,a=n(97321);i("from",a,r)},44731(e,t,n){"use strict";var r=n(90260),i=n(41318).includes,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("includes",function(e){return i(a(this),e,arguments.length>1?arguments[1]:void 0)})},77209(e,t,n){"use strict";var r=n(90260),i=n(41318).indexOf,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("indexOf",function(e){return i(a(this),e,arguments.length>1?arguments[1]:void 0)})},35109(e,t,n){n(19843)("Int16",function(e){return function(t,n,r){return e(this,t,n,r)}})},65125(e,t,n){n(19843)("Int32",function(e){return function(t,n,r){return e(this,t,n,r)}})},87145(e,t,n){n(19843)("Int8",function(e){return function(t,n,r){return e(this,t,n,r)}})},96319(e,t,n){"use strict";var r=n(17854),i=n(90260),a=n(66992),o=n(5112)("iterator"),s=r.Uint8Array,u=a.values,c=a.keys,l=a.entries,f=i.aTypedArray,d=i.exportTypedArrayMethod,h=s&&s.prototype[o],p=!!h&&("values"==h.name||void 0==h.name),b=function(){return u.call(f(this))};d("entries",function(){return l.call(f(this))}),d("keys",function(){return c.call(f(this))}),d("values",b,!p),d(o,b,!p)},58867(e,t,n){"use strict";var r=n(90260),i=r.aTypedArray,a=r.exportTypedArrayMethod,o=[].join;a("join",function(e){return o.apply(i(this),arguments)})},37789(e,t,n){"use strict";var r=n(90260),i=n(86583),a=r.aTypedArray;(0,r.exportTypedArrayMethod)("lastIndexOf",function(e){return i.apply(a(this),arguments)})},33739(e,t,n){"use strict";var r=n(90260),i=n(42092).map,a=n(66304),o=r.aTypedArray;(0,r.exportTypedArrayMethod)("map",function(e){return i(o(this),e,arguments.length>1?arguments[1]:void 0,function(e,t){return new(a(e))(t)})})},95206(e,t,n){"use strict";var r=n(90260),i=n(63832),a=r.aTypedArrayConstructor;(0,r.exportTypedArrayStaticMethod)("of",function(){for(var e=0,t=arguments.length,n=new(a(this))(t);t>e;)n[e]=arguments[e++];return n},i)},14483(e,t,n){"use strict";var r=n(90260),i=n(53671).right,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("reduceRight",function(e){return i(a(this),e,arguments.length,arguments.length>1?arguments[1]:void 0)})},29368(e,t,n){"use strict";var r=n(90260),i=n(53671).left,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("reduce",function(e){return i(a(this),e,arguments.length,arguments.length>1?arguments[1]:void 0)})},12056(e,t,n){"use strict";var r=n(90260),i=r.aTypedArray,a=r.exportTypedArrayMethod,o=Math.floor;a("reverse",function(){for(var e,t=this,n=i(t).length,r=o(n/2),a=0;a1?arguments[1]:void 0,1),n=this.length,r=o(e),s=i(r.length),c=0;if(s+t>n)throw RangeError("Wrong length");for(;ca;)c[a]=n[a++];return c},c)},27462(e,t,n){"use strict";var r=n(90260),i=n(42092).some,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("some",function(e){return i(a(this),e,arguments.length>1?arguments[1]:void 0)})},33824(e,t,n){"use strict";var r=n(90260),i=n(17854),a=n(47293),o=n(13099),s=n(17466),u=n(94362),c=n(68886),l=n(30256),f=n(7392),d=n(98008),h=r.aTypedArray,p=r.exportTypedArrayMethod,b=i.Uint16Array,m=b&&b.prototype.sort,g=!!m&&!a(function(){var e=new b(2);e.sort(null),e.sort({})}),v=!!m&&!a(function(){if(f)return f<74;if(c)return c<67;if(l)return!0;if(d)return d<602;var e,t,n=new b(516),r=Array(516);for(e=0;e<516;e++)t=e%4,n[e]=515-e,r[e]=e-2*t+3;for(n.sort(function(e,t){return(e/4|0)-(t/4|0)}),e=0;e<516;e++)if(n[e]!==r[e])return!0}),y=function(e){return function(t,n){return void 0!==e?+e(t,n)||0:n!=n?-1:t!=t?1:0===t&&0===n?1/t>0&&1/n<0?1:-1:t>n}};p("sort",function(e){var t,n=this;if(void 0!==e&&o(e),v)return m.call(n,e);h(n);var r=s(n.length),i=Array(r);for(t=0;t1?arguments[1]:void 0)}}),a("filterOut")},34286(e,t,n){"use strict";var r=n(82109),i=n(42092).filterReject,a=n(51223);r({target:"Array",proto:!0},{filterReject:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}}),a("filterReject")},77461(e,t,n){"use strict";var r=n(82109),i=n(9671).findLastIndex,a=n(51223);r({target:"Array",proto:!0},{findLastIndex:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}}),a("findLastIndex")},3048(e,t,n){"use strict";var r=n(82109),i=n(9671).findLast,a=n(51223);r({target:"Array",proto:!0},{findLast:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}}),a("findLast")},1999(e,t,n){"use strict";var r=n(82109),i=n(61386),a=n(77475),o=n(51223);r({target:"Array",proto:!0},{groupBy:function(e){var t=arguments.length>1?arguments[1]:void 0;return i(this,e,t,a)}}),o("groupBy")},8e4(e,t,n){var r=n(82109),i=n(43157),a=Object.isFrozen,o=function(e,t){if(!a||!i(e)||!a(e))return!1;for(var n,r=0,o=e.length;r1?arguments[1]:void 0,3);return!u(n,function(e,n,i){if(!r(n,e,t))return i()},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).stopped}})},71957(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(35005),o=n(19670),s=n(13099),u=n(49974),c=n(36707),l=n(54647),f=n(20408);r({target:"Map",proto:!0,real:!0,forced:i},{filter:function(e){var t=o(this),n=l(t),r=u(e,arguments.length>1?arguments[1]:void 0,3),i=new(c(t,a("Map"))),d=s(i.set);return f(n,function(e,n){r(n,e,t)&&d.call(i,e,n)},{AS_ENTRIES:!0,IS_ITERATOR:!0}),i}})},103(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(49974),s=n(54647),u=n(20408);r({target:"Map",proto:!0,real:!0,forced:i},{findKey:function(e){var t=a(this),n=s(t),r=o(e,arguments.length>1?arguments[1]:void 0,3);return u(n,function(e,n,i){if(r(n,e,t))return i(e)},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).result}})},96306(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(49974),s=n(54647),u=n(20408);r({target:"Map",proto:!0,real:!0,forced:i},{find:function(e){var t=a(this),n=s(t),r=o(e,arguments.length>1?arguments[1]:void 0,3);return u(n,function(e,n,i){if(r(n,e,t))return i(n)},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).result}})},8582(e,t,n){var r=n(82109),i=n(27296);r({target:"Map",stat:!0},{from:i})},90618(e,t,n){"use strict";var r=n(82109),i=n(20408),a=n(13099);r({target:"Map",stat:!0},{groupBy:function(e,t){var n=new this;a(t);var r=a(n.has),o=a(n.get),s=a(n.set);return i(e,function(e){var i=t(e);r.call(n,i)?o.call(n,i).push(e):s.call(n,i,[e])}),n}})},74592(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(54647),s=n(46465),u=n(20408);r({target:"Map",proto:!0,real:!0,forced:i},{includes:function(e){return u(o(a(this)),function(t,n,r){if(s(n,e))return r()},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).stopped}})},88440(e,t,n){"use strict";var r=n(82109),i=n(20408),a=n(13099);r({target:"Map",stat:!0},{keyBy:function(e,t){var n=new this;a(t);var r=a(n.set);return i(e,function(e){r.call(n,t(e),e)}),n}})},58276(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(54647),s=n(20408);r({target:"Map",proto:!0,real:!0,forced:i},{keyOf:function(e){return s(o(a(this)),function(t,n,r){if(n===e)return r(t)},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).result}})},35082(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(35005),o=n(19670),s=n(13099),u=n(49974),c=n(36707),l=n(54647),f=n(20408);r({target:"Map",proto:!0,real:!0,forced:i},{mapKeys:function(e){var t=o(this),n=l(t),r=u(e,arguments.length>1?arguments[1]:void 0,3),i=new(c(t,a("Map"))),d=s(i.set);return f(n,function(e,n){d.call(i,r(n,e,t),n)},{AS_ENTRIES:!0,IS_ITERATOR:!0}),i}})},12813(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(35005),o=n(19670),s=n(13099),u=n(49974),c=n(36707),l=n(54647),f=n(20408);r({target:"Map",proto:!0,real:!0,forced:i},{mapValues:function(e){var t=o(this),n=l(t),r=u(e,arguments.length>1?arguments[1]:void 0,3),i=new(c(t,a("Map"))),d=s(i.set);return f(n,function(e,n){d.call(i,e,r(n,e,t))},{AS_ENTRIES:!0,IS_ITERATOR:!0}),i}})},18222(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(13099),s=n(20408);r({target:"Map",proto:!0,real:!0,forced:i},{merge:function(e){for(var t=a(this),n=o(t.set),r=arguments.length,i=0;i1?arguments[1]:void 0,3);return u(n,function(e,n,i){if(r(n,e,t))return i()},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).stopped}})},74442(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(8154);r({target:"Map",proto:!0,real:!0,forced:i},{updateOrInsert:a})},7512(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(13099);r({target:"Map",proto:!0,real:!0,forced:i},{update:function(e,t){var n=a(this),r=arguments.length;o(t);var i=n.has(e);if(!i&&r<3)throw TypeError("Updating absent value");var s=i?n.get(e):o(r>2?arguments[2]:void 0)(e,n);return n.set(e,t(s,e,n)),n}})},87713(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(8154);r({target:"Map",proto:!0,real:!0,forced:i},{upsert:a})},46603(e,t,n){var r=n(82109),i=Math.min,a=Math.max;r({target:"Math",stat:!0},{clamp:function(e,t,n){return i(n,a(t,e))}})},70100(e,t,n){n(82109)({target:"Math",stat:!0},{DEG_PER_RAD:Math.PI/180})},26429(e,t,n){var r=n(82109),i=180/Math.PI;r({target:"Math",stat:!0},{degrees:function(e){return e*i}})},13187(e,t,n){var r=n(82109),i=n(47103),a=n(26130);r({target:"Math",stat:!0},{fscale:function(e,t,n,r,o){return a(i(e,t,n,r,o))}})},60092(e,t,n){n(82109)({target:"Math",stat:!0},{iaddh:function(e,t,n,r){var i=e>>>0,a=n>>>0;return(t>>>0)+(r>>>0)+((i&a|(i|a)&~(i+a>>>0))>>>31)|0}})},19041(e,t,n){n(82109)({target:"Math",stat:!0},{imulh:function(e,t){var n=65535,r=+e,i=+t,a=r&n,o=i&n,s=r>>16,u=i>>16,c=(s*o>>>0)+(a*o>>>16);return s*u+(c>>16)+((a*u>>>0)+(c&n)>>16)}})},30666(e,t,n){n(82109)({target:"Math",stat:!0},{isubh:function(e,t,n,r){var i=e>>>0,a=n>>>0;return(t>>>0)-(r>>>0)-((~i&a|~(i^a)&i-a>>>0)>>>31)|0}})},51638(e,t,n){n(82109)({target:"Math",stat:!0},{RAD_PER_DEG:180/Math.PI})},62975(e,t,n){var r=n(82109),i=Math.PI/180;r({target:"Math",stat:!0},{radians:function(e){return e*i}})},15728(e,t,n){var r=n(82109),i=n(47103);r({target:"Math",stat:!0},{scale:i})},46056(e,t,n){var r=n(82109),i=n(19670),a=n(77023),o=n(24994),s=n(29909),u="Seeded Random",c=u+" Generator",l=s.set,f=s.getterFor(c),d='Math.seededPRNG() argument should have a "seed" field with a finite value.',h=o(function(e){l(this,{type:c,seed:e%2147483647})},u,function(){var e=f(this);return{value:(1073741823&(e.seed=(1103515245*e.seed+12345)%2147483647))/1073741823,done:!1}});r({target:"Math",stat:!0,forced:!0},{seededPRNG:function(e){var t=i(e).seed;if(!a(t))throw TypeError(d);return new h(t)}})},44299(e,t,n){n(82109)({target:"Math",stat:!0},{signbit:function(e){return(e=+e)==e&&0==e?1/e==-1/0:e<0}})},5162(e,t,n){n(82109)({target:"Math",stat:!0},{umulh:function(e,t){var n=65535,r=+e,i=+t,a=r&n,o=i&n,s=r>>>16,u=i>>>16,c=(s*o>>>0)+(a*o>>>16);return s*u+(c>>>16)+((a*u>>>0)+(c&n)>>>16)}})},50292(e,t,n){"use strict";var r=n(82109),i=n(99958),a=n(83009),o="Invalid number representation",s="Invalid radix",u=/^[\da-z]+$/;r({target:"Number",stat:!0},{fromString:function(e,t){var n,r,c=1;if("string"!=typeof e)throw TypeError(o);if(!e.length||"-"==e.charAt(0)&&(c=-1,!(e=e.slice(1)).length))throw SyntaxError(o);if((n=void 0===t?10:i(t))<2||n>36)throw RangeError(s);if(!u.test(e)||(r=a(e,n)).toString(n)!==e)throw SyntaxError(o);return c*r}})},29427(e,t,n){"use strict";var r=n(82109),i=n(80430);r({target:"Number",stat:!0},{range:function(e,t,n){return new i(e,t,n,"number",0,1)}})},96936(e,t,n){n(46314)},99964(e,t,n){"use strict";var r=n(82109),i=n(60996);r({target:"Object",stat:!0},{iterateEntries:function(e){return new i(e,"entries")}})},75238(e,t,n){"use strict";var r=n(82109),i=n(60996);r({target:"Object",stat:!0},{iterateKeys:function(e){return new i(e,"keys")}})},4987(e,t,n){"use strict";var r=n(82109),i=n(60996);r({target:"Object",stat:!0},{iterateValues:function(e){return new i(e,"values")}})},1025(e,t,n){"use strict";var r=n(82109),i=n(19781),a=n(96340),o=n(13099),s=n(19670),u=n(70111),c=n(25787),l=n(3070).f,f=n(68880),d=n(12248),h=n(18554),p=n(58173),b=n(20408),m=n(842),g=n(5112),v=n(29909),y=g("observable"),w=v.get,_=v.set,E=function(e){var t=e.cleanup;if(t){e.cleanup=void 0;try{t()}catch(n){m(n)}}},S=function(e){return void 0===e.observer},k=function(e){var t=e.facade;if(!i){t.closed=!0;var n=e.subscriptionObserver;n&&(n.closed=!0)}e.observer=void 0},x=function(e,t){var n,r=_(this,{cleanup:void 0,observer:s(e),subscriptionObserver:void 0});i||(this.closed=!1);try{(n=p(e.start))&&n.call(e,this)}catch(a){m(a)}if(!S(r)){var u=r.subscriptionObserver=new T(this);try{var c=t(u),l=c;null!=c&&(r.cleanup="function"==typeof c.unsubscribe?function(){l.unsubscribe()}:o(c))}catch(f){u.error(f);return}S(r)&&E(r)}};x.prototype=d({},{unsubscribe:function(){var e=w(this);S(e)||(k(e),E(e))}}),i&&l(x.prototype,"closed",{configurable:!0,get:function(){return S(w(this))}});var T=function(e){_(this,{subscription:e}),i||(this.closed=!1)};T.prototype=d({},{next:function(e){var t=w(w(this).subscription);if(!S(t)){var n=t.observer;try{var r=p(n.next);r&&r.call(n,e)}catch(i){m(i)}}},error:function(e){var t=w(w(this).subscription);if(!S(t)){var n=t.observer;k(t);try{var r=p(n.error);r?r.call(n,e):m(e)}catch(i){m(i)}E(t)}},complete:function(){var e=w(w(this).subscription);if(!S(e)){var t=e.observer;k(e);try{var n=p(t.complete);n&&n.call(t)}catch(r){m(r)}E(e)}}}),i&&l(T.prototype,"closed",{configurable:!0,get:function(){return S(w(w(this).subscription))}});var M=function(e){c(this,M,"Observable"),_(this,{subscriber:o(e)})};d(M.prototype,{subscribe:function(e){var t=arguments.length;return new x("function"==typeof e?{next:e,error:t>1?arguments[1]:void 0,complete:t>2?arguments[2]:void 0}:u(e)?e:{},w(this).subscriber)}}),d(M,{from:function(e){var t="function"==typeof this?this:M,n=p(s(e)[y]);if(n){var r=s(n.call(e));return r.constructor===t?r:new t(function(e){return r.subscribe(e)})}var i=h(e);return new t(function(e){b(i,function(t,n){if(e.next(t),e.closed)return n()},{IS_ITERATOR:!0,INTERRUPTED:!0}),e.complete()})},of:function(){for(var e="function"==typeof this?this:M,t=arguments.length,n=Array(t),r=0;r1?arguments[1]:void 0,3);return!u(n,function(e,n){if(!r(e,e,t))return n()},{IS_ITERATOR:!0,INTERRUPTED:!0}).stopped}})},64362(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(35005),o=n(19670),s=n(13099),u=n(49974),c=n(36707),l=n(96767),f=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{filter:function(e){var t=o(this),n=l(t),r=u(e,arguments.length>1?arguments[1]:void 0,3),i=new(c(t,a("Set"))),d=s(i.add);return f(n,function(e){r(e,e,t)&&d.call(i,e)},{IS_ITERATOR:!0}),i}})},15389(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(49974),s=n(96767),u=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{find:function(e){var t=a(this),n=s(t),r=o(e,arguments.length>1?arguments[1]:void 0,3);return u(n,function(e,n){if(r(e,e,t))return n(e)},{IS_ITERATOR:!0,INTERRUPTED:!0}).result}})},46006(e,t,n){var r=n(82109),i=n(27296);r({target:"Set",stat:!0},{from:i})},90401(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(35005),o=n(19670),s=n(13099),u=n(36707),c=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{intersection:function(e){var t=o(this),n=new(u(t,a("Set"))),r=s(t.has),i=s(n.add);return c(e,function(e){r.call(t,e)&&i.call(n,e)}),n}})},45164(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(13099),s=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{isDisjointFrom:function(e){var t=a(this),n=o(t.has);return!s(e,function(e,r){if(!0===n.call(t,e))return r()},{INTERRUPTED:!0}).stopped}})},91238(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(35005),o=n(19670),s=n(13099),u=n(18554),c=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{isSubsetOf:function(e){var t=u(this),n=o(e),r=n.has;return"function"!=typeof r&&(n=new(a("Set"))(e),r=s(n.has)),!c(t,function(e,t){if(!1===r.call(n,e))return t()},{IS_ITERATOR:!0,INTERRUPTED:!0}).stopped}})},54837(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(13099),s=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{isSupersetOf:function(e){var t=a(this),n=o(t.has);return!s(e,function(e,r){if(!1===n.call(t,e))return r()},{INTERRUPTED:!0}).stopped}})},87485(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(96767),s=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{join:function(e){var t=a(this),n=o(t),r=void 0===e?",":String(e),i=[];return s(n,i.push,{that:i,IS_ITERATOR:!0}),i.join(r)}})},56767(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(35005),o=n(19670),s=n(13099),u=n(49974),c=n(36707),l=n(96767),f=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{map:function(e){var t=o(this),n=l(t),r=u(e,arguments.length>1?arguments[1]:void 0,3),i=new(c(t,a("Set"))),d=s(i.add);return f(n,function(e){d.call(i,r(e,e,t))},{IS_ITERATOR:!0}),i}})},69916(e,t,n){var r=n(82109),i=n(82044);r({target:"Set",stat:!0},{of:i})},76651(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(13099),s=n(96767),u=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{reduce:function(e){var t=a(this),n=s(t),r=arguments.length<2,i=r?void 0:arguments[1];if(o(e),u(n,function(n){r?(r=!1,i=n):i=e(i,n,n,t)},{IS_ITERATOR:!0}),r)throw TypeError("Reduce of empty set with no initial value");return i}})},61437(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(19670),o=n(49974),s=n(96767),u=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{some:function(e){var t=a(this),n=s(t),r=o(e,arguments.length>1?arguments[1]:void 0,3);return u(n,function(e,n){if(r(e,e,t))return n()},{IS_ITERATOR:!0,INTERRUPTED:!0}).stopped}})},35285(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(35005),o=n(19670),s=n(13099),u=n(36707),c=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{symmetricDifference:function(e){var t=o(this),n=new(u(t,a("Set")))(t),r=s(n.delete),i=s(n.add);return c(e,function(e){r.call(n,e)||i.call(n,e)}),n}})},39865(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(35005),o=n(19670),s=n(13099),u=n(36707),c=n(20408);r({target:"Set",proto:!0,real:!0,forced:i},{union:function(e){var t=o(this),n=new(u(t,a("Set")))(t);return c(e,s(n.add),{that:n}),n}})},86035(e,t,n){"use strict";var r=n(82109),i=n(28710).charAt,a=n(47293)(function(){return"𠮷"!=="𠮷".at(0)});r({target:"String",proto:!0,forced:a},{at:function(e){return i(this,e)}})},67501(e,t,n){"use strict";var r=n(82109),i=n(24994),a=n(84488),o=n(41340),s=n(29909),u=n(28710),c=u.codeAt,l=u.charAt,f="String Iterator",d=s.set,h=s.getterFor(f),p=i(function(e){d(this,{type:f,string:e,index:0})},"String",function(){var e,t=h(this),n=t.string,r=t.index;return r>=n.length?{value:void 0,done:!0}:(e=l(n,r),t.index+=e.length,{value:{codePoint:c(e,0),position:r},done:!1})});r({target:"String",proto:!0},{codePoints:function(){return new p(o(a(this)))}})},13728(e,t,n){n(76373)},27207(e,t,n){n(68757)},609(e,t,n){n(97235)("asyncDispose")},21568(e,t,n){n(97235)("dispose")},54534(e,t,n){n(97235)("matcher")},95090(e,t,n){n(97235)("metadata")},48824(e,t,n){n(97235)("observable")},44130(e,t,n){n(97235)("patternMatch")},35954(e,t,n){n(97235)("replaceAll")},38012(e,t,n){n(48675)},26182(e,t,n){"use strict";var r=n(90260),i=n(42092).filterReject,a=n(43074),o=r.aTypedArray;(0,r.exportTypedArrayMethod)("filterOut",function(e){var t=i(o(this),e,arguments.length>1?arguments[1]:void 0);return a(this,t)})},8922(e,t,n){"use strict";var r=n(90260),i=n(42092).filterReject,a=n(43074),o=r.aTypedArray;(0,r.exportTypedArrayMethod)("filterReject",function(e){var t=i(o(this),e,arguments.length>1?arguments[1]:void 0);return a(this,t)})},1118(e,t,n){"use strict";var r=n(90260),i=n(9671).findLastIndex,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("findLastIndex",function(e){return i(a(this),e,arguments.length>1?arguments[1]:void 0)})},37380(e,t,n){"use strict";var r=n(90260),i=n(9671).findLast,a=r.aTypedArray;(0,r.exportTypedArrayMethod)("findLast",function(e){return i(a(this),e,arguments.length>1?arguments[1]:void 0)})},5835(e,t,n){"use strict";var r=n(90260),i=n(61386),a=n(66304),o=r.aTypedArray;(0,r.exportTypedArrayMethod)("groupBy",function(e){var t=arguments.length>1?arguments[1]:void 0;return i(o(this),e,t,a)})},84444(e,t,n){"use strict";var r=n(90260),i=n(60956),a=n(43074),o=r.aTypedArray;(0,r.exportTypedArrayMethod)("uniqueBy",function(e){return a(this,i.call(o(this),e))})},78206(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(34092);r({target:"WeakMap",proto:!0,real:!0,forced:i},{deleteAll:function(){return a.apply(this,arguments)}})},12714(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(37502);r({target:"WeakMap",proto:!0,real:!0,forced:i},{emplace:a})},76478(e,t,n){var r=n(82109),i=n(27296);r({target:"WeakMap",stat:!0},{from:i})},79715(e,t,n){var r=n(82109),i=n(82044);r({target:"WeakMap",stat:!0},{of:i})},5964(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(8154);r({target:"WeakMap",proto:!0,real:!0,forced:i},{upsert:a})},43561(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(31501);r({target:"WeakSet",proto:!0,real:!0,forced:i},{addAll:function(){return a.apply(this,arguments)}})},32049(e,t,n){"use strict";var r=n(82109),i=n(31913),a=n(34092);r({target:"WeakSet",proto:!0,real:!0,forced:i},{deleteAll:function(){return a.apply(this,arguments)}})},86020(e,t,n){var r=n(82109),i=n(27296);r({target:"WeakSet",stat:!0},{from:i})},56585(e,t,n){var r=n(82109),i=n(82044);r({target:"WeakSet",stat:!0},{of:i})},54747(e,t,n){var r=n(17854),i=n(48324),a=n(18533),o=n(68880);for(var s in i){var u=r[s],c=u&&u.prototype;if(c&&c.forEach!==a)try{o(c,"forEach",a)}catch(l){c.forEach=a}}},33948(e,t,n){var r=n(17854),i=n(48324),a=n(66992),o=n(68880),s=n(5112),u=s("iterator"),c=s("toStringTag"),l=a.values;for(var f in i){var d=r[f],h=d&&d.prototype;if(h){if(h[u]!==l)try{o(h,u,l)}catch(p){h[u]=l}if(h[c]||o(h,c,f),i[f]){for(var b in a)if(h[b]!==a[b])try{o(h,b,a[b])}catch(m){h[b]=a[b]}}}}},84633(e,t,n){var r=n(82109),i=n(17854),a=n(20261);r({global:!0,bind:!0,enumerable:!0,forced:!i.setImmediate||!i.clearImmediate},{setImmediate:a.set,clearImmediate:a.clear})},85844(e,t,n){var r=n(82109),i=n(17854),a=n(95948),o=n(35268),s=i.process;r({global:!0,enumerable:!0,noTargetGet:!0},{queueMicrotask:function(e){var t=o&&s.domain;a(t?t.bind(e):e)}})},32564(e,t,n){var r=n(82109),i=n(17854),a=n(88113),o=[].slice,s=/MSIE .\./.test(a),u=function(e){return function(t,n){var r=arguments.length>2,i=r?o.call(arguments,2):void 0;return e(r?function(){("function"==typeof t?t:Function(t)).apply(this,i)}:t,n)}};r({global:!0,bind:!0,forced:s},{setTimeout:u(i.setTimeout),setInterval:u(i.setInterval)})},41637(e,t,n){"use strict";n(66992);var r=n(82109),i=n(35005),a=n(590),o=n(31320),s=n(12248),u=n(58003),c=n(24994),l=n(29909),f=n(25787),d=n(86656),h=n(49974),p=n(70648),b=n(19670),m=n(70111),g=n(41340),v=n(70030),y=n(79114),w=n(18554),_=n(71246),E=n(5112),S=i("fetch"),k=i("Request"),x=k&&k.prototype,T=i("Headers"),M=E("iterator"),O="URLSearchParams",A=O+"Iterator",L=l.set,C=l.getterFor(O),I=l.getterFor(A),D=/\+/g,N=[,,,,],P=function(e){return N[e-1]||(N[e-1]=RegExp("((?:%[\\da-f]{2}){"+e+"})","gi"))},R=function(e){try{return decodeURIComponent(e)}catch(t){return e}},j=function(e){var t=e.replace(D," "),n=4;try{return decodeURIComponent(t)}catch(r){for(;n;)t=t.replace(P(n--),R);return t}},F=/[!'()~]|%20/g,Y={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"},B=function(e){return Y[e]},U=function(e){return encodeURIComponent(e).replace(F,B)},H=function(e,t){if(t)for(var n,r,i=t.split("&"),a=0;a0?arguments[0]:void 0,l=this,h=[];if(L(l,{type:O,entries:h,updateURL:function(){},updateSearchParams:$}),void 0!==c){if(m(c)){if("function"==typeof(e=_(c)))for(n=(t=w(c,e)).next;!(r=n.call(t)).done;){if((o=(a=(i=w(b(r.value))).next).call(i)).done||(s=a.call(i)).done||!a.call(i).done)throw TypeError("Expected sequence with length 2");h.push({key:g(o.value),value:g(s.value)})}else for(u in c)d(c,u)&&h.push({key:u,value:g(c[u])})}else H(h,"string"==typeof c?"?"===c.charAt(0)?c.slice(1):c:g(c))}},K=W.prototype;if(s(K,{append:function(e,t){z(arguments.length,2);var n=C(this);n.entries.push({key:g(e),value:g(t)}),n.updateURL()},delete:function(e){z(arguments.length,1);for(var t=C(this),n=t.entries,r=g(e),i=0;ie.key){i.splice(t,0,e);break}t===n&&i.push(e)}r.updateURL()},forEach:function(e){for(var t,n=C(this).entries,r=h(e,arguments.length>1?arguments[1]:void 0,3),i=0;i1?V(arguments[1]):{})}}),"function"==typeof k){var q=function(e){return f(this,q,"Request"),new k(e,arguments.length>1?V(arguments[1]):{})};x.constructor=q,q.prototype=x,r({global:!0,forced:!0},{Request:q})}}e.exports={URLSearchParams:W,getState:C}},60285(e,t,n){"use strict";n(78783);var r,i=n(82109),a=n(19781),o=n(590),s=n(17854),u=n(36048),c=n(31320),l=n(25787),f=n(86656),d=n(21574),h=n(48457),p=n(28710).codeAt,b=n(33197),m=n(41340),g=n(58003),v=n(41637),y=n(29909),w=s.URL,_=v.URLSearchParams,E=v.getState,S=y.set,k=y.getterFor("URL"),x=Math.floor,T=Math.pow,M="Invalid authority",O="Invalid scheme",A="Invalid host",L="Invalid port",C=/[A-Za-z]/,I=/[\d+-.A-Za-z]/,D=/\d/,N=/^0x/i,P=/^[0-7]+$/,R=/^\d+$/,j=/^[\dA-Fa-f]+$/,F=/[\0\t\n\r #%/:<>?@[\\\]^|]/,Y=/[\0\t\n\r #/:<>?@[\\\]^|]/,B=/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g,U=/[\t\n\r]/g,H=function(e,t){var n,r,i;if("["==t.charAt(0)){if("]"!=t.charAt(t.length-1)||!(n=z(t.slice(1,-1))))return A;e.host=n}else if(Q(e)){if(t=b(t),F.test(t)||null===(n=$(t)))return A;e.host=n}else{if(Y.test(t))return A;for(i=0,n="",r=h(t);i4)return e;for(r=0,n=[];r1&&"0"==i.charAt(0)&&(a=N.test(i)?16:8,i=i.slice(8==a?1:2)),""===i)o=0;else{if(!(10==a?R:8==a?P:j).test(i))return e;o=parseInt(i,a)}n.push(o)}for(r=0;r=T(256,5-t))return null}else if(o>255)return null;for(r=0,s=n.pop();r6))return;for(r=0;d();){if(i=null,r>0){if("."!=d()||!(r<4))return;f++}if(!D.test(d()))return;for(;D.test(d());){if(a=parseInt(d(),10),null===i)i=a;else{if(0==i)return;i=10*i+a}if(i>255)return;f++}u[c]=256*u[c]+i,(2==++r||4==r)&&c++}if(4!=r)return;break}if(":"==d()){if(f++,!d())return}else if(d())return;u[c++]=t}if(null!==l)for(o=c-l,c=7;0!=c&&o>0;)s=u[c],u[c--]=u[l+o-1],u[l+--o]=s;else if(8!=c)return;return u},G=function(e){for(var t=null,n=1,r=null,i=0,a=0;a<8;a++)0!==e[a]?(i>n&&(t=r,n=i),r=null,i=0):(null===r&&(r=a),++i);return i>n&&(t=r,n=i),t},W=function(e){var t,n,r,i;if("number"==typeof e){for(n=0,t=[];n<4;n++)t.unshift(e%256),e=x(e/256);return t.join(".")}if("object"==typeof e){for(n=0,t="",r=G(e);n<8;n++)(!i||0!==e[n])&&(i&&(i=!1),r===n?(t+=n?":":"::",i=!0):(t+=e[n].toString(16),n<7&&(t+=":")));return"["+t+"]"}return e},K={},V=d({},K,{" ":1,'"':1,"<":1,">":1,"`":1}),q=d({},V,{"#":1,"?":1,"{":1,"}":1}),Z=d({},q,{"/":1,":":1,";":1,"=":1,"@":1,"[":1,"\\":1,"]":1,"^":1,"|":1}),X=function(e,t){var n=p(e,0);return n>32&&n<127&&!f(t,e)?e:encodeURIComponent(e)},J={ftp:21,file:null,http:80,https:443,ws:80,wss:443},Q=function(e){return f(J,e.scheme)},ee=function(e){return""!=e.username||""!=e.password},et=function(e){return!e.host||e.cannotBeABaseURL||"file"==e.scheme},en=function(e,t){var n;return 2==e.length&&C.test(e.charAt(0))&&(":"==(n=e.charAt(1))||!t&&"|"==n)},er=function(e){var t;return e.length>1&&en(e.slice(0,2))&&(2==e.length||"/"===(t=e.charAt(2))||"\\"===t||"?"===t||"#"===t)},ei=function(e){var t=e.path,n=t.length;n&&("file"!=e.scheme||1!=n||!en(t[0],!0))&&t.pop()},ea=function(e){return"."===e||"%2e"===e.toLowerCase()},eo=function(e){return".."===(e=e.toLowerCase())||"%2e."===e||".%2e"===e||"%2e%2e"===e},es={},eu={},ec={},el={},ef={},ed={},eh={},ep={},eb={},em={},eg={},ev={},ey={},ew={},e_={},eE={},eS={},ek={},ex={},eT={},eM={},eO=function(e,t,n,i){var a,o,s,u,c=n||es,l=0,d="",p=!1,b=!1,m=!1;for(n||(e.scheme="",e.username="",e.password="",e.host=null,e.port=null,e.path=[],e.query=null,e.fragment=null,e.cannotBeABaseURL=!1,t=t.replace(B,"")),t=t.replace(U,""),a=h(t);l<=a.length;){switch(o=a[l],c){case es:if(o&&C.test(o))d+=o.toLowerCase(),c=eu;else{if(n)return O;c=ec;continue}break;case eu:if(o&&(I.test(o)||"+"==o||"-"==o||"."==o))d+=o.toLowerCase();else if(":"==o){if(n&&(Q(e)!=f(J,d)||"file"==d&&(ee(e)||null!==e.port)||"file"==e.scheme&&!e.host))return;if(e.scheme=d,n){Q(e)&&J[e.scheme]==e.port&&(e.port=null);return}d="","file"==e.scheme?c=ew:Q(e)&&i&&i.scheme==e.scheme?c=el:Q(e)?c=ep:"/"==a[l+1]?(c=ef,l++):(e.cannotBeABaseURL=!0,e.path.push(""),c=ex)}else{if(n)return O;d="",c=ec,l=0;continue}break;case ec:if(!i||i.cannotBeABaseURL&&"#"!=o)return O;if(i.cannotBeABaseURL&&"#"==o){e.scheme=i.scheme,e.path=i.path.slice(),e.query=i.query,e.fragment="",e.cannotBeABaseURL=!0,c=eM;break}c="file"==i.scheme?ew:ed;continue;case el:if("/"==o&&"/"==a[l+1])c=eb,l++;else{c=ed;continue}break;case ef:if("/"==o){c=em;break}c=ek;continue;case ed:if(e.scheme=i.scheme,o==r)e.username=i.username,e.password=i.password,e.host=i.host,e.port=i.port,e.path=i.path.slice(),e.query=i.query;else if("/"==o||"\\"==o&&Q(e))c=eh;else if("?"==o)e.username=i.username,e.password=i.password,e.host=i.host,e.port=i.port,e.path=i.path.slice(),e.query="",c=eT;else if("#"==o)e.username=i.username,e.password=i.password,e.host=i.host,e.port=i.port,e.path=i.path.slice(),e.query=i.query,e.fragment="",c=eM;else{e.username=i.username,e.password=i.password,e.host=i.host,e.port=i.port,e.path=i.path.slice(),e.path.pop(),c=ek;continue}break;case eh:if(Q(e)&&("/"==o||"\\"==o))c=eb;else if("/"==o)c=em;else{e.username=i.username,e.password=i.password,e.host=i.host,e.port=i.port,c=ek;continue}break;case ep:if(c=eb,"/"!=o||"/"!=d.charAt(l+1))continue;l++;break;case eb:if("/"!=o&&"\\"!=o){c=em;continue}break;case em:if("@"==o){p&&(d="%40"+d),p=!0,s=h(d);for(var g=0;g65535)return L;e.port=Q(e)&&w===J[e.scheme]?null:w,d=""}if(n)return;c=eS;continue}break;case ew:if(e.scheme="file","/"==o||"\\"==o)c=e_;else if(i&&"file"==i.scheme){if(o==r)e.host=i.host,e.path=i.path.slice(),e.query=i.query;else if("?"==o)e.host=i.host,e.path=i.path.slice(),e.query="",c=eT;else if("#"==o)e.host=i.host,e.path=i.path.slice(),e.query=i.query,e.fragment="",c=eM;else{er(a.slice(l).join(""))||(e.host=i.host,e.path=i.path.slice(),ei(e)),c=ek;continue}}else{c=ek;continue}break;case e_:if("/"==o||"\\"==o){c=eE;break}i&&"file"==i.scheme&&!er(a.slice(l).join(""))&&(en(i.path[0],!0)?e.path.push(i.path[0]):e.host=i.host),c=ek;continue;case eE:if(o==r||"/"==o||"\\"==o||"?"==o||"#"==o){if(!n&&en(d))c=ek;else if(""==d){if(e.host="",n)return;c=eS}else{if(u=H(e,d))return u;if("localhost"==e.host&&(e.host=""),n)return;d="",c=eS}continue}d+=o;break;case eS:if(Q(e)){if(c=ek,"/"!=o&&"\\"!=o)continue}else if(n||"?"!=o){if(n||"#"!=o){if(o!=r&&(c=ek,"/"!=o))continue}else e.fragment="",c=eM}else e.query="",c=eT;break;case ek:if(o==r||"/"==o||"\\"==o&&Q(e)||!n&&("?"==o||"#"==o)){if(eo(d)?(ei(e),"/"==o||"\\"==o&&Q(e)||e.path.push("")):ea(d)?"/"==o||"\\"==o&&Q(e)||e.path.push(""):("file"==e.scheme&&!e.path.length&&en(d)&&(e.host&&(e.host=""),d=d.charAt(0)+":"),e.path.push(d)),d="","file"==e.scheme&&(o==r||"?"==o||"#"==o))for(;e.path.length>1&&""===e.path[0];)e.path.shift();"?"==o?(e.query="",c=eT):"#"==o&&(e.fragment="",c=eM)}else d+=X(o,q);break;case ex:"?"==o?(e.query="",c=eT):"#"==o?(e.fragment="",c=eM):o!=r&&(e.path[0]+=X(o,K));break;case eT:n||"#"!=o?o!=r&&("'"==o&&Q(e)?e.query+="%27":"#"==o?e.query+="%23":e.query+=X(o,K)):(e.fragment="",c=eM);break;case eM:o!=r&&(e.fragment+=X(o,V))}l++}},eA=function(e){var t,n,r=l(this,eA,"URL"),i=arguments.length>1?arguments[1]:void 0,o=m(e),s=S(r,{type:"URL"});if(void 0!==i){if(i instanceof eA)t=k(i);else if(n=eO(t={},m(i)))throw TypeError(n)}if(n=eO(s,o,null,t))throw TypeError(n);var u=s.searchParams=new _,c=E(u);c.updateSearchParams(s.query),c.updateURL=function(){s.query=String(u)||null},a||(r.href=eC.call(r),r.origin=eI.call(r),r.protocol=eD.call(r),r.username=eN.call(r),r.password=eP.call(r),r.host=eR.call(r),r.hostname=ej.call(r),r.port=eF.call(r),r.pathname=eY.call(r),r.search=eB.call(r),r.searchParams=eU.call(r),r.hash=eH.call(r))},eL=eA.prototype,eC=function(){var e=k(this),t=e.scheme,n=e.username,r=e.password,i=e.host,a=e.port,o=e.path,s=e.query,u=e.fragment,c=t+":";return null!==i?(c+="//",ee(e)&&(c+=n+(r?":"+r:"")+"@"),c+=W(i),null!==a&&(c+=":"+a)):"file"==t&&(c+="//"),c+=e.cannotBeABaseURL?o[0]:o.length?"/"+o.join("/"):"",null!==s&&(c+="?"+s),null!==u&&(c+="#"+u),c},eI=function(){var e=k(this),t=e.scheme,n=e.port;if("blob"==t)try{return new eA(t.path[0]).origin}catch(r){return"null"}return"file"!=t&&Q(e)?t+"://"+W(e.host)+(null!==n?":"+n:""):"null"},eD=function(){return k(this).scheme+":"},eN=function(){return k(this).username},eP=function(){return k(this).password},eR=function(){var e=k(this),t=e.host,n=e.port;return null===t?"":null===n?W(t):W(t)+":"+n},ej=function(){var e=k(this).host;return null===e?"":W(e)},eF=function(){var e=k(this).port;return null===e?"":String(e)},eY=function(){var e=k(this),t=e.path;return e.cannotBeABaseURL?t[0]:t.length?"/"+t.join("/"):""},eB=function(){var e=k(this).query;return e?"?"+e:""},eU=function(){return k(this).searchParams},eH=function(){var e=k(this).fragment;return e?"#"+e:""},e$=function(e,t){return{get:e,set:t,configurable:!0,enumerable:!0}};if(a&&u(eL,{href:e$(eC,function(e){var t=k(this),n=m(e),r=eO(t,n);if(r)throw TypeError(r);E(t.searchParams).updateSearchParams(t.query)}),origin:e$(eI),protocol:e$(eD,function(e){var t=k(this);eO(t,m(e)+":",es)}),username:e$(eN,function(e){var t=k(this),n=h(m(e));if(!et(t)){t.username="";for(var r=0;rc});var r={value:function(){}};function i(){for(var e,t=0,n=arguments.length,r={};t=0&&(n=e.slice(r+1),e=e.slice(0,r)),e&&!t.hasOwnProperty(e))throw Error("unknown type: "+e);return{type:e,name:n}})}function s(e,t){for(var n,r=0,i=e.length;r0)for(var n,r,i=Array(n),a=0;am,dragDisable:()=>u.Z,dragEnable:()=>u.D});var r=n(92626),i=n(25109),a=n(43095),o=n(94017),s=n(24793),u=n(44266),c=n(34299);function l(e){return function(){return e}}function f(e,t,n,r,i,a,o,s,u,c){this.target=e,this.type=t,this.subject=n,this.identifier=r,this.active=i,this.x=a,this.y=o,this.dx=s,this.dy=u,this._=c}function d(){return!i.B.ctrlKey&&!i.B.button}function h(){return this.parentNode}function p(e){return null==e?{x:i.B.x,y:i.B.y}:e}function b(){return navigator.maxTouchPoints||"ontouchstart"in this}function m(){var e,t,n,m,g=d,v=h,y=p,w=b,_={},E=(0,r.Z)("start","drag","end"),S=0,k=0;function x(e){e.on("mousedown.drag",T).filter(w).on("touchstart.drag",A).on("touchmove.drag",L).on("touchend.drag touchcancel.drag",C).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function T(){if(!m&&g.apply(this,arguments)){var r=I("mouse",v.apply(this,arguments),a.Z,this,arguments);r&&((0,o.Z)(i.B.view).on("mousemove.drag",M,!0).on("mouseup.drag",O,!0),(0,u.Z)(i.B.view),(0,c.r)(),n=!1,e=i.B.clientX,t=i.B.clientY,r("start"))}}function M(){if((0,c.Z)(),!n){var r=i.B.clientX-e,a=i.B.clientY-t;n=r*r+a*a>k}_.mouse("drag")}function O(){(0,o.Z)(i.B.view).on("mousemove.drag mouseup.drag",null),(0,u.D)(i.B.view,n),(0,c.Z)(),_.mouse("end")}function A(){if(g.apply(this,arguments)){var e,t,n=i.B.changedTouches,r=v.apply(this,arguments),a=n.length;for(e=0;eo,Z:()=>a});var r=n(94017),i=n(34299);function a(e){var t=e.document.documentElement,n=(0,r.Z)(e).on("dragstart.drag",i.Z,!0);"onselectstart"in t?n.on("selectstart.drag",i.Z,!0):(t.__noselect=t.style.MozUserSelect,t.style.MozUserSelect="none")}function o(e,t){var n=e.document.documentElement,a=(0,r.Z)(e).on("dragstart.drag",null);t&&(a.on("click.drag",i.Z,!0),setTimeout(function(){a.on("click.drag",null)},0)),"onselectstart"in n?a.on("selectstart.drag",null):(n.style.MozUserSelect=n.__noselect,delete n.__noselect)}},34299(e,t,n){"use strict";n.d(t,{Z:()=>a,r:()=>i});var r=n(25109);function i(){r.B.stopImmediatePropagation()}function a(){r.B.preventDefault(),r.B.stopImmediatePropagation()}},9893(e,t,n){"use strict";function r(e,t){var n;function r(){var r,i,a=n.length,o=0,s=0;for(r=0;r=(a=(b+g)/2))?b=a:g=a,(l=n>=(o=(m+v)/2))?m=o:v=o,i=h,!(h=h[f=l<<1|c]))return i[f]=p,e;if(s=+e._x.call(null,h.data),u=+e._y.call(null,h.data),t===s&&n===u)return p.next=h,i?i[f]=p:e._root=p,e;do i=i?i[f]=[,,,,]:e._root=[,,,,],(c=t>=(a=(b+g)/2))?b=a:g=a,(l=n>=(o=(m+v)/2))?m=o:v=o;while((f=l<<1|c)==(d=(u>=o)<<1|s>=a))return i[d]=h,i[f]=p,e}function u(e){var t,n,r,i,a=e.length,o=Array(a),u=Array(a),c=1/0,l=1/0,f=-1/0,d=-1/0;for(n=0;nf&&(f=r),id&&(d=i));if(c>f||l>d)return this;for(this.cover(c,l).cover(f,d),n=0;ne||e>=i||r>t||t>=a;)switch(s=(th)&&!((a=u.y0)>p)&&!((o=u.x1)=v)<<1|e>=g)&&(u=b[b.length-1],b[b.length-1]=b[b.length-1-c],b[b.length-1-c]=u)}else{var y=e-+this._x.call(null,m.data),w=t-+this._y.call(null,m.data),_=y*y+w*w;if(_=(s=(p+m)/2))?p=s:m=s,(l=o>=(u=(b+g)/2))?b=u:g=u,t=h,!(h=h[f=l<<1|c]))return this;if(!h.length)break;(t[f+1&3]||t[f+2&3]||t[f+3&3])&&(n=t,d=f)}for(;h.data!==e;)if(r=h,!(h=h.next))return this;return((i=h.next)&&delete h.next,r)?(i?r.next=i:delete r.next,this):t?(i?t[f]=i:delete t[f],(h=t[0]||t[1]||t[2]||t[3])&&h===(t[3]||t[2]||t[1]||t[0])&&!h.length&&(n?n[d]=h:this._root=h),this):(this._root=i,this)}function b(e){for(var t=0,n=e.length;tr,forceCollide:()=>L,forceLink:()=>B,forceManyBody:()=>V,forceRadial:()=>q,forceSimulation:()=>K,forceX:()=>Z,forceY:()=>X});var M=k.prototype=x.prototype;function O(e){return e.x+e.vx}function A(e){return e.y+e.vy}function L(e){var t,n,r=1,o=1;function s(){for(var e,i,s,c,l,f,d,h=t.length,p=0;ps.index){var b=c-u.x-u.vx,m=l-u.y-u.vy,g=b*b+m*m;gc+p||il+p||oe.r&&(e.r=e[t].r)}function c(){if(t){var r,i,a=t.length;for(r=0,n=Array(a);r1?(null==n?s.remove(e):s.set(e,h(n)),t):s.get(e)},find:function(t,n,r){var i,a,o,s,u,c=0,l=e.length;for(null==r?r=1/0:r*=r,c=0;c1?(c.on(e,n),t):c.on(e)}}}function V(){var e,t,n,r,o=i(-30),s=1,u=1/0,c=.81;function l(r){var i,a=e.length,o=k(e,$,z).visitAfter(d);for(n=r,i=0;i=u)){(e.data!==t||e.next)&&(0===f&&(p+=(f=a())*f),0===d&&(p+=(d=a())*d),ps});var r=n(73888),i=n(31986);function a(e){return function(){var t=this.ownerDocument,n=this.namespaceURI;return n===i.P&&t.documentElement.namespaceURI===i.P?t.createElement(e):t.createElementNS(n,e)}}function o(e){return function(){return this.ownerDocument.createElementNS(e.space,e.local)}}function s(e){var t=(0,r.Z)(e);return(t.local?o:a)(t)}},58556(e,t,n){"use strict";n.r(t),n.d(t,{clientPoint:()=>h.Z,create:()=>a,creator:()=>r.Z,customEvent:()=>S._H,event:()=>S.B,local:()=>s,matcher:()=>c.Z,mouse:()=>l.Z,namespace:()=>f.Z,namespaces:()=>d.Z,select:()=>i.Z,selectAll:()=>b,selection:()=>p.ZP,selector:()=>m.Z,selectorAll:()=>g.Z,style:()=>v.S,touch:()=>y.Z,touches:()=>_,window:()=>E.Z});var r=n(789),i=n(94017);function a(e){return(0,i.Z)((0,r.Z)(e).call(document.documentElement))}var o=0;function s(){return new u}function u(){this._="@"+(++o).toString(36)}u.prototype=s.prototype={constructor:u,get:function(e){for(var t=this._;!(t in e);)if(!(e=e.parentNode))return;return e[t]},set:function(e,t){return e[this._]=t},remove:function(e){return this._ in e&&delete e[this._]},toString:function(){return this._}};var c=n(3083),l=n(43095),f=n(73888),d=n(31986),h=n(42115),p=n(23817);function b(e){return"string"==typeof e?new p.Y1([document.querySelectorAll(e)],[document.documentElement]):new p.Y1([null==e?[]:e],p.Jz)}var m=n(82634),g=n(3545),v=n(49986),y=n(24793),w=n(45553);function _(e,t){null==t&&(t=(0,w.Z)().touches);for(var n=0,r=t?t.length:0,i=Array(r);nr})},43095(e,t,n){"use strict";n.d(t,{Z:()=>a});var r=n(45553),i=n(42115);function a(e){var t=(0,r.Z)();return t.changedTouches&&(t=t.changedTouches[0]),(0,i.Z)(e,t)}},73888(e,t,n){"use strict";n.d(t,{Z:()=>i});var r=n(31986);function i(e){var t=e+="",n=t.indexOf(":");return n>=0&&"xmlns"!==(t=e.slice(0,n))&&(e=e.slice(n+1)),r.Z.hasOwnProperty(t)?{space:r.Z[t],local:e}:e}},31986(e,t,n){"use strict";n.d(t,{P:()=>r,Z:()=>i});var r="http://www.w3.org/1999/xhtml";let i={svg:"http://www.w3.org/2000/svg",xhtml:r,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"}},42115(e,t,n){"use strict";function r(e,t){var n=e.ownerSVGElement||e;if(n.createSVGPoint){var r=n.createSVGPoint();return r.x=t.clientX,r.y=t.clientY,[(r=r.matrixTransform(e.getScreenCTM().inverse())).x,r.y]}var i=e.getBoundingClientRect();return[t.clientX-i.left-e.clientLeft,t.clientY-i.top-e.clientTop]}n.d(t,{Z:()=>r})},94017(e,t,n){"use strict";n.d(t,{Z:()=>i});var r=n(23817);function i(e){return"string"==typeof e?new r.Y1([[document.querySelector(e)]],[document.documentElement]):new r.Y1([[e]],r.Jz)}},23817(e,t,n){"use strict";n.d(t,{Y1:()=>eT,ZP:()=>eO,Jz:()=>ex});var r=n(82634);function i(e){"function"!=typeof e&&(e=(0,r.Z)(e));for(var t=this._groups,n=t.length,i=Array(n),a=0;a=k&&(k=S+1);!(E=y[k])&&++k=0;)(r=i[a])&&(o&&4^r.compareDocumentPosition(o)&&o.parentNode.insertBefore(r,o),o=r);return this}function _(e){function t(t,n){return t&&n?e(t.__data__,n.__data__):!t-!n}e||(e=E);for(var n=this._groups,r=n.length,i=Array(r),a=0;at?1:e>=t?0:NaN}function S(){var e=arguments[0];return arguments[0]=this,e.apply(null,arguments),this}function k(){var e=Array(this.size()),t=-1;return this.each(function(){e[++t]=this}),e}function x(){for(var e=this._groups,t=0,n=e.length;t1?this.each((null==t?F:"function"==typeof t?B:Y)(e,t)):this.node()[e]}function H(e){return e.trim().split(/^|\s+/)}function $(e){return e.classList||new z(e)}function z(e){this._node=e,this._names=H(e.getAttribute("class")||"")}function G(e,t){for(var n=$(e),r=-1,i=t.length;++rthis._names.indexOf(e)&&(this._names.push(e),this._node.setAttribute("class",this._names.join(" ")))},remove:function(e){var t=this._names.indexOf(e);t>=0&&(this._names.splice(t,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(e){return this._names.indexOf(e)>=0}};var ec=n(789);function el(e){var t="function"==typeof e?e:(0,ec.Z)(e);return this.select(function(){return this.appendChild(t.apply(this,arguments))})}function ef(){return null}function ed(e,t){var n="function"==typeof e?e:(0,ec.Z)(e),i=null==t?ef:"function"==typeof t?t:(0,r.Z)(t);return this.select(function(){return this.insertBefore(n.apply(this,arguments),i.apply(this,arguments)||null)})}function eh(){var e=this.parentNode;e&&e.removeChild(this)}function ep(){return this.each(eh)}function eb(){var e=this.cloneNode(!1),t=this.parentNode;return t?t.insertBefore(e,this.nextSibling):e}function em(){var e=this.cloneNode(!0),t=this.parentNode;return t?t.insertBefore(e,this.nextSibling):e}function eg(e){return this.select(e?em:eb)}function ev(e){return arguments.length?this.property("__data__",e):this.node().__data__}var ey=n(25109),ew=n(85021);function e_(e,t,n){var r=(0,ew.Z)(e),i=r.CustomEvent;"function"==typeof i?i=new i(t,n):(i=r.document.createEvent("Event"),n?(i.initEvent(t,n.bubbles,n.cancelable),i.detail=n.detail):i.initEvent(t,!1,!1)),e.dispatchEvent(i)}function eE(e,t){return function(){return e_(this,e,t)}}function eS(e,t){return function(){return e_(this,e,t.apply(this,arguments))}}function ek(e,t){return this.each(("function"==typeof t?eS:eE)(e,t))}var ex=[null];function eT(e,t){this._groups=e,this._parents=t}function eM(){return new eT([[document.documentElement]],ex)}eT.prototype=eM.prototype={constructor:eT,select:i,selectAll:o,filter:u,data:m,enter:l,exit:g,join:v,merge:y,order:w,sort:_,call:S,nodes:k,node:x,size:T,empty:M,each:O,attr:R,style:j.Z,property:U,classed:Z,text:ee,html:ei,raise:eo,lower:eu,append:el,insert:ed,remove:ep,clone:eg,datum:ev,on:ey.ZP,dispatch:ek};let eO=eM},25109(e,t,n){"use strict";n.d(t,{B:()=>i,ZP:()=>l,_H:()=>f});var r={},i=null;function a(e,t,n){return e=o(e,t,n),function(t){var n=t.relatedTarget;n&&(n===this||8&n.compareDocumentPosition(this))||e.call(this,t)}}function o(e,t,n){return function(r){var a=i;i=r;try{e.call(this,this.__data__,t,n)}finally{i=a}}}function s(e){return e.trim().split(/^|\s+/).map(function(e){var t="",n=e.indexOf(".");return n>=0&&(t=e.slice(n+1),e=e.slice(0,n)),{type:e,name:t}})}function u(e){return function(){var t=this.__on;if(t){for(var n,r=0,i=-1,a=t.length;ru,Z:()=>s});var r=n(85021);function i(e){return function(){this.style.removeProperty(e)}}function a(e,t,n){return function(){this.style.setProperty(e,t,n)}}function o(e,t,n){return function(){var r=t.apply(this,arguments);null==r?this.style.removeProperty(e):this.style.setProperty(e,r,n)}}function s(e,t,n){return arguments.length>1?this.each((null==t?i:"function"==typeof t?o:a)(e,t,null==n?"":n)):u(this.node(),e)}function u(e,t){return e.style.getPropertyValue(t)||(0,r.Z)(e).getComputedStyle(e,null).getPropertyValue(t)}},82634(e,t,n){"use strict";function r(){}function i(e){return null==e?r:function(){return this.querySelector(e)}}n.d(t,{Z:()=>i})},3545(e,t,n){"use strict";function r(){return[]}function i(e){return null==e?r:function(){return this.querySelectorAll(e)}}n.d(t,{Z:()=>i})},45553(e,t,n){"use strict";n.d(t,{Z:()=>i});var r=n(25109);function i(){for(var e,t=r.B;e=t.sourceEvent;)t=e;return t}},24793(e,t,n){"use strict";n.d(t,{Z:()=>a});var r=n(45553),i=n(42115);function a(e,t,n){arguments.length<3&&(n=t,t=(0,r.Z)().changedTouches);for(var a,o=0,s=t?t.length:0;or})},71098(e,t,n){"use strict";n.r(t),n.d(t,{arc:()=>C,area:()=>j,areaRadial:()=>W,curveBasis:()=>eM,curveBasisClosed:()=>eA,curveBasisOpen:()=>eC,curveBundle:()=>eD,curveCardinal:()=>eR,curveCardinalClosed:()=>eF,curveCardinalOpen:()=>eB,curveCatmullRom:()=>e$,curveCatmullRomClosed:()=>eG,curveCatmullRomOpen:()=>eK,curveLinear:()=>D,curveLinearClosed:()=>eq,curveMonotoneX:()=>e3,curveMonotoneY:()=>e4,curveNatural:()=>e9,curveStep:()=>e7,curveStepAfter:()=>tt,curveStepBefore:()=>te,line:()=>R,lineRadial:()=>G,linkHorizontal:()=>et,linkRadial:()=>er,linkVertical:()=>en,pie:()=>B,pointRadial:()=>K,radialArea:()=>W,radialLine:()=>G,stack:()=>ta,stackOffsetDiverging:()=>ts,stackOffsetExpand:()=>to,stackOffsetNone:()=>tn,stackOffsetSilhouette:()=>tu,stackOffsetWiggle:()=>tc,stackOrderAppearance:()=>tl,stackOrderAscending:()=>td,stackOrderDescending:()=>tp,stackOrderInsideOut:()=>tb,stackOrderNone:()=>tr,stackOrderReverse:()=>tm,symbol:()=>eS,symbolCircle:()=>ei,symbolCross:()=>ea,symbolDiamond:()=>eu,symbolSquare:()=>ep,symbolStar:()=>eh,symbolTriangle:()=>em,symbolWye:()=>e_,symbols:()=>eE});var r=Math.PI,i=2*r,a=1e-6,o=i-a;function s(){this._x0=this._y0=this._x1=this._y1=null,this._=""}function u(){return new s}s.prototype=u.prototype={constructor:s,moveTo:function(e,t){this._+="M"+(this._x0=this._x1=+e)+","+(this._y0=this._y1=+t)},closePath:function(){null!==this._x1&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")},lineTo:function(e,t){this._+="L"+(this._x1=+e)+","+(this._y1=+t)},quadraticCurveTo:function(e,t,n,r){this._+="Q"+ +e+","+ +t+","+(this._x1=+n)+","+(this._y1=+r)},bezierCurveTo:function(e,t,n,r,i,a){this._+="C"+ +e+","+ +t+","+ +n+","+ +r+","+(this._x1=+i)+","+(this._y1=+a)},arcTo:function(e,t,n,i,o){e=+e,t=+t,n=+n,i=+i,o=+o;var s=this._x1,u=this._y1,c=n-e,l=i-t,f=s-e,d=u-t,h=f*f+d*d;if(o<0)throw Error("negative radius: "+o);if(null===this._x1)this._+="M"+(this._x1=e)+","+(this._y1=t);else if(h>a){if(Math.abs(d*c-l*f)>a&&o){var p=n-s,b=i-u,m=c*c+l*l,g=Math.sqrt(m),v=Math.sqrt(h),y=o*Math.tan((r-Math.acos((m+h-(p*p+b*b))/(2*g*v)))/2),w=y/v,_=y/g;Math.abs(w-1)>a&&(this._+="L"+(e+w*f)+","+(t+w*d)),this._+="A"+o+","+o+",0,0,"+ +(d*p>f*b)+","+(this._x1=e+_*c)+","+(this._y1=t+_*l)}else this._+="L"+(this._x1=e)+","+(this._y1=t)}},arc:function(e,t,n,s,u,c){e=+e,t=+t,n=+n,c=!!c;var l=n*Math.cos(s),f=n*Math.sin(s),d=e+l,h=t+f,p=1^c,b=c?s-u:u-s;if(n<0)throw Error("negative radius: "+n);null===this._x1?this._+="M"+d+","+h:(Math.abs(this._x1-d)>a||Math.abs(this._y1-h)>a)&&(this._+="L"+d+","+h),n&&(b<0&&(b=b%i+i),b>o?this._+="A"+n+","+n+",0,1,"+p+","+(e-l)+","+(t-f)+"A"+n+","+n+",0,1,"+p+","+(this._x1=d)+","+(this._y1=h):b>a&&(this._+="A"+n+","+n+",0,"+ +(b>=r)+","+p+","+(this._x1=e+n*Math.cos(u))+","+(this._y1=t+n*Math.sin(u))))},rect:function(e,t,n,r){this._+="M"+(this._x0=this._x1=+e)+","+(this._y0=this._y1=+t)+"h"+ +n+"v"+ +r+"h"+-n+"Z"},toString:function(){return this._}};let c=u;function l(e){return function(){return e}}var f=Math.abs,d=Math.atan2,h=Math.cos,p=Math.max,b=Math.min,m=Math.sin,g=Math.sqrt,v=1e-12,y=Math.PI,w=y/2,_=2*y;function E(e){return e>1?0:e<-1?y:Math.acos(e)}function S(e){return e>=1?w:e<=-1?-w:Math.asin(e)}function k(e){return e.innerRadius}function x(e){return e.outerRadius}function T(e){return e.startAngle}function M(e){return e.endAngle}function O(e){return e&&e.padAngle}function A(e,t,n,r,i,a,o,s){var u=n-e,c=r-t,l=o-i,f=s-a,d=f*u-l*c;if(!(d*dI*I+D*D&&(T=O,M=A),{cx:T,cy:M,x01:-l,y01:-f,x11:T*(i/S-1),y11:M*(i/S-1)}}function C(){var e=k,t=x,n=l(0),r=null,i=T,a=M,o=O,s=null;function u(){var u,l,p=+e.apply(this,arguments),k=+t.apply(this,arguments),x=i.apply(this,arguments)-w,T=a.apply(this,arguments)-w,M=f(T-x),O=T>x;if(s||(s=u=c()),kv){if(M>_-v)s.moveTo(k*h(x),k*m(x)),s.arc(0,0,k,x,T,!O),p>v&&(s.moveTo(p*h(T),p*m(T)),s.arc(0,0,p,T,x,O));else{var C,I,D=x,N=T,P=x,R=T,j=M,F=M,Y=o.apply(this,arguments)/2,B=Y>v&&(r?+r.apply(this,arguments):g(p*p+k*k)),U=b(f(k-p)/2,+n.apply(this,arguments)),H=U,$=U;if(B>v){var z=S(B/p*m(Y)),G=S(B/k*m(Y));(j-=2*z)>v?(z*=O?1:-1,P+=z,R-=z):(j=0,P=R=(x+T)/2),(F-=2*G)>v?(G*=O?1:-1,D+=G,N-=G):(F=0,D=N=(x+T)/2)}var W=k*h(D),K=k*m(D),V=p*h(R),q=p*m(R);if(U>v){var Z,X=k*h(N),J=k*m(N),Q=p*h(P),ee=p*m(P);if(Mv?$>v?(C=L(Q,ee,W,K,k,$,O),I=L(X,J,V,q,k,$,O),s.moveTo(C.cx+C.x01,C.cy+C.y01),$v&&j>v?H>v?(C=L(V,q,X,J,p,-H,O),I=L(W,K,Q,ee,p,-H,O),s.lineTo(C.cx+C.x01,C.cy+C.y01),H=f;--d)s.point(g[d],v[d]);s.lineEnd(),s.areaEnd()}}m&&(g[l]=+e(h,l,u),v[l]=+n(h,l,u),s.point(t?+t(h,l,u):g[l],r?+r(h,l,u):v[l]))}if(p)return s=null,p+""||null}function f(){return R().defined(i).curve(o).context(a)}return u.x=function(n){return arguments.length?(e="function"==typeof n?n:l(+n),t=null,u):e},u.x0=function(t){return arguments.length?(e="function"==typeof t?t:l(+t),u):e},u.x1=function(e){return arguments.length?(t=null==e?null:"function"==typeof e?e:l(+e),u):t},u.y=function(e){return arguments.length?(n="function"==typeof e?e:l(+e),r=null,u):n},u.y0=function(e){return arguments.length?(n="function"==typeof e?e:l(+e),u):n},u.y1=function(e){return arguments.length?(r=null==e?null:"function"==typeof e?e:l(+e),u):r},u.lineX0=u.lineY0=function(){return f().x(e).y(n)},u.lineY1=function(){return f().x(e).y(r)},u.lineX1=function(){return f().x(t).y(n)},u.defined=function(e){return arguments.length?(i="function"==typeof e?e:l(!!e),u):i},u.curve=function(e){return arguments.length?(o=e,null!=a&&(s=o(a)),u):o},u.context=function(e){return arguments.length?(null==e?a=s=null:s=o(a=e),u):a},u}function F(e,t){return te?1:t>=e?0:NaN}function Y(e){return e}function B(){var e=Y,t=F,n=null,r=l(0),i=l(_),a=l(0);function o(o){var s,u,c,l,f,d=o.length,h=0,p=Array(d),b=Array(d),m=+r.apply(this,arguments),g=Math.min(_,Math.max(-_,i.apply(this,arguments)-m)),v=Math.min(Math.abs(g)/d,a.apply(this,arguments)),y=v*(g<0?-1:1);for(s=0;s0&&(h+=f);for(null!=t?p.sort(function(e,n){return t(b[e],b[n])}):null!=n&&p.sort(function(e,t){return n(o[e],o[t])}),s=0,c=h?(g-d*y)/h:0;s0?f*c:0)+y,b[u]={data:o[u],index:s,value:f,startAngle:m,endAngle:l,padAngle:v};return b}return o.value=function(t){return arguments.length?(e="function"==typeof t?t:l(+t),o):e},o.sortValues=function(e){return arguments.length?(t=e,n=null,o):t},o.sort=function(e){return arguments.length?(n=e,t=null,o):n},o.startAngle=function(e){return arguments.length?(r="function"==typeof e?e:l(+e),o):r},o.endAngle=function(e){return arguments.length?(i="function"==typeof e?e:l(+e),o):i},o.padAngle=function(e){return arguments.length?(a="function"==typeof e?e:l(+e),o):a},o}I.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._context.lineTo(e,t)}}};var U=$(D);function H(e){this._curve=e}function $(e){function t(t){return new H(e(t))}return t._curve=e,t}function z(e){var t=e.curve;return e.angle=e.x,delete e.x,e.radius=e.y,delete e.y,e.curve=function(e){return arguments.length?t($(e)):t()._curve},e}function G(){return z(R().curve(U))}function W(){var e=j().curve(U),t=e.curve,n=e.lineX0,r=e.lineX1,i=e.lineY0,a=e.lineY1;return e.angle=e.x,delete e.x,e.startAngle=e.x0,delete e.x0,e.endAngle=e.x1,delete e.x1,e.radius=e.y,delete e.y,e.innerRadius=e.y0,delete e.y0,e.outerRadius=e.y1,delete e.y1,e.lineStartAngle=function(){return z(n())},delete e.lineX0,e.lineEndAngle=function(){return z(r())},delete e.lineX1,e.lineInnerRadius=function(){return z(i())},delete e.lineY0,e.lineOuterRadius=function(){return z(a())},delete e.lineY1,e.curve=function(e){return arguments.length?t($(e)):t()._curve},e}function K(e,t){return[(t=+t)*Math.cos(e-=Math.PI/2),t*Math.sin(e)]}H.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(e,t){this._curve.point(t*Math.sin(e),-(t*Math.cos(e)))}};var V=Array.prototype.slice;function q(e){return e.source}function Z(e){return e.target}function X(e){var t=q,n=Z,r=N,i=P,a=null;function o(){var o,s=V.call(arguments),u=t.apply(this,s),l=n.apply(this,s);if(a||(a=o=c()),e(a,+r.apply(this,(s[0]=u,s)),+i.apply(this,s),+r.apply(this,(s[0]=l,s)),+i.apply(this,s)),o)return a=null,o+""||null}return o.source=function(e){return arguments.length?(t=e,o):t},o.target=function(e){return arguments.length?(n=e,o):n},o.x=function(e){return arguments.length?(r="function"==typeof e?e:l(+e),o):r},o.y=function(e){return arguments.length?(i="function"==typeof e?e:l(+e),o):i},o.context=function(e){return arguments.length?(a=null==e?null:e,o):a},o}function J(e,t,n,r,i){e.moveTo(t,n),e.bezierCurveTo(t=(t+r)/2,n,t,i,r,i)}function Q(e,t,n,r,i){e.moveTo(t,n),e.bezierCurveTo(t,n=(n+i)/2,r,n,r,i)}function ee(e,t,n,r,i){var a=K(t,n),o=K(t,n=(n+i)/2),s=K(r,n),u=K(r,i);e.moveTo(a[0],a[1]),e.bezierCurveTo(o[0],o[1],s[0],s[1],u[0],u[1])}function et(){return X(J)}function en(){return X(Q)}function er(){var e=X(ee);return e.angle=e.x,delete e.x,e.radius=e.y,delete e.y,e}let ei={draw:function(e,t){var n=Math.sqrt(t/y);e.moveTo(n,0),e.arc(0,0,n,0,_)}},ea={draw:function(e,t){var n=Math.sqrt(t/5)/2;e.moveTo(-3*n,-n),e.lineTo(-n,-n),e.lineTo(-n,-3*n),e.lineTo(n,-3*n),e.lineTo(n,-n),e.lineTo(3*n,-n),e.lineTo(3*n,n),e.lineTo(n,n),e.lineTo(n,3*n),e.lineTo(-n,3*n),e.lineTo(-n,n),e.lineTo(-3*n,n),e.closePath()}};var eo=Math.sqrt(1/3),es=2*eo;let eu={draw:function(e,t){var n=Math.sqrt(t/es),r=n*eo;e.moveTo(0,-n),e.lineTo(r,0),e.lineTo(0,n),e.lineTo(-r,0),e.closePath()}};var ec=.8908130915292852,el=Math.sin(y/10)/Math.sin(7*y/10),ef=Math.sin(_/10)*el,ed=-Math.cos(_/10)*el;let eh={draw:function(e,t){var n=Math.sqrt(t*ec),r=ef*n,i=ed*n;e.moveTo(0,-n),e.lineTo(r,i);for(var a=1;a<5;++a){var o=_*a/5,s=Math.cos(o),u=Math.sin(o);e.lineTo(u*n,-s*n),e.lineTo(s*r-u*i,u*r+s*i)}e.closePath()}},ep={draw:function(e,t){var n=Math.sqrt(t),r=-n/2;e.rect(r,r,n,n)}};var eb=Math.sqrt(3);let em={draw:function(e,t){var n=-Math.sqrt(t/(3*eb));e.moveTo(0,2*n),e.lineTo(-eb*n,-n),e.lineTo(eb*n,-n),e.closePath()}};var eg=-.5,ev=Math.sqrt(3)/2,ey=1/Math.sqrt(12),ew=(ey/2+1)*3;let e_={draw:function(e,t){var n=Math.sqrt(t/ew),r=n/2,i=n*ey,a=r,o=n*ey+n,s=-a,u=o;e.moveTo(r,i),e.lineTo(a,o),e.lineTo(s,u),e.lineTo(eg*r-ev*i,ev*r+eg*i),e.lineTo(eg*a-ev*o,ev*a+eg*o),e.lineTo(eg*s-ev*u,ev*s+eg*u),e.lineTo(eg*r+ev*i,eg*i-ev*r),e.lineTo(eg*a+ev*o,eg*o-ev*a),e.lineTo(eg*s+ev*u,eg*u-ev*s),e.closePath()}};var eE=[ei,ea,eu,ep,eh,em,e_];function eS(){var e=l(ei),t=l(64),n=null;function r(){var r;if(n||(n=r=c()),e.apply(this,arguments).draw(n,+t.apply(this,arguments)),r)return n=null,r+""||null}return r.type=function(t){return arguments.length?(e="function"==typeof t?t:l(t),r):e},r.size=function(e){return arguments.length?(t="function"==typeof e?e:l(+e),r):t},r.context=function(e){return arguments.length?(n=null==e?null:e,r):n},r}function ek(){}function ex(e,t,n){e._context.bezierCurveTo((2*e._x0+e._x1)/3,(2*e._y0+e._y1)/3,(e._x0+2*e._x1)/3,(e._y0+2*e._y1)/3,(e._x0+4*e._x1+t)/6,(e._y0+4*e._y1+n)/6)}function eT(e){this._context=e}function eM(e){return new eT(e)}function eO(e){this._context=e}function eA(e){return new eO(e)}function eL(e){this._context=e}function eC(e){return new eL(e)}function eI(e,t){this._basis=new eT(e),this._beta=t}eT.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:ex(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:ex(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}},eO.prototype={areaStart:ek,areaEnd:ek,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x2=e,this._y2=t;break;case 1:this._point=2,this._x3=e,this._y3=t;break;case 2:this._point=3,this._x4=e,this._y4=t,this._context.moveTo((this._x0+4*this._x1+e)/6,(this._y0+4*this._y1+t)/6);break;default:ex(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}},eL.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+e)/6,r=(this._y0+4*this._y1+t)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:ex(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}},eI.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var e=this._x,t=this._y,n=e.length-1;if(n>0)for(var r,i=e[0],a=t[0],o=e[n]-i,s=t[n]-a,u=-1;++u<=n;)r=u/n,this._basis.point(this._beta*e[u]+(1-this._beta)*(i+r*o),this._beta*t[u]+(1-this._beta)*(a+r*s));this._x=this._y=null,this._basis.lineEnd()},point:function(e,t){this._x.push(+e),this._y.push(+t)}};let eD=function e(t){function n(e){return 1===t?new eT(e):new eI(e,t)}return n.beta=function(t){return e(+t)},n}(.85);function eN(e,t,n){e._context.bezierCurveTo(e._x1+e._k*(e._x2-e._x0),e._y1+e._k*(e._y2-e._y0),e._x2+e._k*(e._x1-t),e._y2+e._k*(e._y1-n),e._x2,e._y2)}function eP(e,t){this._context=e,this._k=(1-t)/6}eP.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:eN(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2,this._x1=e,this._y1=t;break;case 2:this._point=3;default:eN(this,e,t)}this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};let eR=function e(t){function n(e){return new eP(e,0)}return n.tension=function(t){return e(+t)},n}(0);function ej(e,t){this._context=e,this._k=(1-t)/6}ej.prototype={areaStart:ek,areaEnd:ek,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x3=e,this._y3=t;break;case 1:this._point=2,this._context.moveTo(this._x4=e,this._y4=t);break;case 2:this._point=3,this._x5=e,this._y5=t;break;default:eN(this,e,t)}this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};let eF=function e(t){function n(e){return new ej(e,0)}return n.tension=function(t){return e(+t)},n}(0);function eY(e,t){this._context=e,this._k=(1-t)/6}eY.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:eN(this,e,t)}this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};let eB=function e(t){function n(e){return new eY(e,0)}return n.tension=function(t){return e(+t)},n}(0);function eU(e,t,n){var r=e._x1,i=e._y1,a=e._x2,o=e._y2;if(e._l01_a>v){var s=2*e._l01_2a+3*e._l01_a*e._l12_a+e._l12_2a,u=3*e._l01_a*(e._l01_a+e._l12_a);r=(r*s-e._x0*e._l12_2a+e._x2*e._l01_2a)/u,i=(i*s-e._y0*e._l12_2a+e._y2*e._l01_2a)/u}if(e._l23_a>v){var c=2*e._l23_2a+3*e._l23_a*e._l12_a+e._l12_2a,l=3*e._l23_a*(e._l23_a+e._l12_a);a=(a*c+e._x1*e._l23_2a-t*e._l12_2a)/l,o=(o*c+e._y1*e._l23_2a-n*e._l12_2a)/l}e._context.bezierCurveTo(r,i,a,o,e._x2,e._y2)}function eH(e,t){this._context=e,this._alpha=t}eH.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){if(e=+e,t=+t,this._point){var n=this._x2-e,r=this._y2-t;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3;default:eU(this,e,t)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};let e$=function e(t){function n(e){return t?new eH(e,t):new eP(e,0)}return n.alpha=function(t){return e(+t)},n}(.5);function ez(e,t){this._context=e,this._alpha=t}ez.prototype={areaStart:ek,areaEnd:ek,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(e,t){if(e=+e,t=+t,this._point){var n=this._x2-e,r=this._y2-t;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=e,this._y3=t;break;case 1:this._point=2,this._context.moveTo(this._x4=e,this._y4=t);break;case 2:this._point=3,this._x5=e,this._y5=t;break;default:eU(this,e,t)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};let eG=function e(t){function n(e){return t?new ez(e,t):new ej(e,0)}return n.alpha=function(t){return e(+t)},n}(.5);function eW(e,t){this._context=e,this._alpha=t}eW.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){if(e=+e,t=+t,this._point){var n=this._x2-e,r=this._y2-t;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:eU(this,e,t)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};let eK=function e(t){function n(e){return t?new eW(e,t):new eY(e,0)}return n.alpha=function(t){return e(+t)},n}(.5);function eV(e){this._context=e}function eq(e){return new eV(e)}function eZ(e){return e<0?-1:1}function eX(e,t,n){var r=e._x1-e._x0,i=t-e._x1,a=(e._y1-e._y0)/(r||i<0&&-0),o=(n-e._y1)/(i||r<0&&-0),s=(a*i+o*r)/(r+i);return(eZ(a)+eZ(o))*Math.min(Math.abs(a),Math.abs(o),.5*Math.abs(s))||0}function eJ(e,t){var n=e._x1-e._x0;return n?(3*(e._y1-e._y0)/n-t)/2:t}function eQ(e,t,n){var r=e._x0,i=e._y0,a=e._x1,o=e._y1,s=(a-r)/3;e._context.bezierCurveTo(r+s,i+s*t,a-s,o-s*n,a,o)}function e1(e){this._context=e}function e0(e){this._context=new e2(e)}function e2(e){this._context=e}function e3(e){return new e1(e)}function e4(e){return new e0(e)}function e5(e){this._context=e}function e6(e){var t,n,r=e.length-1,i=Array(r),a=Array(r),o=Array(r);for(i[0]=0,a[0]=2,o[0]=e[0]+2*e[1],t=1;t=0;--t)i[t]=(o[t]-i[t+1])/a[t];for(t=0,a[r-1]=(e[r]+i[r-1])/2;t1)for(var n,r,i,a=1,o=e[t[0]],s=o.length;a=0;)n[t]=t;return n}function ti(e,t){return e[t]}function ta(){var e=l([]),t=tr,n=tn,r=ti;function i(i){var a,o,s=e.apply(this,arguments),u=i.length,c=s.length,l=Array(c);for(a=0;a0){for(var n,r,i,a=0,o=e[0].length;a0)for(var n,r,i,a,o,s,u=0,c=e[t[0]].length;u0?(r[0]=a,r[1]=a+=i):i<0?(r[1]=o,r[0]=o+=i):(r[0]=0,r[1]=i)}function tu(e,t){if((n=e.length)>0){for(var n,r=0,i=e[t[0]],a=i.length;r0&&(r=(n=e[t[0]]).length)>0){for(var n,r,i,a=0,o=1;oa&&(a=t,r=n);return r}function td(e){var t=e.map(th);return tr(e).sort(function(e,n){return t[e]-t[n]})}function th(e){for(var t,n=0,r=-1,i=e.length;++r=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,t),this._context.lineTo(e,t);else{var n=this._x*(1-this._t)+e*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,t)}}this._x=e,this._y=t}}},35374(e,t,n){"use strict";n.d(t,{B7:()=>m,HT:()=>g,zO:()=>p});var r,i,a=0,o=0,s=0,u=1e3,c=0,l=0,f=0,d="object"==typeof performance&&performance.now?performance:Date,h="object"==typeof window&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(e){setTimeout(e,17)};function p(){return l||(h(b),l=d.now()+f)}function b(){l=0}function m(){this._call=this._time=this._next=null}function g(e,t,n){var r=new m;return r.restart(e,t,n),r}function v(){p(),++a;for(var e,t=r;t;)(e=l-t._time)>=0&&t._call.call(null,e),t=t._next;--a}function y(){l=(c=d.now())+f,a=o=0;try{v()}finally{a=0,_(),l=0}}function w(){var e=d.now(),t=e-c;t>u&&(f-=t,c=e)}function _(){for(var e,t,n=r,a=1/0;n;)n._call?(a>n._time&&(a=n._time),e=n,n=n._next):(t=n._next,n._next=null,n=e?e._next=t:r=t);i=e,E(a)}function E(e){if(!a){var t;o&&(o=clearTimeout(o)),e-l>24?(e<1/0&&(o=setTimeout(y,e-d.now()-f)),s&&(s=clearInterval(s))):(s||(c=d.now(),s=setInterval(w,u)),a=1,h(y))}}m.prototype=g.prototype={constructor:m,restart:function(e,t,n){if("function"!=typeof e)throw TypeError("callback is not a function");n=(null==n?p():+n)+(null==t?0:+t),this._next||i===this||(i?i._next=this:r=this,i=this),this._call=e,this._time=n,E()},stop:function(){this._call&&(this._call=null,this._time=1/0,E())}}},76626(e,t,n){"use strict";n.r(t),n.d(t,{zoom:()=>t5,zoomIdentity:()=>tq,zoomTransform:()=>tZ});var r,i,a,o,s=n(92626),u=n(44266),c=Math.SQRT2,l=2,f=4,d=1e-12;function h(e){return((e=Math.exp(e))+1/e)/2}function p(e){return((e=Math.exp(e))-1/e)/2}function b(e){return((e=Math.exp(2*e))-1)/(e+1)}function m(e,t){var n,r,i=e[0],a=e[1],o=e[2],s=t[0],u=t[1],m=t[2],g=s-i,v=u-a,y=g*g+v*v;if(yT)throw Error("too late; already scheduled");return n}function P(e,t){var n=R(e,t);if(n.state>A)throw Error("too late; already running");return n}function R(e,t){var n=e.__transition;if(!n||!(n=n[t]))throw Error("transition not found");return n}function j(e,t,n){var r,i=e.__transition;function a(e){n.state=M,n.timer.restart(o,n.delay,n.time),n.delay<=e&&o(e-n.delay)}function o(a){var c,l,f,d;if(n.state!==M)return u();for(c in i)if((d=i[c]).name===n.name){if(d.state===A)return S(o);d.state===L?(d.state=I,d.timer.stop(),d.on.call("interrupt",e,e.__data__,d.index,d.group),delete i[c]):+cO&&n.state180?t+=360:t-e>180&&(e+=360),a.push({i:n.push(i(n)+"rotate(",null,r)-2,x:B(e,t)})):t&&n.push(i(n)+"rotate("+t+r)}function s(e,t,n,a){e!==t?a.push({i:n.push(i(n)+"skewX(",null,r)-2,x:B(e,t)}):t&&n.push(i(n)+"skewX("+t+r)}function u(e,t,n,r,a,o){if(e!==n||t!==r){var s=a.push(i(a)+"scale(",null,",",null,")");o.push({i:s-4,x:B(e,n)},{i:s-2,x:B(t,r)})}else(1!==n||1!==r)&&a.push(i(a)+"scale("+n+","+r+")")}return function(t,n){var r=[],i=[];return t=e(t),n=e(n),a(t.translateX,t.translateY,n.translateX,n.translateY,r,i),o(t.rotate,n.rotate,r,i),s(t.skewX,n.skewX,r,i),u(t.scaleX,t.scaleY,n.scaleX,n.scaleY,r,i),t=n=null,function(e){for(var t,n=-1,a=i.length;++n>8&15|t>>4&240,t>>4&15|240&t,(15&t)<<4|15&t,1):8===n?e_(t>>24&255,t>>16&255,t>>8&255,(255&t)/255):4===n?e_(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|240&t,((15&t)<<4|15&t)/255):null):(t=ec.exec(e))?new ek(t[1],t[2],t[3],1):(t=el.exec(e))?new ek(255*t[1]/100,255*t[2]/100,255*t[3]/100,1):(t=ef.exec(e))?e_(t[1],t[2],t[3],t[4]):(t=ed.exec(e))?e_(255*t[1]/100,255*t[2]/100,255*t[3]/100,t[4]):(t=eh.exec(e))?eO(t[1],t[2]/100,t[3]/100,1):(t=ep.exec(e))?eO(t[1],t[2]/100,t[3]/100,t[4]):eb.hasOwnProperty(e)?ew(eb[e]):"transparent"===e?new ek(NaN,NaN,NaN,0):null}function ew(e){return new ek(e>>16&255,e>>8&255,255&e,1)}function e_(e,t,n,r){return r<=0&&(e=t=n=NaN),new ek(e,t,n,r)}function eE(e){return(e instanceof en||(e=ey(e)),e)?(e=e.rgb(),new ek(e.r,e.g,e.b,e.opacity)):new ek}function eS(e,t,n,r){return 1===arguments.length?eE(e):new ek(e,t,n,null==r?1:r)}function ek(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}function ex(){return"#"+eM(this.r)+eM(this.g)+eM(this.b)}function eT(){var e=this.opacity;return(1===(e=isNaN(e)?1:Math.max(0,Math.min(1,e)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===e?")":", "+e+")")}function eM(e){return((e=Math.max(0,Math.min(255,Math.round(e)||0)))<16?"0":"")+e.toString(16)}function eO(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new eC(e,t,n,r)}function eA(e){if(e instanceof eC)return new eC(e.h,e.s,e.l,e.opacity);if(e instanceof en||(e=ey(e)),!e)return new eC;if(e instanceof eC)return e;var t=(e=e.rgb()).r/255,n=e.g/255,r=e.b/255,i=Math.min(t,n,r),a=Math.max(t,n,r),o=NaN,s=a-i,u=(a+i)/2;return s?(o=t===a?(n-r)/s+(n0&&u<1?0:o,new eC(o,s,u,e.opacity)}function eL(e,t,n,r){return 1===arguments.length?eA(e):new eC(e,t,n,null==r?1:r)}function eC(e,t,n,r){this.h=+e,this.s=+t,this.l=+n,this.opacity=+r}function eI(e,t,n){return(e<60?t+(n-t)*e/60:e<180?n:e<240?t+(n-t)*(240-e)/60:t)*255}function eD(e,t,n,r,i){var a=e*e,o=a*e;return((1-3*e+3*a-o)*t+(4-6*a+3*o)*n+(1+3*e+3*a-3*o)*r+o*i)/6}function eN(e){var t=e.length-1;return function(n){var r=n<=0?n=0:n>=1?(n=1,t-1):Math.floor(n*t),i=e[r],a=e[r+1],o=r>0?e[r-1]:2*i-a,s=r=240?e-240:e+120,i,r),eI(e,i,r),eI(e<120?e+240:e-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var e=this.opacity;return(1===(e=isNaN(e)?1:Math.max(0,Math.min(1,e)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===e?")":", "+e+")")}}));let eU=function e(t){var n=eY(1);function r(e,t){var r=n((e=eS(e)).r,(t=eS(t)).r),i=n(e.g,t.g),a=n(e.b,t.b),o=eB(e.opacity,t.opacity);return function(t){return e.r=r(t),e.g=i(t),e.b=a(t),e.opacity=o(t),e+""}}return r.gamma=e,r}(1);function eH(e){return function(t){var n,r,i=t.length,a=Array(i),o=Array(i),s=Array(i);for(n=0;na&&(i=t.slice(a,i),s[o]?s[o]+=i:s[++o]=i),(n=n[0])===(r=r[0])?s[o]?s[o]+=r:s[++o]=r:(s[++o]=null,u.push({i:o,x:B(n,r)})),a=ez.lastIndex;return a=0&&(e=e.slice(0,t)),!e||"start"===e})}function tc(e,t,n){var r,i,a=tu(t)?N:P;return function(){var o=a(this,e),s=o.on;s!==r&&(i=(r=s).copy()).on(t,n),o.on=i}}function tl(e,t){var n=this._id;return arguments.length<2?R(this.node(),n).on.on(e):this.each(tc(n,e,t))}function tf(e){return function(){var t=this.parentNode;for(var n in this.__transition)if(+n!==e)return;t&&t.removeChild(this)}}function td(){return this.on("end.remove",tf(this._id))}var th=n(82634);function tp(e){var t=this._name,n=this._id;"function"!=typeof e&&(e=(0,th.Z)(e));for(var r=this._groups,i=r.length,a=Array(i),o=0;or?(r+i)/2:Math.min(0,r)||Math.max(0,i),o>a?(a+o)/2:Math.min(0,a)||Math.max(0,o))}function t5(){var e,t,n=tQ,r=t1,i=t4,a=t2,o=t3,c=[0,1/0],l=[[-1/0,-1/0],[1/0,1/0]],f=250,d=m,h=(0,s.Z)("start","zoom","end"),p=500,b=150,_=0;function E(e){e.property("__zoom",t0).on("wheel.zoom",A).on("mousedown.zoom",L).on("dblclick.zoom",C).filter(o).on("touchstart.zoom",I).on("touchmove.zoom",D).on("touchend.zoom touchcancel.zoom",N).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function S(e,t){return(t=Math.max(c[0],Math.min(c[1],t)))===e.k?e:new tV(t,e.x,e.y)}function k(e,t,n){var r=t[0]-n[0]*e.k,i=t[1]-n[1]*e.k;return r===e.x&&i===e.y?e:new tV(e.k,r,i)}function x(e){return[(+e[0][0]+ +e[1][0])/2,(+e[0][1]+ +e[1][1])/2]}function T(e,t,n){e.on("start.zoom",function(){M(this,arguments).start()}).on("interrupt.zoom end.zoom",function(){M(this,arguments).end()}).tween("zoom",function(){var e=this,i=arguments,a=M(e,i),o=r.apply(e,i),s=null==n?x(o):"function"==typeof n?n.apply(e,i):n,u=Math.max(o[1][0]-o[0][0],o[1][1]-o[0][1]),c=e.__zoom,l="function"==typeof t?t.apply(e,i):t,f=d(c.invert(s).concat(u/c.k),l.invert(s).concat(u/l.k));return function(e){if(1===e)e=l;else{var t=f(e),n=u/t[2];e=new tV(n,s[0]-t[0]*n,s[1]-t[1]*n)}a.zoom(null,e)}})}function M(e,t,n){return!n&&e.__zooming||new O(e,t)}function O(e,t){this.that=e,this.args=t,this.active=0,this.extent=r.apply(e,t),this.taps=0}function A(){if(n.apply(this,arguments)){var e=M(this,arguments),t=this.__zoom,r=Math.max(c[0],Math.min(c[1],t.k*Math.pow(2,a.apply(this,arguments)))),o=(0,v.Z)(this);if(e.wheel)(e.mouse[0][0]!==o[0]||e.mouse[0][1]!==o[1])&&(e.mouse[1]=t.invert(e.mouse[0]=o)),clearTimeout(e.wheel);else{if(t.k===r)return;e.mouse=[o,t.invert(o)],F(this),e.start()}tJ(),e.wheel=setTimeout(s,b),e.zoom("mouse",i(k(S(t,r),e.mouse[0],e.mouse[1]),e.extent,l))}function s(){e.wheel=null,e.end()}}function L(){if(!t&&n.apply(this,arguments)){var e=M(this,arguments,!0),r=(0,y.Z)(g.B.view).on("mousemove.zoom",c,!0).on("mouseup.zoom",f,!0),a=(0,v.Z)(this),o=g.B.clientX,s=g.B.clientY;(0,u.Z)(g.B.view),tX(),e.mouse=[a,this.__zoom.invert(a)],F(this),e.start()}function c(){if(tJ(),!e.moved){var t=g.B.clientX-o,n=g.B.clientY-s;e.moved=t*t+n*n>_}e.zoom("mouse",i(k(e.that.__zoom,e.mouse[0]=(0,v.Z)(e.that),e.mouse[1]),e.extent,l))}function f(){r.on("mousemove.zoom mouseup.zoom",null),(0,u.D)(g.B.view,e.moved),tJ(),e.end()}}function C(){if(n.apply(this,arguments)){var e=this.__zoom,t=(0,v.Z)(this),a=e.invert(t),o=e.k*(g.B.shiftKey?.5:2),s=i(k(S(e,o),t,a),r.apply(this,arguments),l);tJ(),f>0?(0,y.Z)(this).transition().duration(f).call(T,s,t):(0,y.Z)(this).call(E.transform,s)}}function I(){if(n.apply(this,arguments)){var t,r,i,a,o=g.B.touches,s=o.length,u=M(this,arguments,g.B.changedTouches.length===s);for(tX(),r=0;r=0?i=setTimeout(r,t-c):(i=null,n||(u=e.apply(o,a),o=a=null))}null==t&&(t=100);var i,a,o,s,u,c=function(){o=this,a=arguments,s=Date.now();var c=n&&!i;return i||(i=setTimeout(r,t)),c&&(u=e.apply(o,a),o=a=null),u};return c.clear=function(){i&&(clearTimeout(i),i=null)},c.flush=function(){i&&(u=e.apply(o,a),o=a=null,clearTimeout(i),i=null)},c}t.debounce=t,e.exports=t},94863:function(e){var t,n;t=this,n=function(){"use strict";var e=function(e){return t(e)&&!n(e)};function t(e){return!!e&&"object"==typeof e}function n(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||i(e)}var r="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function i(e){return e.$$typeof===r}function a(e){return Array.isArray(e)?[]:{}}function o(e,t){return!1!==t.clone&&t.isMergeableObject(e)?d(a(e),e,t):e}function s(e,t,n){return e.concat(t).map(function(e){return o(e,n)})}function u(e,t){if(!t.customMerge)return d;var n=t.customMerge(e);return"function"==typeof n?n:d}function c(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter(function(t){return e.propertyIsEnumerable(t)}):[]}function l(e){return Object.keys(e).concat(c(e))}function f(e,t,n){var r={};return n.isMergeableObject(e)&&l(e).forEach(function(t){r[t]=o(e[t],n)}),l(t).forEach(function(i){n.isMergeableObject(t[i])&&e[i]?r[i]=u(i,n)(e[i],t[i],n):r[i]=o(t[i],n)}),r}function d(t,n,r){(r=r||{}).arrayMerge=r.arrayMerge||s,r.isMergeableObject=r.isMergeableObject||e;var i=Array.isArray(n);return i!==Array.isArray(t)?o(n,r):i?r.arrayMerge(t,n,r):f(t,n,r)}return d.all=function(e,t){if(!Array.isArray(e))throw Error("first argument should be an array");return e.reduce(function(e,n){return d(e,n,t)},{})},d},e.exports=n()},7624(e,t){"use strict";function n(e){return e===e.window?e:9===e.nodeType&&(e.defaultView||e.parentWindow)}t.__esModule=!0,t.default=n,e.exports=t.default},87797(e,t,n){"use strict";var r=n(95318);t.__esModule=!0,t.default=s;var i=r(n(53497)),a=/^(top|right|bottom|left)$/,o=/^([+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/i;function s(e){if(!e)throw TypeError("No Element passed to `getComputedStyle()`");var t=e.ownerDocument;return"defaultView"in t?t.defaultView.opener?e.ownerDocument.defaultView.getComputedStyle(e,null):window.getComputedStyle(e,null):{getPropertyValue:function(t){var n=e.style;"float"==(t=(0,i.default)(t))&&(t="styleFloat");var r=e.currentStyle[t]||null;if(null==r&&n&&n[t]&&(r=n[t]),o.test(r)&&!a.test(t)){var s=n.left,u=e.runtimeStyle,c=u&&u.left;c&&(u.left=e.currentStyle.left),n.left="fontSize"===t?"1em":r,r=n.pixelLeft+"px",n.left=s,c&&(u.left=c)}return r}}}e.exports=t.default},10162(e,t,n){"use strict";var r=n(95318);t.__esModule=!0,t.default=l;var i=r(n(53497)),a=r(n(24403)),o=r(n(87797)),s=r(n(91760)),u=n(20702),c=r(n(43293));function l(e,t,n){var r="",l="",f=t;if("string"==typeof t){if(void 0===n)return e.style[(0,i.default)(t)]||(0,o.default)(e).getPropertyValue((0,a.default)(t));(f={})[t]=n}Object.keys(f).forEach(function(t){var n=f[t];n||0===n?(0,c.default)(t)?l+=t+"("+n+") ":r+=(0,a.default)(t)+": "+n+";":(0,s.default)(e,(0,a.default)(t))}),l&&(r+=u.transform+": "+l+";"),e.style.cssText+=";"+r}e.exports=t.default},91760(e,t){"use strict";function n(e,t){return"removeProperty"in e.style?e.style.removeProperty(t):e.style.removeAttribute(t)}t.__esModule=!0,t.default=n,e.exports=t.default},43293(e,t){"use strict";t.__esModule=!0,t.default=r;var n=/^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i;function r(e){return!!(e&&n.test(e))}e.exports=t.default},20702(e,t,n){"use strict";var r,i,a,o,s,u,c,l,f,d,h,p=n(95318);t.__esModule=!0,t.default=t.animationEnd=t.animationDelay=t.animationTiming=t.animationDuration=t.animationName=t.transitionEnd=t.transitionDuration=t.transitionDelay=t.transitionTiming=t.transitionProperty=t.transform=void 0;var b=p(n(50139)),m="transform";if(t.transform=m,t.animationEnd=a,t.transitionEnd=i,t.transitionDelay=c,t.transitionTiming=u,t.transitionDuration=s,t.transitionProperty=o,t.animationDelay=h,t.animationTiming=d,t.animationDuration=f,t.animationName=l,b.default){var g=y();r=g.prefix,t.transitionEnd=i=g.transitionEnd,t.animationEnd=a=g.animationEnd,t.transform=m=r+"-"+m,t.transitionProperty=o=r+"-transition-property",t.transitionDuration=s=r+"-transition-duration",t.transitionDelay=c=r+"-transition-delay",t.transitionTiming=u=r+"-transition-timing-function",t.animationName=l=r+"-animation-name",t.animationDuration=f=r+"-animation-duration",t.animationTiming=d=r+"-animation-delay",t.animationDelay=h=r+"-animation-timing-function"}var v={transform:m,end:i,property:o,timing:u,delay:c,duration:s};function y(){for(var e,t,n=document.createElement("div").style,r={O:function(e){return"o"+e.toLowerCase()},Moz:function(e){return e.toLowerCase()},Webkit:function(e){return"webkit"+e},ms:function(e){return"MS"+e}},i=Object.keys(r),a="",o=0;o0&&void 0!==arguments[0]?arguments[0]:{},r=n.defaultLayoutOptions,a=void 0===r?{}:r,s=n.algorithms,u=void 0===s?["layered","stress","mrtree","radial","force","disco","sporeOverlap","sporeCompaction","rectpacking"]:s,c=n.workerFactory,l=n.workerUrl;if(i(this,e),this.defaultLayoutOptions=a,this.initialized=!1,void 0===l&&void 0===c)throw Error("Cannot construct an ELK without both 'workerUrl' and 'workerFactory'.");var f=c;void 0!==l&&void 0===c&&(f=function(e){return new Worker(e)});var d=f(l);if("function"!=typeof d.postMessage)throw TypeError("Created worker does not provide the required 'postMessage' function.");this.worker=new o(d),this.worker.postMessage({cmd:"register",algorithms:u}).then(function(e){return t.initialized=!0}).catch(console.err)}return r(e,[{key:"layout",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.layoutOptions,r=void 0===n?this.defaultLayoutOptions:n,i=t.logging,a=void 0!==i&&i,o=t.measureExecutionTime,s=void 0!==o&&o;return e?this.worker.postMessage({cmd:"layout",graph:e,layoutOptions:r,options:{logging:a,measureExecutionTime:s}}):Promise.reject(Error("Missing mandatory parameter 'graph'."))}},{key:"knownLayoutAlgorithms",value:function(){return this.worker.postMessage({cmd:"algorithms"})}},{key:"knownLayoutOptions",value:function(){return this.worker.postMessage({cmd:"options"})}},{key:"knownLayoutCategories",value:function(){return this.worker.postMessage({cmd:"categories"})}},{key:"terminateWorker",value:function(){this.worker.terminate()}}]),e}();n.default=a;var o=function(){function e(t){var n=this;if(i(this,e),void 0===t)throw Error("Missing mandatory parameter 'worker'.");this.resolvers={},this.worker=t,this.worker.onmessage=function(e){setTimeout(function(){n.receive(n,e)},0)}}return r(e,[{key:"postMessage",value:function(e){var t=this.id||0;this.id=t+1,e.id=t;var n=this;return new Promise(function(r,i){n.resolvers[t]=function(e,t){e?(n.convertGwtStyleError(e),i(e)):r(t)},n.worker.postMessage(e)})}},{key:"receive",value:function(e,t){var n=t.data,r=e.resolvers[n.id];r&&(delete e.resolvers[n.id],n.error?r(n.error):r(null,n.data))}},{key:"terminate",value:function(){this.worker.terminate&&this.worker.terminate()}},{key:"convertGwtStyleError",value:function(e){if(e){var t=e.__java$exception;t&&(t.cause&&t.cause.backingJsObject&&(e.cause=t.cause.backingJsObject,this.convertGwtStyleError(e.cause)),delete e.__java$exception)}}}]),e}()},{}],2:[function(e,t,n){"use strict";var r=e("./elk-api.js").default;Object.defineProperty(t.exports,"__esModule",{value:!0}),t.exports=r,r.default=r},{"./elk-api.js":1}]},{},[2])(2)},e.exports=t()},55273(e,t,n){"use strict";function r(){}function i(){}function a(){}function o(){}function s(){}function u(){}function c(){}function l(){}function f(){}function d(){}function h(){}function p(){}function b(){}function m(){}function g(){}function v(){}function y(){}function w(){}function _(){}function E(){}function S(){}function k(){}function x(){}function T(){}function M(){}function O(){}function A(){}function L(){}function C(){}function I(){}function D(){}function N(){}function P(){}function R(){}function j(){}function F(){}function Y(){}function B(){}function U(){}function H(){}function $(){}function z(){}function G(){}function W(){}function K(){}function V(){}function q(){}function Z(){}function X(){}function J(){}function Q(){}function ee(){}function et(){}function en(){}function er(){}function ei(){}function ea(){}function eo(){}function es(){}function eu(){}function ec(){}function el(){}function ef(){}function ed(){}function eh(){}function ep(){}function eb(){}function em(){}function eg(){}function ev(){}function ey(){}function ew(){}function e_(){}function eE(){}function eS(){}function ek(){}function ex(){}function eT(){}function eM(){}function eO(){}function eA(){}function eL(){}function eC(){}function eI(){}function eD(){}function eN(){}function eP(){}function eR(){}function ej(){}function eF(){}function eY(){}function eB(){}function eU(){}function eH(){}function e$(){}function ez(){}function eG(){}function eW(){}function eK(){}function eV(){}function eq(){}function eZ(){}function eX(){}function eJ(){}function eQ(){}function e1(){}function e0(){}function e2(){}function e3(){}function e4(){}function e5(){}function e6(){}function e9(){}function e8(){}function e7(){}function te(){}function tt(){}function tn(){}function tr(){}function ti(){}function ta(){}function to(){}function ts(){}function tu(){}function tc(){}function tl(){}function tf(){}function td(){}function th(){}function tp(){}function tb(){}function tm(){}function tg(){}function tv(){}function ty(){}function tw(){}function t_(){}function tE(){}function tS(){}function tk(){}function tx(){}function tT(){}function tM(){}function tO(){}function tA(){}function tL(){}function tC(){}function tI(){}function tD(){}function tN(){}function tP(){}function tR(){}function tj(){}function tF(){}function tY(){}function tB(){}function tU(){}function tH(){}function t$(){}function tz(){}function tG(){}function tW(){}function tK(){}function tV(){}function tq(){}function tZ(){}function tX(){}function tJ(){}function tQ(){}function t1(){}function t0(){}function t2(){}function t3(){}function t4(){}function t5(){}function t6(){}function t9(){}function t8(){}function t7(){}function ne(){}function nt(){}function nn(){}function nr(){}function ni(){}function na(){}function no(){}function ns(){}function nu(){}function nc(){}function nl(){}function nf(){}function nd(){}function nh(){}function np(){}function nb(){}function nm(){}function ng(){}function nv(){}function ny(){}function nw(){}function n_(){}function nE(){}function nS(){}function nk(){}function nx(){}function nT(){}function nM(){}function nO(){}function nA(){}function nL(){}function nC(){}function nI(){}function nD(){}function nN(){}function nP(){}function nR(){}function nj(){}function nF(){}function nY(){}function nB(){}function nU(){}function nH(){}function n$(){}function nz(){}function nG(){}function nW(){}function nK(){}function nV(){}function nq(){}function nZ(){}function nX(){}function nJ(){}function nQ(){}function n1(){}function n0(){}function n2(){}function n3(){}function n4(){}function n5(){}function n6(){}function n9(){}function n8(){}function n7(){}function re(){}function rt(){}function rn(){}function rr(){}function ri(){}function ra(){}function ro(){}function rs(){}function ru(){}function rc(){}function rl(){}function rf(){}function rd(){}function rh(){}function rp(){}function rb(){}function rm(){}function rg(){}function rv(){}function ry(){}function rw(){}function r_(){}function rE(){}function rS(){}function rk(){}function rx(){}function rT(){}function rM(){}function rO(){}function rA(){}function rL(){}function rC(){}function rI(){}function rD(){}function rN(){}function rP(){}function rR(){}function rj(){}function rF(){}function rY(){}function rB(){}function rU(){}function rH(){}function r$(){}function rz(){}function rG(){}function rW(){}function rK(){}function rV(){}function rq(){}function rZ(){}function rX(){}function rJ(){}function rQ(){}function r1(){}function r0(){}function r2(){}function r3(){}function r4(){}function r5(){}function r6(){}function r9(){}function r8(){}function r7(){}function ie(){}function it(){}function ir(){}function ii(){}function ia(){}function io(){}function is(){}function iu(){}function ic(){}function il(){}function id(){}function ih(){}function ip(){}function ib(){}function im(){}function ig(){}function iv(){}function iy(){}function iw(){}function i_(){}function iE(){}function iS(){}function ik(){}function ix(){}function iT(){}function iM(){}function iO(){}function iA(){}function iL(){}function iC(){}function iI(){}function iD(){}function iN(){}function iP(){}function iR(){}function ij(){}function iF(){}function iY(){}function iB(){}function iU(){}function iH(){}function i$(){}function iz(){}function iG(){}function iW(){}function iK(){}function iV(){}function iq(){}function iZ(){}function iX(){}function iJ(){}function iQ(){}function i1(){}function i0(){}function i2(){}function i3(){}function i4(){}function i5(){}function i6(){}function i9(){}function i8(){}function i7(){}function ae(){}function at(){}function an(){}function ar(){}function ai(){}function aa(){}function ao(){}function as(){}function au(){}function ac(){}function al(){}function af(){}function ad(){}function ah(){}function ap(){}function ab(){}function am(){}function ag(){}function av(){}function ay(){}function aw(){}function a_(){}function aE(){}function aS(){}function ak(){}function ax(){}function aT(){}function aM(){}function aO(){}function aA(){}function aL(){}function aC(){}function aI(){}function aD(){}function aN(){}function aP(){}function aR(){}function aj(){}function aF(){}function aY(){}function aB(){}function aU(){}function aH(){}function a$(){}function az(){}function aG(){}function aW(){}function aK(){}function aV(){}function aq(){}function aZ(){}function aX(){}function aJ(){}function aQ(){}function a1(){}function a0(){}function a2(){}function a3(){}function a4(){}function a5(){}function a6(){}function a9(){}function a8(){}function a7(){}function oe(){}function ot(){}function on(){}function or(){}function oi(){}function oa(){}function oo(){}function os(){}function ou(){}function oc(){}function ol(){}function of(){}function od(){}function oh(){}function op(){}function ob(){}function om(){}function og(){}function ov(){}function oy(){}function ow(){}function o_(){}function oE(){}function oS(){}function ok(){}function ox(){}function oT(){}function oM(){}function oO(){}function oA(){}function oL(){}function oC(){}function oI(){}function oD(){}function oN(){}function oP(){}function oR(){}function oj(){}function oF(){}function oY(){}function oB(){}function oU(){}function oH(){}function o$(){}function oz(){}function oG(){}function oW(){}function oK(){}function oV(){}function oq(){}function oZ(){}function oX(){}function oJ(){}function oQ(){}function o1(){}function o0(){}function o2(){}function o3(){}function o4(){}function o5(){}function o6(){}function o9(){}function o8(){}function o7(){}function se(){}function st(){}function sn(){}function sr(){}function si(){}function sa(){}function so(){}function ss(){}function su(){}function sc(){}function sl(){}function sf(){}function sd(){}function sh(){}function sp(){}function sb(){}function sm(){}function sg(){}function sv(){}function sy(){}function sw(){}function s_(){}function sE(){}function sS(){}function sk(){}function sx(){}function sT(){}function sM(){}function sO(){}function sA(){}function sL(){}function sC(){}function sI(){}function sD(){}function sN(){}function sP(){}function sR(){}function sj(){}function sF(){}function sY(){}function sB(){}function sU(){}function sH(){}function s$(){}function sz(){}function sG(){}function sW(){}function sK(){}function sV(){}function sq(){}function sZ(){}function sX(){}function sJ(){}function sQ(){}function s1(){}function s0(){}function s2(){}function s3(){}function s4(){}function s5(){}function s6(){}function s9(){}function s8(){}function s7(){}function ue(){}function ut(){}function un(){}function ur(){}function ui(){}function ua(){}function uo(){}function us(){}function uu(){}function uc(){}function ul(){}function uf(){}function ud(){}function uh(){}function up(){}function ub(){}function um(){}function ug(){}function uv(){}function uy(){}function uw(){}function u_(){}function uE(){}function uS(){}function uk(){}function ux(){}function uT(){}function uM(){}function uO(){}function uA(){}function uL(){}function uC(){}function uI(){}function uD(){}function uN(){}function uP(){}function uR(){}function uj(){}function uF(){}function uY(){}function uB(){}function uU(){}function uH(){}function u$(){}function uz(){}function uG(){}function uW(){}function uK(){}function uV(){}function uq(){}function uZ(){}function uX(){}function uJ(){}function uQ(){}function u1(){}function u0(){}function u2(){}function u3(){}function u4(){}function u5(){}function u6(){}function u9(){}function u8(){}function u7(){}function ce(){}function ct(){}function cn(e){}function cr(e){}function ci(){m4()}function ca(){eug()}function co(){epz()}function cs(){evw()}function cu(){eEg()}function cc(){eCk()}function cl(){egA()}function cf(){egq()}function cd(){_O()}function ch(){_k()}function cp(){DR()}function cb(){_A()}function cm(){erJ()}function cg(){_C()}function cv(){Xi()}function cy(){en6()}function cw(){Jb()}function c_(){Gw()}function cE(){euv()}function cS(){e_z()}function ck(){en9()}function cx(){K9()}function cT(){eBH()}function cM(){egP()}function cO(){G_()}function cA(){eBy()}function cL(){Gv()}function cC(){en8()}function cI(){eoz()}function cD(){Gx()}function cN(){JK()}function cP(){_I()}function cR(){eTK()}function cj(){egj()}function cF(){eiQ()}function cY(){e_L()}function cB(){eCT()}function cU(){ebJ()}function cH(){eTj()}function c$(){eaB()}function cz(){GS()}function cG(){eDn()}function cW(){eTU()}function cK(){eMK()}function cV(){J1()}function cq(){e_C()}function cZ(){eBB()}function cX(){euw()}function cJ(){ed5()}function cQ(){ePm()}function c1(){De()}function c0(){eiM()}function c2(){eD4()}function c3(e){BJ(e)}function c4(e){this.a=e}function c5(e){this.a=e}function c6(e){this.a=e}function c9(e){this.a=e}function c8(e){this.a=e}function c7(e){this.a=e}function le(e){this.a=e}function lt(e){this.a=e}function ln(e){this.a=e}function lr(e){this.a=e}function li(e){this.a=e}function la(e){this.a=e}function lo(e){this.a=e}function ls(e){this.a=e}function lu(e){this.a=e}function lc(e){this.a=e}function ll(e){this.a=e}function lf(e){this.a=e}function ld(e){this.a=e}function lh(e){this.a=e}function lp(e){this.a=e}function lb(e){this.b=e}function lm(e){this.c=e}function lg(e){this.a=e}function lv(e){this.a=e}function ly(e){this.a=e}function lw(e){this.a=e}function l_(e){this.a=e}function lE(e){this.a=e}function lS(e){this.a=e}function lk(e){this.a=e}function lx(e){this.a=e}function lT(e){this.a=e}function lM(e){this.a=e}function lO(e){this.a=e}function lA(e){this.a=e}function lL(e){this.a=e}function lC(e){this.a=e}function lI(e){this.a=e}function lD(e){this.a=e}function lN(){this.a=[]}function lP(e,t){e.a=t}function lR(e,t){e.a=t}function lj(e,t){e.b=t}function lF(e,t){e.b=t}function lY(e,t){e.b=t}function lB(e,t){e.j=t}function lU(e,t){e.g=t}function lH(e,t){e.i=t}function l$(e,t){e.c=t}function lz(e,t){e.d=t}function lG(e,t){e.d=t}function lW(e,t){e.c=t}function lK(e,t){e.k=t}function lV(e,t){e.c=t}function lq(e,t){e.c=t}function lZ(e,t){e.a=t}function lX(e,t){e.a=t}function lJ(e,t){e.f=t}function lQ(e,t){e.a=t}function l1(e,t){e.b=t}function l0(e,t){e.d=t}function l2(e,t){e.i=t}function l3(e,t){e.o=t}function l4(e,t){e.r=t}function l5(e,t){e.a=t}function l6(e,t){e.b=t}function l9(e,t){e.e=t}function l8(e,t){e.f=t}function l7(e,t){e.g=t}function fe(e,t){e.e=t}function ft(e,t){e.f=t}function fn(e,t){e.f=t}function fr(e,t){e.n=t}function fi(e,t){e.a=t}function fa(e,t){e.a=t}function fo(e,t){e.c=t}function fs(e,t){e.c=t}function fu(e,t){e.d=t}function fc(e,t){e.e=t}function fl(e,t){e.g=t}function ff(e,t){e.a=t}function fd(e,t){e.c=t}function fh(e,t){e.d=t}function fp(e,t){e.e=t}function fb(e,t){e.f=t}function fm(e,t){e.j=t}function fg(e,t){e.a=t}function fv(e,t){e.b=t}function fy(e,t){e.a=t}function fw(e){e.b=e.a}function f_(e){e.c=e.d.d}function fE(e){this.d=e}function fS(e){this.a=e}function fk(e){this.a=e}function fx(e){this.a=e}function fT(e){this.a=e}function fM(e){this.a=e}function fO(e){this.a=e}function fA(e){this.a=e}function fL(e){this.a=e}function fC(e){this.a=e}function fI(e){this.a=e}function fD(e){this.a=e}function fN(e){this.a=e}function fP(e){this.a=e}function fR(e){this.a=e}function fj(e){this.b=e}function fF(e){this.b=e}function fY(e){this.b=e}function fB(e){this.a=e}function fU(e){this.a=e}function fH(e){this.a=e}function f$(e){this.c=e}function fz(e){this.c=e}function fG(e){this.c=e}function fW(e){this.a=e}function fK(e){this.a=e}function fV(e){this.a=e}function fq(e){this.a=e}function fZ(e){this.a=e}function fX(e){this.a=e}function fJ(e){this.a=e}function fQ(e){this.a=e}function f1(e){this.a=e}function f0(e){this.a=e}function f2(e){this.a=e}function f3(e){this.a=e}function f4(e){this.a=e}function f5(e){this.a=e}function f6(e){this.a=e}function f9(e){this.a=e}function f8(e){this.a=e}function f7(e){this.a=e}function de(e){this.a=e}function dt(e){this.a=e}function dn(e){this.a=e}function dr(e){this.a=e}function di(e){this.a=e}function da(e){this.a=e}function ds(e){this.a=e}function du(e){this.a=e}function dc(e){this.a=e}function dl(e){this.a=e}function df(e){this.a=e}function dd(e){this.a=e}function dh(e){this.a=e}function dp(e){this.a=e}function db(e){this.a=e}function dm(e){this.a=e}function dg(e){this.a=e}function dv(e){this.a=e}function dy(e){this.a=e}function dw(e){this.a=e}function d_(e){this.a=e}function dE(e){this.a=e}function dS(e){this.a=e}function dk(e){this.a=e}function dx(e){this.a=e}function dT(e){this.a=e}function dM(e){this.a=e}function dO(e){this.e=e}function dA(e){this.a=e}function dL(e){this.a=e}function dC(e){this.a=e}function dI(e){this.a=e}function dD(e){this.a=e}function dN(e){this.a=e}function dP(e){this.a=e}function dR(e){this.a=e}function dj(e){this.a=e}function dF(e){this.a=e}function dY(e){this.a=e}function dB(e){this.a=e}function dU(e){this.a=e}function dH(e){this.a=e}function d$(e){this.a=e}function dz(e){this.a=e}function dG(e){this.a=e}function dW(e){this.a=e}function dK(e){this.a=e}function dV(e){this.a=e}function dq(e){this.a=e}function dZ(e){this.a=e}function dX(e){this.a=e}function dJ(e){this.a=e}function dQ(e){this.a=e}function d1(e){this.a=e}function d0(e){this.a=e}function d2(e){this.a=e}function d3(e){this.a=e}function d4(e){this.a=e}function d5(e){this.a=e}function d6(e){this.a=e}function d9(e){this.a=e}function d8(e){this.a=e}function d7(e){this.a=e}function he(e){this.a=e}function ht(e){this.a=e}function hn(e){this.a=e}function hr(e){this.a=e}function hi(e){this.a=e}function ha(e){this.a=e}function ho(e){this.a=e}function hs(e){this.a=e}function hu(e){this.a=e}function hc(e){this.a=e}function hl(e){this.a=e}function hf(e){this.a=e}function hd(e){this.a=e}function hh(e){this.a=e}function hp(e){this.a=e}function hb(e){this.a=e}function hm(e){this.a=e}function hg(e){this.a=e}function hv(e){this.c=e}function hy(e){this.b=e}function hw(e){this.a=e}function h_(e){this.a=e}function hE(e){this.a=e}function hS(e){this.a=e}function hk(e){this.a=e}function hx(e){this.a=e}function hT(e){this.a=e}function hM(e){this.a=e}function hO(e){this.a=e}function hA(e){this.a=e}function hL(e){this.a=e}function hC(e){this.a=e}function hI(e){this.a=e}function hD(e){this.a=e}function hN(e){this.a=e}function hP(e){this.a=e}function hR(e){this.a=e}function hj(e){this.a=e}function hF(e){this.a=e}function hY(e){this.a=e}function hB(e){this.a=e}function hU(e){this.a=e}function hH(e){this.a=e}function h$(e){this.a=e}function hz(e){this.a=e}function hG(e){this.a=e}function hW(e){this.a=e}function hK(e){this.a=e}function hV(e){this.a=e}function hq(e){this.a=e}function hZ(e){this.a=e}function hX(e){this.a=e}function hJ(e){this.a=e}function hQ(e){this.a=e}function h1(e){this.a=e}function h0(e){this.a=e}function h2(e){this.a=e}function h3(e){this.a=e}function h4(e){this.a=e}function h5(e){this.a=e}function h6(e){this.a=e}function h9(e){this.a=e}function h8(e){this.a=e}function h7(e){this.a=e}function pe(e){this.a=e}function pt(e){this.a=e}function pn(e){this.a=e}function pr(e){this.a=e}function pi(e){this.a=e}function pa(e){this.a=e}function po(e){this.a=e}function ps(e){this.a=e}function pu(e){this.a=e}function pc(e){this.a=e}function pl(e){this.a=e}function pf(e){this.a=e}function pd(e){this.a=e}function ph(e){this.a=e}function pp(e){this.a=e}function pb(e){this.a=e}function pm(e){this.a=e}function pg(e){this.a=e}function pv(e){this.a=e}function py(e){this.a=e}function pw(e){this.a=e}function p_(e){this.a=e}function pE(e){this.a=e}function pS(e){this.a=e}function pk(e){this.a=e}function px(e){this.a=e}function pT(e){this.a=e}function pM(e){this.a=e}function pO(e){this.b=e}function pA(e){this.f=e}function pL(e){this.a=e}function pC(e){this.a=e}function pI(e){this.a=e}function pD(e){this.a=e}function pN(e){this.a=e}function pP(e){this.a=e}function pR(e){this.a=e}function pj(e){this.a=e}function pF(e){this.a=e}function pY(e){this.a=e}function pB(e){this.a=e}function pU(e){this.b=e}function pH(e){this.c=e}function p$(e){this.e=e}function pz(e){this.a=e}function pG(e){this.a=e}function pW(e){this.a=e}function pK(e){this.a=e}function pV(e){this.a=e}function pq(e){this.d=e}function pZ(e){this.a=e}function pX(e){this.a=e}function pJ(e){this.e=e}function pQ(){this.a=0}function p1(){TG(this)}function p0(){Tz(this)}function p2(){Yy(this)}function p3(){UP(this)}function p4(){cn(this)}function p5(){this.c=tgK}function p6(e,t){t.Wb(e)}function p9(e,t){e.b+=t}function p8(e){e.b=new gQ}function p7(e){return e.e}function be(e){return e.a}function bt(e){return e.a}function bn(e){return e.a}function br(e){return e.a}function bi(e){return e.a}function ba(){return null}function bo(){return null}function bs(){yC(),eY2()}function bu(e){e.b.tf(e.e)}function bc(e,t){e.b=t-e.b}function bl(e,t){e.a=t-e.a}function bf(e,t){t.ad(e.a)}function bd(e,t){ekv(t,e)}function bh(e,t,n){e.Od(n,t)}function bp(e,t){e.e=t,t.b=e}function bb(e){Dn(),this.a=e}function bm(e){Dn(),this.a=e}function bg(e){Dn(),this.a=e}function bv(e){Bx(),this.a=e}function by(e){$O(),e0E.be(e)}function bw(){O5.call(this)}function b_(){O5.call(this)}function bE(){bw.call(this)}function bS(){bw.call(this)}function bk(){bw.call(this)}function bx(){bw.call(this)}function bT(){bw.call(this)}function bM(){bw.call(this)}function bO(){bw.call(this)}function bA(){bw.call(this)}function bL(){bw.call(this)}function bC(){bw.call(this)}function bI(){bw.call(this)}function bD(){this.a=this}function bN(){this.Bb|=256}function bP(){this.b=new xW}function bR(){bR=A,new p2}function bj(){bE.call(this)}function bF(e,t){e.length=t}function bY(e,t){P_(e.a,t)}function bB(e,t){eEU(e.c,t)}function bU(e,t){Yf(e.b,t)}function bH(e,t){ebB(e.a,t)}function b$(e,t){elj(e.a,t)}function bz(e,t){eam(e.e,t)}function bG(e){exZ(e.c,e.b)}function bW(e,t){e.kc().Nb(t)}function bK(e){this.a=efh(e)}function bV(){this.a=new p2}function bq(){this.a=new p2}function bZ(){this.a=new p0}function bX(){this.a=new p0}function bJ(){this.a=new p0}function bQ(){this.a=new ey}function b1(){this.a=new Z6}function b0(){this.a=new tt}function b2(){this.a=new w7}function b3(){this.a=new W9}function b4(){this.a=new zZ}function b5(){this.a=new Cz}function b6(){this.a=new p0}function b9(){this.a=new p0}function b8(){this.a=new p0}function b7(){this.a=new p0}function me(){this.d=new p0}function mt(){this.a=new bV}function mn(){this.a=new p2}function mr(){this.b=new p2}function mi(){this.b=new p0}function ma(){this.e=new p0}function mo(){this.d=new p0}function ms(){this.a=new cS}function mu(){p0.call(this)}function mc(){bZ.call(this)}function ml(){CK.call(this)}function mf(){b9.call(this)}function md(){mh.call(this)}function mh(){p4.call(this)}function mp(){p4.call(this)}function mb(){mp.call(this)}function mm(){$m.call(this)}function mg(){$m.call(this)}function mv(){mq.call(this)}function my(){mq.call(this)}function mw(){mq.call(this)}function m_(){mZ.call(this)}function mE(){_n.call(this)}function mS(){oZ.call(this)}function mk(){oZ.call(this)}function mx(){m0.call(this)}function mT(){m0.call(this)}function mM(){p2.call(this)}function mO(){p2.call(this)}function mA(){p2.call(this)}function mL(){bV.call(this)}function mC(){en0.call(this)}function mI(){bN.call(this)}function mD(){Oy.call(this)}function mN(){Oy.call(this)}function mP(){p2.call(this)}function mR(){p2.call(this)}function mj(){p2.call(this)}function mF(){sr.call(this)}function mY(){sr.call(this)}function mB(){mF.call(this)}function mU(){u7.call(this)}function mH(e){eti.call(this,e)}function m$(e){eti.call(this,e)}function mz(e){ln.call(this,e)}function mG(e){wB.call(this,e)}function mW(e){mG.call(this,e)}function mK(e){wB.call(this,e)}function mV(){this.a=new _n}function mq(){this.a=new bV}function mZ(){this.a=new p2}function mX(){this.a=new p0}function mJ(){this.j=new p0}function mQ(){this.a=new aX}function m1(){this.a=new y4}function m0(){this.a=new sn}function m2(){m2=A,e0d=new vm}function m3(){m3=A,e0f=new vb}function m4(){m4=A,e0l=new i}function m5(){m5=A,e0m=new OV}function m6(e){mG.call(this,e)}function m9(e){mG.call(this,e)}function m8(e){ql.call(this,e)}function m7(e){ql.call(this,e)}function ge(e){IJ.call(this,e)}function gt(e){eEb.call(this,e)}function gn(e){w$.call(this,e)}function gr(e){wG.call(this,e)}function gi(e){wG.call(this,e)}function ga(e){wG.call(this,e)}function go(e){Fu.call(this,e)}function gs(e){go.call(this,e)}function gu(){lD.call(this,{})}function gc(e){Og(),this.a=e}function gl(e){e.b=null,e.c=0}function gf(e,t){e.e=t,eA9(e,t)}function gd(e,t){e.a=t,eSG(e)}function gh(e,t,n){e.a[t.g]=n}function gp(e,t,n){evq(n,e,t)}function gb(e,t){In(t.i,e.n)}function gm(e,t){esW(e).td(t)}function gg(e,t){return e*e/t}function gv(e,t){return e.g-t.g}function gy(e){return new lI(e)}function gw(e){return new B_(e)}function g_(e){go.call(this,e)}function gE(e){go.call(this,e)}function gS(e){go.call(this,e)}function gk(e){Fu.call(this,e)}function gx(e){eiJ(),this.a=e}function gT(e){I7(),this.a=e}function gM(e){jK(),this.f=e}function gO(e){jK(),this.f=e}function gA(e){go.call(this,e)}function gL(e){go.call(this,e)}function gC(e){go.call(this,e)}function gI(e){go.call(this,e)}function gD(e){go.call(this,e)}function gN(e){return BJ(e),e}function gP(e){return BJ(e),e}function gR(e){return BJ(e),e}function gj(e){return BJ(e),e}function gF(e){return BJ(e),e}function gY(e){return e.b==e.c}function gB(e){return!!e&&e.b}function gU(e){return!!e&&e.k}function gH(e){return!!e&&e.j}function g$(e){BJ(e),this.a=e}function gz(e){return esR(e),e}function gG(e){Ya(e,e.length)}function gW(e){go.call(this,e)}function gK(e){go.call(this,e)}function gV(e){go.call(this,e)}function gq(e){go.call(this,e)}function gZ(e){go.call(this,e)}function gX(e){go.call(this,e)}function gJ(e){AI.call(this,e,0)}function gQ(){G$.call(this,12,3)}function g1(){g1=A,e0_=new _}function g0(){g0=A,e0y=new r}function g2(){g2=A,e0k=new b}function g3(){g3=A,e0M=new g}function g4(){throw p7(new bO)}function g5(){throw p7(new bO)}function g6(){throw p7(new bO)}function g9(){throw p7(new bO)}function g8(){throw p7(new bO)}function g7(){throw p7(new bO)}function ve(){this.a=Lq(Y9(eUd))}function vt(e){Dn(),this.a=Y9(e)}function vn(e,t){e.Td(t),t.Sd(e)}function vr(e,t){e.a.ec().Mc(t)}function vi(e,t,n){e.c.lf(t,n)}function va(e){gE.call(this,e)}function vo(e){gL.call(this,e)}function vs(){fM.call(this,"")}function vu(){fM.call(this,"")}function vc(){fM.call(this,"")}function vl(){fM.call(this,"")}function vf(e){gE.call(this,e)}function vd(e){fF.call(this,e)}function vh(e){O2.call(this,e)}function vp(e){vd.call(this,e)}function vb(){ls.call(this,null)}function vm(){ls.call(this,null)}function vg(){vg=A,$O()}function vv(){vv=A,e2d=eyz()}function vy(e){return e.a?e.b:0}function vw(e){return e.a?e.b:0}function v_(e,t){return e.a-t.a}function vE(e,t){return e.a-t.a}function vS(e,t){return e.a-t.a}function vk(e,t){return QO(e,t)}function vx(e,t){return z9(e,t)}function vT(e,t){return t in e.a}function vM(e,t){return e.f=t,e}function vO(e,t){return e.b=t,e}function vA(e,t){return e.c=t,e}function vL(e,t){return e.g=t,e}function vC(e,t){return e.a=t,e}function vI(e,t){return e.f=t,e}function vD(e,t){return e.k=t,e}function vN(e,t){return e.a=t,e}function vP(e,t){return e.e=t,e}function vR(e,t){return e.e=t,e}function vj(e,t){return e.f=t,e}function vF(e,t){e.b=!0,e.d=t}function vY(e,t){e.b=new TS(t)}function vB(e,t,n){t.td(e.a[n])}function vU(e,t,n){t.we(e.a[n])}function vH(e,t){return e.b-t.b}function v$(e,t){return e.g-t.g}function vz(e,t){return e.s-t.s}function vG(e,t){return e?0:t-1}function vW(e,t){return e?0:t-1}function vK(e,t){return e?t-1:0}function vV(e,t){return t.Yf(e)}function vq(e,t){return e.b=t,e}function vZ(e,t){return e.a=t,e}function vX(e,t){return e.c=t,e}function vJ(e,t){return e.d=t,e}function vQ(e,t){return e.e=t,e}function v1(e,t){return e.f=t,e}function v0(e,t){return e.a=t,e}function v2(e,t){return e.b=t,e}function v3(e,t){return e.c=t,e}function v4(e,t){return e.c=t,e}function v5(e,t){return e.b=t,e}function v6(e,t){return e.d=t,e}function v9(e,t){return e.e=t,e}function v8(e,t){return e.f=t,e}function v7(e,t){return e.g=t,e}function ye(e,t){return e.a=t,e}function yt(e,t){return e.i=t,e}function yn(e,t){return e.j=t,e}function yr(e,t){return e.k=t,e}function yi(e,t){return e.j=t,e}function ya(e,t){e_z(),Gc(t,e)}function yo(e,t,n){jX(e.a,t,n)}function ys(e){U8.call(this,e)}function yu(e){U8.call(this,e)}function yc(e){I3.call(this,e)}function yl(e){efB.call(this,e)}function yf(e){eta.call(this,e)}function yd(e){HO.call(this,e)}function yh(e){HO.call(this,e)}function yp(){MA.call(this,"")}function yb(){this.a=0,this.b=0}function ym(){this.b=0,this.a=0}function yg(e,t){e.b=0,enh(e,t)}function yv(e,t){e.c=t,e.b=!0}function yy(e,t){return e.c._b(t)}function yw(e){return e.e&&e.e()}function y_(e){return e?e.d:null}function yE(e,t){return ecD(e.b,t)}function yS(e){return e?e.g:null}function yk(e){return e?e.i:null}function yx(e){return LW(e),e.o}function yT(){yT=A,tmc=evO()}function yM(){yM=A,tml=ewS()}function yO(){yO=A,tgg=evL()}function yA(){yA=A,tvE=evA()}function yL(){yL=A,tvS=eSH()}function yC(){yC=A,tmF=enF()}function yI(){throw p7(new bO)}function yD(){throw p7(new bO)}function yN(){throw p7(new bO)}function yP(){throw p7(new bO)}function yR(){throw p7(new bO)}function yj(){throw p7(new bO)}function yF(e){this.a=new w8(e)}function yY(e){eF7(),eBh(this,e)}function yB(e){this.a=new FG(e)}function yU(e,t){for(;e.ye(t););}function yH(e,t){for(;e.sd(t););}function y$(e,t){return e.a+=t,e}function yz(e,t){return e.a+=t,e}function yG(e,t){return e.a+=t,e}function yW(e,t){return e.a+=t,e}function yK(e){return B1(e),e.a}function yV(e){return e.b!=e.d.c}function yq(e){return e.l|e.m<<22}function yZ(e,t){return e.d[t.p]}function yX(e,t){return eA5(e,t)}function yJ(e,t,n){e.splice(t,n)}function yQ(e){e.c?eL3(e):eL4(e)}function y1(e){this.a=0,this.b=e}function y0(){this.a=new eAs(e5I)}function y2(){this.b=new eAs(e5T)}function y3(){this.b=new eAs(e5H)}function y4(){this.b=new eAs(e5H)}function y5(){throw p7(new bO)}function y6(){throw p7(new bO)}function y9(){throw p7(new bO)}function y8(){throw p7(new bO)}function y7(){throw p7(new bO)}function we(){throw p7(new bO)}function wt(){throw p7(new bO)}function wn(){throw p7(new bO)}function wr(){throw p7(new bO)}function wi(){throw p7(new bO)}function wa(){throw p7(new bC)}function wo(){throw p7(new bC)}function ws(e){this.a=new wu(e)}function wu(e){erh(this,e,ey0())}function wc(e){return!e||BV(e)}function wl(e){return -1!=tvJ[e]}function wf(){0!=e1Z&&(e1Z=0),e1J=-1}function wd(){null==eUn&&(eUn=[])}function wh(e,t){eTl(H9(e.a),t)}function wp(e,t){eTl(H9(e.a),t)}function wb(e,t){OC.call(this,e,t)}function wm(e,t){wb.call(this,e,t)}function wg(e,t){this.b=e,this.c=t}function wv(e,t){this.b=e,this.a=t}function wy(e,t){this.a=e,this.b=t}function ww(e,t){this.a=e,this.b=t}function w_(e,t){this.a=e,this.b=t}function wE(e,t){this.a=e,this.b=t}function wS(e,t){this.a=e,this.b=t}function wk(e,t){this.a=e,this.b=t}function wx(e,t){this.a=e,this.b=t}function wT(e,t){this.a=e,this.b=t}function wM(e,t){this.b=e,this.a=t}function wO(e,t){this.b=e,this.a=t}function wA(e,t){this.b=e,this.a=t}function wL(e,t){this.b=e,this.a=t}function wC(e,t){this.f=e,this.g=t}function wI(e,t){this.e=e,this.d=t}function wD(e,t){this.g=e,this.i=t}function wN(e,t){this.a=e,this.b=t}function wP(e,t){this.a=e,this.f=t}function wR(e,t){this.b=e,this.c=t}function wj(e,t){this.a=e,this.b=t}function wF(e,t){this.a=e,this.b=t}function wY(e,t){this.a=e,this.b=t}function wB(e){Oq(e.dc()),this.c=e}function wU(e){this.b=Pp(Y9(e),83)}function wH(e){this.a=Pp(Y9(e),83)}function w$(e){this.a=Pp(Y9(e),15)}function wz(e){this.a=Pp(Y9(e),15)}function wG(e){this.b=Pp(Y9(e),47)}function wW(){this.q=new eB4.Date}function wK(){wK=A,e0V=new L}function wV(){wV=A,e2o=new T}function wq(e){return e.f.c+e.g.c}function wZ(e,t){return e.b.Hc(t)}function wX(e,t){return e.b.Ic(t)}function wJ(e,t){return e.b.Qc(t)}function wQ(e,t){return e.b.Hc(t)}function w1(e,t){return e.c.uc(t)}function w0(e,t){return e.a._b(t)}function w2(e,t){return ecX(e.c,t)}function w3(e,t){return F9(e.b,t)}function w4(e,t){return e>t&&t0}function Ei(e,t){return 0>ecd(e,t)}function Ea(e,t){return e.a.get(t)}function Eo(e,t){return t.split(e)}function Es(e,t){return F9(e.e,t)}function Eu(e){return BJ(e),!1}function Ec(e){Gq.call(this,e,21)}function El(e,t){zL.call(this,e,t)}function Ef(e,t){wC.call(this,e,t)}function Ed(e,t){wC.call(this,e,t)}function Eh(e){BT(),IJ.call(this,e)}function Ep(e,t){jA(e,e.length,t)}function Eb(e,t){Yj(e,e.length,t)}function Em(e,t,n){t.ud(e.a.Ge(n))}function Eg(e,t,n){t.we(e.a.Fe(n))}function Ev(e,t,n){t.td(e.a.Kb(n))}function Ey(e,t,n){e.Mb(n)&&t.td(n)}function Ew(e,t,n){e.splice(t,0,n)}function E_(e,t){return Aa(e.e,t)}function EE(e,t){this.d=e,this.e=t}function ES(e,t){this.b=e,this.a=t}function Ek(e,t){this.b=e,this.a=t}function Ex(e,t){this.b=e,this.a=t}function ET(e,t){this.a=e,this.b=t}function EM(e,t){this.a=e,this.b=t}function EO(e,t){this.a=e,this.b=t}function EA(e,t){this.a=e,this.b=t}function EL(e,t){this.a=e,this.b=t}function EC(e,t){this.b=e,this.a=t}function EI(e,t){this.b=e,this.a=t}function ED(e,t){wC.call(this,e,t)}function EN(e,t){wC.call(this,e,t)}function EP(e,t){wC.call(this,e,t)}function ER(e,t){wC.call(this,e,t)}function Ej(e,t){wC.call(this,e,t)}function EF(e,t){wC.call(this,e,t)}function EY(e,t){wC.call(this,e,t)}function EB(e,t){wC.call(this,e,t)}function EU(e,t){wC.call(this,e,t)}function EH(e,t){wC.call(this,e,t)}function E$(e,t){wC.call(this,e,t)}function Ez(e,t){wC.call(this,e,t)}function EG(e,t){wC.call(this,e,t)}function EW(e,t){wC.call(this,e,t)}function EK(e,t){wC.call(this,e,t)}function EV(e,t){wC.call(this,e,t)}function Eq(e,t){wC.call(this,e,t)}function EZ(e,t){wC.call(this,e,t)}function EX(e,t){this.a=e,this.b=t}function EJ(e,t){this.a=e,this.b=t}function EQ(e,t){this.a=e,this.b=t}function E1(e,t){this.a=e,this.b=t}function E0(e,t){this.a=e,this.b=t}function E2(e,t){this.a=e,this.b=t}function E3(e,t){this.a=e,this.b=t}function E4(e,t){this.a=e,this.b=t}function E5(e,t){this.a=e,this.b=t}function E6(e,t){this.b=e,this.a=t}function E9(e,t){this.b=e,this.a=t}function E8(e,t){this.b=e,this.a=t}function E7(e,t){this.b=e,this.a=t}function Se(e,t){this.c=e,this.d=t}function St(e,t){this.e=e,this.d=t}function Sn(e,t){this.a=e,this.b=t}function Sr(e,t){this.b=t,this.c=e}function Si(e,t){wC.call(this,e,t)}function Sa(e,t){wC.call(this,e,t)}function So(e,t){wC.call(this,e,t)}function Ss(e,t){wC.call(this,e,t)}function Su(e,t){wC.call(this,e,t)}function Sc(e,t){wC.call(this,e,t)}function Sl(e,t){wC.call(this,e,t)}function Sf(e,t){wC.call(this,e,t)}function Sd(e,t){wC.call(this,e,t)}function Sh(e,t){wC.call(this,e,t)}function Sp(e,t){wC.call(this,e,t)}function Sb(e,t){wC.call(this,e,t)}function Sm(e,t){wC.call(this,e,t)}function Sg(e,t){wC.call(this,e,t)}function Sv(e,t){wC.call(this,e,t)}function Sy(e,t){wC.call(this,e,t)}function Sw(e,t){wC.call(this,e,t)}function S_(e,t){wC.call(this,e,t)}function SE(e,t){wC.call(this,e,t)}function SS(e,t){wC.call(this,e,t)}function Sk(e,t){wC.call(this,e,t)}function Sx(e,t){wC.call(this,e,t)}function ST(e,t){wC.call(this,e,t)}function SM(e,t){wC.call(this,e,t)}function SO(e,t){wC.call(this,e,t)}function SA(e,t){wC.call(this,e,t)}function SL(e,t){wC.call(this,e,t)}function SC(e,t){wC.call(this,e,t)}function SI(e,t){wC.call(this,e,t)}function SD(e,t){wC.call(this,e,t)}function SN(e,t){wC.call(this,e,t)}function SP(e,t){wC.call(this,e,t)}function SR(e,t){wC.call(this,e,t)}function Sj(e,t){wC.call(this,e,t)}function SF(e,t){this.b=e,this.a=t}function SY(e,t){this.a=e,this.b=t}function SB(e,t){this.a=e,this.b=t}function SU(e,t){this.a=e,this.b=t}function SH(e,t){this.a=e,this.b=t}function S$(e,t){wC.call(this,e,t)}function Sz(e,t){wC.call(this,e,t)}function SG(e,t){this.b=e,this.d=t}function SW(e,t){wC.call(this,e,t)}function SK(e,t){wC.call(this,e,t)}function SV(e,t){this.a=e,this.b=t}function Sq(e,t){this.a=e,this.b=t}function SZ(e,t){wC.call(this,e,t)}function SX(e,t){wC.call(this,e,t)}function SJ(e,t){wC.call(this,e,t)}function SQ(e,t){wC.call(this,e,t)}function S1(e,t){wC.call(this,e,t)}function S0(e,t){wC.call(this,e,t)}function S2(e,t){wC.call(this,e,t)}function S3(e,t){wC.call(this,e,t)}function S4(e,t){wC.call(this,e,t)}function S5(e,t){wC.call(this,e,t)}function S6(e,t){wC.call(this,e,t)}function S9(e,t){wC.call(this,e,t)}function S8(e,t){wC.call(this,e,t)}function S7(e,t){wC.call(this,e,t)}function ke(e,t){wC.call(this,e,t)}function kt(e,t){wC.call(this,e,t)}function kn(e,t){return Aa(e.c,t)}function kr(e,t){return Aa(t.b,e)}function ki(e,t){return-e.b.Je(t)}function ka(e,t){return Aa(e.g,t)}function ko(e,t){wC.call(this,e,t)}function ks(e,t){wC.call(this,e,t)}function ku(e,t){this.a=e,this.b=t}function kc(e,t){this.a=e,this.b=t}function kl(e,t){this.a=e,this.b=t}function kf(e,t){wC.call(this,e,t)}function kd(e,t){wC.call(this,e,t)}function kh(e,t){wC.call(this,e,t)}function kp(e,t){wC.call(this,e,t)}function kb(e,t){wC.call(this,e,t)}function km(e,t){wC.call(this,e,t)}function kg(e,t){wC.call(this,e,t)}function kv(e,t){wC.call(this,e,t)}function ky(e,t){wC.call(this,e,t)}function kw(e,t){wC.call(this,e,t)}function k_(e,t){wC.call(this,e,t)}function kE(e,t){wC.call(this,e,t)}function kS(e,t){wC.call(this,e,t)}function kk(e,t){wC.call(this,e,t)}function kx(e,t){wC.call(this,e,t)}function kT(e,t){wC.call(this,e,t)}function kM(e,t){this.a=e,this.b=t}function kO(e,t){this.a=e,this.b=t}function kA(e,t){this.a=e,this.b=t}function kL(e,t){this.a=e,this.b=t}function kC(e,t){this.a=e,this.b=t}function kI(e,t){this.a=e,this.b=t}function kD(e,t){this.a=e,this.b=t}function kN(e,t){wC.call(this,e,t)}function kP(e,t){this.a=e,this.b=t}function kR(e,t){this.a=e,this.b=t}function kj(e,t){this.a=e,this.b=t}function kF(e,t){this.a=e,this.b=t}function kY(e,t){this.a=e,this.b=t}function kB(e,t){this.a=e,this.b=t}function kU(e,t){this.b=e,this.a=t}function kH(e,t){this.b=e,this.a=t}function k$(e,t){this.b=e,this.a=t}function kz(e,t){this.b=e,this.a=t}function kG(e,t){this.a=e,this.b=t}function kW(e,t){this.a=e,this.b=t}function kK(e,t){eOU(e.a,Pp(t,56))}function kV(e,t){QM(e.a,Pp(t,11))}function kq(e,t){return Pj(),t!=e}function kZ(){return vv(),new e2d}function kX(){Gk(),this.b=new bV}function kJ(){eAV(),this.a=new bV}function kQ(){Gy(),jG.call(this)}function k1(e,t){wC.call(this,e,t)}function k0(e,t){this.a=e,this.b=t}function k2(e,t){this.a=e,this.b=t}function k3(e,t){this.a=e,this.b=t}function k4(e,t){this.a=e,this.b=t}function k5(e,t){this.a=e,this.b=t}function k6(e,t){this.a=e,this.b=t}function k9(e,t){this.d=e,this.b=t}function k8(e,t){this.d=e,this.e=t}function k7(e,t){this.f=e,this.c=t}function xe(e,t){this.b=e,this.c=t}function xt(e,t){this.i=e,this.g=t}function xn(e,t){this.e=e,this.a=t}function xr(e,t){this.a=e,this.b=t}function xi(e,t){e.i=null,erA(e,t)}function xa(e,t){e&&Um(tmR,e,t)}function xo(e,t){return edG(e.a,t)}function xs(e){return edK(e.c,e.b)}function xu(e){return e?e.dd():null}function xc(e){return null==e?null:e}function xl(e){return typeof e===eUi}function xf(e){return typeof e===eUa}function xd(e){return typeof e===eUo}function xh(e,t){return e.Hd().Xb(t)}function xp(e,t){return ei7(e.Kc(),t)}function xb(e,t){return 0==ecd(e,t)}function xm(e,t){return ecd(e,t)>=0}function xg(e,t){return 0!=ecd(e,t)}function xv(e){return""+(BJ(e),e)}function xy(e,t){return e.substr(t)}function xw(e){return efH(e),e.d.gc()}function x_(e){return eTe(e,e.c),e}function xE(e){return Rb(null==e),e}function xS(e,t){return e.a+=""+t,e}function xk(e,t){return e.a+=""+t,e}function xx(e,t){return e.a+=""+t,e}function xT(e,t){return e.a+=""+t,e}function xM(e,t){return e.a+=""+t,e}function xO(e,t){return e.a+=""+t,e}function xA(e,t){qQ(e,t,e.a,e.a.a)}function xL(e,t){qQ(e,t,e.c.b,e.c)}function xC(e,t,n){eyc(t,eSE(e,n))}function xI(e,t,n){eyc(t,eSE(e,n))}function xD(e,t){eeS(new Ow(e),t)}function xN(e,t){e.q.setTime(Kj(t))}function xP(e,t){FH.call(this,e,t)}function xR(e,t){FH.call(this,e,t)}function xj(e,t){FH.call(this,e,t)}function xF(e){Yy(this),eij(this,e)}function xY(e){return GK(e,0),null}function xB(e){return e.a=0,e.b=0,e}function xU(e,t){return e.a=t.g+1,e}function xH(e,t){return 2==e.j[t.p]}function x$(e){return YZ(Pp(e,79))}function xz(){xz=A,e4r=euY(epE())}function xG(){xG=A,e7$=euY(eAn())}function xW(){this.b=new w8(ee0(12))}function xK(){this.b=0,this.a=!1}function xV(){this.b=0,this.a=!1}function xq(e){this.a=e,ci.call(this)}function xZ(e){this.a=e,ci.call(this)}function xX(e,t){Cm.call(this,e,t)}function xJ(e,t){Ii.call(this,e,t)}function xQ(e,t){xt.call(this,e,t)}function x1(e,t){eaN.call(this,e,t)}function x0(e,t){AA.call(this,e,t)}function x2(e,t){_5(),Um(tmU,e,t)}function x3(e,t){return Az(e.a,0,t)}function x4(e,t){return e.a.a.a.cc(t)}function x5(e,t){return xc(e)===xc(t)}function x6(e,t){return elN(e.a,t.a)}function x9(e,t){return ME(e.a,t.a)}function x8(e,t){return YM(e.a,t.a)}function x7(e,t){return e.indexOf(t)}function Te(e,t){return e==t?0:e?1:-1}function Tt(e){return e<10?"0"+e:""+e}function Tn(e){return Y9(e),new xq(e)}function Tr(e){return Mk(e.l,e.m,e.h)}function Ti(e){return zy((BJ(e),e))}function Ta(e){return zy((BJ(e),e))}function To(e,t){return ME(e.g,t.g)}function Ts(e){return typeof e===eUa}function Tu(e){return e==e8f||e==e8p}function Tc(e){return e==e8f||e==e8d}function Tl(e){return QI(e.b.b,e,0)}function Tf(e){this.a=kZ(),this.b=e}function Td(e){this.a=kZ(),this.b=e}function Th(e,t){return P_(e.a,t),t}function Tp(e,t){return P_(e.c,t),e}function Tb(e,t){return eat(e.a,t),e}function Tm(e,t){return Dj(),t.a+=e}function Tg(e,t){return Dj(),t.a+=e}function Tv(e,t){return Dj(),t.c+=e}function Ty(e,t){Qe(e,0,e.length,t)}function Tw(){fJ.call(this,new qh)}function T_(){jp.call(this,0,0,0,0)}function TE(){Hr.call(this,0,0,0,0)}function TS(e){this.a=e.a,this.b=e.b}function Tk(e){return e==tpm||e==tpg}function Tx(e){return e==tpy||e==tpb}function TT(e){return e==tss||e==tso}function TM(e){return e!=tbc&&e!=tbl}function TO(e){return e.Lg()&&e.Mg()}function TA(e){return UB(Pp(e,118))}function TL(e){return eat(new K2,e)}function TC(e,t){return new eaN(t,e)}function TI(e,t){return new eaN(t,e)}function TD(e,t,n){ent(e,t),enn(e,n)}function TN(e,t,n){ena(e,t),eni(e,n)}function TP(e,t,n){eno(e,t),ens(e,n)}function TR(e,t,n){enr(e,t),enc(e,n)}function Tj(e,t,n){enu(e,t),enl(e,n)}function TF(e,t){euc(e,t),enp(e,e.D)}function TY(e){k7.call(this,e,!0)}function TB(e,t,n){L3.call(this,e,t,n)}function TU(e){eLQ(),ead.call(this,e)}function TH(){Ef.call(this,"Head",1)}function T$(){Ef.call(this,"Tail",3)}function Tz(e){e.c=Je(e1R,eUp,1,0,5,1)}function TG(e){e.a=Je(e1R,eUp,1,8,5,1)}function TW(e){ety(e.xf(),new dh(e))}function TK(e){return null!=e?esj(e):0}function TV(e,t){return etg(t,zY(e))}function Tq(e,t){return etg(t,zY(e))}function TZ(e,t){return e[e.length]=t}function TX(e,t){return e[e.length]=t}function TJ(e){return Ph(e.b.Kc(),e.a)}function TQ(e,t){return erb(Bi(e.d),t)}function T1(e,t){return erb(Bi(e.g),t)}function T0(e,t){return erb(Bi(e.j),t)}function T2(e,t){Cm.call(this,e.b,t)}function T3(e){jp.call(this,e,e,e,e)}function T4(e){return e.b&&ePE(e),e.a}function T5(e){return e.b&&ePE(e),e.c}function T6(e,t){!e2M&&(e.b=t)}function T9(e,t,n){return Bc(e,t,n),n}function T8(e,t,n){Bc(e.c[t.g],t.g,n)}function T7(e,t,n){Pp(e.c,69).Xh(t,n)}function Me(e,t,n){TP(n,n.i+e,n.j+t)}function Mt(e,t){JL(qt(e.a),Gj(t))}function Mn(e,t){JL(QX(e.a),GF(t))}function Mr(e){eBG(),pJ.call(this,e)}function Mi(e){return null==e?0:esj(e)}function Ma(){Ma=A,tuT=new efY(e59)}function Mo(){Mo=A,new Ms,new p0}function Ms(){new p2,new p2,new p2}function Mu(){Mu=A,bR(),e0S=new p2}function Mc(){Mc=A,eB4.Math.log(2)}function Ml(){Ml=A,tgZ=(_Z(),tmE)}function Mf(){throw p7(new gW(e1O))}function Md(){throw p7(new gW(e1O))}function Mh(){throw p7(new gW(e1A))}function Mp(){throw p7(new gW(e1A))}function Mb(e){this.a=e,PS.call(this,e)}function Mm(e){this.a=e,wU.call(this,e)}function Mg(e){this.a=e,wU.call(this,e)}function Mv(e,t){jM(e.c,e.c.length,t)}function My(e){return e.at?1:0}function MS(e,t){return ecd(e,t)>0?e:t}function Mk(e,t,n){return{l:e,m:t,h:n}}function Mx(e,t){null!=e.a&&kV(t,e.a)}function MT(e){e.a=new C,e.c=new C}function MM(e){this.b=e,this.a=new p0}function MO(e){this.b=new e1,this.a=e}function MA(e){CW.call(this),this.a=e}function ML(){Ef.call(this,"Range",2)}function MC(){evR(),this.a=new eAs(e4k)}function MI(e,t){Y9(t),Uz(e).Jc(new d)}function MD(e,t){return GE(),t.n.b+=e}function MN(e,t,n){return Um(e.g,n,t)}function MP(e,t,n){return Um(e.k,n,t)}function MR(e,t){return Um(e.a,t.a,t)}function Mj(e,t,n){return eho(t,n,e.c)}function MF(e){return new kl(e.c,e.d)}function MY(e){return new kl(e.c,e.d)}function MB(e){return new kl(e.a,e.b)}function MU(e,t){return ej8(e.a,t,null)}function MH(e){Gs(e,null),Go(e,null)}function M$(e){GA(e,null),GL(e,null)}function Mz(){AA.call(this,null,null)}function MG(){AL.call(this,null,null)}function MW(e){this.a=e,p2.call(this)}function MK(e){this.b=(Hj(),new f$(e))}function MV(e){e.j=Je(e18,eUP,310,0,0,1)}function Mq(e,t,n){e.c.Vc(t,Pp(n,133))}function MZ(e,t,n){e.c.ji(t,Pp(n,133))}function MX(e,t){eRT(e),e.Gc(Pp(t,15))}function MJ(e,t){return eR4(e.c,e.b,t)}function MQ(e,t){return new O6(e.Kc(),t)}function M1(e,t){return -1!=eoD(e.Kc(),t)}function M0(e,t){return null!=e.a.Bc(t)}function M2(e){return e.Ob()?e.Pb():null}function M3(e){return ehv(e,0,e.length)}function M4(e,t){return null!=e&&ebs(e,t)}function M5(e,t){e.q.setHours(t),eNq(e,t)}function M6(e,t){e.c&&(Re(t),zd(t))}function M9(e,t,n){Pp(e.Kb(n),164).Nb(t)}function M8(e,t,n){return ejq(e,t,n),n}function M7(e,t,n){e.a=1502^t,e.b=n^e$d}function Oe(e,t,n){return e.a[t.g][n.g]}function Ot(e,t){return e.a[t.c.p][t.p]}function On(e,t){return e.e[t.c.p][t.p]}function Or(e,t){return e.c[t.c.p][t.p]}function Oi(e,t){return e.j[t.p]=eOo(t)}function Oa(e,t){return ZZ(e.f,t.tg())}function Oo(e,t){return ZZ(e.b,t.tg())}function Os(e,t){return e.a0?t*t/e:t*t*100}function Li(e,t){return e>0?t/(e*e):100*t}function La(e,t,n){return P_(t,ef5(e,n))}function Lo(e,t,n){J1(),e.Xe(t)&&n.td(e)}function Ls(e,t,n){var r;(r=e.Zc(t)).Rb(n)}function Lu(e,t,n){return e.a+=t,e.b+=n,e}function Lc(e,t,n){return e.a*=t,e.b*=n,e}function Ll(e,t,n){return e.a-=t,e.b-=n,e}function Lf(e,t){return e.a=t.a,e.b=t.b,e}function Ld(e){return e.a=-e.a,e.b=-e.b,e}function Lh(e){this.c=e,this.a=1,this.b=1}function Lp(e){this.c=e,eno(e,0),ens(e,0)}function Lb(e){_n.call(this),enD(this,e)}function Lm(e){eBp(),p8(this),this.mf(e)}function Lg(e,t){_0(),AA.call(this,e,t)}function Lv(e,t){_2(),AL.call(this,e,t)}function Ly(e,t){_2(),AL.call(this,e,t)}function Lw(e,t){_2(),Lv.call(this,e,t)}function L_(e,t,n){JY.call(this,e,t,n,2)}function LE(e,t){Ml(),jd.call(this,e,t)}function LS(e,t){Ml(),LE.call(this,e,t)}function Lk(e,t){Ml(),LE.call(this,e,t)}function Lx(e,t){Ml(),Lk.call(this,e,t)}function LT(e,t){Ml(),jd.call(this,e,t)}function LM(e,t){Ml(),LT.call(this,e,t)}function LO(e,t){Ml(),jd.call(this,e,t)}function LA(e,t){return e.c.Fc(Pp(t,133))}function LL(e,t,n){return eP9(Qq(e,t),n)}function LC(e,t,n){return t.Qk(e.e,e.c,n)}function LI(e,t,n){return t.Rk(e.e,e.c,n)}function LD(e,t){return ecv(e.e,Pp(t,49))}function LN(e,t,n){elm(QX(e.a),t,GF(n))}function LP(e,t,n){elm(qt(e.a),t,Gj(n))}function LR(e,t){t.$modCount=e.$modCount}function Lj(){Lj=A,tcV=new pO("root")}function LF(){LF=A,tmB=new mx,new mT}function LY(){this.a=new zu,this.b=new zu}function LB(){en0.call(this),this.Bb|=eH3}function LU(){wC.call(this,"GROW_TREE",0)}function LH(e){return null==e?null:eYt(e)}function L$(e){return null==e?null:eEO(e)}function Lz(e){return null==e?null:efF(e)}function LG(e){return null==e?null:efF(e)}function LW(e){null==e.o&&eMb(e)}function LK(e){return Rb(null==e||xl(e)),e}function LV(e){return Rb(null==e||xf(e)),e}function Lq(e){return Rb(null==e||xd(e)),e}function LZ(e){this.q=new eB4.Date(Kj(e))}function LX(e,t){this.c=e,wI.call(this,e,t)}function LJ(e,t){this.a=e,LX.call(this,e,t)}function LQ(e,t){this.d=e,f_(this),this.b=t}function L1(e,t){Jo.call(this,e),this.a=t}function L0(e,t){Jo.call(this,e),this.a=t}function L2(e){edL.call(this,0,0),this.f=e}function L3(e,t,n){XS.call(this,e,t,n,null)}function L4(e,t,n){XS.call(this,e,t,n,null)}function L5(e,t,n){return 0>=e.ue(t,n)?n:t}function L6(e,t,n){return 0>=e.ue(t,n)?t:n}function L9(e,t){return Pp(eef(e.b,t),149)}function L8(e,t){return Pp(eef(e.c,t),229)}function L7(e){return Pp(RJ(e.a,e.b),287)}function Ce(e){return new kl(e.c,e.d+e.a)}function Ct(e){return GE(),TT(Pp(e,197))}function Cn(){Cn=A,e4i=el9((ed6(),tbq))}function Cr(e,t){t.a?eLc(e,t):Ai(e.a,t.b)}function Ci(e,t){!e2M&&P_(e.a,t)}function Ca(e,t){return _k(),eag(t.d.i,e)}function Co(e,t){return erJ(),new eIu(t,e)}function Cs(e,t){return $C(t,ezr),e.f=t,e}function Cu(e,t,n){return n=eDg(e,t,3,n)}function Cc(e,t,n){return n=eDg(e,t,6,n)}function Cl(e,t,n){return n=eDg(e,t,9,n)}function Cf(e,t,n){++e.j,e.Ki(),X8(e,t,n)}function Cd(e,t,n){++e.j,e.Hi(t,e.oi(t,n))}function Ch(e,t,n){var r;(r=e.Zc(t)).Rb(n)}function Cp(e,t,n){return ePT(e.c,e.b,t,n)}function Cb(e,t){return(t&eUu)%e.d.length}function Cm(e,t){pO.call(this,e),this.a=t}function Cg(e,t){pH.call(this,e),this.a=t}function Cv(e,t){pH.call(this,e),this.a=t}function Cy(e,t){this.c=e,eta.call(this,t)}function Cw(e,t){this.a=e,pU.call(this,t)}function C_(e,t){this.a=e,pU.call(this,t)}function CE(e){this.a=(enG(e,eU3),new XM(e))}function CS(e){this.a=(enG(e,eU3),new XM(e))}function Ck(e){return e.a||(e.a=new h),e.a}function Cx(e){return e>8?0:e+1}function CT(e,t){return OQ(),e==t?0:e?1:-1}function CM(e,t,n){return jT(e,Pp(t,22),n)}function CO(e,t,n){return e.apply(t,n)}function CA(e,t,n){return e.a+=ehv(t,0,n),e}function CL(e,t){var n;return n=e.e,e.e=t,n}function CC(e,t){var n;(n=e[e$c]).call(e,t)}function CI(e,t){var n;(n=e[e$c]).call(e,t)}function CD(e,t){e.a.Vc(e.b,t),++e.b,e.c=-1}function CN(e){Yy(e.e),e.d.b=e.d,e.d.a=e.d}function CP(e){e.b?CP(e.b):e.f.c.zc(e.e,e.d)}function CR(e,t,n){_w(),lP(e,t.Ce(e.a,n))}function Cj(e,t){return y_(ehn(e.a,t,!0))}function CF(e,t){return y_(ehr(e.a,t,!0))}function CY(e,t){return vk(Array(t),e)}function CB(e){return String.fromCharCode(e)}function CU(e){return null==e?null:e.message}function CH(){this.a=new p0,this.b=new p0}function C$(){this.a=new tt,this.b=new bP}function Cz(){this.b=new yb,this.c=new p0}function CG(){this.d=new yb,this.e=new yb}function CW(){this.n=new yb,this.o=new yb}function CK(){this.n=new mp,this.i=new TE}function CV(){this.a=new cg,this.b=new i_}function Cq(){this.a=new p0,this.d=new p0}function CZ(){this.b=new bV,this.a=new bV}function CX(){this.b=new p2,this.a=new p2}function CJ(){this.b=new y2,this.a=new ay}function CQ(){CK.call(this),this.a=new yb}function C1(e){eaD.call(this,e,(Qu(),e2D))}function C0(e,t,n,r){jp.call(this,e,t,n,r)}function C2(e,t,n){null!=n&&ern(t,emI(e,n))}function C3(e,t,n){null!=n&&err(t,emI(e,n))}function C4(e,t,n){return n=eDg(e,t,11,n)}function C5(e,t){return e.a+=t.a,e.b+=t.b,e}function C6(e,t){return e.a-=t.a,e.b-=t.b,e}function C9(e,t){return e.n.a=(BJ(t),t+10)}function C8(e,t){return e.n.a=(BJ(t),t+10)}function C7(e,t){return t==e||ev9(eOg(t),e)}function Ie(e,t){return null==Um(e.a,t,"")}function It(e,t){return _k(),!eag(t.d.i,e)}function In(e,t){Tk(e.f)?eMi(e,t):ewz(e,t)}function Ir(e,t){var n;return t.Hh(e.a)}function Ii(e,t){gE.call(this,eJT+e+eXH+t)}function Ia(e,t,n,r){FQ.call(this,e,t,n,r)}function Io(e,t,n,r){FQ.call(this,e,t,n,r)}function Is(e,t,n,r){Io.call(this,e,t,n,r)}function Iu(e,t,n,r){F1.call(this,e,t,n,r)}function Ic(e,t,n,r){F1.call(this,e,t,n,r)}function Il(e,t,n,r){F1.call(this,e,t,n,r)}function If(e,t,n,r){Ic.call(this,e,t,n,r)}function Id(e,t,n,r){Ic.call(this,e,t,n,r)}function Ih(e,t,n,r){Il.call(this,e,t,n,r)}function Ip(e,t,n,r){Id.call(this,e,t,n,r)}function Ib(e,t,n,r){FZ.call(this,e,t,n,r)}function Im(e,t,n){this.a=e,AI.call(this,t,n)}function Ig(e,t,n){this.c=t,this.b=n,this.a=e}function Iv(e,t,n){return e.d=Pp(t.Kb(n),164)}function Iy(e,t){return e.Aj().Nh().Kh(e,t)}function Iw(e,t){return e.Aj().Nh().Ih(e,t)}function I_(e,t){return BJ(e),xc(e)===xc(t)}function IE(e,t){return BJ(e),xc(e)===xc(t)}function IS(e,t){return y_(ehn(e.a,t,!1))}function Ik(e,t){return y_(ehr(e.a,t,!1))}function Ix(e,t){return e.b.sd(new EM(e,t))}function IT(e,t){return e.b.sd(new EO(e,t))}function IM(e,t){return e.b.sd(new EA(e,t))}function IO(e,t,n){return e.lastIndexOf(t,n)}function IA(e,t,n){return elN(e[t.b],e[n.b])}function IL(e,t){return eo3(t,(eBy(),tat),e)}function IC(e,t){return ME(t.a.d.p,e.a.d.p)}function II(e,t){return ME(e.a.d.p,t.a.d.p)}function ID(e,t){return elN(e.c-e.s,t.c-t.s)}function IN(e){return e.c?QI(e.c.a,e,0):-1}function IP(e){return e<100?null:new yf(e)}function IR(e){return e==tba||e==tbs||e==tbo}function Ij(e,t){return M4(t,15)&&eCc(e.c,t)}function IF(e,t){!e2M&&t&&(e.d=t)}function IY(e,t){var n;return!!esq(e,n=t)}function IB(e,t){this.c=e,YC.call(this,e,t)}function IU(e){this.c=e,xj.call(this,eUY,0)}function IH(e,t){Px.call(this,e,e.length,t)}function I$(e,t,n){return Pp(e.c,69).lk(t,n)}function Iz(e,t,n){return Pp(e.c,69).mk(t,n)}function IG(e,t,n){return LC(e,Pp(t,332),n)}function IW(e,t,n){return LI(e,Pp(t,332),n)}function IK(e,t,n){return ey1(e,Pp(t,332),n)}function IV(e,t,n){return e_t(e,Pp(t,332),n)}function Iq(e,t){return null==t?null:ecA(e.b,t)}function IZ(e){return xf(e)?(BJ(e),e):e.ke()}function IX(e){return!isNaN(e)&&!isFinite(e)}function IJ(e){Dn(),this.a=(Hj(),new vd(e))}function IQ(e){Pj(),this.d=e,this.a=new p1}function I1(e,t,n){this.a=e,this.b=t,this.c=n}function I0(e,t,n){this.a=e,this.b=t,this.c=n}function I2(e,t,n){this.d=e,this.b=n,this.a=t}function I3(e){MT(this),HC(this),er7(this,e)}function I4(e){Tz(this),PO(this.c,0,e.Pc())}function I5(e){BH(e.a),Jl(e.c,e.b),e.b=null}function I6(e){this.a=e,wK(),eap(Date.now())}function I9(){I9=A,e2G=new r,e2W=new r}function I8(){I8=A,e2h=new I,e2p=new D}function I7(){I7=A,tmY=Je(e1R,eUp,1,0,5,1)}function De(){De=A,tgH=Je(e1R,eUp,1,0,5,1)}function Dt(){Dt=A,tg$=Je(e1R,eUp,1,0,5,1)}function Dn(){Dn=A,new bb((Hj(),Hj(),e2r))}function Dr(e){return Qu(),eeM((Qc(),e2j),e)}function Di(e){return eum(),eeM((XC(),e2$),e)}function Da(e){return epC(),eeM((qk(),e3d),e)}function Do(e){return eeR(),eeM((qx(),e3b),e)}function Ds(e){return eCp(),eeM((eaF(),e3I),e)}function Du(e){return etx(),eeM((XO(),e3R),e)}function Dc(e){return Qs(),eeM((XA(),e3B),e)}function Dl(e){return QQ(),eeM((XL(),e3z),e)}function Df(e){return eBW(),eeM((xz(),e4r),e)}function Dd(e){return eaY(),eeM((Qf(),e4l),e)}function Dh(e){return ep7(),eeM((Qd(),e4b),e)}function Dp(e){return ebe(),eeM((Qh(),e6z),e)}function Db(e){return _y(),eeM((Vt(),e6W),e)}function Dm(e){return eej(),eeM((qT(),e9h),e)}function Dg(e){return QJ(),eeM((XI(),e96),e)}function Dv(e){return e_x(),eeM((eeW(),e8a),e)}function Dy(e){return eok(),eeM((Ql(),e8b),e)}function Dw(e){return ec4(),eeM((XD(),e8T),e)}function D_(e,t){if(!e)throw p7(new gL(t))}function DE(e){return eEn(),eeM((etQ(),e8R),e)}function DS(e){jp.call(this,e.d,e.c,e.a,e.b)}function Dk(e){jp.call(this,e.d,e.c,e.a,e.b)}function Dx(e,t,n){this.b=e,this.c=t,this.a=n}function DT(e,t,n){this.b=e,this.a=t,this.c=n}function DM(e,t,n){this.a=e,this.b=t,this.c=n}function DO(e,t,n){this.a=e,this.b=t,this.c=n}function DA(e,t,n){this.a=e,this.b=t,this.c=n}function DL(e,t,n){this.a=e,this.b=t,this.c=n}function DC(e,t,n){this.b=e,this.a=t,this.c=n}function DI(e,t,n){this.e=t,this.b=e,this.d=n}function DD(e,t,n){return _w(),e.a.Od(t,n),t}function DN(e){var t;return(t=new ew).e=e,t}function DP(e){var t;return(t=new me).b=e,t}function DR(){DR=A,e8V=new nd,e8q=new nh}function Dj(){Dj=A,e75=new rB,e76=new rU}function DF(e){return eoE(),eeM((Qb(),e7X),e)}function DY(e){return eoS(),eeM((Qg(),tet),e)}function DB(e){return eLz(),eeM((ei3(),tek),e)}function DU(e){return eSg(),eeM((et2(),teI),e)}function DH(e){return Jp(),eeM((qI(),teP),e)}function D$(e){return en7(),eeM((XN(),teY),e)}function Dz(e){return ey4(),eeM((eeU(),tes),e)}function DG(e){return erX(),eeM((Xj(),teb),e)}function DW(e){return enB(),eeM((XP(),te$),e)}function DK(e){return eb6(),eeM((eeY(),teq),e)}function DV(e){return eeF(),eeM((qO(),teJ),e)}function Dq(e){return eoG(),eeM((XR(),te2),e)}function DZ(e){return eEf(),eeM((et6(),te7),e)}function DX(e){return Qx(),eeM((qA(),ttn),e)}function DJ(e){return eyd(),eeM((et4(),ttc),e)}function DQ(e){return e_3(),eeM((et3(),ttm),e)}function D1(e){return eLR(),eeM((eoH(),ttM),e)}function D0(e){return eaU(),eeM((XY(),ttC),e)}function D2(e){return Q1(),eeM((XF(),ttP),e)}function D3(e){return K6(),eeM((qD(),ttF),e)}function D4(e){return ef_(),eeM((eeH(),tnF),e)}function D5(e){return ewY(),eeM((et5(),tst),e)}function D6(e){return euJ(),eeM((XB(),tsa),e)}function D9(e){return ebk(),eeM((Qv(),tsl),e)}function D8(e){return enY(),eeM((X$(),tsR),e)}function D7(e){return eOJ(),eeM((ei2(),tsx),e)}function Ne(e){return esn(),eeM((XH(),tsA),e)}function Nt(e){return Q0(),eeM((qC(),tsI),e)}function Nn(e){return ei0(),eeM((XU(),tsB),e)}function Nr(e){return ebG(),eeM((eeB(),tsm),e)}function Ni(e){return Xo(),eeM((qL(),ts$),e)}function Na(e){return euy(),eeM((XG(),tsK),e)}function No(e){return eiO(),eeM((XW(),tsX),e)}function Ns(e){return eox(),eeM((Xz(),ts0),e)}function Nu(e){return enU(),eeM((XK(),tuo),e)}function Nc(e){return qG(),eeM((qP(),tud),e)}function Nl(e){return zs(),eeM((qR(),tu_),e)}function Nf(e){return zQ(),eeM((qj(),tuk),e)}function Nd(e){return Xa(),eeM((qN(),tu$),e)}function Nh(e){return zo(),eeM((qF(),tuX),e)}function Np(e){return egR(),eeM((Qp(),tu2),e)}function Nb(e){return eS_(),eeM((et9(),tu7),e)}function Nm(e){return z1(),eeM((qU(),tcB),e)}function Ng(e){return erZ(),eeM((qB(),tcX),e)}function Nv(e){return Kn(),eeM((qY(),tc$),e)}function Ny(e){return efx(),eeM((XV(),tc0),e)}function Nw(e){return J0(),eeM((qH(),tc4),e)}function N_(e){return eub(),eeM((Xq(),tc8),e)}function NE(e){return emC(),eeM((Qm(),tlA),e)}function NS(e){return ei1(),eeM((XX(),tlD),e)}function Nk(e){return efS(),eeM((XZ(),tlj),e)}function Nx(e){return eOB(),eeM((eeG(),tfl),e)}function NT(e){return efk(),eeM((XJ(),tfp),e)}function NM(e){return _D(),eeM((K7(),tfm),e)}function NO(e){return _N(),eeM((K8(),tfv),e)}function NA(e){return Xs(),eeM((qz(),tf_),e)}function NL(e){return eEM(),eeM((ee$(),tfM),e)}function NC(e){return _P(),eeM((Ve(),tf7),e)}function NI(e){return eoT(),eeM((q$(),tdn),e)}function ND(e){return epx(),eeM((eez(),tdb),e)}function NN(e){return eSd(),eeM((ei4(),tdk),e)}function NP(e){return ebx(),eeM((et0(),tdD),e)}function NR(e){return eyY(),eeM((et1(),tdJ),e)}function Nj(e){return eB$(),eeM((xG(),e7$),e)}function NF(e){return erq(),eeM((qM(),e8K),e)}function NY(e){return ec3(),eeM((eeK(),tpw),e)}function NB(e){return etT(),eeM((X1(),tpk),e)}function NU(e){return efE(),eeM((Q_(),tpA),e)}function NH(e){return e_a(),eeM((et7(),tpR),e)}function N$(e){return eck(),eeM((XQ(),tpK),e)}function Nz(e){return egF(),eeM((Qw(),tpJ),e)}function NG(e){return eT7(),eeM((eaj(),tp8),e)}function NW(e){return epT(),eeM((eeV(),tbi),e)}function NK(e){return ewf(),eeM((etC(),tbf),e)}function NV(e){return ekU(),eeM((et8(),tbv),e)}function Nq(e){return ed6(),eeM((QS(),tbZ),e)}function NZ(e){return eI3(),eeM((eo$(),tb6),e)}function NX(e){return eYu(),eeM((eeq(),tbB),e)}function NJ(e){return edM(),eeM((QE(),tmt),e)}function NQ(e){return eup(),eeM((Qy(),tmo),e)}function N1(e){return eTy(),eeM((ei5(),tmP),e)}function N0(e,t){return BJ(e),e+(BJ(t),t)}function N2(e,t){return wK(),JL(H9(e.a),t)}function N3(e,t){return wK(),JL(H9(e.a),t)}function N4(e,t){this.c=e,this.a=t,this.b=t-e}function N5(e,t,n){this.a=e,this.b=t,this.c=n}function N6(e,t,n){this.a=e,this.b=t,this.c=n}function N9(e,t,n){this.a=e,this.b=t,this.c=n}function N8(e,t,n){this.a=e,this.b=t,this.c=n}function N7(e,t,n){this.a=e,this.b=t,this.c=n}function Pe(e,t,n){this.e=e,this.a=t,this.c=n}function Pt(e,t,n){Ml(),zl.call(this,e,t,n)}function Pn(e,t,n){Ml(),BP.call(this,e,t,n)}function Pr(e,t,n){Ml(),BP.call(this,e,t,n)}function Pi(e,t,n){Ml(),BP.call(this,e,t,n)}function Pa(e,t,n){Ml(),Pn.call(this,e,t,n)}function Po(e,t,n){Ml(),Pn.call(this,e,t,n)}function Ps(e,t,n){Ml(),Po.call(this,e,t,n)}function Pu(e,t,n){Ml(),Pr.call(this,e,t,n)}function Pc(e,t,n){Ml(),Pi.call(this,e,t,n)}function Pl(e,t){return Y9(e),Y9(t),new wx(e,t)}function Pf(e,t){return Y9(e),Y9(t),new Rn(e,t)}function Pd(e,t){return Y9(e),Y9(t),new Rr(e,t)}function Ph(e,t){return Y9(e),Y9(t),new wM(e,t)}function Pp(e,t){return Rb(null==e||ebs(e,t)),e}function Pb(e){var t;return t=new p0,eel(t,e),t}function Pm(e){var t;return t=new bV,eel(t,e),t}function Pg(e){var t;return ein(t=new b2,e),t}function Pv(e){var t;return ein(t=new _n,e),t}function Py(e){return e.e||(e.e=new p0),e.e}function Pw(e){return e.c||(e.c=new sk),e.c}function P_(e,t){return e.c[e.c.length]=t,!0}function PE(e,t){this.c=e,this.b=t,this.a=!1}function PS(e){this.d=e,f_(this),this.b=Ft(e.d)}function Pk(){this.a=";,;",this.b="",this.c=""}function Px(e,t,n){F$.call(this,t,n),this.a=e}function PT(e,t,n){this.b=e,xP.call(this,t,n)}function PM(e,t,n){this.c=e,EE.call(this,t,n)}function PO(e,t,n){ekp(n,0,e,t,n.length,!1)}function PA(e,t,n,r,i){e.b=t,e.c=n,e.d=r,e.a=i}function PL(e,t){t&&(e.b=t,e.a=(B1(t),t.a))}function PC(e,t,n,r,i){e.d=t,e.c=n,e.a=r,e.b=i}function PI(e){var t,n;t=e.b,n=e.c,e.b=n,e.c=t}function PD(e){var t,n;n=e.d,t=e.a,e.d=t,e.a=n}function PN(e){return eal(YE(Ts(e)?eaL(e):e))}function PP(e,t){return ME(Rx(e.d),Rx(t.d))}function PR(e,t){return t==(eYu(),tbY)?e.c:e.d}function Pj(){Pj=A,tuu=(eYu(),tbY),tuc=tby}function PF(){this.b=gP(LV(epB((eCk(),e9N))))}function PY(e){return _w(),Je(e1R,eUp,1,e,5,1)}function PB(e){return new kl(e.c+e.b,e.d+e.a)}function PU(e,t){return _C(),ME(e.d.p,t.d.p)}function PH(e){return A6(0!=e.b),etw(e,e.a.a)}function P$(e){return A6(0!=e.b),etw(e,e.c.b)}function Pz(e,t){if(!e)throw p7(new gS(t))}function PG(e,t){if(!e)throw p7(new gL(t))}function PW(e,t,n){Se.call(this,e,t),this.b=n}function PK(e,t,n){k8.call(this,e,t),this.c=n}function PV(e,t,n){etn.call(this,t,n),this.d=e}function Pq(e){Dt(),sr.call(this),this.th(e)}function PZ(e,t,n){this.a=e,xQ.call(this,t,n)}function PX(e,t,n){this.a=e,xQ.call(this,t,n)}function PJ(e,t,n){k8.call(this,e,t),this.c=n}function PQ(){ZE(),BY.call(this,(_Q(),tgp))}function P1(e){return null!=e&&!efz(e,tm1,tm0)}function P0(e,t){return(elt(e)<<4|elt(t))&eHd}function P2(e,t){return U_(),eb2(e,t),new Uf(e,t)}function P3(e,t){var n;e.n&&(n=t,P_(e.f,n))}function P4(e,t,n){var r;ee3(e,t,r=new B_(n))}function P5(e,t){var n;return n=e.c,ers(e,t),n}function P6(e,t){return t<0?e.g=-1:e.g=t,e}function P9(e,t){return etN(e),e.a*=t,e.b*=t,e}function P8(e,t,n,r,i){e.c=t,e.d=n,e.b=r,e.a=i}function P7(e,t){return qQ(e,t,e.c.b,e.c),!0}function Re(e){e.a.b=e.b,e.b.a=e.a,e.a=e.b=null}function Rt(e){this.b=e,this.a=Fc(this.b.a).Ed()}function Rn(e,t){this.b=e,this.a=t,ci.call(this)}function Rr(e,t){this.a=e,this.b=t,ci.call(this)}function Ri(e,t){F$.call(this,t,1040),this.a=e}function Ra(e){return 0==e||isNaN(e)?e:e<0?-1:1}function Ro(e){return HR(),e_I(e)==z$(e_P(e))}function Rs(e){return HR(),e_P(e)==z$(e_I(e))}function Ru(e,t){return eyE(e,new Se(t.a,t.b))}function Rc(e){return!q8(e)&&e.c.i.c==e.d.i.c}function Rl(e){var t;return t=e.n,e.a.b+t.d+t.a}function Rf(e){var t;return t=e.n,e.e.b+t.d+t.a}function Rd(e){var t;return t=e.n,e.e.a+t.b+t.c}function Rh(e){return eBG(),++tyv,new jb(0,e)}function Rp(e){return e.a?e.a:Hh(e)}function Rb(e){if(!e)throw p7(new gA(null))}function Rm(){Rm=A,tvm=(Hj(),new fB(eQU))}function Rg(){Rg=A,new ebw((m2(),e0d),(m3(),e0f))}function Rv(){Rv=A,e0B=Je(e15,eUP,19,256,0,1)}function Ry(e,t,n,r){ef3.call(this,e,t,n,r,0,0)}function Rw(e,t,n){return Um(e.b,Pp(n.b,17),t)}function R_(e,t,n){return Um(e.b,Pp(n.b,17),t)}function RE(e,t){return P_(e,new kl(t.a,t.b))}function RS(e,t){return e.c=t)throw p7(new bj)}function FR(e,t,n){return Bc(t,0,R5(t[0],n[0])),t}function Fj(e,t,n){t.Ye(n,gP(LV(Bp(e.b,n)))*e.a)}function FF(e,t,n){return eLG(),eiq(e,t)&&eiq(e,n)}function FY(e){return ekU(),!e.Hc(tbp)&&!e.Hc(tbm)}function FB(e){return new kl(e.c+e.b/2,e.d+e.a/2)}function FU(e,t){return t.kh()?ecv(e.b,Pp(t,49)):t}function FH(e,t){this.e=e,this.d=(64&t)!=0?t|eUR:t}function F$(e,t){this.c=0,this.d=e,this.b=64|t|eUR}function Fz(e){this.b=new XM(11),this.a=(HF(),e)}function FG(e){this.b=null,this.a=(HF(),e||e2s)}function FW(e){this.a=ebb(e.a),this.b=new I4(e.b)}function FK(e){this.b=e,AF.call(this,e),Op(this)}function FV(e){this.b=e,AB.call(this,e),Ob(this)}function Fq(e,t,n){this.a=e,Ia.call(this,t,n,5,6)}function FZ(e,t,n,r){this.b=e,O_.call(this,t,n,r)}function FX(e,t,n,r,i){JB.call(this,e,t,n,r,i,-1)}function FJ(e,t,n,r,i){JU.call(this,e,t,n,r,i,-1)}function FQ(e,t,n,r){O_.call(this,e,t,n),this.b=r}function F1(e,t,n,r){PK.call(this,e,t,n),this.b=r}function F0(e){k7.call(this,e,!1),this.a=!1}function F2(e,t){this.b=e,lm.call(this,e.b),this.a=t}function F3(e,t){Bx(),wj.call(this,e,ecT(new g$(t)))}function F4(e,t){return eBG(),++tyv,new BR(e,t,0)}function F5(e,t){return eBG(),++tyv,new BR(6,e,t)}function F6(e,t){return IE(e.substr(0,t.length),t)}function F9(e,t){return xd(t)?$r(e,t):!!$I(e.f,t)}function F8(e,t){for(BJ(t);e.Ob();)t.td(e.Pb())}function F7(e,t,n){eLQ(),this.e=e,this.d=t,this.a=n}function Ye(e,t,n,r){var i;(i=e.i).i=t,i.a=n,i.b=r}function Yt(e){var t;for(t=e;t.f;)t=t.f;return t}function Yn(e){var t;return A6(null!=(t=eso(e))),t}function Yr(e){var t;return A6(null!=(t=elT(e))),t}function Yi(e,t){var n;return ZQ(t,n=e.a.gc()),n-t}function Ya(e,t){var n;for(n=0;n0?eB4.Math.log(e/t):-100}function YM(e,t){return 0>ecd(e,t)?-1:ecd(e,t)>0?1:0}function YO(e,t,n){return ePQ(e,Pp(t,46),Pp(n,167))}function YA(e,t){return Pp(Ff(Fc(e.a)).Xb(t),42).cd()}function YL(e,t){return eto(t,e.length),new Ri(e,t)}function YC(e,t){this.d=e,Ow.call(this,e),this.e=t}function YI(e){this.d=(BJ(e),e),this.a=0,this.c=eUY}function YD(e,t){pJ.call(this,1),this.a=e,this.b=t}function YN(e,t){return e.c?YN(e.c,t):P_(e.b,t),e}function YP(e,t,n){var r;return r=eep(e,t),V7(e,t,n),r}function YR(e,t){var n;return QO(n=e.slice(0,t),e)}function Yj(e,t,n){var r;for(r=0;r=e.g}function BL(e,t,n){var r;return r=er$(e,t,n),eCK(e,r)}function BC(e,t){var n;n=e.a.length,eep(e,n),V7(e,n,t)}function BI(e,t){var n;(n=console[e]).call(console,t)}function BD(e,t){var n;++e.j,n=e.Vi(),e.Ii(e.oi(n,t))}function BN(e,t,n){Pp(t.b,65),ety(t.a,new N6(e,n,t))}function BP(e,t,n){p$.call(this,t),this.a=e,this.b=n}function BR(e,t,n){pJ.call(this,e),this.a=t,this.b=n}function Bj(e,t,n){this.a=e,pH.call(this,t),this.b=n}function BF(e,t,n){this.a=e,K3.call(this,8,t,null,n)}function BY(e){this.a=(BJ(eJ7),eJ7),this.b=e,new mP}function BB(e){this.c=e,this.b=this.c.a,this.a=this.c.e}function BU(e){this.c=e,this.b=e.a.d.a,LR(e.a.e,this)}function BH(e){A4(-1!=e.c),e.d.$c(e.c),e.b=e.c,e.c=-1}function B$(e){return eB4.Math.sqrt(e.a*e.a+e.b*e.b)}function Bz(e,t){return FP(t,e.a.c.length),RJ(e.a,t)}function BG(e,t){return xc(e)===xc(t)||null!=e&&ecX(e,t)}function BW(e){return 0>=e?new _e:erg(e-1)}function BK(e){return!!tyb&&$r(tyb,e)}function BV(e){return e?e.dc():!e.Kc().Ob()}function Bq(e){return!e.a&&e.c?e.c.b:e.a}function BZ(e){return e.a||(e.a=new O_(e6f,e,4)),e.a}function BX(e){return e.d||(e.d=new O_(tgr,e,1)),e.d}function BJ(e){if(null==e)throw p7(new bM);return e}function BQ(e){e.c?e.c.He():(e.d=!0,eAA(e))}function B1(e){e.c?B1(e.c):(el3(e),e.d=!0)}function B0(e){UG(e.a),e.b=Je(e1R,eUp,1,e.b.length,5,1)}function B2(e,t){return ME(t.j.c.length,e.j.c.length)}function B3(e,t){e.c<0||e.b.b=0?e.Bh(n):ekN(e,t)}function B5(e){var t,n;return(t=e.c.i.c)==(n=e.d.i.c)}function B6(e){if(4!=e.p)throw p7(new bT);return e.e}function B9(e){if(3!=e.p)throw p7(new bT);return e.e}function B8(e){if(6!=e.p)throw p7(new bT);return e.f}function B7(e){if(6!=e.p)throw p7(new bT);return e.k}function Ue(e){if(3!=e.p)throw p7(new bT);return e.j}function Ut(e){if(4!=e.p)throw p7(new bT);return e.j}function Un(e){return e.b||(e.b=new pG(new mR)),e.b}function Ur(e){return -2==e.c&&fd(e,e_d(e.g,e.b)),e.c}function Ui(e,t){var n;return(n=Y6("",e)).n=t,n.i=1,n}function Ua(e,t){jB(Pp(t.b,65),e),ety(t.a,new dv(e))}function Uo(e,t){JL((e.a||(e.a=new C_(e,e)),e.a),t)}function Us(e,t){this.b=e,YC.call(this,e,t),Op(this)}function Uu(e,t){this.b=e,IB.call(this,e,t),Ob(this)}function Uc(e,t,n,r){wD.call(this,e,t),this.d=n,this.a=r}function Ul(e,t,n,r){wD.call(this,e,n),this.a=t,this.f=r}function Uf(e,t){MK.call(this,erv(Y9(e),Y9(t))),this.a=t}function Ud(){e_w.call(this,eQB,(yA(),tvE)),ejt(this)}function Uh(){e_w.call(this,eQc,(yO(),tgg)),eP3(this)}function Up(){wC.call(this,"DELAUNAY_TRIANGULATION",0)}function Ub(e){return String.fromCharCode.apply(null,e)}function Um(e,t,n){return xd(t)?Ge(e,t,n):eS9(e.f,t,n)}function Ug(e){return Hj(),e?e.ve():(HF(),HF(),e2c)}function Uv(e,t,n){return eoM(),n.pg(e,Pp(t.cd(),146))}function Uy(e,t){return Rg(),new ebw(new OK(e),new OW(t))}function Uw(e){return enG(e,eU6),ee1(eft(eft(5,e),e/10|0))}function U_(){U_=A,e0p=new gt(eow(vx(e1$,1),eUK,42,0,[]))}function UE(e){return e.d||(e.d=new fF(e.c.Cc())),e.d}function US(e){return e.a||(e.a=new vp(e.c.vc())),e.a}function Uk(e){return e.b||(e.b=new vd(e.c.ec())),e.b}function Ux(e,t){for(;t-- >0;)e=e<<1|(e<0?1:0);return e}function UT(e,t){return xc(e)===xc(t)||null!=e&&ecX(e,t)}function UM(e,t){return OQ(),Pp(t.b,19).ar&&++r,r}function Hl(e){var t,n;return etV(n=t=new p5,e),n}function Hf(e){var t,n;return e_U(n=t=new p5,e),n}function Hd(e,t){var n;return n=Bp(e.f,t),eiX(t,n),null}function Hh(e){var t;return(t=erw(e))?t:null}function Hp(e){return e.b||(e.b=new FQ(e6g,e,12,3)),e.b}function Hb(e){return null!=e&&wZ(tm$,e.toLowerCase())}function Hm(e,t){return elN(jl(e)*jc(e),jl(t)*jc(t))}function Hg(e,t){return elN(jl(e)*jc(e),jl(t)*jc(t))}function Hv(e,t){return elN(e.d.c+e.d.b/2,t.d.c+t.d.b/2)}function Hy(e,t){return elN(e.g.c+e.g.b/2,t.g.c+t.g.b/2)}function Hw(e,t,n){n.a?ens(e,t.b-e.f/2):eno(e,t.a-e.g/2)}function H_(e,t,n,r){this.a=e,this.b=t,this.c=n,this.d=r}function HE(e,t,n,r){this.a=e,this.b=t,this.c=n,this.d=r}function HS(e,t,n,r){this.e=e,this.a=t,this.c=n,this.d=r}function Hk(e,t,n,r){this.a=e,this.c=t,this.d=n,this.b=r}function Hx(e,t,n,r){Ml(),ZU.call(this,t,n,r),this.a=e}function HT(e,t,n,r){Ml(),ZU.call(this,t,n,r),this.a=e}function HM(e,t){this.a=e,LQ.call(this,e,Pp(e.d,15).Zc(t))}function HO(e){this.f=e,this.c=this.f.e,e.f>0&&evH(this)}function HA(e,t,n,r){this.b=e,this.c=r,xj.call(this,t,n)}function HL(e){return A6(e.b=0&&IE(e.substr(n,t.length),t)}function $N(e,t,n,r,i,a,o){return new qu(e.e,t,n,r,i,a,o)}function $P(e,t,n,r,i,a){this.a=e,en1.call(this,t,n,r,i,a)}function $R(e,t,n,r,i,a){this.a=e,en1.call(this,t,n,r,i,a)}function $j(e,t){this.g=e,this.d=eow(vx(e4N,1),eGW,10,0,[t])}function $F(e,t){this.e=e,this.a=e1R,this.b=eCz(t),this.c=t}function $Y(e,t){CK.call(this),etk(this),this.a=e,this.c=t}function $B(e,t,n,r){Bc(e.c[t.g],n.g,r),Bc(e.c[n.g],t.g,r)}function $U(e,t,n,r){Bc(e.c[t.g],t.g,n),Bc(e.b[t.g],t.g,r)}function $H(){return Xo(),eow(vx(e5u,1),eU4,376,0,[tsH,tsU])}function $$(){return Qx(),eow(vx(e40,1),eU4,479,0,[ttt,tte])}function $z(){return eeF(),eow(vx(e4J,1),eU4,419,0,[teZ,teX])}function $G(){return Jp(),eow(vx(e4V,1),eU4,422,0,[teD,teN])}function $W(){return K6(),eow(vx(e49,1),eU4,420,0,[ttR,ttj])}function $K(){return Q0(),eow(vx(e5a,1),eU4,421,0,[tsL,tsC])}function $V(){return qG(),eow(vx(e5v,1),eU4,523,0,[tuf,tul])}function $q(){return Xa(),eow(vx(e5k,1),eU4,520,0,[tuH,tuU])}function $Z(){return zs(),eow(vx(e5E,1),eU4,516,0,[tuw,tuy])}function $X(){return zQ(),eow(vx(e5S,1),eU4,515,0,[tuE,tuS])}function $J(){return zo(),eow(vx(e5x,1),eU4,455,0,[tuq,tuZ])}function $Q(){return Kn(),eow(vx(e5C,1),eU4,425,0,[tcH,tcU])}function $1(){return z1(),eow(vx(e5L,1),eU4,480,0,[tcF,tcY])}function $0(){return erZ(),eow(vx(e5I,1),eU4,495,0,[tcq,tcZ])}function $2(){return J0(),eow(vx(e5N,1),eU4,426,0,[tc2,tc3])}function $3(){return eoT(),eow(vx(e5V,1),eU4,429,0,[tdt,tde])}function $4(){return Xs(),eow(vx(e5G,1),eU4,430,0,[tfw,tfy])}function $5(){return epC(),eow(vx(e2Q,1),eU4,428,0,[e3f,e3l])}function $6(){return eeR(),eow(vx(e21,1),eU4,427,0,[e3h,e3p])}function $9(){return eej(),eow(vx(e4E,1),eU4,424,0,[e9f,e9d])}function $8(){return erq(),eow(vx(e4F,1),eU4,511,0,[e8W,e8G])}function $7(e,t,n,r){return n>=0?e.jh(t,n,r):e.Sg(null,n,r)}function ze(e){return 0==e.b.b?e.a.$e():PH(e.b)}function zt(e){if(5!=e.p)throw p7(new bT);return jE(e.f)}function zn(e){if(5!=e.p)throw p7(new bT);return jE(e.k)}function zr(e){return xc(e.a)===xc((eiM(),tgW))&&eR1(e),e.a}function zi(e){this.a=Pp(Y9(e),271),this.b=(Hj(),new O4(e))}function za(e,t){l5(this,new kl(e.a,e.b)),l6(this,Pv(t))}function zo(){zo=A,tuq=new SK(ezt,0),tuZ=new SK(ezn,1)}function zs(){zs=A,tuw=new Sz(ezn,0),tuy=new Sz(ezt,1)}function zu(){m9.call(this,new w8(ee0(12))),Oq(!0),this.a=2}function zc(e,t,n){eBG(),pJ.call(this,e),this.b=t,this.a=n}function zl(e,t,n){Ml(),p$.call(this,t),this.a=e,this.b=n}function zf(e){CK.call(this),etk(this),this.a=e,this.c=!0}function zd(e){var t;t=e.c.d.b,e.b=t,e.a=e.c.d,t.a=e.c.d.b=e}function zh(e){var t;enZ(e.a),TW(e.a),efJ(t=new dp(e.a))}function zp(e,t){eC_(e,!0),ety(e.e.wf(),new Dx(e,!0,t))}function zb(e,t){return qe(t),enL(e,Je(ty_,eHT,25,t,15,1),t)}function zm(e,t){return HR(),e==z$(e_I(t))||e==z$(e_P(t))}function zg(e,t){return null==t?xu($I(e.f,null)):Ea(e.g,t)}function zv(e){return 0==e.b?null:(A6(0!=e.b),etw(e,e.a.a))}function zy(e){return 0|Math.max(Math.min(e,eUu),-2147483648)}function zw(e,t){var n=e0w[e.charCodeAt(0)];return null==n?e:n}function z_(e,t){return H5(e,"set1"),H5(t,"set2"),new wF(e,t)}function zE(e,t){var n;return C5(Ld(n=et$(e.f,t)),e.f.d)}function zS(e,t){var n,r;return ej4(e,n=t,r=new H),r.d}function zk(e,t,n,r){var i;i=new CQ,t.a[n.g]=i,jT(e.b,r,i)}function zx(e,t,n){var r;(r=e.Yg(t))>=0?e.sh(r,n):eOh(e,t,n)}function zT(e,t,n){z0(),e&&Um(tmj,e,t),e&&Um(tmR,e,n)}function zM(e,t,n){this.i=new p0,this.b=e,this.g=t,this.a=n}function zO(e,t,n){this.c=new p0,this.e=e,this.f=t,this.b=n}function zA(e,t,n){this.a=new p0,this.e=e,this.f=t,this.c=n}function zL(e,t){MV(this),this.f=t,this.g=e,HD(this),this._d()}function zC(e,t){var n;n=e.q.getHours(),e.q.setDate(t),eNq(e,n)}function zI(e,t){var n;for(Y9(t),n=e.a;n;n=n.c)t.Od(n.g,n.i)}function zD(e){var t;return esb(t=new yF(ee0(e.length)),e),t}function zN(e){function t(){}return t.prototype=e||{},new t}function zP(e,t){return!!eos(e,t)&&(enP(e),!0)}function zR(e,t){if(null==t)throw p7(new bM);return ehF(e,t)}function zj(e){return e.qe()?null:(0,eUt[e.n])}function zF(e){return e.Db>>16!=3?null:Pp(e.Cb,33)}function zY(e){return e.Db>>16!=9?null:Pp(e.Cb,33)}function zB(e){return e.Db>>16!=6?null:Pp(e.Cb,79)}function zU(e){return e.Db>>16!=7?null:Pp(e.Cb,235)}function zH(e){return e.Db>>16!=7?null:Pp(e.Cb,160)}function z$(e){return e.Db>>16!=11?null:Pp(e.Cb,33)}function zz(e,t){var n;return(n=e.Yg(t))>=0?e.lh(n):exu(e,t)}function zG(e,t){var n;return n=new RZ(t),e_h(n,e),new I4(n)}function zW(e){var t;return t=e.d,t=e.si(e.f),JL(e,t),t.Ob()}function zK(e,t){return e.b+=t.b,e.c+=t.c,e.d+=t.d,e.a+=t.a,e}function zV(e,t){return eB4.Math.abs(e)0}function zZ(){this.a=new Tw,this.e=new bV,this.g=0,this.i=0}function zX(e){this.a=e,this.b=Je(e5b,eUP,1944,e.e.length,0,2)}function zJ(e,t,n){var r;r=esg(e,t,n),e.b=new erH(r.c.length)}function zQ(){zQ=A,tuE=new S$(ezh,0),tuS=new S$("UP",1)}function z1(){z1=A,tcF=new SJ(eV2,0),tcY=new SJ("FAN",1)}function z0(){z0=A,tmj=new p2,tmR=new p2,xa(e0r,new o8)}function z2(e){if(0!=e.p)throw p7(new bT);return xg(e.f,0)}function z3(e){if(0!=e.p)throw p7(new bT);return xg(e.k,0)}function z4(e){return e.Db>>16!=3?null:Pp(e.Cb,147)}function z5(e){return e.Db>>16!=6?null:Pp(e.Cb,235)}function z6(e){return e.Db>>16!=17?null:Pp(e.Cb,26)}function z9(e,t){var n=e.a=e.a||[];return n[t]||(n[t]=e.le(t))}function z8(e,t){var n;return null==(n=e.a.get(t))?[]:n}function z7(e,t){var n;n=e.q.getHours(),e.q.setMonth(t),eNq(e,n)}function Ge(e,t,n){return null==t?eS9(e.f,null,n):efi(e.g,t,n)}function Gt(e,t,n,r,i,a){return new Q$(e.e,t,e.aj(),n,r,i,a)}function Gn(e,t,n){return e.a=Az(e.a,0,t)+""+n+xy(e.a,t),e}function Gr(e,t,n){return P_(e.a,(U_(),eb2(t,n),new wD(t,n))),e}function Gi(e){return OX(e.c),e.e=e.a=e.c,e.c=e.c.c,++e.d,e.a.f}function Ga(e){return OX(e.e),e.c=e.a=e.e,e.e=e.e.e,--e.d,e.a.f}function Go(e,t){e.d&&QA(e.d.e,e),e.d=t,e.d&&P_(e.d.e,e)}function Gs(e,t){e.c&&QA(e.c.g,e),e.c=t,e.c&&P_(e.c.g,e)}function Gu(e,t){e.c&&QA(e.c.a,e),e.c=t,e.c&&P_(e.c.a,e)}function Gc(e,t){e.i&&QA(e.i.j,e),e.i=t,e.i&&P_(e.i.j,e)}function Gl(e,t,n){this.a=t,this.c=e,this.b=(Y9(n),new I4(n))}function Gf(e,t,n){this.a=t,this.c=e,this.b=(Y9(n),new I4(n))}function Gd(e,t){this.a=e,this.c=MB(this.a),this.b=new $g(t)}function Gh(e){var t;return el3(e),t=new bV,UJ(e,new di(t))}function Gp(e,t){if(e<0||e>t)throw p7(new gE(e$O+e+e$A+t))}function Gb(e,t){return jR(e.a,t)?Yl(e,Pp(t,22).g,null):null}function Gm(e){return euQ(),OQ(),0!=Pp(e.a,81).d.e}function Gg(){Gg=A,e0g=euY((m5(),eow(vx(e1W,1),eU4,538,0,[e0m])))}function Gv(){Gv=A,ts2=j0(new K2,(e_x(),e8i),(eB$(),e7N))}function Gy(){Gy=A,ts3=j0(new K2,(e_x(),e8i),(eB$(),e7N))}function Gw(){Gw=A,ts5=j0(new K2,(e_x(),e8i),(eB$(),e7N))}function G_(){G_=A,tuh=RI(new K2,(e_x(),e8i),(eB$(),e7o))}function GE(){GE=A,tug=RI(new K2,(e_x(),e8i),(eB$(),e7o))}function GS(){GS=A,tuv=RI(new K2,(e_x(),e8i),(eB$(),e7o))}function Gk(){Gk=A,tux=RI(new K2,(e_x(),e8i),(eB$(),e7o))}function Gx(){Gx=A,tcz=j0(new K2,(egR(),tu0),(eS_(),tu3))}function GT(e,t,n,r){this.c=e,this.d=r,GA(this,t),GL(this,n)}function GM(e){this.c=new _n,this.b=e.b,this.d=e.c,this.a=e.a}function GO(e){this.a=eB4.Math.cos(e),this.b=eB4.Math.sin(e)}function GA(e,t){e.a&&QA(e.a.k,e),e.a=t,e.a&&P_(e.a.k,e)}function GL(e,t){e.b&&QA(e.b.f,e),e.b=t,e.b&&P_(e.b.f,e)}function GC(e,t){BN(e,e.b,e.c),Pp(e.b.b,65),t&&Pp(t.b,65).b}function GI(e,t){elJ(e,t),M4(e.Cb,88)&&eko(Zd(Pp(e.Cb,88)),2)}function GD(e,t){M4(e.Cb,88)&&eko(Zd(Pp(e.Cb,88)),4),er3(e,t)}function GN(e,t){M4(e.Cb,179)&&(Pp(e.Cb,179).tb=null),er3(e,t)}function GP(e,t){return _4(),eec(t)?new RA(t,e):new xe(t,e)}function GR(e,t){var n,r;(r=null!=(n=t.c))&&BC(e,new B_(t.c))}function Gj(e){var t,n;return n=(yO(),t=new p5),etV(n,e),n}function GF(e){var t,n;return n=(yO(),t=new p5),etV(n,e),n}function GY(e,t){var n;return n=new By(e),t.c[t.c.length]=n,n}function GB(e,t){var n;return(n=Pp(ecA(HU(e.a),t),14))?n.gc():0}function GU(e){var t;return el3(e),etc(e,t=(HF(),HF(),e2u))}function GH(e){for(var t;;)if(t=e.Pb(),!e.Ob())return t}function G$(e,t){mK.call(this,new w8(ee0(e))),enG(t,eUN),this.a=t}function Gz(e,t,n){ec5(t,n,e.gc()),this.c=e,this.a=t,this.b=n-t}function GG(e,t,n){var r;ec5(t,n,e.c.length),r=n-t,yJ(e.c,t,r)}function GW(e,t){M7(e,jE(WM(Fv(t,24),e$b)),jE(WM(t,e$b)))}function GK(e,t){if(e<0||e>=t)throw p7(new gE(e$O+e+e$A+t))}function GV(e,t){if(e<0||e>=t)throw p7(new vf(e$O+e+e$A+t))}function Gq(e,t){this.b=(BJ(e),e),this.a=(t&eH0)==0?64|t|eUR:t}function GZ(e){TG(this),bF(this.a,esi(eB4.Math.max(8,e))<<1)}function GX(e){return esp(eow(vx(e50,1),eUP,8,0,[e.i.n,e.n,e.a]))}function GJ(){return eum(),eow(vx(e2L,1),eU4,132,0,[e2B,e2U,e2H])}function GQ(){return etx(),eow(vx(e26,1),eU4,232,0,[e3D,e3N,e3P])}function G1(){return Qs(),eow(vx(e27,1),eU4,461,0,[e3F,e3j,e3Y])}function G0(){return QQ(),eow(vx(e3t,1),eU4,462,0,[e3$,e3H,e3U])}function G2(){return ec4(),eow(vx(e4L,1),eU4,423,0,[e8x,e8k,e8S])}function G3(){return QJ(),eow(vx(e4S,1),eU4,379,0,[e94,e93,e95])}function G4(){return euJ(),eow(vx(e5e,1),eU4,378,0,[tsn,tsr,tsi])}function G5(){return en7(),eow(vx(e4q,1),eU4,314,0,[tej,teR,teF])}function G6(){return enB(),eow(vx(e4Z,1),eU4,337,0,[teB,teH,teU])}function G9(){return eoG(),eow(vx(e4Q,1),eU4,450,0,[te1,teQ,te0])}function G8(){return erX(),eow(vx(e4G,1),eU4,361,0,[tep,teh,ted])}function G7(){return Q1(),eow(vx(e46,1),eU4,303,0,[ttD,ttN,ttI])}function We(){return eaU(),eow(vx(e45,1),eU4,292,0,[ttA,ttL,ttO])}function Wt(){return enY(),eow(vx(e5o,1),eU4,452,0,[tsP,tsD,tsN])}function Wn(){return esn(),eow(vx(e5i,1),eU4,339,0,[tsM,tsT,tsO])}function Wr(){return ei0(),eow(vx(e5s,1),eU4,375,0,[tsj,tsF,tsY])}function Wi(){return eox(),eow(vx(e5f,1),eU4,377,0,[tsQ,ts1,tsJ])}function Wa(){return euy(),eow(vx(e5c,1),eU4,336,0,[tsz,tsG,tsW])}function Wo(){return eiO(),eow(vx(e5l,1),eU4,338,0,[tsZ,tsV,tsq])}function Ws(){return enU(),eow(vx(e5p,1),eU4,454,0,[tur,tui,tua])}function Wu(){return efx(),eow(vx(e5D,1),eU4,442,0,[tc1,tcJ,tcQ])}function Wc(){return eub(),eow(vx(e5P,1),eU4,380,0,[tc5,tc6,tc9])}function Wl(){return efS(),eow(vx(e5Y,1),eU4,381,0,[tlP,tlR,tlN])}function Wf(){return ei1(),eow(vx(e5j,1),eU4,293,0,[tlC,tlI,tlL])}function Wd(){return efk(),eow(vx(e5H,1),eU4,437,0,[tff,tfd,tfh])}function Wh(){return eck(),eow(vx(e57,1),eU4,334,0,[tpG,tpz,tpW])}function Wp(){return etT(),eow(vx(e56,1),eU4,272,0,[tp_,tpE,tpS])}function Wb(e,t){return eMw(e,t,M4(t,99)&&(Pp(t,18).Bb&eH3)!=0)}function Wm(e,t,n){var r;return(r=ePI(e,t,!1)).b<=t&&r.a<=n}function Wg(e,t,n){var r;(r=new ac).b=t,r.a=n,++t.b,P_(e.d,r)}function Wv(e,t){var n;return A3(!!(n=(BJ(e),e).g)),BJ(t),n(t)}function Wy(e,t){var n,r;return r=Yi(e,t),n=e.a.Zc(r),new wR(e,n)}function Ww(e){return e.Db>>16!=6?null:Pp(eTp(e),235)}function W_(e){if(2!=e.p)throw p7(new bT);return jE(e.f)&eHd}function WE(e){if(2!=e.p)throw p7(new bT);return jE(e.k)&eHd}function WS(e){return e.a==(ZE(),tvd)&&ff(e,eM0(e.g,e.b)),e.a}function Wk(e){return e.d==(ZE(),tvd)&&fh(e,eIj(e.g,e.b)),e.d}function Wx(e){return A6(e.ar?1:0}function WY(e,t){var n,r;return r=n=QP(t),Pp(Bp(e.c,r),19).a}function WB(e,t){var n;for(n=e+"";n.length0&&0==e.a[--e.d];);0==e.a[e.d++]&&(e.e=0)}function Kc(e){return e.a?0==e.e.length?e.a.a:e.a.a+""+e.e:e.c}function Kl(e){return!!e.a&&0!=QX(e.a.a).i&&!(e.b&&ebq(e.b))}function Kf(e){return!!e.u&&0!=qt(e.u.a).i&&!(e.n&&ebV(e.n))}function Kd(e){return Rj(e.e.Hd().gc()*e.c.Hd().gc(),16,new c9(e))}function Kh(e,t){return YM(eap(e.q.getTime()),eap(t.q.getTime()))}function Kp(e){return Pp(epg(e,Je(e4C,eGG,17,e.c.length,0,1)),474)}function Kb(e){return Pp(epg(e,Je(e4N,eGW,10,e.c.length,0,1)),193)}function Km(e){return GE(),!q8(e)&&!(!q8(e)&&e.c.i.c==e.d.i.c)}function Kg(e,t,n){var r;r=(Y9(e),new I4(e)),egT(new Gl(r,t,n))}function Kv(e,t,n){var r;r=(Y9(e),new I4(e)),egM(new Gf(r,t,n))}function Ky(e,t){var n;return n=1-t,e.a[n]=erj(e.a[n],n),erj(e,t)}function Kw(e,t){var n;e.e=new mQ,n=eLj(t),Mv(n,e.c),eLJ(e,n,0)}function K_(e,t,n,r){var i;(i=new od).a=t,i.b=n,i.c=r,P7(e.a,i)}function KE(e,t,n,r){var i;(i=new od).a=t,i.b=n,i.c=r,P7(e.b,i)}function KS(e){var t,n,r;return n=eI4(t=new YQ,e),eFg(t),r=n}function Kk(){var e,t,n;return P_(tg6,t=n=e=new p5),t}function Kx(e){return e.j.c=Je(e1R,eUp,1,0,5,1),UG(e.c),Uj(e.a),e}function KT(e){return(_L(),M4(e.g,10))?Pp(e.g,10):null}function KM(e){return!Uz(e).dc()&&(MI(e,new v),!0)}function KO(e){if(!("stack"in e))try{throw e}catch(t){}return e}function KA(e,t){if(e<0||e>=t)throw p7(new gE(eku(e,t)));return e}function KL(e,t,n){if(e<0||tn)throw p7(new gE(eE3(e,t,n)))}function KC(e,t){if(Yf(e.a,t),t.d)throw p7(new go(e$P));t.d=e}function KI(e,t){if(t.$modCount!=e.$modCount)throw p7(new bA)}function KD(e,t){return!!M4(t,42)&&emT(e.a,Pp(t,42))}function KN(e,t){return!!M4(t,42)&&emT(e.a,Pp(t,42))}function KP(e,t){return!!M4(t,42)&&emT(e.a,Pp(t,42))}function KR(e,t){return e.a<=e.b&&(t.ud(e.a++),!0)}function Kj(e){var t;return Ts(e)?-0==(t=e)?0:t:eem(e)}function KF(e){var t;return B1(e),t=new Y,yU(e.a,new dn(t)),t}function KY(e){var t;return B1(e),t=new F,yU(e.a,new dt(t)),t}function KB(e,t){this.a=e,fE.call(this,e),Gp(t,e.gc()),this.b=t}function KU(e){this.e=e,this.b=this.e.a.entries(),this.a=[]}function KH(e){return Rj(e.e.Hd().gc()*e.c.Hd().gc(),273,new c6(e))}function K$(e){return new XM((enG(e,eU6),ee1(eft(eft(5,e),e/10|0))))}function Kz(e){return Pp(epg(e,Je(e4j,eGK,11,e.c.length,0,1)),1943)}function KG(e,t,n){return n.f.c.length>0?YO(e.a,t,n):YO(e.b,t,n)}function KW(e,t,n){e.d&&QA(e.d.e,e),e.d=t,e.d&&jO(e.d.e,n,e)}function KK(e,t){eY5(t,e),PD(e.d),PD(Pp(e_k(e,(eBy(),taq)),207))}function KV(e,t){eY4(t,e),PI(e.d),PI(Pp(e_k(e,(eBy(),taq)),207))}function Kq(e,t){var n,r;return n=zR(e,t),r=null,n&&(r=n.fe()),r}function KZ(e,t){var n,r;return n=eep(e,t),r=null,n&&(r=n.ie()),r}function KX(e,t){var n,r;return n=zR(e,t),r=null,n&&(r=n.ie()),r}function KJ(e,t){var n,r;return n=zR(e,t),r=null,n&&(r=eSa(n)),r}function KQ(e,t,n){var r;return r=ehM(n),eIg(e.g,r,t),eIg(e.i,t,n),t}function K1(e,t,n){var r;r=ehl();try{return CO(e,t,n)}finally{Vx(r)}}function K0(e){var t;t=e.Wg(),this.a=M4(t,69)?Pp(t,69).Zh():t.Kc()}function K2(){mJ.call(this),this.j.c=Je(e1R,eUp,1,0,5,1),this.a=-1}function K3(e,t,n,r){this.d=e,this.n=t,this.g=n,this.o=r,this.p=-1}function K4(e,t,n,r){this.e=r,this.d=null,this.c=e,this.a=t,this.b=n}function K5(e,t,n){this.d=new hg(this),this.e=e,this.i=t,this.f=n}function K6(){K6=A,ttR=new S_(e$8,0),ttj=new S_("TOP_LEFT",1)}function K9(){K9=A,ts7=Uy(ell(1),ell(4)),ts8=Uy(ell(1),ell(2))}function K8(){K8=A,tfv=euY((_N(),eow(vx(e5z,1),eU4,551,0,[tfg])))}function K7(){K7=A,tfm=euY((_D(),eow(vx(e5$,1),eU4,482,0,[tfb])))}function Ve(){Ve=A,tf7=euY((_P(),eow(vx(e5K,1),eU4,530,0,[tf8])))}function Vt(){Vt=A,e6W=euY((_y(),eow(vx(e4w,1),eU4,481,0,[e6G])))}function Vn(){return eaY(),eow(vx(e3r,1),eU4,406,0,[e4c,e4o,e4s,e4u])}function Vr(){return Qu(),eow(vx(e2E,1),eU4,297,0,[e2D,e2N,e2P,e2R])}function Vi(){return ebe(),eow(vx(e4y,1),eU4,394,0,[e6U,e6B,e6H,e6$])}function Va(){return ep7(),eow(vx(e3i,1),eU4,323,0,[e4d,e4f,e4h,e4p])}function Vo(){return eok(),eow(vx(e4A,1),eU4,405,0,[e8f,e8p,e8d,e8h])}function Vs(){return eoE(),eow(vx(e4U,1),eU4,360,0,[e7Z,e7V,e7q,e7K])}function Vu(e,t,n,r){return M4(n,54)?new A7(e,t,n,r):new Fo(e,t,n,r)}function Vc(){return eoS(),eow(vx(e4$,1),eU4,411,0,[e79,e78,e77,tee])}function Vl(e){var t;return e.j==(eYu(),tbj)&&(t=eTt(e),Aa(t,tby))}function Vf(e,t){var n;Gs(n=t.a,t.c.d),Go(n,t.d.d),etH(n.a,e.n)}function Vd(e,t){return Pp(Af(FT(Pp(Zq(e.k,t),15).Oc(),tex)),113)}function Vh(e,t){return Pp(Af(FM(Pp(Zq(e.k,t),15).Oc(),tex)),113)}function Vp(e){return new Gq(eip(Pp(e.a.dd(),14).gc(),e.a.cd()),16)}function Vb(e){return M4(e,14)?Pp(e,14).dc():!e.Kc().Ob()}function Vm(e){return(_L(),M4(e.g,145))?Pp(e.g,145):null}function Vg(e){if(e.e.g!=e.b)throw p7(new bA);return!!e.c&&e.d>0}function Vv(e){return A6(e.b!=e.d.c),e.c=e.b,e.b=e.b.a,++e.a,e.c.c}function Vy(e,t){BJ(t),Bc(e.a,e.c,t),e.c=e.c+1&e.a.length-1,ega(e)}function Vw(e,t){BJ(t),e.b=e.b-1&e.a.length-1,Bc(e.a,e.b,t),ega(e)}function V_(e,t){var n;for(n=e.j.c.length;n0&&ePD(e.g,0,t,0,e.i),t}function VB(e,t){var n;return _5(),!(n=Pp(Bp(tmU,e),55))||n.wj(t)}function VU(e){if(1!=e.p)throw p7(new bT);return jE(e.f)<<24>>24}function VH(e){if(1!=e.p)throw p7(new bT);return jE(e.k)<<24>>24}function V$(e){if(7!=e.p)throw p7(new bT);return jE(e.k)<<16>>16}function Vz(e){if(7!=e.p)throw p7(new bT);return jE(e.f)<<16>>16}function VG(e){var t;for(t=0;e.Ob();)e.Pb(),t=eft(t,1);return ee1(t)}function VW(e,t){var n;return n=new vl,e.xd(n),n.a+="..",t.yd(n),n.a}function VK(e,t,n){var r;r=Pp(Bp(e.g,n),57),P_(e.a.c,new kD(t,r))}function VV(e,t,n){return F_(LV(xu($I(e.f,t))),LV(xu($I(e.f,n))))}function Vq(e,t,n){return eNA(e,t,n,M4(t,99)&&(Pp(t,18).Bb&eH3)!=0)}function VZ(e,t,n){return eN1(e,t,n,M4(t,99)&&(Pp(t,18).Bb&eH3)!=0)}function VX(e,t,n){return eMN(e,t,n,M4(t,99)&&(Pp(t,18).Bb&eH3)!=0)}function VJ(e,t){return e==(eEn(),e8N)&&t==e8N?4:e==e8N||t==e8N?8:32}function VQ(e,t){return xc(t)===xc(e)?"(this Map)":null==t?eUg:efF(t)}function V1(e,t){return Pp(null==t?xu($I(e.f,null)):Ea(e.g,t),281)}function V0(e,t,n){var r;return r=ehM(n),Um(e.b,r,t),Um(e.c,t,n),t}function V2(e,t){var n;for(n=t;n;)Lu(e,n.i,n.j),n=z$(n);return e}function V3(e,t){var n;return n=$a(Pb(new Qj(e,t))),RG(new Qj(e,t)),n}function V4(e,t){var n;return _4(),eEy(n=Pp(e,66).Mj(),t),n.Ok(t)}function V5(e,t,n,r,i){var a;a=eMW(i,n,r),P_(t,eS4(i,a)),e_X(e,i,t)}function V6(e,t,n){e.i=0,e.e=0,t!=n&&(esC(e,t,n),esL(e,t,n))}function V9(e,t){var n;n=e.q.getHours(),e.q.setFullYear(t+eHx),eNq(e,n)}function V8(e,t,n){if(n){var r=n.ee();e.a[t]=r(n)}else delete e.a[t]}function V7(e,t,n){n=n?n.ee()(n):void 0,e.a[t]=n}function qe(e){if(e<0)throw p7(new gI("Negative array size: "+e))}function qt(e){return e.n||(Zd(e),e.n=new j4(e,tgr,e),$E(e)),e.n}function qn(e){return A6(e.a=0&&e.a[n]===t[n];n--);return n<0}function qy(e,t){var n;return(euv(),0!=(n=e.j.g-t.j.g))?n:0}function qw(e,t){return(BJ(t),null!=e.a)?jN(t.Kb(e.a)):e2b}function q_(e){var t;return e?new RZ(e):(t=new Tw,ein(t,e),t)}function qE(e,t){var n;return t.b.Kb(QD(e,t.c.Ee(),n=new ds(t)))}function qS(e){ewP(),M7(this,jE(WM(Fv(e,24),e$b)),jE(WM(e,e$b)))}function qk(){qk=A,e3d=euY((epC(),eow(vx(e2Q,1),eU4,428,0,[e3f,e3l])))}function qx(){qx=A,e3b=euY((eeR(),eow(vx(e21,1),eU4,427,0,[e3h,e3p])))}function qT(){qT=A,e9h=euY((eej(),eow(vx(e4E,1),eU4,424,0,[e9f,e9d])))}function qM(){qM=A,e8K=euY((erq(),eow(vx(e4F,1),eU4,511,0,[e8W,e8G])))}function qO(){qO=A,teJ=euY((eeF(),eow(vx(e4J,1),eU4,419,0,[teZ,teX])))}function qA(){qA=A,ttn=euY((Qx(),eow(vx(e40,1),eU4,479,0,[ttt,tte])))}function qL(){qL=A,ts$=euY((Xo(),eow(vx(e5u,1),eU4,376,0,[tsH,tsU])))}function qC(){qC=A,tsI=euY((Q0(),eow(vx(e5a,1),eU4,421,0,[tsL,tsC])))}function qI(){qI=A,teP=euY((Jp(),eow(vx(e4V,1),eU4,422,0,[teD,teN])))}function qD(){qD=A,ttF=euY((K6(),eow(vx(e49,1),eU4,420,0,[ttR,ttj])))}function qN(){qN=A,tu$=euY((Xa(),eow(vx(e5k,1),eU4,520,0,[tuH,tuU])))}function qP(){qP=A,tud=euY((qG(),eow(vx(e5v,1),eU4,523,0,[tuf,tul])))}function qR(){qR=A,tu_=euY((zs(),eow(vx(e5E,1),eU4,516,0,[tuw,tuy])))}function qj(){qj=A,tuk=euY((zQ(),eow(vx(e5S,1),eU4,515,0,[tuE,tuS])))}function qF(){qF=A,tuX=euY((zo(),eow(vx(e5x,1),eU4,455,0,[tuq,tuZ])))}function qY(){qY=A,tc$=euY((Kn(),eow(vx(e5C,1),eU4,425,0,[tcH,tcU])))}function qB(){qB=A,tcX=euY((erZ(),eow(vx(e5I,1),eU4,495,0,[tcq,tcZ])))}function qU(){qU=A,tcB=euY((z1(),eow(vx(e5L,1),eU4,480,0,[tcF,tcY])))}function qH(){qH=A,tc4=euY((J0(),eow(vx(e5N,1),eU4,426,0,[tc2,tc3])))}function q$(){q$=A,tdn=euY((eoT(),eow(vx(e5V,1),eU4,429,0,[tdt,tde])))}function qz(){qz=A,tf_=euY((Xs(),eow(vx(e5G,1),eU4,430,0,[tfw,tfy])))}function qG(){qG=A,tuf=new Sj("UPPER",0),tul=new Sj("LOWER",1)}function qW(e,t){var n;H1(n=new gu,"x",t.a),H1(n,"y",t.b),BC(e,n)}function qK(e,t){var n;H1(n=new gu,"x",t.a),H1(n,"y",t.b),BC(e,n)}function qV(e,t){var n,r;r=!1;do n=eo6(e,t),r|=n;while(n)return r}function qq(e,t){var n,r;for(n=t,r=0;n>0;)r+=e.a[n],n-=n&-n;return r}function qZ(e,t){var n;for(n=t;n;)Lu(e,-n.i,-n.j),n=z$(n);return e}function qX(e,t){var n,r;for(BJ(t),r=e.Kc();r.Ob();)n=r.Pb(),t.td(n)}function qJ(e,t){var n;return n=t.cd(),new wD(n,e.e.pc(n,Pp(t.dd(),14)))}function qQ(e,t,n,r){var i;(i=new C).c=t,i.b=n,i.a=r,r.b=n.a=i,++e.b}function q1(e,t,n){var r;return r=(GK(t,e.c.length),e.c[t]),e.c[t]=n,r}function q0(e,t,n){return Pp(null==t?eS9(e.f,null,n):efi(e.g,t,n),281)}function q2(e){return e.c&&e.d?WH(e.c)+"->"+WH(e.d):"e_"+Ao(e)}function q3(e,t){return(el3(e),yK(new R1(e,new Qa(t,e.a)))).sd(e2z)}function q4(){return e_x(),eow(vx(e4k,1),eU4,356,0,[e8e,e8t,e8n,e8r,e8i])}function q5(){return eYu(),eow(vx(e6a,1),eGj,61,0,[tbF,tbw,tby,tbj,tbY])}function q6(e){return vg(),function(){return K1(e,this,arguments)}}function q9(){return Date.now?Date.now():(new Date).getTime()}function q8(e){return!!e.c&&!!e.d&&!!e.c.i&&e.c.i==e.d.i}function q7(e){if(!e.c.Sb())throw p7(new bC);return e.a=!0,e.c.Ub()}function Ze(e){e.i=0,Eb(e.b,null),Eb(e.c,null),e.a=null,e.e=null,++e.g}function Zt(e){El.call(this,null==e?eUg:efF(e),M4(e,78)?Pp(e,78):null)}function Zn(e){eBD(),p8(this),this.a=new _n,esJ(this,e),P7(this.a,e)}function Zr(){Tz(this),this.b=new kl(eHQ,eHQ),this.a=new kl(eH1,eH1)}function Zi(e,t){this.c=0,this.b=t,xR.call(this,e,17493),this.a=this.c}function Za(e){Zo(),!e2M&&(this.c=e,this.e=!0,this.a=new p0)}function Zo(){Zo=A,e2M=!0,e2x=!1,e2T=!1,e2A=!1,e2O=!1}function Zs(e,t){return!!M4(t,149)&&IE(e.c,Pp(t,149).c)}function Zu(e,t){var n;return n=0,e&&(n+=e.f.a/2),t&&(n+=t.f.a/2),n}function Zc(e,t){var n;return(n=Pp(eef(e.d,t),23))||Pp(eef(e.e,t),23)}function Zl(e){this.b=e,Ow.call(this,e),this.a=Pp(eaS(this.b.a,4),126)}function Zf(e){this.b=e,AY.call(this,e),this.a=Pp(eaS(this.b.a,4),126)}function Zd(e){return e.t||(e.t=new pR(e),elm(new gT(e),0,e.t)),e.t}function Zh(){return ec3(),eow(vx(e55,1),eU4,103,0,[tpv,tpg,tpm,tpb,tpy])}function Zp(){return epT(),eow(vx(e6n,1),eU4,249,0,[tbt,tbr,tp7,tbe,tbn])}function Zb(){return epx(),eow(vx(e5Q,1),eU4,175,0,[tdh,tdd,tdl,tdp,tdf])}function Zm(){return eEM(),eow(vx(e5W,1),eU4,316,0,[tfE,tfS,tfT,tfk,tfx])}function Zg(){return ebG(),eow(vx(e5n,1),eU4,315,0,[tsb,tsd,tsh,tsf,tsp])}function Zv(){return eb6(),eow(vx(e4X,1),eU4,335,0,[teG,tez,teK,teV,teW])}function Zy(){return eOB(),eow(vx(e5U,1),eU4,355,0,[tfo,tfa,tfu,tfs,tfc])}function Zw(){return ey4(),eow(vx(e4z,1),eU4,363,0,[ter,tea,teo,tei,ten])}function Z_(){return ef_(),eow(vx(e48,1),eU4,163,0,[tnj,tnD,tnN,tnP,tnR])}function ZE(){var e,t;ZE=A,tvf=(yO(),t=new bN),tvd=e=new mC}function ZS(e){var t;return!e.c&&M4(t=e.r,88)&&(e.c=Pp(t,26)),e.c}function Zk(e){return e.e=3,e.d=e.Yb(),2!=e.e&&(e.e=0,!0)}function Zx(e){var t,n,r;return t=e&eHH,Mk(t,n=e>>22&eHH,r=e<0?eH$:0)}function ZT(e){var t,n,r,i;for(r=0,i=(n=e).length;r0?ehe(e,t):eA8(e,-t)}function ZL(e,t){return 0==t||0==e.e?e:t>0?eA8(e,t):ehe(e,-t)}function ZC(e){if(eTk(e))return e.c=e.a,e.a.Pb();throw p7(new bC)}function ZI(e){var t,n;return t=e.c.i,n=e.d.i,t.k==(eEn(),e8C)&&n.k==e8C}function ZD(e){var t;return t=new $b,eaW(t,e),eo3(t,(eBy(),taR),null),t}function ZN(e,t,n){var r;return(r=e.Yg(t))>=0?e._g(r,n,!0):exk(e,t,n)}function ZP(e,t,n,r){var i;for(i=0;it)throw p7(new gE(eS1(e,t,"index")));return e}function Z1(e,t,n,r){var i;return i=Je(ty_,eHT,25,t,15,1),ewD(i,e,t,n,r),i}function Z0(e,t){var n;n=e.q.getHours()+(t/60|0),e.q.setMinutes(t),eNq(e,n)}function Z2(e,t){return eB4.Math.min(Jh(t.a,e.d.d.c),Jh(t.b,e.d.d.c))}function Z3(e,t){return xd(t)?null==t?eTx(e.f,null):eaK(e.g,t):eTx(e.f,t)}function Z4(e){this.c=e,this.a=new fz(this.c.a),this.b=new fz(this.c.b)}function Z5(){this.e=new p0,this.c=new p0,this.d=new p0,this.b=new p0}function Z6(){this.g=new bJ,this.b=new bJ,this.a=new p0,this.k=new p0}function Z9(e,t,n){this.a=e,this.c=t,this.d=n,P_(t.e,this),P_(n.b,this)}function Z8(e,t){xP.call(this,t.rd(),-6&t.qd()),BJ(e),this.a=e,this.b=t}function Z7(e,t){xR.call(this,t.rd(),-6&t.qd()),BJ(e),this.a=e,this.b=t}function Xe(e,t){xj.call(this,t.rd(),-6&t.qd()),BJ(e),this.a=e,this.b=t}function Xt(e,t,n){this.a=e,this.b=t,this.c=n,P_(e.t,this),P_(t.i,this)}function Xn(){this.b=new _n,this.a=new _n,this.b=new _n,this.a=new _n}function Xr(){Xr=A,tdx=new pO("org.eclipse.elk.labels.labelManager")}function Xi(){Xi=A,e7W=new Cm("separateLayerConnections",(eoE(),e7Z))}function Xa(){Xa=A,tuH=new SW("REGULAR",0),tuU=new SW("CRITICAL",1)}function Xo(){Xo=A,tsH=new SI("STACKED",0),tsU=new SI("SEQUENCED",1)}function Xs(){Xs=A,tfw=new S7("FIXED",0),tfy=new S7("CENTER_NODE",1)}function Xu(e,t){var n;return n=ejH(e,t),e.b=new erH(n.c.length),eRj(e,n)}function Xc(e,t,n){var r;return++e.e,--e.f,(r=Pp(e.d[t].$c(n),133)).dd()}function Xl(e){var t;return!e.a&&M4(t=e.r,148)&&(e.a=Pp(t,148)),e.a}function Xf(e){return e.a?e.e?Xf(e.e):null:e}function Xd(e,t){return e.pt.p?-1:0}function Xh(e,t){return BJ(t),e.c=0,"Initial capacity must not be negative")}function XO(){XO=A,e3R=euY((etx(),eow(vx(e26,1),eU4,232,0,[e3D,e3N,e3P])))}function XA(){XA=A,e3B=euY((Qs(),eow(vx(e27,1),eU4,461,0,[e3F,e3j,e3Y])))}function XL(){XL=A,e3z=euY((QQ(),eow(vx(e3t,1),eU4,462,0,[e3$,e3H,e3U])))}function XC(){XC=A,e2$=euY((eum(),eow(vx(e2L,1),eU4,132,0,[e2B,e2U,e2H])))}function XI(){XI=A,e96=euY((QJ(),eow(vx(e4S,1),eU4,379,0,[e94,e93,e95])))}function XD(){XD=A,e8T=euY((ec4(),eow(vx(e4L,1),eU4,423,0,[e8x,e8k,e8S])))}function XN(){XN=A,teY=euY((en7(),eow(vx(e4q,1),eU4,314,0,[tej,teR,teF])))}function XP(){XP=A,te$=euY((enB(),eow(vx(e4Z,1),eU4,337,0,[teB,teH,teU])))}function XR(){XR=A,te2=euY((eoG(),eow(vx(e4Q,1),eU4,450,0,[te1,teQ,te0])))}function Xj(){Xj=A,teb=euY((erX(),eow(vx(e4G,1),eU4,361,0,[tep,teh,ted])))}function XF(){XF=A,ttP=euY((Q1(),eow(vx(e46,1),eU4,303,0,[ttD,ttN,ttI])))}function XY(){XY=A,ttC=euY((eaU(),eow(vx(e45,1),eU4,292,0,[ttA,ttL,ttO])))}function XB(){XB=A,tsa=euY((euJ(),eow(vx(e5e,1),eU4,378,0,[tsn,tsr,tsi])))}function XU(){XU=A,tsB=euY((ei0(),eow(vx(e5s,1),eU4,375,0,[tsj,tsF,tsY])))}function XH(){XH=A,tsA=euY((esn(),eow(vx(e5i,1),eU4,339,0,[tsM,tsT,tsO])))}function X$(){X$=A,tsR=euY((enY(),eow(vx(e5o,1),eU4,452,0,[tsP,tsD,tsN])))}function Xz(){Xz=A,ts0=euY((eox(),eow(vx(e5f,1),eU4,377,0,[tsQ,ts1,tsJ])))}function XG(){XG=A,tsK=euY((euy(),eow(vx(e5c,1),eU4,336,0,[tsz,tsG,tsW])))}function XW(){XW=A,tsX=euY((eiO(),eow(vx(e5l,1),eU4,338,0,[tsZ,tsV,tsq])))}function XK(){XK=A,tuo=euY((enU(),eow(vx(e5p,1),eU4,454,0,[tur,tui,tua])))}function XV(){XV=A,tc0=euY((efx(),eow(vx(e5D,1),eU4,442,0,[tc1,tcJ,tcQ])))}function Xq(){Xq=A,tc8=euY((eub(),eow(vx(e5P,1),eU4,380,0,[tc5,tc6,tc9])))}function XZ(){XZ=A,tlj=euY((efS(),eow(vx(e5Y,1),eU4,381,0,[tlP,tlR,tlN])))}function XX(){XX=A,tlD=euY((ei1(),eow(vx(e5j,1),eU4,293,0,[tlC,tlI,tlL])))}function XJ(){XJ=A,tfp=euY((efk(),eow(vx(e5H,1),eU4,437,0,[tff,tfd,tfh])))}function XQ(){XQ=A,tpK=euY((eck(),eow(vx(e57,1),eU4,334,0,[tpG,tpz,tpW])))}function X1(){X1=A,tpk=euY((etT(),eow(vx(e56,1),eU4,272,0,[tp_,tpE,tpS])))}function X0(){return ewf(),eow(vx(e6r,1),eU4,98,0,[tbl,tbc,tbu,tba,tbs,tbo])}function X2(e,t){return e.o||(e.o=new JY((eBa(),tmy),e6O,e,0)),edG(e.o,t)}function X3(e){return e.g||(e.g=new o2),e.g.d||(e.g.d=new pD(e)),e.g.d}function X4(e){return e.g||(e.g=new o2),e.g.a||(e.g.a=new pN(e)),e.g.a}function X5(e){return e.g||(e.g=new o2),e.g.b||(e.g.b=new pI(e)),e.g.b}function X6(e){return e.g||(e.g=new o2),e.g.c||(e.g.c=new pP(e)),e.g.c}function X9(e,t,n){var r,i;for(r=0,i=new eaN(t,e);rn||t=0?e._g(n,!0,!0):exk(e,t,!0)}function JW(e,t){return elN(gP(LV(e_k(e,(eBU(),tnv)))),gP(LV(e_k(t,tnv))))}function JK(){JK=A,tcG=ehY(ehY(_G(new K2,(egR(),tuQ)),(eS_(),tu8)),tu4)}function JV(e,t,n){var r;return r=esg(e,t,n),e.b=new erH(r.c.length),eLI(e,r)}function Jq(e){if(e.b<=0)throw p7(new bC);return--e.b,e.a-=e.c.c,ell(e.a)}function JZ(e){var t;if(!e.a)throw p7(new UD);return t=e.a,e.a=z$(e.a),t}function JX(e){for(;!e.a;)if(!IM(e.c,new dr(e)))return!1;return!0}function JJ(e){var t;return(Y9(e),M4(e,198))?t=Pp(e,198):new lp(e)}function JQ(e){J1(),Pp(e.We((eBB(),thJ)),174).Fc((ekU(),tbb)),e.Ye(thX,null)}function J1(){J1=A,tdo=new os,tdu=new ou,tds=es0((eBB(),thX),tdo,thL,tdu)}function J0(){J0=A,tc2=new S2("LEAF_NUMBER",0),tc3=new S2("NODE_SIZE",1)}function J2(e,t,n){e.a=t,e.c=n,e.b.a.$b(),HC(e.d),e.e.a.c=Je(e1R,eUp,1,0,5,1)}function J3(e){e.a=Je(ty_,eHT,25,e.b+1,15,1),e.c=Je(ty_,eHT,25,e.b,15,1),e.d=0}function J4(e,t){e.a.ue(t.d,e.b)>0&&(P_(e.c,new PW(t.c,t.d,e.d)),e.b=t.d)}function J5(e,t){if(null==e.g||t>=e.i)throw p7(new xJ(t,e.i));return e.g[t]}function J6(e,t,n){if(euu(e,n),null!=n&&!e.wj(n))throw p7(new bS);return n}function J9(e){var t;if(e.Ek())for(t=e.i-1;t>=0;--t)etj(e,t);return VY(e)}function J8(e){var t,n;if(!e.b)return null;for(n=e.b;t=n.a[0];)n=t;return n}function J7(e,t){var n,r;return qe(t),(n=QO(r=e.slice(0,t),e)).length=t,n}function Qe(e,t,n,r){var i;r=(HF(),r||e2s),eS0(i=e.slice(t,n),e,t,n,-t,r)}function Qt(e,t,n,r,i){return t<0?exk(e,n,r):Pp(n,66).Nj().Pj(e,e.yh(),t,r,i)}function Qn(e){return M4(e,172)?""+Pp(e,172).a:null==e?null:efF(e)}function Qr(e){return M4(e,172)?""+Pp(e,172).a:null==e?null:efF(e)}function Qi(e,t){if(t.a)throw p7(new go(e$P));Yf(e.a,t),t.a=e,e.j||(e.j=t)}function Qa(e,t){xj.call(this,t.rd(),-16449&t.qd()),BJ(e),this.a=e,this.c=t}function Qo(e,t){var n,r;return r=t/e.c.Hd().gc()|0,n=t%e.c.Hd().gc(),X_(e,r,n)}function Qs(){Qs=A,e3F=new EY(ezt,0),e3j=new EY(e$8,1),e3Y=new EY(ezn,2)}function Qu(){Qu=A,e2D=new Ef("All",0),e2N=new TH,e2P=new ML,e2R=new T$}function Qc(){Qc=A,e2j=euY((Qu(),eow(vx(e2E,1),eU4,297,0,[e2D,e2N,e2P,e2R])))}function Ql(){Ql=A,e8b=euY((eok(),eow(vx(e4A,1),eU4,405,0,[e8f,e8p,e8d,e8h])))}function Qf(){Qf=A,e4l=euY((eaY(),eow(vx(e3r,1),eU4,406,0,[e4c,e4o,e4s,e4u])))}function Qd(){Qd=A,e4b=euY((ep7(),eow(vx(e3i,1),eU4,323,0,[e4d,e4f,e4h,e4p])))}function Qh(){Qh=A,e6z=euY((ebe(),eow(vx(e4y,1),eU4,394,0,[e6U,e6B,e6H,e6$])))}function Qp(){Qp=A,tu2=euY((egR(),eow(vx(e5T,1),eU4,393,0,[tuJ,tuQ,tu1,tu0])))}function Qb(){Qb=A,e7X=euY((eoE(),eow(vx(e4U,1),eU4,360,0,[e7Z,e7V,e7q,e7K])))}function Qm(){Qm=A,tlA=euY((emC(),eow(vx(e5R,1),eU4,340,0,[tlO,tlT,tlM,tlx])))}function Qg(){Qg=A,tet=euY((eoS(),eow(vx(e4$,1),eU4,411,0,[e79,e78,e77,tee])))}function Qv(){Qv=A,tsl=euY((ebk(),eow(vx(e5t,1),eU4,197,0,[tsu,tsc,tss,tso])))}function Qy(){Qy=A,tmo=euY((eup(),eow(vx(e6l,1),eU4,396,0,[tmr,tmi,tmn,tma])))}function Qw(){Qw=A,tpJ=euY((egF(),eow(vx(e6e,1),eU4,285,0,[tpX,tpV,tpq,tpZ])))}function Q_(){Q_=A,tpA=euY((efE(),eow(vx(e59,1),eU4,218,0,[tpO,tpT,tpx,tpM])))}function QE(){QE=A,tmt=euY((edM(),eow(vx(e6u,1),eU4,311,0,[tme,tb9,tb7,tb8])))}function QS(){QS=A,tbZ=euY((ed6(),eow(vx(e6o,1),eU4,374,0,[tbV,tbq,tbK,tbW])))}function Qk(){Qk=A,ePm(),tvq=eHQ,tvV=eH1,tvX=new fL(eHQ),tvZ=new fL(eH1)}function Qx(){Qx=A,ttt=new Sb(eGR,0),tte=new Sb("IMPROVE_STRAIGHTNESS",1)}function QT(e,t){return Pj(),P_(e,new kD(t,ell(t.e.c.length+t.g.c.length)))}function QM(e,t){return Pj(),P_(e,new kD(t,ell(t.e.c.length+t.g.c.length)))}function QO(e,t){return 10!=eeg(t)&&eow(esF(t),t.hm,t.__elementTypeId$,eeg(t),e),e}function QA(e,t){var n;return -1!=(n=QI(e,t,0))&&(ZV(e,n),!0)}function QL(e,t){var n;return(n=Pp(Z3(e.e,t),387))?(Re(n),n.e):null}function QC(e){var t;return Ts(e)&&!isNaN(t=0-e)?t:eal(eoQ(e))}function QI(e,t,n){for(;n=0?ebl(e,n,!0,!0):exk(e,t,!0)}function Q8(e,t){var n,r;return _L(),n=Vm(e),r=Vm(t),!!n&&!!r&&!ep5(n.k,r.k)}function Q7(e,t){eno(e,null==t||IX((BJ(t),t))||isNaN((BJ(t),t))?0:(BJ(t),t))}function eee(e,t){ens(e,null==t||IX((BJ(t),t))||isNaN((BJ(t),t))?0:(BJ(t),t))}function eet(e,t){ena(e,null==t||IX((BJ(t),t))||isNaN((BJ(t),t))?0:(BJ(t),t))}function een(e,t){eni(e,null==t||IX((BJ(t),t))||isNaN((BJ(t),t))?0:(BJ(t),t))}function eer(e){(this.q?this.q:(Hj(),Hj(),e2i)).Ac(e.q?e.q:(Hj(),Hj(),e2i))}function eei(e,t){return M4(t,99)&&(Pp(t,18).Bb&eH3)!=0?new x1(t,e):new eaN(t,e)}function eea(e,t){return M4(t,99)&&(Pp(t,18).Bb&eH3)!=0?new x1(t,e):new eaN(t,e)}function eeo(e,t){e4g=new e0,e4v=t,Pp((e4m=e).b,65),Jr(e4m,e4g,null),eRk(e4m)}function ees(e,t,n){var r;return r=e.g[t],Of(e,t,e.oi(t,n)),e.gi(t,n,r),e.ci(),r}function eeu(e,t){var n;return(n=e.Xc(t))>=0&&(e.$c(n),!0)}function eec(e){var t;return e.d!=e.r&&(t=evl(e),e.e=!!t&&t.Cj()==eJK,e.d=t),e.e}function eel(e,t){var n;for(Y9(e),Y9(t),n=!1;t.Ob();)n|=e.Fc(t.Pb());return n}function eef(e,t){var n;return(n=Pp(Bp(e.e,t),387))?(M6(e,n),n.e):null}function eed(e){var t,n;return(t=e/60|0,0==(n=e%60))?""+t:""+t+":"+n}function eeh(e,t){var n,r;return el3(e),r=new Xe(t,e.a),n=new IU(r),new R1(e,n)}function eep(e,t){var n=e.a[t],r=(eoW(),e0O)[typeof n];return r?r(n):euV(typeof n)}function eeb(e){switch(e.g){case 0:return eUu;case 1:return -1;default:return 0}}function eem(e){return 0>evy(e,(Q2(),e0D))?-As(eoQ(e)):e.l+e.m*eHG+e.h*eHW}function eeg(e){return null==e.__elementTypeCategory$?10:e.__elementTypeCategory$}function eev(e){var t;return null!=(t=0==e.b.c.length?null:RJ(e.b,0))&&erD(e,0),t}function eey(e,t){for(;t[0]=0;)++t[0]}function eew(e,t){this.e=t,this.a=eaJ(e),this.a<54?this.f=Kj(e):this.c=ep_(e)}function ee_(e,t,n,r){eBG(),pJ.call(this,26),this.c=e,this.a=t,this.d=n,this.b=r}function eeE(e,t,n){var r,i;for(i=0,r=10;ie.a[r]&&(r=n);return r}function eeI(e,t){var n;return 0==(n=efT(e.e.c,t.e.c))?elN(e.e.d,t.e.d):n}function eeD(e,t){return 0==t.e||0==e.e?e08:(exX(),eAl(e,t))}function eeN(e,t){if(!e)throw p7(new gL(eAL("Enum constant undefined: %s",t)))}function eeP(){eeP=A,e8v=new tp,e8y=new td,e8m=new ty,e8g=new tw,e8w=new t_}function eeR(){eeR=A,e3h=new ER("BY_SIZE",0),e3p=new ER("BY_SIZE_AND_SHAPE",1)}function eej(){eej=A,e9f=new EH("EADES",0),e9d=new EH("FRUCHTERMAN_REINGOLD",1)}function eeF(){eeF=A,teZ=new Sd("READING_DIRECTION",0),teX=new Sd("ROTATION",1)}function eeY(){eeY=A,teq=euY((eb6(),eow(vx(e4X,1),eU4,335,0,[teG,tez,teK,teV,teW])))}function eeB(){eeB=A,tsm=euY((ebG(),eow(vx(e5n,1),eU4,315,0,[tsb,tsd,tsh,tsf,tsp])))}function eeU(){eeU=A,tes=euY((ey4(),eow(vx(e4z,1),eU4,363,0,[ter,tea,teo,tei,ten])))}function eeH(){eeH=A,tnF=euY((ef_(),eow(vx(e48,1),eU4,163,0,[tnj,tnD,tnN,tnP,tnR])))}function ee$(){ee$=A,tfM=euY((eEM(),eow(vx(e5W,1),eU4,316,0,[tfE,tfS,tfT,tfk,tfx])))}function eez(){eez=A,tdb=euY((epx(),eow(vx(e5Q,1),eU4,175,0,[tdh,tdd,tdl,tdp,tdf])))}function eeG(){eeG=A,tfl=euY((eOB(),eow(vx(e5U,1),eU4,355,0,[tfo,tfa,tfu,tfs,tfc])))}function eeW(){eeW=A,e8a=euY((e_x(),eow(vx(e4k,1),eU4,356,0,[e8e,e8t,e8n,e8r,e8i])))}function eeK(){eeK=A,tpw=euY((ec3(),eow(vx(e55,1),eU4,103,0,[tpv,tpg,tpm,tpb,tpy])))}function eeV(){eeV=A,tbi=euY((epT(),eow(vx(e6n,1),eU4,249,0,[tbt,tbr,tp7,tbe,tbn])))}function eeq(){eeq=A,tbB=euY((eYu(),eow(vx(e6a,1),eGj,61,0,[tbF,tbw,tby,tbj,tbY])))}function eeZ(e,t){var n;return(n=Pp(Bp(e.a,t),134))||(n=new eX,Um(e.a,t,n)),n}function eeX(e){var t;return!!(t=Pp(e_k(e,(eBU(),ttU)),305))&&t.a==e}function eeJ(e){var t;return!!(t=Pp(e_k(e,(eBU(),ttU)),305))&&t.i==e}function eeQ(e,t){return BJ(t),FD(e),!!e.d.Ob()&&(t.td(e.d.Pb()),!0)}function ee1(e){return ecd(e,eUu)>0?eUu:0>ecd(e,eHt)?eHt:jE(e)}function ee0(e){return e<3?(enG(e,eU0),e+1):e=0&&t=-.01&&e.a<=ezs&&(e.a=0),e.b>=-.01&&e.b<=ezs&&(e.b=0),e}function ee5(e,t){return t==(I8(),I8(),e2p)?e.toLocaleLowerCase():e.toLowerCase()}function ee6(e){return((2&e.i)!=0?"interface ":(1&e.i)!=0?"":"class ")+(LW(e),e.o)}function ee9(e){var t,n;n=t=new mD,JL((e.q||(e.q=new FQ(tgi,e,11,10)),e.q),n)}function ee8(e,t){var n;return n=t>0?t-1:t,yr(yi(eny(P6(new mV,n),e.n),e.j),e.k)}function ee7(e,t,n,r){var i;e.j=-1,ex8(e,eSu(e,t,n),(_4(),(i=Pp(t,66).Mj()).Ok(r)))}function ete(e){this.g=e,this.f=new p0,this.a=eB4.Math.min(this.g.c.c,this.g.d.c)}function ett(e){this.b=new p0,this.a=new p0,this.c=new p0,this.d=new p0,this.e=e}function etn(e,t){this.a=new p2,this.e=new p2,this.b=(euJ(),tsi),this.c=e,this.b=t}function etr(e,t,n){CK.call(this),etk(this),this.a=e,this.c=n,this.b=t.d,this.f=t.e}function eti(e){this.d=e,this.c=e.c.vc().Kc(),this.b=null,this.a=null,this.e=(m5(),e0m)}function eta(e){if(e<0)throw p7(new gL("Illegal Capacity: "+e));this.g=this.ri(e)}function eto(e,t){if(0>e||e>t)throw p7(new va("fromIndex: 0, toIndex: "+e+e$m+t))}function ets(e){var t;if(e.a==e.b.a)throw p7(new bC);return t=e.a,e.c=t,e.a=e.a.e,t}function etu(e){var t;A4(!!e.c),t=e.c.a,etw(e.d,e.c),e.b==e.c?e.b=t:--e.a,e.c=null}function etc(e,t){var n;return el3(e),n=new HA(e,e.a.rd(),4|e.a.qd(),t),new R1(e,n)}function etl(e,t){var n,r;return(n=Pp(ecA(e.d,t),14))?(r=t,e.e.pc(r,n)):null}function etf(e,t){var n,r;for(r=e.Kc();r.Ob();)eo3(n=Pp(r.Pb(),70),(eBU(),tnt),t)}function etd(e){var t;return(t=gP(LV(e_k(e,(eBy(),tak)))))<0&&eo3(e,tak,t=0),t}function eth(e,t,n){var r;ev_(n,r=eB4.Math.max(0,e.b/2-.5),1),P_(t,new EJ(n,r))}function etp(e,t,n){var r;return zy(Ra(r=e.a.e[Pp(t.a,10).p]-e.a.e[Pp(n.a,10).p]))}function etb(e,t,n,r,i,a){var o;o=ZD(r),Gs(o,i),Go(o,a),exg(e.a,r,new DT(o,t,n.f))}function etm(e,t){var n;if(!(n=eAh(e.Tg(),t)))throw p7(new gL(eZV+t+eZX));return n}function etg(e,t){var n;for(n=e;z$(n);)if((n=z$(n))==t)return!0;return!1}function etv(e,t){var n,r,i;for(i=0,r=t.a.cd(),n=Pp(t.a.dd(),14).gc();i0&&(e.a/=t,e.b/=t),e}function etP(e){var t;return e.w?e.w:((t=Ww(e))&&!t.kh()&&(e.w=t),t)}function etR(e){var t;return null==e?null:e_e(t=Pp(e,190),t.length)}function etj(e,t){if(null==e.g||t>=e.i)throw p7(new xJ(t,e.i));return e.li(t,e.g[t])}function etF(e){var t,n;for(t=e.a.d.j,n=e.c.d.j;t!=n;)erC(e.b,t),t=elI(t);erC(e.b,t)}function etY(e){var t;for(t=0;t=14&&t<=16)),e}function etW(e,t,n){var r=function(){return e.apply(r,arguments)};return t.apply(r,n),r}function etK(e,t,n){var r,i;r=t;do i=gP(e.p[r.p])+n,e.p[r.p]=i,r=e.a[r.p];while(r!=t)}function etV(e,t){var n,r;r=e.a,n=elr(e,t,null),r==t||e.e||(n=eFr(e,t,n)),n&&n.Fi()}function etq(e,t){return Mc(),enj(eHe),eB4.Math.abs(e-t)<=eHe||e==t||isNaN(e)&&isNaN(t)}function etZ(e,t){return Mc(),enj(eHe),eB4.Math.abs(e-t)<=eHe||e==t||isNaN(e)&&isNaN(t)}function etX(e,t){return e_z(),ME(e.b.c.length-e.e.c.length,t.b.c.length-t.e.c.length)}function etJ(e,t){return yk(eif(e,t,jE(efn(eUJ,Ux(jE(efn(null==t?0:esj(t),eUQ)),15)))))}function etQ(){etQ=A,e8R=euY((eEn(),eow(vx(e4P,1),eU4,267,0,[e8N,e8D,e8C,e8P,e8I,e8L])))}function et1(){et1=A,tdJ=euY((eyY(),eow(vx(e54,1),eU4,291,0,[tdX,tdZ,tdq,tdK,tdW,tdV])))}function et0(){et0=A,tdD=euY((ebx(),eow(vx(e53,1),eU4,248,0,[tdM,tdL,tdC,tdI,tdO,tdA])))}function et2(){et2=A,teI=euY((eSg(),eow(vx(e4K,1),eU4,227,0,[teO,teL,teM,teA,teC,teT])))}function et3(){et3=A,ttm=euY((e_3(),eow(vx(e43,1),eU4,275,0,[ttp,ttf,ttb,tth,ttd,ttl])))}function et4(){et4=A,ttc=euY((eyd(),eow(vx(e42,1),eU4,274,0,[tto,tta,ttu,tti,tts,ttr])))}function et5(){et5=A,tst=euY((ewY(),eow(vx(e47,1),eU4,313,0,[to7,to9,to5,to6,tse,to8])))}function et6(){et6=A,te7=euY((eEf(),eow(vx(e41,1),eU4,276,0,[te4,te3,te6,te5,te8,te9])))}function et9(){et9=A,tu7=euY((eS_(),eow(vx(e5A,1),eU4,327,0,[tu8,tu4,tu6,tu5,tu9,tu3])))}function et8(){et8=A,tbv=euY((ekU(),eow(vx(e6i,1),eU4,273,0,[tbm,tbp,tbb,tbh,tbd,tbg])))}function et7(){et7=A,tpR=euY((e_a(),eow(vx(e58,1),eU4,312,0,[tpN,tpI,tpP,tpL,tpD,tpC])))}function ene(){return eT7(),eow(vx(e6t,1),eU4,93,0,[tp1,tpQ,tp2,tp9,tp6,tp5,tp3,tp4,tp0])}function ent(e,t){var n;n=e.a,e.a=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qo(e,0,n,e.a))}function enn(e,t){var n;n=e.b,e.b=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qo(e,1,n,e.b))}function enr(e,t){var n;n=e.b,e.b=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qo(e,3,n,e.b))}function eni(e,t){var n;n=e.f,e.f=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qo(e,3,n,e.f))}function ena(e,t){var n;n=e.g,e.g=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qo(e,4,n,e.g))}function eno(e,t){var n;n=e.i,e.i=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qo(e,5,n,e.i))}function ens(e,t){var n;n=e.j,e.j=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qo(e,6,n,e.j))}function enu(e,t){var n;n=e.j,e.j=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qo(e,1,n,e.j))}function enc(e,t){var n;n=e.c,e.c=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qo(e,4,n,e.c))}function enl(e,t){var n;n=e.k,e.k=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qo(e,2,n,e.k))}function enf(e,t){var n;n=e.d,e.d=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qs(e,2,n,e.d))}function end(e,t){var n;n=e.s,e.s=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qs(e,4,n,e.s))}function enh(e,t){var n;n=e.t,e.t=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new qs(e,5,n,e.t))}function enp(e,t){var n;n=e.F,e.F=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,5,n,t))}function enb(e,t){var n;return(n=Pp(Bp((_5(),tmU),e),55))?n.xj(t):Je(e1R,eUp,1,t,5,1)}function enm(e,t){var n,r;return(n=t in e.a)&&(r=zR(e,t).he())?r.a:null}function eng(e,t){var n,r,i;return n=(r=(yT(),i=new o0),t&&eAu(r,t),r),eri(n,e),n}function env(e,t,n){if(euu(e,n),!e.Bk()&&null!=n&&!e.wj(n))throw p7(new bS);return n}function eny(e,t){return e.n=t,e.n?(e.f=new p0,e.e=new p0):(e.f=null,e.e=null),e}function enw(e,t,n,r,i,a){var o;return enA(n,o=Y6(e,t)),o.i=i?8:0,o.f=r,o.e=i,o.g=a,o}function en_(e,t,n,r,i){this.d=t,this.k=r,this.f=i,this.o=-1,this.p=1,this.c=e,this.a=n}function enE(e,t,n,r,i){this.d=t,this.k=r,this.f=i,this.o=-1,this.p=2,this.c=e,this.a=n}function enS(e,t,n,r,i){this.d=t,this.k=r,this.f=i,this.o=-1,this.p=6,this.c=e,this.a=n}function enk(e,t,n,r,i){this.d=t,this.k=r,this.f=i,this.o=-1,this.p=7,this.c=e,this.a=n}function enx(e,t,n,r,i){this.d=t,this.j=r,this.e=i,this.o=-1,this.p=4,this.c=e,this.a=n}function enT(e,t){var n,r,i,a;for(i=0,a=(r=t).length;i=0),0>ehP(e.d,e.c)&&(e.a=e.a-1&e.d.a.length-1,e.b=e.d.c),e.c=-1}function enR(e){return e.a<54?e.f<0?-1:e.f>0?1:0:(e.c||(e.c=euK(e.f)),e.c).e}function enj(e){if(!(e>=0))throw p7(new gL("tolerance ("+e+") must be >= 0"));return e}function enF(){return tdc||(tdc=new eC$,es4(tdc,eow(vx(e20,1),eUp,130,0,[new cZ]))),tdc}function enY(){enY=A,tsP=new SL(ezo,0),tsD=new SL("INPUT",1),tsN=new SL("OUTPUT",2)}function enB(){enB=A,teB=new Sl("ARD",0),teH=new Sl("MSD",1),teU=new Sl("MANUAL",2)}function enU(){enU=A,tur=new SR("BARYCENTER",0),tui=new SR(eG7,1),tua=new SR(eWe,2)}function enH(e,t){var n;if(n=e.gc(),t<0||t>n)throw p7(new Ii(t,n));return new IB(e,t)}function en$(e,t){var n;return M4(t,42)?e.c.Mc(t):(n=edG(e,t),ehx(e,t),n)}function enz(e,t,n){return eu2(e,t),er3(e,n),end(e,0),enh(e,1),els(e,!0),eli(e,!0),e}function enG(e,t){if(e<0)throw p7(new gL(t+" cannot be negative but was: "+e));return e}function enW(e,t){var n,r;for(n=0,r=e.gc();n0)?Pp(RJ(n.a,r-1),10):null}function ert(e,t){var n;n=e.k,e.k=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,2,n,e.k))}function ern(e,t){var n;n=e.f,e.f=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,8,n,e.f))}function err(e,t){var n;n=e.i,e.i=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,7,n,e.i))}function eri(e,t){var n;n=e.a,e.a=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,8,n,e.a))}function era(e,t){var n;n=e.b,e.b=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,0,n,e.b))}function ero(e,t){var n;n=e.b,e.b=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,0,n,e.b))}function ers(e,t){var n;n=e.c,e.c=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,1,n,e.c))}function eru(e,t){var n;n=e.c,e.c=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,1,n,e.c))}function erc(e,t){var n;n=e.c,e.c=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,4,n,e.c))}function erl(e,t){var n;n=e.d,e.d=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,1,n,e.d))}function erf(e,t){var n;n=e.D,e.D=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,2,n,e.D))}function erd(e,t){e.r>0&&e.c0&&0!=e.g&&erd(e.i,t/e.r*e.i.d))}function erh(e,t,n){var r;e.b=t,e.a=n,r=(512&e.a)==512?new mU:new u7,e.c=eLV(r,e.b,e.a)}function erp(e,t){return eLt(e.e,t)?(_4(),eec(t)?new RA(t,e):new xe(t,e)):new xr(t,e)}function erb(e,t){return yS(eid(e.a,t,jE(efn(eUJ,Ux(jE(efn(null==t?0:esj(t),eUQ)),15)))))}function erm(e,t,n){return Qz(e,new f9(t),new ea,new f8(n),eow(vx(e2L,1),eU4,132,0,[]))}function erg(e){var t,n;return 0>e?new _e:(t=e+1,n=new Zi(t,e),new L0(null,n))}function erv(e,t){var n;return Hj(),n=new w8(1),xd(e)?Ge(n,e,t):eS9(n.f,e,t),new f$(n)}function ery(e,t){var n,r;return(n=e.o+e.p)<(r=t.o+t.p)?-1:n==r?0:1}function erw(e){var t;return(t=e_k(e,(eBU(),tnc)),M4(t,160))?edo(Pp(t,160)):null}function er_(e){var t;return(t=esi(e=eB4.Math.max(e,2)),e>t)?(t<<=1)>0?t:eU2:t}function erE(e){switch(OZ(3!=e.e),e.e){case 2:return!1;case 0:return!0}return Zk(e)}function erS(e,t){var n;return!!M4(t,8)&&(n=Pp(t,8),e.a==n.a&&e.b==n.b)}function erk(e,t,n){var r,i,a;return a=t>>5,i=31&t,r=WM(Fy(e.n[n][a],jE(Fg(i,1))),3)}function erx(e,t){var n,r;for(r=t.vc().Kc();r.Ob();)evQ(e,(n=Pp(r.Pb(),42)).cd(),n.dd())}function erT(e,t){var n;n=new e0,Pp(t.b,65),Pp(t.b,65),Pp(t.b,65),ety(t.a,new N9(e,n,t))}function erM(e,t){var n;n=e.b,e.b=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,21,n,e.b))}function erO(e,t){var n;n=e.d,e.d=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,11,n,e.d))}function erA(e,t){var n;n=e.j,e.j=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,13,n,e.j))}function erL(e,t,n){var r,i,a;for(a=e.a.length-1,i=e.b,r=0;r>>31;0!=r&&(e[n]=r)}function eip(e,t){var n,r;for(Hj(),r=new p0,n=0;n0&&(this.g=this.ri(this.i+(this.i/8|0)+1),e.Qc(this.g))}function eiR(e,t){PJ.call(this,tgd,e,t),this.b=this,this.a=eAY(e.Tg(),ee2(this.e.Tg(),this.c))}function eij(e,t){var n,r;for(BJ(t),r=t.vc().Kc();r.Ob();)n=Pp(r.Pb(),42),e.zc(n.cd(),n.dd())}function eiF(e,t,n){var r;for(r=n.Kc();r.Ob();)if(!Vq(e,t,r.Pb()))return!1;return!0}function eiY(e,t,n,r,i){var a;return n&&(a=edv(t.Tg(),e.c),i=n.gh(t,-1-(-1==a?r:a),null,i)),i}function eiB(e,t,n,r,i){var a;return n&&(a=edv(t.Tg(),e.c),i=n.ih(t,-1-(-1==a?r:a),null,i)),i}function eiU(e){var t;if(-2==e.b){if(0==e.e)t=-1;else for(t=0;0==e.a[t];t++);e.b=t}return e.b}function eiH(e){switch(e.g){case 2:return eYu(),tbY;case 4:return eYu(),tby;default:return e}}function ei$(e){switch(e.g){case 1:return eYu(),tbj;case 3:return eYu(),tbw;default:return e}}function eiz(e){var t,n,r;return e.j==(eYu(),tbw)&&(t=eTt(e),n=Aa(t,tby),(r=Aa(t,tbY))||r&&n)}function eiG(e){var t,n;return t=Pp(e.e&&e.e(),9),n=Pp(YR(t,t.length),9),new I1(t,n,t.length)}function eiW(e,t){ewG(t,eG9,1),efJ(_p(new dp((__(),new U7(e,!1,!1,new tO))))),eEj(t)}function eiK(e,t){return OQ(),xd(e)?ZZ(e,Lq(t)):xf(e)?F_(e,LV(t)):xl(e)?Fw(e,LK(t)):e.wd(t)}function eiV(e,t){t.q=e,e.d=eB4.Math.max(e.d,t.r),e.b+=t.d+(0==e.a.c.length?0:e.c),P_(e.a,t)}function eiq(e,t){var n,r,i,a;return i=e.c,n=e.c+e.b,a=e.d,r=e.d+e.a,t.a>i&&t.aa&&t.b1||e.Ob())return++e.a,e.g=0,t=e.i,e.Ob(),t;throw p7(new bC)}function eaA(e){var t;return Ma(),En(tuT,e)||((t=new af).a=e,CM(tuT,e,t)),Pp(UA(tuT,e),635)}function eaL(e){var t,n,r,i;return r=0,(i=e)<0&&(i+=eHW,r=eH$),n=zy(i/eHG),Mk(t=zy(i-n*eHG),n,r)}function eaC(e){var t,n,r;for(r=0,n=new _t(e.a);n.aecd(e,0)&&(e=PN(e)),64-(0!=(t=jE(Fv(e,32)))?exv(t):exv(jE(e))+32)}function eaQ(e){var t;return t=Pp(e_k(e,(eBU(),tt1)),61),e.k==(eEn(),e8C)&&(t==(eYu(),tbY)||t==tby)}function ea1(e,t,n){var r,i;(i=Pp(e_k(e,(eBy(),taR)),74))&&(eu_(r=new mE,0,i),etH(r,n),er7(t,r))}function ea0(e,t,n){var r,i,a,o;r=(o=Bq(e)).d,i=o.c,a=e.n,t&&(a.a=a.a-r.b-i.a),n&&(a.b=a.b-r.d-i.b)}function ea2(e,t){var n,r;return(n=e.j)!=(r=t.j)?n.g-r.g:e.p==t.p?0:n==(eYu(),tbw)?e.p-t.p:t.p-e.p}function ea3(e){var t,n;for(eYp(e),n=new fz(e.d);n.a>22),i=e.h+t.h+(r>>22),Mk(n&eHH,r&eHH,i&eH$)}function eor(e,t){var n,r,i;return n=e.l-t.l,r=e.m-t.m+(n>>22),i=e.h-t.h+(r>>22),Mk(n&eHH,r&eHH,i&eH$)}function eoi(e){var t;return e<128?((t=(RH(),e0Y)[e])||(t=e0Y[e]=new fA(e)),t):new fA(e)}function eoa(e){var t;return M4(e,78)?e:((t=e&&e.__java$exception)||(t=new euq(e),by(t)),t)}function eoo(e){if(M4(e,186))return Pp(e,118);if(e)return null;throw p7(new gD(eXR))}function eos(e,t){if(null==t)return!1;for(;e.a!=e.b;)if(ecX(t,ecn(e)))return!0;return!1}function eou(e){return!!e.a.Ob()||e.a==e.d&&(e.a=new KU(e.e.f),e.a.Ob())}function eoc(e,t){var n,r;return 0!=(r=(n=t.Pc()).length)&&(PO(e.c,e.c.length,n),!0)}function eol(e,t,n){var r,i;for(i=t.vc().Kc();i.Ob();)r=Pp(i.Pb(),42),e.yc(r.cd(),r.dd(),n);return e}function eof(e,t){var n,r;for(r=new fz(e.b);r.a=0,"Negative initial capacity"),PG(t>=0,"Non-positive load factor"),Yy(this)}function eoV(e,t,n){return!(e>=128)&&(e<64?xg(WM(Fg(1,e),n),0):xg(WM(Fg(1,e-64),t),0))}function eoq(e,t){return!!e&&!!t&&e!=t&&0>efT(e.b.c,t.b.c+t.b.b)&&0>efT(t.b.c,e.b.c+e.b.b)}function eoZ(e){var t,n,r;return n=e.n,r=e.o,t=e.d,new Hr(n.a-t.b,n.b-t.d,r.a+(t.b+t.c),r.b+(t.d+t.a))}function eoX(e){var t,n,r,i;for(n=e.a,r=0,i=n.length;r(r=e.gc()))throw p7(new Ii(t,r));return e.hi()&&(n=zG(e,n)),e.Vh(t,n)}function eo2(e,t,n){return null==n?(e.q||(e.q=new p2),Z3(e.q,t)):(e.q||(e.q=new p2),Um(e.q,t,n)),e}function eo3(e,t,n){return null==n?(e.q||(e.q=new p2),Z3(e.q,t)):(e.q||(e.q=new p2),Um(e.q,t,n)),e}function eo4(e){var t,n;return n=new Z5,eaW(n,e),eo3(n,(erV(),e9j),e),t=new p2,eNY(e,n,t),eFS(e,n,t),n}function eo5(e){var t,n,r;for(eLG(),n=Je(e50,eUP,8,2,0,1),r=0,t=0;t<2;t++)r+=.5,n[t]=emh(r,e);return n}function eo6(e,t){var n,r,i,a;for(a=0,n=!1,r=e.a[t].length;a>=1);return t}function esa(e){var t,n;return 32==(n=exv(e.h))?32==(t=exv(e.m))?exv(e.l)+32:t+20-10:n-12}function eso(e){var t;return null==(t=e.a[e.b])?null:(Bc(e.a,e.b,null),e.b=e.b+1&e.a.length-1,t)}function ess(e){var t,n;return t=e.t-e.k[e.o.p]*e.d+e.j[e.o.p]>e.f,n=e.u+e.e[e.o.p]*e.d>e.f*e.s*e.d,t||n}function esu(e,t,n){var r,i;return r=new Js(t,n),i=new H,e.b=eLg(e,e.b,r,i),i.b||++e.c,e.b.b=!1,i.d}function esc(e,t,n){var r,i,a,o;for(o=ecZ(t,n),a=0,i=o.Kc();i.Ob();)r=Pp(i.Pb(),11),Um(e.c,r,ell(a++))}function esl(e){var t,n;for(n=new fz(e.a.b);n.an&&(n=e[t]);return n}function esg(e,t,n){var r;return r=new p0,eA0(e,t,r,(eYu(),tby),!0,!1),eA0(e,n,r,tbY,!1,!1),r}function esv(e,t,n){var r,i,a,o;return a=null,i=Kq(o=t,"labels"),a=(eT2((r=new kG(e,n)).a,r.b,i),i)}function esy(e,t,n,r){var i;return!(!(i=eMv(e,t,n,r))&&(i=elh(e,n,r)))||eR3(e,t,i)?i:null}function esw(e,t,n,r){var i;return!(!(i=eMy(e,t,n,r))&&(i=elp(e,n,r)))||eR3(e,t,i)?i:null}function es_(e,t){var n;for(n=0;n1||t>=0&&e.b<3)}function esP(e){var t,n,r;for(t=new mE,r=epL(e,0);r.b!=r.d.c;)n=Pp(Vv(r),8),Ls(t,0,new TS(n));return t}function esR(e){var t,n;for(n=new fz(e.a.b);n.ar?1:0}function esJ(e,t){return!!eO2(e,t)&&(exg(e.b,Pp(e_k(t,(eBU(),ttX)),21),t),P7(e.a,t),!0)}function esQ(e){var t,n;(t=Pp(e_k(e,(eBU(),tng)),10))&&(QA((n=t.c).a,t),0==n.a.c.length&&QA(Bq(t).b,n))}function es1(e){return e2M?Je(e2k,e$_,572,0,0,1):Pp(epg(e.a,Je(e2k,e$_,572,e.a.c.length,0,1)),842)}function es0(e,t,n,r){return U_(),new gt(eow(vx(e1$,1),eUK,42,0,[(eb2(e,t),new wD(e,t)),(eb2(n,r),new wD(n,r))]))}function es2(e,t,n){var r,i;return enz(i=r=new mD,t,n),JL((e.q||(e.q=new FQ(tgi,e,11,10)),e.q),i),i}function es3(e){var t,n,r,i;for(t=0,r=Je(e17,eUP,2,n=(i=Eo(tmx,e)).length,6,1);t=e.b.c.length)&&(es6(e,2*t+1),(n=2*t+2)=0&&e[r]===t[r];r--);return r<0?0:Ei(WM(e[r],eH8),WM(t[r],eH8))?-1:1}function es7(e,t){var n,r;for(r=epL(e,0);r.b!=r.d.c;)(n=Pp(Vv(r),214)).e.length>0&&(t.td(n),n.i&&elk(n))}function eue(e,t){var n,r;return r=Pp(eaS(e.a,4),126),n=Je(e6N,eJM,415,t,0,1),null!=r&&ePD(r,0,n,0,r.length),n}function eut(e,t){var n;return n=new eCg((256&e.f)!=0,e.i,e.a,e.d,(16&e.f)!=0,e.j,e.g,t),null!=e.e||(n.c=e),n}function eun(e,t){var n,r;for(r=e.Zb().Cc().Kc();r.Ob();)if((n=Pp(r.Pb(),14)).Hc(t))return!0;return!1}function eur(e,t,n,r,i){var a,o;for(o=n;o<=i;o++)for(a=t;a<=r;a++)if(emy(e,a,o))return!0;return!1}function eui(e,t,n){var r,i,a,o;for(BJ(n),o=!1,a=e.Zc(t),i=n.Kc();i.Ob();)r=i.Pb(),a.Rb(r),o=!0;return o}function eua(e,t){var n;return e===t||!!M4(t,83)&&(n=Pp(t,83),eEB(Fc(e),n.vc()))}function euo(e,t,n){var r,i;for(i=n.Kc();i.Ob();)if(r=Pp(i.Pb(),42),e.re(t,r.dd()))return!0;return!1}function eus(e,t,n){return e.d[t.p][n.p]||(ebp(e,t,n),e.d[t.p][n.p]=!0,e.d[n.p][t.p]=!0),e.a[t.p][n.p]}function euu(e,t){if(!e.ai()&&null==t)throw p7(new gL("The 'no null' constraint is violated"));return t}function euc(e,t){null==e.D&&null!=e.B&&(e.D=e.B,e.B=null),erf(e,null==t?null:(BJ(t),t)),e.C&&e.yk(null)}function eul(e,t){var n;return!!(e&&e!=t&&Ln(t,(eBU(),tt8)))&&(n=Pp(e_k(t,(eBU(),tt8)),10))!=e}function euf(e){switch(e.i){case 2:return!0;case 1:return!1;case -1:++e.c;default:return e.pl()}}function eud(e){switch(e.i){case -2:return!0;case -1:return!1;case 1:--e.c;default:return e.ql()}}function euh(e){zL.call(this,"The given string does not match the expected format for individual spacings.",e)}function eup(){eup=A,tmr=new kN("ELK",0),tmi=new kN("JSON",1),tmn=new kN("DOT",2),tma=new kN("SVG",3)}function eub(){eub=A,tc5=new S3(eGR,0),tc6=new S3("RADIAL_COMPACTION",1),tc9=new S3("WEDGE_COMPACTION",2)}function eum(){eum=A,e2B=new Ed("CONCURRENT",0),e2U=new Ed("IDENTITY_FINISH",1),e2H=new Ed("UNORDERED",2)}function eug(){eug=A,e6q=(_y(),e6G),e6V=new xX(ezj,e6q),e6K=new pO(ezF),e6Z=new pO(ezY),e6X=new pO(ezB)}function euv(){euv=A,e72=new n1,e73=new n0,e70=new n2,e71=new n3,e7J=(BJ(e7Q=new n4),new P)}function euy(){euy=A,tsz=new SD("CONSERVATIVE",0),tsG=new SD("CONSERVATIVE_SOFT",1),tsW=new SD("SLOPPY",2)}function euw(){euw=A,tpH=new T3(15),tpU=new T2((eBB(),thN),tpH),tp$=th3,tpj=td3,tpF=thx,tpB=thO,tpY=thM}function eu_(e,t,n){var r,i,a;for(r=new _n,a=epL(n,0);a.b!=a.d.c;)i=Pp(Vv(a),8),P7(r,new TS(i));eui(e,t,r)}function euE(e){var t,n,r;for(t=0,r=Je(e50,eUP,8,e.b,0,1),n=epL(e,0);n.b!=n.d.c;)r[t++]=Pp(Vv(n),8);return r}function euS(e){var t;return 0!=(t=(e.a||(e.a=new FQ(tgn,e,9,5)),e.a)).i?_K(Pp(etj(t,0),678)):null}function euk(e,t){var n;return(n=eft(e,t),Ei(WA(e,t),0)|xm(WA(e,n),0))?n:eft(eUY,WA(Fy(n,63),1))}function eux(e,t){var n;n=null!=epB((edk(),to3))&&null!=t.wg()?gP(LV(t.wg()))/gP(LV(epB(to3))):1,Um(e.b,t,n)}function euT(e,t){var n,r;return(n=Pp(e.d.Bc(t),14))?((r=e.e.hc()).Gc(n),e.e.d-=n.gc(),n.$b(),r):null}function euM(e,t){var n,r;if(0!=(r=e.c[t]))for(e.c[t]=0,e.d-=r,n=t+1;n0)return FP(t-1,e.a.c.length),ZV(e.a,t-1);throw p7(new bL)}function euA(e,t,n){if(t<0)throw p7(new gE(eq1+t));tt)throw p7(new gL(e$x+e+e$T+t));if(e<0||t>n)throw p7(new va(e$x+e+e$M+t+e$m+n))}function euC(e){if(!e.a||(8&e.a.i)==0)throw p7(new gC("Enumeration class expected for layout option "+e.f))}function euI(e){var t;++e.j,0==e.i?e.g=null:e.ieVq?e-n>eVq:n-e>eVq)}function euG(e,t){return!e||t&&!e.j||M4(e,124)&&0==Pp(e,124).a.b?0:e.Re()}function euW(e,t){return!e||t&&!e.k||M4(e,124)&&0==Pp(e,124).a.a?0:e.Se()}function euK(e){return(eLQ(),e<0)?-1!=e?new ep4(-1,-e):e03:e<=10?e05[zy(e)]:new ep4(1,e)}function euV(e){throw eoW(),p7(new gs("Unexpected typeof result '"+e+"'; please report this bug to the GWT team"))}function euq(e){g0(),MV(this),HD(this),this.e=e,eA9(this,e),this.g=null==e?eUg:efF(e),this.a="",this.b=e,this.a=""}function euZ(){this.a=new a4,this.f=new hW(this),this.b=new hK(this),this.i=new hV(this),this.e=new hq(this)}function euX(){m6.call(this,new Ju(ee0(16))),enG(2,eUN),this.b=2,this.a=new Uc(null,null,0,null),bp(this.a,this.a)}function euJ(){euJ=A,tsn=new SS("DUMMY_NODE_OVER",0),tsr=new SS("DUMMY_NODE_UNDER",1),tsi=new SS("EQUAL",2)}function euQ(){euQ=A,e8u=zD(eow(vx(e55,1),eU4,103,0,[(ec3(),tpm),tpg])),e8c=zD(eow(vx(e55,1),eU4,103,0,[tpy,tpb]))}function eu1(e){return(eYu(),tbC).Hc(e.j)?gP(LV(e_k(e,(eBU(),tnM)))):esp(eow(vx(e50,1),eUP,8,0,[e.i.n,e.n,e.a])).b}function eu0(e){var t,n,r,i;for(n=(r=e.b.a).a.ec().Kc();n.Ob();)t=Pp(n.Pb(),561),i=new eMq(t,e.e,e.f),P_(e.g,i)}function eu2(e,t){var n,r,i;r=e.nk(t,null),i=null,t&&(i=(yO(),n=new p5),etV(i,e.r)),(r=ew3(e,i,r))&&r.Fi()}function eu3(e,t){var n,r;for(r=0!=eMU(e.d,1),n=!0;n;)n=!1,n=t.c.Tf(t.e,r),n|=eAb(e,t,r,!1),r=!r;er0(e)}function eu4(e,t){var n,r,i;return r=!1,n=t.q.d,t.di&&(eyC(t.q,i),r=n!=t.q.d)),r}function eu5(e,t){var n,r,i,a,o,s,u,c;return u=t.i,c=t.j,i=(r=e.f).i,a=r.j,o=u-i,s=c-a,n=eB4.Math.sqrt(o*o+s*s)}function eu6(e,t){var n,r;return(r=ehO(e))||(tmT||(tmT=new sh),n=(eRe(),eSR(t)),JL((r=new pq(n)).Vk(),e)),r}function eu9(e,t){var n,r;return(n=Pp(e.c.Bc(t),14))?((r=e.hc()).Gc(n),e.d-=n.gc(),n.$b(),e.mc(r)):e.jc()}function eu8(e,t){var n;for(n=0;n=e.c.b:e.a<=e.c.b))throw p7(new bC);return t=e.a,e.a+=e.c.c,++e.b,ell(t)}function eci(e){var t;return t=new ete(e),Kv(e.a,e8w,new g$(eow(vx(e4M,1),eUp,369,0,[t]))),t.d&&P_(t.f,t.d),t.f}function eca(e){var t;return eaW(t=new MA(e.a),e),eo3(t,(eBU(),tnc),e),t.o.a=e.g,t.o.b=e.f,t.n.a=e.i,t.n.b=e.j,t}function eco(e,t,n,r){var i,a;for(a=e.Kc();a.Ob();)(i=Pp(a.Pb(),70)).n.a=t.a+(r.a-i.o.a)/2,i.n.b=t.b,t.b+=i.o.b+n}function ecs(e,t,n){var r,i;for(i=t.a.a.ec().Kc();i.Ob();)if($o(e,r=Pp(i.Pb(),57),n))return!0;return!1}function ecu(e){var t,n;for(n=new fz(e.r);n.a=0?t:-t;r>0;)r%2==0?(n*=n,r=r/2|0):(i*=n,r-=1);return t<0?1/i:i}function ecw(e,t){var n,r,i;for(i=1,n=e,r=t>=0?t:-t;r>0;)r%2==0?(n*=n,r=r/2|0):(i*=n,r-=1);return t<0?1/i:i}function ec_(e){var t,n,r,i;if(null!=e){for(n=0;n0&&esJ(n=Pp(RJ(e.a,e.a.c.length-1),570),t))&&P_(e.a,new Zn(t))}function ecP(e){var t,n;Dj(),t=e.d.c-e.e.c,ety((n=Pp(e.g,145)).b,new d7(t)),ety(n.c,new he(t)),qX(n.i,new ht(t))}function ecR(e){var t;return t=new vc,t.a+="VerticalSegment ",xT(t,e.e),t.a+=" ",xM(t,OU(new ve,new fz(e.k))),t.a}function ecj(e){var t;return(t=Pp(eef(e.c.c,""),229))||(t=new GM(v3(v2(new of,""),"Other")),epy(e.c.c,"",t)),t}function ecF(e){var t;return(64&e.Db)!=0?eMT(e):(t=new O1(eMT(e)),t.a+=" (name: ",xk(t,e.zb),t.a+=")",t.a)}function ecY(e,t,n){var r,i;return i=e.sb,e.sb=t,(4&e.Db)!=0&&(1&e.Db)==0&&(r=new FX(e,1,4,i,t),n?n.Ei(r):n=r),n}function ecB(e,t){var n,r,i;for(n=0,i=efr(e,t).Kc();i.Ob();)n+=null!=e_k(r=Pp(i.Pb(),11),(eBU(),tng))?1:0;return n}function ecU(e,t,n){var r,i,a;for(r=0,a=epL(e,0);a.b!=a.d.c&&!((i=gP(LV(Vv(a))))>n);)i>=t&&++r;return r}function ecH(e,t,n){var r,i;return r=new Q$(e.e,3,13,null,(i=t.c)||(eBK(),tgA),ebv(e,t),!1),n?n.Ei(r):n=r,n}function ec$(e,t,n){var r,i;return r=new Q$(e.e,4,13,(i=t.c)||(eBK(),tgA),null,ebv(e,t),!1),n?n.Ei(r):n=r,n}function ecz(e,t,n){var r,i;return i=e.r,e.r=t,(4&e.Db)!=0&&(1&e.Db)==0&&(r=new FX(e,1,8,i,e.r),n?n.Ei(r):n=r),n}function ecG(e,t){var n,r;return(r=(n=Pp(t,676)).vk())||n.wk(r=M4(t,88)?new k9(e,Pp(t,26)):new Ke(e,Pp(t,148))),r}function ecW(e,t,n){var r;e.qi(e.i+1),r=e.oi(t,n),t!=e.i&&ePD(e.g,t,e.g,t+1,e.i-t),Bc(e.g,t,r),++e.i,e.bi(t,n),e.ci()}function ecK(e,t){var n;return t.a&&(n=t.a.a.length,e.a?xM(e.a,e.b):e.a=new O0(e.d),Ka(e.a,t.a,t.d.length,n)),e}function ecV(e,t){var n,r,i,a;if(t.vi(e.a),null!=(a=Pp(eaS(e.a,8),1936)))for(r=0,i=(n=a).length;rn)throw p7(new gE(e$x+e+e$M+t+", size: "+n));if(e>t)throw p7(new gL(e$x+e+e$T+t))}function ec6(e,t,n){if(t<0)ekN(e,n);else{if(!n.Ij())throw p7(new gL(eZV+n.ne()+eZq));Pp(n,66).Nj().Vj(e,e.yh(),t)}}function ec9(e,t,n,r,i,a,o,s){var u;for(u=n;a=r||t=s.ue(e[t],e[u])?Bc(i,a++,e[t++]):Bc(i,a++,e[u++])}function ec8(e,t,n,r,i,a){this.e=new p0,this.f=(enY(),tsP),P_(this.e,e),this.d=t,this.a=n,this.b=r,this.f=i,this.c=a}function ec7(e,t){var n,r;for(r=new Ow(e);r.e!=r.i.gc();)if(n=Pp(epH(r),26),xc(t)===xc(n))return!0;return!1}function ele(e){var t,n,r,i;for(eBW(),n=epE(),r=0,i=n.length;r=65&&e<=70?e-65+10:e>=97&&e<=102?e-97+10:e>=48&&e<=57?e-48:0}function eln(e){var t;return(64&e.Db)!=0?eMT(e):(t=new O1(eMT(e)),t.a+=" (source: ",xk(t,e.d),t.a+=")",t.a)}function elr(e,t,n){var r,i;return i=e.a,e.a=t,(4&e.Db)!=0&&(1&e.Db)==0&&(r=new FX(e,1,5,i,e.a),n?ey7(n,r):n=r),n}function eli(e,t){var n;n=(256&e.Bb)!=0,t?e.Bb|=256:e.Bb&=-257,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new ZB(e,1,2,n,t))}function ela(e,t){var n;n=(256&e.Bb)!=0,t?e.Bb|=256:e.Bb&=-257,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new ZB(e,1,8,n,t))}function elo(e,t){var n;n=(256&e.Bb)!=0,t?e.Bb|=256:e.Bb&=-257,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new ZB(e,1,8,n,t))}function els(e,t){var n;n=(512&e.Bb)!=0,t?e.Bb|=512:e.Bb&=-513,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new ZB(e,1,3,n,t))}function elu(e,t){var n;n=(512&e.Bb)!=0,t?e.Bb|=512:e.Bb&=-513,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new ZB(e,1,9,n,t))}function elc(e,t){var n;return -1==e.b&&e.a&&(n=e.a.Gj(),e.b=n?e.c.Xg(e.a.aj(),n):edv(e.c.Tg(),e.a)),e.c.Og(e.b,t)}function ell(e){var t,n;return e>-129&&e<128?(t=e+128,(n=(Rv(),e0B)[t])||(n=e0B[t]=new fC(e)),n):new fC(e)}function elf(e){var t,n;return e>-129&&e<128?(t=e+128,(n=(RU(),e0K)[t])||(n=e0K[t]=new fD(e)),n):new fD(e)}function eld(e){var t,n;return(t=e.k)==(eEn(),e8C)&&((n=Pp(e_k(e,(eBU(),tt1)),61))==(eYu(),tbw)||n==tbj)}function elh(e,t,n){var r,i,a;return(a=i=eMC(e.b,t))&&(r=Pp(eP9(Qq(e,a),""),26))?eMv(e,r,t,n):null}function elp(e,t,n){var r,i,a;return(a=i=eMC(e.b,t))&&(r=Pp(eP9(Qq(e,a),""),26))?eMy(e,r,t,n):null}function elb(e,t){var n,r;for(r=new Ow(e);r.e!=r.i.gc();)if(n=Pp(epH(r),138),xc(t)===xc(n))return!0;return!1}function elm(e,t,n){var r;if(t>(r=e.gc()))throw p7(new Ii(t,r));if(e.hi()&&e.Hc(n))throw p7(new gL(eXB));e.Xh(t,n)}function elg(e,t){var n;if(null==(n=etJ(e.i,t)))throw p7(new gK("Node did not exist in input."));return eiX(t,n),null}function elv(e,t){var n;if(n=eAh(e,t),M4(n,322))return Pp(n,34);throw p7(new gL(eZV+t+"' is not a valid attribute"))}function ely(e,t,n){var r,i;for(r=0,i=M4(t,99)&&(Pp(t,18).Bb&eH3)!=0?new x1(t,e):new eaN(t,e);rt?1:e==t?0==e?elN(1/e,1/t):0:isNaN(e)?isNaN(t)?0:1:-1}function elP(e,t){ewG(t,"Sort end labels",1),_r(UJ(eeh(new R1(null,new Gq(e.b,16)),new t2),new t3),new t4),eEj(t)}function elR(e,t,n){var r,i;return e.ej()?(i=e.fj(),r=exm(e,t,n),e.$i(e.Zi(7,ell(n),r,t,i)),r):exm(e,t,n)}function elj(e,t){var n,r,i;null==e.d?(++e.e,--e.f):(i=t.cd(),r=((n=t.Sh())&eUu)%e.d.length,Xc(e,r,eML(e,r,n,i)))}function elF(e,t){var n;n=(e.Bb&eXt)!=0,t?e.Bb|=eXt:e.Bb&=-1025,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new ZB(e,1,10,n,t))}function elY(e,t){var n;n=(e.Bb&eH0)!=0,t?e.Bb|=eH0:e.Bb&=-4097,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new ZB(e,1,12,n,t))}function elB(e,t){var n;n=(e.Bb&eJV)!=0,t?e.Bb|=eJV:e.Bb&=-8193,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new ZB(e,1,15,n,t))}function elU(e,t){var n;n=(e.Bb&eJq)!=0,t?e.Bb|=eJq:e.Bb&=-2049,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new ZB(e,1,11,n,t))}function elH(e,t){var n;return 0!=(n=elN(e.b.c,t.b.c))||0!=(n=elN(e.a.a,t.a.a))?n:elN(e.a.b,t.a.b)}function el$(e,t){var n;if(null==(n=Bp(e.k,t)))throw p7(new gK("Port did not exist in input."));return eiX(t,n),null}function elz(e){var t,n;for(n=eM$(etP(e)).Kc();n.Ob();)if(eDM(e,t=Lq(n.Pb())))return qb((_X(),tgh),t);return null}function elG(e,t){var n,r,i,a,o;for(i=0,o=eAY(e.e.Tg(),t),a=0,n=Pp(e.g,119);i>10)+eH4&eHd,t[1]=(1023&e)+56320&eHd,ehv(t,0,t.length)}function el0(e){var t,n;return(n=Pp(e_k(e,(eBy(),tal)),103))==(ec3(),tpv)?(t=gP(LV(e_k(e,tiX))))>=1?tpg:tpb:n}function el2(e){switch(Pp(e_k(e,(eBy(),tag)),218).g){case 1:return new ig;case 3:return new iE;default:return new im}}function el3(e){if(e.c)el3(e.c);else if(e.d)throw p7(new gC("Stream already terminated, can't be modified or used"))}function el4(e){var t;return(64&e.Db)!=0?eMT(e):(t=new O1(eMT(e)),t.a+=" (identifier: ",xk(t,e.k),t.a+=")",t.a)}function el5(e,t,n){var r,i;return r=(yT(),i=new oJ),ent(r,t),enn(r,n),e&&JL((e.a||(e.a=new O_(e6h,e,5)),e.a),r),r}function el6(e,t,n,r){var i,a;return BJ(r),BJ(n),null==(a=null==(i=e.xc(t))?n:_i(Pp(i,15),Pp(n,14)))?e.Bc(t):e.zc(t,a),a}function el9(e){var t,n,r,i;return n=(t=Pp(yw((i=(r=e.gm).f)==e1G?r:i),9),new I1(t,Pp(CY(t,t.length),9),0)),erC(n,e),n}function el8(e,t,n){var r,i;for(i=e.a.ec().Kc();i.Ob();)if(r=Pp(i.Pb(),10),eot(n,Pp(RJ(t,r.p),14)))return r;return null}function el7(e,t,n){var r;try{esE(e,t,n)}catch(i){if(i=eoa(i),M4(i,597))throw r=i,p7(new Zt(r));throw p7(i)}return t}function efe(e,t){var n;return Ts(e)&&Ts(t)&&eHV<(n=e-t)&&n>1,e.k=n-1>>1}function efo(){var e,t,n;ewP(),n=e2w+++Date.now(),e=zy(eB4.Math.floor(n*e$h))&e$b,t=zy(n-e*e$p),this.a=1502^e,this.b=t^e$d}function efs(e){var t,n,r;for(t=new p0,r=new fz(e.j);r.a34028234663852886e22?eHQ:t<-34028234663852886e22?eH1:t}function efp(e){return e-=e>>1&1431655765,e=((e=(e>>2&858993459)+(858993459&e))>>4)+e&252645135,e+=e>>8,63&(e+=e>>16)}function efb(e){var t,n,r,i;for(t=new CS(e.Hd().gc()),i=0,r=JJ(e.Hd().Kc());r.Ob();)Gr(t,n=r.Pb(),ell(i++));return eEA(t.a)}function efm(e,t){var n,r,i;for(i=new p2,r=t.vc().Kc();r.Ob();)Um(i,(n=Pp(r.Pb(),42)).cd(),eab(e,Pp(n.dd(),15)));return i}function efg(e,t){0==e.n.c.length&&P_(e.n,new zO(e.s,e.t,e.i)),P_(e.b,t),eml(Pp(RJ(e.n,e.n.c.length-1),211),t),eNk(e,t)}function efv(e){return(e.c!=e.b.b||e.i!=e.g.b)&&(e.a.c=Je(e1R,eUp,1,0,5,1),eoc(e.a,e.b),eoc(e.a,e.g),e.c=e.b.b,e.i=e.g.b),e.a}function efy(e,t){var n,r,i;for(i=0,r=Pp(t.Kb(e),20).Kc();r.Ob();)gN(LK(e_k(n=Pp(r.Pb(),17),(eBU(),tnE))))||++i;return i}function efw(e,t){var n,r,i;i=gP(LV(ed$(r=KT(t),(eBy(),toO)))),ev_(t,n=eB4.Math.max(0,i/2-.5),1),P_(e,new E9(t,n))}function ef_(){ef_=A,tnj=new ST(eGR,0),tnD=new ST("FIRST",1),tnN=new ST(eWi,2),tnP=new ST("LAST",3),tnR=new ST(eWa,4)}function efE(){efE=A,tpO=new kb(ezo,0),tpT=new kb("POLYLINE",1),tpx=new kb("ORTHOGONAL",2),tpM=new kb("SPLINES",3)}function efS(){efS=A,tlP=new S6("ASPECT_RATIO_DRIVEN",0),tlR=new S6("MAX_SCALE_DRIVEN",1),tlN=new S6("AREA_DRIVEN",2)}function efk(){efk=A,tff=new S8("P1_STRUCTURE",0),tfd=new S8("P2_PROCESSING_ORDER",1),tfh=new S8("P3_EXECUTION",2)}function efx(){efx=A,tc1=new S0("OVERLAP_REMOVAL",0),tcJ=new S0("COMPACTION",1),tcQ=new S0("GRAPH_SIZE_CALCULATION",2)}function efT(e,t){return Mc(),enj(eHe),eB4.Math.abs(e-t)<=eHe||e==t||isNaN(e)&&isNaN(t)?0:et?1:Te(isNaN(e),isNaN(t))}function efM(e,t){var n,r;for(n=epL(e,0);n.b!=n.d.c;){if((r=gR(LV(Vv(n))))==t)return;if(r>t){Ks(n);break}}YU(n,t)}function efO(e,t){var n,r,i,a,o;if(n=t.f,epy(e.c.d,n,t),null!=t.g)for(i=t.g,a=0,o=i.length;at&&r.ue(e[a-1],e[a])>0;--a)o=e[a],Bc(e,a,e[a-1]),Bc(e,a-1,o)}function efL(e,t,n,r){if(t<0)eOh(e,n,r);else{if(!n.Ij())throw p7(new gL(eZV+n.ne()+eZq));Pp(n,66).Nj().Tj(e,e.yh(),t,r)}}function efC(e,t){if(t==e.d)return e.e;if(t==e.e)return e.d;throw p7(new gL("Node "+t+" not part of edge "+e))}function efI(e,t){switch(t.g){case 2:return e.b;case 1:return e.c;case 4:return e.d;case 3:return e.a;default:return!1}}function efD(e,t){switch(t.g){case 2:return e.b;case 1:return e.c;case 4:return e.d;case 3:return e.a;default:return!1}}function efN(e,t,n,r){switch(t){case 3:return e.f;case 4:return e.g;case 5:return e.i;case 6:return e.j}return ec2(e,t,n,r)}function efP(e){return e.k==(eEn(),e8N)&&q3(new R1(null,new YI(new Fa(OH(efc(e).a.Kc(),new c)))),new it)}function efR(e){return null==e.e?e:(e.c||(e.c=new eCg((256&e.f)!=0,e.i,e.a,e.d,(16&e.f)!=0,e.j,e.g,null)),e.c)}function efj(e,t){return e.h==eHz&&0==e.m&&0==e.l?(t&&(e0A=Mk(0,0,0)),Tr((Q2(),e0I))):(t&&(e0A=Mk(e.l,e.m,e.h)),Mk(0,0,0))}function efF(e){var t;return Array.isArray(e)&&e.im===O?yx(esF(e))+"@"+(t=esj(e)>>>0).toString(16):e.toString()}function efY(e){var t;this.a=(t=Pp(e.e&&e.e(),9),new I1(t,Pp(CY(t,t.length),9),0)),this.b=Je(e1R,eUp,1,this.a.a.length,5,1)}function efB(e){var t,n,r;for(this.a=new Tw,r=new fz(e);r.a0&&(GV(t-1,e.length),58==e.charCodeAt(t-1))&&!efz(e,tm1,tm0)}function efz(e,t,n){var r,i;for(r=0,i=e.length;r=i)return t.c+n;return t.c+t.b.gc()}function efK(e,t){var n,r,i,a;for(LF(),r=J9(e),i=t,Qe(r,0,r.length,i),n=0;n0&&(r+=i,++n);return n>1&&(r+=e.d*(n-1)),r}function efq(e){var t,n,r;for(r=new vs,r.a+="[",t=0,n=e.gc();t0&&this.b>0&&ji(this.c,this.b,this.a)}function ef4(e){edk(),this.c=ZW(eow(vx(e5Z,1),eUp,831,0,[to2])),this.b=new p2,this.a=e,Um(this.b,to3,1),ety(to4,new h4(this))}function ef5(e,t){var n;return e.d?F9(e.b,t)?Pp(Bp(e.b,t),51):(n=t.Kf(),Um(e.b,t,n),n):t.Kf()}function ef6(e,t){var n;return xc(e)===xc(t)||!!M4(t,91)&&(n=Pp(t,91),e.e==n.e&&e.d==n.d&&qv(e,n.a))}function ef9(e){switch(eYu(),e.g){case 4:return tbw;case 1:return tby;case 3:return tbj;case 2:return tbY;default:return tbF}}function ef8(e,t){switch(t){case 3:return 0!=e.f;case 4:return 0!=e.g;case 5:return 0!=e.i;case 6:return 0!=e.j}return eaT(e,t)}function ef7(e){switch(e.g){case 0:return new aV;case 1:return new aq;default:throw p7(new gL(eqa+(null!=e.f?e.f:""+e.g)))}}function ede(e){switch(e.g){case 0:return new aK;case 1:return new aZ;default:throw p7(new gL(eWt+(null!=e.f?e.f:""+e.g)))}}function edt(e){switch(e.g){case 0:return new mZ;case 1:return new m_;default:throw p7(new gL(eqN+(null!=e.f?e.f:""+e.g)))}}function edn(e){switch(e.g){case 1:return new aU;case 2:return new LY;default:throw p7(new gL(eqa+(null!=e.f?e.f:""+e.g)))}}function edr(e){var t,n;if(e.b)return e.b;for(n=e2M?null:e.d;n;){if(t=e2M?null:n.b)return t;n=e2M?null:n.d}return _g(),e2F}function edi(e){var t,n,r;return 0==e.e?0:(t=e.d<<5,n=e.a[e.d-1],e.e<0&&(r=eiU(e))==e.d-1&&(--n,n|=0),t-=exv(n))}function eda(e){var t,n,r;return e>5,t=31&e,(r=Je(ty_,eHT,25,n+1,15,1))[n]=1<3;)i*=10,--a;e=(e+(i>>1))/i|0}return r.i=e,!0}function edl(e){return euQ(),OQ(),!!(efD(Pp(e.a,81).j,Pp(e.b,103))||0!=Pp(e.a,81).d.e&&efD(Pp(e.a,81).j,Pp(e.b,103)))}function edf(e){J1(),Pp(e.We((eBB(),thL)),174).Hc((eI3(),tb4))&&(Pp(e.We(thJ),174).Fc((ekU(),tbg)),Pp(e.We(thL),174).Mc(tb4))}function edd(e,t){var n,r;if(!t)return!1;for(n=0;n=0;--r)for(i=0,t=n[r];i>1,this.k=t-1>>1}function edC(e,t){ewG(t,"End label post-processing",1),_r(UJ(eeh(new R1(null,new Gq(e.b,16)),new tV),new tq),new tZ),eEj(t)}function edI(e,t,n){var r,i;return r=gP(e.p[t.i.p])+gP(e.d[t.i.p])+t.n.b+t.a.b,(i=gP(e.p[n.i.p])+gP(e.d[n.i.p])+n.n.b+n.a.b)-r}function edD(e,t,n){var r,i;for(i=0,r=WM(n,eH8);0!=ecd(r,0)&&i0&&(GV(0,t.length),43==t.charCodeAt(0))?t.substr(1):t)}function edR(e){var t;return null==e?null:new TU((t=ePh(e,!0)).length>0&&(GV(0,t.length),43==t.charCodeAt(0))?t.substr(1):t)}function edj(e,t){var n;return e.i>0&&(t.lengthe.i&&Bc(t,e.i,null),t}function edF(e,t,n){var r,i,a;return e.ej()?(r=e.i,a=e.fj(),ecW(e,r,t),i=e.Zi(3,null,t,r,a),n?n.Ei(i):n=i):ecW(e,e.i,t),n}function edY(e,t,n){var r,i;return r=new Q$(e.e,4,10,M4(i=t.c,88)?Pp(i,26):(eBK(),tgI),null,ebv(e,t),!1),n?n.Ei(r):n=r,n}function edB(e,t,n){var r,i;return r=new Q$(e.e,3,10,null,M4(i=t.c,88)?Pp(i,26):(eBK(),tgI),ebv(e,t),!1),n?n.Ei(r):n=r,n}function edU(e){var t;return Cn(),t=new TS(Pp(e.e.We((eBB(),thO)),8)),e.B.Hc((eI3(),tbQ))&&(t.a<=0&&(t.a=20),t.b<=0&&(t.b=20)),t}function edH(e){var t;return ebk(),t=(e.q?e.q:(Hj(),Hj(),e2i))._b((eBy(),ta0))?Pp(e_k(e,ta0),197):Pp(e_k(Bq(e),ta2),197)}function ed$(e,t){var n,r;return r=null,Ln(e,(eBy(),toD))&&(n=Pp(e_k(e,toD),94)).Xe(t)&&(r=n.We(t)),null==r&&(r=e_k(Bq(e),t)),r}function edz(e,t){var n,r,i;return!!M4(t,42)&&(r=(n=Pp(t,42)).cd(),i=ecA(e.Rc(),r),BG(i,n.dd())&&(null!=i||e.Rc()._b(r)))}function edG(e,t){var n,r,i;return e.f>0&&(e.qj(),i=((r=null==t?0:esj(t))&eUu)%e.d.length,-1!=(n=eML(e,i,r,t)))}function edW(e,t){var n,r,i;return e.f>0&&(e.qj(),i=((r=null==t?0:esj(t))&eUu)%e.d.length,n=exx(e,i,r,t))?n.dd():null}function edK(e,t){var n,r,i,a;for(i=0,a=eAY(e.e.Tg(),t),n=Pp(e.g,119);i1?WO(Fg(t.a[1],32),WM(t.a[0],eH8)):WM(t.a[0],eH8),Kj(efn(t.e,n))))}function edQ(e,t){var n;return Ts(e)&&Ts(t)&&eHV<(n=e%t)&&n>5,t&=31,r=Je(ty_,eHT,25,i=e.d+n+(0==t?0:1),15,1),ewZ(r,e.a,n,t),a=new F7(e.e,i,r),Ku(a),a}function eht(e,t,n){var r,i;r=Pp(zg(tv4,t),117),i=Pp(zg(tv5,t),117),n?(Ge(tv4,e,r),Ge(tv5,e,i)):(Ge(tv5,e,r),Ge(tv4,e,i))}function ehn(e,t,n){var r,i,a;for(i=null,a=e.b;a;){if(r=e.a.ue(t,a.d),n&&0==r)return a;r>=0?a=a.a[1]:(i=a,a=a.a[0])}return i}function ehr(e,t,n){var r,i,a;for(i=null,a=e.b;a;){if(r=e.a.ue(t,a.d),n&&0==r)return a;r<=0?a=a.a[0]:(i=a,a=a.a[1])}return i}function ehi(e,t,n,r){var i,a,o;return i=!1,ejB(e.f,n,r)&&(epn(e.f,e.a[t][n],e.a[t][r]),o=(a=e.a[t])[r],a[r]=a[n],a[n]=o,i=!0),i}function eha(e,t,n,r,i){var a,o,s;for(o=i;t.b!=t.c;)a=Pp(Yn(t),10),s=Pp(efr(a,r).Xb(0),11),e.d[s.p]=o++,n.c[n.c.length]=s;return o}function eho(e,t,n){var r,i,a,o,s;return o=e.k,s=t.k,i=LV(ed$(e,r=n[o.g][s.g])),a=LV(ed$(t,r)),eB4.Math.max((BJ(i),i),(BJ(a),a))}function ehs(e,t,n){var r,i,a,o;for(r=n/e.c.length,i=0,o=new fz(e);o.a2e3&&(e1X=e,e1J=eB4.setTimeout(wf,10)),0==e1Z++&&(eeA((g1(),e0_)),!0)}function ehf(e,t){var n,r,i;for(r=new Fa(OH(efc(e).a.Kc(),new c));eTk(r);)if((i=(n=Pp(ZC(r),17)).d.i).c==t)return!1;return!0}function ehd(e,t){var n,r;if(M4(t,245)){r=Pp(t,245);try{return n=e.vd(r),0==n}catch(i){if(i=eoa(i),!M4(i,205))throw p7(i)}}return!1}function ehh(){return Error.stackTraceLimit>0?(eB4.Error.stackTraceLimit=Error.stackTraceLimit=64,!0):"stack"in Error()}function ehp(e,t){return Mc(),Mc(),enj(eHe),(eB4.Math.abs(e-t)<=eHe||e==t||isNaN(e)&&isNaN(t)?0:et?1:Te(isNaN(e),isNaN(t)))>0}function ehb(e,t){return Mc(),Mc(),enj(eHe),(eB4.Math.abs(e-t)<=eHe||e==t||isNaN(e)&&isNaN(t)?0:et?1:Te(isNaN(e),isNaN(t)))<0}function ehm(e,t){return Mc(),Mc(),enj(eHe),(eB4.Math.abs(e-t)<=eHe||e==t||isNaN(e)&&isNaN(t)?0:et?1:Te(isNaN(e),isNaN(t)))<=0}function ehg(e,t){for(var n=0;!t[n]||""==t[n];)n++;for(var r=t[n++];neH6)return n.fh();if((r=n.Zg())||n==e)break}return r}function ehA(e){return(z0(),M4(e,156))?Pp(Bp(tmR,e0r),288).vg(e):F9(tmR,esF(e))?Pp(Bp(tmR,esF(e)),288).vg(e):null}function ehL(e){if(ehZ(eq6,e))return OQ(),e0P;if(ehZ(eq9,e))return OQ(),e0N;throw p7(new gL("Expecting true or false"))}function ehC(e,t){if(t.c==e)return t.d;if(t.d==e)return t.c;throw p7(new gL("Input edge is not connected to the input port."))}function ehI(e,t){return e.e>t.e?1:e.et.d?e.e:e.d=48&&e<48+eB4.Math.min(10,10)?e-48:e>=97&&e<97?e-97+10:e>=65&&e<65?e-65+10:-1}function ehN(e,t){var n;return xc(t)===xc(e)||!!M4(t,21)&&(n=Pp(t,21)).gc()==e.gc()&&e.Ic(n)}function ehP(e,t){var n,r,i,a;return(r=e.a.length-1,n=t-e.b&r,a=e.c-t&r,A2(n<(i=e.c-e.b&r)),n>=a)?(euD(e,t),-1):(euN(e,t),1)}function ehR(e,t){var n,r;for(n=(GV(t,e.length),e.charCodeAt(t)),r=t+1;rt.e?1:e.ft.f?1:esj(e)-esj(t)}function ehZ(e,t){return BJ(e),null!=t&&(!!IE(e,t)||e.length==t.length&&IE(e.toLowerCase(),t.toLowerCase()))}function ehX(e,t){var n,r,i,a;for(r=0,i=t.gc();r0&&0>ecd(e,128)?(t=jE(e)+128,(n=(RB(),e0H)[t])||(n=e0H[t]=new fI(e)),n):new fI(e)}function eh1(e,t){var n,r;return(n=t.Hh(e.a))&&null!=(r=Lq(edW((n.b||(n.b=new L_((eBK(),tgF),tgf,n)),n.b),eXP)))?r:t.ne()}function eh0(e,t){var n,r;return(n=t.Hh(e.a))&&null!=(r=Lq(edW((n.b||(n.b=new L_((eBK(),tgF),tgf,n)),n.b),eXP)))?r:t.ne()}function eh2(e,t){var n,r;for(Gk(),r=new Fa(OH(efs(e).a.Kc(),new c));eTk(r);)if((n=Pp(ZC(r),17)).d.i==t||n.c.i==t)return n;return null}function eh3(e,t,n){this.c=e,this.f=new p0,this.e=new yb,this.j=new R$,this.n=new R$,this.b=t,this.g=new Hr(t.c,t.d,t.b,t.a),this.a=n}function eh4(e){var t,n,r,i;for(r=0,this.a=new Tw,this.d=new bV,this.e=0,i=(n=e).length;r0)}function ept(e){var t;xc(eT8(e,(eBB(),thl)))===xc((eck(),tpG))&&(z$(e)?(t=Pp(eT8(z$(e),thl),334),ebu(e,thl,t)):ebu(e,thl,tpW))}function epn(e,t,n){var r,i;e_m(e.e,t,n,(eYu(),tbY)),e_m(e.i,t,n,tby),e.a&&(i=Pp(e_k(t,(eBU(),tnc)),11),r=Pp(e_k(n,tnc),11),WW(e.g,i,r))}function epr(e,t,n){var r,i,a;r=t.c.p,a=t.p,e.b[r][a]=new $j(e,t),n&&(e.a[r][a]=new hv(t),(i=Pp(e_k(t,(eBU(),tt8)),10))&&exg(e.d,i,t))}function epi(e,t){var n,r,i;if(P_(e9n,e),t.Fc(e),n=Pp(Bp(e9t,e),21))for(i=n.Kc();i.Ob();)-1!=QI(e9n,r=Pp(i.Pb(),33),0)||epi(r,t)}function epa(e,t,n){var r;(e2x?(edr(e),0):e2T?(_g(),0):e2A?(_g(),0):!e2O||(_g(),1))||((r=new I6(t)).b=n,eEt(e,r))}function epo(e,t){var n;n=!e.A.Hc((ed6(),tbq))||e.q==(ewf(),tbo),e.u.Hc((ekU(),tbp))?n?eY_(e,t):eF3(e,t):e.u.Hc(tbm)&&(n?eFO(e,t):eYY(e,t))}function eps(e,t){var n,r;if(++e.j,null!=t&&exM(t,n=M4(r=e.a.Cb,97)?Pp(r,97).Jg():null)){ehU(e.a,4,n);return}ehU(e.a,4,Pp(t,126))}function epu(e,t,n){return new Hr(eB4.Math.min(e.a,t.a)-n/2,eB4.Math.min(e.b,t.b)-n/2,eB4.Math.abs(e.a-t.a)+n,eB4.Math.abs(e.b-t.b)+n)}function epc(e,t){var n,r;return 0!=(n=ME(e.a.c.p,t.a.c.p))?n:0!=(r=ME(e.a.d.i.p,t.a.d.i.p))?r:ME(t.a.d.p,e.a.d.p)}function epl(e,t,n){var r,i,a,o;return(a=t.j)!=(o=n.j)?a.g-o.g:(r=e.f[t.p],i=e.f[n.p],0==r&&0==i?0:0==r?-1:0==i?1:elN(r,i))}function epf(e,t,n){var r,i,a;if(!n[t.d])for(n[t.d]=!0,i=new fz(efv(t));i.a=(i=e.length))return i;for(t=t>0?t:0;tr&&Bc(t,r,null),t}function epv(e,t){var n,r;for(r=e.a.length,t.lengthr&&Bc(t,r,null),t}function epy(e,t,n){var r,i,a;return(i=Pp(Bp(e.e,t),387))?(a=CL(i,n),M6(e,i),a):(r=new PM(e,t,n),Um(e.e,t,r),zd(r),null)}function epw(e){var t;if(null==e)return null;if(null==(t=eMI(ePh(e,!0))))throw p7(new gV("Invalid hexBinary value: '"+e+"'"));return t}function ep_(e){return(eLQ(),0>ecd(e,0))?0!=ecd(e,-1)?new ey$(-1,QC(e)):e03:0>=ecd(e,10)?e05[jE(e)]:new ey$(1,e)}function epE(){return eBW(),eow(vx(e3n,1),eU4,159,0,[e4e,e37,e4t,e30,e31,e32,e35,e34,e33,e38,e39,e36,e3J,e3X,e3Q,e3q,e3V,e3Z,e3W,e3G,e3K,e4n])}function epS(e){var t;this.d=new p0,this.j=new yb,this.g=new yb,t=e.g.b,this.f=Pp(e_k(Bq(t),(eBy(),tal)),103),this.e=gP(LV(epj(t,toN)))}function epk(e){this.b=new p0,this.e=new p0,this.d=e,this.a=!yK(UJ(new R1(null,new YI(new Z4(e.b))),new f2(new ir))).sd((_w(),e2z))}function epx(){epx=A,tdh=new ko("PARENTS",0),tdd=new ko("NODES",1),tdl=new ko("EDGES",2),tdp=new ko("PORTS",3),tdf=new ko("LABELS",4)}function epT(){epT=A,tbt=new kw("DISTRIBUTED",0),tbr=new kw("JUSTIFIED",1),tp7=new kw("BEGIN",2),tbe=new kw(e$8,3),tbn=new kw("END",4)}function epM(e){var t;switch(t=e.yi(null)){case 10:return 0;case 15:return 1;case 14:return 2;case 11:return 3;case 21:return 4}return -1}function epO(e){switch(e.g){case 1:return ec3(),tpy;case 4:return ec3(),tpm;case 2:return ec3(),tpg;case 3:return ec3(),tpb}return ec3(),tpv}function epA(e,t,n){var r;switch((r=n.q.getFullYear()-eHx+eHx)<0&&(r=-r),t){case 1:e.a+=r;break;case 2:eeE(e,r%100,2);break;default:eeE(e,r,t)}}function epL(e,t){var n,r;if(Gp(t,e.b),t>=e.b>>1)for(r=e.c,n=e.b;n>t;--n)r=r.b;else for(n=0,r=e.a.a;n=64&&t<128&&(i=WO(i,Fg(1,t-64)));return i}function epj(e,t){var n,r;return r=null,Ln(e,(eBB(),tpa))&&(n=Pp(e_k(e,tpa),94)).Xe(t)&&(r=n.We(t)),null==r&&Bq(e)&&(r=e_k(Bq(e),t)),r}function epF(e,t){var n,r,i;(r=(i=t.d.i).k)!=(eEn(),e8N)&&r!=e8L&&(n=new Fa(OH(efc(i).a.Kc(),new c)),eTk(n)&&Um(e.k,t,Pp(ZC(n),17)))}function epY(e,t){var n,r,i;return r=ee2(e.Tg(),t),(n=t-e.Ah())<0?(i=e.Yg(r))>=0?e.lh(i):exu(e,r):n<0?exu(e,r):Pp(r,66).Nj().Sj(e,e.yh(),n)}function epB(e){var t;if(!M4(e.a,4))return e.a;if(null==(t=ehA(e.a)))throw p7(new gC(eq8+e.b+"'. "+eq4+(LW(e6D),e6D.k)+eq5));return t}function epU(e){var t;if(null==e)return null;if(null==(t=eYD(ePh(e,!0))))throw p7(new gV("Invalid base64Binary value: '"+e+"'"));return t}function epH(e){var t;try{return t=e.i.Xb(e.e),e.mj(),e.g=e.e++,t}catch(n){if(n=eoa(n),M4(n,73))throw e.mj(),p7(new bC);throw p7(n)}}function ep$(e){var t;try{return t=e.c.ki(e.e),e.mj(),e.g=e.e++,t}catch(n){if(n=eoa(n),M4(n,73))throw e.mj(),p7(new bC);throw p7(n)}}function epz(){epz=A,e67=(eBB(),tpt),e63=ths,e6J=td2,e64=thN,e69=(evw(),e3y),e66=e3g,e68=e3_,e65=e3m,e61=(eug(),e6V),e6Q=e6K,e60=e6Z,e62=e6X}function epG(e){switch(_M(),this.c=new p0,this.d=e,e.g){case 0:case 2:this.a=Ug(e8_),this.b=eHQ;break;case 3:case 1:this.a=e8_,this.b=eH1}}function epW(e,t,n){var r,i;if(e.c)eno(e.c,e.c.i+t),ens(e.c,e.c.j+n);else for(i=new fz(e.b);i.a0&&(P_(e.b,new PE(t.a,n)),0<(r=t.a.length)?t.a=t.a.substr(0,0):0>r&&(t.a+=M3(Je(tyw,eHl,25,-r,15,1))))}function epq(e,t){var n,r,i;for(n=e.o,i=Pp(Pp(Zq(e.r,t),21),84).Kc();i.Ob();)(r=Pp(i.Pb(),111)).e.a=ego(r,n.a),r.e.b=n.b*gP(LV(r.b.We(e4a)))}function epZ(e,t){var n,r,i,a;return i=e.k,n=gP(LV(e_k(e,(eBU(),tnv)))),a=t.k,r=gP(LV(e_k(t,tnv))),a!=(eEn(),e8C)?-1:i!=e8C?1:n==r?0:n=0?e.hh(t,n,r):(e.eh()&&(r=(i=e.Vg())>=0?e.Qg(r):e.eh().ih(e,-1-i,null,r)),e.Sg(t,n,r))}function ep2(e,t){switch(t){case 7:e.e||(e.e=new Ih(e6g,e,7,4)),eRT(e.e);return;case 8:e.d||(e.d=new Ih(e6g,e,8,5)),eRT(e.d);return}edS(e,t)}function ep3(e,t){var n;n=e.Zc(t);try{return n.Pb()}catch(r){if(r=eoa(r),M4(r,109))throw p7(new gE("Can't get element "+t));throw p7(r)}}function ep4(e,t){this.e=e,t=0&&(n.d=e.t);break;case 3:e.t>=0&&(n.a=e.t)}e.C&&(n.b=e.C.b,n.c=e.C.c)}function ep7(){ep7=A,e4d=new EN(ezb,0),e4f=new EN(ezm,1),e4h=new EN(ezg,2),e4p=new EN(ezv,3),e4d.a=!1,e4f.a=!0,e4h.a=!1,e4p.a=!0}function ebe(){ebe=A,e6U=new ED(ezb,0),e6B=new ED(ezm,1),e6H=new ED(ezg,2),e6$=new ED(ezv,3),e6U.a=!1,e6B.a=!0,e6H.a=!1,e6$.a=!0}function ebt(e){var t;t=e.a;do(t=Pp(ZC(new Fa(OH(efu(t).a.Kc(),new c))),17).c.i).k==(eEn(),e8D)&&e.b.Fc(t);while(t.k==(eEn(),e8D))e.b=eaa(e.b)}function ebn(e){var t,n,r;for(r=e.c.a,e.p=(Y9(r),new I4(r)),n=new fz(r);n.an.b))}function ebs(e,t){return xd(e)?!!e0c[t]:e.hm?!!e.hm[t]:xf(e)?!!e0u[t]:!!xl(e)&&!!e0s[t]}function ebu(e,t,n){return null==n?(e.o||(e.o=new JY((eBa(),tmy),e6O,e,0)),ehx(e.o,t)):(e.o||(e.o=new JY((eBa(),tmy),e6O,e,0)),evQ(e.o,t,n)),e}function ebc(e,t,n,r){var i,a;a=t.Xe((eBB(),thS))?Pp(t.We(thS),21):e.j,(i=ele(a))!=(eBW(),e4n)&&(!n||ehj(i))&&eEU(eMD(e,i,r),t)}function ebl(e,t,n,r){var i,a,o;return a=ee2(e.Tg(),t),(i=t-e.Ah())<0?(o=e.Yg(a))>=0?e._g(o,n,!0):exk(e,a,n):Pp(a,66).Nj().Pj(e,e.yh(),i,n,r)}function ebf(e,t,n,r){var i,a,o;n.mh(t)&&(_4(),eec(t)?ehX(e,i=Pp(n.ah(t),153)):(a=(o=t)?Pp(r,49).xh(o):null)&&p6(n.ah(t),a))}function ebd(e){switch(e.g){case 1:return eaY(),e4c;case 3:return eaY(),e4o;case 2:return eaY(),e4u;case 4:return eaY(),e4s;default:return null}}function ebh(e){switch(typeof e){case eUo:return ebA(e);case eUa:return zy(e);case eUi:return OQ(),e?1231:1237;default:return null==e?0:Ao(e)}}function ebp(e,t,n){if(e.e)switch(e.b){case 1:HJ(e.c,t,n);break;case 0:HQ(e.c,t,n)}else V6(e.c,t,n);e.a[t.p][n.p]=e.c.i,e.a[n.p][t.p]=e.c.e}function ebb(e){var t,n;if(null==e)return null;for(t=0,n=Je(e4N,eUP,193,e.length,0,2);t=0)return i;if(e.Fk()){for(r=0;r=(i=e.gc()))throw p7(new Ii(t,i));if(e.hi()&&(r=e.Xc(n))>=0&&r!=t)throw p7(new gL(eXB));return e.mi(t,n)}function ebw(e,t){if(this.a=Pp(Y9(e),245),this.b=Pp(Y9(t),245),e.vd(t)>0||e==(m3(),e0f)||t==(m2(),e0d))throw p7(new gL("Invalid range: "+VW(e,t)))}function eb_(e){var t,n;for(this.b=new p0,this.c=e,this.a=!1,n=new fz(e.a);n.a0),(t&-t)==t)return zy(t*eMU(e,31)*4656612873077393e-25);do r=(n=eMU(e,31))%t;while(n-r+(t-1)<0)return zy(r)}function ebA(e){var t,n,r;return(I9(),null!=(r=e2W[n=":"+e]))?zy((BJ(r),r)):(t=null==(r=e2G[n])?eAC(e):zy((BJ(r),r)),HB(),e2W[n]=t,t)}function ebL(e,t,n){ewG(n,"Compound graph preprocessor",1),e.a=new zu,eFC(e,t,null),eRs(e,t),eOz(e),eo3(t,(eBU(),ttW),e.a),e.a=null,Yy(e.b),eEj(n)}function ebC(e,t,n){switch(n.g){case 1:e.a=t.a/2,e.b=0;break;case 2:e.a=t.a,e.b=t.b/2;break;case 3:e.a=t.a/2,e.b=t.b;break;case 4:e.a=0,e.b=t.b/2}}function ebI(e){var t,n,r;for(r=Pp(Zq(e.a,(ey4(),tea)),15).Kc();r.Ob();)t=egD(n=Pp(r.Pb(),101)),Yz(e,n,t[0],(erX(),ted),0),Yz(e,n,t[1],tep,1)}function ebD(e){var t,n,r;for(r=Pp(Zq(e.a,(ey4(),teo)),15).Kc();r.Ob();)t=egD(n=Pp(r.Pb(),101)),Yz(e,n,t[0],(erX(),ted),0),Yz(e,n,t[1],tep,1)}function ebN(e){switch(e.g){case 0:return null;case 1:return new er1;case 2:return new mQ;default:throw p7(new gL(eqa+(null!=e.f?e.f:""+e.g)))}}function ebP(e,t,n){var r,i;for(eod(e,t-e.s,n-e.t),i=new fz(e.n);i.a1&&(a=ebE(e,t)),a}function ebj(e){var t;return e.f&&e.f.kh()&&(t=Pp(e.f,49),e.f=Pp(ecv(e,t),82),e.f!=t&&(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,9,8,t,e.f))),e.f}function ebF(e){var t;return e.i&&e.i.kh()&&(t=Pp(e.i,49),e.i=Pp(ecv(e,t),82),e.i!=t&&(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,9,7,t,e.i))),e.i}function ebY(e){var t;return e.b&&(64&e.b.Db)!=0&&(t=e.b,e.b=Pp(ecv(e,t),18),e.b!=t&&(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,9,21,t,e.b))),e.b}function ebB(e,t){var n,r,i;null==e.d?(++e.e,++e.f):(r=t.Sh(),eO1(e,e.f+1),i=(r&eUu)%e.d.length,(n=e.d[i])||(n=e.d[i]=e.uj()),n.Fc(t),++e.f)}function ebU(e,t,n){var r;return!t.Kj()&&(-2!=t.Zj()?null==(r=t.zj())?null==n:ecX(r,n):t.Hj()==e.e.Tg()&&null==n)}function ebH(){var e;enG(16,eU0),e=er_(16),this.b=Je(e1z,eU1,317,e,0,1),this.c=Je(e1z,eU1,317,e,0,1),this.a=null,this.e=null,this.i=0,this.f=e-1,this.g=0}function eb$(e){CW.call(this),this.k=(eEn(),e8N),this.j=(enG(6,eU3),new XM(6)),this.b=(enG(2,eU3),new XM(2)),this.d=new md,this.f=new mb,this.a=e}function ebz(e){var t,n;!(e.c.length<=1)&&(t=eLW(e,(eYu(),tbj)),eSe(e,Pp(t.a,19).a,Pp(t.b,19).a),n=eLW(e,tbY),eSe(e,Pp(n.a,19).a,Pp(n.b,19).a))}function ebG(){ebG=A,tsb=new Sx("SIMPLE",0),tsd=new Sx(eWg,1),tsh=new Sx("LINEAR_SEGMENTS",2),tsf=new Sx("BRANDES_KOEPF",3),tsp=new Sx(eVI,4)}function ebW(e,t,n){IR(Pp(e_k(t,(eBy(),tol)),98))||(Q3(e,t,eEC(t,n)),Q3(e,t,eEC(t,(eYu(),tbj))),Q3(e,t,eEC(t,tbw)),Hj(),Mv(t.j,new hm(e)))}function ebK(e,t,n,r){var i,a,o;for(o=(i=r?Pp(Zq(e.a,t),21):Pp(Zq(e.b,t),21)).Kc();o.Ob();)if(eL8(e,n,a=Pp(o.Pb(),33)))return!0;return!1}function ebV(e){var t,n;for(n=new Ow(e);n.e!=n.i.gc();)if((t=Pp(epH(n),87)).e||0!=(t.d||(t.d=new O_(tgr,t,1)),t.d).i)return!0;return!1}function ebq(e){var t,n;for(n=new Ow(e);n.e!=n.i.gc();)if((t=Pp(epH(n),87)).e||0!=(t.d||(t.d=new O_(tgr,t,1)),t.d).i)return!0;return!1}function ebZ(e){var t,n,r;for(t=0,r=new fz(e.c.a);r.a102?-1:e<=57?e-48:e<65?-1:e<=70?e-65+10:e<97?-1:e-97+10}function eb2(e,t){if(null==e)throw p7(new gD("null key in entry: null="+t));if(null==t)throw p7(new gD("null value in entry: "+e+"=null"))}function eb3(e,t){for(var n,r;e.Ob();)if(!t.Ob()||(n=e.Pb(),r=t.Pb(),!(xc(n)===xc(r)||null!=n&&ecX(n,r))))return!1;return!t.Ob()}function eb4(e,t){var n;return n=eow(vx(tyx,1),eH5,25,15,[euG(e.a[0],t),euG(e.a[1],t),euG(e.a[2],t)]),e.d&&(n[0]=eB4.Math.max(n[0],n[2]),n[2]=n[0]),n}function eb5(e,t){var n;return n=eow(vx(tyx,1),eH5,25,15,[euW(e.a[0],t),euW(e.a[1],t),euW(e.a[2],t)]),e.d&&(n[0]=eB4.Math.max(n[0],n[2]),n[2]=n[0]),n}function eb6(){eb6=A,teG=new Sf("GREEDY",0),tez=new Sf(eWv,1),teK=new Sf(eWg,2),teV=new Sf("MODEL_ORDER",3),teW=new Sf("GREEDY_MODEL_ORDER",4)}function eb9(e,t){var n,r,i;for(e.b[t.g]=1,r=epL(t.d,0);r.b!=r.d.c;)i=(n=Pp(Vv(r),188)).c,1==e.b[i.g]?P7(e.a,n):2==e.b[i.g]?e.b[i.g]=1:eb9(e,i)}function eb8(e,t){var n,r,i;for(i=new XM(t.gc()),r=t.Kc();r.Ob();)(n=Pp(r.Pb(),286)).c==n.f?eE5(e,n,n.c):eEQ(e,n)||(i.c[i.c.length]=n);return i}function eb7(e,t,n){var r,i,a,o,s;for(s=e.r+t,e.r+=t,e.d+=n,r=n/e.n.c.length,i=0,o=new fz(e.n);o.aa&&Bc(t,a,null),t}function emx(e,t){var n,r;if(r=e.gc(),null==t){for(n=0;n0&&(u+=i),c[l]=o,o+=s*(u+r)}function emj(e){var t,n,r;for(t=0,r=e.f,e.n=Je(tyx,eH5,25,r,15,1),e.d=Je(tyx,eH5,25,r,15,1);t0?e.c:0),++i;e.b=r,e.d=a}function emW(e,t){var n,r,i,a,o;for(r=0,i=0,n=0,o=new fz(t);o.a0?e.g:0),++n;e.c=i,e.d=r}function emK(e,t){var n;return n=eow(vx(tyx,1),eH5,25,15,[ebM(e,(etx(),e3D),t),ebM(e,e3N,t),ebM(e,e3P,t)]),e.f&&(n[0]=eB4.Math.max(n[0],n[2]),n[2]=n[0]),n}function emV(e,t,n){var r;try{eCQ(e,t+e.j,n+e.k,!1,!0)}catch(i){if(i=eoa(i),M4(i,73))throw r=i,p7(new gE(r.g+ezk+t+eUd+n+")."));throw p7(i)}}function emq(e,t,n){var r;try{eCQ(e,t+e.j,n+e.k,!0,!1)}catch(i){if(i=eoa(i),M4(i,73))throw r=i,p7(new gE(r.g+ezk+t+eUd+n+")."));throw p7(i)}}function emZ(e){var t;Ln(e,(eBy(),taZ))&&((t=Pp(e_k(e,taZ),21)).Hc((eT7(),tp1))?(t.Mc(tp1),t.Fc(tp2)):t.Hc(tp2)&&(t.Mc(tp2),t.Fc(tp1)))}function emX(e){var t;Ln(e,(eBy(),taZ))&&((t=Pp(e_k(e,taZ),21)).Hc((eT7(),tp9))?(t.Mc(tp9),t.Fc(tp5)):t.Hc(tp5)&&(t.Mc(tp5),t.Fc(tp9)))}function emJ(e,t,n){ewG(n,"Self-Loop ordering",1),_r(UQ(UJ(UJ(eeh(new R1(null,new Gq(t.b,16)),new n9),new n8),new n7),new re),new d1(e)),eEj(n)}function emQ(e,t,n,r){var i,a;for(i=t;i0&&(i.b+=t),i}function em8(e,t){var n,r,i;for(i=new yb,r=e.Kc();r.Ob();)eIn(n=Pp(r.Pb(),37),0,i.b),i.b+=n.f.b+t,i.a=eB4.Math.max(i.a,n.f.a);return i.a>0&&(i.a+=t),i}function em7(e){var t,n,r;for(r=eUu,n=new fz(e.a);n.a>16==6?e.Cb.ih(e,5,e6E,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||e.zh(),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function egr(e){$O();var t=e.e;if(t&&t.stack){var n=t.stack,r=t+"\n";return n.substring(0,r.length)==r&&(n=n.substring(r.length)),n.split("\n")}return[]}function egi(e){var t;return(t=(en4(),e0U))[e>>>28]|t[e>>24&15]<<4|t[e>>20&15]<<8|t[e>>16&15]<<12|t[e>>12&15]<<16|t[e>>8&15]<<20|t[e>>4&15]<<24|t[15&e]<<28}function ega(e){var t,n,r;e.b==e.c&&(r=e.a.length,n=esi(eB4.Math.max(8,r))<<1,0!=e.b?(t=CY(e.a,n),erL(e,t,r),e.a=t,e.b=0):bF(e.a,n),e.c=r)}function ego(e,t){var n;return(n=e.b).Xe((eBB(),thK))?n.Hf()==(eYu(),tbY)?-n.rf().a-gP(LV(n.We(thK))):t+gP(LV(n.We(thK))):n.Hf()==(eYu(),tbY)?-n.rf().a:t}function egs(e){var t;return 0!=e.b.c.length&&Pp(RJ(e.b,0),70).a?Pp(RJ(e.b,0),70).a:null!=(t=Hh(e))?t:""+(e.c?QI(e.c.a,e,0):-1)}function egu(e){var t;return 0!=e.f.c.length&&Pp(RJ(e.f,0),70).a?Pp(RJ(e.f,0),70).a:null!=(t=Hh(e))?t:""+(e.i?QI(e.i.j,e,0):-1)}function egc(e,t){var n,r;if(t<0||t>=e.gc())return null;for(n=t;n0?e.c:0),i=eB4.Math.max(i,t.d),++r;e.e=a,e.b=i}function egd(e){var t,n;if(!e.b)for(e.b=K$(Pp(e.f,118).Ag().i),n=new Ow(Pp(e.f,118).Ag());n.e!=n.i.gc();)t=Pp(epH(n),137),P_(e.b,new gO(t));return e.b}function egh(e,t){var n,r,i;if(t.dc())return LF(),LF(),tmB;for(n=new Cy(e,t.gc()),i=new Ow(e);i.e!=i.i.gc();)r=epH(i),t.Hc(r)&&JL(n,r);return n}function egp(e,t,n,r){return 0==t?r?(e.o||(e.o=new JY((eBa(),tmy),e6O,e,0)),e.o):(e.o||(e.o=new JY((eBa(),tmy),e6O,e,0)),X6(e.o)):ebl(e,t,n,r)}function egb(e){var t,n;if(e.rb)for(t=0,n=e.rb.i;t>22))>>22)<0)&&(e.l=n&eHH,e.m=r&eHH,e.h=i&eH$,!0))}function egw(e,t,n,r,i,a,o){var s,u;return!(t.Ae()&&((u=e.a.ue(n,r))<0||!i&&0==u)||t.Be()&&((s=e.a.ue(n,a))>0||!o&&0==s))}function eg_(e,t){var n;if(euv(),0!=(n=e.j.g-t.j.g))return 0;switch(e.j.g){case 2:return efy(t,e73)-efy(e,e73);case 4:return efy(e,e72)-efy(t,e72)}return 0}function egE(e){switch(e.g){case 0:return te3;case 1:return te4;case 2:return te5;case 3:return te6;case 4:return te9;case 5:return te8;default:return null}}function egS(e,t,n){var r,i;return r=(eu2(i=new mN,t),er3(i,n),JL((e.c||(e.c=new FQ(tga,e,12,10)),e.c),i),i),end(r,0),enh(r,1),els(r,!0),eli(r,!0),r}function egk(e,t){var n,r;if(t>=e.i)throw p7(new xJ(t,e.i));return++e.j,n=e.g[t],(r=e.i-t-1)>0&&ePD(e.g,t+1,e.g,t,r),Bc(e.g,--e.i,null),e.fi(t,n),e.ci(),n}function egx(e,t){var n,r;return e.Db>>16==17?e.Cb.ih(e,21,tm7,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||e.zh(),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function egT(e){var t,n,r,i;for(Hj(),Mv(e.c,e.a),i=new fz(e.c);i.an.a.c.length))throw p7(new gL("index must be >= 0 and <= layer node count"));e.c&&QA(e.c.a,e),e.c=n,n&&jO(n.a,t,e)}function egH(e,t){var n,r,i;for(r=new Fa(OH(efs(e).a.Kc(),new c));eTk(r);)return n=Pp(ZC(r),17),i=Pp(t.Kb(n),10),new c5(Y9(i.n.b+i.o.b/2));return m4(),m4(),e0l}function eg$(e,t){this.c=new p2,this.a=e,this.b=t,this.d=Pp(e_k(e,(eBU(),tnx)),304),xc(e_k(e,(eBy(),taX)))===xc((Qx(),tte))?this.e=new mg:this.e=new mm}function egz(e,t){var n,r,i,a;for(a=0,r=new fz(e);r.a>16==6?e.Cb.ih(e,6,e6g,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||(eBa(),tmp),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function eg0(e,t){var n,r;return e.Db>>16==7?e.Cb.ih(e,1,e6p,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||(eBa(),tmm),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function eg2(e,t){var n,r;return e.Db>>16==9?e.Cb.ih(e,9,e6k,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||(eBa(),tmv),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function eg3(e,t){var n,r;return e.Db>>16==5?e.Cb.ih(e,9,tgt,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||(eBK(),tgT),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function eg4(e,t){var n,r;return e.Db>>16==3?e.Cb.ih(e,0,e6y,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||(eBK(),tgy),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function eg5(e,t){var n,r;return e.Db>>16==7?e.Cb.ih(e,6,e6E,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||(eBK(),tgP),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function eg6(){this.a=new o6,this.g=new ebH,this.j=new ebH,this.b=new p2,this.d=new ebH,this.i=new ebH,this.k=new p2,this.c=new p2,this.e=new p2,this.f=new p2}function eg9(e,t,n){var r,i,a;for(n<0&&(n=0),a=e.i,i=n;ieH6)return eg7(e,r);if(r==e)return!0}}return!1}function eve(e){switch(Ab(),e.q.g){case 5:ekK(e,(eYu(),tbw)),ekK(e,tbj);break;case 4:eMz(e,(eYu(),tbw)),eMz(e,tbj);break;default:eYa(e,(eYu(),tbw)),eYa(e,tbj)}}function evt(e){switch(Ab(),e.q.g){case 5:exG(e,(eYu(),tby)),exG(e,tbY);break;case 4:epq(e,(eYu(),tby)),epq(e,tbY);break;default:eYo(e,(eYu(),tby)),eYo(e,tbY)}}function evn(e){var t,n;(t=Pp(e_k(e,(eCk(),e9O)),19))?0==(n=t.a)?eo3(e,(erV(),e9F),new efo):eo3(e,(erV(),e9F),new qS(n)):eo3(e,(erV(),e9F),new qS(1))}function evr(e,t){var n;switch(n=e.i,t.g){case 1:return-(e.n.b+e.o.b);case 2:return e.n.a-n.o.a;case 3:return e.n.b-n.o.b;case 4:return-(e.n.a+e.o.a)}return 0}function evi(e,t){switch(e.g){case 0:return t==(ef_(),tnN)?e7V:e7q;case 1:return t==(ef_(),tnN)?e7V:e7K;case 2:return t==(ef_(),tnN)?e7K:e7q;default:return e7K}}function eva(e,t){var n,r,i;for(QA(e.a,t),e.e-=t.r+(0==e.a.c.length?0:e.c),i=eqe,r=new fz(e.a);r.a>16==3?e.Cb.ih(e,12,e6k,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||(eBa(),tmh),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function evs(e,t){var n,r;return e.Db>>16==11?e.Cb.ih(e,10,e6k,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||(eBa(),tmg),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function evu(e,t){var n,r;return e.Db>>16==10?e.Cb.ih(e,11,tm7,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||(eBK(),tgD),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function evc(e,t){var n,r;return e.Db>>16==10?e.Cb.ih(e,12,tgi,t):(r=ebY(Pp(ee2((n=Pp(eaS(e,16),26))||(eBK(),tgR),e.Db>>16),18)),e.Cb.ih(e,r.n,r.f,t))}function evl(e){var t;return(1&e.Bb)==0&&e.r&&e.r.kh()&&(t=Pp(e.r,49),e.r=Pp(ecv(e,t),138),e.r!=t&&(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,9,8,t,e.r))),e.r}function evf(e,t,n){var r;return r=eow(vx(tyx,1),eH5,25,15,[e_u(e,(etx(),e3D),t,n),e_u(e,e3N,t,n),e_u(e,e3P,t,n)]),e.f&&(r[0]=eB4.Math.max(r[0],r[2]),r[2]=r[0]),r}function evd(e,t){var n,r,i;if(0!=(i=eb8(e,t)).c.length)for(Mv(i,new nD),n=i.c.length,r=0;r>19)!=(c=t.h>>19)?c-u:(i=e.h)!=(s=t.h)?i-s:(r=e.m)!=(o=t.m)?r-o:(n=e.l)-(a=t.l)}function evw(){evw=A,e3E=(eCp(),e3A),e3_=new xX(e$J,e3E),e3w=(eeR(),e3p),e3y=new xX(e$Q,e3w),e3v=(epC(),e3f),e3g=new xX(e$1,e3v),e3m=new xX(e$0,(OQ(),!0))}function ev_(e,t,n){var r,i;r=t*n,M4(e.g,145)?(i=Vm(e)).f.d?i.f.a||(e.d.a+=r+ezs):(e.d.d-=r+ezs,e.d.a+=r+ezs):M4(e.g,10)&&(e.d.d-=r,e.d.a+=2*r)}function evE(e,t,n){var r,i,a,o,s;for(i=e[n.g],s=new fz(t.d);s.a0?e.g:0),++n;t.b=r,t.e=i}function evk(e){var t,n,r;if(r=e.b,w4(e.i,r.length)){for(n=2*r.length,e.b=Je(e1z,eU1,317,n,0,1),e.c=Je(e1z,eU1,317,n,0,1),e.f=n-1,e.i=0,t=e.a;t;t=t.c)ekT(e,t,t);++e.g}}function evx(e,t,n,r){var i,a,o,s;for(i=0;io&&(s=o/r),i>a&&(u=a/i),Ol(e,eB4.Math.min(s,u)),e}function evO(){var e,t;ePm();try{if(t=Pp(eyv((_Q(),tgp),eXe),2014))return t}catch(n){if(n=eoa(n),M4(n,102))e=n,Fi((Mo(),e));else throw p7(n)}return new o1}function evA(){var e,t;Qk();try{if(t=Pp(eyv((_Q(),tgp),eQB),2024))return t}catch(n){if(n=eoa(n),M4(n,102))e=n,Fi((Mo(),e));else throw p7(n)}return new uc}function evL(){var e,t;ePm();try{if(t=Pp(eyv((_Q(),tgp),eQc),1941))return t}catch(n){if(n=eoa(n),M4(n,102))e=n,Fi((Mo(),e));else throw p7(n)}return new sT}function evC(e,t,n){var r,i;return i=e.e,e.e=t,(4&e.Db)!=0&&(1&e.Db)==0&&(r=new FX(e,1,4,i,t),n?n.Ei(r):n=r),i!=t&&(n=t?eFr(e,eOl(e,t),n):eFr(e,e.a,n)),n}function evI(){wW.call(this),this.e=-1,this.a=!1,this.p=eHt,this.k=-1,this.c=-1,this.b=-1,this.g=!1,this.f=-1,this.j=-1,this.n=-1,this.i=-1,this.d=-1,this.o=eHt}function evD(e,t){var n,r,i;if(r=e.b.d.d,e.a||(r+=e.b.d.a),i=t.b.d.d,t.a||(i+=t.b.d.a),0==(n=elN(r,i))){if(!e.a&&t.a)return -1;if(!t.a&&e.a)return 1}return n}function evN(e,t){var n,r,i;if(r=e.b.b.d,e.a||(r+=e.b.b.a),i=t.b.b.d,t.a||(i+=t.b.b.a),0==(n=elN(r,i))){if(!e.a&&t.a)return -1;if(!t.a&&e.a)return 1}return n}function evP(e,t){var n,r,i;if(r=e.b.g.d,e.a||(r+=e.b.g.a),i=t.b.g.d,t.a||(i+=t.b.g.a),0==(n=elN(r,i))){if(!e.a&&t.a)return -1;if(!t.a&&e.a)return 1}return n}function evR(){evR=A,e99=j0(RI(RI(RI(new K2,(e_x(),e8r),(eB$(),e7f)),e8r,e7b),e8i,e7E),e8i,e87),e97=RI(RI(new K2,e8r,e8Q),e8r,e7e),e98=j0(new K2,e8i,e7n)}function evj(e){var t,n,r,i,a;for(t=Pp(e_k(e,(eBU(),ttq)),83),a=e.n,r=t.Cc().Kc();r.Ob();)i=(n=Pp(r.Pb(),306)).i,i.c+=a.a,i.d+=a.b,n.c?eL3(n):eL4(n);eo3(e,ttq,null)}function evF(e,t,n){var r,i;switch(r=(i=e.b).d,t.g){case 1:return-r.d-n;case 2:return i.o.a+r.c+n;case 3:return i.o.b+r.a+n;case 4:return-r.b-n;default:return -1}}function evY(e){var t,n,r,i,a;if(r=0,i=ezq,e.b)for(t=0;t<360;t++)n=.017453292519943295*t,eIq(e,e.d,0,0,eV7,n),(a=e.b.ig(e.d))0&&(o=(a&eUu)%e.d.length,i=exx(e,o,a,t)))?s=i.ed(n):(r=e.tj(a,t,n),e.c.Fc(r),null)}function ev1(e,t){var n,r,i,a;switch(ecG(e,t)._k()){case 3:case 2:for(i=0,a=(n=ePk(t)).i;i=0;r--)if(IE(e[r].d,t)||IE(e[r].d,n)){e.length>=r+1&&e.splice(0,r+1);break}return e}function eyt(e,t){var n;return Ts(e)&&Ts(t)&&eHV<(n=e/t)&&n0&&(e.b+=2,e.a+=r):(e.b+=1,e.a+=eB4.Math.min(r,i))}function eyc(e,t){var n,r;if(r=!1,xd(t)&&(r=!0,BC(e,new B_(Lq(t)))),!r&&M4(t,236)&&(r=!0,BC(e,(n=IZ(Pp(t,236)),new lI(n)))),!r)throw p7(new gk(eXE))}function eyl(e,t,n,r){var i,a,o;return i=new Q$(e.e,1,10,M4(o=t.c,88)?Pp(o,26):(eBK(),tgI),M4(a=n.c,88)?Pp(a,26):(eBK(),tgI),ebv(e,t),!1),r?r.Ei(i):r=i,r}function eyf(e){var t,n;switch(Pp(e_k(Bq(e),(eBy(),taP)),420).g){case 0:return t=e.n,n=e.o,new kl(t.a+n.a/2,t.b+n.b/2);case 1:return new TS(e.n);default:return null}}function eyd(){eyd=A,tto=new Sm(eGR,0),tta=new Sm("LEFTUP",1),ttu=new Sm("RIGHTUP",2),tti=new Sm("LEFTDOWN",3),tts=new Sm("RIGHTDOWN",4),ttr=new Sm("BALANCED",5)}function eyh(e,t,n){var r,i,a;if(0==(r=elN(e.a[t.p],e.a[n.p]))){if(i=Pp(e_k(t,(eBU(),tt7)),15),a=Pp(e_k(n,tt7),15),i.Hc(n))return -1;if(a.Hc(t))return 1}return r}function eyp(e){switch(e.g){case 1:return new a$;case 2:return new az;case 3:return new aH;case 0:return null;default:throw p7(new gL(eqa+(null!=e.f?e.f:""+e.g)))}}function eyb(e,t,n){switch(t){case 1:e.n||(e.n=new FQ(e6S,e,1,7)),eRT(e.n),e.n||(e.n=new FQ(e6S,e,1,7)),Y4(e.n,Pp(n,14));return;case 2:ert(e,Lq(n));return}esU(e,t,n)}function eym(e,t,n){switch(t){case 3:eni(e,gP(LV(n)));return;case 4:ena(e,gP(LV(n)));return;case 5:eno(e,gP(LV(n)));return;case 6:ens(e,gP(LV(n)));return}eyb(e,t,n)}function eyg(e,t,n){var r,i,a;(i=ew3(a=r=new mN,t,null))&&i.Fi(),er3(a,n),JL((e.c||(e.c=new FQ(tga,e,12,10)),e.c),a),end(a,0),enh(a,1),els(a,!0),eli(a,!0)}function eyv(e,t){var n,r,i;return M4(n=Ea(e.g,t),235)?((i=Pp(n,235)).Qh(),i.Nh()):M4(n,498)?i=(r=Pp(n,1938)).b:null}function eyy(e,t,n,r){var i,a;return Y9(t),Y9(n),a=Pp(Iq(e.d,t),19),QW(!!a,"Row %s not in %s",t,e.e),i=Pp(Iq(e.b,n),19),QW(!!i,"Column %s not in %s",n,e.c),eoy(e,a.a,i.a,r)}function eyw(e,t,n,r,i,a,o){var s,u,c,l,f;if(l=i[a],f=emH(s=(c=a==o-1)?r:0,l),10!=r&&eow(vx(e,o-a),t[a],n[a],s,f),!c)for(++a,u=0;u1||-1==s?(a=Pp(u,15),i.Wb(ehk(e,a))):i.Wb(eI4(e,Pp(u,56))))}function eyP(e,t,n,r){wd();var i=eUn;function a(){for(var e=0;eeVW);)i>-.000001&&++n;return n}function eyW(e,t){var n;t!=e.b?(n=null,e.b&&(n=$7(e.b,e,-4,n)),t&&(n=ep0(t,e,-4,n)),(n=ecm(e,t,n))&&n.Fi()):(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,3,t,t))}function eyK(e,t){var n;t!=e.f?(n=null,e.f&&(n=$7(e.f,e,-1,n)),t&&(n=ep0(t,e,-1,n)),(n=ecg(e,t,n))&&n.Fi()):(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,0,t,t))}function eyV(e){var t,n,r;if(null==e)return null;if((n=Pp(e,15)).dc())return"";for(r=new vs,t=n.Kc();t.Ob();)xk(r,(eR7(),Lq(t.Pb()))),r.a+=" ";return x3(r,r.a.length-1)}function eyq(e){var t,n,r;if(null==e)return null;if((n=Pp(e,15)).dc())return"";for(r=new vs,t=n.Kc();t.Ob();)xk(r,(eR7(),Lq(t.Pb()))),r.a+=" ";return x3(r,r.a.length-1)}function eyZ(e,t,n){var r,i;return(r=e.c[t.c.p][t.p],i=e.c[n.c.p][n.p],null!=r.a&&null!=i.a)?F_(r.a,i.a):null!=r.a?-1:null!=i.a?1:0}function eyX(e,t){var n,r,i,a,o,s;if(t)for(a=t.a.length,s=((n=new Fs(a)).b-n.a)*n.c<0?(_9(),eB3):new OR(n);s.Ob();)i=KZ(t,(o=Pp(s.Pb(),19)).a),UX((r=new pu(e)).a,i)}function eyJ(e,t){var n,r,i,a,o,s;if(t)for(a=t.a.length,s=((n=new Fs(a)).b-n.a)*n.c<0?(_9(),eB3):new OR(n);s.Ob();)i=KZ(t,(o=Pp(s.Pb(),19)).a),UZ((r=new h7(e)).a,i)}function eyQ(e){var t;if(null!=e&&e.length>0&&33==UI(e,e.length-1))try{return t=eSR(Az(e,0,e.length-1)),null==t.e}catch(n){if(n=eoa(n),!M4(n,32))throw p7(n)}return!1}function ey1(e,t,n){var r,i,a;return r=t.ak(),a=t.dd(),i=r.$j()?$N(e,3,r,null,a,eN1(e,r,a,M4(r,99)&&(Pp(r,18).Bb&eH3)!=0),!0):$N(e,1,r,r.zj(),a,-1,!0),n?n.Ei(i):n=i,n}function ey0(){var e,t,n;for(e=0,t=0;e<1;e++){if(0==(n=eTa((GV(e,1),"X".charCodeAt(e)))))throw p7(new gX("Unknown Option: "+"X".substr(e)));t|=n}return t}function ey2(e,t,n){var r,i,a;switch(i=el0(r=Bq(t)),a=new eES,Gc(a,t),n.g){case 1:ekv(a,elC(ef9(i)));break;case 2:ekv(a,ef9(i))}return eo3(a,(eBy(),toc),LV(e_k(e,toc))),a}function ey3(e){var t,n;return t=Pp(ZC(new Fa(OH(efu(e.a).a.Kc(),new c))),17),n=Pp(ZC(new Fa(OH(efc(e.a).a.Kc(),new c))),17),gN(LK(e_k(t,(eBU(),tnE))))||gN(LK(e_k(n,tnE)))}function ey4(){ey4=A,ter=new Sa("ONE_SIDE",0),tea=new Sa("TWO_SIDES_CORNER",1),teo=new Sa("TWO_SIDES_OPPOSING",2),tei=new Sa("THREE_SIDES",3),ten=new Sa("FOUR_SIDES",4)}function ey5(e,t,n,r,i){var a,o;a=Pp(qE(UJ(t.Oc(),new ih),JF(new U,new B,new en,eow(vx(e2L,1),eU4,132,0,[(eum(),e2U)]))),15),o=Pp(eay(e.b,n,r),15),0==i?o.Wc(0,a):o.Gc(a)}function ey6(e,t){var n,r,i,a,o;for(a=new fz(t.a);a.a0&&egL(this,this.c-1,(eYu(),tby)),this.c0&&e[0].length>0&&(this.c=gN(LK(e_k(Bq(e[0][0]),(eBU(),tne))))),this.a=Je(e5d,eUP,2018,e.length,0,2),this.b=Je(e5h,eUP,2019,e.length,0,2),this.d=new euX}function ewu(e){return 0!=e.c.length&&((GK(0,e.c.length),Pp(e.c[0],17)).c.i.k==(eEn(),e8D)||q3(UQ(new R1(null,new Gq(e,16)),new iJ),new iQ))}function ewc(e,t,n){return ewG(n,"Tree layout",1),Kx(e.b),Yb(e.b,(egR(),tuJ),tuJ),Yb(e.b,tuQ,tuQ),Yb(e.b,tu1,tu1),Yb(e.b,tu0,tu0),e.a=eRq(e.b,t),eAG(e,t,eiI(n,1)),eEj(n),t}function ewl(e,t){var n,r,i,a,o,s,u;for(s=eLj(t),a=t.f,u=t.g,o=eB4.Math.sqrt(a*a+u*u),i=0,r=new fz(s);r.a=0?(n=eyt(e,eHK),r=edQ(e,eHK)):(n=eyt(t=Fy(e,1),5e8),r=eft(Fg(r=edQ(t,5e8),1),WM(e,1))),WO(Fg(r,32),WM(n,eH8))}function ewM(e,t,n){var r,i;switch(r=(A6(0!=t.b),Pp(etw(t,t.a.a),8)),n.g){case 0:r.b=0;break;case 2:r.b=e.f;break;case 3:r.a=0;break;default:r.a=e.g}return YU(i=epL(t,0),r),t}function ewO(e,t,n,r){var i,a,o,s,u;switch(u=e.b,s=epd(o=(a=t.d).j,u.d[o.g],n),i=C5(MB(a.n),a.a),a.j.g){case 1:case 3:s.a+=i.a;break;case 2:case 4:s.b+=i.b}qQ(r,s,r.c.b,r.c)}function ewA(e,t,n){var r,i,a,o;for(o=QI(e.e,t,0),(a=new ma).b=n,r=new KB(e.e,o);r.b1;t>>=1)(1&t)!=0&&(r=eeD(r,n)),n=1==n.d?eeD(n,n):new eh5(eDE(n.a,n.d,Je(ty_,eHT,25,n.d<<1,15,1)));return eeD(r,n)}function ewP(){var e,t,n,r;for(t=32,ewP=A,e2v=Je(tyx,eH5,25,25,15,1),e2y=Je(tyx,eH5,25,33,15,1),r=152587890625e-16;t>=0;t--)e2y[t]=r,r*=.5;for(e=24,n=1;e>=0;e--)e2v[e]=n,n*=.5}function ewR(e){var t,n;if(gN(LK(eT8(e,(eBy(),taI))))){for(n=new Fa(OH(eOi(e).a.Kc(),new c));eTk(n);)if(t=Pp(ZC(n),79),exb(t)&&gN(LK(eT8(t,taD))))return!0}return!1}function ewj(e,t){var n,r,i;Yf(e.f,t)&&(t.b=e,r=t.c,-1!=QI(e.j,r,0)||P_(e.j,r),i=t.d,-1!=QI(e.j,i,0)||P_(e.j,i),0!=(n=t.a.b).c.length&&(e.i||(e.i=new epS(e)),ea_(e.i,n)))}function ewF(e){var t,n,r,i,a;return(r=(n=e.c.d).j)==(a=(i=e.d.d).j)?n.p=0&&IE(e.substr(t,3),"GMT")?(n[0]=t+3,eDh(e,n,r)):(t>=0&&IE(e.substr(t,3),"UTC")&&(n[0]=t+3),eDh(e,n,r))}function ewz(e,t){var n,r,i,a,o;for(a=e.g.a,o=e.g.b,r=new fz(e.d);r.an;a--)e[a]|=t[a-n-1]>>>o,e[a-1]=t[a-n-1]<=e.f)break;a.c[a.c.length]=n}return a}function ew1(e){var t,n,r,i;for(t=null,i=new fz(e.wf());i.a0&&ePD(e.g,t,e.g,t+r,s),o=n.Kc(),e.i+=r,i=0;ia&&F6(c,ee5(n[s],e2h))&&(i=s,a=u);return i>=0&&(r[0]=t+a),i}function ew9(e,t){var n;if(0!=(n=To(e.b.Hf(),t.b.Hf())))return n;switch(e.b.Hf().g){case 1:case 2:return ME(e.b.sf(),t.b.sf());case 3:case 4:return ME(t.b.sf(),e.b.sf())}return 0}function ew8(e){var t,n,r;for(r=e.e.c.length,e.a=RF(ty_,[eUP,eHT],[48,25],15,[r,r],2),n=new fz(e.c);n.a>4&15,a=15&e[r],o[i++]=tmk[n],o[i++]=tmk[a];return ehv(o,0,o.length)}function e_t(e,t,n){var r,i,a;return r=t.ak(),a=t.dd(),i=r.$j()?$N(e,4,r,a,null,eN1(e,r,a,M4(r,99)&&(Pp(r,18).Bb&eH3)!=0),!0):$N(e,r.Kj()?2:1,r,a,r.zj(),-1,!0),n?n.Ei(i):n=i,n}function e_n(e){var t,n;return e>=eH3?(t=eH4+(e-eH3>>10&1023)&eHd,n=56320+(e-eH3&1023)&eHd,String.fromCharCode(t)+""+String.fromCharCode(n)):String.fromCharCode(e&eHd)}function e_r(e,t){var n,r,i,a;return Cn(),(i=Pp(Pp(Zq(e.r,t),21),84)).gc()>=2&&(r=Pp(i.Kc().Pb(),111),n=e.u.Hc((ekU(),tbh)),a=e.u.Hc(tbg),!r.a&&!n&&(2==i.gc()||a))}function e_i(e,t,n,r,i){var a,o,s;for(a=eLx(e,t,n,r,i),s=!1;!a;)eME(e,i,!0),s=!0,a=eLx(e,t,n,r,i);s&&eME(e,i,!1),0!=(o=eoA(i)).c.length&&(e.d&&e.d.lg(o),e_i(e,i,n,r,o))}function e_a(){e_a=A,tpN=new km(eGR,0),tpI=new km("DIRECTED",1),tpP=new km("UNDIRECTED",2),tpL=new km("ASSOCIATION",3),tpD=new km("GENERALIZATION",4),tpC=new km("DEPENDENCY",5)}function e_o(e,t){var n;if(!zY(e))throw p7(new gC(eZL));switch(n=zY(e),t.g){case 1:return-(e.j+e.f);case 2:return e.i-n.g;case 3:return e.j-n.f;case 4:return-(e.i+e.g)}return 0}function e_s(e,t){var n,r;for(BJ(t),r=e.b.c.length,P_(e.b,t);r>0;){if(n=r,r=(r-1)/2|0,0>=e.a.ue(RJ(e.b,r),t))return q1(e.b,n,t),!0;q1(e.b,n,RJ(e.b,r))}return q1(e.b,r,t),!0}function e_u(e,t,n,r){var i,a;if(i=0,n)i=euW(e.a[n.g][t.g],r);else for(a=0;a=s)}function e_l(e,t,n,r){var i;if(i=!1,xd(r)&&(i=!0,P4(t,n,Lq(r))),!i&&xl(r)&&(i=!0,e_l(e,t,n,r)),!i&&M4(r,236)&&(i=!0,H1(t,n,Pp(r,236))),!i)throw p7(new gk(eXE))}function e_f(e,t){var n,r,i;if((n=t.Hh(e.a))&&null!=(i=edW((n.b||(n.b=new L_((eBK(),tgF),tgf,n)),n.b),eQe))){for(r=1;r<(eSp(),tvs).length;++r)if(IE(tvs[r],i))return r}return 0}function e_d(e,t){var n,r,i;if((n=t.Hh(e.a))&&null!=(i=edW((n.b||(n.b=new L_((eBK(),tgF),tgf,n)),n.b),eQe))){for(r=1;r<(eSp(),tvu).length;++r)if(IE(tvu[r],i))return r}return 0}function e_h(e,t){var n,r,i,a;if(BJ(t),(a=e.a.gc())0?1:0;a.a[i]!=n;)a=a.a[i],i=e.a.ue(n.d,a.d)>0?1:0;a.a[i]=r,r.b=n.b,r.a[0]=n.a[0],r.a[1]=n.a[1],n.a[0]=null,n.a[1]=null}function e_y(e){var t,n;return ekU(),t=jL(tbp,eow(vx(e6i,1),eU4,273,0,[tbm])),!(eaC(z_(t,e))>1)&&(n=jL(tbh,eow(vx(e6i,1),eU4,273,0,[tbd,tbg])),!(eaC(z_(n,e))>1))}function e_w(e,t){var n;M4(n=zg((_Q(),tgp),e),498)?Ge(tgp,e,new k5(this,t)):Ge(tgp,e,this),e_8(this,t),t==(yO(),tgg)?(this.wb=Pp(this,1939),Pp(t,1941)):this.wb=(BM(),tgv)}function e__(e){var t,n,r;if(null==e)return null;for(n=0,t=null;n=eHf?"error":r>=900?"warn":r>=800?"info":"log",e.a),e.b&&eAp(t,n,e.b,"Exception: ",!0))}function e_k(e,t){var n,r;return null!=(r=(e.q||(e.q=new p2),Bp(e.q,t)))?r:(M4(n=t.wg(),4)&&(null==n?(e.q||(e.q=new p2),Z3(e.q,t)):(e.q||(e.q=new p2),Um(e.q,t,n))),n)}function e_x(){e_x=A,e8e=new Ez("P1_CYCLE_BREAKING",0),e8t=new Ez("P2_LAYERING",1),e8n=new Ez("P3_NODE_ORDERING",2),e8r=new Ez("P4_NODE_PLACEMENT",3),e8i=new Ez("P5_EDGE_ROUTING",4)}function e_T(e,t){var n,r,i,a,o;for(r=(i=1==t?e8c:e8u).a.ec().Kc();r.Ob();)for(n=Pp(r.Pb(),103),o=Pp(Zq(e.f.c,n),21).Kc();o.Ob();)a=Pp(o.Pb(),46),QA(e.b.b,a.b),QA(e.b.a,Pp(a.b,81).d)}function e_M(e,t){var n;if(eeP(),e.c!=t.c)return elN(e.c,t.c);if(e.b==t.b||eiS(e.b,t.b)){if(n=Tu(e.b)?1:-1,e.a&&!t.a)return n;if(!e.a&&t.a)return-n}return ME(e.b.g,t.b.g)}function e_O(e,t){var n;ewG(t,"Hierarchical port position processing",1),(n=e.b).c.length>0&&eI6((GK(0,n.c.length),Pp(n.c[0],29)),e),n.c.length>1&&eI6(Pp(RJ(n,n.c.length-1),29),e),eEj(t)}function e_A(e,t){var n,r,i;if(e_Y(e,t))return!0;for(r=new fz(t);r.a=(i=e.Vi())||t<0)throw p7(new gE(eXU+t+eXH+i));if(n>=i||n<0)throw p7(new gE(eX$+n+eXH+i));return t!=n?(a=e.Ti(n),e.Hi(t,a),a):e.Oi(n)}function e_j(e){var t,n,r;if(r=e,e)for(t=0,n=e.Ug();n;n=n.Ug()){if(++t>eH6)return e_j(n);if(r=n,n==e)throw p7(new gC("There is a cycle in the containment hierarchy of "+e))}return r}function e_F(e){var t,n,r;for(r=new eaP(eUd,"[","]"),n=e.Kc();n.Ob();)ZJ(r,xc(t=n.Pb())===xc(e)?"(this Collection)":null==t?eUg:efF(t));return r.a?0==r.e.length?r.a.a:r.a.a+""+r.e:r.c}function e_Y(e,t){var n,r;if(r=!1,2>t.gc())return!1;for(n=0;n=e.charCodeAt(r));)++r;for(t=n;t>r&&(GV(t-1,e.length),32>=e.charCodeAt(t-1));)--t;return r>0||t1&&(e.j.b+=e.e)):(e.j.a+=n.a,e.j.b=eB4.Math.max(e.j.b,n.b),e.d.c.length>1&&(e.j.a+=e.e))}function e_z(){e_z=A,tec=eow(vx(e6a,1),eGj,61,0,[(eYu(),tbw),tby,tbj]),teu=eow(vx(e6a,1),eGj,61,0,[tby,tbj,tbY]),tel=eow(vx(e6a,1),eGj,61,0,[tbj,tbY,tbw]),tef=eow(vx(e6a,1),eGj,61,0,[tbY,tbw,tby])}function e_G(e,t,n,r){var i,a,o,s,u,c,l;if(o=e.c.d,s=e.d.d,o.j!=s.j)for(l=e.b,i=o.j,u=null;i!=s.j;)u=0==t?elI(i):elL(i),P7(r,C5(a=epd(i,l.d[i.g],n),c=epd(u,l.d[u.g],n))),i=u}function e_W(e,t,n,r){var i,a,o,s,u;return o=egN(e.a,t,n),s=Pp(o.a,19).a,a=Pp(o.b,19).a,r&&(u=Pp(e_k(t,(eBU(),tng)),10),i=Pp(e_k(n,tng),10),u&&i&&(V6(e.b,u,i),s+=e.b.i,a+=e.b.e)),s>a}function e_K(e){var t,n,r,i,a,o,s,u,c;for(r=0,this.a=ebb(e),this.b=new p0,i=(n=e).length;rL7(e.d).c?(e.i+=e.g.c,ed3(e.d)):L7(e.d).c>L7(e.g).c?(e.e+=e.d.c,ed3(e.g)):(e.i+=R6(e.g),e.e+=R6(e.d),ed3(e.g),ed3(e.d))}function e_X(e,t,n){var r,i,a,o;for(a=t.q,o=t.r,new GT((Xa(),tuU),t,a,1),new GT(tuU,a,o,1),i=new fz(n);i.as&&(u=s/r),i>a&&(c=a/i),o=eB4.Math.min(u,c),e.a+=o*(t.a-e.a),e.b+=o*(t.b-e.b)}function e_5(e,t,n,r,i){var a,o;for(o=!1,a=Pp(RJ(n.b,0),33);eNK(e,t,a,r,i)&&(o=!0,eyL(n,a),0!=n.b.c.length);)a=Pp(RJ(n.b,0),33);return 0==n.b.c.length&&eva(n.j,n),o&&emG(t.q),o}function e_6(e,t){var n,r,i,a;if(eLG(),t.b<2)return!1;for(r=n=Pp(Vv(a=epL(t,0)),8);a.b!=a.d.c;){if(eOV(e,r,i=Pp(Vv(a),8)))return!0;r=i}return!!eOV(e,r,n)}function e_9(e,t,n,r){var i,a;return 0==n?(e.o||(e.o=new JY((eBa(),tmy),e6O,e,0)),Iz(e.o,t,r)):(a=Pp(ee2((i=Pp(eaS(e,16),26))||e.zh(),n),66)).Nj().Rj(e,ehH(e),n-Y1(e.zh()),t,r)}function e_8(e,t){var n;t!=e.sb?(n=null,e.sb&&(n=Pp(e.sb,49).ih(e,1,e6w,n)),t&&(n=Pp(t,49).gh(e,1,e6w,n)),(n=ecY(e,t,n))&&n.Fi()):(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,4,t,t))}function e_7(e,t){var n,r,i,a;if(t)i=enm(t,"x"),enr((n=new pa(e)).a,(BJ(i),i)),a=enm(t,"y"),enc((r=new po(e)).a,(BJ(a),a));else throw p7(new gK("All edge sections need an end point."))}function eEe(e,t){var n,r,i,a;if(t)i=enm(t,"x"),enu((n=new pn(e)).a,(BJ(i),i)),a=enm(t,"y"),enl((r=new pr(e)).a,(BJ(a),a));else throw p7(new gK("All edge sections need a start point."))}function eEt(e,t){var n,r,i,a,o,s,u;for(r=es1(e),a=0,s=r.length;a>22-t,i=e.h<>22-t):t<44?(n=0,r=e.l<>44-t):(n=0,r=0,i=e.l<e))return 0==t||t==e?1:0==e?0:ev6(e)/(ev6(t)*ev6(e-t));throw p7(new gL("k must be smaller than n"))}function eEh(e,t){var n,r,i,a;for(n=new TY(e);null!=n.g||n.c?null==n.g||0!=n.i&&Pp(n.g[n.i-1],47).Ob():zW(n);)if(M4(a=Pp(eM5(n),56),160))for(i=0,r=Pp(a,160);i>4],t[2*n+1]=tv0[15&a];return ehv(t,0,t.length)}function eEA(e){var t,n,r;switch(U_(),r=e.c.length){case 0:return e0p;case 1:return P2((t=Pp(ekM(new fz(e)),42)).cd(),t.dd());default:return n=Pp(epg(e,Je(e1$,eUK,42,e.c.length,0,1)),165),new gt(n)}}function eEL(e){var t,n,r,i,a,o;for(t=new p1,n=new p1,Vw(t,e),Vw(n,e);n.b!=n.c;)for(i=Pp(Yn(n),37),o=new fz(i.a);o.a0&&eIl(e,n,t),i):exV(e,t,n)}function eEN(e,t,n){var r,i,a,o;if(0!=t.b){for(r=new _n,o=epL(t,0);o.b!=o.d.c;)er7(r,eoO(a=Pp(Vv(o),86))),(i=a.e).a=Pp(e_k(a,(eR6(),tcg)),19).a,i.b=Pp(e_k(a,tcv),19).a;eEN(e,r,eiI(n,r.b/e.a|0))}}function eEP(e,t){var n,r,i,a,o;if(e.e<=t||Wm(e,e.g,t))return e.g;for(a=e.r,r=e.g,o=e.r,i=(a-r)/2+r;r+11&&(e.e.b+=e.a)):(e.e.a+=n.a,e.e.b=eB4.Math.max(e.e.b,n.b),e.d.c.length>1&&(e.e.a+=e.a))}function eEH(e){var t,n,r,i;switch(t=(i=e.i).b,r=i.j,n=i.g,i.a.g){case 0:n.a=(e.g.b.o.a-r.a)/2;break;case 1:n.a=t.d.n.a+t.d.a.a;break;case 2:n.a=t.d.n.a+t.d.a.a-r.a;break;case 3:n.b=t.d.n.b+t.d.a.b}}function eE$(e,t,n,r,i){if(rr&&(e.a=r),e.bi&&(e.b=i),e}function eEz(e){if(M4(e,149))return eAi(Pp(e,149));if(M4(e,229))return efZ(Pp(e,229));if(M4(e,23))return eEa(Pp(e,23));throw p7(new gL(eXx+e_F(new g$(eow(vx(e1R,1),eUp,1,5,[e])))))}function eEG(e,t,n,r,i){var a,o,s;for(o=0,a=!0;o>>i|n[o+r+1]<>>i,++o}return a}function eEW(e,t,n,r){var i,a,o;if(t.k==(eEn(),e8D)){for(a=new Fa(OH(efu(t).a.Kc(),new c));eTk(a);)if((o=(i=Pp(ZC(a),17)).c.i.k)==e8D&&e.c.a[i.c.i.c.p]==r&&e.c.a[t.c.p]==n)return!0}return!1}function eEK(e,t){var n,r,i,a;return t&=63,n=e.h&eH$,t<22?(a=n>>>t,i=e.m>>t|n<<22-t,r=e.l>>t|e.m<<22-t):t<44?(a=0,i=n>>>t-22,r=e.m>>t-22|e.h<<44-t):(a=0,i=0,r=n>>>t-44),Mk(r&eHH,i&eHH,a&eH$)}function eEV(e,t,n,r){var i;this.b=r,this.e=e==(enU(),tui),i=t[n],this.d=RF(tyE,[eUP,e$5],[177,25],16,[i.length,i.length],2),this.a=RF(ty_,[eUP,eHT],[48,25],15,[i.length,i.length],2),this.c=new ewo(t,n)}function eEq(e){var t,n,r;for(e.k=new G$((eYu(),eow(vx(e6a,1),eGj,61,0,[tbF,tbw,tby,tbj,tbY])).length,e.j.c.length),r=new fz(e.j);r.a=n)return eE5(e,t,r.p),!0;return!1}function eE1(e){var t;return(64&e.Db)!=0?eEp(e):(t=new O0(eZ$),e.a&&xM(xM((t.a+=' "',t),e.a),'"'),xM(yW(xM(yW(xM(yW(xM(yW((t.a+=" (",t),e.i),","),e.j)," | "),e.g),","),e.f),")"),t.a)}function eE0(e,t,n){var r,i,a,o,s;for(o=0,s=eAY(e.e.Tg(),t),i=Pp(e.g,119),r=0;on?eS1(e,n,"start index"):t<0||t>n?eS1(t,n,"end index"):eCG("end index (%s) must not be less than start index (%s)",eow(vx(e1R,1),eUp,1,5,[ell(t),ell(e)]))}function eE4(e,t){var n,r,i,a;for(r=0,i=e.length;r0&&eE9(e,a,n));t.p=0}function eE8(e){var t;this.c=new _n,this.f=e.e,this.e=e.d,this.i=e.g,this.d=e.c,this.b=e.b,this.k=e.j,this.a=e.a,e.i?this.j=e.i:this.j=(t=Pp(yw(e5Q),9),new I1(t,Pp(CY(t,t.length),9),0)),this.g=e.f}function eE7(e){var t,n,r,i;for(t=Bd(xM(new O0("Predicates."),"and"),40),n=!0,i=new fE(e);i.b0?s[o-1]:Je(e4N,eGW,10,0,0,1),i=s[o],c=o=0?e.Bh(i):ekN(e,r);else throw p7(new gL(eZV+r.ne()+eZq))}else throw p7(new gL(eZJ+t+eZQ))}else ec6(e,n,r)}function eSa(e){var t,n;if(n=null,t=!1,M4(e,204)&&(t=!0,n=Pp(e,204).a),!t&&M4(e,258)&&(t=!0,n=""+Pp(e,258).a),!t&&M4(e,483)&&(t=!0,n=""+Pp(e,483).a),!t)throw p7(new gk(eXE));return n}function eSo(e,t){var n,r;if(!e.f)return t.Ob();for(;t.Ob();)if(M4(r=(n=Pp(t.Pb(),72)).ak(),99)&&(Pp(r,18).Bb&eZ1)!=0&&(!e.e||r.Gj()!=e6d||0!=r.aj())&&null!=n.dd())return t.Ub(),!0;return!1}function eSs(e,t){var n,r;if(!e.f)return t.Sb();for(;t.Sb();)if(M4(r=(n=Pp(t.Ub(),72)).ak(),99)&&(Pp(r,18).Bb&eZ1)!=0&&(!e.e||r.Gj()!=e6d||0!=r.aj())&&null!=n.dd())return t.Pb(),!0;return!1}function eSu(e,t,n){var r,i,a,o,s,u;for(o=0,u=eAY(e.e.Tg(),t),r=0,s=e.i,i=Pp(e.g,119);o1&&(t.c[t.c.length]=a)}function eSf(e){var t,n,r,i;for(er7(n=new _n,e.o),r=new mc;0!=n.b;)(i=eYP(e,t=Pp(0==n.b?null:(A6(0!=n.b),etw(n,n.a.a)),508),!0))&&P_(r.a,t);for(;0!=r.a.c.length;)eYP(e,t=Pp(euO(r),508),!1)}function eSd(){eSd=A,tdS=new ks(ezo,0),tdm=new ks("BOOLEAN",1),tdw=new ks("INT",2),tdE=new ks("STRING",3),tdg=new ks("DOUBLE",4),tdv=new ks("ENUM",5),tdy=new ks("ENUMSET",6),td_=new ks("OBJECT",7)}function eSh(e,t){var n,r,i,a,o;r=eB4.Math.min(e.c,t.c),a=eB4.Math.min(e.d,t.d),i=eB4.Math.max(e.c+e.b,t.c+t.b),o=eB4.Math.max(e.d+e.a,t.d+t.a),i=(i/2|0))for(this.e=r?r.c:null,this.d=i;n++0;)Gi(this);this.b=t,this.a=null}function eSk(e,t){var n,r;t.a?eAk(e,t):((n=Pp(Ik(e.b,t.b),57))&&n==e.a[t.b.f]&&n.a&&n.a!=t.b.a&&n.c.Fc(t.b),(r=Pp(IS(e.b,t.b),57))&&e.a[r.f]==t.b&&r.a&&r.a!=t.b.a&&t.b.c.Fc(r),Ai(e.b,t.b))}function eSx(e,t){var n,r;if(n=Pp(UA(e.b,t),124),Pp(Pp(Zq(e.r,t),21),84).dc()){n.n.b=0,n.n.c=0;return}n.n.b=e.C.b,n.n.c=e.C.c,e.A.Hc((ed6(),tbq))&&eCD(e,t),r=ebi(e,t),eLZ(e,t)==(epT(),tbt)&&(r+=2*e.w),n.a.a=r}function eST(e,t){var n,r;if(n=Pp(UA(e.b,t),124),Pp(Pp(Zq(e.r,t),21),84).dc()){n.n.d=0,n.n.a=0;return}n.n.d=e.C.d,n.n.a=e.C.a,e.A.Hc((ed6(),tbq))&&eCN(e,t),r=eba(e,t),eLZ(e,t)==(epT(),tbt)&&(r+=2*e.w),n.a.b=r}function eSM(e,t){var n,r,i,a;for(a=new p0,r=new fz(t);r.aeB4.Math.abs(r-i))}function eSU(e,t,n){var r,i,a,o,s,u;if(null!=(s=Pp(eaS(e.a,8),1936)))for(a=0,o=(i=s).length;an.a&&(r.Hc((eyY(),tdW))?i=(t.a-n.a)/2:r.Hc(tdV)&&(i=t.a-n.a)),t.b>n.b&&(r.Hc((eyY(),tdZ))?a=(t.b-n.b)/2:r.Hc(tdq)&&(a=t.b-n.b)),e_g(e,i,a)}function eSJ(e,t,n,r,i,a,o,s,u,c,l,f,d){M4(e.Cb,88)&&eko(Zd(Pp(e.Cb,88)),4),er3(e,n),e.f=o,elY(e,s),elU(e,u),elF(e,c),elB(e,l),els(e,f),elZ(e,d),eli(e,!0),end(e,i),e.ok(a),eu2(e,t),null!=r&&(e.i=null,erA(e,r))}function eSQ(e){var t,n;if(!e.f)return e.n>0;for(;e.n>0;){if(M4(n=(t=Pp(e.k.Xb(e.n-1),72)).ak(),99)&&(Pp(n,18).Bb&eZ1)!=0&&(!e.e||n.Gj()!=e6d||0!=n.aj())&&null!=t.dd())return!0;--e.n}return!1}function eS1(e,t,n){if(e<0)return eCG(eUh,eow(vx(e1R,1),eUp,1,5,[n,ell(e)]));if(!(t<0))return eCG("%s (%s) must not be greater than size (%s)",eow(vx(e1R,1),eUp,1,5,[n,ell(e),ell(t)]));throw p7(new gL(eUb+t))}function eS0(e,t,n,r,i,a){var o,s,u,c;if((o=r-n)<7){efA(t,n,r,a);return}if(c=(u=n+i)+((s=r+i)-u>>1),eS0(t,e,u,c,-i,a),eS0(t,e,c,s,-i,a),0>=a.ue(e[c-1],e[c])){for(;n=0?e.sh(a,n):eOh(e,i,n);else throw p7(new gL(eZV+i.ne()+eZq))}else throw p7(new gL(eZJ+t+eZQ))}else efL(e,r,i,n)}function eS6(e){var t,n,r,i;if(n=Pp(e,49).qh())try{if(r=null,(t=eMC((_Q(),tgp),eDv(efR(n))))&&(i=t.rh())&&(r=i.Wk(gF(n.e))),r&&r!=e)return eS6(r)}catch(a){if(a=eoa(a),!M4(a,60))throw p7(a)}return e}function eS9(e,t,n){var r,i,a,o;if(o=null==t?0:e.b.se(t),0==(i=null==(r=e.a.get(o))?[]:r).length)e.a.set(o,i);else if(a=euj(e,t,i))return a.ed(n);return Bc(i,i.length,new EE(t,n)),++e.c,$c(e.b),null}function eS8(e,t){var n,r;return Kx(e.a),Yb(e.a,(erZ(),tcq),tcq),Yb(e.a,tcZ,tcZ),r=new K2,RI(r,tcZ,(efx(),tc1)),xc(eT8(t,(egj(),tlf)))!==xc((eub(),tc5))&&RI(r,tcZ,tcJ),RI(r,tcZ,tcQ),Tb(e.a,r),n=eRq(e.a,t)}function eS7(e){if(!e)return g3(),e0M;var t=e.valueOf?e.valueOf():e;if(t!==e){var n=e0O[typeof t];return n?n(t):euV(typeof t)}return e instanceof Array||e instanceof eB4.Array?new lL(e):new lD(e)}function eke(e,t,n){var r,i,a;switch(a=e.o,(i=(r=Pp(UA(e.p,n),244)).i).b=ek0(r),i.a=ek1(r),i.b=eB4.Math.max(i.b,a.a),i.b>a.a&&!t&&(i.b=a.a),i.c=-(i.b-a.a)/2,n.g){case 1:i.d=-i.a;break;case 3:i.d=a.b}eNE(r),eNM(r)}function ekt(e,t,n){var r,i,a;switch(a=e.o,(i=(r=Pp(UA(e.p,n),244)).i).b=ek0(r),i.a=ek1(r),i.a=eB4.Math.max(i.a,a.b),i.a>a.b&&!t&&(i.a=a.b),i.d=-(i.a-a.b)/2,n.g){case 4:i.c=-i.b;break;case 2:i.c=a.a}eNE(r),eNM(r)}function ekn(e,t){var n,r,i,a,o;if(!t.dc()){if(i=Pp(t.Xb(0),128),1==t.gc()){eA1(e,i,i,1,0,t);return}for(n=1;n0)try{i=eDa(t,eHt,eUu)}catch(a){if(a=eoa(a),M4(a,127))throw r=a,p7(new QH(r));throw p7(a)}return i<(n=(e.a||(e.a=new pK(e)),e.a)).i&&i>=0?Pp(etj(n,i),56):null}function eku(e,t){if(e<0)return eCG(eUh,eow(vx(e1R,1),eUp,1,5,["index",ell(e)]));if(!(t<0))return eCG("%s (%s) must be less than size (%s)",eow(vx(e1R,1),eUp,1,5,["index",ell(e),ell(t)]));throw p7(new gL(eUb+t))}function ekc(e){var t,n,r,i,a;if(null==e)return eUg;for(r=0,a=new eaP(eUd,"[","]"),i=(n=e).length;re.a.ue(RJ(e.b,o),RJ(e.b,a))&&(s=o),s),!(0>e.a.ue(i,RJ(e.b,r))));)q1(e.b,t,RJ(e.b,r)),t=r;q1(e.b,t,i)}function ekp(e,t,n,r,i,a){var o,s,u,c,l;for(xc(e)===xc(n)&&(e=e.slice(t,t+i),t=0),u=n,s=t,c=t+i;s0)for(o=e.c.d,s=e.d.d,i=Ol(C6(new kl(s.a,s.b),o),1/(r+1)),a=new kl(o.a,o.b),n=new fz(e.a);n.a=0?e._g(n,!0,!0):exk(e,i,!0),153),Pp(r,215).ol(t);else throw p7(new gL(eZV+t.ne()+eZq))}function ekP(e){var t,n;return e>-140737488355328&&e<0x800000000000?0==e?0:((t=e<0)&&(e=-e),n=zy(eB4.Math.floor(eB4.Math.log(e)/.6931471805599453)),(!t||e!=eB4.Math.pow(2,n))&&++n,n):eaJ(eap(e))}function ekR(e){var t,n,r,i,a,o,s;for(a=new Tw,n=new fz(e);n.a2&&s.e.b+s.j.b<=2&&(i=s,r=o),a.a.zc(i,a),i.q=r);return a}function ekj(e,t){var n,r,i;return r=new eb$(e),eaW(r,t),eo3(r,(eBU(),ttQ),t),eo3(r,(eBy(),tol),(ewf(),tbo)),eo3(r,tiq,(ebx(),tdA)),lK(r,(eEn(),e8C)),n=new eES,Gc(n,r),ekv(n,(eYu(),tbY)),i=new eES,Gc(i,r),ekv(i,tby),r}function ekF(e){switch(e.g){case 0:return new gx((enU(),tur));case 1:return new cC;case 2:return new cF;default:throw p7(new gL("No implementation is available for the crossing minimizer "+(null!=e.f?e.f:""+e.g)))}}function ekY(e,t){var n,r,i,a,o;for(e.c[t.p]=!0,P_(e.a,t),o=new fz(t.j);o.a=(a=o.gc()))o.$b();else for(r=0,i=o.Kc();r0?g5():o<0&&ekJ(e,t,-o),!0)}function ek1(e){var t,n,r,i,a,o,s;if(s=0,0==e.b){for(i=0,o=eb4(e,!0),t=0,a=(r=o).length;i0&&(s+=n,++t);t>1&&(s+=e.c*(t-1))}else s=vy(eib(U1(UJ(Yw(e.a),new eS),new ek)));return s>0?s+e.n.d+e.n.a:0}function ek0(e){var t,n,r,i,a,o,s;if(s=0,0==e.b)s=vy(eib(U1(UJ(Yw(e.a),new e_),new eE)));else{for(i=0,o=eb5(e,!0),t=0,a=(r=o).length;i0&&(s+=n,++t);t>1&&(s+=e.c*(t-1))}return s>0?s+e.n.b+e.n.c:0}function ek2(e,t){var n,r,i,a;for(n=(a=Pp(UA(e.b,t),124)).a,i=Pp(Pp(Zq(e.r,t),21),84).Kc();i.Ob();)(r=Pp(i.Pb(),111)).c&&(n.a=eB4.Math.max(n.a,Rd(r.c)));if(n.a>0)switch(t.g){case 2:a.n.c=e.s;break;case 4:a.n.b=e.s}}function ek3(e,t){var n,r,i;return 0==(n=Pp(e_k(t,(eCk(),e9M)),19).a-Pp(e_k(e,e9M),19).a)?(r=C6(MB(Pp(e_k(e,(erV(),e9P)),8)),Pp(e_k(e,e9R),8)),i=C6(MB(Pp(e_k(t,e9P),8)),Pp(e_k(t,e9R),8)),elN(r.a*r.b,i.a*i.b)):n}function ek4(e,t){var n,r,i;return 0==(n=Pp(e_k(t,(eTj(),tcD)),19).a-Pp(e_k(e,tcD),19).a)?(r=C6(MB(Pp(e_k(e,(eR6(),tce)),8)),Pp(e_k(e,tct),8)),i=C6(MB(Pp(e_k(t,tce),8)),Pp(e_k(t,tct),8)),elN(r.a*r.b,i.a*i.b)):n}function ek5(e){var t,n;return n=new vc,n.a+="e_",null!=(t=eaZ(e))&&(n.a+=""+t),e.c&&e.d&&(xM((n.a+=" ",n),egu(e.c)),xM(xT((n.a+="[",n),e.c.i),"]"),xM((n.a+=eGH,n),egu(e.d)),xM(xT((n.a+="[",n),e.d.i),"]")),n.a}function ek6(e){switch(e.g){case 0:return new cD;case 1:return new cN;case 2:return new cI;case 3:return new cP;default:throw p7(new gL("No implementation is available for the layout phase "+(null!=e.f?e.f:""+e.g)))}}function ek9(e,t,n,r,i){var a;switch(a=0,i.g){case 1:a=eB4.Math.max(0,t.b+e.b-(n.b+r));break;case 3:a=eB4.Math.max(0,-e.b-r);break;case 2:a=eB4.Math.max(0,-e.a-r);break;case 4:a=eB4.Math.max(0,t.a+e.a-(n.a+r))}return a}function ek8(e,t,n){var r,i,a,o,s;if(n)for(i=n.a.length,s=((r=new Fs(i)).b-r.a)*r.c<0?(_9(),eB3):new OR(r);s.Ob();)eXh in(a=KZ(n,(o=Pp(s.Pb(),19)).a)).a||eXp in a.a?eId(e,a,t):eBe(e,a,t),Om(Pp(Bp(e.b,ehM(a)),79))}function ek7(e){var t,n;switch(e.b){case -1:return!0;case 0:if((n=e.t)>1||-1==n||(t=evl(e))&&(_4(),t.Cj()==eJK))return e.b=-1,!0;return e.b=1,!1;default:return!1}}function exe(e,t){var n,r,i,a,o;for(i=0,r=(t.s||(t.s=new FQ(tm6,t,21,17)),t.s),a=null,o=r.i;i=0&&r=0?e._g(n,!0,!0):exk(e,i,!0),153),Pp(r,215).ll(t);throw p7(new gL(eZV+t.ne()+eZX))}function exc(){var e;return(_6(),tg9)?Pp(eMC((_Q(),tgp),eQc),1939):(x2(e1$,new ut),ej9(),e=Pp(M4(zg((_Q(),tgp),eQc),547)?zg(tgp,eQc):new Uh,547),tg9=!0,eBY(e),eB0(e),Um((_1(),tgm),e,new sM),Ge(tgp,eQc,e),e)}function exl(e,t){var n,r,i,a;e.j=-1,TO(e.e)?(n=e.i,a=0!=e.i,Zz(e,t),r=new Q$(e.e,3,e.c,null,t,n,a),i=t.Qk(e.e,e.c,null),(i=ey1(e,t,i))?(i.Ei(r),i.Fi()):eam(e.e,r)):(Zz(e,t),(i=t.Qk(e.e,e.c,null))&&i.Fi())}function exf(e,t){var n,r,i;if(i=0,(r=t[0])>=e.length)return -1;for(n=(GV(r,e.length),e.charCodeAt(r));n>=48&&n<=57&&(i=10*i+(n-48),!(++r>=e.length));)n=(GV(r,e.length),e.charCodeAt(r));return r>t[0]?t[0]=r:i=-1,i}function exd(e){var t,n,r,i,a;return i=Pp(e.a,19).a,a=Pp(e.b,19).a,n=i,r=a,t=eB4.Math.max(eB4.Math.abs(i),eB4.Math.abs(a)),i<=0&&i==a?(n=0,r=a-1):i==-t&&a!=t?(n=a,r=i,a>=0&&++n):(n=-a,r=i),new kD(ell(n),ell(r))}function exh(e,t,n,r){var i,a,o,s,u,c;for(i=0;i=0&&c>=0&&u=e.i)throw p7(new gE(eXU+t+eXH+e.i));if(n>=e.i)throw p7(new gE(eX$+n+eXH+e.i));return r=e.g[n],t!=n&&(t>16))>>16&16),e>>=t,n+=t=(r=e-256)>>16&8,e<<=t,n+=t=(r=e-eH0)>>16&4,e<<=t,n+=t=(r=e-eUR)>>16&2,e<<=t,n+2-(t=(r=e>>14)&~(r>>1)))}function exy(e){var t,n,r,i;for(HR(),e9n=new p0,e9t=new p2,e9e=new p0,t=(e.a||(e.a=new FQ(e6k,e,10,11)),e.a),eYE(t),i=new Ow(t);i.e!=i.i.gc();)r=Pp(epH(i),33),-1==QI(e9n,r,0)&&(n=new p0,P_(e9e,n),epi(r,n));return e9e}function exw(e,t,n){var r,i,a,o;e.a=n.b.d,M4(t,352)?(i=eLO(Pp(t,79),!1,!1),a=eEF(i),qX(a,r=new d_(e)),eNI(a,i),null!=t.We((eBB(),thg))&&qX(Pp(t.We(thg),74),r)):((o=Pp(t,470)).Hg(o.Dg()+e.a.a),o.Ig(o.Eg()+e.a.b))}function ex_(e,t){var n,r,i,a,o,s,u,c;for(s=1,c=gP(LV(e_k(t,(eBy(),toH)))),u=e[0].n.a+e[0].o.a+e[0].d.c+c;s=0)?n:(s=B$(C6(new kl(o.c+o.b/2,o.d+o.a/2),new kl(a.c+a.b/2,a.d+a.a/2))),-(eDz(a,o)-1)*s)}function exS(e,t,n){var r;_r(new R1(null,(n.a||(n.a=new FQ(e6v,n,6,6)),new Gq(n.a,16))),new kC(e,t)),_r(new R1(null,(n.n||(n.n=new FQ(e6S,n,1,7)),new Gq(n.n,16))),new kI(e,t)),(r=Pp(eT8(n,(eBB(),thg)),74))&&eil(r,e,t)}function exk(e,t,n){var r,i,a;if(a=eR3((eSp(),tvc),e.Tg(),t))return _4(),Pp(a,66).Oj()||(a=Wk(QZ(tvc,a))),i=Pp((r=e.Yg(a))>=0?e._g(r,!0,!0):exk(e,a,!0),153),Pp(i,215).hl(t,n);throw p7(new gL(eZV+t.ne()+eZX))}function exx(e,t,n,r){var i,a,o,s,u;if(i=e.d[t]){if(a=i.g,u=i.i,null!=r){for(s=0;s=n&&(r=t,o=(c=(u.c+u.a)/2)-n,u.c<=c-n&&(i=new N4(u.c,o),jO(e,r++,i)),(s=c+n)<=u.a&&(a=new N4(s,u.a),Gp(r,e.c.length),Ew(e.c,r,a)))}function exI(e){var t;if(e.c||null!=e.g){if(null==e.g)return!0;if(0==e.i)return!1;t=Pp(e.g[e.i-1],47)}else e.d=e.si(e.f),JL(e,e.d),t=e.d;return t==e.b&&null.km>=null.jm()?(eM5(e),exI(e)):t.Ob()}function exD(e,t,n){var r,i,a,o,s;if((s=n)||(s=P6(new mV,0)),ewG(s,eGA,1),ejY(e.c,t),1==(o=ejz(e.a,t)).gc())eRd(Pp(o.Xb(0),37),s);else for(a=1/o.gc(),i=o.Kc();i.Ob();)eRd(r=Pp(i.Pb(),37),eiI(s,a));vi(e.a,o,t),eL7(t),eEj(s)}function exN(e){if(this.a=e,e.c.i.k==(eEn(),e8C))this.c=e.c,this.d=Pp(e_k(e.c.i,(eBU(),tt1)),61);else if(e.d.i.k==e8C)this.c=e.d,this.d=Pp(e_k(e.d.i,(eBU(),tt1)),61);else throw p7(new gL("Edge "+e+" is not an external edge."))}function exP(e,t){var n,r,i;i=e.b,e.b=t,(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,3,i,e.b)),t?t!=e&&(er3(e,t.zb),enf(e,t.d),erc(e,null==(n=null==(r=t.c)?t.zb:r)||IE(n,t.zb)?null:n)):(er3(e,null),enf(e,0),erc(e,null))}function exR(e){var t,n;if(!e.f)return e.n=(o=null==(n=Pp(eaS(e.a,4),126))?0:n.length))throw p7(new Ii(t,o));return i=n[t],1==o?r=null:(r=Je(e6N,eJM,415,o-1,0,1),ePD(n,0,r,0,t),(a=o-t-1)>0&&ePD(n,t+1,r,t,a)),eps(e,r),eSU(e,t,i),i}function ex$(){ex$=A,tvw=Pp(etj(H9((yL(),tvS).qb),6),34),tvg=Pp(etj(H9(tvS.qb),3),34),tvv=Pp(etj(H9(tvS.qb),4),34),tvy=Pp(etj(H9(tvS.qb),5),18),eyD(tvw),eyD(tvg),eyD(tvv),eyD(tvy),tv_=new g$(eow(vx(tm6,1),eJ4,170,0,[tvw,tvg]))}function exz(e,t){var n;this.d=new mh,this.b=t,this.e=new TS(t.qf()),n=e.u.Hc((ekU(),tbb)),e.u.Hc(tbp)?e.D?this.a=n&&!t.If():this.a=!0:e.u.Hc(tbm)&&n?this.a=!(t.zf().Kc().Ob()||t.Bf().Kc().Ob()):this.a=!1}function exG(e,t){var n,r,i,a;for(n=e.o.a,a=Pp(Pp(Zq(e.r,t),21),84).Kc();a.Ob();)(i=Pp(a.Pb(),111)).e.a=(r=i.b).Xe((eBB(),thK))?r.Hf()==(eYu(),tbY)?-r.rf().a-gP(LV(r.We(thK))):n+gP(LV(r.We(thK))):r.Hf()==(eYu(),tbY)?-r.rf().a:n}function exW(e,t){var n,r,i,a;n=Pp(e_k(e,(eBy(),tal)),103),a=Pp(eT8(t,tob),61),(i=Pp(e_k(e,tol),98))!=(ewf(),tbc)&&i!=tbl?a==(eYu(),tbF)&&(a=eNh(t,n))==tbF&&(a=ef9(n)):a=(r=eRl(t))>0?ef9(n):elC(ef9(n)),ebu(t,tob,a)}function exK(e,t){var n,r,i,a,o;for(o=e.j,t.a!=t.b&&Mv(o,new ia),i=o.c.length/2|0,r=0;r0&&eIl(e,n,t),a):null!=r.a?(eIl(e,t,n),-1):null!=i.a?(eIl(e,n,t),1):0}function exq(e,t){var n,r,i,a;e.ej()?(n=e.Vi(),a=e.fj(),++e.j,e.Hi(n,e.oi(n,t)),r=e.Zi(3,null,t,n,a),e.bj()&&(i=e.cj(t,null))?(i.Ei(r),i.Fi()):e.$i(r)):(BD(e,t),e.bj()&&(i=e.cj(t,null))&&i.Fi())}function exZ(e,t){var n,r,i,a,o;for(o=eAY(e.e.Tg(),t),i=new o7,n=Pp(e.g,119),a=e.i;--a>=0;)r=n[a],o.rl(r.ak())&&JL(i,r);!eYK(e,i)&&TO(e.e)&&bz(e,t.$j()?$N(e,6,t,(Hj(),e2r),null,-1,!1):$N(e,t.Kj()?2:1,t,null,null,-1,!1))}function exX(){var e,t;for(t=0,exX=A,e2t=Je(e0t,eUP,91,32,0,1),e2n=Je(e0t,eUP,91,32,0,1),e=1;t<=18;t++)e2t[t]=ep_(e),e2n[t]=ep_(Fg(e,t)),e=efn(e,5);for(;to)))&&(!t.q||(o=(r=t.C).c.c.a-r.o.a/2,!((i=r.n.a-n)>o))))}function exQ(e,t){var n;ewG(t,"Partition preprocessing",1),n=Pp(qE(UJ(eeh(UJ(new R1(null,new Gq(e.a,16)),new nZ),new nX),new nJ),JF(new U,new B,new en,eow(vx(e2L,1),eU4,132,0,[(eum(),e2U)]))),15),_r(n.Oc(),new nQ),eEj(t)}function ex1(e){var t,n,r,i,a,o,s;for(Gk(),n=new qh,i=new fz(e.e.b);i.a1?e.e*=gP(e.a):e.f/=gP(e.a),eu0(e),ehK(e),eCj(e),eo3(e.b,(epz(),e62),e.g)}function ex9(e,t,n){var r,i,a,o,s,u;for(r=0,u=n,t||(r=n*(e.c.length-1),u*=-1),a=new fz(e);a.a=0?(!t&&(t=new vu,r>0&&xk(t,e.substr(0,r))),t.a+="\\",Bf(t,n&eHd)):t&&Bf(t,n&eHd);return t?t.a:e}function eTh(e){var t;if(!e.a)throw p7(new gC("IDataType class expected for layout option "+e.f));if(null==(t=VN(e.a)))throw p7(new gC("Couldn't create new instance of property '"+e.f+"'. "+eq4+(LW(e6D),e6D.k)+eq5));return Pp(t,414)}function eTp(e){var t,n,r,i,a;return(a=e.eh())&&a.kh()&&(i=ecv(e,a))!=a?(n=e.Vg(),r=(t=e.Vg())>=0?e.Qg(null):e.eh().ih(e,-1-t,null,null),e.Rg(Pp(i,49),n),r&&r.Fi(),e.Lg()&&e.Mg()&&n>-1&&eam(e,new FX(e,9,n,a,i)),i):a}function eTb(e){var t,n,r,i,a,o,s,u;for(r=0,o=0,a=e.f.e;r>5)>=e.d)return e.e<0;if(n=e.a[i],t=1<<(31&t),e.e<0){if(i<(r=eiU(e)))return!1;n=r==i?-n:~n}return(n&t)!=0}function eT_(e,t,n,r){var i;Pp(n.b,65),Pp(n.b,65),Pp(r.b,65),Pp(r.b,65),P9(i=C6(MB(Pp(n.b,65).c),Pp(r.b,65).c),ekg(Pp(n.b,65),Pp(r.b,65),i)),Pp(r.b,65),Pp(r.b,65),Pp(r.b,65).c.a,i.a,Pp(r.b,65).c.b,i.b,Pp(r.b,65),ety(r.a,new N9(e,t,r))}function eTE(e,t){var n,r,i,a,o,s,u;if(a=t.e){for(o=0,n=eTp(a),r=Pp(e.g,674);o>16)),15).Xc(a))0&&(Tk(e.a.c)&&t.n.d||Tx(e.a.c)&&t.n.b||(t.g.d+=eB4.Math.max(0,r/2-.5)),Tk(e.a.c)&&t.n.a||Tx(e.a.c)&&t.n.c||(t.g.a-=r-1))}function eTO(e){var t,n,r,i,a;if(i=new p0,a=eDC(e,i),t=Pp(e_k(e,(eBU(),tng)),10))for(r=new fz(t.j);r.a>t,a=e.m>>t|n<<22-t,i=e.l>>t|e.m<<22-t):t<44?(o=r?eH$:0,a=n>>t-22,i=e.m>>t-22|n<<44-t):(o=r?eH$:0,a=r?eHH:0,i=n>>t-44),Mk(i&eHH,a&eHH,o&eH$)}function eTI(e){var t,n,r,i,a,o;for(this.c=new p0,this.d=e,r=eHQ,i=eHQ,t=eH1,n=eH1,o=epL(e,0);o.b!=o.d.c;)a=Pp(Vv(o),8),r=eB4.Math.min(r,a.a),i=eB4.Math.min(i,a.b),t=eB4.Math.max(t,a.a),n=eB4.Math.max(n,a.b);this.a=new Hr(r,i,t-r,n-i)}function eTD(e,t){var n,r,i,a,o,s;for(a=new fz(e.b);a.a0&&M4(t,42)&&(e.a.qj(),a=null==(u=(c=Pp(t,42)).cd())?0:esj(u),o=Cb(e.a,a),n=e.a.d[o])){for(s=0,r=Pp(n.g,367),l=n.i;s=2)for(t=LV((n=i.Kc()).Pb());n.Ob();)a=t,t=LV(n.Pb()),r=eB4.Math.min(r,(BJ(t),t-(BJ(a),a)));return r}function eTX(e,t){var n,r,i,a,o;qQ(r=new _n,t,r.c.b,r.c);do for(n=(A6(0!=r.b),Pp(etw(r,r.a.a),86)),e.b[n.g]=1,a=epL(n.d,0);a.b!=a.d.c;)o=(i=Pp(Vv(a),188)).c,1==e.b[o.g]?P7(e.a,i):2==e.b[o.g]?e.b[o.g]=1:qQ(r,o,r.c.b,r.c);while(0!=r.b)}function eTJ(e,t){var n,r,i;if(xc(t)===xc(Y9(e)))return!0;if(!M4(t,15)||(r=Pp(t,15),(i=e.gc())!=r.gc()))return!1;if(!M4(r,54))return eb3(e.Kc(),r.Kc());for(n=0;n0&&(i=n),o=new fz(e.f.e);o.a0?(t-=1,n-=1):r>=0&&i<0?(t+=1,n+=1):r>0&&i>=0?(t-=1,n+=1):(t+=1,n-=1),new kD(ell(t),ell(n))}function eMf(e,t){if(e.ct.c)return 1;if(e.bt.b)return 1;if(e.a!=t.a)return esj(e.a)-esj(t.a);else if(e.d==(qG(),tuf)&&t.d==tul)return -1;else if(e.d==tul&&t.d==tuf)return 1;return 0}function eMd(e,t){var n,r,i,a,o;return(o=(a=t.a).c.i==t.b?a.d:a.c,r=a.c.i==t.b?a.c:a.d,(i=edI(e.a,o,r))>0&&i0):i<0&&-i0)}function eMh(e,t,n,r){var i,a,o,s,u,c,l,f;for(i=(t-e.d)/e.c.c.length,a=0,e.a+=n,e.d=t,f=new fz(e.c);f.a>24;return o}function eMb(e){if(e.pe()){var t=e.c;t.qe()?e.o="["+t.n:t.pe()?e.o="["+t.ne():e.o="[L"+t.ne()+";",e.b=t.me()+"[]",e.k=t.oe()+"[]";return}var n=e.j,r=e.d;r=r.split("/"),e.o=ehg(".",[n,ehg("$",r)]),e.b=ehg(".",[n,ehg(".",r)]),e.k=r[r.length-1]}function eMm(e,t){var n,r,i,a,o;for(o=null,a=new fz(e.e.a);a.a=0;t-=2)for(n=0;n<=t;n+=2)(e.b[n]>e.b[n+2]||e.b[n]===e.b[n+2]&&e.b[n+1]>e.b[n+3])&&(r=e.b[n+2],e.b[n+2]=e.b[n],e.b[n]=r,r=e.b[n+3],e.b[n+3]=e.b[n+1],e.b[n+1]=r);e.c=!0}}function eMk(e,t){var n,r,i,a,o,s,u,c;for(a=(o=1==t?e8c:e8u).a.ec().Kc();a.Ob();)for(i=Pp(a.Pb(),103),u=Pp(Zq(e.f.c,i),21).Kc();u.Ob();)switch(s=Pp(u.Pb(),46),r=Pp(s.b,81),n=(c=Pp(s.a,189)).c,i.g){case 2:case 1:r.g.d+=n;break;case 4:case 3:r.g.c+=n}}function eMx(e,t){var n,r,i,a,o,s,u,c,l;for(s=0,c=-1,l=0,u=(o=e).length;s0&&++l;++c}return l}function eMT(e){var t,n;return n=new O0(yx(e.gm)),n.a+="@",xM(n,(t=esj(e)>>>0).toString(16)),e.kh()?(n.a+=" (eProxyURI: ",xT(n,e.qh()),e.$g()&&(n.a+=" eClass: ",xT(n,e.$g())),n.a+=")"):e.$g()&&(n.a+=" (eClass: ",xT(n,e.$g()),n.a+=")"),n.a}function eMM(e){var t,n,r,i;if(e.e)throw p7(new gC((LW(e2J),e$j+e2J.k+e$F)));for(e.d==(ec3(),tpv)&&eF_(e,tpm),n=new fz(e.a.a);n.a>24}return n}function eMD(e,t,n){var r,i,a;if(!(i=Pp(UA(e.i,t),306))){if(i=new etr(e.d,t,n),jT(e.i,t,i),ehj(t))Od(e.a,t.c,t.b,i);else switch(a=eSv(t),r=Pp(UA(e.p,a),244),a.g){case 1:case 3:i.j=!0,gh(r,t.b,i);break;case 4:case 2:i.k=!0,gh(r,t.c,i)}}return i}function eMN(e,t,n,r){var i,a,o,s,u,c;if(s=new o7,u=eAY(e.e.Tg(),t),i=Pp(e.g,119),_4(),Pp(t,66).Oj())for(o=0;o=0)return i;for(a=1,s=new fz(t.j);s.a0&&t.ue((GK(i-1,e.c.length),Pp(e.c[i-1],10)),a)>0;)q1(e,i,(GK(i-1,e.c.length),Pp(e.c[i-1],10))),--i;GK(i,e.c.length),e.c[i]=a}n.a=new p2,n.b=new p2}function eMj(e,t,n){var r,i,a,o,s,u,c,l;for(o=0,l=(r=Pp(t.e&&t.e(),9),new I1(r,Pp(CY(r,r.length),9),0)),s=(a=u=eIk(n,"[\\[\\]\\s,]+")).length;o0&&(Tk(e.a.c)&&t.n.d||Tx(e.a.c)&&t.n.b||(t.g.d-=eB4.Math.max(0,r/2-.5)),Tk(e.a.c)&&t.n.a||Tx(e.a.c)&&t.n.c||(t.g.a+=eB4.Math.max(0,r-1)))}function eMY(e,t,n){var r,i;if((e.c-e.b&e.a.length-1)==2)t==(eYu(),tbw)||t==tby?(etf(Pp(eso(e),15),(egF(),tpV)),etf(Pp(eso(e),15),tpq)):(etf(Pp(eso(e),15),(egF(),tpq)),etf(Pp(eso(e),15),tpV));else for(i=new UN(e);i.a!=i.b;)etf(r=Pp(ecn(i),15),n)}function eMB(e,t){var n,r,i,a,o,s,u;for(i=Pb(new pL(e)),s=new KB(i,i.c.length),a=Pb(new pL(t)),u=new KB(a,a.c.length),o=null;s.b>0&&u.b>0;)if((n=(A6(s.b>0),Pp(s.a.Xb(s.c=--s.b),33)))==(r=(A6(u.b>0),Pp(u.a.Xb(u.c=--u.b),33))))o=n;else break;return o}function eMU(e,t){var n,r,i,a,o,s;return(a=e.a*e$d+1502*e.b,s=e.b*e$d+11,a+=n=eB4.Math.floor(s*e$h),s-=n*e$p,a%=e$p,e.a=a,e.b=s,t<=24)?eB4.Math.floor(e.a*e2v[t]):((r=(i=e.a*(1<=2147483648&&(r-=eH7),r)}function eMH(e,t,n){var r,i,a,o;WY(e,t)>WY(e,n)?(r=efr(n,(eYu(),tby)),e.d=r.dc()?0:Rk(Pp(r.Xb(0),11)),o=efr(t,tbY),e.b=o.dc()?0:Rk(Pp(o.Xb(0),11))):(i=efr(n,(eYu(),tbY)),e.d=i.dc()?0:Rk(Pp(i.Xb(0),11)),a=efr(t,tby),e.b=a.dc()?0:Rk(Pp(a.Xb(0),11)))}function eM$(e){var t,n,r,i,a,o,s;if(e&&(t=e.Hh(eQc))&&null!=(o=Lq(edW((t.b||(t.b=new L_((eBK(),tgF),tgf,t)),t.b),"conversionDelegates")))){for(s=new p0,r=eIk(o,"\\w+"),i=0,a=r.length;ie.c);o++)i.a>=e.s&&(a<0&&(a=o),s=o);return u=(e.s+e.c)/2,a>=0&&(r=eIe(e,t,a,s),u=_V((GK(r,t.c.length),Pp(t.c[r],329))),exC(t,r,n)),u}function eMK(){eMK=A,tlK=new T2((eBB(),td2),1.3),tlX=thc,tfe=new T3(15),tl7=new T2(thN,tfe),tfr=new T2(tpl,15),tlV=td9,tl3=thx,tl4=thO,tl5=thL,tl2=thS,tl6=thD,tft=thJ,tl8=(eTU(),tl$),tl0=tlU,tl9=tlH,tfn=tlG,tlJ=tlB,tlQ=thb,tl1=thm,tlZ=tlY,tlq=tlF,tfi=tlW}function eMV(e,t,n){var r,i,a,o,s,u,c;for(erl(o=a=new sa,(BJ(t),t)),c=(o.b||(o.b=new L_((eBK(),tgF),tgf,o)),o.b),u=1;u0&&eRJ(this,i)}function eMZ(e,t,n,r,i,a){var o,s,u;if(!i[t.b]){for(i[t.b]=!0,(o=r)||(o=new Z5),P_(o.e,t),u=a[t.b].Kc();u.Ob();)(s=Pp(u.Pb(),282)).d!=n&&s.c!=n&&(s.c!=t&&eMZ(e,s.c,t,o,i,a),s.d!=t&&eMZ(e,s.d,t,o,i,a),P_(o.c,s),eoc(o.d,s.b));return o}return null}function eMX(e){var t,n,r,i,a,o,s;for(t=0,i=new fz(e.e);i.a=2}function eMJ(e,t){var n,r,i,a;for(ewG(t,"Self-Loop pre-processing",1),r=new fz(e.a);r.a1)&&(t=jL(tp1,eow(vx(e6t,1),eU4,93,0,[tpQ,tp2])),!(eaC(z_(t,e))>1)&&(r=jL(tp9,eow(vx(e6t,1),eU4,93,0,[tp6,tp5])),!(eaC(z_(r,e))>1)))}function eM0(e,t){var n,r,i;return(n=t.Hh(e.a))&&null!=(i=Lq(edW((n.b||(n.b=new L_((eBK(),tgF),tgf,n)),n.b),"affiliation")))?-1==(r=O8(i,e_n(35)))?elp(e,Fr(e,etP(t.Hj())),i):0==r?elp(e,null,i.substr(1)):elp(e,i.substr(0,r),i.substr(r+1)):null}function eM2(e){var t,n,r;try{return null==e?eUg:efF(e)}catch(i){if(i=eoa(i),M4(i,102))return t=i,r=yx(esF(e))+"@"+(n=(wK(),ebh(e)>>>0)).toString(16),epa(eob(),(_g(),"Exception during lenientFormat for "+r),t),"<"+r+" threw "+yx(t.gm)+">";throw p7(i)}}function eM3(e){switch(e.g){case 0:return new ck;case 1:return new cy;case 2:return new _j;case 3:return new i$;case 4:return new CZ;case 5:return new cx;default:throw p7(new gL("No implementation is available for the layerer "+(null!=e.f?e.f:""+e.g)))}}function eM4(e,t,n){var r,i,a;for(a=new fz(e.t);a.a0&&(r.b.n-=r.c,r.b.n<=0&&r.b.u>0&&P7(t,r.b));for(i=new fz(e.i);i.a0&&(r.a.u-=r.c,r.a.u<=0&&r.a.n>0&&P7(n,r.a))}function eM5(e){var t,n,r,i,a;if(null==e.g&&(e.d=e.si(e.f),JL(e,e.d),e.c))return e.f;if(i=(t=Pp(e.g[e.i-1],47)).Pb(),e.e=t,(n=e.si(i)).Ob())e.d=n,JL(e,n);else for(e.d=null;!t.Ob()&&(Bc(e.g,--e.i,null),0!=e.i);)t=r=Pp(e.g[e.i-1],47);return i}function eM6(e,t){var n,r,i,a,o,s;if(i=(r=t).ak(),eLt(e.e,i)){if(i.hi()&&Vq(e,i,r.dd()))return!1}else for(a=0,s=eAY(e.e.Tg(),i),n=Pp(e.g,119);a1||n>1)return 2;return t+n==1?2:0}function eOs(e,t,n){var r,i,a,o,s;for(ewG(n,"ELK Force",1),gN(LK(eT8(t,(eCk(),e9E))))||zh(r=new df((_q(),new gM(t)))),s=eo4(t),evn(s),esO(e,Pp(e_k(s,e9v),424)),a=(o=eNx(e.a,s)).Kc();a.Ob();)i=Pp(a.Pb(),231),eIL(e.b,i,eiI(n,1/o.gc()));s=eYC(o),eYh(s),eEj(n)}function eOu(e,t){var n,r,i,a,o;if(ewG(t,"Breaking Point Processor",1),eFM(e),gN(LK(e_k(e,(eBy(),toJ))))){for(i=new fz(e.b);i.a=0?e._g(r,!0,!0):exk(e,a,!0),153),Pp(i,215).ml(t,n)}else throw p7(new gL(eZV+t.ne()+eZq))}function eOp(e,t){var n,r,i,a,o;for(r=1,n=new p0,i=eeh(new R1(null,new Gq(e,16)),new aM),a=eeh(new R1(null,new Gq(e,16)),new aO),o=QN(Xg(U1(eAa(eow(vx(e2C,1),eUp,833,0,[i,a])),new aA)));r=2*t&&P_(n,new N4(o[r-1]+t,o[r]-t));return n}function eOb(e,t,n){ewG(n,"Eades radial",1),n.n&&t&&WG(n,KS(t),(eup(),tmr)),e.d=Pp(eT8(t,(Lj(),tcV)),33),e.c=gP(LV(eT8(t,(egj(),tl_)))),e.e=ebN(Pp(eT8(t,tlE),293)),e.a=ef7(Pp(eT8(t,tlk),426)),e.b=eyp(Pp(eT8(t,tlg),340)),evY(e),n.n&&t&&WG(n,KS(t),(eup(),tmr))}function eOm(e,t,n){var r,i,a,o,s,u,c,l;if(n)for(a=n.a.length,s=((r=new Fs(a)).b-r.a)*r.c<0?(_9(),eB3):new OR(r);s.Ob();)(i=KZ(n,(o=Pp(s.Pb(),19)).a))&&(eB8=null,u=Vj(e,(c=(yT(),l=new mk),t&&eOL(c,t),c),i),ert(u,KJ(i,eXS)),ewU(i,u),eka(i,u),esv(e,i,u))}function eOg(e){var t,n,r,i,a,o;if(!e.j){if(o=new sd,null==(a=(t=tgz).a.zc(e,t))){for(r=new Ow($E(e));r.e!=r.i.gc();)n=Pp(epH(r),26),i=eOg(n),Y4(o,i),JL(o,n);t.a.Bc(e)}euI(o),e.j=new xQ((Pp(etj(H9((BM(),tgv).o),11),18),o.i),o.g),Zd(e).b&=-33}return e.j}function eOv(e){var t,n,r,i;if(null==e)return null;if(r=ePh(e,!0),i=eQq.length,IE(r.substr(r.length-i,i),eQq)){if(4==(n=r.length)){if(43==(t=(GV(0,r.length),r.charCodeAt(0))))return tvX;if(45==t)return tvZ}else if(3==n)return tvX}return new bK(r)}function eOy(e){var t,n,r;return((n=e.l)&n-1)!=0||((r=e.m)&r-1)!=0||((t=e.h)&t-1)!=0||0==t&&0==r&&0==n?-1:0==t&&0==r&&0!=n?enq(n):0==t&&0!=r&&0==n?enq(r)+22:0!=t&&0==r&&0==n?enq(t)+44:-1}function eOw(e,t){var n,r,i,a,o;for(ewG(t,"Edge joining",1),n=gN(LK(e_k(e,(eBy(),toz)))),i=new fz(e.b);i.a1)for(i=new fz(e.a);i.a0),a.a.Xb(a.c=--a.b),CD(a,i),A6(a.becd(r,0)?(i=eHf-jE(edQ(QC(r),eHf)))==eHf&&(i=0):i=jE(edQ(r,eHf)),1==t?Bd(e,48+(i=eB4.Math.min((i+50)/100|0,9))&eHd):2==t?eeE(e,i=eB4.Math.min((i+5)/10|0,99),2):(eeE(e,i,3),t>3&&eeE(e,0,t-3))}function eOM(e){var t,n,r,i;return xc(e_k(e,(eBy(),taM)))===xc((eck(),tpz))?!e.e&&xc(e_k(e,tat))!==xc((eaU(),ttO)):(r=Pp(e_k(e,tan),292),i=gN(LK(e_k(e,tao)))||xc(e_k(e,tas))===xc((en7(),teR)),t=Pp(e_k(e,tae),19).a,n=e.a.c.length,!i&&r!=(eaU(),ttO)&&(0==t||t>n))}function eOO(e){var t,n;for(n=0;n0);n++);if(n>0&&n0);t++);return t>0&&n>16!=6&&t){if(eg7(e,t))throw p7(new gL(eZ4+ex2(e)));r=null,e.Cb&&(r=(n=e.Db>>16)>=0?eg1(e,r):e.Cb.ih(e,-1-n,null,r)),t&&(r=ep0(t,e,6,r)),(r=Cc(e,t,r))&&r.Fi()}else(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,6,t,t))}function eOL(e,t){var n,r;if(t!=e.Cb||e.Db>>16!=9&&t){if(eg7(e,t))throw p7(new gL(eZ4+eC5(e)));r=null,e.Cb&&(r=(n=e.Db>>16)>=0?eg2(e,r):e.Cb.ih(e,-1-n,null,r)),t&&(r=ep0(t,e,9,r)),(r=Cl(e,t,r))&&r.Fi()}else(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,9,t,t))}function eOC(e,t){var n,r;if(t!=e.Cb||e.Db>>16!=3&&t){if(eg7(e,t))throw p7(new gL(eZ4+ePY(e)));r=null,e.Cb&&(r=(n=e.Db>>16)>=0?evo(e,r):e.Cb.ih(e,-1-n,null,r)),t&&(r=ep0(t,e,12,r)),(r=Cu(e,t,r))&&r.Fi()}else(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,3,t,t))}function eOI(e){var t,n,r,i,a;if(r=evl(e),null==(a=e.j)&&r)return e.$j()?null:r.zj();if(M4(r,148)){if((n=r.Aj())&&(i=n.Nh())!=e.i){if((t=Pp(r,148)).Ej())try{e.g=i.Kh(t,a)}catch(o){if(o=eoa(o),M4(o,78))e.g=null;else throw p7(o)}e.i=i}return e.g}return null}function eOD(e){var t;return t=new p0,P_(t,new EL(new kl(e.c,e.d),new kl(e.c+e.b,e.d))),P_(t,new EL(new kl(e.c,e.d),new kl(e.c,e.d+e.a))),P_(t,new EL(new kl(e.c+e.b,e.d+e.a),new kl(e.c+e.b,e.d))),P_(t,new EL(new kl(e.c+e.b,e.d+e.a),new kl(e.c,e.d+e.a))),t}function eON(e,t,n,r){var i,a,o;if(o=eyn(t,n),r.c[r.c.length]=t,-1==e.j[o.p]||2==e.j[o.p]||e.a[t.p])return r;for(e.j[o.p]=-1,a=new Fa(OH(efs(o).a.Kc(),new c));eTk(a);)if(i=Pp(ZC(a),17),!q8(i)&&!(!q8(i)&&i.c.i.c==i.d.i.c)&&i!=t)return eON(e,i,o,r);return r}function eOP(e,t,n){var r,i,a;for(a=t.a.ec().Kc();a.Ob();)i=Pp(a.Pb(),79),(r=Pp(Bp(e.b,i),266))||(z$(e_I(i))==z$(e_P(i))?eLk(e,i,n):e_I(i)==z$(e_P(i))?null==Bp(e.c,i)&&null!=Bp(e.b,e_P(i))&&eFt(e,i,n,!1):null==Bp(e.d,i)&&null!=Bp(e.b,e_I(i))&&eFt(e,i,n,!0))}function eOR(e,t){var n,r,i,a,o,s,u;for(i=e.Kc();i.Ob();)for(r=Pp(i.Pb(),10),s=new eES,Gc(s,r),ekv(s,(eYu(),tby)),eo3(s,(eBU(),tnm),(OQ(),!0)),o=t.Kc();o.Ob();)a=Pp(o.Pb(),10),u=new eES,Gc(u,a),ekv(u,tbY),eo3(u,tnm,!0),n=new $b,eo3(n,tnm,!0),Gs(n,s),Go(n,u)}function eOj(e,t,n,r){var i,a,o,s;i=ehu(e,t,n),a=ehu(e,n,t),o=Pp(Bp(e.c,t),112),s=Pp(Bp(e.c,n),112),ir.b.g&&(a.c[a.c.length]=r);return a}function eOB(){eOB=A,tfo=new S9("CANDIDATE_POSITION_LAST_PLACED_RIGHT",0),tfa=new S9("CANDIDATE_POSITION_LAST_PLACED_BELOW",1),tfu=new S9("CANDIDATE_POSITION_WHOLE_DRAWING_RIGHT",2),tfs=new S9("CANDIDATE_POSITION_WHOLE_DRAWING_BELOW",3),tfc=new S9("WHOLE_DRAWING",4)}function eOU(e,t){if(M4(t,239))return elg(e,Pp(t,33));if(M4(t,186))return el$(e,Pp(t,118));if(M4(t,354))return Hd(e,Pp(t,137));if(M4(t,352))return eNP(e,Pp(t,79));if(t)return null;else throw p7(new gL(eXx+e_F(new g$(eow(vx(e1R,1),eUp,1,5,[t])))))}function eOH(e){var t,n,r,i,a,o,s;for(a=new _n,i=new fz(e.d.a);i.a1)for(t=Al((n=new b1,++e.b,n),e.d),s=epL(a,0);s.b!=s.d.c;)o=Pp(Vv(s),121),eAx(_f(_l(_d(_c(new bQ,1),0),t),o))}function eO$(e,t){var n,r;if(t!=e.Cb||e.Db>>16!=11&&t){if(eg7(e,t))throw p7(new gL(eZ4+eC4(e)));r=null,e.Cb&&(r=(n=e.Db>>16)>=0?evs(e,r):e.Cb.ih(e,-1-n,null,r)),t&&(r=ep0(t,e,10,r)),(r=C4(e,t,r))&&r.Fi()}else(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,11,t,t))}function eOz(e){var t,n,r,i;for(r=new esz(new fS(e.b).a);r.b;)n=etz(r),i=Pp(n.cd(),11),eo3(t=Pp(n.dd(),10),(eBU(),tnc),i),eo3(i,tng,t),eo3(i,tt6,(OQ(),!0)),ekv(i,Pp(e_k(t,tt1),61)),e_k(t,tt1),eo3(i.i,(eBy(),tol),(ewf(),tbu)),Pp(e_k(Bq(i.i),tt3),21).Fc((eLR(),ttS))}function eOG(e,t,n){var r,i,a,o,s,u;if(a=0,o=0,e.c)for(u=new fz(e.d.i.j);u.aa.a)?-1:i.a(u=null==e.d?0:e.d.length)))return!1;for(a=0,l=e.d,e.d=Je(e6C,eJA,63,2*u+4,0,1);a=0x7fffffffffffffff?(Q2(),e0L):(i=!1,e<0&&(i=!0,e=-e),r=0,e>=eHW&&(r=zy(e/eHW),e-=r*eHW),n=0,e>=eHG&&(n=zy(e/eHG),e-=n*eHG),a=Mk(t=zy(e),n,r),i&&esh(a),a)}function eO6(e,t){var n,r,i,a;for(n=!t||!e.u.Hc((ekU(),tbp)),a=0,i=new fz(e.e.Cf());i.a=-t&&r==t?new kD(ell(n-1),ell(r)):new kD(ell(n),ell(r-1))}function eAn(){return eB$(),eow(vx(e4B,1),eU4,77,0,[e85,e82,e86,e7d,e7C,e7m,e7j,e7_,e7A,e7s,e7x,e7w,e7L,e7r,e7Y,e8Z,e7k,e7D,e7h,e7I,e7U,e7M,e8X,e7O,e7H,e7P,e7B,e7p,e7e,e7b,e7f,e7F,e81,e88,e7v,e8Q,e7y,e7c,e7i,e7E,e7o,e83,e80,e7l,e7a,e7S,e7R,e8J,e7T,e7u,e7g,e7t,e87,e7N,e89,e7n,e84])}function eAr(e,t,n){e.d=0,e.b=0,t.k==(eEn(),e8P)&&n.k==e8P&&Pp(e_k(t,(eBU(),tnc)),10)==Pp(e_k(n,tnc),10)&&(QP(t).j==(eYu(),tbw)?eMH(e,t,n):eMH(e,n,t)),t.k==e8P&&n.k==e8D?QP(t).j==(eYu(),tbw)?e.d=1:e.b=1:n.k==e8P&&t.k==e8D&&(QP(n).j==(eYu(),tbw)?e.b=1:e.d=1),emu(e,t,n)}function eAi(e){var t,n,r,i,a,o,s,u,c,l,f;return f=ewW(e),(u=null!=(t=e.a))&&P4(f,"category",e.a),(o=!(i=wc(new fk(e.d))))&&(ee3(f,"knownOptions",c=new lN),n=new pS(c),qX(new fk(e.d),n)),(s=!(a=wc(e.g)))&&(ee3(f,"supportedFeatures",l=new lN),r=new pk(l),qX(e.g,r)),f}function eAa(e){var t,n,r,i,a,o,s,u,c;for(u=0,r=!1,t=336,n=0,a=new CE(e.length),c=(s=e).length;u>16!=7&&t){if(eg7(e,t))throw p7(new gL(eZ4+eE1(e)));r=null,e.Cb&&(r=(n=e.Db>>16)>=0?eg0(e,r):e.Cb.ih(e,-1-n,null,r)),t&&(r=Pp(t,49).gh(e,1,e6p,r)),(r=j2(e,t,r))&&r.Fi()}else(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,7,t,t))}function eAc(e,t){var n,r;if(t!=e.Cb||e.Db>>16!=3&&t){if(eg7(e,t))throw p7(new gL(eZ4+eln(e)));r=null,e.Cb&&(r=(n=e.Db>>16)>=0?eg4(e,r):e.Cb.ih(e,-1-n,null,r)),t&&(r=Pp(t,49).gh(e,0,e6y,r)),(r=j3(e,t,r))&&r.Fi()}else(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,3,t,t))}function eAl(e,t){var n,r,i,a,o,s,u,c,l;return(exX(),t.d>e.d&&(s=e,e=t,t=s),t.d<63)?eLm(e,t):(o=(-2&e.d)<<4,c=ZL(e,o),l=ZL(t,o),r=eNz(e,ZA(c,o)),i=eNz(t,ZA(l,o)),u=eAl(c,l),n=eAl(r,i),a=eAl(eNz(c,r),eNz(i,l)),a=eP5(eP5(a,u),n),a=ZA(a,o),u=ZA(u,o<<1),eP5(eP5(u,a),n))}function eAf(e,t,n){var r,i,a,o,s;for(o=ecZ(e,n),s=Je(e4N,eGW,10,t.length,0,1),r=0,a=o.Kc();a.Ob();)gN(LK(e_k(i=Pp(a.Pb(),11),(eBU(),tt6))))&&(s[r++]=Pp(e_k(i,tng),10));if(r=0;a+=n?1:-1)o|=t.c.Sf(u,a,n,r&&!gN(LK(e_k(t.j,(eBU(),tt2))))&&!gN(LK(e_k(t.j,(eBU(),tnS))))),o|=t.q._f(u,a,n),o|=eCA(e,u[a],n,r);return Yf(e.c,t),o}function eAm(e,t,n){var r,i,a,o,s,u,c,l,f,d;for(l=Kz(e.j),f=0,d=l.length;f1&&(e.a=!0),jU(Pp(n.b,65),C5(MB(Pp(t.b,65).c),Ol(C6(MB(Pp(n.b,65).a),Pp(t.b,65).a),i))),GC(e,t),eAy(e,n)}function eAw(e){var t,n,r,i,a,o,s;for(a=new fz(e.a.a);a.a0&&a>0?o.p=t++:r>0?o.p=n++:a>0?o.p=i++:o.p=n++}Hj(),Mv(e.j,new nG)}function eAE(e){var t,n;n=null,t=Pp(RJ(e.g,0),17);do{if(Ln(n=t.d.i,(eBU(),tna)))return Pp(e_k(n,tna),11).i;if(n.k!=(eEn(),e8N)&&eTk(new Fa(OH(efc(n).a.Kc(),new c))))t=Pp(ZC(new Fa(OH(efc(n).a.Kc(),new c))),17);else if(n.k!=e8N)return null}while(!!n&&n.k!=(eEn(),e8N))return n}function eAS(e,t){var n,r,i,a,o,s,u,c,l;for(a=1,s=t.j,o=t.g,c=em1(e,o,u=Pp(RJ(s,s.c.length-1),113),l=(GK(0,s.c.length),Pp(s.c[0],113)));ac&&(u=n,l=i,c=r);t.a=l,t.c=u}function eAk(e,t){var n,r;if(!(r=YB(e.b,t.b)))throw p7(new gC("Invalid hitboxes for scanline constraint calculation."));(eop(t.b,Pp(CF(e.b,t.b),57))||eop(t.b,Pp(Cj(e.b,t.b),57)))&&(wK(),t.b),e.a[t.b.f]=Pp(Ik(e.b,t.b),57),(n=Pp(IS(e.b,t.b),57))&&(e.a[n.f]=t.b)}function eAx(e){if(!e.a.d||!e.a.e)throw p7(new gC((LW(e23),e23.k+" must have a source and target "+(LW(e24),e24.k)+" specified.")));if(e.a.d==e.a.e)throw p7(new gC("Network simplex does not support self-loops: "+e.a+" "+e.a.d+" "+e.a.e));return Am(e.a.d.g,e.a),Am(e.a.e.b,e.a),e.a}function eAT(e,t,n){var r,i,a,o,s,u,c;for(c=new yB(new hA(e)),o=eow(vx(e4j,1),eGK,11,0,[t,n]),s=0,u=o.length;su-e.b&&su-e.a&&s0&&++h;++d}return h}function eAF(e,t){var n,r,i,a,o;for(o=Pp(e_k(t,(eTj(),tcN)),425),a=epL(t.b,0);a.b!=a.d.c;)if(i=Pp(Vv(a),86),0==e.b[i.g]){switch(o.g){case 0:eb9(e,i);break;case 1:eTX(e,i)}e.b[i.g]=2}for(r=epL(e.a,0);r.b!=r.d.c;)eds((n=Pp(Vv(r),188)).b.d,n,!0),eds(n.c.b,n,!0);eo3(t,(eR6(),tch),e.a)}function eAY(e,t){var n,r,i,a;return(_4(),t)?t==(eR7(),tvG)||(t==tvM||t==tvx||t==tvT)&&e!=tvk?new eF2(e,t):((n=(r=Pp(t,677)).pk())||(UH(QZ((eSp(),tvc),t)),n=r.pk()),a=(n.i||(n.i=new p2),n.i),(i=Pp(xu($I(a.f,e)),1942))||Um(a,e,i=new eF2(e,t)),i):tvb}function eAB(e,t){var n,r,i,a,o,s,u,c,l;for(a=0,u=Pp(e_k(e,(eBU(),tnc)),11),c=esp(eow(vx(e50,1),eUP,8,0,[u.i.n,u.n,u.a])).a,l=e.i.n.b,o=(i=n=Kp(e.e)).length;a0?a.a?n>(s=a.b.rf().a)&&(i=(n-s)/2,a.d.b=i,a.d.c=i):a.d.c=e.s+n:FY(e.u)&&((r=ew1(a.b)).c<0&&(a.d.b=-r.c),r.c+r.b>a.b.rf().a&&(a.d.c=r.c+r.b-a.b.rf().a))}function eAz(e,t){var n,r,i,a;for(ewG(t,"Semi-Interactive Crossing Minimization Processor",1),n=!1,i=new fz(e.b);i.a=0){if(t==n)return new kD(ell(-t-1),ell(-t-1));if(t==-n)return new kD(ell(-t),ell(n+1))}return eB4.Math.abs(t)>eB4.Math.abs(n)?t<0?new kD(ell(-t),ell(n)):new kD(ell(-t),ell(n+1)):new kD(ell(t+1),ell(n))}function eAK(e){var t,n;n=Pp(e_k(e,(eBy(),taY)),163),t=Pp(e_k(e,(eBU(),tt9)),303),n==(ef_(),tnN)?(eo3(e,taY,tnj),eo3(e,tt9,(Q1(),ttN))):n==tnR?(eo3(e,taY,tnj),eo3(e,tt9,(Q1(),ttI))):t==(Q1(),ttN)?(eo3(e,taY,tnN),eo3(e,tt9,ttD)):t==ttI&&(eo3(e,taY,tnR),eo3(e,tt9,ttD))}function eAV(){eAV=A,tuY=new ad,tuP=RI(new K2,(e_x(),e8n),(eB$(),e7h)),tuF=j0(RI(new K2,e8n,e7M),e8i,e7T),tuB=ehY(ehY(_G(j0(RI(new K2,e8e,e7j),e8i,e7R),e8r),e7P),e7F),tuR=j0(RI(RI(RI(new K2,e8t,e7m),e8r,e7v),e8r,e7y),e8i,e7g),tuj=j0(RI(RI(new K2,e8r,e7y),e8r,e88),e8i,e89)}function eAq(){eAq=A,tuz=RI(j0(new K2,(e_x(),e8i),(eB$(),e7t)),e8n,e7h),tuV=ehY(ehY(_G(j0(RI(new K2,e8e,e7j),e8i,e7R),e8r),e7P),e7F),tuG=j0(RI(RI(RI(new K2,e8t,e7m),e8r,e7v),e8r,e7y),e8i,e7g),tuK=RI(RI(new K2,e8n,e7M),e8i,e7T),tuW=j0(RI(RI(new K2,e8r,e7y),e8r,e88),e8i,e89)}function eAZ(e,t,n,r,i){var a,o;(q8(t)||t.c.i.c!=t.d.i.c)&&erS(esp(eow(vx(e50,1),eUP,8,0,[i.i.n,i.n,i.a])),n)||q8(t)||(t.c==i?Ls(t.a,0,new TS(n)):P7(t.a,new TS(n)),r&&!w0(e.a,n)&&((o=Pp(e_k(t,(eBy(),taR)),74))||eo3(t,taR,o=new mE),qQ(o,a=new TS(n),o.c.b,o.c),Yf(e.a,a)))}function eAX(e){var t,n;for(n=new Fa(OH(efu(e).a.Kc(),new c));eTk(n);)if((t=Pp(ZC(n),17)).c.i.k!=(eEn(),e8I))throw p7(new gq(eWr+egs(e)+"' has its layer constraint set to FIRST, but has at least one incoming edge that does not come from a FIRST_SEPARATE node. That must not happen."))}function eAJ(e,t,n){var r,i,a,o,s,u,c;if(0==(i=efp(254&e.Db)))e.Eb=n;else{if(1==i)s=Je(e1R,eUp,1,2,5,1),0==(a=emF(e,t))?(s[0]=n,s[1]=e.Eb):(s[0]=e.Eb,s[1]=n);else for(r=2,s=Je(e1R,eUp,1,i+1,5,1),o=etG(e.Eb),u=0,c=0;r<=128;r<<=1)r==t?s[c++]=n:(e.Db&r)!=0&&(s[c++]=o[u++]);e.Eb=s}e.Db|=t}function eAQ(e,t,n){var r,i,a,o;for(this.b=new p0,i=0,r=0,o=new fz(e);o.a0&&(i+=(a=Pp(RJ(this.b,0),167)).o,r+=a.p),i*=2,r*=2,t>1?i=zy(eB4.Math.ceil(i*t)):r=zy(eB4.Math.ceil(r/t)),this.a=new edL(i,r)}function eA1(e,t,n,r,i,a){var o,s,u,c,l,f,d,h,p,b,m,g;for(l=r,t.j&&t.o?(b=(h=Pp(Bp(e.f,t.A),57)).d.c+h.d.b,--l):b=t.a.c+t.a.b,f=i,n.q&&n.o?(c=(h=Pp(Bp(e.f,n.C),57)).d.c,++f):c=n.a.c,m=c-b,p=b+(s=m/(u=eB4.Math.max(2,f-l))),d=l;d=0;o+=i?1:-1){for(s=t[o],u=r==(eYu(),tby)?i?efr(s,r):eaa(efr(s,r)):i?eaa(efr(s,r)):efr(s,r),a&&(e.c[s.p]=u.gc()),f=u.Kc();f.Ob();)l=Pp(f.Pb(),11),e.d[l.p]=c++;eoc(n,u)}}function eA2(e,t,n){var r,i,a,o,s,u,c,l;for(a=gP(LV(e.b.Kc().Pb())),c=gP(LV(eaX(t.b))),l=C5(r=Ol(MB(e.a),c-n),i=Ol(MB(t.a),n-a)),Ol(l,1/(c-a)),this.a=l,this.b=new p0,s=!0,(o=e.b.Kc()).Pb();o.Ob();)u=gP(LV(o.Pb())),s&&u-n>eVW&&(this.b.Fc(n),s=!1),this.b.Fc(u);s&&this.b.Fc(n)}function eA3(e){var t,n,r,i;if(eIh(e,e.n),e.d.c.length>0){for(gG(e.c);eTT(e,Pp(Wx(new fz(e.e.a)),121))>5,t&=31,r>=e.d)return e.e<0?(eLQ(),e03):(eLQ(),e08);if(i=Je(ty_,eHT,25,(a=e.d-r)+1,15,1),eEG(i,a,e.a,r,t),e.e<0){for(n=0;n0&&e.a[n]<<32-t!=0){for(n=0;n=0)&&(!(n=eR3((eSp(),tvc),i,t))||((r=n.Zj())>1||-1==r)&&3!=Ur(QZ(tvc,n))))}function eLn(e,t,n,r){var i,a,o,s,u;return(s=ewH(Pp(etj((t.b||(t.b=new Ih(e6m,t,4,7)),t.b),0),82)),u=ewH(Pp(etj((t.c||(t.c=new Ih(e6m,t,5,8)),t.c),0),82)),z$(s)==z$(u)||etg(u,s))?null:(o=zF(t))==n?r:(a=Pp(Bp(e.a,o),10))&&(i=a.e)?i:null}function eLr(e,t){var n;switch(n=Pp(e_k(e,(eBy(),tam)),276),ewG(t,"Label side selection ("+n+")",1),n.g){case 0:eTD(e,(egF(),tpV));break;case 1:eTD(e,(egF(),tpq));break;case 2:eNW(e,(egF(),tpV));break;case 3:eNW(e,(egF(),tpq));break;case 4:eLL(e,(egF(),tpV));break;case 5:eLL(e,(egF(),tpq))}eEj(t)}function eLi(e,t,n){var r,i,a,o,s,u;if((o=e[r=vK(n,e.length)])[0].k==(eEn(),e8C))for(i=0,a=vW(n,o.length),u=t.j;i0&&(n[0]+=e.d,o-=n[0]),n[2]>0&&(n[2]+=e.d,o-=n[2]),a=eB4.Math.max(0,o),n[1]=eB4.Math.max(n[1],o),ZR(e,e3N,i.c+r.b+n[0]-(n[1]-o)/2,n),t==e3N&&(e.c.b=a,e.c.c=i.c+r.b+(a-o)/2)}function eLy(){this.c=Je(tyx,eH5,25,(eYu(),eow(vx(e6a,1),eGj,61,0,[tbF,tbw,tby,tbj,tbY])).length,15,1),this.b=Je(tyx,eH5,25,eow(vx(e6a,1),eGj,61,0,[tbF,tbw,tby,tbj,tbY]).length,15,1),this.a=Je(tyx,eH5,25,eow(vx(e6a,1),eGj,61,0,[tbF,tbw,tby,tbj,tbY]).length,15,1),Ep(this.c,eHQ),Ep(this.b,eH1),Ep(this.a,eH1)}function eLw(e,t,n){var r,i,a,o;if(t<=n?(i=t,a=n):(i=n,a=t),r=0,null==e.b)e.b=Je(ty_,eHT,25,2,15,1),e.b[0]=i,e.b[1]=a,e.c=!0;else{if(r=e.b.length,e.b[r-1]+1==i){e.b[r-1]=a;return}o=Je(ty_,eHT,25,r+2,15,1),ePD(e.b,0,o,0,r),e.b=o,e.b[r-1]>=i&&(e.c=!1,e.a=!1),e.b[r++]=i,e.b[r]=a,e.c||eMS(e)}}function eL_(e,t,n){var r,i,a,o,s,u,c;for(c=t.d,e.a=new XM(c.c.length),e.c=new p2,s=new fz(c);s.a=0?e._g(c,!1,!0):exk(e,n,!1),58);n:for(a=f.Kc();a.Ob();){for(l=0,i=Pp(a.Pb(),56);l1;)eLN(i,i.i-1);return r}function eLA(e,t){var n,r,i,a,o,s,u;for(ewG(t,"Comment post-processing",1),a=new fz(e.b);a.ae.d[o.p]&&(n+=qq(e.b,a),Vw(e.a,ell(a)));for(;!gY(e.a);)eek(e.b,Pp(Yn(e.a),19).a)}return n}function eLD(e,t,n){var r,i,a,o;for(a=(t.a||(t.a=new FQ(e6k,t,10,11)),t.a).i,i=new Ow((t.a||(t.a=new FQ(e6k,t,10,11)),t.a));i.e!=i.i.gc();)0==((r=Pp(epH(i),33)).a||(r.a=new FQ(e6k,r,10,11)),r.a).i||(a+=eLD(e,r,!1));if(n)for(o=z$(t);o;)a+=(o.a||(o.a=new FQ(e6k,o,10,11)),o.a).i,o=z$(o);return a}function eLN(e,t){var n,r,i,a;return e.ej()?(r=null,i=e.fj(),e.ij()&&(r=e.kj(e.pi(t),null)),n=e.Zi(4,a=egk(e,t),null,t,i),e.bj()&&null!=a?(r=e.dj(a,r))?(r.Ei(n),r.Fi()):e.$i(n):r?(r.Ei(n),r.Fi()):e.$i(n),a):(a=egk(e,t),e.bj()&&null!=a&&(r=e.dj(a,null))&&r.Fi(),a)}function eLP(e){var t,n,r,i,a,o,s,u,c,l;for(c=e.a,t=new bV,u=0,r=new fz(e.d);r.as.d&&(l=s.d+s.a+c));n.c.d=l,t.a.zc(n,t),u=eB4.Math.max(u,n.c.d+n.c.a)}return u}function eLR(){eLR=A,ttv=new Sv("COMMENTS",0),ttw=new Sv("EXTERNAL_PORTS",1),tt_=new Sv("HYPEREDGES",2),ttE=new Sv("HYPERNODES",3),ttS=new Sv("NON_FREE_PORTS",4),ttk=new Sv("NORTH_SOUTH_PORTS",5),ttT=new Sv(eWw,6),ttg=new Sv("CENTER_LABELS",7),tty=new Sv("END_LABELS",8),ttx=new Sv("PARTITIONS",9)}function eLj(e){var t,n,r,i,a;for(i=new p0,t=new Rq((e.a||(e.a=new FQ(e6k,e,10,11)),e.a)),r=new Fa(OH(eOi(e).a.Kc(),new c));eTk(r);)n=Pp(ZC(r),79),!M4(etj((n.b||(n.b=new Ih(e6m,n,4,7)),n.b),0),186)&&(a=ewH(Pp(etj((n.c||(n.c=new Ih(e6m,n,5,8)),n.c),0),82)),t.a._b(a)||(i.c[i.c.length]=a));return i}function eLF(e){var t,n,r,i,a,o;for(a=new bV,t=new Rq((e.a||(e.a=new FQ(e6k,e,10,11)),e.a)),i=new Fa(OH(eOi(e).a.Kc(),new c));eTk(i);)r=Pp(ZC(i),79),!M4(etj((r.b||(r.b=new Ih(e6m,r,4,7)),r.b),0),186)&&(o=ewH(Pp(etj((r.c||(r.c=new Ih(e6m,r,5,8)),r.c),0),82)),t.a._b(o)||(n=a.a.zc(o,a)));return a}function eLY(e,t,n,r,i){return r<0?((r=ew6(e,i,eow(vx(e17,1),eUP,2,6,[eHh,eHp,eHb,eHm,eHg,eHv,eHy,eHw,eH_,eHE,eHS,eHk]),t))<0&&(r=ew6(e,i,eow(vx(e17,1),eUP,2,6,["Jan","Feb","Mar","Apr",eHg,"Jun","Jul","Aug","Sep","Oct","Nov","Dec"]),t)),!(r<0)&&(n.k=r,!0)):r>0&&(n.k=r-1,!0)}function eLB(e,t,n,r,i){return r<0?((r=ew6(e,i,eow(vx(e17,1),eUP,2,6,[eHh,eHp,eHb,eHm,eHg,eHv,eHy,eHw,eH_,eHE,eHS,eHk]),t))<0&&(r=ew6(e,i,eow(vx(e17,1),eUP,2,6,["Jan","Feb","Mar","Apr",eHg,"Jun","Jul","Aug","Sep","Oct","Nov","Dec"]),t)),!(r<0)&&(n.k=r,!0)):r>0&&(n.k=r-1,!0)}function eLU(e,t,n,r,i,a){var o,s,u,c;if(s=32,r<0){if(t[0]>=e.length||43!=(s=UI(e,t[0]))&&45!=s||(++t[0],(r=exf(e,t))<0))return!1;45==s&&(r=-r)}return 32==s&&t[0]-n==2&&2==i.b&&(o=(c=(u=new wW).q.getFullYear()-eHx+eHx-80)%100,a.a=r==o,r+=(c/100|0)*100+(r=c&&(u=r);u&&(l=eB4.Math.max(l,u.a.o.a)),l>d&&(f=c,d=l)}return f}function eLV(e,t,n){var r,i,a;if(e.e=n,e.d=0,e.b=0,e.f=1,e.i=t,(16&e.e)==16&&(e.i=eIw(e.i)),e.j=e.i.length,eBM(e),a=ehT(e),e.d!=e.j)throw p7(new gX(eBJ((Mo(),eXV))));if(e.g){for(r=0;reqg?Mv(u,e.b):r<=eqg&&r>eqv?Mv(u,e.d):r<=eqv&&r>eqy?Mv(u,e.c):r<=eqy&&Mv(u,e.a),a=eLJ(e,u,a);return i}function eLQ(){var e;for(e=0,eLQ=A,e04=new XE(1,1),e06=new XE(1,10),e08=new XE(0,0),e03=new XE(-1,1),e05=eow(vx(e0t,1),eUP,91,0,[e08,e04,new XE(1,2),new XE(1,3),new XE(1,4),new XE(1,5),new XE(1,6),new XE(1,7),new XE(1,8),new XE(1,9),e06]),e09=Je(e0t,eUP,91,32,0,1);e1)&&(r=new kl(i,n.b),P7(t.a,r)),enD(t.a,eow(vx(e50,1),eUP,8,0,[d,f]))}function eL6(e){_Y(e,new ewB(vQ(vq(vJ(vX(new oc,eZA),"ELK Randomizer"),'Distributes the nodes randomly on the plane, leading to very obfuscating layouts. Can be useful to demonstrate the power of "real" layout algorithms.'),new oz))),KE(e,eZA,ezW,tb$),KE(e,eZA,eGi,15),KE(e,eZA,eGo,ell(0)),KE(e,eZA,ezG,eGt)}function eL9(){var e,t,n,r,i,a;for(t=0,eL9=A,tv1=Je(tyk,eZ8,25,255,15,1),tv0=Je(tyw,eHl,25,16,15,1);t<255;t++)tv1[t]=-1;for(n=57;n>=48;n--)tv1[n]=n-48<<24>>24;for(r=70;r>=65;r--)tv1[r]=r-65+10<<24>>24;for(i=102;i>=97;i--)tv1[i]=i-97+10<<24>>24;for(a=0;a<10;a++)tv0[a]=48+a&eHd;for(e=10;e<=15;e++)tv0[e]=65+e-10&eHd}function eL8(e,t,n){var r,i,a,o,s,u,c,l;return s=t.i-e.g/2,u=n.i-e.g/2,c=t.j-e.g/2,l=n.j-e.g/2,a=t.g+e.g/2,o=n.g+e.g/2,r=t.f+e.g/2,i=n.f+e.g/2,!!(s>19!=0)return"-"+eCr(eoQ(e));for(n=e,r="";!(0==n.l&&0==n.m&&0==n.h);){if(n=eRV(n,i=Zx(eHK),!0),t=""+yq(e0A),!(0==n.l&&0==n.m&&0==n.h))for(a=9-t.length;a>0;a--)t="0"+t;r=t+r}return r}function eCi(){if(!Object.create||!Object.getOwnPropertyNames)return!1;var e="__proto__",t=Object.create(null);return void 0===t[e]&&0==Object.getOwnPropertyNames(t).length&&(t[e]=42,42===t[e]&&0!=Object.getOwnPropertyNames(t).length)}function eCa(e){var t,n,r,i,a,o,s;for(t=!1,n=0,i=new fz(e.d.b);i.a=e.a||!ewg(t,n))return -1;if(Vb(Pp(r.Kb(t),20)))return 1;for(i=0,o=Pp(r.Kb(t),20).Kc();o.Ob();)if(-1==(s=eCu(e,u=(a=Pp(o.Pb(),17)).c.i==t?a.d.i:a.c.i,n,r))||(i=eB4.Math.max(i,s))>e.c-1)return -1;return i+1}function eCc(e,t){var n,r,i,a,o,s;if(xc(t)===xc(e))return!0;if(!M4(t,15)||(r=Pp(t,15),s=e.gc(),r.gc()!=s))return!1;if(o=r.Kc(),e.ni()){for(n=0;n0){if(e.qj(),null!=t){for(a=0;a>24;case 97:case 98:case 99:case 100:case 101:case 102:return e-97+10<<24>>24;case 65:case 66:case 67:case 68:case 69:case 70:return e-65+10<<24>>24;default:throw p7(new vo("Invalid hexadecimal"))}}function eCh(e,t,n){var r,i,a,o;for(ewG(n,"Processor order nodes",2),e.a=gP(LV(e_k(t,(eTj(),tcR)))),i=new _n,o=epL(t.b,0);o.b!=o.d.c;)gN(LK(e_k(a=Pp(Vv(o),86),(eR6(),tcm))))&&qQ(i,a,i.c.b,i.c);eRt(e,r=(A6(0!=i.b),Pp(i.a.a.c,86))),n.b||erd(n,1),eC1(e,r,0-gP(LV(e_k(r,(eR6(),tcu))))/2,0),n.b||erd(n,1),eEj(n)}function eCp(){eCp=A,e3C=new Ej("SPIRAL",0),e3T=new Ej("LINE_BY_LINE",1),e3M=new Ej("MANHATTAN",2),e3x=new Ej("JITTER",3),e3A=new Ej("QUADRANTS_LINE_BY_LINE",4),e3L=new Ej("QUADRANTS_MANHATTAN",5),e3O=new Ej("QUADRANTS_JITTER",6),e3k=new Ej("COMBINE_LINE_BY_LINE_MANHATTAN",7),e3S=new Ej("COMBINE_JITTER_MANHATTAN",8)}function eCb(e,t,n,r){var i,a,o,s,u,c;for(u=eya(e,n),c=eya(t,n),i=!1;u&&c;)if(r||egl(u,c,n))o=eya(u,n),s=eya(c,n),QB(t),QB(e),a=u.c,ejf(u,!1),ejf(c,!1),n?(egU(t,c.p,a),t.p=c.p,egU(e,u.p+1,a),e.p=u.p):(egU(e,u.p,a),e.p=u.p,egU(t,c.p+1,a),t.p=c.p),Gu(u,null),Gu(c,null),u=o,c=s,i=!0;else break;return i}function eCm(e,t,n,r){var i,a,o,s,u;for(i=!1,a=!1,s=new fz(r.j);s.a=t.length)throw p7(new gE("Greedy SwitchDecider: Free layer not in graph."));this.c=t[e],this.e=new IQ(r),er$(this.e,this.c,(eYu(),tbY)),this.i=new IQ(r),er$(this.i,this.c,tby),this.f=new jy(this.c),this.a=!a&&i.i&&!i.s&&this.c[0].k==(eEn(),e8C),this.a&&eSt(this,e,t.length)}function eC_(e,t){var n,r,i,a,o,s;a=!e.B.Hc((eI3(),tbX)),o=e.B.Hc(tb1),e.a=new edA(o,a,e.c),e.n&&HI(e.a.n,e.n),gh(e.g,(etx(),e3N),e.a),t||((r=new eh6(1,a,e.c)).n.a=e.k,jT(e.p,(eYu(),tbw),r),(i=new eh6(1,a,e.c)).n.d=e.k,jT(e.p,tbj,i),(s=new eh6(0,a,e.c)).n.c=e.k,jT(e.p,tbY,s),(n=new eh6(0,a,e.c)).n.b=e.k,jT(e.p,tby,n))}function eCE(e){var t,n,r;switch((t=Pp(e_k(e.d,(eBy(),tag)),218)).g){case 2:n=eBn(e);break;case 3:n=(r=new p0,_r(UJ(UQ(eeh(eeh(new R1(null,new Gq(e.d.b,16)),new rJ),new rQ),new r1),new rY),new ha(r)),r);break;default:throw p7(new gC("Compaction not supported for "+t+" edges."))}eRD(e,n),qX(new fk(e.g),new hr(e))}function eCS(e,t){var n;return(n=new eX,t&&eaW(n,Pp(Bp(e.a,e6p),94)),M4(t,470)&&eaW(n,Pp(Bp(e.a,e6b),94)),M4(t,354))?(eaW(n,Pp(Bp(e.a,e6S),94)),n):(M4(t,82)&&eaW(n,Pp(Bp(e.a,e6m),94)),M4(t,239))?(eaW(n,Pp(Bp(e.a,e6k),94)),n):M4(t,186)?(eaW(n,Pp(Bp(e.a,e6x),94)),n):(M4(t,352)&&eaW(n,Pp(Bp(e.a,e6g),94)),n)}function eCk(){eCk=A,e9M=new T2((eBB(),th4),ell(1)),e9D=new T2(tpl,80),e9I=new T2(tpr,5),e9p=new T2(td2,eGt),e9O=new T2(th5,ell(1)),e9C=new T2(th8,(OQ(),!0)),e9k=new T3(50),e9S=new T2(thN,e9k),e9m=thb,e9x=thV,e9b=new T2(thn,!1),e9E=thD,e9_=thL,e9w=thx,e9y=thS,e9T=thJ,e9v=(eEg(),e9i),e9N=e9c,e9g=e9r,e9A=e9o,e9L=e9u}function eCx(e){var t,n,r,i,a,o,s,u;for(u=new Zr,s=new fz(e.a);s.a0&&t=0)return!1;if(t.p=n.b,P_(n.e,t),i==(eEn(),e8D)||i==e8P){for(o=new fz(t.j);o.a1||-1==o)&&(a|=16),(i.Bb&eZ1)!=0&&(a|=64)),(n.Bb&eH3)!=0&&(a|=eJq),a|=eXt):M4(t,457)?a|=512:(r=t.Bj())&&(1&r.i)!=0&&(a|=256),(512&e.Bb)!=0&&(a|=128),a}function eCG(e,t){var n,r,i,a,o;for(i=0,e=null==e?eUg:(BJ(e),e);ie.d[s.p]&&(n+=qq(e.b,a),Vw(e.a,ell(a))):++o;for(n+=e.b.d*o;!gY(e.a);)eek(e.b,Pp(Yn(e.a),19).a)}return n}function eCV(e,t){var n;return e.f==tvm?(n=Ur(QZ((eSp(),tvc),t)),e.e?4==n&&t!=(ex$(),tvw)&&t!=(ex$(),tvg)&&t!=(ex$(),tvv)&&t!=(ex$(),tvy):2==n):!!(e.d&&(e.d.Hc(t)||e.d.Hc(Wk(QZ((eSp(),tvc),t)))||e.d.Hc(eR3((eSp(),tvc),e.b,t))))||!!(e.f&&eOq((eSp(),e.f),U$(QZ(tvc,t))))&&(n=Ur(QZ(tvc,t)),e.e?4==n:2==n)}function eCq(e,t,n,r){var i,a,o,s,u,c,l,f;return u=(o=Pp(eT8(n,(eBB(),th3)),8)).a,l=o.b+e,(i=eB4.Math.atan2(l,u))<0&&(i+=eV7),(i+=t)>eV7&&(i-=eV7),c=(s=Pp(eT8(r,th3),8)).a,f=s.b+e,(a=eB4.Math.atan2(f,c))<0&&(a+=eV7),(a+=t)>eV7&&(a-=eV7),Mc(),enj(1e-10),1e-10>=eB4.Math.abs(i-a)||i==a||isNaN(i)&&isNaN(a)?0:ia?1:Te(isNaN(i),isNaN(a))}function eCZ(e){var t,n,r,i,a,o,s;for(s=new p2,r=new fz(e.a.b);r.a=e.o)throw p7(new bj);s=t>>5,o=31&t,a=Fg(1,jE(Fg(o,1))),i?e.n[n][s]=WO(e.n[n][s],a):e.n[n][s]=WM(e.n[n][s],PN(a)),a=Fg(a,1),r?e.n[n][s]=WO(e.n[n][s],a):e.n[n][s]=WM(e.n[n][s],PN(a))}catch(u){if(u=eoa(u),M4(u,320))throw p7(new gE(ez_+e.o+"*"+e.p+ezE+t+eUd+n+ezS));throw p7(u)}}function eC1(e,t,n,r){var i,a,o;t&&(a=gP(LV(e_k(t,(eR6(),tcd))))+r,o=n+gP(LV(e_k(t,tcu)))/2,eo3(t,tcg,ell(jE(eap(eB4.Math.round(a))))),eo3(t,tcv,ell(jE(eap(eB4.Math.round(o))))),0==t.d.b||eC1(e,Pp(M2((i=epL(new hz(t).a.d,0),new hG(i))),86),n+gP(LV(e_k(t,tcu)))+e.a,r+gP(LV(e_k(t,tcc)))),null!=e_k(t,tcb)&&eC1(e,Pp(e_k(t,tcb),86),n,r))}function eC0(e,t){var n,r,i,a,o,s,u,c,l,f,d;for(i=2*gP(LV(e_k(u=Bq(t.a),(eBy(),toI)))),l=gP(LV(e_k(u,toY))),c=eB4.Math.max(i,l),a=Je(tyx,eH5,25,t.f-t.c+1,15,1),r=-c,n=0,s=t.b.Kc();s.Ob();)o=Pp(s.Pb(),10),r+=e.a[o.c.p]+c,a[n++]=r;for(r+=e.a[t.a.c.p]+c,a[n++]=r,d=new fz(t.e);d.a0&&(r=(e.n||(e.n=new FQ(e6S,e,1,7)),Pp(etj(e.n,0),137)).a)&&xM(xM((t.a+=' "',t),r),'"')),xM(yW(xM(yW(xM(yW(xM(yW((t.a+=" (",t),e.i),","),e.j)," | "),e.g),","),e.f),")"),t.a)}function eC5(e){var t,n,r;return(64&e.Db)!=0?eEp(e):(t=new O0(eZG),(n=e.k)?xM(xM((t.a+=' "',t),n),'"'):(e.n||(e.n=new FQ(e6S,e,1,7)),e.n.i>0&&(r=(e.n||(e.n=new FQ(e6S,e,1,7)),Pp(etj(e.n,0),137)).a)&&xM(xM((t.a+=' "',t),r),'"')),xM(yW(xM(yW(xM(yW(xM(yW((t.a+=" (",t),e.i),","),e.j)," | "),e.g),","),e.f),")"),t.a)}function eC6(e,t){var n,r,i,a,o,s,u;if(null==t||0==t.length)return null;if(!(i=Pp(zg(e.a,t),149))){for(r=(s=new fT(e.b).a.vc().Kc(),new fN(s));r.a.Ob();)if(o=(n=(a=Pp(r.a.Pb(),42),Pp(a.dd(),149))).c,u=t.length,IE(o.substr(o.length-u,u),t)&&(t.length==o.length||46==UI(o,o.length-t.length-1))){if(i)return null;i=n}i&&Ge(e.a,t,i)}return i}function eC9(e,t){var n,r,i,a;return(n=new eD,i=(r=Pp(qE(UQ(new R1(null,new Gq(e.f,16)),n),Qz(new q,new Z,new er,new ei,eow(vx(e2L,1),eU4,132,0,[(eum(),e2H),e2U]))),21)).gc(),a=(r=Pp(qE(UQ(new R1(null,new Gq(t.f,16)),n),Qz(new q,new Z,new er,new ei,eow(vx(e2L,1),eU4,132,0,[e2H,e2U]))),21)).gc(),ii.p?(ekv(a,tbj),a.d&&(s=a.o.b,t=a.a.b,a.a.b=s-t)):a.j==tbj&&i.p>e.p&&(ekv(a,tbw),a.d&&(s=a.o.b,t=a.a.b,a.a.b=-(s-t)));break}return i}function eIe(e,t,n,r){var i,a,o,s,u,c,l,f,d,h,p;if(a=n,n1)&&(r=new kl(i,n.b),P7(t.a,r)),enD(t.a,eow(vx(e50,1),eUP,8,0,[d,f]))}function eIy(e,t,n){var r,i,a,o,s,u;if(!t)return null;if(!(n<=-1))return ebY(Pp(ee2(e.Tg(),n),18));if(r=ee2(t.Tg(),-1-n),M4(r,99))return Pp(r,18);for(s=0,u=(o=Pp(t.ah(r),153)).gc();s0){for(i=u.length;i>0&&""==u[i-1];)--i;i=t.d.a.gc()){o=t.a.c,s=t.a.c+t.a.b,u=new kl(o+(s-o)/2,t.b),P7(Pp(t.d.a.ec().Kc().Pb(),17).a,u);continue}if((i=Pp(Bp(t.c,n),459)).b||i.c){eIv(e,n,t);continue}(a=e.d==(euy(),tsW)&&(i.d||i.e)&&exJ(e,t)&&1>=t.d.a.gc())?eFd(n,t):eL5(e,n,t)}t.k&&qX(t.d,new nn)}}function eIq(e,t,n,r,i,a){var o,s,u,c,l,f,d,h,p,b,m,g,v,y;for(s=(r+i)/2+(d=a),m=n*eB4.Math.cos(s),g=n*eB4.Math.sin(s),v=m-t.g/2,y=g-t.f/2,eno(t,v),ens(t,y),f=e.a.jg(t),(b=2*eB4.Math.acos(n/n+e.c))=40)&&eNo(e),eRi(e),eA3(e),n=elM(e),r=0;n&&r0&&P7(e.f,a)):(e.c[o]-=c+1,e.c[o]<=0&&e.a[o]>0&&P7(e.e,a))))}function eI1(e){var t,n,r,i,a,o,s,u,c;for(s=new yB(Pp(Y9(new eP),62)),c=eH1,n=new fz(e.d);n.a=0&&un?t:n;c<=f;++c)c==n?s=r++:(a=i[c],l=p.rl(a.ak()),c==t&&(u=c!=f||l?r:r-1),l&&++r);return d=Pp(elR(e,t,n),72),s!=u&&bz(e,new JU(e.e,7,o,ell(s),h.dd(),u)),d}return Pp(elR(e,t,n),72)}function eDe(e,t){var n,r,i,a,o,s,u;for(ewG(t,"Port order processing",1),u=Pp(e_k(e,(eBy(),tom)),421),r=new fz(e.b);r.a=0&&(!(s=egy(e,o))||(c<22?u.l|=1<>>1,o.m=l>>>1|(1&f)<<21,o.l=d>>>1|(1&l)<<21,--c;return n&&esh(u),a&&(r?(e0A=eoQ(e),i&&(e0A=eor(e0A,(Q2(),e0I)))):e0A=Mk(e.l,e.m,e.h)),u}function eDi(e,t){var n,r,i,a,o,s,u,c,l,f;for(c=e.e[t.c.p][t.p]+1,u=t.c.a.c.length+1,s=new fz(e.a);s.a0&&(GV(0,e.length),45==e.charCodeAt(0)||(GV(0,e.length),43==e.charCodeAt(0)))?1:0;rn)throw p7(new vo(eHJ+e+'"'));return s}function eDo(e){var t,n,r,i,a,o,s;for(o=new _n,a=new fz(e.a);a.a1)&&1==t&&Pp(e.a[e.b],10).k==(eEn(),e8I)?eD3(Pp(e.a[e.b],10),(egF(),tpV)):r&&(!n||(e.c-e.b&e.a.length-1)>1)&&1==t&&Pp(e.a[e.c-1&e.a.length-1],10).k==(eEn(),e8I)?eD3(Pp(e.a[e.c-1&e.a.length-1],10),(egF(),tpq)):(e.c-e.b&e.a.length-1)==2?(eD3(Pp(eso(e),10),(egF(),tpV)),eD3(Pp(eso(e),10),tpq)):eM8(e,i),qr(e)}function eDf(e,t,n){var r,i,a,o,s;for(a=0,i=new Ow((e.a||(e.a=new FQ(e6k,e,10,11)),e.a));i.e!=i.i.gc();)r=Pp(epH(i),33),o="",0==(r.n||(r.n=new FQ(e6S,r,1,7)),r.n).i||(o=Pp(etj((r.n||(r.n=new FQ(e6S,r,1,7)),r.n),0),137).a),eaW(s=new esH(a++,t,o),r),eo3(s,(eR6(),tcl),r),s.e.b=r.j+r.f/2,s.f.a=eB4.Math.max(r.g,1),s.e.a=r.i+r.g/2,s.f.b=eB4.Math.max(r.f,1),P7(t.b,s),eS9(n.f,r,s)}function eDd(e){var t,n,r,i,a;r=Pp(e_k(e,(eBU(),tnc)),33),a=Pp(eT8(r,(eBy(),ta4)),174).Hc((ed6(),tbq)),!e.e&&(i=Pp(e_k(e,tt3),21),t=new kl(e.f.a+e.d.b+e.d.c,e.f.b+e.d.d+e.d.a),i.Hc((eLR(),ttw))?(ebu(r,tol,(ewf(),tbo)),eYx(r,t.a,t.b,!1,!0)):gN(LK(eT8(r,ta5)))||eYx(r,t.a,t.b,!0,!0)),a?ebu(r,ta4,el9(tbq)):ebu(r,ta4,(n=Pp(yw(e6o),9),new I1(n,Pp(CY(n,n.length),9),0)))}function eDh(e,t,n){var r,i,a,o;if(t[0]>=e.length)return n.o=0,!0;switch(UI(e,t[0])){case 43:i=1;break;case 45:i=-1;break;default:return n.o=0,!0}if(++t[0],a=t[0],0==(o=exf(e,t))&&t[0]==a)return!1;if(t[0]=0&&s!=n&&(a=new FX(e,1,s,o,null),r?r.Ei(a):r=a),n>=0&&(a=new FX(e,1,n,s==n?o:null,t),r?r.Ei(a):r=a)),r}function eDv(e){var t,n,r;if(null==e.b){if(r=new vs,null!=e.i&&(xk(r,e.i),r.a+=":"),(256&e.f)!=0){for((256&e.f)!=0&&null!=e.a&&(Hb(e.i)||(r.a+="//"),xk(r,e.a)),null!=e.d&&(r.a+="/",xk(r,e.d)),(16&e.f)!=0&&(r.a+="/"),t=0,n=e.j.length;td)&&(f=(u=ePI(r,d,!1)).a,l+s+f<=t.b&&(JR(n,a-n.s),n.c=!0,JR(r,a-n.s),ebP(r,n.s,n.t+n.d+s),r.k=!0,eiV(n.q,r),h=!0,i&&(enN(t,r),r.j=t,e.c.length>o&&(eva((GK(o,e.c.length),Pp(e.c[o],200)),r),0==(GK(o,e.c.length),Pp(e.c[o],200)).a.c.length&&ZV(e,o)))),h)}function eDx(e,t){var n,r,i,a,o,s;if(ewG(t,"Partition midprocessing",1),i=new zu,_r(UJ(new R1(null,new Gq(e.a,16)),new nK),new dQ(i)),0!=i.d){for(r=(s=Pp(qE(GU((a=i.i,new R1(null,(a||(i.i=new OC(i,i.c))).Nc()))),JF(new U,new B,new en,eow(vx(e2L,1),eU4,132,0,[(eum(),e2U)]))),15)).Kc(),n=Pp(r.Pb(),19);r.Ob();)o=Pp(r.Pb(),19),eOR(Pp(Zq(i,n),21),Pp(Zq(i,o),21)),n=o;eEj(t)}}function eDT(e,t,n){var r,i,a,o,s,u,c,l;if(0==t.p){for(t.p=1,(o=n)||(i=new p0,a=(r=Pp(yw(e6a),9),new I1(r,Pp(CY(r,r.length),9),0)),o=new kD(i,a)),Pp(o.a,15).Fc(t),t.k==(eEn(),e8C)&&Pp(o.b,21).Fc(Pp(e_k(t,(eBU(),tt1)),61)),u=new fz(t.j);u.a0){if(i=Pp(e.Ab.g,1934),null==t){for(a=0;a1)for(r=new fz(i);r.an.s&&ss&&(s=i,f.c=Je(e1R,eUp,1,0,5,1)),i==s&&P_(f,new kD(n.c.i,n)));Hj(),Mv(f,e.c),jO(e.b,u.p,f)}}function eDR(e,t){var n,r,i,a,o,s,u,l,f;for(o=new fz(t.b);o.as&&(s=i,f.c=Je(e1R,eUp,1,0,5,1)),i==s&&P_(f,new kD(n.d.i,n)));Hj(),Mv(f,e.c),jO(e.f,u.p,f)}}function eDj(e){_Y(e,new ewB(vQ(vq(vJ(vX(new oc,eZn),"ELK Box"),"Algorithm for packing of unconnected boxes, i.e. graphs without edges."),new oA))),KE(e,eZn,ezW,td$),KE(e,eZn,eGi,15),KE(e,eZn,eGr,ell(0)),KE(e,eZn,eqC,epB(tdj)),KE(e,eZn,eGh,epB(tdY)),KE(e,eZn,eGd,epB(tdU)),KE(e,eZn,ezG,eZt),KE(e,eZn,eGu,epB(tdF)),KE(e,eZn,eGM,epB(tdB)),KE(e,eZn,eZr,epB(tdP)),KE(e,eZn,eVg,epB(tdR))}function eDF(e,t){var n,r,i,a,o,s,u,c,l;if(o=(i=e.i).o.a,a=i.o.b,o<=0&&a<=0)return eYu(),tbF;switch(c=e.n.a,l=e.n.b,s=e.o.a,n=e.o.b,t.g){case 2:case 1:if(c<0)return eYu(),tbY;if(c+s>o)return eYu(),tby;break;case 4:case 3:if(l<0)return eYu(),tbw;if(l+n>a)return eYu(),tbj}return(u=(c+s/2)/o)+(r=(l+n/2)/a)<=1&&u-r<=0?(eYu(),tbY):u+r>=1&&u-r>=0?(eYu(),tby):r<.5?(eYu(),tbw):(eYu(),tbj)}function eDY(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b;for(n=!1,l=gP(LV(e_k(t,(eBy(),toF)))),p=eHe*l,i=new fz(t.b);i.a(u=s.n.b-s.d.d+d.a)+p&&(b=f.g+d.g,d.a=(d.g*d.a+f.g*f.a)/b,d.g=b,f.f=d,n=!0)),a=s,f=d;return n}function eDB(e,t,n,r,i,a,o){var s,u,c,l,f,d;for(d=new TE,c=t.Kc();c.Ob();)for(s=Pp(c.Pb(),839),f=new fz(s.wf());f.a0?s.a?i>(c=s.b.rf().b)&&(e.v||1==s.c.d.c.length?(o=(i-c)/2,s.d.d=o,s.d.a=o):(r=((n=Pp(RJ(s.c.d,0),181).rf().b)-c)/2,s.d.d=eB4.Math.max(0,r),s.d.a=i-r-c)):s.d.a=e.t+i:FY(e.u)&&((a=ew1(s.b)).d<0&&(s.d.d=-a.d),a.d+a.a>s.b.rf().b&&(s.d.a=a.d+a.a-s.b.rf().b))}function eD$(e,t){var n;switch(eeg(e)){case 6:return xd(t);case 7:return xf(t);case 8:return xl(t);case 3:return Array.isArray(t)&&!((n=eeg(t))>=14&&n<=16);case 11:return null!=t&&typeof t===eUs;case 12:return null!=t&&(typeof t===eUr||typeof t==eUs);case 0:return ebs(t,e.__elementTypeId$);case 2:return YS(t)&&t.im!==O;case 1:return YS(t)&&t.im!==O||ebs(t,e.__elementTypeId$);default:return!0}}function eDz(e,t){var n,r,i,a;return(r=eB4.Math.min(eB4.Math.abs(e.c-(t.c+t.b)),eB4.Math.abs(e.c+e.b-t.c)),a=eB4.Math.min(eB4.Math.abs(e.d-(t.d+t.a)),eB4.Math.abs(e.d+e.a-t.d)),(n=eB4.Math.abs(e.c+e.b/2-(t.c+t.b/2)))>e.b/2+t.b/2||(i=eB4.Math.abs(e.d+e.a/2-(t.d+t.a/2)))>e.a/2+t.a/2)?1:0==n&&0==i?0:0==n?a/i+1:0==i?r/n+1:eB4.Math.min(r/n,a/i)+1}function eDG(e,t){var n,r,i,a,o,s;return(i=enR(e),s=enR(t),i!=s)?it.f?1:0:(r=e.e-t.e,(n=(e.d>0?e.d:eB4.Math.floor((e.a-1)*eH9)+1)-(t.d>0?t.d:eB4.Math.floor((t.a-1)*eH9)+1))>r+1)?i:n0&&(o=eeD(o,eN4(r))),ehI(a,o))}function eDW(e,t){var n,r,i,a,o,s,u;for(a=0,s=0,u=0,i=new fz(e.f.e);i.a0&&e.d!=(QJ(),e95)&&(s+=o*(r.d.a+e.a[t.b][r.b]*(t.d.a-r.d.a)/n)),n>0&&e.d!=(QJ(),e93)&&(u+=o*(r.d.b+e.a[t.b][r.b]*(t.d.b-r.d.b)/n)));switch(e.d.g){case 1:return new kl(s/a,t.d.b);case 2:return new kl(t.d.a,u/a);default:return new kl(s/a,u/a)}}function eDK(e,t){var n,r,i,a,o;if(euv(),o=Pp(e_k(e.i,(eBy(),tol)),98),0!=(a=e.j.g-t.j.g)||!(o==(ewf(),tba)||o==tbs||o==tbo))return 0;if(o==(ewf(),tba)&&(n=Pp(e_k(e,tof),19),r=Pp(e_k(t,tof),19),n&&r&&0!=(i=n.a-r.a)))return i;switch(e.j.g){case 1:return elN(e.n.a,t.n.a);case 2:return elN(e.n.b,t.n.b);case 3:return elN(t.n.a,e.n.a);case 4:return elN(t.n.b,e.n.b);default:throw p7(new gC(eGz))}}function eDV(e){var t,n,r,i,a,o;for(n=(e.a||(e.a=new O_(e6h,e,5)),e.a).i+2,o=new XM(n),P_(o,new kl(e.j,e.k)),_r(new R1(null,(e.a||(e.a=new O_(e6h,e,5)),new Gq(e.a,16))),new h6(o)),P_(o,new kl(e.b,e.c)),t=1;t0&&(eoY(u,!1,(ec3(),tpm)),eoY(u,!0,tpg)),ety(t.g,new E4(e,n)),Um(e.g,t,n)}function eDZ(){var e;for(e=2,eDZ=A,e0$=eow(vx(ty_,1),eHT,25,15,[-1,-1,30,19,15,13,11,11,10,9,9,8,8,8,8,7,7,7,7,7,7,7,6,6,6,6,6,6,6,6,6,6,6,6,6,6,5]),e0z=Je(ty_,eHT,25,37,15,1),e0G=eow(vx(ty_,1),eHT,25,15,[-1,-1,63,40,32,28,25,23,21,20,19,19,18,18,17,17,16,16,16,15,15,15,15,14,14,14,14,14,14,13,13,13,13,13,13,13,13]),e0W=Je(tyS,eH2,25,37,14,1);e<=36;e++)e0z[e]=zy(eB4.Math.pow(e,e0$[e])),e0W[e]=eyt(eUY,e0z[e])}function eDX(e){var t;if(1!=(e.a||(e.a=new FQ(e6v,e,6,6)),e.a).i)throw p7(new gL(eZC+(e.a||(e.a=new FQ(e6v,e,6,6)),e.a).i));return t=new mE,eoo(Pp(etj((e.b||(e.b=new Ih(e6m,e,4,7)),e.b),0),82))&&er7(t,eBE(e,eoo(Pp(etj((e.b||(e.b=new Ih(e6m,e,4,7)),e.b),0),82)),!1)),eoo(Pp(etj((e.c||(e.c=new Ih(e6m,e,5,8)),e.c),0),82))&&er7(t,eBE(e,eoo(Pp(etj((e.c||(e.c=new Ih(e6m,e,5,8)),e.c),0),82)),!0)),t}function eDJ(e,t){var n,r,i,a,o;for(i=t.d?e.a.c==(zs(),tuw)?efu(t.b):efc(t.b):e.a.c==(zs(),tuy)?efu(t.b):efc(t.b),a=!1,r=new Fa(OH(i.a.Kc(),new c));eTk(r);)if(n=Pp(ZC(r),17),!(!(o=gN(e.a.f[e.a.g[t.b.p].p]))&&!q8(n)&&n.c.i.c==n.d.i.c||gN(e.a.n[e.a.g[t.b.p].p])||gN(e.a.n[e.a.g[t.b.p].p]))&&(a=!0,w0(e.b,e.a.g[emN(n,t.b).p])))return t.c=!0,t.a=n,t;return t.c=a,t.a=null,t}function eDQ(e,t,n,r,i){var a,o,s,u,c,l,f;for(Hj(),Mv(e,new oU),s=new KB(e,0),f=new p0,a=0;s.b2*a?(l=new etD(f),c=jl(o)/jc(o),u=eY9(l,t,new mp,n,r,i,c),C5(xB(l.e),u),f.c=Je(e1R,eUp,1,0,5,1),a=0,f.c[f.c.length]=l,f.c[f.c.length]=o,a=jl(l)*jc(l)+jl(o)*jc(o)):(f.c[f.c.length]=o,a+=jl(o)*jc(o));return f}function eD1(e,t,n){var r,i,a,o,s,u,c;if(0==(r=n.gc()))return!1;if(e.ej()){if(u=e.fj(),edu(e,t,n),o=1==r?e.Zi(3,null,n.Kc().Pb(),t,u):e.Zi(5,null,n,t,u),e.bj()){for(s=r<100?null:new yf(r),a=t+r,i=t;i0){for(o=0;o>16==-15&&e.Cb.nh()&&QU(new JB(e.Cb,9,13,n,e.c,ebv(QX(Pp(e.Cb,59)),e))):M4(e.Cb,88)&&e.Db>>16==-23&&e.Cb.nh()&&(M4(t=e.c,88)||(t=(eBK(),tgI)),M4(n,88)||(n=(eBK(),tgI)),QU(new JB(e.Cb,9,10,n,t,ebv(qt(Pp(e.Cb,26)),e)))))),e.c}function eD6(e,t){var n,r,i,a,o,s,u,c,l,f;for(ewG(t,"Hypernodes processing",1),i=new fz(e.b);i.an)return i}function eNe(e,t){var n,r,i;r=0!=eMU(e.d,1),(gN(LK(e_k(t.j,(eBU(),tt2))))||gN(LK(e_k(t.j,tnS))))&&xc(e_k(t.j,(eBy(),ti9)))!==xc((esn(),tsM))?r=gN(LK(e_k(t.j,tt2))):t.c.Tf(t.e,r),eAb(e,t,r,!0),gN(LK(e_k(t.j,tnS)))&&eo3(t.j,tnS,(OQ(),!1)),gN(LK(e_k(t.j,tt2)))&&(eo3(t.j,tt2,(OQ(),!1)),eo3(t.j,tnS,!0)),n=eSY(e,t);do{if(er0(e),0==n)return 0;r=!r,i=n,eAb(e,t,r,!1),n=eSY(e,t)}while(i>n)return i}function eNt(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p;if(t==n)return!0;if(t=eTE(e,t),n=eTE(e,n),!(r=eb1(t)))return(s=t.e)==(h=n.e);if((l=eb1(n))!=r)return!!l&&(u=r.Dj())==(p=l.Dj())&&null!=u;if(a=(o=(t.d||(t.d=new O_(tgr,t,1)),t.d)).i,d=(n.d||(n.d=new O_(tgr,n,1)),n.d),a==d.i){for(c=0;c0,s=efC(t,a),n?Ag(s.b,t):Ag(s.g,t),1==efv(s).c.length&&qQ(r,s,r.c.b,r.c),i=new kD(a,t),Vw(e.o,i),QA(e.e.a,a))}function eNs(e,t){var n,r,i,a,o,s,u;return r=eB4.Math.abs(FB(e.b).a-FB(t.b).a),s=eB4.Math.abs(FB(e.b).b-FB(t.b).b),i=0,u=0,n=1,o=1,r>e.b.b/2+t.b.b/2&&(n=1-(i=eB4.Math.min(eB4.Math.abs(e.b.c-(t.b.c+t.b.b)),eB4.Math.abs(e.b.c+e.b.b-t.b.c)))/r),s>e.b.a/2+t.b.a/2&&(o=1-(u=eB4.Math.min(eB4.Math.abs(e.b.d-(t.b.d+t.b.a)),eB4.Math.abs(e.b.d+e.b.a-t.b.d)))/s),(1-(a=eB4.Math.min(n,o)))*eB4.Math.sqrt(r*r+s*s)}function eNu(e){var t,n,r,i;for(eFX(e,e.e,e.f,(zo(),tuq),!0,e.c,e.i),eFX(e,e.e,e.f,tuq,!1,e.c,e.i),eFX(e,e.e,e.f,tuZ,!0,e.c,e.i),eFX(e,e.e,e.f,tuZ,!1,e.c,e.i),eNd(e,e.c,e.e,e.f,e.i),r=new KB(e.i,0);r.b=65;n--)tvJ[n]=n-65<<24>>24;for(r=122;r>=97;r--)tvJ[r]=r-97+26<<24>>24;for(i=57;i>=48;i--)tvJ[i]=i-48+52<<24>>24;for(a=0,tvJ[43]=62,tvJ[47]=63;a<=25;a++)tvQ[a]=65+a&eHd;for(o=26,u=0;o<=51;++o,u++)tvQ[o]=97+u&eHd;for(e=52,s=0;e<=61;++e,s++)tvQ[e]=48+s&eHd;tvQ[62]=43,tvQ[63]=47}function eNf(e,t){var n,r,i,a,o,s,u,c,l,f,d,h;if(e.dc())return new yb;for(c=0,f=0,i=e.Kc();i.Ob();)a=(r=Pp(i.Pb(),37)).f,c=eB4.Math.max(c,a.a),f+=a.a*a.b;for(c=eB4.Math.max(c,eB4.Math.sqrt(f)*gP(LV(e_k(Pp(e.Kc().Pb(),37),(eBy(),tiX))))),d=0,h=0,u=0,n=t,s=e.Kc();s.Ob();)d+(l=(o=Pp(s.Pb(),37)).f).a>c&&(d=0,h+=u+t,u=0),eIn(o,d,h),n=eB4.Math.max(n,d+l.a),u=eB4.Math.max(u,l.b),d+=l.a+t;return new kl(n+t,h+u+t)}function eNd(e,t,n,r,i){var a,o,s,u,c,l,f;for(o=new fz(t);o.aa)return eYu(),tby;break;case 4:case 3:if(u<0)return eYu(),tbw;if(u+e.f>i)return eYu(),tbj}return(o=(s+e.g/2)/a)+(n=(u+e.f/2)/i)<=1&&o-n<=0?(eYu(),tbY):o+n>=1&&o-n>=0?(eYu(),tby):n<.5?(eYu(),tbw):(eYu(),tbj)}function eNp(e,t,n,r,i){var a,o;if(a=eft(WM(t[0],eH8),WM(r[0],eH8)),e[0]=jE(a),a=Fv(a,32),n>=i){for(o=1;o0&&(i.b[o++]=0,i.b[o++]=a.b[0]-1),t=1;t0&&(l0(u,u.d-i.d),i.c==(Xa(),tuU)&&lQ(u,u.a-i.d),u.d<=0&&u.i>0&&qQ(t,u,t.c.b,t.c));for(a=new fz(e.f);a.a0&&(l2(s,s.i-i.d),i.c==(Xa(),tuU)&&l1(s,s.b-i.d),s.i<=0&&s.d>0&&qQ(n,s,n.c.b,n.c))}function eNv(e,t,n){var r,i,a,o,s,u,c,l;for(ewG(n,"Processor compute fanout",1),Yy(e.b),Yy(e.a),s=null,a=epL(t.b,0);!s&&a.b!=a.d.c;)gN(LK(e_k(c=Pp(Vv(a),86),(eR6(),tcm))))&&(s=c);for(qQ(u=new _n,s,u.c.b,u.c),eYc(e,u),l=epL(t.b,0);l.b!=l.d.c;)o=Lq(e_k(c=Pp(Vv(l),86),(eR6(),tca))),eo3(c,tci,ell(i=null!=zg(e.b,o)?Pp(zg(e.b,o),19).a:0)),eo3(c,tcn,ell(r=1+(null!=zg(e.a,o)?Pp(zg(e.a,o),19).a:0)));eEj(n)}function eNy(e,t,n,r,i){var a,o,s,u,c,l,f,d,h,p;for(u=0,d=eyG(e,n);u0),r.a.Xb(r.c=--r.b),f>d+u&&BH(r);for(o=new fz(h);o.a0),r.a.Xb(r.c=--r.b)}}function eNw(){var e,t,n,r,i,a;if(eBG(),tyg)return tyg;for(e=(++tyv,new WZ(4)),ePR(e,eYB(e1_,!0)),ej0(e,eYB("M",!0)),ej0(e,eYB("C",!0)),a=(++tyv,new WZ(4)),r=0;r<11;r++)eLw(a,r,r);return t=(++tyv,new WZ(4)),ePR(t,eYB("M",!0)),eLw(t,4448,4607),eLw(t,65438,65439),i=(++tyv,new Mr(2)),eRv(i,e),eRv(i,tye),(n=(++tyv,new Mr(2))).$l(jS(a,eYB("L",!0))),n.$l(t),n=(++tyv,new qa(3,n)),tyg=n=(++tyv,new YD(i,n))}function eN_(e){var t,n;if(t=Lq(eT8(e,(eBB(),tdQ))),!eae(t,e)&&!X2(e,th6)&&(0!=(e.a||(e.a=new FQ(e6k,e,10,11)),e.a).i||gN(LK(eT8(e,thh))))){if(null==t||0==e_H(t).length){if(!eae(eG1,e))throw eFh(e,n=xM(xM(new O0("Unable to load default layout algorithm "),eG1)," for unconfigured node ")),p7(new gq(n.a))}else throw eFh(e,n=xM(xM(new O0("Layout algorithm '"),t),"' not found for ")),p7(new gq(n.a))}}function eNE(e){var t,n,r,i,a,o,s,u,c,l,f,d,h;if(n=e.i,t=e.n,0==e.b)for(h=n.c+t.b,d=n.b-t.b-t.c,o=e.a,u=0,l=o.length;u0&&(f-=r[0]+e.c,r[0]+=e.c),r[2]>0&&(f-=r[2]+e.c),r[1]=eB4.Math.max(r[1],f),jQ(e.a[1],n.c+t.b+r[0]-(r[1]-f)/2,r[1]);for(a=e.a,s=0,c=a.length;s0?(e.n.c.length-1)*e.i:0,r=new fz(e.n);r.a1)for(r=epL(i,0);r.b!=r.d.c;)for(n=Pp(Vv(r),231),a=0,u=new fz(n.e);u.a0&&(t[0]+=e.c,f-=t[0]),t[2]>0&&(f-=t[2]+e.c),t[1]=eB4.Math.max(t[1],f),j1(e.a[1],r.d+n.d+t[0]-(t[1]-f)/2,t[1]);else for(p=r.d+n.d,h=r.a-n.d-n.a,o=e.a,u=0,l=o.length;u=0&&a!=n)throw p7(new gL(eXB));for(u=0,i=0;u=efT(e.b.c,i.b.c+i.b.b)&&0>=efT(i.b.c,e.b.c+e.b.b)&&0>=efT(e.b.d,i.b.d+i.b.a)&&0>=efT(i.b.d,e.b.d+e.b.a)){if(0==efT(i.b.c,e.b.c+e.b.b)&&r.a<0||0==efT(i.b.c+i.b.b,e.b.c)&&r.a>0||0==efT(i.b.d,e.b.d+e.b.a)&&r.b<0||0==efT(i.b.d+i.b.a,e.b.d)&&r.b>0){s=0;break}}else s=eB4.Math.min(s,ekg(e,i,r));s=eB4.Math.min(s,eNC(e,a,s,r))}return s}function eNI(e,t){var n,r,i,a,o,s,u;if(e.b<2)throw p7(new gL("The vector chain must contain at least a source and a target point."));for(Tj(t,(i=(A6(0!=e.b),Pp(e.a.a.c,8))).a,i.b),u=new AF((t.a||(t.a=new O_(e6h,t,5)),t.a)),o=epL(e,1);o.agP(Ot(o.g,o.d[0]).a)?(A6(u.b>0),u.a.Xb(u.c=--u.b),CD(u,o),i=!0):s.e&&s.e.gc()>0&&(a=(s.e||(s.e=new p0),s.e).Mc(t),c=(s.e||(s.e=new p0),s.e).Mc(n),(a||c)&&((s.e||(s.e=new p0),s.e).Fc(o),++o.c));i||(r.c[r.c.length]=o)}function eNH(e){var t,n,r;if(TM(Pp(e_k(e,(eBy(),tol)),98)))for(n=new fz(e.j);n.a>>0).toString(16),n.length-2,n.length):e>=eH3?"\\v"+Az(n="0"+(t=e>>>0).toString(16),n.length-6,n.length):""+String.fromCharCode(e&eHd)}return r}function eNz(e,t){var n,r,i,a,o,s,u,c,l,f;if(o=e.e,0==(u=t.e))return e;if(0==o)return 0==t.e?t:new F7(-t.e,t.d,t.a);if((a=e.d)+(s=t.d)==2)return n=WM(e.a[0],eH8),r=WM(t.a[0],eH8),o<0&&(n=QC(n)),u<0&&(r=QC(r)),ep_(efe(n,r));if(-1==(i=a!=s?a>s?1:-1:es8(e.a,t.a,a)))f=-u,l=o==u?Z1(t.a,s,e.a,a):X7(t.a,s,e.a,a);else if(f=o,o==u){if(0==i)return eLQ(),e08;l=Z1(e.a,a,t.a,s)}else l=X7(e.a,a,t.a,s);return c=new F7(f,l.length,l),Ku(c),c}function eNG(e){var t,n,r,i,a,o;for(this.e=new p0,this.a=new p0,n=e.b-1;n<3;n++)Ls(e,0,Pp(ep3(e,0),8));if(e.b<4)throw p7(new gL("At (least dimension + 1) control points are necessary!"));for(this.b=3,this.d=!0,this.c=!1,eMO(this,e.b+this.b-1),o=new p0,a=new fz(this.e),t=0;t=t.o&&n.f<=t.f||.5*t.a<=n.f&&1.5*t.a>=n.f){if((o=Pp(RJ(t.n,t.n.c.length-1),211)).e+o.d+n.g+i<=r&&((a=Pp(RJ(t.n,t.n.c.length-1),211)).f-e.f+n.f<=e.b||1==e.a.c.length))return efg(t,n),!0;if(t.s+n.g<=r&&(t.t+t.d+n.f+i<=e.b||1==e.a.c.length))return P_(t.b,n),s=Pp(RJ(t.n,t.n.c.length-1),211),P_(t.n,new zO(t.s,s.f+s.a+t.i,t.i)),eml(Pp(RJ(t.n,t.n.c.length-1),211),n),eNk(t,n),!0}return!1}function eNV(e,t,n){var r,i,a,o;return e.ej()?(i=null,a=e.fj(),r=e.Zi(1,o=ees(e,t,n),n,t,a),e.bj()&&!(e.ni()&&null!=o?ecX(o,n):xc(o)===xc(n))?(null!=o&&(i=e.dj(o,i)),i=e.cj(n,i),e.ij()&&(i=e.lj(o,n,i)),i?(i.Ei(r),i.Fi()):e.$i(r)):(e.ij()&&(i=e.lj(o,n,i)),i?(i.Ei(r),i.Fi()):e.$i(r)),o):(o=ees(e,t,n),e.bj()&&!(e.ni()&&null!=o?ecX(o,n):xc(o)===xc(n))&&(i=null,null!=o&&(i=e.dj(o,null)),(i=e.cj(n,i))&&i.Fi()),o)}function eNq(e,t){var n,r,i,a,o,s,u,c;t%=24,e.q.getHours()!=t&&((r=new eB4.Date(e.q.getTime())).setDate(r.getDate()+1),(s=e.q.getTimezoneOffset()-r.getTimezoneOffset())>0&&(u=s/60|0,c=s%60,i=e.q.getDate(),(n=e.q.getHours())+u>=24&&++i,a=new eB4.Date(e.q.getFullYear(),e.q.getMonth(),i,t+u,e.q.getMinutes()+c,e.q.getSeconds(),e.q.getMilliseconds()),e.q.setTime(a.getTime()))),o=e.q.getTime(),e.q.setTime(o+36e5),e.q.getHours()!=t&&e.q.setTime(o)}function eNZ(e,t){var n,r,i,a,o;if(ewG(t,"Path-Like Graph Wrapping",1),0==e.b.c.length||(n=(o=(null==(i=new eTN(e)).i&&(i.i=eis(i,new iP)),gP(i.i)*i.f))/(null==i.i&&(i.i=eis(i,new iP)),gP(i.i)),i.b>n)){eEj(t);return}switch(Pp(e_k(e,(eBy(),toq)),337).g){case 2:a=new iF;break;case 0:a=new iO;break;default:a=new iY}if(r=a.Vf(e,i),!a.Wf())switch(Pp(e_k(e,to0),338).g){case 2:r=ekE(i,r);break;case 1:r=ewQ(i,r)}eRw(e,i,r),eEj(t)}function eNX(e,t){var n,r,i,a;if(GW(e.d,e.e),e.c.a.$b(),0!=gP(LV(e_k(t.j,(eBy(),ti3))))||0!=gP(LV(e_k(t.j,ti3))))for(n=ezq,xc(e_k(t.j,ti9))!==xc((esn(),tsM))&&eo3(t.j,(eBU(),tt2),(OQ(),!0)),a=Pp(e_k(t.j,to$),19).a,i=0;i(i=(GK(s+1,t.c.length),Pp(t.c[s+1],19)).a-r)&&++c,P_(o,(GK(s+c,t.c.length),Pp(t.c[s+c],19))),u+=(GK(s+c,t.c.length),Pp(t.c[s+c],19)).a-r,++n;n1&&(u>jl(s)*jc(s)/2||0==o.b)&&(f=new etD(d),l=jl(s)/jc(s),c=eY9(f,t,new mp,n,r,i,l),C5(xB(f.e),c),s=f,h.c[h.c.length]=f,u=0,d.c=Je(e1R,eUp,1,0,5,1)));return eoc(h,d),h}function eN2(e,t,n,r){var i,a,o,s,u,c,l,f,d,h,p,b;if(n.mh(t)&&(l=(h=t)?Pp(r,49).xh(h):null)){if(b=n.bh(t,e.a),(p=t.t)>1||-1==p){if(f=Pp(b,69),d=Pp(l,69),f.dc())d.$b();else for(o=!!ebY(t),a=0,s=e.a?f.Kc():f.Zh();s.Ob();)c=Pp(s.Pb(),56),(i=Pp(eef(e,c),56))?(o?-1==(u=d.Xc(i))?d.Xh(a,i):a!=u&&d.ji(a,i):d.Xh(a,i),++a):e.b&&!o&&(d.Xh(a,c),++a)}else null==b?l.Wb(null):null==(i=eef(e,b))?e.b&&!ebY(t)&&l.Wb(b):l.Wb(i)}}function eN3(e,t){var n,r,i,a,o,s,u,l;for(n=new nf,i=new Fa(OH(efu(t).a.Kc(),new c));eTk(i);)if(r=Pp(ZC(i),17),!q8(r)&&ewg(s=r.c.i,e8q)){if(-1==(l=eCu(e,s,e8q,e8V)))continue;n.b=eB4.Math.max(n.b,l),n.a||(n.a=new p0),P_(n.a,s)}for(o=new Fa(OH(efc(t).a.Kc(),new c));eTk(o);)if(a=Pp(ZC(o),17),!q8(a)&&ewg(u=a.d.i,e8V)){if(-1==(l=eCu(e,u,e8V,e8q)))continue;n.d=eB4.Math.max(n.d,l),n.c||(n.c=new p0),P_(n.c,u)}return n}function eN4(e){var t,n,r,i;if(exX(),t=zy(e),e1e6)throw p7(new g_("power of ten too big"));if(e<=eUu)return ZA(exT(e2t[1],t),t);for(i=r=exT(e2t[1],eUu),n=eap(e-eUu),t=zy(e%eUu);ecd(n,eUu)>0;)i=eeD(i,r),n=efe(n,eUu);for(i=eeD(i,exT(e2t[1],t)),i=ZA(i,eUu),n=eap(e-eUu);ecd(n,eUu)>0;)i=ZA(i,eUu),n=efe(n,eUu);return ZA(i,t)}function eN5(e,t){var n,r,i,a,o,s,u,c,l;for(ewG(t,"Hierarchical port dummy size processing",1),u=new p0,l=new p0,n=2*(r=gP(LV(e_k(e,(eBy(),toA))))),a=new fz(e.b);a.ac&&r>c)l=s,c=gP(t.p[s.p])+gP(t.d[s.p])+s.o.b+s.d.a;else{i=!1,n.n&&P3(n,"bk node placement breaks on "+s+" which should have been after "+l);break}if(!i)break}return n.n&&P3(n,t+" is feasible: "+i),i}function ePr(e,t,n,r){var i,a,o,s,u,c,l;for(s=-1,l=new fz(e);l.a=m&&e.e[u.p]>p*e.b||y>=n*m)&&(d.c[d.c.length]=s,s=new p0,er7(o,a),a.a.$b(),c-=l,h=eB4.Math.max(h,c*e.b+b),c+=y,v=y,y=0,l=0,b=0);return new kD(h,d)}function ePs(e){var t,n,r,i,a,o,s,u,c,l,f,d,h;for(n=(c=new fT(e.c.b).a.vc().Kc(),new fN(c));n.a.Ob();)null==(i=(t=(s=Pp(n.a.Pb(),42),Pp(s.dd(),149))).a)&&(i=""),(r=L8(e.c,i))||0!=i.length||(r=ecj(e)),r&&!eds(r.c,t,!1)&&P7(r.c,t);for(o=epL(e.a,0);o.b!=o.d.c;)a=Pp(Vv(o),478),l=Zc(e.c,a.a),h=Zc(e.c,a.b),l&&h&&P7(l.c,new kD(h,a.c));for(HC(e.a),d=epL(e.b,0);d.b!=d.d.c;)f=Pp(Vv(d),478),t=L9(e.c,f.a),u=Zc(e.c,f.b),t&&u&&_U(t,u,f.c);HC(e.b)}function ePu(e,t,n){var r,i,a,o,s,u,c,l,f,d,h;a=new lD(e),o=new eg6,i=(Ze(o.g),Ze(o.j),Yy(o.b),Ze(o.d),Ze(o.i),Yy(o.k),Yy(o.c),Yy(o.e),h=ekH(o,a,null),eMA(o,a),h),t&&(s=ePA(c=new lD(t)),eEh(i,eow(vx(e5q,1),eUp,527,0,[s]))),d=!1,f=!1,n&&(eXW in(c=new lD(n)).a&&(d=zR(c,eXW).ge().a),eXK in c.a&&(f=zR(c,eXK).ge().a)),l=yr(eny(new mV,d),f),eER(new or,i,l),eXW in a.a&&ee3(a,eXW,null),(d||f)&&(eNj(l,u=new gu,d,f),ee3(a,eXW,u)),r=new pp(o),esA(new TY(i),r)}function ePc(e,t,n){var r,i,a,o,s,u,c,l,f;for(u=0,o=new evI,c=eow(vx(ty_,1),eHT,25,15,[0]),i=-1,a=0,r=0;u0){if(i<0&&l.a&&(i=u,a=c[0],r=0),i>=0){if(s=l.b,u==i&&0==(s-=r++))return 0;if(!eYw(t,c,l,s,o)){u=i-1,c[0]=a;continue}}else if(i=-1,!eYw(t,c,l,0,o))return 0}else{if(i=-1,32==UI(l.c,0)){if(f=c[0],eey(t,c),c[0]>f)continue}else if($D(t,l.c,c[0])){c[0]+=l.c.length;continue}return 0}return eYn(o,n)?c[0]:0}function ePl(e){var t,n,r,i,a,o,s,u;if(!e.f){if(u=new su,s=new su,null==(o=(t=tgz).a.zc(e,t))){for(a=new Ow($E(e));a.e!=a.i.gc();)i=Pp(epH(a),26),Y4(u,ePl(i));t.a.Bc(e),t.a.gc()}for(r=(e.s||(e.s=new FQ(tm6,e,21,17)),new Ow(e.s));r.e!=r.i.gc();)n=Pp(epH(r),170),M4(n,99)&&JL(s,Pp(n,18));euI(s),e.r=new PX(e,(Pp(etj(H9((BM(),tgv).o),6),18),s.i),s.g),Y4(u,e.r),euI(u),e.f=new xQ((Pp(etj(H9(tgv.o),5),18),u.i),u.g),Zd(e).b&=-3}return e.f}function ePf(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p;for(c=0,r=Je(ty_,eHT,25,o=e.o,15,1),i=Je(ty_,eHT,25,o,15,1),t=Je(ty_,eHT,25,n=e.p,15,1),a=Je(ty_,eHT,25,n,15,1);c=0&&!emy(e,l,f);)--f;i[l]=f}for(h=0;h=0&&!emy(e,s,p);)--s;a[p]=s}for(u=0;ut[d]&&dr[u]&&eCQ(e,u,d,!1,!0)}function ePd(e){var t,n,r,i,a,o,s,u;n=gN(LK(e_k(e,(eCk(),e9b)))),a=e.a.c.d,s=e.a.d.d,n?(o=Ol(C6(new kl(s.a,s.b),a),.5),u=Ol(MB(e.e),.5),t=C6(C5(new kl(a.a,a.b),o),u),Lf(e.d,t)):(i=gP(LV(e_k(e.a,e9I))),r=e.d,a.a>=s.a?a.b>=s.b?(r.a=s.a+(a.a-s.a)/2+i,r.b=s.b+(a.b-s.b)/2-i-e.e.b):(r.a=s.a+(a.a-s.a)/2+i,r.b=a.b+(s.b-a.b)/2+i):a.b>=s.b?(r.a=a.a+(s.a-a.a)/2+i,r.b=s.b+(a.b-s.b)/2+i):(r.a=a.a+(s.a-a.a)/2+i,r.b=a.b+(s.b-a.b)/2-i-e.e.b))}function ePh(e,t){var n,r,i,a,o,s,u;if(null==e)return null;if(0==(a=e.length))return"";for(u=Je(tyw,eHl,25,a,15,1),Ji(0,a,e.length),Ji(0,a,u.length),YF(e,0,a,u,0),n=null,s=t,i=0,o=0;i0?Az(n.a,0,a-1):"":e.substr(0,a-1):n?n.a:e}function ePp(e){_Y(e,new ewB(vQ(vq(vJ(vX(new oc,ezH),"ELK DisCo"),"Layouter for arranging unconnected subgraphs. The subgraphs themselves are, by default, not laid out."),new e4))),KE(e,ezH,ez$,epB(e67)),KE(e,ezH,ezz,epB(e63)),KE(e,ezH,ezG,epB(e6J)),KE(e,ezH,ezW,epB(e64)),KE(e,ezH,e$Q,epB(e69)),KE(e,ezH,e$1,epB(e66)),KE(e,ezH,e$J,epB(e68)),KE(e,ezH,e$0,epB(e65)),KE(e,ezH,ezj,epB(e61)),KE(e,ezH,ezF,epB(e6Q)),KE(e,ezH,ezY,epB(e60)),KE(e,ezH,ezB,epB(e62))}function ePb(e,t,n,r){var i,a,o,s,u,c,l,f,d;if(a=new eb$(e),lK(a,(eEn(),e8P)),eo3(a,(eBy(),tol),(ewf(),tbo)),i=0,t){for(o=new eES,eo3(o,(eBU(),tnc),t),eo3(a,tnc,t.i),ekv(o,(eYu(),tbY)),Gc(o,a),d=Kp(t.e),l=0,f=(c=d).length;lenR(e)?1:0,n=e.e,i=(r.length,eB4.Math.abs(zy(e.e)),new vl),1==t&&(i.a+="-"),e.e>0){if((n-=r.length-t)>=0){for(i.a+="0.";n>e0Z.length;n-=e0Z.length)RX(i,e0Z);CA(i,e0Z,zy(n)),xM(i,r.substr(t))}else n=t-n,xM(i,Az(r,t,zy(n))),i.a+=".",xM(i,xy(r,zy(n)))}else{for(xM(i,r.substr(t));n<-e0Z.length;n+=e0Z.length)RX(i,e0Z);CA(i,e0Z,zy(-n))}return i.a}function ePv(e,t,n,r){var i,a,o,s,u,c,l,f,d;return(c=(u=C6(new kl(n.a,n.b),e)).a*t.b-u.b*t.a,l=t.a*r.b-t.b*r.a,f=(u.a*r.b-u.b*r.a)/l,d=c/l,0!=l)?f>=0&&f<=1&&d>=0&&d<=1?C5(new kl(e.a,e.b),Ol(new kl(t.a,t.b),f)):null:0!=c?null:(a=Jh(e,i=C5(new kl(n.a,n.b),Ol(new kl(r.a,r.b),.5))),o=Jh(C5(new kl(e.a,e.b),t),i),s=.5*eB4.Math.sqrt(r.a*r.a+r.b*r.b),at.a&&(r.Hc((eyY(),tdW))?e.c.a+=(n.a-t.a)/2:r.Hc(tdV)&&(e.c.a+=n.a-t.a)),n.b>t.b&&(r.Hc((eyY(),tdZ))?e.c.b+=(n.b-t.b)/2:r.Hc(tdq)&&(e.c.b+=n.b-t.b)),Pp(e_k(e,(eBU(),tt3)),21).Hc((eLR(),ttw))&&(n.a>t.a||n.b>t.b))for(s=new fz(e.a);s.at.a&&(r.Hc((eyY(),tdW))?e.c.a+=(n.a-t.a)/2:r.Hc(tdV)&&(e.c.a+=n.a-t.a)),n.b>t.b&&(r.Hc((eyY(),tdZ))?e.c.b+=(n.b-t.b)/2:r.Hc(tdq)&&(e.c.b+=n.b-t.b)),Pp(e_k(e,(eBU(),tt3)),21).Hc((eLR(),ttw))&&(n.a>t.a||n.b>t.b))for(o=new fz(e.a);o.at&&(i=0,a+=l.b+n,f.c[f.c.length]=l,l=new W6(a,n),r=new es$(0,l.f,l,n),enN(l,r),i=0),0==r.b.c.length||u.f>=r.o&&u.f<=r.f||.5*r.a<=u.f&&1.5*r.a>=u.f?efg(r,u):(o=new es$(r.s+r.r+n,l.f,l,n),enN(l,o),efg(o,u)),i=u.i+u.g;return f.c[f.c.length]=l,f}function ePk(e){var t,n,r,i,a,o,s,u;if(!e.a){if(e.o=null,u=new pj(e),t=new sc,null==(s=(n=tgz).a.zc(e,n))){for(o=new Ow($E(e));o.e!=o.i.gc();)a=Pp(epH(o),26),Y4(u,ePk(a));n.a.Bc(e),n.a.gc()}for(i=(e.s||(e.s=new FQ(tm6,e,21,17)),new Ow(e.s));i.e!=i.i.gc();)r=Pp(epH(i),170),M4(r,322)&&JL(t,Pp(r,34));euI(t),e.k=new PZ(e,(Pp(etj(H9((BM(),tgv).o),7),18),t.i),t.g),Y4(u,e.k),euI(u),e.a=new xQ((Pp(etj(H9(tgv.o),4),18),u.i),u.g),Zd(e).b&=-2}return e.a}function ePx(e,t,n,r,i,a,o){var s,u,c,l,f,d;return f=!1,u=eO4(n.q,t.f+t.b-n.q.f),!((d=i-(n.q.e+u-o))=(GK(a,e.c.length),Pp(e.c[a],200)).e,(!((l=(s=ePI(r,d,!1)).a)>t.b)||!!c)&&((c||l<=t.b)&&(c&&l>t.b?(n.d=l,JR(n,eEP(n,l))):(eyC(n.q,u),n.c=!0),JR(r,i-(n.s+n.r)),ebP(r,n.q.e+n.q.d,t.f),enN(t,r),e.c.length>a&&(eva((GK(a,e.c.length),Pp(e.c[a],200)),r),0==(GK(a,e.c.length),Pp(e.c[a],200)).a.c.length&&ZV(e,a)),f=!0),f))}function ePT(e,t,n,r){var i,a,o,s,u,c,l;if(l=eAY(e.e.Tg(),t),i=0,a=Pp(e.g,119),u=null,_4(),Pp(t,66).Oj()){for(s=0;se.o.a&&(l=(u-e.o.a)/2,s.b=eB4.Math.max(s.b,l),s.c=eB4.Math.max(s.c,l))}}function ePA(e){var t,n,r,i,a,o,s,u;for(a=new W8,Tp(a,(eoM(),tdr)),r=(i=erG(e,Je(e17,eUP,2,0,6,1)),new fE(new g$(new wY(e,i).b)));r.b0?e.i:0)>t&&u>0&&(a=0,o+=u+e.i,i=eB4.Math.max(i,d),r+=u+e.i,u=0,d=0,n&&(++f,P_(e.n,new zO(e.s,o,e.i))),s=0),d+=c.g+(s>0?e.i:0),u=eB4.Math.max(u,c.f),n&&eml(Pp(RJ(e.n,f),211),c),a+=c.g+(s>0?e.i:0),++s;return i=eB4.Math.max(i,d),r+=u,n&&(e.r=i,e.d=r,egf(e.j)),new Hr(e.s,e.t,i,r)}function ePD(e,t,n,r,i){var a,o,s,u,c,l,f,d,h;if(wK(),Yh(e,"src"),Yh(n,"dest"),d=esF(e),u=esF(n),Pz((4&d.i)!=0,"srcType is not an array"),Pz((4&u.i)!=0,"destType is not an array"),f=d.c,o=u.c,Pz((1&f.i)!=0?f==o:(1&o.i)==0,"Array types don't match"),h=e.length,c=n.length,t<0||r<0||i<0||t+i>h||r+i>c)throw p7(new bE);if((1&f.i)==0&&d!=u){if(l=etG(e),a=etG(n),xc(e)===xc(n)&&tr;)Bc(a,s,l[--t]);else for(s=r+i;r0&&ekp(e,t,n,r,i,!0)}function ePN(){ePN=A,e07=eow(vx(ty_,1),eHT,25,15,[eHt,1162261467,eU2,1220703125,362797056,1977326743,eU2,387420489,eHK,214358881,429981696,815730721,1475789056,170859375,268435456,410338673,612220032,893871739,128e7,1801088541,113379904,148035889,191102976,244140625,308915776,387420489,481890304,594823321,729e6,887503681,eU2,1291467969,1544804416,1838265625,60466176]),e2e=eow(vx(ty_,1),eHT,25,15,[-1,-1,31,19,15,13,11,11,10,9,9,8,8,8,8,7,7,7,7,7,7,7,6,6,6,6,6,6,6,6,6,6,6,6,6,6,5])}function ePP(e){var t,n,r,i,a,o,s,u;for(i=new fz(e.b);i.a=e.b.length?(a[i++]=o.b[r++],a[i++]=o.b[r++]):r>=o.b.length?(a[i++]=e.b[n++],a[i++]=e.b[n++]):o.b[r]0?e.i:0)),++t;for(efX(e.n,u),e.d=n,e.r=r,e.g=0,e.f=0,e.e=0,e.o=eHQ,e.p=eHQ,a=new fz(e.b);a.a0&&(i=(e.n||(e.n=new FQ(e6S,e,1,7)),Pp(etj(e.n,0),137)).a)&&xM(xM((t.a+=' "',t),i),'"')),(n=(e.b||(e.b=new Ih(e6m,e,4,7)),!(e.b.i<=1&&(e.c||(e.c=new Ih(e6m,e,5,8)),e.c.i<=1))))?(t.a+=" [",t):(t.a+=" ",t),xM(t,OU(new ve,new Ow(e.b))),n&&(t.a+="]"),t.a+=eGH,n&&(t.a+="["),xM(t,OU(new ve,new Ow(e.c))),n&&(t.a+="]"),t.a)}function ePB(e,t){var n,r,i,a,o,s,u;if(e.a){if(s=e.a.ne(),u=null,null!=s?t.a+=""+s:null!=(o=e.a.Dj())&&(-1!=(a=x7(o,e_n(91)))?(u=o.substr(a),t.a+=""+Az(null==o?eUg:(BJ(o),o),0,a)):t.a+=""+o),e.d&&0!=e.d.i){for(i=!0,t.a+="<",r=new Ow(e.d);r.e!=r.i.gc();)n=Pp(epH(r),87),i?i=!1:(t.a+=eUd,t),ePB(n,t);t.a+=">"}null!=u&&(t.a+=""+u)}else e.e?null!=(s=e.e.zb)&&(t.a+=""+s):(t.a+="?",e.b?(t.a+=" super ",ePB(e.b,t)):e.f&&(t.a+=" extends ",ePB(e.f,t)))}function ePU(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T;for(_=e.c,E=t.c,n=QI(_.a,e,0),r=QI(E.a,t,0),y=Pp(edE(e,(enY(),tsD)).Kc().Pb(),11),x=Pp(edE(e,tsN).Kc().Pb(),11),w=Pp(edE(t,tsD).Kc().Pb(),11),T=Pp(edE(t,tsN).Kc().Pb(),11),g=Kp(y.e),S=Kp(x.g),v=Kp(w.e),k=Kp(T.g),egU(e,r,E),l=0,p=(o=v).length;ll?new GT((Xa(),tuH),n,t,c-l):c>0&&l>0&&(new GT((Xa(),tuH),t,n,0),new GT(tuH,n,t,0))),o)}function ePz(e,t){var n,r,i,a,o,s;for(o=new esz(new fS(e.f.b).a);o.b;){if(a=etz(o),i=Pp(a.cd(),594),1==t){if(i.gf()!=(ec3(),tpy)&&i.gf()!=tpb)continue}else if(i.gf()!=(ec3(),tpm)&&i.gf()!=tpg)continue;switch(r=Pp(Pp(a.dd(),46).b,81),n=(s=Pp(Pp(a.dd(),46).a,189)).c,i.gf().g){case 2:r.g.c=e.e.a,r.g.b=eB4.Math.max(1,r.g.b+n);break;case 1:r.g.c=r.g.c+n,r.g.b=eB4.Math.max(1,r.g.b-n);break;case 4:r.g.d=e.e.b,r.g.a=eB4.Math.max(1,r.g.a+n);break;case 3:r.g.d=r.g.d+n,r.g.a=eB4.Math.max(1,r.g.a-n)}}}function ePG(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b;for(s=Je(ty_,eHT,25,t.b.c.length,15,1),c=Je(e4P,eU4,267,t.b.c.length,0,1),u=Je(e4N,eGW,10,t.b.c.length,0,1),f=e.a,d=0,h=f.length;d0&&u[r]&&(p=Mj(e.b,u[r],i)),b=eB4.Math.max(b,i.c.c.b+p);for(a=new fz(l.e);a.a1)throw p7(new gL(eQ$));u||(a=V4(t,r.Kc().Pb()),o.Fc(a))}return eo0(e,eSu(e,t,n),o)}function ePZ(e,t){var n,r,i,a;for(etY(t.b.j),_r(UQ(new R1(null,new Gq(t.d,16)),new iy),new iw),a=new fz(t.d);a.ae.o.b||(n=efr(e,tby),(s=t.d+t.a+(n.gc()-1)*o)>e.o.b)))}function eP5(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p;if(o=e.e,u=t.e,0==o)return t;if(0==u)return e;if((a=e.d)+(s=t.d)==2)return(n=WM(e.a[0],eH8),r=WM(t.a[0],eH8),o==u)?(p=jE(l=eft(n,r)),0==(h=jE(Fy(l,32)))?new XE(o,p):new F7(o,2,eow(vx(ty_,1),eHT,25,15,[p,h]))):ep_(o<0?efe(r,n):efe(n,r));if(o==u)d=o,f=a>=s?X7(e.a,a,t.a,s):X7(t.a,s,e.a,a);else{if(0==(i=a!=s?a>s?1:-1:es8(e.a,t.a,a)))return eLQ(),e08;1==i?(d=o,f=Z1(e.a,a,t.a,s)):(d=u,f=Z1(t.a,s,e.a,a))}return c=new F7(d,f.length,f),Ku(c),c}function eP6(e,t,n,r,i,a,o){var s,u,c,l,f,d,h;return f=gN(LK(e_k(t,(eBy(),taV)))),d=null,a==(enY(),tsD)&&r.c.i==n?d=r.c:a==tsN&&r.d.i==n&&(d=r.d),(c=o)&&f&&!d?(P_(c.e,r),h=eB4.Math.max(gP(LV(e_k(c.d,tak))),gP(LV(e_k(r,tak)))),eo3(c.d,tak,h)):(l=(eYu(),tbF),d?l=d.j:TM(Pp(e_k(n,tol),98))&&(l=a==tsD?tbY:tby),u=eP8(e,t,n,a,l,r),s=ZD((Bq(n),r)),a==tsD?(Gs(s,Pp(RJ(u.j,0),11)),Go(s,i)):(Gs(s,i),Go(s,Pp(RJ(u.j,0),11))),c=new ec8(r,s,u,Pp(e_k(u,(eBU(),tnc)),11),a,!d)),exg(e.a,r,new DT(c.d,t,a)),c}function eP9(e,t){var n,r,i,a,o,s,u,c,l,f;if(l=null,e.d&&(l=Pp(zg(e.d,t),138)),!l){if(f=(a=e.a.Mh()).i,!e.d||wq(e.d)!=f){for(u=new p2,e.d&&eij(u,e.d),s=c=u.f.c+u.g.c;s0?(h=(p-1)*n,s&&(h+=r),l&&(h+=r),!(h=e.b[i+1])i+=2;else if(n0)for(r=new I4(Pp(Zq(e.a,a),21)),Hj(),Mv(r,new dT(t)),i=new KB(a.b,0);i.b_)?(u=2,o=eUu):0==u?(u=1,o=S):(u=0,o=S):(h=S>=o||o-S0?1:Te(isNaN(r),isNaN(0)))>=0^(enj(eVU),(eB4.Math.abs(s)<=eVU||0==s||isNaN(s)&&isNaN(0)?0:s<0?-1:s>0?1:Te(isNaN(s),isNaN(0)))>=0))?eB4.Math.max(s,r):(enj(eVU),(eB4.Math.abs(r)<=eVU||0==r||isNaN(r)&&isNaN(0)?0:r<0?-1:r>0?1:Te(isNaN(r),isNaN(0)))>0)?eB4.Math.sqrt(s*s+r*r):-eB4.Math.sqrt(s*s+r*r)}function eRv(e,t){var n,r,i,a,o,s;if(t){if(e.a||(e.a=new bZ),2==e.e){bY(e.a,t);return}if(1==t.e){for(i=0;i=eH3?xk(n,el1(r)):Bf(n,r&eHd),o=(++tyv,new zc(10,null,0)),Yu(e.a,o,s-1)):xk(n=(o.bm().length,new vu),o.bm()),0==t.e?(r=t._l())>=eH3?xk(n,el1(r)):Bf(n,r&eHd):xk(n,t.bm()),Pp(o,521).b=n.a}}function eRy(e){var t,n,r,i,a;return null!=e.g?e.g:e.a<32?(e.g=eYS(eap(e.f),zy(e.e)),e.g):(i=eBw((e.c||(e.c=euK(e.f)),e.c),0),0==e.e)?i:(t=(e.c||(e.c=euK(e.f)),e.c).e<0?2:1,n=i.length,r=-e.e+n-t,a=new vc,a.a+=""+i,e.e>0&&r>=-6?r>=0?Gn(a,n-zy(e.e),"."):(a.a=Az(a.a,0,t-1)+"0."+xy(a.a,t-1),Gn(a,t+1,ehv(e0Z,0,-zy(r)-1))):(n-t>=1&&(Gn(a,t,"."),++n),Gn(a,n,"E"),r>0&&Gn(a,++n,"+"),Gn(a,++n,""+Fb(eap(r)))),e.g=a.a,e.g)}function eRw(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b,m;if(!n.dc()){for(s=0,d=0,p=Pp((r=n.Kc()).Pb(),19).a;s1&&(u=c.mg(u,e.a,s));return 1==u.c.length?Pp(RJ(u,u.c.length-1),220):2==u.c.length?eRr((GK(0,u.c.length),Pp(u.c[0],220)),(GK(1,u.c.length),Pp(u.c[1],220)),o,a):null}function eRk(e){var t,n,r,i,a,o;for(ety(e.a,new eJ),n=new fz(e.a);n.a=eB4.Math.abs(r.b)?(r.b=0,a.d+a.a>o.d&&a.do.c&&a.c0){if(t=new xt(e.i,e.g),a=(n=e.i)<100?null:new yf(n),e.ij())for(r=0;r0){for(s=e.g,c=e.i,ZG(e),a=c<100?null:new yf(c),r=0;r>13|(15&e.m)<<9,i=e.m>>4&8191,a=e.m>>17|(255&e.h)<<5,o=(1048320&e.h)>>8,s=8191&t.l,u=t.l>>13|(15&t.m)<<9,c=t.m>>4&8191,l=t.m>>17|(255&t.h)<<5,f=(1048320&t.h)>>8,k=n*s,x=r*s,T=i*s,M=a*s,O=o*s,0!=u&&(x+=n*u,T+=r*u,M+=i*u,O+=a*u),0!=c&&(T+=n*c,M+=r*c,O+=i*c),0!=l&&(M+=n*l,O+=r*l),0!=f&&(O+=n*f),d=(h=k&eHH)+(p=(511&x)<<13),m=k>>22,g=x>>9,b=m+g+(v=(262143&T)<<4)+(y=(31&M)<<17),_=T>>18,w=_+(E=M>>5)+(S=(4095&O)<<8),b+=d>>22,d&=eHH,w+=b>>22,Mk(d,b&=eHH,w&=eH$)}function eRA(e){var t,n,r,i,a,o,s;if(0!=(s=Pp(RJ(e.j,0),11)).g.c.length&&0!=s.e.c.length)throw p7(new gC("Interactive layout does not support NORTH/SOUTH ports with incoming _and_ outgoing edges."));if(0!=s.g.c.length){for(a=eHQ,n=new fz(s.g);n.a4){if(!e.wj(t))return!1;if(e.rk()){if(u=(r=(i=Pp(t,49)).Ug())==e.e&&(e.Dk()?i.Og(i.Vg(),e.zk())==e.Ak():-1-i.Vg()==e.aj()),e.Ek()&&!u&&!r&&i.Zg()){for(a=0;a0&&(c=e.n.a/a);break;case 2:case 4:(i=e.i.o.b)>0&&(c=e.n.b/i)}eo3(e,(eBU(),tnv),c)}if(u=e.o,o=e.a,r)o.a=r.a,o.b=r.b,e.d=!0;else if(t!=tbc&&t!=tbl&&s!=tbF)switch(s.g){case 1:o.a=u.a/2;break;case 2:o.a=u.a,o.b=u.b/2;break;case 3:o.a=u.a/2,o.b=u.b;break;case 4:o.b=u.b/2}else o.a=u.a/2,o.b=u.b/2}function eRP(e){var t,n,r,i,a,o,s,u,c,l;if(e.ej()){if(l=e.Vi(),u=e.fj(),l>0){if(t=new eiP(e.Gi()),a=(n=l)<100?null:new yf(n),Cf(e,n,t.g),i=1==n?e.Zi(4,etj(t,0),null,0,u):e.Zi(6,t,null,-1,u),e.bj()){for(r=new Ow(t);r.e!=r.i.gc();)a=e.dj(epH(r),a);a?(a.Ei(i),a.Fi()):e.$i(i)}else a?(a.Ei(i),a.Fi()):e.$i(i)}else Cf(e,e.Vi(),e.Wi()),e.$i(e.Zi(6,(Hj(),e2r),null,-1,u))}else if(e.bj()){if((l=e.Vi())>0){for(s=e.Wi(),c=l,Cf(e,l,s),a=c<100?null:new yf(c),r=0;re.d[o.p]&&(n+=qq(e.b,a)*Pp(u.b,19).a,Vw(e.a,ell(a)));for(;!gY(e.a);)eek(e.b,Pp(Yn(e.a),19).a)}return n}function eRF(e,t,n,r){var i,a,o,s,u,c,l,f,d,h,p,b,m;for((f=new TS(Pp(eT8(e,(e_C(),tdB)),8))).a=eB4.Math.max(f.a-n.b-n.c,0),f.b=eB4.Math.max(f.b-n.d-n.a,0),(null==(i=LV(eT8(e,tdN)))||(BJ(i),i<=0))&&(i=1.3),s=new p0,p=new Ow((e.a||(e.a=new FQ(e6k,e,10,11)),e.a));p.e!=p.i.gc();)h=Pp(epH(p),33),o=new Lp(h),s.c[s.c.length]=o;switch((d=Pp(eT8(e,tdP),311)).g){case 3:m=eDQ(s,t,f.a,f.b,(c=r,BJ(i),c));break;case 1:m=eN0(s,t,f.a,f.b,(l=r,BJ(i),l));break;default:m=eRH(s,t,f.a,f.b,(u=r,BJ(i),u))}a=new etD(m),b=eY9(a,t,n,f.a,f.b,r,(BJ(i),i)),eYx(e,b.a,b.b,!1,!0)}function eRY(e,t){var n,r,i,a;n=t.b,a=new I4(n.j),i=0,(r=n.j).c=Je(e1R,eUp,1,0,5,1),Y$(Pp(eay(e.b,(eYu(),tbw),(erX(),tep)),15),n),i=emQ(a,i,new r3,r),Y$(Pp(eay(e.b,tbw,teh),15),n),i=emQ(a,i,new r2,r),Y$(Pp(eay(e.b,tbw,ted),15),n),Y$(Pp(eay(e.b,tby,tep),15),n),Y$(Pp(eay(e.b,tby,teh),15),n),i=emQ(a,i,new r4,r),Y$(Pp(eay(e.b,tby,ted),15),n),Y$(Pp(eay(e.b,tbj,tep),15),n),i=emQ(a,i,new r5,r),Y$(Pp(eay(e.b,tbj,teh),15),n),i=emQ(a,i,new r6,r),Y$(Pp(eay(e.b,tbj,ted),15),n),Y$(Pp(eay(e.b,tbY,tep),15),n),i=emQ(a,i,new ic,r),Y$(Pp(eay(e.b,tbY,teh),15),n),Y$(Pp(eay(e.b,tbY,ted),15),n)}function eRB(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b;for(ewG(t,"Layer size calculation",1),l=eHQ,c=eH1,i=!1,s=new fz(e.b);s.a.5?g-=2*o*(p-.5):p<.5&&(g+=2*a*(.5-p)),g<(i=s.d.b)&&(g=i),b=s.d.c,g>m.a-b-l&&(g=m.a-b-l),s.n.a=t+g}}function eRH(e,t,n,r,i){var a,o,s,u,c,l,f,d,h,p,b,m;for(s=Je(tyx,eH5,25,e.c.length,15,1),d=new Fz(new oB),egV(d,e),c=0,b=new p0;0!=d.b.c.length;)if(o=Pp(0==d.b.c.length?null:RJ(d.b,0),157),c>1&&jl(o)*jc(o)/2>s[0]){for(a=0;as[a];)++a;p=new Gz(b,0,a+1),f=new etD(p),l=jl(o)/jc(o),u=eY9(f,t,new mp,n,r,i,l),C5(xB(f.e),u),Ja(e_s(d,f)),egV(d,h=new Gz(b,a+1,b.c.length)),b.c=Je(e1R,eUp,1,0,5,1),c=0,jA(s,s.length,0)}else null!=(m=0==d.b.c.length?null:RJ(d.b,0))&&erD(d,0),c>0&&(s[c]=s[c-1]),s[c]+=jl(o)*jc(o),++c,b.c[b.c.length]=o;return b}function eR$(e){var t,n,r,i,a;if((r=Pp(e_k(e,(eBy(),taY)),163))==(ef_(),tnN)){for(n=new Fa(OH(efu(e).a.Kc(),new c));eTk(n);)if(t=Pp(ZC(n),17),!ZI(t))throw p7(new gq(eWr+egs(e)+"' has its layer constraint set to FIRST_SEPARATE, but has at least one incoming edge. FIRST_SEPARATE nodes must not have incoming edges."))}else if(r==tnR){for(a=new Fa(OH(efc(e).a.Kc(),new c));eTk(a);)if(i=Pp(ZC(a),17),!ZI(i))throw p7(new gq(eWr+egs(e)+"' has its layer constraint set to LAST_SEPARATE, but has at least one outgoing edge. LAST_SEPARATE nodes must not have outgoing edges."))}}function eRz(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p;for(ewG(t,"Label dummy removal",1),r=gP(LV(e_k(e,(eBy(),toL)))),i=gP(LV(e_k(e,toN))),c=Pp(e_k(e,tal),103),u=new fz(e.b);u.a0&&eE9(e,s,f);for(i=new fz(f);i.a>19!=0&&(t=eoQ(t),u=!u),o=eOy(t),a=!1,i=!1,r=!1,e.h==eHz&&0==e.m&&0==e.l){if(i=!0,a=!0,-1!=o)return s=eTC(e,o),u&&esh(s),n&&(e0A=Mk(0,0,0)),s;e=Tr((Q2(),e0L)),r=!0,u=!u}else e.h>>19!=0&&(a=!0,e=eoQ(e),r=!0,u=!u);return -1!=o?esk(e,o,u,a,n):0>evy(e,t)?(n&&(e0A=a?eoQ(e):Mk(e.l,e.m,e.h)),Mk(0,0,0)):eDr(r?e:Mk(e.l,e.m,e.h),t,u,a,i,n)}function eRq(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p;if(e.e&&e.c.ct.f)&&!(t.g>e.f)){for(n=0,r=0,o=e.w.a.ec().Kc();o.Ob();)i=Pp(o.Pb(),11),euz(esp(eow(vx(e50,1),eUP,8,0,[i.i.n,i.n,i.a])).b,t.g,t.f)&&++n;for(s=e.r.a.ec().Kc();s.Ob();)i=Pp(s.Pb(),11),euz(esp(eow(vx(e50,1),eUP,8,0,[i.i.n,i.n,i.a])).b,t.g,t.f)&&--n;for(u=t.w.a.ec().Kc();u.Ob();)i=Pp(u.Pb(),11),euz(esp(eow(vx(e50,1),eUP,8,0,[i.i.n,i.n,i.a])).b,e.g,e.f)&&++r;for(a=t.r.a.ec().Kc();a.Ob();)i=Pp(a.Pb(),11),euz(esp(eow(vx(e50,1),eUP,8,0,[i.i.n,i.n,i.a])).b,e.g,e.f)&&--r;n=0)return i=efd(e,t.substr(1,o-1)),eYF(e,l=t.substr(o+1,u-(o+1)),i)}else{if(n=-1,null==e0F&&(e0F=RegExp("\\d")),e0F.test(String.fromCharCode(s))&&(n=IO(t,e_n(46),u-1))>=0){r=Pp(ZN(e,etm(e,t.substr(1,n-1)),!1),58),c=0;try{c=eDa(t.substr(n+1),eHt,eUu)}catch(d){if(d=eoa(d),M4(d,127))throw a=d,p7(new QH(a));throw p7(d)}if(c=0)return n;switch(Ur(QZ(e,n))){case 2:if(IE("",ecG(e,n.Hj()).ne())){if(u=U$(QZ(e,n)),s=UH(QZ(e,n)),l=eMv(e,t,u,s))return l;for(o=0,f=(i=eIx(e,t)).gc();o1)throw p7(new gL(eQ$));for(o=0,l=eAY(e.e.Tg(),t),r=Pp(e.g,119);o1,c=new Z4(d.b);My(c.a)||My(c.b);)f=(u=Pp(My(c.a)?Wx(c.a):Wx(c.b),17)).c==d?u.d:u.c,eB4.Math.abs(esp(eow(vx(e50,1),eUP,8,0,[f.i.n,f.n,f.a])).b-o.b)>1&&eAZ(e,u,o,a,d)}}function eR8(e){var t,n,r,i,a,o;if(i=new KB(e.e,0),r=new KB(e.a,0),e.d)for(n=0;neVW;){for(a=t,o=0;eB4.Math.abs(t-a)0),i.a.Xb(i.c=--i.b),eNy(e,e.b-o,a,r,i),A6(i.b0),r.a.Xb(r.c=--r.b)}if(!e.d)for(n=0;n0?(e.f[l.p]=h/(l.e.c.length+l.g.c.length),e.c=eB4.Math.min(e.c,e.f[l.p]),e.b=eB4.Math.max(e.b,e.f[l.p])):s&&(e.f[l.p]=h)}}function ejt(e){e.b=null,e.bb=null,e.fb=null,e.qb=null,e.a=null,e.c=null,e.d=null,e.e=null,e.f=null,e.n=null,e.M=null,e.L=null,e.Q=null,e.R=null,e.K=null,e.db=null,e.eb=null,e.g=null,e.i=null,e.j=null,e.k=null,e.gb=null,e.o=null,e.p=null,e.q=null,e.r=null,e.$=null,e.ib=null,e.S=null,e.T=null,e.t=null,e.s=null,e.u=null,e.v=null,e.w=null,e.B=null,e.A=null,e.C=null,e.D=null,e.F=null,e.G=null,e.H=null,e.I=null,e.J=null,e.P=null,e.Z=null,e.U=null,e.V=null,e.W=null,e.X=null,e.Y=null,e._=null,e.ab=null,e.cb=null,e.hb=null,e.nb=null,e.lb=null,e.mb=null,e.ob=null,e.pb=null,e.jb=null,e.kb=null,e.N=!1,e.O=!1}function ejn(e,t,n){var r,i,a,o;for(ewG(n,"Graph transformation ("+e.a+")",1),o=WC(t.a),a=new fz(t.b);a.a0&&(e.a=u+(p-1)*a,t.c.b+=e.a,t.f.b+=e.a),0!=b.a.gc()&&(p=ejF(h=new YJ(1,a),t,b,m,t.f.b+u-t.c.b))>0&&(t.f.b+=u+(p-1)*a)}function eji(e,t){var n,r,i,a;a=e.F,null==t?(e.F=null,euc(e,null)):(e.F=(BJ(t),t),-1!=(r=x7(t,e_n(60)))?(i=t.substr(0,r),-1!=x7(t,e_n(46))||IE(i,eUi)||IE(i,eJZ)||IE(i,eJX)||IE(i,eJJ)||IE(i,eJQ)||IE(i,eJ1)||IE(i,eJ0)||IE(i,eJ2)||(i=eJ3),-1!=(n=O8(t,e_n(62)))&&(i+=""+t.substr(n+1)),euc(e,i)):(i=t,-1==x7(t,e_n(46))&&(-1!=(r=x7(t,e_n(91)))&&(i=t.substr(0,r)),IE(i,eUi)||IE(i,eJZ)||IE(i,eJX)||IE(i,eJJ)||IE(i,eJQ)||IE(i,eJ1)||IE(i,eJ0)||IE(i,eJ2)?i=t:(i=eJ3,-1!=r&&(i+=""+t.substr(r)))),euc(e,i),i==t&&(e.F=e.D))),(4&e.Db)!=0&&(1&e.Db)==0&&eam(e,new FX(e,1,5,a,t))}function eja(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y;if(!((b=t.b.c.length)<3)){for(h=Je(ty_,eHT,25,b,15,1),f=0,l=new fz(t.b);l.ao)&&Yf(e.b,Pp(m.b,17));++s}a=o}}}function ejo(e,t){var n;if(null==t||IE(t,eUg)||0==t.length&&e.k!=(eSd(),tdy))return null;switch(e.k.g){case 1:return ehZ(t,eq6)?(OQ(),e0P):ehZ(t,eq9)?(OQ(),e0N):null;case 2:try{return ell(eDa(t,eHt,eUu))}catch(r){if(r=eoa(r),M4(r,127))return null;throw p7(r)}case 4:try{return eEu(t)}catch(i){if(i=eoa(i),M4(i,127))return null;throw p7(i)}case 3:return t;case 5:return euC(e),exs(e,t);case 6:return euC(e),eMj(e,e.a,t);case 7:try{return(n=eTh(e)).Jf(t),n}catch(a){if(a=eoa(a),M4(a,32))return null;throw p7(a)}default:throw p7(new gC("Invalid type set for this layout option."))}}function ejs(e){var t,n,r,i,a,o,s;for(eeP(),s=new b6,n=new fz(e);n.a=s.b.c)&&(s.b=t),(!s.c||t.c<=s.c.c)&&(s.d=s.c,s.c=t),(!s.e||t.d>=s.e.d)&&(s.e=t),(!s.f||t.d<=s.f.d)&&(s.f=t);return r=new epG((eok(),e8f)),Kv(e,e8y,new g$(eow(vx(e4M,1),eUp,369,0,[r]))),o=new epG(e8p),Kv(e,e8v,new g$(eow(vx(e4M,1),eUp,369,0,[o]))),i=new epG(e8d),Kv(e,e8g,new g$(eow(vx(e4M,1),eUp,369,0,[i]))),a=new epG(e8h),Kv(e,e8m,new g$(eow(vx(e4M,1),eUp,369,0,[a]))),eOk(r.c,e8f),eOk(i.c,e8d),eOk(a.c,e8h),eOk(o.c,e8p),s.a.c=Je(e1R,eUp,1,0,5,1),eoc(s.a,r.c),eoc(s.a,eaa(i.c)),eoc(s.a,a.c),eoc(s.a,eaa(o.c)),s}function eju(e){var t;switch(e.d){case 1:if(e.hj())return -2!=e.o;break;case 2:if(e.hj())return -2==e.o;break;case 3:case 5:case 4:case 6:case 7:return e.o>-2;default:return!1}switch(t=e.gj(),e.p){case 0:return null!=t&&gN(LK(t))!=xg(e.k,0);case 1:return null!=t&&Pp(t,217).a!=jE(e.k)<<24>>24;case 2:return null!=t&&Pp(t,172).a!=(jE(e.k)&eHd);case 6:return null!=t&&xg(Pp(t,162).a,e.k);case 5:return null!=t&&Pp(t,19).a!=jE(e.k);case 7:return null!=t&&Pp(t,184).a!=jE(e.k)<<16>>16;case 3:return null!=t&&gP(LV(t))!=e.j;case 4:return null!=t&&Pp(t,155).a!=e.j;default:return null==t?null!=e.n:!ecX(t,e.n)}}function ejc(e,t,n){var r,i,a,o;return e.Fk()&&e.Ek()&&(o=FU(e,Pp(n,56)),xc(o)!==xc(n))?(e.Oi(t),e.Ui(t,J6(e,t,o)),e.rk()&&(a=(i=Pp(n,49),e.Dk()?e.Bk()?i.ih(e.b,ebY(Pp(ee2($S(e.b),e.aj()),18)).n,Pp(ee2($S(e.b),e.aj()).Yj(),26).Bj(),null):i.ih(e.b,edv(i.Tg(),ebY(Pp(ee2($S(e.b),e.aj()),18))),null,null):i.ih(e.b,-1-e.aj(),null,null)),Pp(o,49).eh()||(a=(r=Pp(o,49),e.Dk()?e.Bk()?r.gh(e.b,ebY(Pp(ee2($S(e.b),e.aj()),18)).n,Pp(ee2($S(e.b),e.aj()).Yj(),26).Bj(),a):r.gh(e.b,edv(r.Tg(),ebY(Pp(ee2($S(e.b),e.aj()),18))),null,a):r.gh(e.b,-1-e.aj(),null,a))),a&&a.Fi()),TO(e.b)&&e.$i(e.Zi(9,n,o,t,!1)),o):n}function ejl(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w;for(l=gP(LV(e_k(e,(eBy(),toC)))),r=gP(LV(e_k(e,toG))),eo3(d=new oG,toC,l+r),g=(c=t).d,b=c.c.i,v=c.d.i,m=Tl(b.c),y=Tl(v.c),i=new p0,f=m;f<=y;f++)s=new eb$(e),lK(s,(eEn(),e8D)),eo3(s,(eBU(),tnc),c),eo3(s,tol,(ewf(),tbo)),eo3(s,toD,d),h=Pp(RJ(e.b,f),29),f==m?egU(s,h.a.c.length-n,h):Gu(s,h),(w=gP(LV(e_k(c,tak))))<0&&eo3(c,tak,w=0),s.o.b=w,p=eB4.Math.floor(w/2),o=new eES,ekv(o,(eYu(),tbY)),Gc(o,s),o.n.b=p,u=new eES,ekv(u,tby),Gc(u,s),u.n.b=p,Go(c,o),a=new $b,eaW(a,c),eo3(a,taR,null),Gs(a,u),Go(a,g),evT(s,c,a),i.c[i.c.length]=a,c=a;return i}function ejf(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y;for(u=Pp(eEC(e,(eYu(),tbY)).Kc().Pb(),11).e,h=Pp(eEC(e,tby).Kc().Pb(),11).g,s=u.c.length,y=GX(Pp(RJ(e.j,0),11));s-- >0;){for(b=(GK(0,u.c.length),Pp(u.c[0],17)),a=QI(v=(i=(GK(0,h.c.length),Pp(h.c[0],17))).d.e,i,0),KW(b,i.d,a),Gs(i,null),Go(i,null),p=b.a,t&&P7(p,new TS(y)),r=epL(i.a,0);r.b!=r.d.c;)n=Pp(Vv(r),8),P7(p,new TS(n));for(g=b.b,d=new fz(i.b);d.a0&&(o=eB4.Math.max(o,eix(e.C.b+r.d.b,i))),l=r,f=i,d=a;e.C&&e.C.c>0&&(h=d+e.C.c,c&&(h+=l.d.c),o=eB4.Math.max(o,(Mc(),enj(ezs),eB4.Math.abs(f-1)<=ezs||1==f||isNaN(f)&&isNaN(1)?0:h/(1-f)))),n.n.b=0,n.a.a=o}function ejh(e,t){var n,r,i,a,o,s,u,c,l,f,d,h;if(n=Pp(UA(e.b,t),124),(u=Pp(Pp(Zq(e.r,t),21),84)).dc()){n.n.d=0,n.n.a=0;return}for(c=e.u.Hc((ekU(),tbp)),o=0,e.A.Hc((ed6(),tbq))&&eCN(e,t),s=u.Kc(),l=null,d=0,f=0;s.Ob();)a=gP(LV((r=Pp(s.Pb(),111)).b.We((Ab(),e4a)))),i=r.b.rf().b,l?(h=f+l.d.a+e.w+r.d.d,o=eB4.Math.max(o,(Mc(),enj(ezs),eB4.Math.abs(d-a)<=ezs||d==a||isNaN(d)&&isNaN(a)?0:h/(a-d)))):e.C&&e.C.d>0&&(o=eB4.Math.max(o,eix(e.C.d+r.d.d,a))),l=r,d=a,f=i;e.C&&e.C.a>0&&(h=f+e.C.a,c&&(h+=l.d.a),o=eB4.Math.max(o,(Mc(),enj(ezs),eB4.Math.abs(d-1)<=ezs||1==d||isNaN(d)&&isNaN(1)?0:h/(1-d)))),n.n.d=0,n.a.b=o}function ejp(e,t,n){var r,i,a,o,s,u;for(o=0,this.g=e,s=t.d.length,u=n.d.length,this.d=Je(e4N,eGW,10,s+u,0,1);o0?etU(this,this.f/this.a):null!=Ot(t.g,t.d[0]).a&&null!=Ot(n.g,n.d[0]).a?etU(this,(gP(Ot(t.g,t.d[0]).a)+gP(Ot(n.g,n.d[0]).a))/2):null!=Ot(t.g,t.d[0]).a?etU(this,Ot(t.g,t.d[0]).a):null!=Ot(n.g,n.d[0]).a&&etU(this,Ot(n.g,n.d[0]).a)}function ejb(e,t){var n,r,i,a,o,s,u,c,l,f;for(e.a=new Bv(eiG(e55)),r=new fz(t.a);r.a=1&&(m-o>0&&f>=0?(u.n.a+=b,u.n.b+=a*o):m-o<0&&l>=0&&(u.n.a+=b*m,u.n.b+=a));e.o.a=t.a,e.o.b=t.b,eo3(e,(eBy(),ta4),(ed6(),r=Pp(yw(e6o),9),new I1(r,Pp(CY(r,r.length),9),0)))}function ej_(e,t,n,r,i,a){var o;if(!(null==t||!efz(t,tmJ,tmQ)))throw p7(new gL("invalid scheme: "+t));if(!e&&!(null!=n&&-1==x7(n,e_n(35))&&n.length>0&&(GV(0,n.length),47!=n.charCodeAt(0))))throw p7(new gL("invalid opaquePart: "+n));if(e&&!(null!=t&&wZ(tm$,t.toLowerCase()))&&!(null==n||!efz(n,tm1,tm0))||e&&null!=t&&wZ(tm$,t.toLowerCase())&&!eyQ(n))throw p7(new gL(eJI+n));if(!ef$(r))throw p7(new gL("invalid device: "+r));if(!ece(i))throw o=null==i?"invalid segments: null":"invalid segment: "+euR(i),p7(new gL(o));if(!(null==a||-1==x7(a,e_n(35))))throw p7(new gL("invalid query: "+a))}function ejE(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g;for(ewG(t,"Calculate Graph Size",1),t.n&&e&&WG(t,KS(e),(eup(),tmr)),s=ezq,u=ezq,a=eqe,o=eqe,f=new Ow((e.a||(e.a=new FQ(e6k,e,10,11)),e.a));f.e!=f.i.gc();)p=(c=Pp(epH(f),33)).i,b=c.j,g=c.g,r=c.f,i=Pp(eT8(c,(eBB(),thy)),142),s=eB4.Math.min(s,p-i.b),u=eB4.Math.min(u,b-i.d),a=eB4.Math.max(a,p+g+i.c),o=eB4.Math.max(o,b+r+i.a);for(h=Pp(eT8(e,(eBB(),thN)),116),d=new kl(s-h.b,u-h.d),l=new Ow((e.a||(e.a=new FQ(e6k,e,10,11)),e.a));l.e!=l.i.gc();)c=Pp(epH(l),33),eno(c,c.i-d.a),ens(c,c.j-d.b);m=a-s+(h.b+h.c),n=o-u+(h.d+h.a),ena(e,m),eni(e,n),t.n&&e&&WG(t,KS(e),(eup(),tmr))}function ejS(e){var t,n,r,i,a,o,s,u,c,l;for(r=new p0,o=new fz(e.e.a);o.a0){epV(e,n,0),n.a+=String.fromCharCode(r),epV(e,n,i=ehR(t,a)),a+=i-1;continue}39==r?a+11)for(b=Je(ty_,eHT,25,e.b.b.c.length,15,1),f=0,c=new fz(e.b.b);c.a=s&&i<=u)s<=i&&a<=u?(n[l++]=i,n[l++]=a,r+=2):s<=i?(n[l++]=i,n[l++]=u,e.b[r]=u+1,o+=2):a<=u?(n[l++]=s,n[l++]=a,r+=2):(n[l++]=s,n[l++]=u,e.b[r]=u+1);else if(ueHe)&&s<10)vR(e.c,new tf),ejM(e),Ym(e.c),ejv(e.f)}function ejL(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b,m;if(gN(LK(e_k(n,(eBy(),taI)))))for(s=new fz(n.j);s.a=2){for(o=Pp(Vv(u=epL(n,0)),8),s=Pp(Vv(u),8);s.a0&&eoY(l,!0,(ec3(),tpg)),s.k==(eEn(),e8C)&&UP(l),Um(e.f,s,t)}}function ejN(e,t,n){var r,i,a,o,s,u,c,l,f,d;switch(ewG(n,"Node promotion heuristic",1),e.g=t,eYs(e),e.q=Pp(e_k(t,(eBy(),taz)),260),l=Pp(e_k(e.g,ta$),19).a,a=new nH,e.q.g){case 2:case 1:default:eRn(e,a);break;case 3:for(e.q=(eOJ(),tsk),eRn(e,a),u=0,s=new fz(e.a);s.ae.j&&(e.q=tsv,eRn(e,a));break;case 4:for(e.q=(eOJ(),tsk),eRn(e,a),c=0,i=new fz(e.b);i.ae.k&&(e.q=ts_,eRn(e,a));break;case 6:d=zy(eB4.Math.ceil(e.f.length*l/100)),eRn(e,new dq(d));break;case 5:f=zy(eB4.Math.ceil(e.d*l/100)),eRn(e,new dZ(f))}eLC(e,t),eEj(n)}function ejP(e,t,n){var r,i,a,o;this.j=e,this.e=ewi(e),this.o=this.j.e,this.i=!!this.o,this.p=this.i?Pp(RJ(n,Bq(this.o).p),214):null,i=Pp(e_k(e,(eBU(),tt3)),21),this.g=i.Hc((eLR(),ttw)),this.b=new p0,this.d=new ed0(this.e),o=Pp(e_k(this.j,tnw),230),this.q=eaG(t,o,this.e),this.k=new zX(this),a=ZW(eow(vx(e4H,1),eUp,225,0,[this,this.d,this.k,this.q])),t!=(enU(),tur)||gN(LK(e_k(e,(eBy(),ti7))))?t==tur&&gN(LK(e_k(e,(eBy(),ti7))))?(r=new ews(this.e),a.c[a.c.length]=r,this.c=new erB(r,o,Pp(this.q,402))):this.c=new Sr(t,this):(r=new ews(this.e),a.c[a.c.length]=r,this.c=new K5(r,o,Pp(this.q,402))),P_(a,this.c),eP0(a,this.e),this.s=eY0(this.k)}function ejR(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w;for(p=(f=Pp(M2((o=epL(new hz(t).a.d,0),new hG(o))),86))?Pp(e_k(f,(eR6(),tco)),86):null,i=1;f&&p;){for(s=0,u=0,w=0,n=f,r=p;s=e.i?(++e.i,P_(e.a,ell(1)),P_(e.b,f)):(r=e.c[t.p][1],q1(e.a,l,ell(Pp(RJ(e.a,l),19).a+1-r)),q1(e.b,l,gP(LV(RJ(e.b,l)))+f-r*e.e)),(e.q==(eOJ(),tsv)&&(Pp(RJ(e.a,l),19).a>e.j||Pp(RJ(e.a,l-1),19).a>e.j)||e.q==ts_&&(gP(LV(RJ(e.b,l)))>e.k||gP(LV(RJ(e.b,l-1)))>e.k))&&(u=!1),o=new Fa(OH(efu(t).a.Kc(),new c));eTk(o);)s=(a=Pp(ZC(o),17)).c.i,e.f[s.p]==l&&(d=ejj(e,s),i+=Pp(d.a,19).a,u=u&&gN(LK(d.b)));return e.f[t.p]=l,i+=e.c[t.p][0],new kD(ell(i),(OQ(),!!u))}function ejF(e,t,n,r,i){var a,o,s,u,c,l,f,d,h,p,b,m,g;for(f=new p2,o=new p0,ekD(e,n,e.d.fg(),o,f),ekD(e,r,e.d.gg(),o,f),e.b=.2*(b=eTZ(eeh(new R1(null,new Gq(o,16)),new aL)),m=eTZ(eeh(new R1(null,new Gq(o,16)),new aC)),eB4.Math.min(b,m)),a=0,s=0;s=2&&(g=eOY(o,!0,d),e.e||(e.e=new h$(e)),ehB(e.e,g,o,e.b)),ewv(o,d),eFn(o),h=-1,l=new fz(o);l.as)}function ejU(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b;for(n=Pp(e_k(e,(eBy(),tol)),98),o=e.f,a=e.d,s=o.a+a.b+a.c,u=0-a.d-e.c.b,l=o.b+a.d+a.a-e.c.b,c=new p0,f=new p0,i=new fz(t);i.a0),Pp(l.a.Xb(l.c=--l.b),17));a!=r&&l.b>0;)e.a[a.p]=!0,e.a[r.p]=!0,a=(A6(l.b>0),Pp(l.a.Xb(l.c=--l.b),17));l.b>0&&BH(l)}}function ejZ(e,t,n){var r,i,a,o,s,u,c,l,f;if(e.a!=t.Aj())throw p7(new gL(eZ5+t.ne()+eZ6));if(r=ecG((eSp(),tvc),t).$k())return r.Aj().Nh().Ih(r,n);if(o=ecG(tvc,t).al()){if(null==n)return null;if((s=Pp(n,15)).dc())return"";for(f=new vs,a=s.Kc();a.Ob();)i=a.Pb(),xk(f,o.Aj().Nh().Ih(o,i)),f.a+=" ";return x3(f,f.a.length-1)}if(!(l=ecG(tvc,t).bl()).dc()){for(c=l.Kc();c.Ob();)if((u=Pp(c.Pb(),148)).wj(n))try{if(f=u.Aj().Nh().Ih(u,n),null!=f)return f}catch(d){if(d=eoa(d),!M4(d,102))throw p7(d)}throw p7(new gL("Invalid value: '"+n+"' for datatype :"+t.ne()))}return Pp(t,834).Fj(),null==n?null:M4(n,172)?""+Pp(n,172).a:esF(n)==e1Q?MU(tmS[0],Pp(n,199)):efF(n)}function ejX(e){var t,n,r,i,a,o,s,u,c,l;for(c=new _n,s=new _n,a=new fz(e);a.a-1){for(i=epL(s,0);i.b!=i.d.c;)(r=Pp(Vv(i),128)).v=o;for(;0!=s.b;)for(r=Pp(egW(s,0),128),n=new fz(r.i);n.a0&&(n+=u.n.a+u.o.a/2,++f),p=new fz(u.j);p.a0&&(n/=f),g=Je(tyx,eH5,25,r.a.c.length,15,1),s=0,c=new fz(r.a);c.a=s&&i<=u)s<=i&&a<=u?r+=2:s<=i?(e.b[r]=u+1,o+=2):a<=u?(n[l++]=i,n[l++]=s-1,r+=2):(n[l++]=i,n[l++]=s-1,e.b[r]=u+1,o+=2);else if(u0?i-=864e5:i+=864e5,u=new LZ(eft(eap(t.q.getTime()),i))),l=new vl,c=e.a.length,a=0;a=97&&r<=122||r>=65&&r<=90){for(o=a+1;o=c)throw p7(new gL("Missing trailing '"));o+10&&0==n.c&&(t||(t=new p0),t.c[t.c.length]=n);if(t)for(;0!=t.c.length;){if((n=Pp(ZV(t,0),233)).b&&n.b.c.length>0){for(a=(n.b||(n.b=new p0),new fz(n.b));a.aQI(e,n,0))return new kD(i,n)}else if(gP(Ot(i.g,i.d[0]).a)>gP(Ot(n.g,n.d[0]).a))return new kD(i,n)}for(s=(n.e||(n.e=new p0),n.e).Kc();s.Ob();)u=((o=Pp(s.Pb(),233)).b||(o.b=new p0),o.b),Gp(0,u.c.length),Ew(u.c,0,n),o.c==u.c.length&&(t.c[t.c.length]=o)}return null}function eFe(e,t){var n,r,i,a,o,s,u,c,l;if(null==e)return eUg;if(null!=(u=t.a.zc(e,t)))return"[...]";for(a=0,n=new eaP(eUd,"[","]"),o=(i=e).length;a=14&&l<=16)?t.a._b(r)?(n.a?xM(n.a,n.b):n.a=new O0(n.d),xx(n.a,"[...]")):ZJ(n,eFe(s=etG(r),c=new Rq(t))):M4(r,177)?ZJ(n,ekd(Pp(r,177))):M4(r,190)?ZJ(n,ewh(Pp(r,190))):M4(r,195)?ZJ(n,eEm(Pp(r,195))):M4(r,2012)?ZJ(n,ewp(Pp(r,2012))):M4(r,48)?ZJ(n,ekf(Pp(r,48))):M4(r,364)?ZJ(n,ekG(Pp(r,364))):M4(r,832)?ZJ(n,ekl(Pp(r,832))):M4(r,104)&&ZJ(n,ekc(Pp(r,104))):ZJ(n,null==r?eUg:efF(r));return n.a?0==n.e.length?n.a.a:n.a.a+""+n.e:n.c}function eFt(e,t,n,r){var i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y;for(s=eLO(t,!1,!1),g=eEF(s),r&&(g=esP(g)),y=gP(LV(eT8(t,(epz(),e63)))),m=(A6(0!=g.b),Pp(g.a.a.c,8)),f=Pp(ep3(g,1),8),g.b>2?(l=new p0,eoc(l,new Gz(g,1,g.b)),a=eBk(l,y+e.a),v=new eTI(a),eaW(v,t),n.c[n.c.length]=v):v=r?Pp(Bp(e.b,e_I(t)),266):Pp(Bp(e.b,e_P(t)),266),u=e_I(t),r&&(u=e_P(t)),o=eEJ(m,u),c=y+e.a,o.a?(c+=eB4.Math.abs(m.b-f.b),b=new kl(f.a,(f.b+m.b)/2)):(c+=eB4.Math.abs(m.a-f.a),b=new kl((f.a+m.a)/2,f.b)),r?Um(e.d,t,new emL(v,o,b,c)):Um(e.c,t,new emL(v,o,b,c)),Um(e.b,t,v),p=(t.n||(t.n=new FQ(e6S,t,1,7)),t.n),h=new Ow(p);h.e!=h.i.gc();)d=Pp(epH(h),137),i=eIt(e,d,!0,0,0),n.c[n.c.length]=i}function eFn(e){var t,n,r,i,a,o,s,u,c,l;for(c=new p0,s=new p0,o=new fz(e);o.a-1){for(a=new fz(s);a.a0)&&(l3(u,eB4.Math.min(u.o,i.o-1)),l2(u,u.i-1),0==u.i&&(s.c[s.c.length]=u))}}function eFr(e,t,n){var r,i,a,o,s,u,c;if(c=e.c,t||(t=tgK),e.c=t,(4&e.Db)!=0&&(1&e.Db)==0&&(u=new FX(e,1,2,c,e.c),n?n.Ei(u):n=u),c!=t){if(M4(e.Cb,284))e.Db>>16==-10?n=Pp(e.Cb,284).nk(t,n):e.Db>>16==-15&&(t||(t=(eBK(),tgA)),c||(c=(eBK(),tgA)),e.Cb.nh()&&(u=new Q$(e.Cb,1,13,c,t,ebv(QX(Pp(e.Cb,59)),e),!1),n?n.Ei(u):n=u));else if(M4(e.Cb,88))e.Db>>16==-23&&(M4(t,88)||(t=(eBK(),tgI)),M4(c,88)||(c=(eBK(),tgI)),e.Cb.nh()&&(u=new Q$(e.Cb,1,10,c,t,ebv(qt(Pp(e.Cb,26)),e),!1),n?n.Ei(u):n=u));else if(M4(e.Cb,444))for(o=((s=Pp(e.Cb,836)).b||(s.b=new pG(new mR)),s.b),a=(r=new esz(new fS(o.a).a),new pW(r));a.a.b;)n=eFr(i=Pp(etz(a.a).cd(),87),eOl(i,s),n)}return n}function eFi(e,t){var n,r,i,a,o,s,u,c,l,f,d;for(o=gN(LK(eT8(e,(eBy(),taI)))),d=Pp(eT8(e,toh),21),u=!1,c=!1,f=new Ow((e.c||(e.c=new FQ(e6x,e,9,9)),e.c));f.e!=f.i.gc()&&(!u||!c);){for(a=Pp(epH(f),118),s=0,i=Y_(enM(eow(vx(e1B,1),eUp,20,0,[(a.d||(a.d=new Ih(e6g,a,8,5)),a.d),(a.e||(a.e=new Ih(e6g,a,7,4)),a.e)])));eTk(i)&&(r=Pp(ZC(i),79),l=o&&exb(r)&&gN(LK(eT8(r,taD))),n=eRL((r.b||(r.b=new Ih(e6m,r,4,7)),r.b),a)?e==z$(ewH(Pp(etj((r.c||(r.c=new Ih(e6m,r,5,8)),r.c),0),82))):e==z$(ewH(Pp(etj((r.b||(r.b=new Ih(e6m,r,4,7)),r.b),0),82))),!((l||n)&&++s>1)););s>0?u=!0:d.Hc((ekU(),tbp))&&(a.n||(a.n=new FQ(e6S,a,1,7)),a.n).i>0&&(u=!0),s>1&&(c=!0)}u&&t.Fc((eLR(),ttw)),c&&t.Fc((eLR(),tt_))}function eFa(e){var t,n,r,i,a,o,s,u,c,l,f,d;if((d=Pp(eT8(e,(eBB(),thx)),21)).dc())return null;if(s=0,o=0,d.Hc((ed6(),tbV))){for(l=Pp(eT8(e,thV),98),r=2,n=2,i=2,a=2,t=z$(e)?Pp(eT8(z$(e),the),103):Pp(eT8(e,the),103),c=new Ow((e.c||(e.c=new FQ(e6x,e,9,9)),e.c));c.e!=c.i.gc();)if(u=Pp(epH(c),118),(f=Pp(eT8(u,th0),61))==(eYu(),tbF)&&(f=eNh(u,t),ebu(u,th0,f)),l==(ewf(),tbo))switch(f.g){case 1:r=eB4.Math.max(r,u.i+u.g);break;case 2:n=eB4.Math.max(n,u.j+u.f);break;case 3:i=eB4.Math.max(i,u.i+u.g);break;case 4:a=eB4.Math.max(a,u.j+u.f)}else switch(f.g){case 1:r+=u.g+2;break;case 2:n+=u.f+2;break;case 3:i+=u.g+2;break;case 4:a+=u.f+2}s=eB4.Math.max(r,i),o=eB4.Math.max(n,a)}return eYx(e,s,o,!0,!0)}function eFo(e,t,n,r,i){var a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w;for(v=Pp(qE(etc(UJ(new R1(null,new Gq(t.d,16)),new hc(n)),new hl(n)),JF(new U,new B,new en,eow(vx(e2L,1),eU4,132,0,[(eum(),e2U)]))),15),f=eUu,l=eHt,u=new fz(t.b.j);u.a0)?c&&(d=g.p,o?++d:--d,h=!(eOV(r=eoZ(f=Pp(RJ(g.c.a,d),10)),E,n[0])||FF(r,E,n[0]))):h=!0),p=!1,(_=t.D.i)&&_.c&&s.e&&((l=o&&_.p>0||!o&&_.p<_.c.a.c.length-1)?(d=_.p,o?--d:++d,p=!(eOV(r=eoZ(f=Pp(RJ(_.c.a,d),10)),n[0],k)||FF(r,n[0],k))):p=!0),h&&p&&P7(e.a,S),h||enD(e.a,eow(vx(e50,1),eUP,8,0,[b,m])),p||enD(e.a,eow(vx(e50,1),eUP,8,0,[w,y]))}function eFh(e,t){var n,r,i,a,o,s,u,c;if(M4(e.Ug(),160)?(eFh(Pp(e.Ug(),160),t),t.a+=" > "):t.a+="Root ",IE((n=e.Tg().zb).substr(0,3),"Elk")?xM(t,n.substr(3)):(t.a+=""+n,t),i=e.zg()){xM((t.a+=" ",t),i);return}if(M4(e,354)&&(c=Pp(e,137).a)){xM((t.a+=" ",t),c);return}for(o=new Ow(e.Ag());o.e!=o.i.gc();)if(c=(a=Pp(epH(o),137)).a){xM((t.a+=" ",t),c);return}if(M4(e,352)&&((r=Pp(e,79)).b||(r.b=new Ih(e6m,r,4,7)),0!=r.b.i&&(r.c||(r.c=new Ih(e6m,r,5,8)),0!=r.c.i))){for(t.a+=" (",s=new AF((r.b||(r.b=new Ih(e6m,r,4,7)),r.b));s.e!=s.i.gc();)s.e>0&&(t.a+=eUd),eFh(Pp(epH(s),160),t);for(t.a+=eGH,u=new AF((r.c||(r.c=new Ih(e6m,r,5,8)),r.c));u.e!=u.i.gc();)u.e>0&&(t.a+=eUd),eFh(Pp(epH(u),160),t);t.a+=")"}}function eFp(e,t,n){var r,i,a,o,s,u,c,l,f,d,h;if(a=Pp(e_k(e,(eBU(),tnc)),79)){for(r=e.a,C5(i=new TS(n),eyr(e)),eag(e.d.i,e.c.i)?(d=e.c,f=esp(eow(vx(e50,1),eUP,8,0,[d.n,d.a])),C6(f,n)):f=GX(e.c),qQ(r,f,r.a,r.a.a),h=GX(e.d),null!=e_k(e,tnC)&&C5(h,Pp(e_k(e,tnC),8)),qQ(r,h,r.c.b,r.c),etH(r,i),o=eLO(a,!0,!0),ern(o,Pp(etj((a.b||(a.b=new Ih(e6m,a,4,7)),a.b),0),82)),err(o,Pp(etj((a.c||(a.c=new Ih(e6m,a,5,8)),a.c),0),82)),eNI(r,o),l=new fz(e.b);l.a=0){for(u=null,s=new KB(l.a,c+1);s.bo?1:Te(isNaN(0),isNaN(o)))<0&&(enj(eVU),(eB4.Math.abs(o-1)<=eVU||1==o||isNaN(o)&&isNaN(1)?0:o<1?-1:o>1?1:Te(isNaN(o),isNaN(1)))<0)&&(enj(eVU),(eB4.Math.abs(0-s)<=eVU||0==s||isNaN(0)&&isNaN(s)?0:0s?1:Te(isNaN(0),isNaN(s)))<0)&&(enj(eVU),(eB4.Math.abs(s-1)<=eVU||1==s||isNaN(s)&&isNaN(1)?0:s<1?-1:s>1?1:Te(isNaN(s),isNaN(1)))<0)))}function eFg(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E;for(f=new BU(new fQ(e));f.b!=f.c.a.d;)for(b=0,s=Pp((l=JO(f)).d,56),t=Pp(l.e,56),w=(null==(o=s.Tg()).i&&eNT(o),o.i).length;b=0&&b=c.c.c.length?VJ((eEn(),e8N),e8D):VJ((eEn(),e8D),e8D),l*=2,a=n.a.g,n.a.g=eB4.Math.max(a,a+(l-a)),o=n.b.g,n.b.g=eB4.Math.max(o,o+(l-o)),i=t}}}function eFw(e,t,n,r,i){var a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_;for(_=Pg(e),l=new p0,f=(s=e.c.length)-1,d=s+1;0!=_.a.c;){for(;0!=n.b;)y=(A6(0!=n.b),Pp(etw(n,n.a.a),112)),zS(_.a,y),y.g=f--,eNg(y,t,n,r);for(;0!=t.b;)w=(A6(0!=t.b),Pp(etw(t,t.a.a),112)),zS(_.a,w),w.g=d++,eNg(w,t,n,r);for(c=eHt,g=(o=new C1(new Ap(new fP(_.a).a).b),new fR(o));Et(g.a.a);){if(m=(a=AJ(g.a),Pp(a.cd(),112)),!r&&m.b>0&&m.a<=0){l.c=Je(e1R,eUp,1,0,5,1),l.c[l.c.length]=m;break}(b=m.i-m.d)>=c&&(b>c&&(l.c=Je(e1R,eUp,1,0,5,1),c=b),l.c[l.c.length]=m)}0!=l.c.length&&(u=Pp(RJ(l,ebO(i,l.c.length)),112),zS(_.a,u),u.g=d++,eNg(u,t,n,r),l.c=Je(e1R,eUp,1,0,5,1))}for(v=e.c.length+1,p=new fz(e);p.a0&&(d.d+=l.n.d,d.d+=l.d),d.a>0&&(d.a+=l.n.a,d.a+=l.d),d.b>0&&(d.b+=l.n.b,d.b+=l.d),d.c>0&&(d.c+=l.n.c,d.c+=l.d),d}function eFx(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p;for(d=n.d,f=n.c,o=(a=new kl(n.f.a+n.d.b+n.d.c,n.f.b+n.d.d+n.d.a)).b,c=new fz(e.a);c.a=(l=Pp(Pp(Zq(e.r,t),21),84)).gc()||t==(eYu(),tby)||t==(eYu(),tbY)){eYY(e,t);return}for(b=e.u.Hc((ekU(),tbg)),n=t==(eYu(),tbw)?(eaY(),e4c):(eaY(),e4o),g=t==tbw?(QQ(),e3U):(QQ(),e3$),r=vN(DP(n),e.s),m=t==tbw?eHQ:eH1,c=l.Kc();c.Ob();)(s=Pp(c.Pb(),111)).c&&!(s.c.d.c.length<=0)&&(p=s.b.rf(),h=s.e,(d=(f=s.c).i).b=(a=f.n,f.e.a+a.b+a.c),d.a=(o=f.n,f.e.b+o.d+o.a),b?(d.c=h.a-(i=f.n,f.e.a+i.b+i.c)-e.s,b=!1):d.c=h.a+p.a+e.s,$C(g,ezr),f.f=g,JC(f,(Qs(),e3Y)),P_(r.d,new jH(d,elO(r,d))),m=t==tbw?eB4.Math.min(m,h.b):eB4.Math.max(m,h.b+s.b.rf().b));for(m+=t==tbw?-e.t:e.t,edp((r.e=m,r)),u=l.Kc();u.Ob();)(s=Pp(u.Pb(),111)).c&&!(s.c.d.c.length<=0)&&(d=s.c.i,d.c-=s.e.a,d.d-=s.e.b)}function eFA(e,t,n){var r;if(ewG(n,"StretchWidth layering",1),0==t.a.c.length){eEj(n);return}for(e.c=t,e.t=0,e.u=0,e.i=eHQ,e.g=eH1,e.d=gP(LV(e_k(t,(eBy(),toO)))),ebn(e),eTR(e),eTP(e),eyo(e),ed2(e),e.i=eB4.Math.max(1,e.i),e.g=eB4.Math.max(1,e.g),e.d=e.d/e.i,e.f=e.g/e.i,e.s=ebZ(e),r=new By(e.c),P_(e.c.b,r),e.r=WC(e.p),e.n=zb(e.k,e.k.length);0!=e.r.c.length;)e.o=ecu(e),!e.o||ess(e)&&0!=e.b.a.gc()?(ey6(e,r),r=new By(e.c),P_(e.c.b,r),er7(e.a,e.b),e.b.a.$b(),e.t=e.u,e.u=0):ess(e)?(e.c.b.c=Je(e1R,eUp,1,0,5,1),r=new By(e.c),P_(e.c.b,r),e.t=0,e.u=0,e.b.a.$b(),e.a.a.$b(),++e.f,e.r=WC(e.p),e.n=zb(e.k,e.k.length)):(Gu(e.o,r),QA(e.r,e.o),Yf(e.b,e.o),e.t=e.t-e.k[e.o.p]*e.d+e.j[e.o.p],e.u+=e.e[e.o.p]*e.d);t.a.c=Je(e1R,eUp,1,0,5,1),eSj(t.b),eEj(n)}function eFL(e){var t,n,r,i;for(_r(UJ(new R1(null,new Gq(e.a.b,16)),new rH),new r$),eyR(e),_r(UJ(new R1(null,new Gq(e.a.b,16)),new rz),new rG),e.c==(efE(),tpM)&&(_r(UJ(eeh(new R1(null,new Gq(new fk(e.f),1)),new rW),new rK),new hn(e)),_r(UJ(UQ(eeh(eeh(new R1(null,new Gq(e.d.b,16)),new rV),new rq),new rZ),new rX),new hi(e))),i=new kl(eHQ,eHQ),t=new kl(eH1,eH1),r=new fz(e.a.b);r.a0&&(e.c[t.c.p][t.p].d+=eMU(e.i,24)*e$h*.07000000029802322-.03500000014901161,e.c[t.c.p][t.p].a=e.c[t.c.p][t.p].d/e.c[t.c.p][t.p].b)}}function eFD(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p,b,m;for(p=new fz(e);p.ar.d,r.d=eB4.Math.max(r.d,t),s&&n&&(r.d=eB4.Math.max(r.d,r.a),r.a=r.d+i);break;case 3:n=t>r.a,r.a=eB4.Math.max(r.a,t),s&&n&&(r.a=eB4.Math.max(r.a,r.d),r.d=r.a+i);break;case 2:n=t>r.c,r.c=eB4.Math.max(r.c,t),s&&n&&(r.c=eB4.Math.max(r.b,r.c),r.b=r.c+i);break;case 4:n=t>r.b,r.b=eB4.Math.max(r.b,t),s&&n&&(r.b=eB4.Math.max(r.b,r.c),r.c=r.b+i)}}}function eFj(e){var t,n,r,i,a,o,s,u,c,l,f;for(c=new fz(e);c.a0||l.j==tbY&&l.e.c.length-l.g.c.length<0)){t=!1;break}for(i=new fz(l.g);i.a=c&&_>=m&&(d+=p.n.b+b.n.b+b.a.b-w,++s));if(n)for(o=new fz(v.e);o.a=c&&_>=m&&(d+=p.n.b+b.n.b+b.a.b-w,++s))}s>0&&(E+=d/s,++h)}h>0?(t.a=i*E/h,t.g=h):(t.a=0,t.g=0)}function eFY(e,t){var n,r,i,a,o,s,u,c,l,f,d;for(i=new fz(e.a.b);i.aeH1||t.o==tuE&&l0&&eno(g,w*E),_>0&&ens(g,_*S);for(ear(e.b,new te),t=new p0,s=new esz(new fS(e.c).a);s.b;)o=etz(s),r=Pp(o.cd(),79),n=Pp(o.dd(),395).a,i=eLO(r,!1,!1),f=ewM(e_I(r),eEF(i),n),eNI(f,i),(y=e_D(r))&&-1==QI(t,y,0)&&(t.c[t.c.length]=y,Hw(y,(A6(0!=f.b),Pp(f.a.a.c,8)),n));for(m=new esz(new fS(e.d).a);m.b;)b=etz(m),r=Pp(b.cd(),79),n=Pp(b.dd(),395).a,i=eLO(r,!1,!1),f=ewM(e_P(r),esP(eEF(i)),n),eNI(f=esP(f),i),(y=e_N(r))&&-1==QI(t,y,0)&&(t.c[t.c.length]=y,Hw(y,(A6(0!=f.b),Pp(f.c.b.c,8)),n))}function eFz(e,t,n,r){var i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k;if(0!=n.c.length){for(p=new p0,h=new fz(n);h.aeB4.Math.abs(v-m))continue;v1)for(h=new eRM(p,y,r),qX(y,new SV(e,h)),o.c[o.c.length]=h,f=y.a.ec().Kc();f.Ob();)QA(a,(l=Pp(f.Pb(),46)).b);if(s.a.gc()>1)for(h=new eRM(p,s,r),qX(s,new Sq(e,h)),o.c[o.c.length]=h,f=s.a.ec().Kc();f.Ob();)QA(a,(l=Pp(f.Pb(),46)).b)}}function eFJ(e){_Y(e,new ewB(vZ(vQ(vq(vJ(vX(new oc,eqp),"ELK Radial"),'A radial layout provider which is based on the algorithm of Peter Eades published in "Drawing free trees.", published by International Institute for Advanced Study of Social Information Science, Fujitsu Limited in 1991. The radial layouter takes a tree and places the nodes in radial order around the root. The nodes of the same tree level are placed on the same radius.'),new aW),eqp))),KE(e,eqp,eVT,epB(tlw)),KE(e,eqp,eGi,epB(tlS)),KE(e,eqp,eGh,epB(tlh)),KE(e,eqp,eGM,epB(tlp)),KE(e,eqp,eGd,epB(tlb)),KE(e,eqp,eGp,epB(tld)),KE(e,eqp,eGf,epB(tlm)),KE(e,eqp,eGb,epB(tly)),KE(e,eqp,eql,epB(tll)),KE(e,eqp,eqc,epB(tlf)),KE(e,eqp,eqh,epB(tlg)),KE(e,eqp,eqs,epB(tlv)),KE(e,eqp,equ,epB(tl_)),KE(e,eqp,eqf,epB(tlE)),KE(e,eqp,eqd,epB(tlk))}function eFQ(e){var t;if(this.r=U2(new ex,new eT),this.b=new efY(Pp(Y9(e6a),290)),this.p=new efY(Pp(Y9(e6a),290)),this.i=new efY(Pp(Y9(e3n),290)),this.e=e,this.o=new TS(e.rf()),this.D=e.Df()||gN(LK(e.We((eBB(),thh)))),this.A=Pp(e.We((eBB(),thx)),21),this.B=Pp(e.We(thL),21),this.q=Pp(e.We(thV),98),this.u=Pp(e.We(thJ),21),!e_y(this.u))throw p7(new gq("Invalid port label placement: "+this.u));if(this.v=gN(LK(e.We(th1))),this.j=Pp(e.We(thS),21),!eM1(this.j))throw p7(new gq("Invalid node label placement: "+this.j));this.n=Pp(egG(e,th_),116),this.k=gP(LV(egG(e,tps))),this.d=gP(LV(egG(e,tpo))),this.w=gP(LV(egG(e,tpp))),this.s=gP(LV(egG(e,tpu))),this.t=gP(LV(egG(e,tpc))),this.C=Pp(egG(e,tpd),142),this.c=2*this.d,t=!this.B.Hc((eI3(),tbX)),this.f=new eh6(0,t,0),this.g=new eh6(1,t,0),gh(this.f,(etx(),e3N),this.g)}function eF1(e,t,n,r,i){var a,o,s,u,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M;for(w=0,b=0,p=0,h=1,y=new Ow((e.a||(e.a=new FQ(e6k,e,10,11)),e.a));y.e!=y.i.gc();)g=Pp(epH(y),33),h+=VG(new Fa(OH(eOi(g).a.Kc(),new c))),x=g.g,b=eB4.Math.max(b,x),d=g.f,p=eB4.Math.max(p,d),w+=x*d;for(m=(e.a||(e.a=new FQ(e6k,e,10,11)),e.a).i,o=w+2*r*r*h*m,a=eB4.Math.sqrt(o),u=eB4.Math.max(a*n,b),s=eB4.Math.max(a/n,p),v=new Ow((e.a||(e.a=new FQ(e6k,e,10,11)),e.a));v.e!=v.i.gc();)g=Pp(epH(v),33),T=i.b+(eMU(t,26)*e$l+eMU(t,27)*e$f)*(u-g.g),M=i.b+(eMU(t,26)*e$l+eMU(t,27)*e$f)*(s-g.f),eno(g,T),ens(g,M);for(k=u+(i.b+i.c),S=s+(i.d+i.a),E=new Ow((e.a||(e.a=new FQ(e6k,e,10,11)),e.a));E.e!=E.i.gc();)for(_=Pp(epH(E),33),f=new Fa(OH(eOi(_).a.Kc(),new c));eTk(f);)l=Pp(ZC(f),79),eTc(l)||eBv(l,t,k,S);eYx(e,k+=i.b+i.c,S+=i.d+i.a,!1,!0)}function eF0(e){var t,n,r,i,a,o,s,u,c,l,f;if(null==e)throw p7(new vo(eUg));if(c=e,a=e.length,u=!1,a>0&&(45==(t=(GV(0,e.length),e.charCodeAt(0)))||43==t)&&(e=e.substr(1),--a,u=45==t),0==a)throw p7(new vo(eHJ+c+'"'));for(;e.length>0&&(GV(0,e.length),48==e.charCodeAt(0));)e=e.substr(1),--a;if(a>(eDZ(),e0G)[10])throw p7(new vo(eHJ+c+'"'));for(i=0;i0&&(f=-parseInt(e.substr(0,r),10),e=e.substr(r),a-=r,n=!1);a>=o;){if(r=parseInt(e.substr(0,o),10),e=e.substr(o),a-=o,n)n=!1;else{if(0>ecd(f,s))throw p7(new vo(eHJ+c+'"'));f=efn(f,l)}f=efe(f,r)}if(ecd(f,0)>0||!u&&(f=QC(f),0>ecd(f,0)))throw p7(new vo(eHJ+c+'"'));return f}function eF2(e,t){var n,r,i,a,o,s,u;if(Rm(),this.a=new MW(this),this.b=e,this.c=t,this.f=Yg(QZ((eSp(),tvc),t)),this.f.dc()){if((s=ev1(tvc,e))==t)for(this.e=!0,this.d=new p0,this.f=new o5,this.f.Fc(eQB),Pp(eP9(Qq(tvc,etP(e)),""),26)==e&&this.f.Fc(Fr(tvc,etP(e))),i=eIT(tvc,e).Kc();i.Ob();)switch(Ur(QZ(tvc,r=Pp(i.Pb(),170)))){case 4:this.d.Fc(r);break;case 5:this.f.Gc(Yg(QZ(tvc,r)))}else if(_4(),Pp(t,66).Oj())for(o=0,this.e=!0,this.f=null,this.d=new p0,u=(null==e.i&&eNT(e),e.i).length;o=0&&o0&&(Pp(UA(e.b,t),124).a.b=n)}function eF4(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g;for(ewG(t,"Comment pre-processing",1),n=0,u=new fz(e.a);u.a0&&64!=(u=(GV(0,t.length),t.charCodeAt(0)))){if(37==u&&(f=t.lastIndexOf("%"),c=!1,0!=f&&(f==d-1||(c=(GV(f+1,t.length),46==t.charCodeAt(f+1)))))){if(y=IE("%",o=t.substr(1,f-1))?null:eYy(o),r=0,c)try{r=eDa(t.substr(f+2),eHt,eUu)}catch(w){if(w=eoa(w),M4(w,127))throw s=w,p7(new QH(s));throw p7(w)}for(m=erW(e.Wg());m.Ob();)if(M4(p=eaO(m),510)&&(v=(i=Pp(p,590)).d,(null==y?null==v:IE(y,v))&&0==r--))return i;return null}if(h=-1==(l=t.lastIndexOf("."))?t:t.substr(0,l),n=0,-1!=l)try{n=eDa(t.substr(l+1),eHt,eUu)}catch(_){if(_=eoa(_),M4(_,127))h=t;else throw p7(_)}for(h=IE("%",h)?null:eYy(h),b=erW(e.Wg());b.Ob();)if(M4(p=eaO(b),191)&&(g=(a=Pp(p,191)).ne(),(null==h?null==g:IE(h,g))&&0==n--))return a;return null}return eR2(e,t)}function eF8(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M;for(E=new p0,p=new fz(e.b);p.a=e.length)return{done:!0};var r=e[n++];return{value:[r,t.get(r)],done:!1}}}},eCi()||(e.prototype.createObject=function(){return{}},e.prototype.get=function(e){return this.obj[":"+e]},e.prototype.set=function(e,t){this.obj[":"+e]=t},e.prototype[e$c]=function(e){delete this.obj[":"+e]},e.prototype.keys=function(){var e=[];for(var t in this.obj)58==t.charCodeAt(0)&&e.push(t.substring(1));return e}),e}function eYt(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p,b,m;if(eNl(),null==e)return null;if(0==(f=8*e.length))return"";for(u=0,s=f%24,h=f/24|0,a=null,a=Je(tyw,eHl,25,4*(d=0!=s?h+1:h),15,1),c=0,l=0,t=0,n=0,r=0,o=0,i=0;u>24,c=(3&t)<<24>>24,p=(-128&t)==0?t>>2<<24>>24:(t>>2^192)<<24>>24,b=(-128&n)==0?n>>4<<24>>24:(n>>4^240)<<24>>24,m=(-128&r)==0?r>>6<<24>>24:(r>>6^252)<<24>>24,a[o++]=tvQ[p],a[o++]=tvQ[b|c<<4],a[o++]=tvQ[l<<2|m],a[o++]=tvQ[63&r];return 8==s?(c=(3&(t=e[i]))<<24>>24,p=(-128&t)==0?t>>2<<24>>24:(t>>2^192)<<24>>24,a[o++]=tvQ[p],a[o++]=tvQ[c<<4],a[o++]=61,a[o++]=61):16==s&&(t=e[i],l=(15&(n=e[i+1]))<<24>>24,c=(3&t)<<24>>24,p=(-128&t)==0?t>>2<<24>>24:(t>>2^192)<<24>>24,b=(-128&n)==0?n>>4<<24>>24:(n>>4^240)<<24>>24,a[o++]=tvQ[p],a[o++]=tvQ[b|c<<4],a[o++]=tvQ[l<<2],a[o++]=61),ehv(a,0,a.length)}function eYn(e,t){var n,r,i,a,o,s,u;if(0==e.e&&e.p>0&&(e.p=-(e.p-1)),e.p>eHt&&V9(t,e.p-eHx),o=t.q.getDate(),zC(t,1),e.k>=0&&z7(t,e.k),e.c>=0?zC(t,e.c):e.k>=0?(r=35-(u=new est(t.q.getFullYear()-eHx,t.q.getMonth(),35)).q.getDate(),zC(t,eB4.Math.min(r,o))):zC(t,o),e.f<0&&(e.f=t.q.getHours()),e.b>0&&e.f<12&&(e.f+=12),M5(t,24==e.f&&e.g?0:e.f),e.j>=0&&Z0(t,e.j),e.n>=0&&Jf(t,e.n),e.i>=0&&xN(t,eft(efn(eyt(eap(t.q.getTime()),eHf),eHf),e.i)),e.a&&(V9(i=new wW,i.q.getFullYear()-eHx-80),Ei(eap(t.q.getTime()),eap(i.q.getTime()))&&V9(t,i.q.getFullYear()-eHx+100)),e.d>=0){if(-1==e.c)(n=(7+e.d-t.q.getDay())%7)>3&&(n-=7),s=t.q.getMonth(),zC(t,t.q.getDate()+n),t.q.getMonth()!=s&&zC(t,t.q.getDate()+(n>0?-7:7));else if(t.q.getDay()!=e.d)return!1}return e.o>eHt&&(a=t.q.getTimezoneOffset(),xN(t,eft(eap(t.q.getTime()),(e.o-a)*60*eHf))),!0}function eYr(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w;if(i=e_k(t,(eBU(),tnc)),M4(i,239)){for(p=Pp(i,33),b=t.e,d=new TS(t.c),a=t.d,d.a+=a.b,d.b+=a.d,w=Pp(eT8(p,(eBy(),ta9)),174),Aa(w,(eI3(),tbJ))&&(h=Pp(eT8(p,ta7),116),lR(h,a.a),lG(h,a.d),lj(h,a.b),lW(h,a.c)),n=new p0,l=new fz(t.a);l.a0&&P_(e.p,f),P_(e.o,f);t-=r,p=u+t,l+=t*e.e,q1(e.a,s,ell(p)),q1(e.b,s,l),e.j=eB4.Math.max(e.j,p),e.k=eB4.Math.max(e.k,l),e.d+=t,t+=m}}function eYu(){var e;eYu=A,tbF=new kS(ezo,0),tbw=new kS(ezb,1),tby=new kS(ezm,2),tbj=new kS(ezg,3),tbY=new kS(ezv,4),tbx=(Hj(),new vd((e=Pp(yw(e6a),9),new I1(e,Pp(CY(e,e.length),9),0)))),tbT=ecO(jL(tbw,eow(vx(e6a,1),eGj,61,0,[]))),tb_=ecO(jL(tby,eow(vx(e6a,1),eGj,61,0,[]))),tbN=ecO(jL(tbj,eow(vx(e6a,1),eGj,61,0,[]))),tbR=ecO(jL(tbY,eow(vx(e6a,1),eGj,61,0,[]))),tbC=ecO(jL(tbw,eow(vx(e6a,1),eGj,61,0,[tbj]))),tbk=ecO(jL(tby,eow(vx(e6a,1),eGj,61,0,[tbY]))),tbD=ecO(jL(tbw,eow(vx(e6a,1),eGj,61,0,[tbY]))),tbM=ecO(jL(tbw,eow(vx(e6a,1),eGj,61,0,[tby]))),tbP=ecO(jL(tbj,eow(vx(e6a,1),eGj,61,0,[tbY]))),tbE=ecO(jL(tby,eow(vx(e6a,1),eGj,61,0,[tbj]))),tbL=ecO(jL(tbw,eow(vx(e6a,1),eGj,61,0,[tby,tbY]))),tbS=ecO(jL(tby,eow(vx(e6a,1),eGj,61,0,[tbj,tbY]))),tbI=ecO(jL(tbw,eow(vx(e6a,1),eGj,61,0,[tbj,tbY]))),tbO=ecO(jL(tbw,eow(vx(e6a,1),eGj,61,0,[tby,tbj]))),tbA=ecO(jL(tbw,eow(vx(e6a,1),eGj,61,0,[tby,tbj,tbY])))}function eYc(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y;if(0!=t.b){for(h=new _n,s=null,p=null,r=zy(eB4.Math.floor(eB4.Math.log(t.b)*eB4.Math.LOG10E)+1),u=0,y=epL(t,0);y.b!=y.d.c;)for(g=Pp(Vv(y),86),xc(p)!==xc(e_k(g,(eR6(),tca)))&&(p=Lq(e_k(g,tca)),u=0),eo3(g,tca,s=null!=p?p+WB(u++,r):WB(u++,r)),m=(i=epL(new hz(g).a.d,0),new hG(i));yV(m.a);)qQ(h,b=Pp(Vv(m.a),188).c,h.c.b,h.c),eo3(b,tca,s);for(o=0,d=new p2;o=u){A6(g.b>0),g.a.Xb(g.c=--g.b);break}b.a>c&&(i?(eoc(i.b,b.b),i.a=eB4.Math.max(i.a,b.a),BH(g)):(P_(b.b,f),b.c=eB4.Math.min(b.c,c),b.a=eB4.Math.max(b.a,u),i=b))}i||((i=new mi).c=c,i.a=u,CD(g,i),P_(i.b,f))}for(s=t.b,l=0,m=new fz(r);m.as?1:0:(e.b&&(e.b._b(a)&&(i=Pp(e.b.xc(a),19).a),e.b._b(u)&&(s=Pp(e.b.xc(u),19).a)),is?1:0);return 0!=t.e.c.length&&0!=n.g.c.length?1:-1}function eYd(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S;for(ewG(t,eWo,1),b=new p0,E=new p0,c=new fz(e.b);c.a0&&(w-=p),eRU(o,w),f=0,h=new fz(o.a);h.a0),s.a.Xb(s.c=--s.b)),u=.4*r*f,!a&&s.bt.d.c){if((p=e.c[t.a.d])==(g=e.c[d.a.d]))continue;eAx(_f(_l(_d(_c(new bQ,1),100),p),g))}}}}}}function eYy(e){var t,n,r,i,a,o,s,u;if(eRe(),null==e)return null;if((i=x7(e,e_n(37)))<0)return e;for(u=new O0(e.substr(0,i)),t=Je(tyk,eZ8,25,4,15,1),s=0,r=0,o=e.length;ii+2&&eoV((GV(i+1,e.length),e.charCodeAt(i+1)),tmZ,tmX)&&eoV((GV(i+2,e.length),e.charCodeAt(i+2)),tmZ,tmX)){if(n=P0((GV(i+1,e.length),e.charCodeAt(i+1)),(GV(i+2,e.length),e.charCodeAt(i+2))),i+=2,r>0?(192&n)==128?t[s++]=n<<24>>24:r=0:n>=128&&((224&n)==192?(t[s++]=n<<24>>24,r=2):(240&n)==224?(t[s++]=n<<24>>24,r=3):(248&n)==240&&(t[s++]=n<<24>>24,r=4)),r>0){if(s==r){switch(s){case 2:Bd(u,((31&t[0])<<6|63&t[1])&eHd);break;case 3:Bd(u,((15&t[0])<<12|(63&t[1])<<6|63&t[2])&eHd)}s=0,r=0}}else{for(a=0;a0){if(o+r>e.length)return!1;s=exf(e.substr(0,o+r),t)}else s=exf(e,t)}switch(a){case 71:return s=ew6(e,o,eow(vx(e17,1),eUP,2,6,[eHM,eHO]),t),i.e=s,!0;case 77:return eLY(e,t,i,s,o);case 76:return eLB(e,t,i,s,o);case 69:return eS$(e,t,o,i);case 99:return eSz(e,t,o,i);case 97:return s=ew6(e,o,eow(vx(e17,1),eUP,2,6,["AM","PM"]),t),i.b=s,!0;case 121:return eLU(e,t,o,s,n,i);case 100:if(s<=0)return!1;return i.c=s,!0;case 83:if(s<0)return!1;return edc(s,o,t[0],i);case 104:12==s&&(s=0);case 75:case 72:if(s<0)return!1;return i.f=s,i.g=!1,!0;case 107:if(s<0)return!1;return i.f=s,i.g=!0,!0;case 109:if(s<0)return!1;return i.j=s,!0;case 115:if(s<0)return!1;return i.n=s,!0;case 90:if(oE&&(p.c=E-p.b),P_(o.d,new jH(p,elO(o,p))),v=t==tbw?eB4.Math.max(v,b.b+c.b.rf().b):eB4.Math.min(v,b.b));for(v+=t==tbw?e.t:-e.t,(y=edp((o.e=v,o)))>0&&(Pp(UA(e.b,t),124).a.b=y),l=d.Kc();l.Ob();)(c=Pp(l.Pb(),111)).c&&!(c.c.d.c.length<=0)&&(p=c.c.i,p.c-=c.e.a,p.d-=c.e.b)}function eYE(e){var t,n,r,i,a,o,s,u,l,f,d,h,p;for(t=new p2,u=new Ow(e);u.e!=u.i.gc();){for(s=Pp(epH(u),33),n=new bV,Um(e9t,s,n),p=new e5,i=Pp(qE(new R1(null,new YI(new Fa(OH(eOr(s).a.Kc(),new c)))),jD(p,JF(new U,new B,new en,eow(vx(e2L,1),eU4,132,0,[(eum(),e2U)])))),83),enC(n,Pp(i.xc((OQ(),!0)),14),new e6),o=(r=Pp(qE(UJ(Pp(i.xc(!1),15).Lc(),new e9),JF(new U,new B,new en,eow(vx(e2L,1),eU4,132,0,[e2U]))),15)).Kc();o.Ob();)(h=e_D(a=Pp(o.Pb(),79)))&&((l=Pp(xu($I(t.f,h)),21))||(l=eA7(h),eS9(t.f,h,l)),er7(n,l));for(i=Pp(qE(new R1(null,new YI(new Fa(OH(eOi(s).a.Kc(),new c)))),jD(p,JF(new U,new B,new en,eow(vx(e2L,1),eU4,132,0,[e2U])))),83),enC(n,Pp(i.xc(!0),14),new e8),d=(r=Pp(qE(UJ(Pp(i.xc(!1),15).Lc(),new e7),JF(new U,new B,new en,eow(vx(e2L,1),eU4,132,0,[e2U]))),15)).Kc();d.Ob();)(h=e_N(f=Pp(d.Pb(),79)))&&((l=Pp(xu($I(t.f,h)),21))||(l=eA7(h),eS9(t.f,h,l)),er7(n,l))}}function eYS(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b;if(ePN(),(u=0>ecd(e,0))&&(e=QC(e)),0==ecd(e,0))switch(t){case 0:return"0";case 1:return e$e;case 2:return"0.00";case 3:return"0.000";case 4:return"0.0000";case 5:return"0.00000";case 6:return"0.000000";default:return h=new vc,t<0?(h.a+="0E+",h):(h.a+="0E",h),h.a+=t==eHt?"2147483648":""+-t,h.a}f=Je(tyw,eHl,25,(l=18)+1,15,1),n=l,b=e;do c=b,b=eyt(b,10),f[--n]=jE(eft(48,efe(c,efn(b,10))))&eHd;while(0!=ecd(b,0))if(i=efe(efe(efe(l,n),t),1),0==t)return u&&(f[--n]=45),ehv(f,n,l-n);if(t>0&&ecd(i,-6)>=0){if(ecd(i,0)>=0){for(a=n+jE(i),s=l-1;s>=a;s--)f[s+1]=f[s];return f[++a]=46,u&&(f[--n]=45),ehv(f,n,l-n+1)}for(o=2;Ei(o,eft(QC(i),1));o++)f[--n]=48;return f[--n]=46,f[--n]=48,u&&(f[--n]=45),ehv(f,n,l-n)}return p=n+1,r=l,d=new vl,u&&(d.a+="-"),r-p>=1?(Bd(d,f[n]),d.a+=".",d.a+=ehv(f,n+1,l-n-1)):d.a+=ehv(f,n,l-n),d.a+="E",ecd(i,0)>0&&(d.a+="+"),d.a+=""+Fb(i),d.a}function eYk(e,t,n){var r,i,a,o,s,u,c,l,f,d,h;if(e.e.a.$b(),e.f.a.$b(),e.c.c=Je(e1R,eUp,1,0,5,1),e.i.c=Je(e1R,eUp,1,0,5,1),e.g.a.$b(),t)for(o=new fz(t.a);o.a=1&&(_-c>0&&p>=0?(eno(f,f.i+w),ens(f,f.j+u*c)):_-c<0&&h>=0&&(eno(f,f.i+w*_),ens(f,f.j+u)));return ebu(e,(eBB(),thx),(ed6(),a=Pp(yw(e6o),9),new I1(a,Pp(CY(a,a.length),9),0))),new kl(E,l)}function eYT(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p;if(h=z$(ewH(Pp(etj((e.b||(e.b=new Ih(e6m,e,4,7)),e.b),0),82))),p=z$(ewH(Pp(etj((e.c||(e.c=new Ih(e6m,e,5,8)),e.c),0),82))),f=h==p,s=new yb,(t=Pp(eT8(e,(euw(),tpj)),74))&&t.b>=2){if(0==(e.a||(e.a=new FQ(e6v,e,6,6)),e.a).i)n=(yT(),i=new oQ),JL((e.a||(e.a=new FQ(e6v,e,6,6)),e.a),n);else if((e.a||(e.a=new FQ(e6v,e,6,6)),e.a).i>1)for(d=new AF((e.a||(e.a=new FQ(e6v,e,6,6)),e.a));d.e!=d.i.gc();)ey_(d);eNI(t,Pp(etj((e.a||(e.a=new FQ(e6v,e,6,6)),e.a),0),202))}if(f)for(r=new Ow((e.a||(e.a=new FQ(e6v,e,6,6)),e.a));r.e!=r.i.gc();)for(n=Pp(epH(r),202),c=new Ow((n.a||(n.a=new O_(e6h,n,5)),n.a));c.e!=c.i.gc();)u=Pp(epH(c),469),s.a=eB4.Math.max(s.a,u.a),s.b=eB4.Math.max(s.b,u.b);for(o=new Ow((e.n||(e.n=new FQ(e6S,e,1,7)),e.n));o.e!=o.i.gc();)a=Pp(epH(o),137),(l=Pp(eT8(a,tp$),8))&&TP(a,l.a,l.b),f&&(s.a=eB4.Math.max(s.a,a.i+a.g),s.b=eB4.Math.max(s.b,a.j+a.f));return s}function eYM(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,k,x;for(s=0,y=t.c.length,i=new eIW(e.a,n,null,null),x=Je(tyx,eH5,25,y,15,1),b=Je(tyx,eH5,25,y,15,1),p=Je(tyx,eH5,25,y,15,1),m=0;sx[u]&&(m=u),f=new fz(e.a.b);f.ah&&(a&&(xL(E,d),xL(k,ell(c.b-1))),A=n.b,L+=d+t,d=0,l=eB4.Math.max(l,n.b+n.c+O)),eno(s,A),ens(s,L),l=eB4.Math.max(l,A+O+n.c),d=eB4.Math.max(d,f),A+=O+t;if(l=eB4.Math.max(l,r),(M=L+d+n.a)ez8,x=eB4.Math.abs(d.b-p.b)>ez8,(!n&&k&&x||n&&(k||x))&&P7(m.a,w)),er7(m.a,r),d=0==r.b?w:(A6(0!=r.b),Pp(r.c.b.c,8)),ea1(h,f,b),eiy(i)==S&&(Bq(S.i)!=i.a&&eSb(b=new yb,Bq(S.i),v),eo3(m,tnC,b)),eEw(h,m,v),l.a.zc(h,l);Gs(m,_),Go(m,S)}for(c=l.a.ec().Kc();c.Ob();)Gs(u=Pp(c.Pb(),17),null),Go(u,null);eEj(t)}function eYC(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w;if(1==e.gc())return Pp(e.Xb(0),231);if(0>=e.gc())return new Z5;for(i=e.Kc();i.Ob();){for(n=Pp(i.Pb(),231),p=0,l=eUu,f=eUu,u=eHt,c=eHt,h=new fz(n.e);h.as&&(y=0,w+=o+g,o=0),eIJ(b,n,y,w),t=eB4.Math.max(t,y+m.a),o=eB4.Math.max(o,m.b),y+=m.a+g;return b}function eYI(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p;switch(l=new mE,e.a.g){case 3:d=Pp(e_k(t.e,(eBU(),tnO)),15),h=Pp(e_k(t.j,tnO),15),p=Pp(e_k(t.f,tnO),15),n=Pp(e_k(t.e,tnT),15),r=Pp(e_k(t.j,tnT),15),i=Pp(e_k(t.f,tnT),15),o=new p0,eoc(o,d),h.Jc(new iN),eoc(o,M4(h,152)?ZK(Pp(h,152)):M4(h,131)?Pp(h,131).a:M4(h,54)?new gn(h):new w$(h)),eoc(o,p),a=new p0,eoc(a,n),eoc(a,M4(r,152)?ZK(Pp(r,152)):M4(r,131)?Pp(r,131).a:M4(r,54)?new gn(r):new w$(r)),eoc(a,i),eo3(t.f,tnO,o),eo3(t.f,tnT,a),eo3(t.f,tnA,t.f),eo3(t.e,tnO,null),eo3(t.e,tnT,null),eo3(t.j,tnO,null),eo3(t.j,tnT,null);break;case 1:er7(l,t.e.a),P7(l,t.i.n),er7(l,eaa(t.j.a)),P7(l,t.a.n),er7(l,t.f.a);break;default:er7(l,t.e.a),er7(l,eaa(t.j.a)),er7(l,t.f.a)}HC(t.f.a),er7(t.f.a,l),Gs(t.f,t.e.c),s=Pp(e_k(t.e,(eBy(),taR)),74),c=Pp(e_k(t.j,taR),74),u=Pp(e_k(t.f,taR),74),(s||c||u)&&(Yp(f=new mE,u),Yp(f,c),Yp(f,s),eo3(t.f,taR,f)),Gs(t.j,null),Go(t.j,null),Gs(t.e,null),Go(t.e,null),Gu(t.a,null),Gu(t.i,null),t.g&&eYI(e,t.g)}function eYD(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p,b,m;if(eNl(),null==e||(a=Q4(e),(p=elw(a))%4!=0))return null;if(0==(b=p/4|0))return Je(tyk,eZ8,25,0,15,1);for(f=null,t=0,n=0,r=0,i=0,o=0,s=0,u=0,c=0,h=0,d=0,l=0,f=Je(tyk,eZ8,25,3*b,15,1);h>4)<<24>>24,f[d++]=((15&n)<<4|r>>2&15)<<24>>24,f[d++]=(r<<6|i)<<24>>24}if(!wl(o=a[l++])||!wl(s=a[l++]))return null;if(t=tvJ[o],n=tvJ[s],u=a[l++],c=a[l++],-1==tvJ[u]||-1==tvJ[c])return 61==u&&61==c?(15&n)!=0?null:(m=Je(tyk,eZ8,25,3*h+1,15,1),ePD(f,0,m,0,3*h),m[d]=(t<<2|n>>4)<<24>>24,m):61==u||61!=c?null:(3&(r=tvJ[u]))!=0?null:(m=Je(tyk,eZ8,25,3*h+2,15,1),ePD(f,0,m,0,3*h),m[d++]=(t<<2|n>>4)<<24>>24,m[d]=((15&n)<<4|r>>2&15)<<24>>24,m);return r=tvJ[u],i=tvJ[c],f[d++]=(t<<2|n>>4)<<24>>24,f[d++]=((15&n)<<4|r>>2&15)<<24>>24,f[d++]=(r<<6|i)<<24>>24,f}function eYN(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_;for(ewG(t,eWo,1),p=Pp(e_k(e,(eBy(),tag)),218),i=new fz(e.b);i.a=2){for(b=!0,n=Pp(Wx(d=new fz(a.j)),11),h=null;d.a0&&(i=Pp(RJ(m.c.a,E-1),10),o=e.i[i.p],k=eB4.Math.ceil(Mj(e.n,i,m)),a=_.a.e-m.d.d-(o.a.e+i.o.b+i.d.a)-k),c=eHQ,E0&&S.a.e.e-S.a.a-(S.b.e.e-S.b.a)<0,p=y.a.e.e-y.a.a-(y.b.e.e-y.b.a)<0&&S.a.e.e-S.a.a-(S.b.e.e-S.b.a)>0,h=y.a.e.e+y.b.aS.b.e.e+S.a.a,w=0,!b&&!p&&(d?a+f>0?w=f:c-r>0&&(w=r):h&&(a+s>0?w=s:c-v>0&&(w=v))),_.a.e+=w,_.b&&(_.d.e+=w),!1))}function eYR(e,t,n){var r,i,a,o,s,u,c,l,f,d;if(r=new Hr(t.qf().a,t.qf().b,t.rf().a,t.rf().b),i=new TE,e.c)for(o=new fz(t.wf());o.ac&&(r.a+=M3(Je(tyw,eHl,25,-c,15,1))),r.a+="Is",x7(u,e_n(32))>=0)for(i=0;i=r.o.b/2}v?(g=Pp(e_k(r,(eBU(),tnI)),15))?d?a=g:(i=Pp(e_k(r,ttB),15))?a=g.gc()<=i.gc()?g:i:(a=new p0,eo3(r,ttB,a)):(a=new p0,eo3(r,tnI,a)):(i=Pp(e_k(r,(eBU(),ttB)),15))?f?a=i:(g=Pp(e_k(r,tnI),15))?a=i.gc()<=g.gc()?i:g:(a=new p0,eo3(r,tnI,a)):(a=new p0,eo3(r,ttB,a)),a.Fc(e),eo3(e,(eBU(),ttH),n),t.d==n?(Go(t,null),n.e.c.length+n.g.c.length==0&&Gc(n,null),esQ(n)):(Gs(t,null),n.e.c.length+n.g.c.length==0&&Gc(n,null)),HC(t.a)}function eYH(e,t){var n,r,i,a,o,s,u,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A,L;for(y=new KB(e.b,0),f=t.Kc(),b=0,l=Pp(f.Pb(),19).a,E=0,n=new bV,k=new Tw;y.b=e.a&&(r=eN3(e,y),f=eB4.Math.max(f,r.b),_=eB4.Math.max(_,r.d),P_(s,new kD(y,r)));for(l=0,x=new p0;l0),g.a.Xb(g.c=--g.b),T=new By(e.b),CD(g,T),A6(g.b0?(c=0,m&&(c+=s),c+=(x-1)*o,y&&(c+=s),k&&y&&(c=eB4.Math.max(c,eAD(y,o,v,S))),!(c0){for(i=0,d=l<100?null:new yf(l),p=(c=new eiP(t)).g,g=Je(ty_,eHT,25,l,15,1),r=0,w=new eta(l);i=0;)if(null!=h?ecX(h,p[u]):xc(h)===xc(p[u])){g.length<=r&&(m=g,g=Je(ty_,eHT,25,2*g.length,15,1),ePD(m,0,g,0,r)),g[r++]=i,JL(w,p[u]);break v}if(xc(h)===xc(s))break}}if(c=w,p=w.g,l=r,r>g.length&&(m=g,g=Je(ty_,eHT,25,r,15,1),ePD(m,0,g,0,r)),r>0){for(a=0,y=!0;a=0;)egk(e,g[o]);if(r!=l){for(i=l;--i>=r;)egk(c,i);m=g,g=Je(ty_,eHT,25,r,15,1),ePD(m,0,g,0,r)}t=c}}}else for(t=egh(e,t),i=e.i;--i>=0;)t.Hc(e.g[i])&&(egk(e,i),y=!0);if(!y)return!1;if(null!=g){for(f=1==(n=t.gc())?Gt(e,4,t.Kc().Pb(),null,g[0],b):Gt(e,6,t,g,g[0],b),d=n<100?null:new yf(n),i=t.Kc();i.Ob();)d=IW(e,Pp(h=i.Pb(),72),d);d?(d.Ei(f),d.Fi()):eam(e.e,f)}else{for(d=IP(t.gc()),i=t.Kc();i.Ob();)d=IW(e,Pp(h=i.Pb(),72),d);d&&d.Fi()}return!0}function eYV(e,t){var n,r,i,a,o,s,u,l,f,d,h,p,b,m,g,v,y,w;for((n=new eb_(t)).a||eDc(t),l=eCx(t),u=new zu,g=new eLy,m=new fz(t.a);m.a0||n.o==tuS&&i0?(f=Pp(RJ(d.c.a,o-1),10),k=Mj(e.b,d,f),m=d.n.b-d.d.d-(f.n.b+f.o.b+f.d.a+k)):m=d.n.b-d.d.d,c=eB4.Math.min(m,c),oo?eIc(e,t,n):eIc(e,n,t),io?1:0}return r=Pp(e_k(t,(eBU(),tnu)),19).a,a=Pp(e_k(n,tnu),19).a,r>a?eIc(e,t,n):eIc(e,n,t),ra?1:0}function eYQ(e,t,n,r){var i,a,o,s,u,c,l,f,d,h,p,b,m,g,v;if(gN(LK(eT8(t,(eBB(),thI))))||(c=0!=(t.a||(t.a=new FQ(e6k,t,10,11)),t.a).i,l=!(f=ekq(t)).dc(),!c&&!l))return Hj(),Hj(),e2r;if(!(i=Pp(eT8(t,th6),149)))throw p7(new gq("Resolved algorithm is not set; apply a LayoutAlgorithmResolver before computing layout."));if(v=ka(i,(eTy(),tmC)),ept(t),!c&&l&&!v)return Hj(),Hj(),e2r;if(u=new p0,xc(eT8(t,thl))===xc((eck(),tpz))&&(ka(i,tmO)||ka(i,tmM)))for(h=eCL(e,t),er7(p=new _n,(t.a||(t.a=new FQ(e6k,t,10,11)),t.a));0!=p.b;)ept(d=Pp(0==p.b?null:(A6(0!=p.b),etw(p,p.a.a)),33)),(g=xc(eT8(d,thl))===xc(tpW))||X2(d,tdQ)&&!Zs(i,eT8(d,th6))?(s=eYQ(e,d,n,r),eoc(u,s),ebu(d,thl,tpW),eIU(d)):er7(p,(d.a||(d.a=new FQ(e6k,d,10,11)),d.a));else for(h=(t.a||(t.a=new FQ(e6k,t,10,11)),t.a).i,o=new Ow((t.a||(t.a=new FQ(e6k,t,10,11)),t.a));o.e!=o.i.gc();)a=Pp(epH(o),33),s=eYQ(e,a,n,r),eoc(u,s),eIU(a);for(m=new fz(u);m.a=0?ef9(s):elC(ef9(s)),e.Ye(tob,h)),c=new yb,d=!1,e.Xe(tou)?(Lf(c,Pp(e.We(tou),8)),d=!0):Oc(c,o.a/2,o.b/2),h.g){case 4:eo3(l,taY,(ef_(),tnN)),eo3(l,ttV,(eoG(),te0)),l.o.b=o.b,b<0&&(l.o.a=-b),ekv(f,(eYu(),tby)),d||(c.a=o.a),c.a-=o.a;break;case 2:eo3(l,taY,(ef_(),tnR)),eo3(l,ttV,(eoG(),teQ)),l.o.b=o.b,b<0&&(l.o.a=-b),ekv(f,(eYu(),tbY)),d||(c.a=0);break;case 1:eo3(l,tt9,(Q1(),ttN)),l.o.a=o.a,b<0&&(l.o.b=-b),ekv(f,(eYu(),tbj)),d||(c.b=o.b),c.b-=o.b;break;case 3:eo3(l,tt9,(Q1(),ttI)),l.o.a=o.a,b<0&&(l.o.b=-b),ekv(f,(eYu(),tbw)),d||(c.b=0)}if(Lf(f.n,c),eo3(l,tou,c),t==tba||t==tbs||t==tbo){if(p=0,t==tba&&e.Xe(tof))switch(h.g){case 1:case 2:p=Pp(e.We(tof),19).a;break;case 3:case 4:p=-Pp(e.We(tof),19).a}else switch(h.g){case 4:case 2:p=a.b,t==tbs&&(p/=i.b);break;case 1:case 3:p=a.a,t==tbs&&(p/=i.a)}eo3(l,tnv,p)}return eo3(l,tt1,h),l}function eY0(e){var t,n,r,i,a,o,s,u,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T;if((n=gP(LV(e_k(e.a.j,(eBy(),tar)))))<-1||!e.a.i||IR(Pp(e_k(e.a.o,tol),98))||2>efr(e.a.o,(eYu(),tby)).gc()&&2>efr(e.a.o,tbY).gc())return!0;if(e.a.c.Rf())return!1;for(E=0,_=0,w=new p0,u=e.a.e,l=0,f=u.length;l=n}function eY2(){function n(e){var t=this;this.dispatch=function(t){var n=t.data;switch(n.cmd){case"algorithms":var r=edh((Hj(),new fF(new fT(tmF.b))));e.postMessage({id:n.id,data:r});break;case"categories":var i=edh((Hj(),new fF(new fT(tmF.c))));e.postMessage({id:n.id,data:i});break;case"options":var a=edh((Hj(),new fF(new fT(tmF.d))));e.postMessage({id:n.id,data:a});break;case"register":ejy(n.algorithms),e.postMessage({id:n.id});break;case"layout":ePu(n.graph,n.layoutOptions||{},n.options||{}),e.postMessage({id:n.id,data:n.graph})}},this.saveDispatch=function(n){try{t.dispatch(n)}catch(r){e.postMessage({id:n.data.id,error:r})}}}function r(e){var t=this;this.dispatcher=new n({postMessage:function(e){t.onmessage({data:e})}}),this.postMessage=function(e){setTimeout(function(){t.dispatcher.saveDispatch({data:e})},0)}}if(yC(),typeof document===e$E&&typeof self!==e$E){var i=new n(self);self.onmessage=i.saveDispatch}else"object"!==e$E&&e.exports&&(Object.defineProperty(t,"__esModule",{value:!0}),e.exports={default:r,Worker:r})}function eY3(e){e.N||(e.N=!0,e.b=eak(e,0),er6(e.b,0),er6(e.b,1),er6(e.b,2),e.bb=eak(e,1),er6(e.bb,0),er6(e.bb,1),e.fb=eak(e,2),er6(e.fb,3),er6(e.fb,4),er9(e.fb,5),e.qb=eak(e,3),er6(e.qb,0),er9(e.qb,1),er9(e.qb,2),er6(e.qb,3),er6(e.qb,4),er9(e.qb,5),er6(e.qb,6),e.a=eax(e,4),e.c=eax(e,5),e.d=eax(e,6),e.e=eax(e,7),e.f=eax(e,8),e.g=eax(e,9),e.i=eax(e,10),e.j=eax(e,11),e.k=eax(e,12),e.n=eax(e,13),e.o=eax(e,14),e.p=eax(e,15),e.q=eax(e,16),e.s=eax(e,17),e.r=eax(e,18),e.t=eax(e,19),e.u=eax(e,20),e.v=eax(e,21),e.w=eax(e,22),e.B=eax(e,23),e.A=eax(e,24),e.C=eax(e,25),e.D=eax(e,26),e.F=eax(e,27),e.G=eax(e,28),e.H=eax(e,29),e.J=eax(e,30),e.I=eax(e,31),e.K=eax(e,32),e.M=eax(e,33),e.L=eax(e,34),e.P=eax(e,35),e.Q=eax(e,36),e.R=eax(e,37),e.S=eax(e,38),e.T=eax(e,39),e.U=eax(e,40),e.V=eax(e,41),e.X=eax(e,42),e.W=eax(e,43),e.Y=eax(e,44),e.Z=eax(e,45),e.$=eax(e,46),e._=eax(e,47),e.ab=eax(e,48),e.cb=eax(e,49),e.db=eax(e,50),e.eb=eax(e,51),e.gb=eax(e,52),e.hb=eax(e,53),e.ib=eax(e,54),e.jb=eax(e,55),e.kb=eax(e,56),e.lb=eax(e,57),e.mb=eax(e,58),e.nb=eax(e,59),e.ob=eax(e,60),e.pb=eax(e,61))}function eY4(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w;if(v=0,0==t.f.a)for(m=new fz(e);m.ac&&0==(GK(c,t.c.length),Pp(t.c[c],200)).a.c.length;)QA(t,(GK(c,t.c.length),t.c[c]));if(!u){--a;continue}if(eDk(t,l,i,u,d,n,c,r)){f=!0;continue}if(d){if(ePx(t,l,i,u,n,c,r)){f=!0;continue}if(eu4(l,i)){i.c=!0,f=!0;continue}}else if(eu4(l,i)){i.c=!0,f=!0;continue}if(f)continue}if(eu4(l,i)){i.c=!0,f=!0,u&&(u.k=!1);continue}emG(i.q)}return f}function eY9(e,t,n,r,i,a,o){var s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A,L;for(b=0,T=0,c=new fz(e.b);c.ab&&(a&&(xL(E,h),xL(k,ell(l.b-1)),P_(e.d,p),s.c=Je(e1R,eUp,1,0,5,1)),A=n.b,L+=h+t,h=0,f=eB4.Math.max(f,n.b+n.c+O)),s.c[s.c.length]=u,epW(u,A,L),f=eB4.Math.max(f,A+O+n.c),h=eB4.Math.max(h,d),A+=O+t,p=u;if(eoc(e.a,s),P_(e.d,Pp(RJ(s,s.c.length-1),157)),f=eB4.Math.max(f,r),(M=L+h+n.a)1&&(o=eB4.Math.min(o,eB4.Math.abs(Pp(ep3(s.a,1),8).b-l.b)))));else for(b=new fz(t.j);b.ai&&(a=d.a-i,o=eUu,r.c=Je(e1R,eUp,1,0,5,1),i=d.a),d.a>=i&&(r.c[r.c.length]=s,s.a.b>1&&(o=eB4.Math.min(o,eB4.Math.abs(Pp(ep3(s.a,s.a.b-2),8).b-d.b)))));if(0!=r.c.length&&a>t.o.a/2&&o>t.o.b/2){for(h=new eES,Gc(h,t),ekv(h,(eYu(),tbw)),h.n.a=t.o.a/2,g=new eES,Gc(g,t),ekv(g,tbj),g.n.a=t.o.a/2,g.n.b=t.o.b,u=new fz(r);u.a=c.b?Gs(s,g):Gs(s,h)):(c=Pp(P$(s.a),8),(m=0==s.a.b?GX(s.c):Pp(AZ(s.a),8)).b>=c.b?Go(s,g):Go(s,h)),(f=Pp(e_k(s,(eBy(),taR)),74))&&eds(f,c,!0);t.n.a=i-t.o.a/2}}function eBe(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A,L,C,I;if(T=null,O=t,M=V0(e,VF(n),O),ert(M,KJ(O,eXS)),A=Pp(etJ(e.g,ekZ(zR(O,eXi))),33),d=zR(O,"sourcePort"),r=null,d&&(r=ekZ(d)),L=Pp(etJ(e.j,r),118),!A)throw b=(p="An edge must have a source node (edge id: '"+(s=ehM(O)))+eXO,p7(new gK(b));if(L&&!BG(zY(L),A))throw g=(m="The source port of an edge must be a port of the edge's source node (edge id: '"+(u=KJ(O,eXS)))+eXO,p7(new gK(g));if(k=(M.b||(M.b=new Ih(e6m,M,4,7)),M.b),a=null,JL(k,a=L||A),C=Pp(etJ(e.g,ekZ(zR(O,eXC))),33),h=zR(O,"targetPort"),i=null,h&&(i=ekZ(h)),I=Pp(etJ(e.j,i),118),!C)throw y=(v="An edge must have a target node (edge id: '"+(f=ehM(O)))+eXO,p7(new gK(y));if(I&&!BG(zY(I),C))throw _=(w="The target port of an edge must be a port of the edge's target node (edge id: '"+(c=KJ(O,eXS)))+eXO,p7(new gK(_));if(x=(M.c||(M.c=new Ih(e6m,M,5,8)),M.c),o=null,JL(x,o=I||C),0==(M.b||(M.b=new Ih(e6m,M,4,7)),M.b).i||0==(M.c||(M.c=new Ih(e6m,M,5,8)),M.c).i)throw S=(E=eXM+(l=KJ(O,eXS)))+eXO,p7(new gK(S));return ewU(O,M),eMu(O,M),T=esv(e,O,M)}function eBt(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T;return f=eNf(A_(e,(eYu(),tbx)),t),p=em9(A_(e,tbT),t),w=em9(A_(e,tbN),t),k=em8(A_(e,tbR),t),d=em8(A_(e,tb_),t),v=em9(A_(e,tbD),t),b=em9(A_(e,tbM),t),E=em9(A_(e,tbP),t),_=em9(A_(e,tbE),t),x=em8(A_(e,tbk),t),g=em9(A_(e,tbC),t),y=em9(A_(e,tbL),t),S=em9(A_(e,tbS),t),T=em8(A_(e,tbI),t),h=em8(A_(e,tbO),t),m=em9(A_(e,tbA),t),n=esm(eow(vx(tyx,1),eH5,25,15,[v.a,k.a,E.a,T.a])),r=esm(eow(vx(tyx,1),eH5,25,15,[p.a,f.a,w.a,m.a])),i=g.a,a=esm(eow(vx(tyx,1),eH5,25,15,[b.a,d.a,_.a,h.a])),c=esm(eow(vx(tyx,1),eH5,25,15,[v.b,p.b,b.b,y.b])),u=esm(eow(vx(tyx,1),eH5,25,15,[k.b,f.b,d.b,m.b])),l=x.b,s=esm(eow(vx(tyx,1),eH5,25,15,[E.b,w.b,_.b,S.b])),JD(A_(e,tbx),n+i,c+l),JD(A_(e,tbA),n+i,c+l),JD(A_(e,tbT),n+i,0),JD(A_(e,tbN),n+i,c+l+u),JD(A_(e,tbR),0,c+l),JD(A_(e,tb_),n+i+r,c+l),JD(A_(e,tbM),n+i+r,0),JD(A_(e,tbP),0,c+l+u),JD(A_(e,tbE),n+i+r,c+l+u),JD(A_(e,tbk),0,c),JD(A_(e,tbC),n,0),JD(A_(e,tbS),0,c+l+u),JD(A_(e,tbO),n+i+r,0),(o=new yb).a=esm(eow(vx(tyx,1),eH5,25,15,[n+r+i+a,x.a,y.a,S.a])),o.b=esm(eow(vx(tyx,1),eH5,25,15,[c+u+l+s,g.b,T.b,h.b])),o}function eBn(e){var t,n,r,i,a,o,s,u,l,f,d,h,p,b,m,g;for(m=new p0,h=new fz(e.d.b);h.ai.d.d+i.d.a?f.f.d=!0:(f.f.d=!0,f.f.a=!0))),r.b!=r.d.c&&(t=n);f&&(a=Pp(Bp(e.f,o.d.i),57),t.ba.d.d+a.d.a?f.f.d=!0:(f.f.d=!0,f.f.a=!0))}for(s=new Fa(OH(efu(p).a.Kc(),new c));eTk(s);)0!=(o=Pp(ZC(s),17)).a.b&&(t=Pp(AZ(o.a),8),o.d.j==(eYu(),tbw)&&((g=new ePe(t,new kl(t.a,i.d.d),i,o)).f.a=!0,g.a=o.d,m.c[m.c.length]=g),o.d.j==tbj&&((g=new ePe(t,new kl(t.a,i.d.d+i.d.a),i,o)).f.d=!0,g.a=o.d,m.c[m.c.length]=g))}return m}function eBr(e,t,n){var r,i,a,o,s,u,c,l,f;if(ewG(n,"Network simplex node placement",1),e.e=t,e.n=Pp(e_k(t,(eBU(),tnx)),304),eRx(e),ey8(e),_r(eeh(new R1(null,new Gq(e.e.b,16)),new i2),new hR(e)),_r(UJ(eeh(UJ(eeh(new R1(null,new Gq(e.e.b,16)),new aa),new ao),new as),new au),new hP(e)),gN(LK(e_k(e.e,(eBy(),taQ))))&&(o=eiI(n,1),ewG(o,"Straight Edges Pre-Processing",1),eFy(e),eEj(o)),ebR(e.f),a=Pp(e_k(t,to$),19).a*e.f.a.c.length,eIX(vC(vI(DN(e.f),a),!1),eiI(n,1)),0!=e.d.a.gc()){for(o=eiI(n,1),ewG(o,"Flexible Where Space Processing",1),s=Pp(Af(FM(UQ(new R1(null,new Gq(e.f.a,16)),new i3),new iZ)),19).a,c=(u=Pp(Af(FT(UQ(new R1(null,new Gq(e.f.a,16)),new i4),new iX)),19).a)-s,l=Al(new b1,e.f),f=Al(new b1,e.f),eAx(_f(_l(_c(_d(new bQ,2e4),c),l),f)),_r(UJ(UJ(Yw(e.i),new i5),new i6),new Hn(s,l,c,f)),i=e.d.a.ec().Kc();i.Ob();)(r=Pp(i.Pb(),213)).g=1;eIX(vC(vI(DN(e.f),a),!1),eiI(o,1)),eEj(o)}gN(LK(e_k(t,taQ)))&&(o=eiI(n,1),ewG(o,"Straight Edges Post-Processing",1),eSf(e),eEj(o)),ej3(e),e.e=null,e.f=null,e.i=null,e.c=null,Yy(e.k),e.j=null,e.a=null,e.o=null,e.d.a.$b(),eEj(n)}function eBi(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_;for(s=new fz(e.a.b);s.a0){if(r=f.gc(),c=zy(eB4.Math.floor((r+1)/2))-1,i=zy(eB4.Math.ceil((r+1)/2))-1,t.o==tuS)for(l=i;l>=c;l--)t.a[w.p]==w&&(b=Pp(f.Xb(l),46),p=Pp(b.a,10),!w0(n,b.b)&&h>e.b.e[p.p]&&(t.a[p.p]=w,t.g[w.p]=t.g[p.p],t.a[w.p]=t.g[w.p],t.f[t.g[w.p].p]=(OQ(),!!(gN(t.f[t.g[w.p].p])&w.k==(eEn(),e8D))),h=e.b.e[p.p]));else for(l=c;l<=i;l++)t.a[w.p]==w&&(g=Pp(f.Xb(l),46),m=Pp(g.a,10),!w0(n,g.b)&&h=p&&(v>p&&(h.c=Je(e1R,eUp,1,0,5,1),p=v),h.c[h.c.length]=o);0!=h.c.length&&(d=Pp(RJ(h,ebO(t,h.c.length)),128),M.a.Bc(d),d.s=b++,eM4(d,x,E),h.c=Je(e1R,eUp,1,0,5,1))}for(w=e.c.length+1,s=new fz(e);s.aT.s&&(BH(n),QA(T.i,r),r.c>0&&(r.a=T,P_(T.t,r),r.b=S,P_(S.i,r)))}function eBs(e){var t,n,r,i,a;switch(t=e.c){case 11:return e.Ml();case 12:return e.Ol();case 14:return e.Ql();case 15:return e.Tl();case 16:return e.Rl();case 17:return e.Ul();case 21:return eBM(e),eBG(),eBG(),tye;case 10:switch(e.a){case 65:return e.yl();case 90:return e.Dl();case 122:return e.Kl();case 98:return e.El();case 66:return e.zl();case 60:return e.Jl();case 62:return e.Hl()}}switch(a=eY8(e),t=e.c){case 3:return e.Zl(a);case 4:return e.Xl(a);case 5:return e.Yl(a);case 0:if(123==e.a&&e.d=48&&t<=57){for(r=t-48;i=48&&t<=57;)if((r=10*r+t-48)<0)throw p7(new gX(eBJ((Mo(),eJ_))))}else throw p7(new gX(eBJ((Mo(),eJg))));if(n=r,44==t){if(i>=e.j)throw p7(new gX(eBJ((Mo(),eJy))));if((t=UI(e.i,i++))>=48&&t<=57){for(n=t-48;i=48&&t<=57;)if((n=10*n+t-48)<0)throw p7(new gX(eBJ((Mo(),eJ_))));if(r>n)throw p7(new gX(eBJ((Mo(),eJw))))}else n=-1}if(125!=t)throw p7(new gX(eBJ((Mo(),eJv))));e.sl(i)?(a=(eBG(),eBG(),++tyv,new qa(9,a)),e.d=i+1):(a=(eBG(),eBG(),++tyv,new qa(3,a)),e.d=i),a.dm(r),a.cm(n),eBM(e)}}return a}function eBu(e,t,n,r,i){var a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M;for(b=new XM(t.b),w=new XM(t.b),d=new XM(t.b),k=new XM(t.b),m=new XM(t.b),S=epL(t,0);S.b!=S.d.c;)for(_=Pp(Vv(S),11),s=new fz(_.g);s.a0,g=_.g.c.length>0,c&&g?d.c[d.c.length]=_:c?b.c[b.c.length]=_:g&&(w.c[w.c.length]=_);for(p=new fz(b);p.aefT(Jh(y.d,x),Jh(y.d,y.a))&&(a.c[a.c.length]=y);for(n.c=Je(e1R,eUp,1,0,5,1),w=new fz(a);w.a1)for(p=new AF((e.a||(e.a=new FQ(e6v,e,6,6)),e.a));p.e!=p.i.gc();)ey_(p);for(o=Pp(etj((e.a||(e.a=new FQ(e6v,e,6,6)),e.a),0),202),m=A,A>_+w?m=_+w:A<_-w&&(m=_-w),g=L,L>E+b?g=E+b:L_-w&&m<_+w&&g>E-b&&gA+O?k=A+O:_L+S?x=L+S:EA-O&&kL-S&&xn&&(d=n-1),(h=P+eMU(t,24)*e$h*f-f/2)<0?h=1:h>r&&(h=r-1),i=(yT(),u=new oJ),ent(i,d),enn(i,h),JL((o.a||(o.a=new O_(e6h,o,5)),o.a),i)}function eBy(){eBy=A,tox=(eBB(),th7),toT=tpe,toM=tpt,toO=tpn,toL=tpr,toC=tpi,toN=tpo,toR=tpu,toj=tpc,toP=tps,toF=tpl,toB=tpf,toH=tpp,toD=tpa,tok=(eBH(),tih),toA=tip,toI=tib,toY=tim,tov=new T2(th4,ell(0)),toy=til,tow=tif,to_=tid,toQ=tiB,toG=tiy,toW=tiE,toq=tiL,toK=tix,toV=tiM,to0=tiG,to1=tiH,toX=tiR,toZ=tiN,toJ=tiF,ta0=tit,ta2=tin,taE=trE,taS=trx,toe=new T3(12),ta7=new T2(thN,toe),tav=(efE(),tpx),tag=new T2(tha,tav),toc=new T2(thK,0),toE=new T2(th5,ell(1)),tiX=new T2(td2,eGt),ta8=thI,tol=thV,tob=th0,tac=td7,tiq=td1,taM=thl,toS=new T2(th8,(OQ(),!0)),taI=thh,taD=thp,ta4=thx,ta9=thL,ta5=thM,tad=(ec3(),tpv),tal=new T2(the,tad),taZ=thS,taq=th_,toh=thJ,tod=thX,top=th1,tor=(epT(),tbr),new T2(thB,tor),toa=th$,too=thz,tos=thG,toi=thH,toz=tiv,taG=trZ,taz=trV,to$=tig,taY=trB,tau=trs,tas=tra,ti7=tn1,tae=tn0,tan=tn6,tat=tn2,tao=trr,taK=trJ,taV=trQ,taP=trD,ta3=tio,taJ=tr3,tax=trO,ta1=tr7,taw=trg,ta_=trw,ti8=td9,taX=tr1,ti0=tn$,ti1=tnU,tiQ=tnB,taA=trC,taO=trL,taL=trI,ta6=thO,taR=thg,tak=ths,tab=thr,tap=thn,tar=tn7,tof=thZ,tiJ=td6,taC=thd,tou=thW,tot=thR,ton=thF,taU=tr$,taH=trG,tog=th3,tiZ=tnY,ta$=trK,tam=trh,tah=trf,taW=thy,taj=trj,taQ=tr6,toU=tpd,taf=trc,tom=tiu,tay=trb,taF=trY,tai=trt,taN=thm,taB=trH,taa=trn,ti9=tnJ,ti5=tnq,ti3=tnK,ti4=tnV,ti6=tnX,ti2=tnG,taT=trA}function eBw(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A;if(ePN(),k=e.e,p=e.d,i=e.a,0==k)switch(t){case 0:return"0";case 1:return e$e;case 2:return"0.00";case 3:return"0.000";case 4:return"0.0000";case 5:return"0.00000";case 6:return"0.000000";default:return E=new vc,t<0?(E.a+="0E+",E):(E.a+="0E",E),E.a+=-t,E.a}if(w=Je(tyw,eHl,25,(y=10*p+1+7)+1,15,1),n=y,1==p){if((s=i[0])<0){A=WM(s,eH8);do b=A,A=eyt(A,10),w[--n]=48+jE(efe(b,efn(A,10)))&eHd;while(0!=ecd(A,0))}else{A=s;do b=A,A=A/10|0,w[--n]=48+(b-10*A)&eHd;while(0!=A)}}else{T=Je(ty_,eHT,25,p,15,1),ePD(i,0,T,0,O=p);I:for(;;){for(S=0,c=O-1;c>=0;c--)g=ewT(M=eft(Fg(S,32),WM(T[c],eH8))),T[c]=jE(g),S=jE(Fv(g,32));v=jE(S),m=n;do w[--n]=48+v%10&eHd;while(0!=(v=v/10|0)&&0!=n)for(u=0,r=9-m+n;u0;u++)w[--n]=48;for(f=O-1;0==T[f];f--)if(0==f)break I;O=f+1}for(;48==w[n];)++n}if(h=k<0,o=y-n-t-1,0==t)return h&&(w[--n]=45),ehv(w,n,y-n);if(t>0&&o>=-6){if(o>=0){for(l=n+o,d=y-1;d>=l;d--)w[d+1]=w[d];return w[++l]=46,h&&(w[--n]=45),ehv(w,n,y-n+1)}for(f=2;f<-o+1;f++)w[--n]=48;return w[--n]=46,w[--n]=48,h&&(w[--n]=45),ehv(w,n,y-n)}return x=n+1,a=y,_=new vl,h&&(_.a+="-"),a-x>=1?(Bd(_,w[n]),_.a+=".",_.a+=ehv(w,n+1,y-n-1)):_.a+=ehv(w,n,y-n),_.a+="E",o>0&&(_.a+="+"),_.a+=""+o,_.a}function eB_(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E;switch(e.c=t,e.g=new p2,n=(_q(),new gM(e.c)),efJ(r=new dp(n)),y=Lq(eT8(e.c,(e_L(),tfD))),u=Pp(eT8(e.c,tfP),316),_=Pp(eT8(e.c,tfR),429),o=Pp(eT8(e.c,tfO),482),w=Pp(eT8(e.c,tfN),430),e.j=gP(LV(eT8(e.c,tfj))),s=e.a,u.g){case 0:s=e.a;break;case 1:s=e.b;break;case 2:s=e.i;break;case 3:s=e.e;break;case 4:s=e.f;break;default:throw p7(new gL(eqN+(null!=u.f?u.f:""+u.g)))}if(e.d=new zM(s,_,o),eo3(e.d,(ei6(),e6F),LK(eT8(e.c,tfL))),e.d.c=gN(LK(eT8(e.c,tfA))),0==H8(e.c).i)return e.d;for(f=new Ow(H8(e.c));f.e!=f.i.gc();){for(h=(l=Pp(epH(f),33)).g/2,d=l.f/2,E=new kl(l.i+h,l.j+d);F9(e.g,E);)Lu(E,(eB4.Math.random()-.5)*ez8,(eB4.Math.random()-.5)*ez8);b=Pp(eT8(l,(eBB(),thy)),142),m=new Gd(E,new Hr(E.a-h-e.j/2-b.b,E.b-d-e.j/2-b.d,l.g+e.j+(b.b+b.c),l.f+e.j+(b.d+b.a))),P_(e.d.i,m),Um(e.g,E,new kD(m,l))}switch(w.g){case 0:if(null==y)e.d.d=Pp(RJ(e.d.i,0),65);else for(v=new fz(e.d.i);v.a1&&qQ(l,g,l.c.b,l.c),etu(i)));g=v}return l}function eBS(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A,L,C,I,D;for(ewG(n,"Greedy cycle removal",1),D=(y=t.a).c.length,e.a=Je(ty_,eHT,25,D,15,1),e.c=Je(ty_,eHT,25,D,15,1),e.b=Je(ty_,eHT,25,D,15,1),c=0,g=new fz(y);g.a0?O+1:1);for(o=new fz(E.g);o.a0?O+1:1)}0==e.c[c]?P7(e.e,b):0==e.a[c]&&P7(e.f,b),++c}for(p=-1,h=1,f=new p0,e.d=Pp(e_k(t,(eBU(),tnw)),230);D>0;){for(;0!=e.e.b;)L=Pp(PH(e.e),10),e.b[L.p]=p--,eIQ(e,L),--D;for(;0!=e.f.b;)C=Pp(PH(e.f),10),e.b[C.p]=h++,eIQ(e,C),--D;if(D>0){for(d=eHt,v=new fz(y);v.a=d&&(w>d&&(f.c=Je(e1R,eUp,1,0,5,1),d=w),f.c[f.c.length]=b);l=e.Zf(f),e.b[l.p]=h++,eIQ(e,l),--D}}for(c=0,A=y.c.length+1;ce.b[I]&&(eNF(r,!0),eo3(t,ttK,(OQ(),!0)));e.a=null,e.c=null,e.b=null,HC(e.f),HC(e.e),eEj(n)}function eBk(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g;for(r=new p0,s=new p0,m=t/2,h=e.gc(),i=Pp(e.Xb(0),8),g=Pp(e.Xb(1),8),p=eT5(i.a,i.b,g.a,g.b,m),P_(r,(GK(0,p.c.length),Pp(p.c[0],8))),P_(s,(GK(1,p.c.length),Pp(p.c[1],8))),c=2;c=0;u--)P7(n,(GK(u,o.c.length),Pp(o.c[u],8)));return n}function eBx(e){var t,n,r,i,a,o,s,u,c,l,f,d,h;if(o=!0,f=null,r=null,i=null,t=!1,h=tmH,c=null,a=null,(u=epm(e,s=0,tmJ,tmQ))=0&&IE(e.substr(s,2),"//")?(s+=2,u=epm(e,s,tm1,tm0),r=e.substr(s,u-s),s=u):null!=f&&(s==e.length||(GV(s,e.length),47!=e.charCodeAt(s)))&&(o=!1,-1==(u=O7(e,e_n(35),s))&&(u=e.length),r=e.substr(s,u-s),s=u);if(!n&&s0&&58==UI(l,l.length-1)&&(i=l,s=u)),s=e.j){e.a=-1,e.c=1;return}if(t=UI(e.i,e.d++),e.a=t,1==e.b){switch(t){case 92:if(r=10,e.d>=e.j)throw p7(new gX(eBJ((Mo(),eXZ))));e.a=UI(e.i,e.d++);break;case 45:(512&e.e)==512&&e.d=e.j||63!=UI(e.i,e.d))break;if(++e.d>=e.j)throw p7(new gX(eBJ((Mo(),eXX))));switch(t=UI(e.i,e.d++)){case 58:r=13;break;case 61:r=14;break;case 33:r=15;break;case 91:r=19;break;case 62:r=18;break;case 60:if(e.d>=e.j)throw p7(new gX(eBJ((Mo(),eXX))));if(61==(t=UI(e.i,e.d++)))r=16;else if(33==t)r=17;else throw p7(new gX(eBJ((Mo(),eXJ))));break;case 35:for(;e.d=e.j)throw p7(new gX(eBJ((Mo(),eXZ))));e.a=UI(e.i,e.d++);break;default:r=0}e.c=r}function eBO(e){var t,n,r,i,a,o,s,u,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A;if((k=Pp(e_k(e,(eBy(),tol)),98))!=(ewf(),tbc)&&k!=tbl){for(p=(b=e.b).c.length,f=new XM((enG(p+2,eU6),ee1(eft(eft(5,p+2),(p+2)/10|0)))),m=new XM((enG(p+2,eU6),ee1(eft(eft(5,p+2),(p+2)/10|0)))),P_(f,new p2),P_(f,new p2),P_(m,new p0),P_(m,new p0),S=new p0,t=0;t=E||!ehf(v,r))&&(r=GY(t,f)),Gu(v,r),a=new Fa(OH(efu(v).a.Kc(),new c));eTk(a);)i=Pp(ZC(a),17),!e.a[i.p]&&(m=i.c.i,--e.e[m.p],0==e.e[m.p]&&Ja(e_s(p,m)));for(l=f.c.length-1;l>=0;--l)P_(t.b,(GK(l,f.c.length),Pp(f.c[l],29)));t.a.c=Je(e1R,eUp,1,0,5,1),eEj(n)}function eBL(e){var t,n,r,i,a,o,s,u,c;for(e.b=1,eBM(e),t=null,0==e.c&&94==e.a?(eBM(e),t=(eBG(),eBG(),++tyv,new WZ(4)),eLw(t,0,e1f),s=(++tyv,new WZ(4))):s=(eBG(),eBG(),++tyv,new WZ(4)),i=!0;1!=(c=e.c);){if(0==c&&93==e.a&&!i){t&&(ej0(t,s),s=t);break}if(n=e.a,r=!1,10==c)switch(n){case 100:case 68:case 119:case 87:case 115:case 83:ePR(s,eDu(n)),r=!0;break;case 105:case 73:case 99:case 67:(n=(ePR(s,eDu(n)),-1))<0&&(r=!0);break;case 112:case 80:if(!(u=ext(e,n)))throw p7(new gX(eBJ((Mo(),eJe))));ePR(s,u),r=!0;break;default:n=eCn(e)}else if(24==c&&!i){if(t&&(ej0(t,s),s=t),a=eBL(e),ej0(s,a),0!=e.c||93!=e.a)throw p7(new gX(eBJ((Mo(),eJi))));break}if(eBM(e),!r){if(0==c){if(91==n)throw p7(new gX(eBJ((Mo(),eJa))));if(93==n)throw p7(new gX(eBJ((Mo(),eJo))));if(45==n&&!i&&93!=e.a)throw p7(new gX(eBJ((Mo(),eJs))))}if(0!=e.c||45!=e.a||45==n&&i)eLw(s,n,n);else{if(eBM(e),1==(c=e.c))throw p7(new gX(eBJ((Mo(),eJn))));if(0==c&&93==e.a)eLw(s,n,n),eLw(s,45,45);else if(0==c&&93==e.a||24==c)throw p7(new gX(eBJ((Mo(),eJs))));else{if(o=e.a,0==c){if(91==o)throw p7(new gX(eBJ((Mo(),eJa))));if(93==o)throw p7(new gX(eBJ((Mo(),eJo))));if(45==o)throw p7(new gX(eBJ((Mo(),eJs))))}else 10==c&&(o=eCn(e));if(eBM(e),n>o)throw p7(new gX(eBJ((Mo(),eJl))));eLw(s,n,o)}}}i=!1}if(1==e.c)throw p7(new gX(eBJ((Mo(),eJn))));return eMS(s),eRo(s),e.b=0,eBM(e),s}function eBC(e){eMV(e.c,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#decimal"])),eMV(e.d,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#integer"])),eMV(e.e,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#boolean"])),eMV(e.f,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"EBoolean",eXP,"EBoolean:Object"])),eMV(e.i,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#byte"])),eMV(e.g,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#hexBinary"])),eMV(e.j,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"EByte",eXP,"EByte:Object"])),eMV(e.n,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"EChar",eXP,"EChar:Object"])),eMV(e.t,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#double"])),eMV(e.u,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"EDouble",eXP,"EDouble:Object"])),eMV(e.F,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#float"])),eMV(e.G,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"EFloat",eXP,"EFloat:Object"])),eMV(e.I,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#int"])),eMV(e.J,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"EInt",eXP,"EInt:Object"])),eMV(e.N,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#long"])),eMV(e.O,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"ELong",eXP,"ELong:Object"])),eMV(e.Z,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#short"])),eMV(e.$,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"EShort",eXP,"EShort:Object"])),eMV(e._,eJ7,eow(vx(e17,1),eUP,2,6,[eQd,"http://www.w3.org/2001/XMLSchema#string"]))}function eBI(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O;if(1==e.c.length)return GK(0,e.c.length),Pp(e.c[0],135);if(e.c.length<=0)return new Xn;for(u=new fz(e);u.af&&(M=0,O+=l+S,l=0),eOd(_,o,M,O),t=eB4.Math.max(t,M+E.a),l=eB4.Math.max(l,E.b),M+=E.a+S;for(w=new p2,n=new p2,x=new fz(e);x.aeMg(a))&&(f=a);for(f||(f=(GK(0,m.c.length),Pp(m.c[0],180))),b=new fz(t.b);b.a=-1900?1:0,n>=4?xM(e,eow(vx(e17,1),eUP,2,6,[eHM,eHO])[s]):xM(e,eow(vx(e17,1),eUP,2,6,["BC","AD"])[s]);break;case 121:epA(e,n,r);break;case 77:eIZ(e,n,r);break;case 107:0==(u=i.q.getHours())?eeE(e,24,n):eeE(e,u,n);break;case 83:eOT(e,n,i);break;case 69:l=r.q.getDay(),5==n?xM(e,eow(vx(e17,1),eUP,2,6,["S","M","T","W","T","F","S"])[l]):4==n?xM(e,eow(vx(e17,1),eUP,2,6,[eHA,eHL,eHC,eHI,eHD,eHN,eHP])[l]):xM(e,eow(vx(e17,1),eUP,2,6,["Sun","Mon","Tue","Wed","Thu","Fri","Sat"])[l]);break;case 97:i.q.getHours()>=12&&24>i.q.getHours()?xM(e,eow(vx(e17,1),eUP,2,6,["AM","PM"])[1]):xM(e,eow(vx(e17,1),eUP,2,6,["AM","PM"])[0]);break;case 104:0==(f=i.q.getHours()%12)?eeE(e,12,n):eeE(e,f,n);break;case 75:eeE(e,d=i.q.getHours()%12,n);break;case 72:eeE(e,h=i.q.getHours(),n);break;case 99:p=r.q.getDay(),5==n?xM(e,eow(vx(e17,1),eUP,2,6,["S","M","T","W","T","F","S"])[p]):4==n?xM(e,eow(vx(e17,1),eUP,2,6,[eHA,eHL,eHC,eHI,eHD,eHN,eHP])[p]):3==n?xM(e,eow(vx(e17,1),eUP,2,6,["Sun","Mon","Tue","Wed","Thu","Fri","Sat"])[p]):eeE(e,p,1);break;case 76:b=r.q.getMonth(),5==n?xM(e,eow(vx(e17,1),eUP,2,6,["J","F","M","A","M","J","J","A","S","O","N","D"])[b]):4==n?xM(e,eow(vx(e17,1),eUP,2,6,[eHh,eHp,eHb,eHm,eHg,eHv,eHy,eHw,eH_,eHE,eHS,eHk])[b]):3==n?xM(e,eow(vx(e17,1),eUP,2,6,["Jan","Feb","Mar","Apr",eHg,"Jun","Jul","Aug","Sep","Oct","Nov","Dec"])[b]):eeE(e,b+1,n);break;case 81:m=r.q.getMonth()/3|0,n<4?xM(e,eow(vx(e17,1),eUP,2,6,["Q1","Q2","Q3","Q4"])[m]):xM(e,eow(vx(e17,1),eUP,2,6,["1st quarter","2nd quarter","3rd quarter","4th quarter"])[m]);break;case 100:eeE(e,g=r.q.getDate(),n);break;case 109:eeE(e,c=i.q.getMinutes(),n);break;case 115:eeE(e,o=i.q.getSeconds(),n);break;case 122:n<4?xM(e,a.c[0]):xM(e,a.c[1]);break;case 118:xM(e,a.b);break;case 90:n<3?xM(e,ekA(a)):3==n?xM(e,ek$(a)):xM(e,ekz(a.a));break;default:return!1}return!0}function eBF(e,t,n,r){var i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A;if(eIi(t),u=Pp(etj((t.b||(t.b=new Ih(e6m,t,4,7)),t.b),0),82),l=Pp(etj((t.c||(t.c=new Ih(e6m,t,5,8)),t.c),0),82),s=ewH(u),c=ewH(l),o=0==(t.a||(t.a=new FQ(e6v,t,6,6)),t.a).i?null:Pp(etj((t.a||(t.a=new FQ(e6v,t,6,6)),t.a),0),202),S=Pp(Bp(e.a,s),10),M=Pp(Bp(e.a,c),10),k=null,O=null,M4(u,186)&&(M4(E=Pp(Bp(e.a,u),299),11)?k=Pp(E,11):M4(E,10)&&(S=Pp(E,10),k=Pp(RJ(S.j,0),11))),M4(l,186)&&(M4(T=Pp(Bp(e.a,l),299),11)?O=Pp(T,11):M4(T,10)&&(M=Pp(T,10),O=Pp(RJ(M.j,0),11))),!S||!M)throw p7(new gZ("The source or the target of edge "+t+" could not be found. This usually happens when an edge connects a node laid out by ELK Layered to a node in another level of hierarchy laid out by either another instance of ELK Layered or another layout algorithm alltogether. The former can be solved by setting the hierarchyHandling option to INCLUDE_CHILDREN."));for(b=new $b,eaW(b,t),eo3(b,(eBU(),tnc),t),eo3(b,(eBy(),taR),null),h=Pp(e_k(r,tt3),21),S==M&&h.Fc((eLR(),ttT)),k||(_=(enY(),tsN),x=null,o&&TM(Pp(e_k(S,tol),98))&&(V2(x=new kl(o.j,o.k),zF(t)),qZ(x,n),etg(c,s)&&(_=tsD,C5(x,S.n))),k=ePH(S,x,_,r)),O||(_=(enY(),tsD),A=null,o&&TM(Pp(e_k(M,tol),98))&&(V2(A=new kl(o.b,o.c),zF(t)),qZ(A,n)),O=ePH(M,A,_,Bq(M))),Gs(b,k),Go(b,O),(k.e.c.length>1||k.g.c.length>1||O.e.c.length>1||O.g.c.length>1)&&h.Fc((eLR(),tt_)),d=new Ow((t.n||(t.n=new FQ(e6S,t,1,7)),t.n));d.e!=d.i.gc();)if(f=Pp(epH(d),137),!gN(LK(eT8(f,ta8)))&&f.a)switch(m=eca(f),P_(b.b,m),Pp(e_k(m,tab),272).g){case 1:case 2:h.Fc((eLR(),tty));break;case 0:h.Fc((eLR(),ttg)),eo3(m,tab,(etT(),tp_))}if(a=Pp(e_k(r,tas),314),g=Pp(e_k(r,ta3),315),i=a==(en7(),teR)||g==(ebG(),tsd),o&&0!=(o.a||(o.a=new O_(e6h,o,5)),o.a).i&&i){for(v=eEF(o),p=new mE,w=epL(v,0);w.b!=w.d.c;)y=Pp(Vv(w),8),P7(p,new TS(y));eo3(b,tnl,p)}return b}function eBY(e){e.gb||(e.gb=!0,e.b=eak(e,0),er6(e.b,18),er9(e.b,19),e.a=eak(e,1),er6(e.a,1),er9(e.a,2),er9(e.a,3),er9(e.a,4),er9(e.a,5),e.o=eak(e,2),er6(e.o,8),er6(e.o,9),er9(e.o,10),er9(e.o,11),er9(e.o,12),er9(e.o,13),er9(e.o,14),er9(e.o,15),er9(e.o,16),er9(e.o,17),er9(e.o,18),er9(e.o,19),er9(e.o,20),er9(e.o,21),er9(e.o,22),er9(e.o,23),ee9(e.o),ee9(e.o),ee9(e.o),ee9(e.o),ee9(e.o),ee9(e.o),ee9(e.o),ee9(e.o),ee9(e.o),ee9(e.o),e.p=eak(e,3),er6(e.p,2),er6(e.p,3),er6(e.p,4),er6(e.p,5),er9(e.p,6),er9(e.p,7),ee9(e.p),ee9(e.p),e.q=eak(e,4),er6(e.q,8),e.v=eak(e,5),er9(e.v,9),ee9(e.v),ee9(e.v),ee9(e.v),e.w=eak(e,6),er6(e.w,2),er6(e.w,3),er6(e.w,4),er9(e.w,5),e.B=eak(e,7),er9(e.B,1),ee9(e.B),ee9(e.B),ee9(e.B),e.Q=eak(e,8),er9(e.Q,0),ee9(e.Q),e.R=eak(e,9),er6(e.R,1),e.S=eak(e,10),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),ee9(e.S),e.T=eak(e,11),er9(e.T,10),er9(e.T,11),er9(e.T,12),er9(e.T,13),er9(e.T,14),ee9(e.T),ee9(e.T),e.U=eak(e,12),er6(e.U,2),er6(e.U,3),er9(e.U,4),er9(e.U,5),er9(e.U,6),er9(e.U,7),ee9(e.U),e.V=eak(e,13),er9(e.V,10),e.W=eak(e,14),er6(e.W,18),er6(e.W,19),er6(e.W,20),er9(e.W,21),er9(e.W,22),er9(e.W,23),e.bb=eak(e,15),er6(e.bb,10),er6(e.bb,11),er6(e.bb,12),er6(e.bb,13),er6(e.bb,14),er6(e.bb,15),er6(e.bb,16),er9(e.bb,17),ee9(e.bb),ee9(e.bb),e.eb=eak(e,16),er6(e.eb,2),er6(e.eb,3),er6(e.eb,4),er6(e.eb,5),er6(e.eb,6),er6(e.eb,7),er9(e.eb,8),er9(e.eb,9),e.ab=eak(e,17),er6(e.ab,0),er6(e.ab,1),e.H=eak(e,18),er9(e.H,0),er9(e.H,1),er9(e.H,2),er9(e.H,3),er9(e.H,4),er9(e.H,5),ee9(e.H),e.db=eak(e,19),er9(e.db,2),e.c=eax(e,20),e.d=eax(e,21),e.e=eax(e,22),e.f=eax(e,23),e.i=eax(e,24),e.g=eax(e,25),e.j=eax(e,26),e.k=eax(e,27),e.n=eax(e,28),e.r=eax(e,29),e.s=eax(e,30),e.t=eax(e,31),e.u=eax(e,32),e.fb=eax(e,33),e.A=eax(e,34),e.C=eax(e,35),e.D=eax(e,36),e.F=eax(e,37),e.G=eax(e,38),e.I=eax(e,39),e.J=eax(e,40),e.L=eax(e,41),e.M=eax(e,42),e.N=eax(e,43),e.O=eax(e,44),e.P=eax(e,45),e.X=eax(e,46),e.Y=eax(e,47),e.Z=eax(e,48),e.$=eax(e,49),e._=eax(e,50),e.cb=eax(e,51),e.K=eax(e,52))}function eBB(){var e,t;eBB=A,tdQ=new pO(eZi),th6=new pO(eZa),td0=(ebx(),tdM),td1=new xX(eVi,td0),new pQ,td2=new xX(ezG,null),td3=new pO(eZo),td8=(eyY(),jL(tdX,eow(vx(e54,1),eU4,291,0,[tdK]))),td9=new xX(eVg,td8),td7=new xX(eVr,(OQ(),!1)),tht=(ec3(),tpv),the=new xX(eVu,tht),tho=(efE(),tpO),tha=new xX(eKB,tho),thc=new xX(eqC,!1),thf=(eck(),tpG),thl=new xX(eKP,thf),thP=new T3(12),thN=new xX(ezW,thP),thb=new xX(eGu,!1),thm=new xX(eVA,!1),thD=new xX(eGf,!1),thq=(ewf(),tbl),thV=new xX(eGc,thq),th3=new pO(eVT),th4=new pO(eGr),th5=new pO(eGo),th8=new pO(eGs),thv=new mE,thg=new xX(eVv,thv),td6=new xX(eV_,!1),thd=new xX(eVE,!1),new pO(eZs),thw=new mh,thy=new xX(eVM,thw),thI=new xX(eVt,!1),new pQ,th9=new xX(eZu,1),new xX(eZc,!0),ell(0),new xX(eZl,ell(100)),new xX(eZf,!1),ell(0),new xX(eZd,ell(4e3)),ell(0),new xX(eZh,ell(400)),new xX(eZp,!1),new xX(eZb,!1),new xX(eZm,!0),new xX(eZg,!1),td5=(edM(),tme),td4=new xX(eZr,td5),th7=new xX(eKQ,10),tpe=new xX(eK1,10),tpt=new xX(ez$,20),tpn=new xX(eK0,10),tpr=new xX(eGa,2),tpi=new xX(eK2,10),tpo=new xX(eK3,0),tps=new xX(eK6,5),tpu=new xX(eK4,1),tpc=new xX(eK5,1),tpl=new xX(eGi,20),tpf=new xX(eK9,10),tpp=new xX(eK8,10),tpa=new pO(eK7),tph=new T_,tpd=new xX(eVO,tph),thF=new pO(eVx),thj=!1,thR=new xX(eVk,thj),thE=new T3(5),th_=new xX(eVc,thE),thk=(eT7(),t=Pp(yw(e6t),9),new I1(t,Pp(CY(t,t.length),9),0)),thS=new xX(eGp,thk),thU=(epT(),tbt),thB=new xX(eVd,thU),th$=new pO(eVh),thz=new pO(eVp),thG=new pO(eVb),thH=new pO(eVm),thT=(e=Pp(yw(e6o),9),new I1(e,Pp(CY(e,e.length),9),0)),thx=new xX(eGh,thT),thC=el9((eI3(),tbQ)),thL=new xX(eGd,thC),thA=new kl(0,0),thO=new xX(eGM,thA),thM=new xX(eVs,!1),thi=(etT(),tp_),thr=new xX(eVy,thi),thn=new xX(eGl,!1),new pO(eZv),ell(1),new xX(eZy,null),thW=new pO(eVS),thZ=new pO(eVw),th2=(eYu(),tbF),th0=new xX(eVn,th2),thK=new pO(eVe),thQ=(ekU(),el9(tbm)),thJ=new xX(eGb,thQ),thX=new xX(eVl,!1),th1=new xX(eVf,!0),thh=new xX(eVa,!1),thp=new xX(eVo,!1),ths=new xX(ezz,1),thu=(e_a(),tpN),new xX(eZw,thu),thY=!0}function eBU(){var e,t;eBU=A,tnc=new pO(eGm),ttz=new pO("coordinateOrigin"),tny=new pO("processors"),tt$=new Cm("compoundNode",(OQ(),!1)),tt6=new Cm("insideConnections",!1),tnl=new pO("originalBendpoints"),tnf=new pO("originalDummyNodePosition"),tnd=new pO("originalLabelEdge"),tn_=new pO("representedLabels"),ttq=new pO("endLabels"),ttZ=new pO("endLabel.origin"),tnt=new Cm("labelSide",(egF(),tpX)),tns=new Cm("maxEdgeThickness",0),tnE=new Cm("reversed",!1),tnw=new pO(eGg),tni=new Cm("longEdgeSource",null),tna=new Cm("longEdgeTarget",null),tnr=new Cm("longEdgeHasLabelDummies",!1),tnn=new Cm("longEdgeBeforeLabelDummy",!1),ttV=new Cm("edgeConstraint",(eoG(),te1)),tt8=new pO("inLayerLayoutUnit"),tt9=new Cm("inLayerConstraint",(Q1(),ttD)),tt7=new Cm("inLayerSuccessorConstraint",new p0),tne=new Cm("inLayerSuccessorConstraintBetweenNonDummies",!1),tng=new pO("portDummy"),ttG=new Cm("crossingHint",ell(0)),tt3=new Cm("graphProperties",(t=Pp(yw(e44),9),new I1(t,Pp(CY(t,t.length),9),0))),tt1=new Cm("externalPortSide",(eYu(),tbF)),tt0=new Cm("externalPortSize",new yb),ttJ=new pO("externalPortReplacedDummies"),ttQ=new pO("externalPortReplacedDummy"),ttX=new Cm("externalPortConnections",(e=Pp(yw(e6a),9),new I1(e,Pp(CY(e,e.length),9),0))),tnv=new Cm(ezf,0),ttY=new pO("barycenterAssociates"),tnI=new pO("TopSideComments"),ttB=new pO("BottomSideComments"),ttH=new pO("CommentConnectionPort"),tt5=new Cm("inputCollect",!1),tnb=new Cm("outputCollect",!1),ttK=new Cm("cyclic",!1),ttW=new pO("crossHierarchyMap"),tnC=new pO("targetOffset"),new Cm("splineLabelSize",new yb),tnx=new pO("spacings"),tnm=new Cm("partitionConstraint",!1),ttU=new pO("breakingPoint.info"),tnA=new pO("splines.survivingEdge"),tnO=new pO("splines.route.start"),tnT=new pO("splines.edgeChain"),tnp=new pO("originalPortConstraints"),tnk=new pO("selfLoopHolder"),tnM=new pO("splines.nsPortY"),tnu=new pO("modelOrder"),tno=new pO("longEdgeTargetNode"),tt2=new Cm(eW_,!1),tnS=new Cm(eW_,!1),tt4=new pO("layerConstraints.hiddenNodes"),tnh=new pO("layerConstraints.opposidePort"),tnL=new pO("targetNode.modelOrder")}function eBH(){eBH=A,trl=(eeF(),teZ),trc=new xX(eWE,trl),trO=new xX(eWS,(OQ(),!1)),trN=(K6(),ttR),trD=new xX(eWk,trN),trJ=new xX(eWx,!1),trQ=new xX(eWT,!0),tnY=new xX(eWM,!1),tic=(Q0(),tsL),tiu=new xX(eWO,tic),ell(1),tig=new xX(eWA,ell(7)),tiv=new xX(eWL,!1),trA=new xX(eWC,!1),tru=(eb6(),teG),trs=new xX(eWI,tru),trX=(ewY(),to7),trZ=new xX(eWD,trX),trU=(ef_(),tnj),trB=new xX(eWN,trU),ell(-1),trY=new xX(eWP,ell(-1)),ell(-1),trH=new xX(eWR,ell(-1)),ell(-1),tr$=new xX(eWj,ell(4)),ell(-1),trG=new xX(eWF,ell(2)),trq=(eOJ(),tsS),trV=new xX(eWY,trq),ell(0),trK=new xX(eWB,ell(0)),trj=new xX(eWU,ell(eUu)),tro=(en7(),tej),tra=new xX(eWH,tro),tn1=new xX(eW$,!1),tn7=new xX(eWz,.1),trr=new xX(eWG,!1),ell(-1),trt=new xX(eWW,ell(-1)),ell(-1),trn=new xX(eWK,ell(-1)),ell(0),tn0=new xX(eWV,ell(40)),tn9=(eaU(),ttL),tn6=new xX(eWq,tn9),tn3=ttO,tn2=new xX(eWZ,tn3),tis=(ebG(),tsf),tio=new xX(eWX,tis),tr6=new pO(eWJ),tr0=(Qx(),tte),tr1=new xX(eWQ,tr0),tr4=(eyd(),tto),tr3=new xX(eW1,tr4),new pQ,tr7=new xX(eW0,.3),tit=new pO(eW2),tir=(ebk(),tsu),tin=new xX(eW3,tir),trv=(ei0(),tsF),trg=new xX(eW4,trv),tr_=(Xo(),tsH),trw=new xX(eW5,tr_),trS=(euy(),tsW),trE=new xX(eW6,trS),trx=new xX(eW9,.2),trb=new xX(eW8,2),tih=new xX(eW7,null),tib=new xX(eKe,10),tip=new xX(eKt,10),tim=new xX(eKn,20),ell(0),til=new xX(eKr,ell(0)),ell(0),tif=new xX(eKi,ell(0)),ell(0),tid=new xX(eKa,ell(0)),tnB=new xX(eKo,!1),tnz=(e_3(),ttp),tn$=new xX(eKs,tnz),tnH=(Jp(),teN),tnU=new xX(eKu,tnH),trC=new xX(eKc,!1),ell(0),trL=new xX(eKl,ell(16)),ell(0),trI=new xX(eKf,ell(5)),tiU=(eox(),tsQ),tiB=new xX(eKd,tiU),tiy=new xX(eKh,10),tiE=new xX(eKp,1),tiC=(enB(),teH),tiL=new xX(eKb,tiC),tix=new pO(eKm),tiO=ell(1),ell(0),tiM=new xX(eKg,tiO),tiW=(eiO(),tsV),tiG=new xX(eKv,tiW),tiH=new pO(eKy),tiR=new xX(eKw,!0),tiN=new xX(eK_,2),tiF=new xX(eKE,!0),trp=(eEf(),te9),trh=new xX(eKS,trp),trd=(eSg(),teO),trf=new xX(eKk,trd),tnQ=(esn(),tsM),tnJ=new xX(eKx,tnQ),tnX=new xX(eKT,!1),tnW=(ec4(),e8x),tnG=new xX(eKM,tnW),tnZ=(euJ(),tsn),tnq=new xX(eKO,tnZ),tnK=new xX(eKA,0),tnV=new xX(eKL,0),trR=teK,trP=teR,trz=to8,trW=to8,trF=to5,tre=(eck(),tpz),tri=tej,tn8=tej,tn4=tej,tn5=tpz,tr9=tsp,tr8=tsf,tr2=tsf,tr5=tsf,tie=tsh,tia=tsp,tii=tsp,trk=(efE(),tpM),trT=tpM,trM=tsW,trm=tpT,tiw=ts1,ti_=tsJ,tiS=ts1,tik=tsJ,tiI=ts1,tiD=tsJ,tiT=teU,tiA=teH,tiK=ts1,tiV=tsJ,ti$=ts1,tiz=tsJ,tij=tsJ,tiP=tsJ,tiY=tsJ}function eB$(){eB$=A,e85=new Eq("DIRECTION_PREPROCESSOR",0),e82=new Eq("COMMENT_PREPROCESSOR",1),e86=new Eq("EDGE_AND_LAYER_CONSTRAINT_EDGE_REVERSER",2),e7d=new Eq("INTERACTIVE_EXTERNAL_PORT_POSITIONER",3),e7C=new Eq("PARTITION_PREPROCESSOR",4),e7m=new Eq("LABEL_DUMMY_INSERTER",5),e7j=new Eq("SELF_LOOP_PREPROCESSOR",6),e7_=new Eq("LAYER_CONSTRAINT_PREPROCESSOR",7),e7A=new Eq("PARTITION_MIDPROCESSOR",8),e7s=new Eq("HIGH_DEGREE_NODE_LAYER_PROCESSOR",9),e7x=new Eq("NODE_PROMOTION",10),e7w=new Eq("LAYER_CONSTRAINT_POSTPROCESSOR",11),e7L=new Eq("PARTITION_POSTPROCESSOR",12),e7r=new Eq("HIERARCHICAL_PORT_CONSTRAINT_PROCESSOR",13),e7Y=new Eq("SEMI_INTERACTIVE_CROSSMIN_PROCESSOR",14),e8Z=new Eq("BREAKING_POINT_INSERTER",15),e7k=new Eq("LONG_EDGE_SPLITTER",16),e7D=new Eq("PORT_SIDE_PROCESSOR",17),e7h=new Eq("INVERTED_PORT_PROCESSOR",18),e7I=new Eq("PORT_LIST_SORTER",19),e7U=new Eq("SORT_BY_INPUT_ORDER_OF_MODEL",20),e7M=new Eq("NORTH_SOUTH_PORT_PREPROCESSOR",21),e8X=new Eq("BREAKING_POINT_PROCESSOR",22),e7O=new Eq(eG7,23),e7H=new Eq(eWe,24),e7P=new Eq("SELF_LOOP_PORT_RESTORER",25),e7B=new Eq("SINGLE_EDGE_GRAPH_WRAPPER",26),e7p=new Eq("IN_LAYER_CONSTRAINT_PROCESSOR",27),e7e=new Eq("END_NODE_PORT_LABEL_MANAGEMENT_PROCESSOR",28),e7b=new Eq("LABEL_AND_NODE_SIZE_PROCESSOR",29),e7f=new Eq("INNERMOST_NODE_MARGIN_CALCULATOR",30),e7F=new Eq("SELF_LOOP_ROUTER",31),e81=new Eq("COMMENT_NODE_MARGIN_CALCULATOR",32),e88=new Eq("END_LABEL_PREPROCESSOR",33),e7v=new Eq("LABEL_DUMMY_SWITCHER",34),e8Q=new Eq("CENTER_LABEL_MANAGEMENT_PROCESSOR",35),e7y=new Eq("LABEL_SIDE_SELECTOR",36),e7c=new Eq("HYPEREDGE_DUMMY_MERGER",37),e7i=new Eq("HIERARCHICAL_PORT_DUMMY_SIZE_PROCESSOR",38),e7E=new Eq("LAYER_SIZE_AND_GRAPH_HEIGHT_CALCULATOR",39),e7o=new Eq("HIERARCHICAL_PORT_POSITION_PROCESSOR",40),e83=new Eq("CONSTRAINTS_POSTPROCESSOR",41),e80=new Eq("COMMENT_POSTPROCESSOR",42),e7l=new Eq("HYPERNODE_PROCESSOR",43),e7a=new Eq("HIERARCHICAL_PORT_ORTHOGONAL_EDGE_ROUTER",44),e7S=new Eq("LONG_EDGE_JOINER",45),e7R=new Eq("SELF_LOOP_POSTPROCESSOR",46),e8J=new Eq("BREAKING_POINT_REMOVER",47),e7T=new Eq("NORTH_SOUTH_PORT_POSTPROCESSOR",48),e7u=new Eq("HORIZONTAL_COMPACTOR",49),e7g=new Eq("LABEL_DUMMY_REMOVER",50),e7t=new Eq("FINAL_SPLINE_BENDPOINTS_CALCULATOR",51),e87=new Eq("END_LABEL_SORTER",52),e7N=new Eq("REVERSED_EDGE_RESTORER",53),e89=new Eq("END_LABEL_POSTPROCESSOR",54),e7n=new Eq("HIERARCHICAL_NODE_RESIZER",55),e84=new Eq("DIRECTION_POSTPROCESSOR",56)}function eBz(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A,L,C,I,D,N,P,R,j,F,Y,B,U,H,$,z,G,W,K,V,q,Z,X,J,Q,ee,et,en,er,ei,ea,eo;for(I=0,X=0,P=(A=t).length;I0&&(e.a[H.p]=X++)}for(D=0,en=0,R=(L=n).length;D0;){for(H=(A6(W.b>0),Pp(W.a.Xb(W.c=--W.b),11)),G=0,s=new fz(H.e);s.a0&&(H.j==(eYu(),tbw)?(e.a[H.p]=en,++en):(e.a[H.p]=en+j+Y,++Y))}en+=Y}for(C=0,z=new p2,p=new Tw,N=(O=t).length;Cc.b&&(c.b=K)):H.i.c==Z&&(Kc.c&&(c.c=K));for(Qe(b,0,b.length,null),et=Je(ty_,eHT,25,b.length,15,1),r=Je(ty_,eHT,25,en+1,15,1),g=0;g0;)S%2>0&&(i+=ea[S+1]),S=(S-1)/2|0,++ea[S];for(w=0,x=Je(e5g,eUp,362,2*b.length,0,1);w'?":IE(eXJ,e)?"'(?<' or '(? toIndex: ",e$M=", toIndex: ",e$O="Index: ",e$A=", Size: ",e$L="org.eclipse.elk.alg.common",e$C={62:1},e$I="org.eclipse.elk.alg.common.compaction",e$D="Scanline/EventHandler",e$N="org.eclipse.elk.alg.common.compaction.oned",e$P="CNode belongs to another CGroup.",e$R="ISpacingsHandler/1",e$j="The ",e$F=" instance has been finished already.",e$Y="The direction ",e$B=" is not supported by the CGraph instance.",e$U="OneDimensionalCompactor",e$H="OneDimensionalCompactor/lambda$0$Type",e$$="Quadruplet",e$z="ScanlineConstraintCalculator",e$G="ScanlineConstraintCalculator/ConstraintsScanlineHandler",e$W="ScanlineConstraintCalculator/ConstraintsScanlineHandler/lambda$0$Type",e$K="ScanlineConstraintCalculator/Timestamp",e$V="ScanlineConstraintCalculator/lambda$0$Type",e$q={169:1,45:1},e$Z="org.eclipse.elk.alg.common.compaction.options",e$X="org.eclipse.elk.core.data",e$J="org.eclipse.elk.polyomino.traversalStrategy",e$Q="org.eclipse.elk.polyomino.lowLevelSort",e$1="org.eclipse.elk.polyomino.highLevelSort",e$0="org.eclipse.elk.polyomino.fill",e$2={130:1},e$3="polyomino",e$4="org.eclipse.elk.alg.common.networksimplex",e$5={177:1,3:1,4:1},e$6="org.eclipse.elk.alg.common.nodespacing",e$9="org.eclipse.elk.alg.common.nodespacing.cellsystem",e$8="CENTER",e$7={212:1,326:1},eze={3:1,4:1,5:1,595:1},ezt="LEFT",ezn="RIGHT",ezr="Vertical alignment cannot be null",ezi="BOTTOM",eza="org.eclipse.elk.alg.common.nodespacing.internal",ezo="UNDEFINED",ezs=.01,ezu="org.eclipse.elk.alg.common.nodespacing.internal.algorithm",ezc="LabelPlacer/lambda$0$Type",ezl="LabelPlacer/lambda$1$Type",ezf="portRatioOrPosition",ezd="org.eclipse.elk.alg.common.overlaps",ezh="DOWN",ezp="org.eclipse.elk.alg.common.polyomino",ezb="NORTH",ezm="EAST",ezg="SOUTH",ezv="WEST",ezy="org.eclipse.elk.alg.common.polyomino.structures",ezw="Direction",ez_="Grid is only of size ",ezE=". Requested point (",ezS=") is out of bounds.",ezk=" Given center based coordinates were (",ezx="org.eclipse.elk.graph.properties",ezT="IPropertyHolder",ezM={3:1,94:1,134:1},ezO="org.eclipse.elk.alg.common.spore",ezA="org.eclipse.elk.alg.common.utils",ezL={209:1},ezC="org.eclipse.elk.core",ezI="Connected Components Compaction",ezD="org.eclipse.elk.alg.disco",ezN="org.eclipse.elk.alg.disco.graph",ezP="org.eclipse.elk.alg.disco.options",ezR="CompactionStrategy",ezj="org.eclipse.elk.disco.componentCompaction.strategy",ezF="org.eclipse.elk.disco.componentCompaction.componentLayoutAlgorithm",ezY="org.eclipse.elk.disco.debug.discoGraph",ezB="org.eclipse.elk.disco.debug.discoPolys",ezU="componentCompaction",ezH="org.eclipse.elk.disco",ez$="org.eclipse.elk.spacing.componentComponent",ezz="org.eclipse.elk.edge.thickness",ezG="org.eclipse.elk.aspectRatio",ezW="org.eclipse.elk.padding",ezK="org.eclipse.elk.alg.disco.transform",ezV=1.5707963267948966,ezq=17976931348623157e292,ezZ={3:1,4:1,5:1,192:1},ezX={3:1,6:1,4:1,5:1,106:1,120:1},ezJ="org.eclipse.elk.alg.force",ezQ="ComponentsProcessor",ez1="ComponentsProcessor/1",ez0="org.eclipse.elk.alg.force.graph",ez2="Component Layout",ez3="org.eclipse.elk.alg.force.model",ez4="org.eclipse.elk.force.model",ez5="org.eclipse.elk.force.iterations",ez6="org.eclipse.elk.force.repulsivePower",ez9="org.eclipse.elk.force.temperature",ez8=.001,ez7="org.eclipse.elk.force.repulsion",eGe="org.eclipse.elk.alg.force.options",eGt=1.600000023841858,eGn="org.eclipse.elk.force",eGr="org.eclipse.elk.priority",eGi="org.eclipse.elk.spacing.nodeNode",eGa="org.eclipse.elk.spacing.edgeLabel",eGo="org.eclipse.elk.randomSeed",eGs="org.eclipse.elk.separateConnectedComponents",eGu="org.eclipse.elk.interactive",eGc="org.eclipse.elk.portConstraints",eGl="org.eclipse.elk.edgeLabels.inline",eGf="org.eclipse.elk.omitNodeMicroLayout",eGd="org.eclipse.elk.nodeSize.options",eGh="org.eclipse.elk.nodeSize.constraints",eGp="org.eclipse.elk.nodeLabels.placement",eGb="org.eclipse.elk.portLabels.placement",eGm="origin",eGg="random",eGv="boundingBox.upLeft",eGy="boundingBox.lowRight",eGw="org.eclipse.elk.stress.fixed",eG_="org.eclipse.elk.stress.desiredEdgeLength",eGE="org.eclipse.elk.stress.dimension",eGS="org.eclipse.elk.stress.epsilon",eGk="org.eclipse.elk.stress.iterationLimit",eGx="org.eclipse.elk.stress",eGT="ELK Stress",eGM="org.eclipse.elk.nodeSize.minimum",eGO="org.eclipse.elk.alg.force.stress",eGA="Layered layout",eGL="org.eclipse.elk.alg.layered",eGC="org.eclipse.elk.alg.layered.compaction.components",eGI="org.eclipse.elk.alg.layered.compaction.oned",eGD="org.eclipse.elk.alg.layered.compaction.oned.algs",eGN="org.eclipse.elk.alg.layered.compaction.recthull",eGP="org.eclipse.elk.alg.layered.components",eGR="NONE",eGj={3:1,6:1,4:1,9:1,5:1,122:1},eGF={3:1,6:1,4:1,5:1,141:1,106:1,120:1},eGY="org.eclipse.elk.alg.layered.compound",eGB={51:1},eGU="org.eclipse.elk.alg.layered.graph",eGH=" -> ",eG$="Not supported by LGraph",eGz="Port side is undefined",eGG={3:1,6:1,4:1,5:1,474:1,141:1,106:1,120:1},eGW={3:1,6:1,4:1,5:1,141:1,193:1,203:1,106:1,120:1},eGK={3:1,6:1,4:1,5:1,141:1,1943:1,203:1,106:1,120:1},eGV="([{\"' \r\n",eGq=")]}\"' \r\n",eGZ="The given string contains parts that cannot be parsed as numbers.",eGX="org.eclipse.elk.core.math",eGJ={3:1,4:1,142:1,207:1,414:1},eGQ={3:1,4:1,116:1,207:1,414:1},eG1="org.eclipse.elk.layered",eG0="org.eclipse.elk.alg.layered.graph.transform",eG2="ElkGraphImporter",eG3="ElkGraphImporter/lambda$0$Type",eG4="ElkGraphImporter/lambda$1$Type",eG5="ElkGraphImporter/lambda$2$Type",eG6="ElkGraphImporter/lambda$4$Type",eG9="Node margin calculation",eG8="org.eclipse.elk.alg.layered.intermediate",eG7="ONE_SIDED_GREEDY_SWITCH",eWe="TWO_SIDED_GREEDY_SWITCH",eWt="No implementation is available for the layout processor ",eWn="IntermediateProcessorStrategy",eWr="Node '",eWi="FIRST_SEPARATE",eWa="LAST_SEPARATE",eWo="Odd port side processing",eWs="org.eclipse.elk.alg.layered.intermediate.compaction",eWu="org.eclipse.elk.alg.layered.intermediate.greedyswitch",eWc="org.eclipse.elk.alg.layered.p3order.counting",eWl={225:1},eWf="org.eclipse.elk.alg.layered.intermediate.loops",eWd="org.eclipse.elk.alg.layered.intermediate.loops.ordering",eWh="org.eclipse.elk.alg.layered.intermediate.loops.routing",eWp="org.eclipse.elk.alg.layered.intermediate.preserveorder",eWb="org.eclipse.elk.alg.layered.intermediate.wrapping",eWm="org.eclipse.elk.alg.layered.options",eWg="INTERACTIVE",eWv="DEPTH_FIRST",eWy="EDGE_LENGTH",eWw="SELF_LOOPS",eW_="firstTryWithInitialOrder",eWE="org.eclipse.elk.layered.directionCongruency",eWS="org.eclipse.elk.layered.feedbackEdges",eWk="org.eclipse.elk.layered.interactiveReferencePoint",eWx="org.eclipse.elk.layered.mergeEdges",eWT="org.eclipse.elk.layered.mergeHierarchyEdges",eWM="org.eclipse.elk.layered.allowNonFlowPortsToSwitchSides",eWO="org.eclipse.elk.layered.portSortingStrategy",eWA="org.eclipse.elk.layered.thoroughness",eWL="org.eclipse.elk.layered.unnecessaryBendpoints",eWC="org.eclipse.elk.layered.generatePositionAndLayerIds",eWI="org.eclipse.elk.layered.cycleBreaking.strategy",eWD="org.eclipse.elk.layered.layering.strategy",eWN="org.eclipse.elk.layered.layering.layerConstraint",eWP="org.eclipse.elk.layered.layering.layerChoiceConstraint",eWR="org.eclipse.elk.layered.layering.layerId",eWj="org.eclipse.elk.layered.layering.minWidth.upperBoundOnWidth",eWF="org.eclipse.elk.layered.layering.minWidth.upperLayerEstimationScalingFactor",eWY="org.eclipse.elk.layered.layering.nodePromotion.strategy",eWB="org.eclipse.elk.layered.layering.nodePromotion.maxIterations",eWU="org.eclipse.elk.layered.layering.coffmanGraham.layerBound",eWH="org.eclipse.elk.layered.crossingMinimization.strategy",eW$="org.eclipse.elk.layered.crossingMinimization.forceNodeModelOrder",eWz="org.eclipse.elk.layered.crossingMinimization.hierarchicalSweepiness",eWG="org.eclipse.elk.layered.crossingMinimization.semiInteractive",eWW="org.eclipse.elk.layered.crossingMinimization.positionChoiceConstraint",eWK="org.eclipse.elk.layered.crossingMinimization.positionId",eWV="org.eclipse.elk.layered.crossingMinimization.greedySwitch.activationThreshold",eWq="org.eclipse.elk.layered.crossingMinimization.greedySwitch.type",eWZ="org.eclipse.elk.layered.crossingMinimization.greedySwitchHierarchical.type",eWX="org.eclipse.elk.layered.nodePlacement.strategy",eWJ="org.eclipse.elk.layered.nodePlacement.favorStraightEdges",eWQ="org.eclipse.elk.layered.nodePlacement.bk.edgeStraightening",eW1="org.eclipse.elk.layered.nodePlacement.bk.fixedAlignment",eW0="org.eclipse.elk.layered.nodePlacement.linearSegments.deflectionDampening",eW2="org.eclipse.elk.layered.nodePlacement.networkSimplex.nodeFlexibility",eW3="org.eclipse.elk.layered.nodePlacement.networkSimplex.nodeFlexibility.default",eW4="org.eclipse.elk.layered.edgeRouting.selfLoopDistribution",eW5="org.eclipse.elk.layered.edgeRouting.selfLoopOrdering",eW6="org.eclipse.elk.layered.edgeRouting.splines.mode",eW9="org.eclipse.elk.layered.edgeRouting.splines.sloppy.layerSpacingFactor",eW8="org.eclipse.elk.layered.edgeRouting.polyline.slopedEdgeZoneWidth",eW7="org.eclipse.elk.layered.spacing.baseValue",eKe="org.eclipse.elk.layered.spacing.edgeNodeBetweenLayers",eKt="org.eclipse.elk.layered.spacing.edgeEdgeBetweenLayers",eKn="org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers",eKr="org.eclipse.elk.layered.priority.direction",eKi="org.eclipse.elk.layered.priority.shortness",eKa="org.eclipse.elk.layered.priority.straightness",eKo="org.eclipse.elk.layered.compaction.connectedComponents",eKs="org.eclipse.elk.layered.compaction.postCompaction.strategy",eKu="org.eclipse.elk.layered.compaction.postCompaction.constraints",eKc="org.eclipse.elk.layered.highDegreeNodes.treatment",eKl="org.eclipse.elk.layered.highDegreeNodes.threshold",eKf="org.eclipse.elk.layered.highDegreeNodes.treeHeight",eKd="org.eclipse.elk.layered.wrapping.strategy",eKh="org.eclipse.elk.layered.wrapping.additionalEdgeSpacing",eKp="org.eclipse.elk.layered.wrapping.correctionFactor",eKb="org.eclipse.elk.layered.wrapping.cutting.strategy",eKm="org.eclipse.elk.layered.wrapping.cutting.cuts",eKg="org.eclipse.elk.layered.wrapping.cutting.msd.freedom",eKv="org.eclipse.elk.layered.wrapping.validify.strategy",eKy="org.eclipse.elk.layered.wrapping.validify.forbiddenIndices",eKw="org.eclipse.elk.layered.wrapping.multiEdge.improveCuts",eK_="org.eclipse.elk.layered.wrapping.multiEdge.distancePenalty",eKE="org.eclipse.elk.layered.wrapping.multiEdge.improveWrappedEdges",eKS="org.eclipse.elk.layered.edgeLabels.sideSelection",eKk="org.eclipse.elk.layered.edgeLabels.centerLabelPlacementStrategy",eKx="org.eclipse.elk.layered.considerModelOrder.strategy",eKT="org.eclipse.elk.layered.considerModelOrder.noModelOrder",eKM="org.eclipse.elk.layered.considerModelOrder.components",eKO="org.eclipse.elk.layered.considerModelOrder.longEdgeStrategy",eKA="org.eclipse.elk.layered.considerModelOrder.crossingCounterNodeInfluence",eKL="org.eclipse.elk.layered.considerModelOrder.crossingCounterPortInfluence",eKC="layering",eKI="layering.minWidth",eKD="layering.nodePromotion",eKN="crossingMinimization",eKP="org.eclipse.elk.hierarchyHandling",eKR="crossingMinimization.greedySwitch",eKj="nodePlacement",eKF="nodePlacement.bk",eKY="edgeRouting",eKB="org.eclipse.elk.edgeRouting",eKU="spacing",eKH="priority",eK$="compaction",eKz="compaction.postCompaction",eKG="Specifies whether and how post-process compaction is applied.",eKW="highDegreeNodes",eKK="wrapping",eKV="wrapping.cutting",eKq="wrapping.validify",eKZ="wrapping.multiEdge",eKX="edgeLabels",eKJ="considerModelOrder",eKQ="org.eclipse.elk.spacing.commentComment",eK1="org.eclipse.elk.spacing.commentNode",eK0="org.eclipse.elk.spacing.edgeEdge",eK2="org.eclipse.elk.spacing.edgeNode",eK3="org.eclipse.elk.spacing.labelLabel",eK4="org.eclipse.elk.spacing.labelPortHorizontal",eK5="org.eclipse.elk.spacing.labelPortVertical",eK6="org.eclipse.elk.spacing.labelNode",eK9="org.eclipse.elk.spacing.nodeSelfLoop",eK8="org.eclipse.elk.spacing.portPort",eK7="org.eclipse.elk.spacing.individual",eVe="org.eclipse.elk.port.borderOffset",eVt="org.eclipse.elk.noLayout",eVn="org.eclipse.elk.port.side",eVr="org.eclipse.elk.debugMode",eVi="org.eclipse.elk.alignment",eVa="org.eclipse.elk.insideSelfLoops.activate",eVo="org.eclipse.elk.insideSelfLoops.yo",eVs="org.eclipse.elk.nodeSize.fixedGraphSize",eVu="org.eclipse.elk.direction",eVc="org.eclipse.elk.nodeLabels.padding",eVl="org.eclipse.elk.portLabels.nextToPortIfPossible",eVf="org.eclipse.elk.portLabels.treatAsGroup",eVd="org.eclipse.elk.portAlignment.default",eVh="org.eclipse.elk.portAlignment.north",eVp="org.eclipse.elk.portAlignment.south",eVb="org.eclipse.elk.portAlignment.west",eVm="org.eclipse.elk.portAlignment.east",eVg="org.eclipse.elk.contentAlignment",eVv="org.eclipse.elk.junctionPoints",eVy="org.eclipse.elk.edgeLabels.placement",eVw="org.eclipse.elk.port.index",eV_="org.eclipse.elk.commentBox",eVE="org.eclipse.elk.hypernode",eVS="org.eclipse.elk.port.anchor",eVk="org.eclipse.elk.partitioning.activate",eVx="org.eclipse.elk.partitioning.partition",eVT="org.eclipse.elk.position",eVM="org.eclipse.elk.margins",eVO="org.eclipse.elk.spacing.portsSurrounding",eVA="org.eclipse.elk.interactiveLayout",eVL="org.eclipse.elk.core.util",eVC={3:1,4:1,5:1,593:1},eVI="NETWORK_SIMPLEX",eVD={123:1,51:1},eVN="org.eclipse.elk.alg.layered.p1cycles",eVP="org.eclipse.elk.alg.layered.p2layers",eVR={402:1,225:1},eVj={832:1,3:1,4:1},eVF="org.eclipse.elk.alg.layered.p3order",eVY="org.eclipse.elk.alg.layered.p4nodes",eVB={3:1,4:1,5:1,840:1},eVU=1e-5,eVH="org.eclipse.elk.alg.layered.p4nodes.bk",eV$="org.eclipse.elk.alg.layered.p5edges",eVz="org.eclipse.elk.alg.layered.p5edges.orthogonal",eVG="org.eclipse.elk.alg.layered.p5edges.orthogonal.direction",eVW=1e-6,eVK="org.eclipse.elk.alg.layered.p5edges.splines",eVV=.09999999999999998,eVq=1e-8,eVZ=4.71238898038469,eVX=3.141592653589793,eVJ="org.eclipse.elk.alg.mrtree",eVQ="org.eclipse.elk.alg.mrtree.graph",eV1="org.eclipse.elk.alg.mrtree.intermediate",eV0="Set neighbors in level",eV2="DESCENDANTS",eV3="org.eclipse.elk.mrtree.weighting",eV4="org.eclipse.elk.mrtree.searchOrder",eV5="org.eclipse.elk.alg.mrtree.options",eV6="org.eclipse.elk.mrtree",eV9="org.eclipse.elk.tree",eV8="org.eclipse.elk.alg.radial",eV7=6.283185307179586,eqe=5e-324,eqt="org.eclipse.elk.alg.radial.intermediate",eqn="org.eclipse.elk.alg.radial.intermediate.compaction",eqr={3:1,4:1,5:1,106:1},eqi="org.eclipse.elk.alg.radial.intermediate.optimization",eqa="No implementation is available for the layout option ",eqo="org.eclipse.elk.alg.radial.options",eqs="org.eclipse.elk.radial.orderId",equ="org.eclipse.elk.radial.radius",eqc="org.eclipse.elk.radial.compactor",eql="org.eclipse.elk.radial.compactionStepSize",eqf="org.eclipse.elk.radial.sorter",eqd="org.eclipse.elk.radial.wedgeCriteria",eqh="org.eclipse.elk.radial.optimizationCriteria",eqp="org.eclipse.elk.radial",eqb="org.eclipse.elk.alg.radial.p1position.wedge",eqm="org.eclipse.elk.alg.radial.sorting",eqg=5.497787143782138,eqv=3.9269908169872414,eqy=2.356194490192345,eqw="org.eclipse.elk.alg.rectpacking",eq_="org.eclipse.elk.alg.rectpacking.firstiteration",eqE="org.eclipse.elk.alg.rectpacking.options",eqS="org.eclipse.elk.rectpacking.optimizationGoal",eqk="org.eclipse.elk.rectpacking.lastPlaceShift",eqx="org.eclipse.elk.rectpacking.currentPosition",eqT="org.eclipse.elk.rectpacking.desiredPosition",eqM="org.eclipse.elk.rectpacking.onlyFirstIteration",eqO="org.eclipse.elk.rectpacking.rowCompaction",eqA="org.eclipse.elk.rectpacking.expandToAspectRatio",eqL="org.eclipse.elk.rectpacking.targetWidth",eqC="org.eclipse.elk.expandNodes",eqI="org.eclipse.elk.rectpacking",eqD="org.eclipse.elk.alg.rectpacking.util",eqN="No implementation available for ",eqP="org.eclipse.elk.alg.spore",eqR="org.eclipse.elk.alg.spore.options",eqj="org.eclipse.elk.sporeCompaction",eqF="org.eclipse.elk.underlyingLayoutAlgorithm",eqY="org.eclipse.elk.processingOrder.treeConstruction",eqB="org.eclipse.elk.processingOrder.spanningTreeCostFunction",eqU="org.eclipse.elk.processingOrder.preferredRoot",eqH="org.eclipse.elk.processingOrder.rootSelection",eq$="org.eclipse.elk.structure.structureExtractionStrategy",eqz="org.eclipse.elk.compaction.compactionStrategy",eqG="org.eclipse.elk.compaction.orthogonal",eqW="org.eclipse.elk.overlapRemoval.maxIterations",eqK="org.eclipse.elk.overlapRemoval.runScanline",eqV="processingOrder",eqq="overlapRemoval",eqZ="org.eclipse.elk.sporeOverlap",eqX="org.eclipse.elk.alg.spore.p1structure",eqJ="org.eclipse.elk.alg.spore.p2processingorder",eqQ="org.eclipse.elk.alg.spore.p3execution",eq1="Invalid index: ",eq0="org.eclipse.elk.core.alg",eq2={331:1},eq3={288:1},eq4="Make sure its type is registered with the ",eq5=" utility class.",eq6="true",eq9="false",eq8="Couldn't clone property '",eq7=.05,eZe="org.eclipse.elk.core.options",eZt=1.2999999523162842,eZn="org.eclipse.elk.box",eZr="org.eclipse.elk.box.packingMode",eZi="org.eclipse.elk.algorithm",eZa="org.eclipse.elk.resolvedAlgorithm",eZo="org.eclipse.elk.bendPoints",eZs="org.eclipse.elk.labelManager",eZu="org.eclipse.elk.scaleFactor",eZc="org.eclipse.elk.animate",eZl="org.eclipse.elk.animTimeFactor",eZf="org.eclipse.elk.layoutAncestors",eZd="org.eclipse.elk.maxAnimTime",eZh="org.eclipse.elk.minAnimTime",eZp="org.eclipse.elk.progressBar",eZb="org.eclipse.elk.validateGraph",eZm="org.eclipse.elk.validateOptions",eZg="org.eclipse.elk.zoomToFit",eZv="org.eclipse.elk.font.name",eZy="org.eclipse.elk.font.size",eZw="org.eclipse.elk.edge.type",eZ_="partitioning",eZE="nodeLabels",eZS="portAlignment",eZk="nodeSize",eZx="port",eZT="portLabels",eZM="insideSelfLoops",eZO="org.eclipse.elk.fixed",eZA="org.eclipse.elk.random",eZL="port must have a parent node to calculate the port side",eZC="The edge needs to have exactly one edge section. Found: ",eZI="org.eclipse.elk.core.util.adapters",eZD="org.eclipse.emf.ecore",eZN="org.eclipse.elk.graph",eZP="EMapPropertyHolder",eZR="ElkBendPoint",eZj="ElkGraphElement",eZF="ElkConnectableShape",eZY="ElkEdge",eZB="ElkEdgeSection",eZU="EModelElement",eZH="ENamedElement",eZ$="ElkLabel",eZz="ElkNode",eZG="ElkPort",eZW={92:1,90:1},eZK="org.eclipse.emf.common.notify.impl",eZV="The feature '",eZq="' is not a valid changeable feature",eZZ="Expecting null",eZX="' is not a valid feature",eZJ="The feature ID",eZQ=" is not a valid feature ID",eZ1=32768,eZ0={105:1,92:1,90:1,56:1,49:1,97:1},eZ2="org.eclipse.emf.ecore.impl",eZ3="org.eclipse.elk.graph.impl",eZ4="Recursive containment not allowed for ",eZ5="The datatype '",eZ6="' is not a valid classifier",eZ9="The value '",eZ8={190:1,3:1,4:1},eZ7="The class '",eXe="http://www.eclipse.org/elk/ElkGraph",eXt=1024,eXn="property",eXr="value",eXi="source",eXa="properties",eXo="identifier",eXs="height",eXu="width",eXc="parent",eXl="text",eXf="children",eXd="hierarchical",eXh="sources",eXp="targets",eXb="sections",eXm="bendPoints",eXg="outgoingShape",eXv="incomingShape",eXy="outgoingSections",eXw="incomingSections",eX_="org.eclipse.emf.common.util",eXE="Severe implementation error in the Json to ElkGraph importer.",eXS="id",eXk="org.eclipse.elk.graph.json",eXx="Unhandled parameter types: ",eXT="startPoint",eXM="An edge must have at least one source and one target (edge id: '",eXO="').",eXA="Referenced edge section does not exist: ",eXL=" (edge id: '",eXC="target",eXI="sourcePoint",eXD="targetPoint",eXN="group",eXP="name",eXR="connectableShape cannot be null",eXj="edge cannot be null",eXF="Passed edge is not 'simple'.",eXY="org.eclipse.elk.graph.util",eXB="The 'no duplicates' constraint is violated",eXU="targetIndex=",eXH=", size=",eX$="sourceIndex=",eXz={3:1,4:1,20:1,28:1,52:1,14:1,15:1,54:1,67:1,63:1,58:1},eXG={3:1,4:1,20:1,28:1,52:1,14:1,47:1,15:1,54:1,67:1,63:1,58:1,588:1},eXW="logging",eXK="measureExecutionTime",eXV="parser.parse.1",eXq="parser.parse.2",eXZ="parser.next.1",eXX="parser.next.2",eXJ="parser.next.3",eXQ="parser.next.4",eX1="parser.factor.1",eX0="parser.factor.2",eX2="parser.factor.3",eX3="parser.factor.4",eX4="parser.factor.5",eX5="parser.factor.6",eX6="parser.atom.1",eX9="parser.atom.2",eX8="parser.atom.3",eX7="parser.atom.4",eJe="parser.atom.5",eJt="parser.cc.1",eJn="parser.cc.2",eJr="parser.cc.3",eJi="parser.cc.5",eJa="parser.cc.6",eJo="parser.cc.7",eJs="parser.cc.8",eJu="parser.ope.1",eJc="parser.ope.2",eJl="parser.ope.3",eJf="parser.descape.1",eJd="parser.descape.2",eJh="parser.descape.3",eJp="parser.descape.4",eJb="parser.descape.5",eJm="parser.process.1",eJg="parser.quantifier.1",eJv="parser.quantifier.2",eJy="parser.quantifier.3",eJw="parser.quantifier.4",eJ_="parser.quantifier.5",eJE="org.eclipse.emf.common.notify",eJS={415:1,672:1},eJk={3:1,4:1,20:1,28:1,52:1,14:1,15:1,67:1,58:1},eJx={366:1,143:1},eJT="index=",eJM={3:1,4:1,5:1,126:1},eJO={3:1,4:1,20:1,28:1,52:1,14:1,15:1,54:1,67:1,58:1},eJA={3:1,6:1,4:1,5:1,192:1},eJL={3:1,4:1,5:1,165:1,367:1},eJC=";/?:@&=+$,",eJI="invalid authority: ",eJD="EAnnotation",eJN="ETypedElement",eJP="EStructuralFeature",eJR="EAttribute",eJj="EClassifier",eJF="EEnumLiteral",eJY="EGenericType",eJB="EOperation",eJU="EParameter",eJH="EReference",eJ$="ETypeParameter",eJz="org.eclipse.emf.ecore.util",eJG={76:1},eJW={3:1,20:1,14:1,15:1,58:1,589:1,76:1,69:1,95:1},eJK="org.eclipse.emf.ecore.util.FeatureMap$Entry",eJV=8192,eJq=2048,eJZ="byte",eJX="char",eJJ="double",eJQ="float",eJ1="int",eJ0="long",eJ2="short",eJ3="java.lang.Object",eJ4={3:1,4:1,5:1,247:1},eJ5={3:1,4:1,5:1,673:1},eJ6={3:1,4:1,20:1,28:1,52:1,14:1,15:1,54:1,67:1,63:1,58:1,69:1},eJ9={3:1,4:1,20:1,28:1,52:1,14:1,15:1,54:1,67:1,63:1,58:1,76:1,69:1,95:1},eJ8="mixed",eJ7="http:///org/eclipse/emf/ecore/util/ExtendedMetaData",eQe="kind",eQt={3:1,4:1,5:1,674:1},eQn={3:1,4:1,20:1,28:1,52:1,14:1,15:1,67:1,58:1,76:1,69:1,95:1},eQr={20:1,28:1,52:1,14:1,15:1,58:1,69:1},eQi={47:1,125:1,279:1},eQa={72:1,332:1},eQo="The value of type '",eQs="' must be of type '",eQu=1316,eQc="http://www.eclipse.org/emf/2002/Ecore",eQl=-32768,eQf="constraints",eQd="baseType",eQh="getEStructuralFeature",eQp="getFeatureID",eQb="feature",eQm="getOperationID",eQg="operation",eQv="defaultValue",eQy="eTypeParameters",eQw="isInstance",eQ_="getEEnumLiteral",eQE="eContainingClass",eQS={55:1},eQk={3:1,4:1,5:1,119:1},eQx="org.eclipse.emf.ecore.resource",eQT={92:1,90:1,591:1,1935:1},eQM="org.eclipse.emf.ecore.resource.impl",eQO="unspecified",eQA="simple",eQL="attribute",eQC="attributeWildcard",eQI="element",eQD="elementWildcard",eQN="collapse",eQP="itemType",eQR="namespace",eQj="##targetNamespace",eQF="whiteSpace",eQY="wildcards",eQB="http://www.eclipse.org/emf/2003/XMLType",eQU="##any",eQH="uninitialized",eQ$="The multiplicity constraint is violated",eQz="org.eclipse.emf.ecore.xml.type",eQG="ProcessingInstruction",eQW="SimpleAnyType",eQK="XMLTypeDocumentRoot",eQV="org.eclipse.emf.ecore.xml.type.impl",eQq="INF",eQZ="processing",eQX="ENTITIES_._base",eQJ="minLength",eQQ="ENTITY",eQ1="NCName",eQ0="IDREFS_._base",eQ2="integer",eQ3="token",eQ4="pattern",eQ5="[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*",eQ6="\\i\\c*",eQ9="[\\i-[:]][\\c-[:]]*",eQ8="nonPositiveInteger",eQ7="maxInclusive",e1e="NMTOKEN",e1t="NMTOKENS_._base",e1n="nonNegativeInteger",e1r="minInclusive",e1i="normalizedString",e1a="unsignedByte",e1o="unsignedInt",e1s="18446744073709551615",e1u="unsignedShort",e1c="processingInstruction",e1l="org.eclipse.emf.ecore.xml.type.internal",e1f=1114111,e1d="Internal Error: shorthands: \\u",e1h="xml:isDigit",e1p="xml:isWord",e1b="xml:isSpace",e1m="xml:isNameChar",e1g="xml:isInitialNameChar",e1v="09٠٩۰۹०९০৯੦੯૦૯୦୯௧௯౦౯೦೯൦൯๐๙໐໙༠༩",e1y="AZaz\xc0\xd6\xd8\xf6\xf8ıĴľŁňŊžƀǃǍǰǴǵǺȗɐʨʻˁΆΆΈΊΌΌΎΡΣώϐϖϚϚϜϜϞϞϠϠϢϳЁЌЎяёќўҁҐӄӇӈӋӌӐӫӮӵӸӹԱՖՙՙաֆאתװײءغفيٱڷںھۀێېۓەەۥۦअहऽऽक़ॡঅঌএঐওনপরললশহড়ঢ়য়ৡৰৱਅਊਏਐਓਨਪਰਲਲ਼ਵਸ਼ਸਹਖ਼ੜਫ਼ਫ਼ੲੴઅઋઍઍએઑઓનપરલળવહઽઽૠૠଅଌଏଐଓନପରଲଳଶହଽଽଡ଼ଢ଼ୟୡஅஊஎஐஒகஙசஜஜஞடணதநபமவஷஹఅఌఎఐఒనపళవహౠౡಅಌಎಐಒನಪಳವಹೞೞೠೡഅഌഎഐഒനപഹൠൡกฮะะาำเๅກຂຄຄງຈຊຊຍຍດທນຟມຣລລວວສຫອຮະະາຳຽຽເໄཀཇཉཀྵႠჅაჶᄀᄀᄂᄃᄅᄇᄉᄉᄋᄌᄎᄒᄼᄼᄾᄾᅀᅀᅌᅌᅎᅎᅐᅐᅔᅕᅙᅙᅟᅡᅣᅣᅥᅥᅧᅧᅩᅩᅭᅮᅲᅳᅵᅵᆞᆞᆨᆨᆫᆫᆮᆯᆷᆸᆺᆺᆼᇂᇫᇫᇰᇰᇹᇹḀẛẠỹἀἕἘἝἠὅὈὍὐὗὙὙὛὛὝὝὟώᾀᾴᾶᾼιιῂῄῆῌῐΐῖΊῠῬῲῴῶῼΩΩKÅ℮℮ↀↂ〇〇〡〩ぁゔァヺㄅㄬ一龥가힣",e1w="Private Use",e1_="ASSIGNED",e1E="\0\x7f\x80\xffĀſƀɏɐʯʰ˿̀ͯͰϿЀӿ԰֏֐׿؀ۿ܀ݏހ޿ऀॿঀ৿਀੿઀૿଀୿஀௿ఀ౿ಀ೿ഀൿ඀෿฀๿຀໿ༀ࿿က႟Ⴀჿᄀᇿሀ፿Ꭰ᏿᐀ᙿ ᚟ᚠ᛿ក៿᠀᢯Ḁỿἀ῿ ⁰₟₠⃏⃐⃿℀⅏⅐↏←⇿∀⋿⌀⏿␀␿⑀⑟①⓿─╿▀▟■◿☀⛿✀➿⠀⣿⺀⻿⼀⿟⿰⿿ 〿぀ゟ゠ヿ㄀ㄯ㄰㆏㆐㆟ㆠㆿ㈀㋿㌀㏿㐀䶵一鿿ꀀ꒏꒐꓏가힣豈﫿ffﭏﭐ﷿︠︯︰﹏﹐﹯ﹰ﻾\uFEFF\uFEFF＀￯",e1S="UNASSIGNED",e1k={3:1,117:1},e1x="org.eclipse.emf.ecore.xml.type.util",e1T={3:1,4:1,5:1,368:1},e1M="org.eclipse.xtext.xbase.lib",e1O="Cannot add elements to a Range",e1A="Cannot set elements in a Range",e1L="Cannot remove elements from a Range",e1C="locale",e1I="default",e1D="user.agent",e1N=null;eB4.goog=eB4.goog||{},eB4.goog.global=eB4.goog.global||eB4,e_Q(),eTS(1,null,{},r),eUe.Fb=function(e){return x5(this,e)},eUe.Gb=function(){return this.gm},eUe.Hb=function(){return Ao(this)},eUe.Ib=function(){var e;return yx(esF(this))+"@"+(e=esj(this)>>>0).toString(16)},eUe.equals=function(e){return this.Fb(e)},eUe.hashCode=function(){return this.Hb()},eUe.toString=function(){return this.Ib()},eTS(290,1,{290:1,2026:1},ese),eUe.le=function(e){var t;return(t=new ese).i=4,e>1?t.c=z9(this,e-1):t.c=this,t},eUe.me=function(){return LW(this),this.b},eUe.ne=function(){return yx(this)},eUe.oe=function(){return LW(this),this.k},eUe.pe=function(){return(4&this.i)!=0},eUe.qe=function(){return(1&this.i)!=0},eUe.Ib=function(){return ee6(this)},eUe.i=0;var e1P=1,e1R=Y5(eUc,"Object",1),e1j=Y5(eUc,"Class",290);eTS(1998,1,eUl),Y5(eUf,"Optional",1998),eTS(1170,1998,eUl,i),eUe.Fb=function(e){return e===this},eUe.Hb=function(){return 2040732332},eUe.Ib=function(){return"Optional.absent()"},eUe.Jb=function(e){return Y9(e),m4(),e0l},Y5(eUf,"Absent",1170),eTS(628,1,{},ve),Y5(eUf,"Joiner",628);var e1F=RL(eUf,"Predicate");eTS(582,1,{169:1,582:1,3:1,45:1},c4),eUe.Mb=function(e){return es_(this,e)},eUe.Lb=function(e){return es_(this,e)},eUe.Fb=function(e){var t;return!!M4(e,582)&&(t=Pp(e,582),eT$(this.a,t.a))},eUe.Hb=function(){return esS(this.a)+306654252},eUe.Ib=function(){return eE7(this.a)},Y5(eUf,"Predicates/AndPredicate",582),eTS(408,1998,{408:1,3:1},c5),eUe.Fb=function(e){var t;return!!M4(e,408)&&(t=Pp(e,408),ecX(this.a,t.a))},eUe.Hb=function(){return 1502476572+esj(this.a)},eUe.Ib=function(){return eUm+this.a+")"},eUe.Jb=function(e){return new c5(H5(e.Kb(this.a),"the Function passed to Optional.transform() must not return null."))},Y5(eUf,"Present",408),eTS(198,1,eUv),eUe.Nb=function(e){F8(this,e)},eUe.Qb=function(){g4()},Y5(eUy,"UnmodifiableIterator",198),eTS(1978,198,eUw),eUe.Qb=function(){g4()},eUe.Rb=function(e){throw p7(new bO)},eUe.Wb=function(e){throw p7(new bO)},Y5(eUy,"UnmodifiableListIterator",1978),eTS(386,1978,eUw),eUe.Ob=function(){return this.c0},eUe.Pb=function(){if(this.c>=this.d)throw p7(new bC);return this.Xb(this.c++)},eUe.Tb=function(){return this.c},eUe.Ub=function(){if(this.c<=0)throw p7(new bC);return this.Xb(--this.c)},eUe.Vb=function(){return this.c-1},eUe.c=0,eUe.d=0,Y5(eUy,"AbstractIndexedListIterator",386),eTS(699,198,eUv),eUe.Ob=function(){return erE(this)},eUe.Pb=function(){return QR(this)},eUe.e=1,Y5(eUy,"AbstractIterator",699),eTS(1986,1,{224:1}),eUe.Zb=function(){var e;return(e=this.f)||(this.f=this.ac())},eUe.Fb=function(e){return es5(this,e)},eUe.Hb=function(){return esj(this.Zb())},eUe.dc=function(){return 0==this.gc()},eUe.ec=function(){return Fh(this)},eUe.Ib=function(){return efF(this.Zb())},Y5(eUy,"AbstractMultimap",1986),eTS(726,1986,eU_),eUe.$b=function(){enK(this)},eUe._b=function(e){return yy(this,e)},eUe.ac=function(){return new wI(this,this.c)},eUe.ic=function(e){return this.hc()},eUe.bc=function(){return new OC(this,this.c)},eUe.jc=function(){return this.mc(this.hc())},eUe.kc=function(){return new m$(this)},eUe.lc=function(){return ew4(this.c.vc().Nc(),new o,64,this.d)},eUe.cc=function(e){return Zq(this,e)},eUe.fc=function(e){return eu9(this,e)},eUe.gc=function(){return this.d},eUe.mc=function(e){return Hj(),new fF(e)},eUe.nc=function(){return new mH(this)},eUe.oc=function(){return ew4(this.c.Cc().Nc(),new a,64,this.d)},eUe.pc=function(e,t){return new XS(this,e,t,null)},eUe.d=0,Y5(eUy,"AbstractMapBasedMultimap",726),eTS(1631,726,eU_),eUe.hc=function(){return new XM(this.a)},eUe.jc=function(){return Hj(),Hj(),e2r},eUe.cc=function(e){return Pp(Zq(this,e),15)},eUe.fc=function(e){return Pp(eu9(this,e),15)},eUe.Zb=function(){return HU(this)},eUe.Fb=function(e){return es5(this,e)},eUe.qc=function(e){return Pp(Zq(this,e),15)},eUe.rc=function(e){return Pp(eu9(this,e),15)},eUe.mc=function(e){return $a(Pp(e,15))},eUe.pc=function(e,t){return Vu(this,e,Pp(t,15),null)},Y5(eUy,"AbstractListMultimap",1631),eTS(732,1,eUE),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return this.c.Ob()||this.e.Ob()},eUe.Pb=function(){var e;return this.e.Ob()||(e=Pp(this.c.Pb(),42),this.b=e.cd(),this.a=Pp(e.dd(),14),this.e=this.a.Kc()),this.sc(this.b,this.e.Pb())},eUe.Qb=function(){this.e.Qb(),this.a.dc()&&this.c.Qb(),--this.d.d},Y5(eUy,"AbstractMapBasedMultimap/Itr",732),eTS(1099,732,eUE,mH),eUe.sc=function(e,t){return t},Y5(eUy,"AbstractMapBasedMultimap/1",1099),eTS(1100,1,{},a),eUe.Kb=function(e){return Pp(e,14).Nc()},Y5(eUy,"AbstractMapBasedMultimap/1methodref$spliterator$Type",1100),eTS(1101,732,eUE,m$),eUe.sc=function(e,t){return new wD(e,t)},Y5(eUy,"AbstractMapBasedMultimap/2",1101);var e1Y=RL(eUS,"Map");eTS(1967,1,eUk),eUe.wc=function(e){ear(this,e)},eUe.yc=function(e,t,n){return el6(this,e,t,n)},eUe.$b=function(){this.vc().$b()},eUe.tc=function(e){return emT(this,e)},eUe._b=function(e){return!!ewt(this,e,!1)},eUe.uc=function(e){var t,n,r;for(n=this.vc().Kc();n.Ob();)if(r=(t=Pp(n.Pb(),42)).dd(),xc(e)===xc(r)||null!=e&&ecX(e,r))return!0;return!1},eUe.Fb=function(e){var t,n,r;if(e===this)return!0;if(!M4(e,83)||(r=Pp(e,83),this.gc()!=r.gc()))return!1;for(n=r.vc().Kc();n.Ob();)if(t=Pp(n.Pb(),42),!this.tc(t))return!1;return!0},eUe.xc=function(e){return xu(ewt(this,e,!1))},eUe.Hb=function(){return eoP(this.vc())},eUe.dc=function(){return 0==this.gc()},eUe.ec=function(){return new fk(this)},eUe.zc=function(e,t){throw p7(new gW("Put not supported on this map"))},eUe.Ac=function(e){eij(this,e)},eUe.Bc=function(e){return xu(ewt(this,e,!0))},eUe.gc=function(){return this.vc().gc()},eUe.Ib=function(){return ewb(this)},eUe.Cc=function(){return new fT(this)},Y5(eUS,"AbstractMap",1967),eTS(1987,1967,eUk),eUe.bc=function(){return new wU(this)},eUe.vc=function(){return Fd(this)},eUe.ec=function(){var e;return(e=this.g)||(this.g=this.bc())},eUe.Cc=function(){var e;return(e=this.i)||(this.i=new wH(this))},Y5(eUy,"Maps/ViewCachingAbstractMap",1987),eTS(389,1987,eUk,wI),eUe.xc=function(e){return etl(this,e)},eUe.Bc=function(e){return euT(this,e)},eUe.$b=function(){this.d==this.e.c?this.e.$b():RG(new RK(this))},eUe._b=function(e){return ecD(this.d,e)},eUe.Ec=function(){return new c7(this)},eUe.Dc=function(){return this.Ec()},eUe.Fb=function(e){return this===e||ecX(this.d,e)},eUe.Hb=function(){return esj(this.d)},eUe.ec=function(){return this.e.ec()},eUe.gc=function(){return this.d.gc()},eUe.Ib=function(){return efF(this.d)},Y5(eUy,"AbstractMapBasedMultimap/AsMap",389);var e1B=RL(eUc,"Iterable");eTS(28,1,eUx),eUe.Jc=function(e){qX(this,e)},eUe.Lc=function(){return this.Oc()},eUe.Nc=function(){return new Gq(this,0)},eUe.Oc=function(){return new R1(null,this.Nc())},eUe.Fc=function(e){throw p7(new gW("Add not supported on this collection"))},eUe.Gc=function(e){return er7(this,e)},eUe.$b=function(){UG(this)},eUe.Hc=function(e){return eds(this,e,!1)},eUe.Ic=function(e){return eot(this,e)},eUe.dc=function(){return 0==this.gc()},eUe.Mc=function(e){return eds(this,e,!0)},eUe.Pc=function(){return Fn(this)},eUe.Qc=function(e){return emk(this,e)},eUe.Ib=function(){return e_F(this)},Y5(eUS,"AbstractCollection",28);var e1U=RL(eUS,"Set");eTS(eUT,28,eUM),eUe.Nc=function(){return new Gq(this,1)},eUe.Fb=function(e){return ehN(this,e)},eUe.Hb=function(){return eoP(this)},Y5(eUS,"AbstractSet",eUT),eTS(1970,eUT,eUM),Y5(eUy,"Sets/ImprovedAbstractSet",1970),eTS(1971,1970,eUM),eUe.$b=function(){this.Rc().$b()},eUe.Hc=function(e){return edz(this,e)},eUe.dc=function(){return this.Rc().dc()},eUe.Mc=function(e){var t;return!!this.Hc(e)&&(t=Pp(e,42),this.Rc().ec().Mc(t.cd()))},eUe.gc=function(){return this.Rc().gc()},Y5(eUy,"Maps/EntrySet",1971),eTS(1097,1971,eUM,c7),eUe.Hc=function(e){return ecC(this.a.d.vc(),e)},eUe.Kc=function(){return new RK(this.a)},eUe.Rc=function(){return this.a},eUe.Mc=function(e){var t;return!!ecC(this.a.d.vc(),e)&&(t=Pp(e,42),ZM(this.a.e,t.cd()),!0)},eUe.Nc=function(){return Pl(this.a.d.vc().Nc(),new le(this.a))},Y5(eUy,"AbstractMapBasedMultimap/AsMap/AsMapEntries",1097),eTS(1098,1,{},le),eUe.Kb=function(e){return qJ(this.a,Pp(e,42))},Y5(eUy,"AbstractMapBasedMultimap/AsMap/AsMapEntries/0methodref$wrapEntry$Type",1098),eTS(730,1,eUE,RK),eUe.Nb=function(e){F8(this,e)},eUe.Pb=function(){var e;return e=Pp(this.b.Pb(),42),this.a=Pp(e.dd(),14),qJ(this.c,e)},eUe.Ob=function(){return this.b.Ob()},eUe.Qb=function(){eah(!!this.a),this.b.Qb(),this.c.e.d-=this.a.gc(),this.a.$b(),this.a=null},Y5(eUy,"AbstractMapBasedMultimap/AsMap/AsMapIterator",730),eTS(532,1970,eUM,wU),eUe.$b=function(){this.b.$b()},eUe.Hc=function(e){return this.b._b(e)},eUe.Jc=function(e){Y9(e),this.b.wc(new lk(e))},eUe.dc=function(){return this.b.dc()},eUe.Kc=function(){return new gr(this.b.vc().Kc())},eUe.Mc=function(e){return!!this.b._b(e)&&(this.b.Bc(e),!0)},eUe.gc=function(){return this.b.gc()},Y5(eUy,"Maps/KeySet",532),eTS(318,532,eUM,OC),eUe.$b=function(){var e;RG((e=this.b.vc().Kc(),new wg(this,e)))},eUe.Ic=function(e){return this.b.ec().Ic(e)},eUe.Fb=function(e){return this===e||ecX(this.b.ec(),e)},eUe.Hb=function(){return esj(this.b.ec())},eUe.Kc=function(){var e;return e=this.b.vc().Kc(),new wg(this,e)},eUe.Mc=function(e){var t,n;return n=0,(t=Pp(this.b.Bc(e),14))&&(n=t.gc(),t.$b(),this.a.d-=n),n>0},eUe.Nc=function(){return this.b.ec().Nc()},Y5(eUy,"AbstractMapBasedMultimap/KeySet",318),eTS(731,1,eUE,wg),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return this.c.Ob()},eUe.Pb=function(){return this.a=Pp(this.c.Pb(),42),this.a.cd()},eUe.Qb=function(){var e;eah(!!this.a),e=Pp(this.a.dd(),14),this.c.Qb(),this.b.a.d-=e.gc(),e.$b(),this.a=null},Y5(eUy,"AbstractMapBasedMultimap/KeySet/1",731),eTS(491,389,{83:1,161:1},LX),eUe.bc=function(){return this.Sc()},eUe.ec=function(){return this.Tc()},eUe.Sc=function(){return new wb(this.c,this.Uc())},eUe.Tc=function(){var e;return(e=this.b)||(this.b=this.Sc())},eUe.Uc=function(){return Pp(this.d,161)},Y5(eUy,"AbstractMapBasedMultimap/SortedAsMap",491),eTS(542,491,eUO,LJ),eUe.bc=function(){return new wm(this.a,Pp(Pp(this.d,161),171))},eUe.Sc=function(){return new wm(this.a,Pp(Pp(this.d,161),171))},eUe.ec=function(){var e;return Pp((e=this.b)||(this.b=new wm(this.a,Pp(Pp(this.d,161),171))),271)},eUe.Tc=function(){var e;return Pp((e=this.b)||(this.b=new wm(this.a,Pp(Pp(this.d,161),171))),271)},eUe.Uc=function(){return Pp(Pp(this.d,161),171)},Y5(eUy,"AbstractMapBasedMultimap/NavigableAsMap",542),eTS(490,318,eUA,wb),eUe.Nc=function(){return this.b.ec().Nc()},Y5(eUy,"AbstractMapBasedMultimap/SortedKeySet",490),eTS(388,490,eUL,wm),Y5(eUy,"AbstractMapBasedMultimap/NavigableKeySet",388),eTS(541,28,eUx,XS),eUe.Fc=function(e){var t,n;return efH(this),n=this.d.dc(),(t=this.d.Fc(e))&&(++this.f.d,n&&CP(this)),t},eUe.Gc=function(e){var t,n,r;return!e.dc()&&(r=(efH(this),this.d.gc()),(t=this.d.Gc(e))&&(n=this.d.gc(),this.f.d+=n-r,0==r&&CP(this)),t)},eUe.$b=function(){var e;0!=(e=(efH(this),this.d.gc()))&&(this.d.$b(),this.f.d-=e,jY(this))},eUe.Hc=function(e){return efH(this),this.d.Hc(e)},eUe.Ic=function(e){return efH(this),this.d.Ic(e)},eUe.Fb=function(e){return e===this||(efH(this),ecX(this.d,e))},eUe.Hb=function(){return efH(this),esj(this.d)},eUe.Kc=function(){return efH(this),new PS(this)},eUe.Mc=function(e){var t;return efH(this),(t=this.d.Mc(e))&&(--this.f.d,jY(this)),t},eUe.gc=function(){return xw(this)},eUe.Nc=function(){return efH(this),this.d.Nc()},eUe.Ib=function(){return efH(this),efF(this.d)},Y5(eUy,"AbstractMapBasedMultimap/WrappedCollection",541);var e1H=RL(eUS,"List");eTS(728,541,{20:1,28:1,14:1,15:1},Fo),eUe.ad=function(e){er8(this,e)},eUe.Nc=function(){return efH(this),this.d.Nc()},eUe.Vc=function(e,t){var n;efH(this),n=this.d.dc(),Pp(this.d,15).Vc(e,t),++this.a.d,n&&CP(this)},eUe.Wc=function(e,t){var n,r,i;return!t.dc()&&(i=(efH(this),this.d.gc()),(n=Pp(this.d,15).Wc(e,t))&&(r=this.d.gc(),this.a.d+=r-i,0==i&&CP(this)),n)},eUe.Xb=function(e){return efH(this),Pp(this.d,15).Xb(e)},eUe.Xc=function(e){return efH(this),Pp(this.d,15).Xc(e)},eUe.Yc=function(){return efH(this),new Mb(this)},eUe.Zc=function(e){return efH(this),new HM(this,e)},eUe.$c=function(e){var t;return efH(this),t=Pp(this.d,15).$c(e),--this.a.d,jY(this),t},eUe._c=function(e,t){return efH(this),Pp(this.d,15)._c(e,t)},eUe.bd=function(e,t){return efH(this),Vu(this.a,this.e,Pp(this.d,15).bd(e,t),this.b?this.b:this)},Y5(eUy,"AbstractMapBasedMultimap/WrappedList",728),eTS(1096,728,{20:1,28:1,14:1,15:1,54:1},A7),Y5(eUy,"AbstractMapBasedMultimap/RandomAccessWrappedList",1096),eTS(620,1,eUE,PS),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return UW(this),this.b.Ob()},eUe.Pb=function(){return UW(this),this.b.Pb()},eUe.Qb=function(){OG(this)},Y5(eUy,"AbstractMapBasedMultimap/WrappedCollection/WrappedIterator",620),eTS(729,620,eUC,Mb,HM),eUe.Qb=function(){OG(this)},eUe.Rb=function(e){var t;t=0==xw(this.a),(UW(this),Pp(this.b,125)).Rb(e),++this.a.a.d,t&&CP(this.a)},eUe.Sb=function(){return(UW(this),Pp(this.b,125)).Sb()},eUe.Tb=function(){return(UW(this),Pp(this.b,125)).Tb()},eUe.Ub=function(){return(UW(this),Pp(this.b,125)).Ub()},eUe.Vb=function(){return(UW(this),Pp(this.b,125)).Vb()},eUe.Wb=function(e){(UW(this),Pp(this.b,125)).Wb(e)},Y5(eUy,"AbstractMapBasedMultimap/WrappedList/WrappedListIterator",729),eTS(727,541,eUA,L3),eUe.Nc=function(){return efH(this),this.d.Nc()},Y5(eUy,"AbstractMapBasedMultimap/WrappedSortedSet",727),eTS(1095,727,eUL,TB),Y5(eUy,"AbstractMapBasedMultimap/WrappedNavigableSet",1095),eTS(1094,541,eUM,L4),eUe.Nc=function(){return efH(this),this.d.Nc()},Y5(eUy,"AbstractMapBasedMultimap/WrappedSet",1094),eTS(1103,1,{},o),eUe.Kb=function(e){return Xb(Pp(e,42))},Y5(eUy,"AbstractMapBasedMultimap/lambda$1$Type",1103),eTS(1102,1,{},lt),eUe.Kb=function(e){return new wD(this.a,e)},Y5(eUy,"AbstractMapBasedMultimap/lambda$2$Type",1102);var e1$=RL(eUS,"Map/Entry");eTS(345,1,eUI),eUe.Fb=function(e){var t;return!!M4(e,42)&&(t=Pp(e,42),BG(this.cd(),t.cd())&&BG(this.dd(),t.dd()))},eUe.Hb=function(){var e,t;return e=this.cd(),t=this.dd(),(null==e?0:esj(e))^(null==t?0:esj(t))},eUe.ed=function(e){throw p7(new bO)},eUe.Ib=function(){return this.cd()+"="+this.dd()},Y5(eUy,eUD,345),eTS(1988,28,eUx),eUe.$b=function(){this.fd().$b()},eUe.Hc=function(e){var t;return!!M4(e,42)&&(t=Pp(e,42),Kr(this.fd(),t.cd(),t.dd()))},eUe.Mc=function(e){var t;return!!M4(e,42)&&(t=Pp(e,42),Ki(this.fd(),t.cd(),t.dd()))},eUe.gc=function(){return this.fd().d},Y5(eUy,"Multimaps/Entries",1988),eTS(733,1988,eUx,ln),eUe.Kc=function(){return this.a.kc()},eUe.fd=function(){return this.a},eUe.Nc=function(){return this.a.lc()},Y5(eUy,"AbstractMultimap/Entries",733),eTS(734,733,eUM,mz),eUe.Nc=function(){return this.a.lc()},eUe.Fb=function(e){return eEB(this,e)},eUe.Hb=function(){return eie(this)},Y5(eUy,"AbstractMultimap/EntrySet",734),eTS(735,28,eUx,lr),eUe.$b=function(){this.a.$b()},eUe.Hc=function(e){return eun(this.a,e)},eUe.Kc=function(){return this.a.nc()},eUe.gc=function(){return this.a.d},eUe.Nc=function(){return this.a.oc()},Y5(eUy,"AbstractMultimap/Values",735),eTS(1989,28,{835:1,20:1,28:1,14:1}),eUe.Jc=function(e){Y9(e),Uz(this).Jc(new lS(e))},eUe.Nc=function(){var e;return ew4(e=Uz(this).Nc(),new y,64|1296&e.qd(),this.a.d)},eUe.Fc=function(e){return g5(),!0},eUe.Gc=function(e){return Y9(this),Y9(e),M4(e,543)?KM(Pp(e,835)):!e.dc()&&eel(this,e.Kc())},eUe.Hc=function(e){var t;return((t=Pp(ecA(HU(this.a),e),14))?t.gc():0)>0},eUe.Fb=function(e){return eMc(this,e)},eUe.Hb=function(){return esj(Uz(this))},eUe.dc=function(){return Uz(this).dc()},eUe.Mc=function(e){return ekJ(this,e,1)>0},eUe.Ib=function(){return efF(Uz(this))},Y5(eUy,"AbstractMultiset",1989),eTS(1991,1970,eUM),eUe.$b=function(){enK(this.a.a)},eUe.Hc=function(e){var t,n;return!!M4(e,492)&&(n=Pp(e,416),!(0>=Pp(n.a.dd(),14).gc())&&(t=GB(this.a,n.a.cd()))==Pp(n.a.dd(),14).gc())},eUe.Mc=function(e){var t,n,r,i;return!!M4(e,492)&&(t=(n=Pp(e,416)).a.cd(),0!=(r=Pp(n.a.dd(),14).gc()))&&ekQ(i=this.a,t,r)},Y5(eUy,"Multisets/EntrySet",1991),eTS(1109,1991,eUM,li),eUe.Kc=function(){return new ga(Fd(HU(this.a.a)).Kc())},eUe.gc=function(){return HU(this.a.a).gc()},Y5(eUy,"AbstractMultiset/EntrySet",1109),eTS(619,726,eU_),eUe.hc=function(){return this.gd()},eUe.jc=function(){return this.hd()},eUe.cc=function(e){return this.jd(e)},eUe.fc=function(e){return this.kd(e)},eUe.Zb=function(){var e;return(e=this.f)||(this.f=this.ac())},eUe.hd=function(){return Hj(),Hj(),e2a},eUe.Fb=function(e){return es5(this,e)},eUe.jd=function(e){return Pp(Zq(this,e),21)},eUe.kd=function(e){return Pp(eu9(this,e),21)},eUe.mc=function(e){return Hj(),new vd(Pp(e,21))},eUe.pc=function(e,t){return new L4(this,e,Pp(t,21))},Y5(eUy,"AbstractSetMultimap",619),eTS(1657,619,eU_),eUe.hc=function(){return new yB(this.b)},eUe.gd=function(){return new yB(this.b)},eUe.jc=function(){return Bo(new yB(this.b))},eUe.hd=function(){return Bo(new yB(this.b))},eUe.cc=function(e){return Pp(Pp(Zq(this,e),21),84)},eUe.jd=function(e){return Pp(Pp(Zq(this,e),21),84)},eUe.fc=function(e){return Pp(Pp(eu9(this,e),21),84)},eUe.kd=function(e){return Pp(Pp(eu9(this,e),21),84)},eUe.mc=function(e){return M4(e,271)?Bo(Pp(e,271)):(Hj(),new O4(Pp(e,84)))},eUe.Zb=function(){var e;return(e=this.f)||(this.f=M4(this.c,171)?new LJ(this,Pp(this.c,171)):M4(this.c,161)?new LX(this,Pp(this.c,161)):new wI(this,this.c))},eUe.pc=function(e,t){return M4(t,271)?new TB(this,e,Pp(t,271)):new L3(this,e,Pp(t,84))},Y5(eUy,"AbstractSortedSetMultimap",1657),eTS(1658,1657,eU_),eUe.Zb=function(){var e;return Pp(Pp((e=this.f)||(this.f=M4(this.c,171)?new LJ(this,Pp(this.c,171)):M4(this.c,161)?new LX(this,Pp(this.c,161)):new wI(this,this.c)),161),171)},eUe.ec=function(){var e;return Pp(Pp((e=this.i)||(this.i=M4(this.c,171)?new wm(this,Pp(this.c,171)):M4(this.c,161)?new wb(this,Pp(this.c,161)):new OC(this,this.c)),84),271)},eUe.bc=function(){return M4(this.c,171)?new wm(this,Pp(this.c,171)):M4(this.c,161)?new wb(this,Pp(this.c,161)):new OC(this,this.c)},Y5(eUy,"AbstractSortedKeySortedSetMultimap",1658),eTS(2010,1,{1947:1}),eUe.Fb=function(e){return ev7(this,e)},eUe.Hb=function(){var e;return eoP((e=this.g)||(this.g=new la(this)))},eUe.Ib=function(){var e;return ewb((e=this.f)||(this.f=new OP(this)))},Y5(eUy,"AbstractTable",2010),eTS(665,eUT,eUM,la),eUe.$b=function(){g6()},eUe.Hc=function(e){var t,n;return!!M4(e,468)&&(t=Pp(e,682),!!(n=Pp(ecA(Y7(this.a),xh(t.c.e,t.b)),83))&&ecC(n.vc(),new wD(xh(t.c.c,t.a),X_(t.c,t.b,t.a))))},eUe.Kc=function(){return $e(this.a)},eUe.Mc=function(e){var t,n;return!!M4(e,468)&&(t=Pp(e,682),!!(n=Pp(ecA(Y7(this.a),xh(t.c.e,t.b)),83))&&ecI(n.vc(),new wD(xh(t.c.c,t.a),X_(t.c,t.b,t.a))))},eUe.gc=function(){return R8(this.a)},eUe.Nc=function(){return KH(this.a)},Y5(eUy,"AbstractTable/CellSet",665),eTS(1928,28,eUx,lo),eUe.$b=function(){g6()},eUe.Hc=function(e){return ewx(this.a,e)},eUe.Kc=function(){return $t(this.a)},eUe.gc=function(){return R8(this.a)},eUe.Nc=function(){return Kd(this.a)},Y5(eUy,"AbstractTable/Values",1928),eTS(1632,1631,eU_),Y5(eUy,"ArrayListMultimapGwtSerializationDependencies",1632),eTS(513,1632,eU_,gQ,G$),eUe.hc=function(){return new XM(this.a)},eUe.a=0,Y5(eUy,"ArrayListMultimap",513),eTS(664,2010,{664:1,1947:1,3:1},exj),Y5(eUy,"ArrayTable",664),eTS(1924,386,eUw,OI),eUe.Xb=function(e){return new eo7(this.a,e)},Y5(eUy,"ArrayTable/1",1924),eTS(1925,1,{},c6),eUe.ld=function(e){return new eo7(this.a,e)},Y5(eUy,"ArrayTable/1methodref$getCell$Type",1925),eTS(2011,1,{682:1}),eUe.Fb=function(e){var t;return e===this||!!M4(e,468)&&(t=Pp(e,682),BG(xh(this.c.e,this.b),xh(t.c.e,t.b))&&BG(xh(this.c.c,this.a),xh(t.c.c,t.a))&&BG(X_(this.c,this.b,this.a),X_(t.c,t.b,t.a)))},eUe.Hb=function(){return euF(eow(vx(e1R,1),eUp,1,5,[xh(this.c.e,this.b),xh(this.c.c,this.a),X_(this.c,this.b,this.a)]))},eUe.Ib=function(){return"("+xh(this.c.e,this.b)+","+xh(this.c.c,this.a)+")="+X_(this.c,this.b,this.a)},Y5(eUy,"Tables/AbstractCell",2011),eTS(468,2011,{468:1,682:1},eo7),eUe.a=0,eUe.b=0,eUe.d=0,Y5(eUy,"ArrayTable/2",468),eTS(1927,1,{},c9),eUe.ld=function(e){return Qo(this.a,e)},Y5(eUy,"ArrayTable/2methodref$getValue$Type",1927),eTS(1926,386,eUw,OD),eUe.Xb=function(e){return Qo(this.a,e)},Y5(eUy,"ArrayTable/3",1926),eTS(1979,1967,eUk),eUe.$b=function(){RG(this.kc())},eUe.vc=function(){return new lx(this)},eUe.lc=function(){return new Uq(this.kc(),this.gc())},Y5(eUy,"Maps/IteratorBasedAbstractMap",1979),eTS(828,1979,eUk),eUe.$b=function(){throw p7(new bO)},eUe._b=function(e){return yE(this.c,e)},eUe.kc=function(){return new ON(this,this.c.b.c.gc())},eUe.lc=function(){return Rj(this.c.b.c.gc(),16,new c8(this))},eUe.xc=function(e){var t;return(t=Pp(Iq(this.c,e),19))?this.nd(t.a):null},eUe.dc=function(){return this.c.b.c.dc()},eUe.ec=function(){return Fl(this.c)},eUe.zc=function(e,t){var n;if(!(n=Pp(Iq(this.c,e),19)))throw p7(new gL(this.md()+" "+e+" not in "+Fl(this.c)));return this.od(n.a,t)},eUe.Bc=function(e){throw p7(new bO)},eUe.gc=function(){return this.c.b.c.gc()},Y5(eUy,"ArrayTable/ArrayMap",828),eTS(1923,1,{},c8),eUe.ld=function(e){return Bs(this.a,e)},Y5(eUy,"ArrayTable/ArrayMap/0methodref$getEntry$Type",1923),eTS(1921,345,eUI,wk),eUe.cd=function(){return OB(this.a,this.b)},eUe.dd=function(){return this.a.nd(this.b)},eUe.ed=function(e){return this.a.od(this.b,e)},eUe.b=0,Y5(eUy,"ArrayTable/ArrayMap/1",1921),eTS(1922,386,eUw,ON),eUe.Xb=function(e){return Bs(this.a,e)},Y5(eUy,"ArrayTable/ArrayMap/2",1922),eTS(1920,828,eUk,F2),eUe.md=function(){return"Column"},eUe.nd=function(e){return X_(this.b,this.a,e)},eUe.od=function(e,t){return eoy(this.b,this.a,e,t)},eUe.a=0,Y5(eUy,"ArrayTable/Row",1920),eTS(829,828,eUk,OP),eUe.nd=function(e){return new F2(this.a,e)},eUe.zc=function(e,t){return Pp(t,83),g9()},eUe.od=function(e,t){return Pp(t,83),g8()},eUe.md=function(){return"Row"},Y5(eUy,"ArrayTable/RowMap",829),eTS(1120,1,eUj,wx),eUe.qd=function(){return -262&this.a.qd()},eUe.rd=function(){return this.a.rd()},eUe.Nb=function(e){this.a.Nb(new ww(e,this.b))},eUe.sd=function(e){return this.a.sd(new wy(e,this.b))},Y5(eUy,"CollectSpliterators/1",1120),eTS(1121,1,eUF,wy),eUe.td=function(e){this.a.td(this.b.Kb(e))},Y5(eUy,"CollectSpliterators/1/lambda$0$Type",1121),eTS(1122,1,eUF,ww),eUe.td=function(e){this.a.td(this.b.Kb(e))},Y5(eUy,"CollectSpliterators/1/lambda$1$Type",1122),eTS(1123,1,eUj,K4),eUe.qd=function(){return this.a},eUe.rd=function(){return this.d&&(this.b=MS(this.b,this.d.rd())),MS(this.b,0)},eUe.Nb=function(e){this.d&&(this.d.Nb(e),this.d=null),this.c.Nb(new wv(this.e,e)),this.b=0},eUe.sd=function(e){for(;;){if(this.d&&this.d.sd(e))return xg(this.b,eUY)&&(this.b=efe(this.b,1)),!0;if(this.d=null,!this.c.sd(new w_(this,this.e)))return!1}},eUe.a=0,eUe.b=0,Y5(eUy,"CollectSpliterators/1FlatMapSpliterator",1123),eTS(1124,1,eUF,w_),eUe.td=function(e){Iv(this.a,this.b,e)},Y5(eUy,"CollectSpliterators/1FlatMapSpliterator/lambda$0$Type",1124),eTS(1125,1,eUF,wv),eUe.td=function(e){M9(this.b,this.a,e)},Y5(eUy,"CollectSpliterators/1FlatMapSpliterator/lambda$1$Type",1125),eTS(1117,1,eUj,Ig),eUe.qd=function(){return 16464|this.b},eUe.rd=function(){return this.a.rd()},eUe.Nb=function(e){this.a.xe(new wS(e,this.c))},eUe.sd=function(e){return this.a.ye(new wE(e,this.c))},eUe.b=0,Y5(eUy,"CollectSpliterators/1WithCharacteristics",1117),eTS(1118,1,eUB,wE),eUe.ud=function(e){this.a.td(this.b.ld(e))},Y5(eUy,"CollectSpliterators/1WithCharacteristics/lambda$0$Type",1118),eTS(1119,1,eUB,wS),eUe.ud=function(e){this.a.td(this.b.ld(e))},Y5(eUy,"CollectSpliterators/1WithCharacteristics/lambda$1$Type",1119),eTS(245,1,eUU),eUe.wd=function(e){return this.vd(Pp(e,245))},eUe.vd=function(e){var t;return e==(m2(),e0d)?1:e==(m3(),e0f)?-1:0!=(t=(Rg(),eiK(this.a,e.a)))?t:M4(this,519)==M4(e,519)?0:M4(this,519)?1:-1},eUe.zd=function(){return this.a},eUe.Fb=function(e){return ehd(this,e)},Y5(eUy,"Cut",245),eTS(1761,245,eUU,vb),eUe.vd=function(e){return e==this?0:1},eUe.xd=function(e){throw p7(new b_)},eUe.yd=function(e){e.a+="+∞)"},eUe.zd=function(){throw p7(new gC(eUH))},eUe.Hb=function(){return wK(),ebh(this)},eUe.Ad=function(e){return!1},eUe.Ib=function(){return"+∞"},Y5(eUy,"Cut/AboveAll",1761),eTS(519,245,{245:1,519:1,3:1,35:1},OW),eUe.xd=function(e){xT((e.a+="(",e),this.a)},eUe.yd=function(e){Bd(xT(e,this.a),93)},eUe.Hb=function(){return~esj(this.a)},eUe.Ad=function(e){return Rg(),0>eiK(this.a,e)},eUe.Ib=function(){return"/"+this.a+"\\"},Y5(eUy,"Cut/AboveValue",519),eTS(1760,245,eUU,vm),eUe.vd=function(e){return e==this?0:-1},eUe.xd=function(e){e.a+="(-∞"},eUe.yd=function(e){throw p7(new b_)},eUe.zd=function(){throw p7(new gC(eUH))},eUe.Hb=function(){return wK(),ebh(this)},eUe.Ad=function(e){return!0},eUe.Ib=function(){return"-∞"},Y5(eUy,"Cut/BelowAll",1760),eTS(1762,245,eUU,OK),eUe.xd=function(e){xT((e.a+="[",e),this.a)},eUe.yd=function(e){Bd(xT(e,this.a),41)},eUe.Hb=function(){return esj(this.a)},eUe.Ad=function(e){return Rg(),0>=eiK(this.a,e)},eUe.Ib=function(){return"\\"+this.a+"/"},Y5(eUy,"Cut/BelowValue",1762),eTS(537,1,eU$),eUe.Jc=function(e){qX(this,e)},eUe.Ib=function(){return elq(Pp(H5(this,"use Optional.orNull() instead of Optional.or(null)"),20).Kc())},Y5(eUy,"FluentIterable",537),eTS(433,537,eU$,xq),eUe.Kc=function(){return new Fa(OH(this.a.Kc(),new c))},Y5(eUy,"FluentIterable/2",433),eTS(1046,537,eU$,xZ),eUe.Kc=function(){return Y_(this)},Y5(eUy,"FluentIterable/3",1046),eTS(708,386,eUw,Oj),eUe.Xb=function(e){return this.a[e].Kc()},Y5(eUy,"FluentIterable/3/1",708),eTS(1972,1,{}),eUe.Ib=function(){return efF(this.Bd().b)},Y5(eUy,"ForwardingObject",1972),eTS(1973,1972,eUz),eUe.Bd=function(){return this.Cd()},eUe.Jc=function(e){qX(this,e)},eUe.Lc=function(){return this.Oc()},eUe.Nc=function(){return new Gq(this,0)},eUe.Oc=function(){return new R1(null,this.Nc())},eUe.Fc=function(e){return this.Cd(),yD()},eUe.Gc=function(e){return this.Cd(),yN()},eUe.$b=function(){this.Cd(),yP()},eUe.Hc=function(e){return this.Cd().Hc(e)},eUe.Ic=function(e){return this.Cd().Ic(e)},eUe.dc=function(){return this.Cd().b.dc()},eUe.Kc=function(){return this.Cd().Kc()},eUe.Mc=function(e){return this.Cd(),yR()},eUe.gc=function(){return this.Cd().b.gc()},eUe.Pc=function(){return this.Cd().Pc()},eUe.Qc=function(e){return this.Cd().Qc(e)},Y5(eUy,"ForwardingCollection",1973),eTS(1980,28,eUG),eUe.Kc=function(){return this.Ed()},eUe.Fc=function(e){throw p7(new bO)},eUe.Gc=function(e){throw p7(new bO)},eUe.$b=function(){throw p7(new bO)},eUe.Hc=function(e){return null!=e&&eds(this,e,!1)},eUe.Dd=function(){switch(this.gc()){case 0:return Bx(),Bx(),e0h;case 1:return Bx(),new Rz(Y9(this.Ed().Pb()));default:return new F3(this,this.Pc())}},eUe.Mc=function(e){throw p7(new bO)},Y5(eUy,"ImmutableCollection",1980),eTS(712,1980,eUG,bb),eUe.Kc=function(){return JJ(this.a.Kc())},eUe.Hc=function(e){return null!=e&&this.a.Hc(e)},eUe.Ic=function(e){return this.a.Ic(e)},eUe.dc=function(){return this.a.dc()},eUe.Ed=function(){return JJ(this.a.Kc())},eUe.gc=function(){return this.a.gc()},eUe.Pc=function(){return this.a.Pc()},eUe.Qc=function(e){return this.a.Qc(e)},eUe.Ib=function(){return efF(this.a)},Y5(eUy,"ForwardingImmutableCollection",712),eTS(152,1980,eUW),eUe.Kc=function(){return this.Ed()},eUe.Yc=function(){return this.Fd(0)},eUe.Zc=function(e){return this.Fd(e)},eUe.ad=function(e){er8(this,e)},eUe.Nc=function(){return new Gq(this,16)},eUe.bd=function(e,t){return this.Gd(e,t)},eUe.Vc=function(e,t){throw p7(new bO)},eUe.Wc=function(e,t){throw p7(new bO)},eUe.Fb=function(e){return eTJ(this,e)},eUe.Hb=function(){return eaI(this)},eUe.Xc=function(e){return null==e?-1:emx(this,e)},eUe.Ed=function(){return this.Fd(0)},eUe.Fd=function(e){return AR(this,e)},eUe.$c=function(e){throw p7(new bO)},eUe._c=function(e,t){throw p7(new bO)},eUe.Gd=function(e,t){var n;return ecT((n=new wz(this),new Gz(n,e,t)))},Y5(eUy,"ImmutableList",152),eTS(2006,152,eUW),eUe.Kc=function(){return JJ(this.Hd().Kc())},eUe.bd=function(e,t){return ecT(this.Hd().bd(e,t))},eUe.Hc=function(e){return null!=e&&this.Hd().Hc(e)},eUe.Ic=function(e){return this.Hd().Ic(e)},eUe.Fb=function(e){return ecX(this.Hd(),e)},eUe.Xb=function(e){return xh(this,e)},eUe.Hb=function(){return esj(this.Hd())},eUe.Xc=function(e){return this.Hd().Xc(e)},eUe.dc=function(){return this.Hd().dc()},eUe.Ed=function(){return JJ(this.Hd().Kc())},eUe.gc=function(){return this.Hd().gc()},eUe.Gd=function(e,t){return ecT(this.Hd().bd(e,t))},eUe.Pc=function(){return this.Hd().Qc(Je(e1R,eUp,1,this.Hd().gc(),5,1))},eUe.Qc=function(e){return this.Hd().Qc(e)},eUe.Ib=function(){return efF(this.Hd())},Y5(eUy,"ForwardingImmutableList",2006),eTS(714,1,eUV),eUe.vc=function(){return Fc(this)},eUe.wc=function(e){ear(this,e)},eUe.ec=function(){return Fl(this)},eUe.yc=function(e,t,n){return el6(this,e,t,n)},eUe.Cc=function(){return this.Ld()},eUe.$b=function(){throw p7(new bO)},eUe._b=function(e){return null!=this.xc(e)},eUe.uc=function(e){return this.Ld().Hc(e)},eUe.Jd=function(){return new bm(this)},eUe.Kd=function(){return new bg(this)},eUe.Fb=function(e){return eua(this,e)},eUe.Hb=function(){return Fc(this).Hb()},eUe.dc=function(){return 0==this.gc()},eUe.zc=function(e,t){return g7()},eUe.Bc=function(e){throw p7(new bO)},eUe.Ib=function(){return eEo(this)},eUe.Ld=function(){return this.e?this.e:this.e=this.Kd()},eUe.c=null,eUe.d=null,eUe.e=null,Y5(eUy,"ImmutableMap",714),eTS(715,714,eUV),eUe._b=function(e){return yE(this,e)},eUe.uc=function(e){return w1(this.b,e)},eUe.Id=function(){return ecM(new lu(this))},eUe.Jd=function(){return ecM(Uk(this.b))},eUe.Kd=function(){return Dn(),new bb(UE(this.b))},eUe.Fb=function(e){return w2(this.b,e)},eUe.xc=function(e){return Iq(this,e)},eUe.Hb=function(){return esj(this.b.c)},eUe.dc=function(){return this.b.c.dc()},eUe.gc=function(){return this.b.c.gc()},eUe.Ib=function(){return efF(this.b.c)},Y5(eUy,"ForwardingImmutableMap",715),eTS(1974,1973,eUq),eUe.Bd=function(){return this.Md()},eUe.Cd=function(){return this.Md()},eUe.Nc=function(){return new Gq(this,1)},eUe.Fb=function(e){return e===this||this.Md().Fb(e)},eUe.Hb=function(){return this.Md().Hb()},Y5(eUy,"ForwardingSet",1974),eTS(1069,1974,eUq,lu),eUe.Bd=function(){return US(this.a.b)},eUe.Cd=function(){return US(this.a.b)},eUe.Hc=function(e){if(M4(e,42)&&null==Pp(e,42).cd())return!1;try{return wQ(US(this.a.b),e)}catch(t){if(t=eoa(t),M4(t,205))return!1;throw p7(t)}},eUe.Md=function(){return US(this.a.b)},eUe.Qc=function(e){var t;return t=$L(US(this.a.b),e),US(this.a.b).b.gc()=0?"+":"")+(n/60|0),t=Tt(eB4.Math.abs(n)%60),(e_E(),e2l)[this.q.getDay()]+" "+e2f[this.q.getMonth()]+" "+Tt(this.q.getDate())+" "+Tt(this.q.getHours())+":"+Tt(this.q.getMinutes())+":"+Tt(this.q.getSeconds())+" GMT"+e+t+" "+this.q.getFullYear()};var e1Q=Y5(eUS,"Date",199);eTS(1915,199,eHB,evI),eUe.a=!1,eUe.b=0,eUe.c=0,eUe.d=0,eUe.e=0,eUe.f=0,eUe.g=!1,eUe.i=0,eUe.j=0,eUe.k=0,eUe.n=0,eUe.o=0,eUe.p=0,Y5("com.google.gwt.i18n.shared.impl","DateRecord",1915),eTS(1966,1,{}),eUe.fe=function(){return null},eUe.ge=function(){return null},eUe.he=function(){return null},eUe.ie=function(){return null},eUe.je=function(){return null},Y5(eHU,"JSONValue",1966),eTS(216,1966,{216:1},lN,lL),eUe.Fb=function(e){return!!M4(e,216)&&W$(this.a,Pp(e,216).a)},eUe.ee=function(){return be},eUe.Hb=function(){return $n(this.a)},eUe.fe=function(){return this},eUe.Ib=function(){var e,t,n;for(t=0,n=new O0("["),e=this.a.length;t0&&(n.a+=","),xT(n,eep(this,t));return n.a+="]",n.a},Y5(eHU,"JSONArray",216),eTS(483,1966,{483:1},lC),eUe.ee=function(){return bt},eUe.ge=function(){return this},eUe.Ib=function(){return OQ(),""+this.a},eUe.a=!1,Y5(eHU,"JSONBoolean",483),eTS(985,60,eHr,gs),Y5(eHU,"JSONException",985),eTS(1023,1966,{},g),eUe.ee=function(){return bo},eUe.Ib=function(){return eUg},Y5(eHU,"JSONNull",1023),eTS(258,1966,{258:1},lI),eUe.Fb=function(e){return!!M4(e,258)&&this.a==Pp(e,258).a},eUe.ee=function(){return bn},eUe.Hb=function(){return Ti(this.a)},eUe.he=function(){return this},eUe.Ib=function(){return this.a+""},eUe.a=0,Y5(eHU,"JSONNumber",258),eTS(183,1966,{183:1},gu,lD),eUe.Fb=function(e){return!!M4(e,183)&&W$(this.a,Pp(e,183).a)},eUe.ee=function(){return br},eUe.Hb=function(){return $n(this.a)},eUe.ie=function(){return this},eUe.Ib=function(){var e,t,n,r,i,a,o;for(r=0,o=new O0("{"),e=!0,i=(n=a=erG(this,Je(e17,eUP,2,0,6,1))).length;r=0?":"+this.c:"")+")"},eUe.c=0;var e18=Y5(eUc,"StackTraceElement",310);e0c={3:1,475:1,35:1,2:1};var e17=Y5(eUc,eHa,2);eTS(107,418,{475:1},vs,vu,O1),Y5(eUc,"StringBuffer",107),eTS(100,418,{475:1},vc,vl,O0),Y5(eUc,"StringBuilder",100),eTS(687,73,eHZ,vf),Y5(eUc,"StringIndexOutOfBoundsException",687),eTS(2043,1,{}),eTS(844,1,{},N),eUe.Kb=function(e){return Pp(e,78).e},Y5(eUc,"Throwable/lambda$0$Type",844),eTS(41,60,{3:1,102:1,60:1,78:1,41:1},bO,gW),Y5(eUc,"UnsupportedOperationException",41),eTS(240,236,{3:1,35:1,236:1,240:1},eew,yY),eUe.wd=function(e){return eDG(this,Pp(e,240))},eUe.ke=function(){return eEu(eRy(this))},eUe.Fb=function(e){var t;return this===e||!!M4(e,240)&&(t=Pp(e,240),this.e==t.e&&0==eDG(this,t))},eUe.Hb=function(){var e;return 0!=this.b?this.b:this.a<54?(e=eap(this.f),this.b=jE(WM(e,-1)),this.b=33*this.b+jE(WM(Fv(e,32),-1)),this.b=17*this.b+zy(this.e),this.b):(this.b=17*ect(this.c)+zy(this.e),this.b)},eUe.Ib=function(){return eRy(this)},eUe.a=0,eUe.b=0,eUe.d=0,eUe.e=0,eUe.f=0;var e0e=Y5("java.math","BigDecimal",240);eTS(91,236,{3:1,35:1,236:1,91:1},ep4,XE,F7,ey$,eh5,TU),eUe.wd=function(e){return ehI(this,Pp(e,91))},eUe.ke=function(){return eEu(eBw(this,0))},eUe.Fb=function(e){return ef6(this,e)},eUe.Hb=function(){return ect(this)},eUe.Ib=function(){return eBw(this,0)},eUe.b=-2,eUe.c=0,eUe.d=0,eUe.e=0;var e0t=Y5("java.math","BigInteger",91);eTS(488,1967,eUk),eUe.$b=function(){Yy(this)},eUe._b=function(e){return F9(this,e)},eUe.uc=function(e){return euo(this,e,this.g)||euo(this,e,this.f)},eUe.vc=function(){return new fS(this)},eUe.xc=function(e){return Bp(this,e)},eUe.zc=function(e,t){return Um(this,e,t)},eUe.Bc=function(e){return Z3(this,e)},eUe.gc=function(){return wq(this)},Y5(eUS,"AbstractHashMap",488),eTS(261,eUT,eUM,fS),eUe.$b=function(){this.a.$b()},eUe.Hc=function(e){return KN(this,e)},eUe.Kc=function(){return new esz(this.a)},eUe.Mc=function(e){var t;return!!KN(this,e)&&(t=Pp(e,42).cd(),this.a.Bc(t),!0)},eUe.gc=function(){return this.a.gc()},Y5(eUS,"AbstractHashMap/EntrySet",261),eTS(262,1,eUE,esz),eUe.Nb=function(e){F8(this,e)},eUe.Pb=function(){return etz(this)},eUe.Ob=function(){return this.b},eUe.Qb=function(){JM(this)},eUe.b=!1,Y5(eUS,"AbstractHashMap/EntrySetIterator",262),eTS(417,1,eUE,fE),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return Et(this)},eUe.Pb=function(){return HL(this)},eUe.Qb=function(){BH(this)},eUe.b=0,eUe.c=-1,Y5(eUS,"AbstractList/IteratorImpl",417),eTS(96,417,eUC,KB),eUe.Qb=function(){BH(this)},eUe.Rb=function(e){CD(this,e)},eUe.Sb=function(){return this.b>0},eUe.Tb=function(){return this.b},eUe.Ub=function(){return A6(this.b>0),this.a.Xb(this.c=--this.b)},eUe.Vb=function(){return this.b-1},eUe.Wb=function(e){A4(-1!=this.c),this.a._c(this.c,e)},Y5(eUS,"AbstractList/ListIteratorImpl",96),eTS(219,52,eU5,Gz),eUe.Vc=function(e,t){Gp(e,this.b),this.c.Vc(this.a+e,t),++this.b},eUe.Xb=function(e){return GK(e,this.b),this.c.Xb(this.a+e)},eUe.$c=function(e){var t;return GK(e,this.b),t=this.c.$c(this.a+e),--this.b,t},eUe._c=function(e,t){return GK(e,this.b),this.c._c(this.a+e,t)},eUe.gc=function(){return this.b},eUe.a=0,eUe.b=0,Y5(eUS,"AbstractList/SubList",219),eTS(384,eUT,eUM,fk),eUe.$b=function(){this.a.$b()},eUe.Hc=function(e){return this.a._b(e)},eUe.Kc=function(){var e;return e=this.a.vc().Kc(),new fx(e)},eUe.Mc=function(e){return!!this.a._b(e)&&(this.a.Bc(e),!0)},eUe.gc=function(){return this.a.gc()},Y5(eUS,"AbstractMap/1",384),eTS(691,1,eUE,fx),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return this.a.Ob()},eUe.Pb=function(){var e;return(e=Pp(this.a.Pb(),42)).cd()},eUe.Qb=function(){this.a.Qb()},Y5(eUS,"AbstractMap/1/1",691),eTS(226,28,eUx,fT),eUe.$b=function(){this.a.$b()},eUe.Hc=function(e){return this.a.uc(e)},eUe.Kc=function(){var e;return e=this.a.vc().Kc(),new fN(e)},eUe.gc=function(){return this.a.gc()},Y5(eUS,"AbstractMap/2",226),eTS(294,1,eUE,fN),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return this.a.Ob()},eUe.Pb=function(){var e;return(e=Pp(this.a.Pb(),42)).dd()},eUe.Qb=function(){this.a.Qb()},Y5(eUS,"AbstractMap/2/1",294),eTS(484,1,{484:1,42:1}),eUe.Fb=function(e){var t;return!!M4(e,42)&&(t=Pp(e,42),UT(this.d,t.cd())&&UT(this.e,t.dd()))},eUe.cd=function(){return this.d},eUe.dd=function(){return this.e},eUe.Hb=function(){return TK(this.d)^TK(this.e)},eUe.ed=function(e){return CL(this,e)},eUe.Ib=function(){return this.d+"="+this.e},Y5(eUS,"AbstractMap/AbstractEntry",484),eTS(383,484,{484:1,383:1,42:1},EE),Y5(eUS,"AbstractMap/SimpleEntry",383),eTS(1984,1,e$t),eUe.Fb=function(e){var t;return!!M4(e,42)&&(t=Pp(e,42),UT(this.cd(),t.cd())&&UT(this.dd(),t.dd()))},eUe.Hb=function(){return TK(this.cd())^TK(this.dd())},eUe.Ib=function(){return this.cd()+"="+this.dd()},Y5(eUS,eUD,1984),eTS(1992,1967,eUO),eUe.tc=function(e){return ZO(this,e)},eUe._b=function(e){return IY(this,e)},eUe.vc=function(){return new fj(this)},eUe.xc=function(e){var t;return xu(esq(this,t=e))},eUe.ec=function(){return new fP(this)},Y5(eUS,"AbstractNavigableMap",1992),eTS(739,eUT,eUM,fj),eUe.Hc=function(e){return M4(e,42)&&ZO(this.b,Pp(e,42))},eUe.Kc=function(){return new C1(this.b)},eUe.Mc=function(e){var t;return!!M4(e,42)&&(t=Pp(e,42),Jl(this.b,t))},eUe.gc=function(){return this.b.c},Y5(eUS,"AbstractNavigableMap/EntrySet",739),eTS(493,eUT,eUL,fP),eUe.Nc=function(){return new Ec(this)},eUe.$b=function(){gl(this.a)},eUe.Hc=function(e){return IY(this.a,e)},eUe.Kc=function(){var e;return e=new C1(new Ap(this.a).b),new fR(e)},eUe.Mc=function(e){return!!IY(this.a,e)&&(zS(this.a,e),!0)},eUe.gc=function(){return this.a.c},Y5(eUS,"AbstractNavigableMap/NavigableKeySet",493),eTS(494,1,eUE,fR),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return Et(this.a.a)},eUe.Pb=function(){var e;return(e=AJ(this.a)).cd()},eUe.Qb=function(){I5(this.a)},Y5(eUS,"AbstractNavigableMap/NavigableKeySet/1",494),eTS(2004,28,eUx),eUe.Fc=function(e){return Ja(e_s(this,e)),!0},eUe.Gc=function(e){return BJ(e),PG(e!=this,"Can't add a queue to itself"),er7(this,e)},eUe.$b=function(){for(;null!=eev(this););},Y5(eUS,"AbstractQueue",2004),eTS(302,28,{4:1,20:1,28:1,14:1},p1,GZ),eUe.Fc=function(e){return Vy(this,e),!0},eUe.$b=function(){qr(this)},eUe.Hc=function(e){return eos(new UN(this),e)},eUe.dc=function(){return gY(this)},eUe.Kc=function(){return new UN(this)},eUe.Mc=function(e){return zP(new UN(this),e)},eUe.gc=function(){return this.c-this.b&this.a.length-1},eUe.Nc=function(){return new Gq(this,272)},eUe.Qc=function(e){var t;return t=this.c-this.b&this.a.length-1,e.lengtht&&Bc(e,t,null),e},eUe.b=0,eUe.c=0,Y5(eUS,"ArrayDeque",302),eTS(446,1,eUE,UN),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return this.a!=this.b},eUe.Pb=function(){return ecn(this)},eUe.Qb=function(){enP(this)},eUe.a=0,eUe.b=0,eUe.c=-1,Y5(eUS,"ArrayDeque/IteratorImpl",446),eTS(12,52,e$n,p0,XM,I4),eUe.Vc=function(e,t){jO(this,e,t)},eUe.Fc=function(e){return P_(this,e)},eUe.Wc=function(e,t){return euP(this,e,t)},eUe.Gc=function(e){return eoc(this,e)},eUe.$b=function(){this.c=Je(e1R,eUp,1,0,5,1)},eUe.Hc=function(e){return -1!=QI(this,e,0)},eUe.Jc=function(e){ety(this,e)},eUe.Xb=function(e){return RJ(this,e)},eUe.Xc=function(e){return QI(this,e,0)},eUe.dc=function(){return 0==this.c.length},eUe.Kc=function(){return new fz(this)},eUe.$c=function(e){return ZV(this,e)},eUe.Mc=function(e){return QA(this,e)},eUe.Ud=function(e,t){GG(this,e,t)},eUe._c=function(e,t){return q1(this,e,t)},eUe.gc=function(){return this.c.length},eUe.ad=function(e){Mv(this,e)},eUe.Pc=function(){return AW(this)},eUe.Qc=function(e){return epg(this,e)};var e0n=Y5(eUS,"ArrayList",12);eTS(7,1,eUE,fz),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return My(this)},eUe.Pb=function(){return Wx(this)},eUe.Qb=function(){Yv(this)},eUe.a=0,eUe.b=-1,Y5(eUS,"ArrayList/1",7),eTS(2013,eB4.Function,{},S),eUe.te=function(e,t){return elN(e,t)},eTS(154,52,e$r,g$),eUe.Hc=function(e){return -1!=enW(this,e)},eUe.Jc=function(e){var t,n,r,i;for(BJ(e),n=this.a,r=0,i=n.length;r>>0).toString(16))},eUe.f=0,eUe.i=eH1;var e2X=Y5(e$N,"CNode",57);eTS(814,1,{},b5),Y5(e$N,"CNode/CNodeBuilder",814),eTS(1525,1,{},eh),eUe.Oe=function(e,t){return 0},eUe.Pe=function(e,t){return 0},Y5(e$N,e$R,1525),eTS(1790,1,{},ep),eUe.Le=function(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p,b;for(c=eHQ,r=new fz(e.a.b);r.ar.d.c||r.d.c==a.d.c&&r.d.b0?e+this.n.d+this.n.a:0},eUe.Se=function(){var e,t,n,r,i;if(i=0,this.e)this.b?i=this.b.a:this.a[1][1]&&(i=this.a[1][1].Se());else if(this.g)i=efV(this,evf(this,null,!0));else for(t=(etx(),eow(vx(e26,1),eU4,232,0,[e3D,e3N,e3P])),n=0,r=t.length;n0?i+this.n.b+this.n.c:0},eUe.Te=function(){var e,t,n,r,i;if(this.g)for(e=evf(this,null,!1),n=(etx(),eow(vx(e26,1),eU4,232,0,[e3D,e3N,e3P])),r=0,i=n.length;r0&&(r[0]+=this.d,n-=r[0]),r[2]>0&&(r[2]+=this.d,n-=r[2]),this.c.a=eB4.Math.max(0,n),this.c.d=t.d+e.d+(this.c.a-n)/2,r[1]=eB4.Math.max(r[1],n),ZP(this,e3N,t.d+e.d+r[0]-(r[1]-n)/2,r)},eUe.b=null,eUe.d=0,eUe.e=!1,eUe.f=!1,eUe.g=!1;var e29=0,e28=0;Y5(e$9,"GridContainerCell",1473),eTS(461,22,{3:1,35:1,22:1,461:1},EY);var e27=enw(e$9,"HorizontalLabelAlignment",461,e1G,G1,Dc);eTS(306,212,{212:1,306:1},zf,etr,$Y),eUe.Re=function(){return Rf(this)},eUe.Se=function(){return Rd(this)},eUe.a=0,eUe.c=!1;var e3e=Y5(e$9,"LabelCell",306);eTS(244,326,{212:1,326:1,244:1},eh6),eUe.Re=function(){return ek1(this)},eUe.Se=function(){return ek0(this)},eUe.Te=function(){eNE(this)},eUe.Ue=function(){eNM(this)},eUe.b=0,eUe.c=0,eUe.d=!1,Y5(e$9,"StripContainerCell",244),eTS(1626,1,eU8,e_),eUe.Mb=function(e){return gU(Pp(e,212))},Y5(e$9,"StripContainerCell/lambda$0$Type",1626),eTS(1627,1,{},eE),eUe.Fe=function(e){return Pp(e,212).Se()},Y5(e$9,"StripContainerCell/lambda$1$Type",1627),eTS(1628,1,eU8,eS),eUe.Mb=function(e){return gH(Pp(e,212))},Y5(e$9,"StripContainerCell/lambda$2$Type",1628),eTS(1629,1,{},ek),eUe.Fe=function(e){return Pp(e,212).Re()},Y5(e$9,"StripContainerCell/lambda$3$Type",1629),eTS(462,22,{3:1,35:1,22:1,462:1},EB);var e3t=enw(e$9,"VerticalLabelAlignment",462,e1G,G0,Dl);eTS(789,1,{},eFQ),eUe.c=0,eUe.d=0,eUe.k=0,eUe.s=0,eUe.t=0,eUe.v=!1,eUe.w=0,eUe.D=!1,Y5(eza,"NodeContext",789),eTS(1471,1,e$C,ex),eUe.ue=function(e,t){return To(Pp(e,61),Pp(t,61))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eza,"NodeContext/0methodref$comparePortSides$Type",1471),eTS(1472,1,e$C,eT),eUe.ue=function(e,t){return ew9(Pp(e,111),Pp(t,111))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eza,"NodeContext/1methodref$comparePortContexts$Type",1472),eTS(159,22,{3:1,35:1,22:1,159:1},ei_);var e3n=enw(eza,"NodeLabelLocation",159,e1G,epE,Df);eTS(111,1,{111:1},exz),eUe.a=!1,Y5(eza,"PortContext",111),eTS(1476,1,eUF,eM),eUe.td=function(e){yQ(Pp(e,306))},Y5(ezu,ezc,1476),eTS(1477,1,eU8,eO),eUe.Mb=function(e){return!!Pp(e,111).c},Y5(ezu,ezl,1477),eTS(1478,1,eUF,eA),eUe.td=function(e){yQ(Pp(e,111).c)},Y5(ezu,"LabelPlacer/lambda$2$Type",1478),eTS(1475,1,eUF,eC),eUe.td=function(e){Cn(),bu(Pp(e,111))},Y5(ezu,"NodeLabelAndSizeUtilities/lambda$0$Type",1475),eTS(790,1,eUF,Dx),eUe.td=function(e){_H(this.b,this.c,this.a,Pp(e,181))},eUe.a=!1,eUe.c=!1,Y5(ezu,"NodeLabelCellCreator/lambda$0$Type",790),eTS(1474,1,eUF,db),eUe.td=function(e){bB(this.a,Pp(e,181))},Y5(ezu,"PortContextCreator/lambda$0$Type",1474),eTS(1829,1,{},eI),Y5(ezd,"GreedyRectangleStripOverlapRemover",1829),eTS(1830,1,e$C,eL),eUe.ue=function(e,t){return Ay(Pp(e,222),Pp(t,222))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(ezd,"GreedyRectangleStripOverlapRemover/0methodref$compareByYCoordinate$Type",1830),eTS(1786,1,{},me),eUe.a=5,eUe.e=0,Y5(ezd,"RectangleStripOverlapRemover",1786),eTS(1787,1,e$C,eN),eUe.ue=function(e,t){return Aw(Pp(e,222),Pp(t,222))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(ezd,"RectangleStripOverlapRemover/0methodref$compareLeftRectangleBorders$Type",1787),eTS(1789,1,e$C,eP),eUe.ue=function(e,t){return YY(Pp(e,222),Pp(t,222))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(ezd,"RectangleStripOverlapRemover/1methodref$compareRightRectangleBorders$Type",1789),eTS(406,22,{3:1,35:1,22:1,406:1},EU);var e3r=enw(ezd,"RectangleStripOverlapRemover/OverlapRemovalDirection",406,e1G,Vn,Dd);eTS(222,1,{222:1},jH),Y5(ezd,"RectangleStripOverlapRemover/RectangleNode",222),eTS(1788,1,eUF,dm),eUe.td=function(e){emA(this.a,Pp(e,222))},Y5(ezd,"RectangleStripOverlapRemover/lambda$1$Type",1788),eTS(1304,1,e$C,eR),eUe.ue=function(e,t){return eRu(Pp(e,167),Pp(t,167))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(ezp,"PolyominoCompactor/CornerCasesGreaterThanRestComparator",1304),eTS(1307,1,{},ej),eUe.Kb=function(e){return Pp(e,324).a},Y5(ezp,"PolyominoCompactor/CornerCasesGreaterThanRestComparator/lambda$0$Type",1307),eTS(1308,1,eU8,eF),eUe.Mb=function(e){return Pp(e,323).a},Y5(ezp,"PolyominoCompactor/CornerCasesGreaterThanRestComparator/lambda$1$Type",1308),eTS(1309,1,eU8,eY),eUe.Mb=function(e){return Pp(e,323).a},Y5(ezp,"PolyominoCompactor/CornerCasesGreaterThanRestComparator/lambda$2$Type",1309),eTS(1302,1,e$C,eB),eUe.ue=function(e,t){return eC9(Pp(e,167),Pp(t,167))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(ezp,"PolyominoCompactor/MinNumOfExtensionDirectionsComparator",1302),eTS(1305,1,{},eD),eUe.Kb=function(e){return Pp(e,324).a},Y5(ezp,"PolyominoCompactor/MinNumOfExtensionDirectionsComparator/lambda$0$Type",1305),eTS(767,1,e$C,eU),eUe.ue=function(e,t){return eaq(Pp(e,167),Pp(t,167))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(ezp,"PolyominoCompactor/MinNumOfExtensionsComparator",767),eTS(1300,1,e$C,eH),eUe.ue=function(e,t){return ery(Pp(e,321),Pp(t,321))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(ezp,"PolyominoCompactor/MinPerimeterComparator",1300),eTS(1301,1,e$C,e$),eUe.ue=function(e,t){return ebg(Pp(e,321),Pp(t,321))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(ezp,"PolyominoCompactor/MinPerimeterComparatorWithShape",1301),eTS(1303,1,e$C,ez),eUe.ue=function(e,t){return eIz(Pp(e,167),Pp(t,167))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(ezp,"PolyominoCompactor/SingleExtensionSideGreaterThanRestComparator",1303),eTS(1306,1,{},eG),eUe.Kb=function(e){return Pp(e,324).a},Y5(ezp,"PolyominoCompactor/SingleExtensionSideGreaterThanRestComparator/lambda$0$Type",1306),eTS(777,1,{},EC),eUe.Ce=function(e,t){return KG(this,Pp(e,46),Pp(t,167))},Y5(ezp,"SuccessorCombination",777),eTS(644,1,{},eW),eUe.Ce=function(e,t){var n;return exd((n=Pp(e,46),Pp(t,167),n))},Y5(ezp,"SuccessorJitter",644),eTS(643,1,{},eK),eUe.Ce=function(e,t){var n;return eAW((n=Pp(e,46),Pp(t,167),n))},Y5(ezp,"SuccessorLineByLine",643),eTS(568,1,{},eV),eUe.Ce=function(e,t){var n;return eMl((n=Pp(e,46),Pp(t,167),n))},Y5(ezp,"SuccessorManhattan",568),eTS(1356,1,{},eq),eUe.Ce=function(e,t){var n;return eAt((n=Pp(e,46),Pp(t,167),n))},Y5(ezp,"SuccessorMaxNormWindingInMathPosSense",1356),eTS(400,1,{},dg),eUe.Ce=function(e,t){return YO(this,e,t)},eUe.c=!1,eUe.d=!1,eUe.e=!1,eUe.f=!1,Y5(ezp,"SuccessorQuadrantsGeneric",400),eTS(1357,1,{},eZ),eUe.Kb=function(e){return Pp(e,324).a},Y5(ezp,"SuccessorQuadrantsGeneric/lambda$0$Type",1357),eTS(323,22,{3:1,35:1,22:1,323:1},EN),eUe.a=!1;var e3i=enw(ezy,ezw,323,e1G,Va,Dh);eTS(1298,1,{}),eUe.Ib=function(){var e,t,n,r,i,a;for(i=0,n=" ",e=ell(0);i=0?"b"+e+"["+q2(this.a)+"]":"b["+q2(this.a)+"]":"b_"+Ao(this)},Y5(ez0,"FBendpoint",559),eTS(282,134,{3:1,282:1,94:1,134:1},CH),eUe.Ib=function(){return q2(this)},Y5(ez0,"FEdge",282),eTS(231,134,{3:1,231:1,94:1,134:1},Z5);var e4_=Y5(ez0,"FGraph",231);eTS(447,357,{3:1,447:1,357:1,94:1,134:1},qp),eUe.Ib=function(){return null==this.b||0==this.b.length?"l["+q2(this.a)+"]":"l_"+this.b},Y5(ez0,"FLabel",447),eTS(144,357,{3:1,144:1,357:1,94:1,134:1},Bw),eUe.Ib=function(){return WH(this)},eUe.b=0,Y5(ez0,"FNode",144),eTS(2003,1,{}),eUe.bf=function(e){eD2(this,e)},eUe.cf=function(){emz(this)},eUe.d=0,Y5(ez3,"AbstractForceModel",2003),eTS(631,2003,{631:1},eaR),eUe.af=function(e,t){var n,r,i,a,o;return ekL(this.f,e,t),i=C6(MB(t.d),e.d),o=eB4.Math.sqrt(i.a*i.a+i.b*i.b),r=eB4.Math.max(0,o-B$(e.e)/2-B$(t.e)/2),a=(n=esT(this.e,e,t))>0?-YT(r,this.c)*n:Li(r,this.b)*Pp(e_k(e,(eCk(),e9M)),19).a,Ol(i,a/o),i},eUe.bf=function(e){eD2(this,e),this.a=Pp(e_k(e,(eCk(),e9g)),19).a,this.c=gP(LV(e_k(e,e9D))),this.b=gP(LV(e_k(e,e9A)))},eUe.df=function(e){return e0&&(a-=gg(r,this.a)*n),Ol(i,a*this.b/o),i},eUe.bf=function(e){var t,n,r,i,a,o,s;for(eD2(this,e),this.b=gP(LV(e_k(e,(eCk(),e9N)))),this.c=this.b/Pp(e_k(e,e9g),19).a,r=e.e.c.length,a=0,i=0,s=new fz(e.e);s.a0},eUe.a=0,eUe.b=0,eUe.c=0,Y5(ez3,"FruchtermanReingoldModel",632),eTS(849,1,e$2,cu),eUe.Qe=function(e){efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,ez4),""),"Force Model"),"Determines the model for force calculation."),e9a),(eSd(),tdv)),e4E),el9((epx(),tdh))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,ez5),""),"Iterations"),"The number of iterations on the force model."),ell(300)),tdw),e15),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,ez6),""),"Repulsive Power"),"Determines how many bend points are added to the edge; such bend points are regarded as repelling particles in the force model"),ell(0)),tdw),e15),el9(tdl)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,ez9),""),"FR Temperature"),"The temperature is used as a scaling factor for particle displacements."),ez8),tdg),e13),el9(tdh)))),K_(e,ez9,ez4,e9l),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,ez7),""),"Eades Repulsion"),"Factor for repulsive forces in Eades' model."),5),tdg),e13),el9(tdh)))),K_(e,ez7,ez4,e9s),eYi((new cc,e))},Y5(eGe,"ForceMetaDataProvider",849),eTS(424,22,{3:1,35:1,22:1,424:1},EH);var e4E=enw(eGe,"ForceModelStrategy",424,e1G,$9,Dm);eTS(988,1,e$2,cc),eUe.Qe=function(e){eYi(e)},Y5(eGe,"ForceOptions",988),eTS(989,1,{},tr),eUe.$e=function(){return new b0},eUe._e=function(e){},Y5(eGe,"ForceOptions/ForceFactory",989),eTS(850,1,e$2,cl),eUe.Qe=function(e){efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGw),""),"Fixed Position"),"Prevent that the node is moved by the layout algorithm."),(OQ(),!1)),(eSd(),tdm)),e11),el9((epx(),tdd))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eG_),""),"Desired Edge Length"),"Either specified for parent nodes or for individual edges, where the latter takes higher precedence."),100),tdg),e13),jL(tdh,eow(vx(e5Q,1),eU4,175,0,[tdl]))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGE),""),"Layout Dimension"),"Dimensions that are permitted to be altered during layout."),e9U),tdv),e4S),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGS),""),"Stress Epsilon"),"Termination criterion for the iterative process."),ez8),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGk),""),"Iteration Limit"),"Maximum number of performed iterations. Takes higher precedence than 'epsilon'."),ell(eUu)),tdw),e15),el9(tdh)))),ejQ((new cf,e))},Y5(eGe,"StressMetaDataProvider",850),eTS(992,1,e$2,cf),eUe.Qe=function(e){ejQ(e)},Y5(eGe,"StressOptions",992),eTS(993,1,{},ti),eUe.$e=function(){return new C$},eUe._e=function(e){},Y5(eGe,"StressOptions/StressFactory",993),eTS(1128,209,ezL,C$),eUe.Ze=function(e,t){var n,r,i,a,o;for(ewG(t,eGT,1),gN(LK(eT8(e,(egq(),e9q))))?gN(LK(eT8(e,e90)))||zh(n=new df((_q(),new gM(e)))):eOs(new b0,e,eiI(t,1)),i=eo4(e),o=(r=eNx(this.a,i)).Kc();o.Ob();)!((a=Pp(o.Pb(),231)).e.c.length<=1)&&(eRa(this.b,a),eMn(this.b),ety(a.d,new ta));i=eYC(r),eYh(i),eEj(t)},Y5(eGO,"StressLayoutProvider",1128),eTS(1129,1,eUF,ta),eUe.td=function(e){ePd(Pp(e,447))},Y5(eGO,"StressLayoutProvider/lambda$0$Type",1129),eTS(990,1,{},bP),eUe.c=0,eUe.e=0,eUe.g=0,Y5(eGO,"StressMajorization",990),eTS(379,22,{3:1,35:1,22:1,379:1},E$);var e4S=enw(eGO,"StressMajorization/Dimension",379,e1G,G3,Dg);eTS(991,1,e$C,dE),eUe.ue=function(e,t){return IA(this.a,Pp(e,144),Pp(t,144))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGO,"StressMajorization/lambda$0$Type",991),eTS(1229,1,{},W9),Y5(eGL,"ElkLayered",1229),eTS(1230,1,eUF,to),eUe.td=function(e){exn(Pp(e,37))},Y5(eGL,"ElkLayered/lambda$0$Type",1230),eTS(1231,1,eUF,dS),eUe.td=function(e){IL(this.a,Pp(e,37))},Y5(eGL,"ElkLayered/lambda$1$Type",1231),eTS(1263,1,{},MC),Y5(eGL,"GraphConfigurator",1263),eTS(759,1,eUF,dk),eUe.td=function(e){e_1(this.a,Pp(e,10))},Y5(eGL,"GraphConfigurator/lambda$0$Type",759),eTS(760,1,{},ts),eUe.Kb=function(e){return evR(),new R1(null,new Gq(Pp(e,29).a,16))},Y5(eGL,"GraphConfigurator/lambda$1$Type",760),eTS(761,1,eUF,dx),eUe.td=function(e){e_1(this.a,Pp(e,10))},Y5(eGL,"GraphConfigurator/lambda$2$Type",761),eTS(1127,209,ezL,b3),eUe.Ze=function(e,t){var n;n=eN7(new mn,e),xc(eT8(e,(eBy(),taM)))===xc((eck(),tpz))?ef0(this.a,n,t):exD(this.a,n,t),eYr(new ch,n)},Y5(eGL,"LayeredLayoutProvider",1127),eTS(356,22,{3:1,35:1,22:1,356:1},Ez);var e4k=enw(eGL,"LayeredPhases",356,e1G,q4,Dv);eTS(1651,1,{},enX),eUe.i=0,Y5(eGC,"ComponentsToCGraphTransformer",1651),eTS(1652,1,{},tu),eUe.ef=function(e,t){return eB4.Math.min(null!=e.a?gP(e.a):e.c.i,null!=t.a?gP(t.a):t.c.i)},eUe.ff=function(e,t){return eB4.Math.min(null!=e.a?gP(e.a):e.c.i,null!=t.a?gP(t.a):t.c.i)},Y5(eGC,"ComponentsToCGraphTransformer/1",1652),eTS(81,1,{81:1}),eUe.i=0,eUe.k=!0,eUe.o=eH1;var e4x=Y5(eGI,"CNode",81);eTS(460,81,{460:1,81:1},Ah,eh3),eUe.Ib=function(){return""},Y5(eGC,"ComponentsToCGraphTransformer/CRectNode",460),eTS(1623,1,{},tc),Y5(eGC,"OneDimensionalComponentsCompaction",1623),eTS(1624,1,{},tl),eUe.Kb=function(e){return Gm(Pp(e,46))},eUe.Fb=function(e){return this===e},Y5(eGC,"OneDimensionalComponentsCompaction/lambda$0$Type",1624),eTS(1625,1,{},tf),eUe.Kb=function(e){return edl(Pp(e,46))},eUe.Fb=function(e){return this===e},Y5(eGC,"OneDimensionalComponentsCompaction/lambda$1$Type",1625),eTS(1654,1,{},Bv),Y5(eGI,"CGraph",1654),eTS(189,1,{189:1},eh4),eUe.b=0,eUe.c=0,eUe.e=0,eUe.g=!0,eUe.i=eH1,Y5(eGI,"CGroup",189),eTS(1653,1,{},tb),eUe.ef=function(e,t){return eB4.Math.max(null!=e.a?gP(e.a):e.c.i,null!=t.a?gP(t.a):t.c.i)},eUe.ff=function(e,t){return eB4.Math.max(null!=e.a?gP(e.a):e.c.i,null!=t.a?gP(t.a):t.c.i)},Y5(eGI,e$R,1653),eTS(1655,1,{},exO),eUe.d=!1;var e4T=Y5(eGI,e$U,1655);eTS(1656,1,{},tm),eUe.Kb=function(e){return _T(),OQ(),0!=Pp(Pp(e,46).a,81).d.e},eUe.Fb=function(e){return this===e},Y5(eGI,e$H,1656),eTS(823,1,{},R$),eUe.a=!1,eUe.b=!1,eUe.c=!1,eUe.d=!1,Y5(eGI,e$$,823),eTS(1825,1,{},j$),Y5(eGD,e$z,1825);var e4M=RL(eGN,e$D);eTS(1826,1,{369:1},$h),eUe.Ke=function(e){eLh(this,Pp(e,466))},Y5(eGD,e$G,1826),eTS(1827,1,e$C,tg),eUe.ue=function(e,t){return Hy(Pp(e,81),Pp(t,81))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGD,e$W,1827),eTS(466,1,{466:1},E6),eUe.a=!1,Y5(eGD,e$K,466),eTS(1828,1,e$C,tv),eUe.ue=function(e,t){return evP(Pp(e,466),Pp(t,466))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGD,e$V,1828),eTS(140,1,{140:1},Se,PW),eUe.Fb=function(e){var t;return null!=e&&e4O==esF(e)&&(t=Pp(e,140),UT(this.c,t.c)&&UT(this.d,t.d))},eUe.Hb=function(){return euF(eow(vx(e1R,1),eUp,1,5,[this.c,this.d]))},eUe.Ib=function(){return"("+this.c+eUd+this.d+(this.a?"cx":"")+this.b+")"},eUe.a=!0,eUe.c=0,eUe.d=0;var e4O=Y5(eGN,"Point",140);eTS(405,22,{3:1,35:1,22:1,405:1},EG);var e4A=enw(eGN,"Point/Quadrant",405,e1G,Vo,Dy);eTS(1642,1,{},b6),eUe.b=null,eUe.c=null,eUe.d=null,eUe.e=null,eUe.f=null,Y5(eGN,"RectilinearConvexHull",1642),eTS(574,1,{369:1},epG),eUe.Ke=function(e){J4(this,Pp(e,140))},eUe.b=0,Y5(eGN,"RectilinearConvexHull/MaximalElementsEventHandler",574),eTS(1644,1,e$C,th),eUe.ue=function(e,t){return U3(LV(e),LV(t))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGN,"RectilinearConvexHull/MaximalElementsEventHandler/lambda$0$Type",1644),eTS(1643,1,{369:1},ete),eUe.Ke=function(e){eAo(this,Pp(e,140))},eUe.a=0,eUe.b=null,eUe.c=null,eUe.d=null,eUe.e=null,Y5(eGN,"RectilinearConvexHull/RectangleEventHandler",1643),eTS(1645,1,e$C,tp),eUe.ue=function(e,t){return WI(Pp(e,140),Pp(t,140))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGN,"RectilinearConvexHull/lambda$0$Type",1645),eTS(1646,1,e$C,td),eUe.ue=function(e,t){return WD(Pp(e,140),Pp(t,140))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGN,"RectilinearConvexHull/lambda$1$Type",1646),eTS(1647,1,e$C,ty),eUe.ue=function(e,t){return WP(Pp(e,140),Pp(t,140))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGN,"RectilinearConvexHull/lambda$2$Type",1647),eTS(1648,1,e$C,tw),eUe.ue=function(e,t){return WN(Pp(e,140),Pp(t,140))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGN,"RectilinearConvexHull/lambda$3$Type",1648),eTS(1649,1,e$C,t_),eUe.ue=function(e,t){return e_M(Pp(e,140),Pp(t,140))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGN,"RectilinearConvexHull/lambda$4$Type",1649),eTS(1650,1,{},Gf),Y5(eGN,"Scanline",1650),eTS(2005,1,{}),Y5(eGP,"AbstractGraphPlacer",2005),eTS(325,1,{325:1},Lm),eUe.mf=function(e){return!!this.nf(e)&&(exg(this.b,Pp(e_k(e,(eBU(),ttX)),21),e),!0)},eUe.nf=function(e){var t,n,r,i;for(t=Pp(e_k(e,(eBU(),ttX)),21),r=(i=Pp(Zq(e8E,t),21)).Kc();r.Ob();)if(n=Pp(r.Pb(),21),!Pp(Zq(this.b,n),15).dc())return!1;return!0},Y5(eGP,"ComponentGroup",325),eTS(765,2005,{},b9),eUe.of=function(e){var t,n;for(n=new fz(this.a);n.ah&&(_=0,E+=d+i,d=0),m=o.c,eIn(o,_+m.a,E+m.b),xB(m),n=eB4.Math.max(n,_+v.a),d=eB4.Math.max(d,v.b),_+=v.a+i;if(t.f.a=n,t.f.b=E+d,gN(LK(e_k(a,tiQ)))){for(eBb(r=new tE,e,i),f=e.Kc();f.Ob();)C5(xB((l=Pp(f.Pb(),37)).c),r.e);C5(xB(t.f),r.a)}JN(t,e)},Y5(eGP,"SimpleRowGraphPlacer",1291),eTS(1292,1,e$C,tx),eUe.ue=function(e,t){return eaV(Pp(e,37),Pp(t,37))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGP,"SimpleRowGraphPlacer/1",1292),eTS(1262,1,e$q,tT),eUe.Lb=function(e){var t;return!!(t=Pp(e_k(Pp(e,243).b,(eBy(),taR)),74))&&0!=t.b},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){var t;return!!(t=Pp(e_k(Pp(e,243).b,(eBy(),taR)),74))&&0!=t.b},Y5(eGY,"CompoundGraphPostprocessor/1",1262),eTS(1261,1,eGB,mr),eUe.pf=function(e,t){ebL(this,Pp(e,37),t)},Y5(eGY,"CompoundGraphPreprocessor",1261),eTS(441,1,{441:1},ec8),eUe.c=!1,Y5(eGY,"CompoundGraphPreprocessor/ExternalPort",441),eTS(243,1,{243:1},DT),eUe.Ib=function(){return AV(this.c)+":"+ek5(this.b)},Y5(eGY,"CrossHierarchyEdge",243),eTS(763,1,e$C,dT),eUe.ue=function(e,t){return egB(this,Pp(e,243),Pp(t,243))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eGY,"CrossHierarchyEdgeComparator",763),eTS(299,134,{3:1,299:1,94:1,134:1}),eUe.p=0,Y5(eGU,"LGraphElement",299),eTS(17,299,{3:1,17:1,299:1,94:1,134:1},$b),eUe.Ib=function(){return ek5(this)};var e4C=Y5(eGU,"LEdge",17);eTS(37,299,{3:1,20:1,37:1,299:1,94:1,134:1},enJ),eUe.Jc=function(e){qX(this,e)},eUe.Kc=function(){return new fz(this.b)},eUe.Ib=function(){return 0==this.b.c.length?"G-unlayered"+e_F(this.a):0==this.a.c.length?"G-layered"+e_F(this.b):"G[layerless"+e_F(this.a)+", layers"+e_F(this.b)+"]"};var e4I=Y5(eGU,"LGraph",37);eTS(657,1,{}),eUe.qf=function(){return this.e.n},eUe.We=function(e){return e_k(this.e,e)},eUe.rf=function(){return this.e.o},eUe.sf=function(){return this.e.p},eUe.Xe=function(e){return Ln(this.e,e)},eUe.tf=function(e){this.e.n.a=e.a,this.e.n.b=e.b},eUe.uf=function(e){this.e.o.a=e.a,this.e.o.b=e.b},eUe.vf=function(e){this.e.p=e},Y5(eGU,"LGraphAdapters/AbstractLShapeAdapter",657),eTS(577,1,{839:1},dM),eUe.wf=function(){var e,t;if(!this.b)for(this.b=AH(this.a.b.c.length),t=new fz(this.a.b);t.a0&&eu7((GV(t-1,e.length),e.charCodeAt(t-1)),eGq);)--t;if(a> ",e),egu(n)),xM(xT((e.a+="[",e),n.i),"]")),e.a},eUe.c=!0,eUe.d=!1;var e4j=Y5(eGU,"LPort",11);eTS(397,1,eU$,dA),eUe.Jc=function(e){qX(this,e)},eUe.Kc=function(){var e;return e=new fz(this.a.e),new dL(e)},Y5(eGU,"LPort/1",397),eTS(1290,1,eUE,dL),eUe.Nb=function(e){F8(this,e)},eUe.Pb=function(){return Pp(Wx(this.a),17).c},eUe.Ob=function(){return My(this.a)},eUe.Qb=function(){Yv(this.a)},Y5(eGU,"LPort/1/1",1290),eTS(359,1,eU$,dC),eUe.Jc=function(e){qX(this,e)},eUe.Kc=function(){var e;return e=new fz(this.a.g),new dI(e)},Y5(eGU,"LPort/2",359),eTS(762,1,eUE,dI),eUe.Nb=function(e){F8(this,e)},eUe.Pb=function(){return Pp(Wx(this.a),17).d},eUe.Ob=function(){return My(this.a)},eUe.Qb=function(){Yv(this.a)},Y5(eGU,"LPort/2/1",762),eTS(1283,1,eU$,E5),eUe.Jc=function(e){qX(this,e)},eUe.Kc=function(){return new Z4(this)},Y5(eGU,"LPort/CombineIter",1283),eTS(201,1,eUE,Z4),eUe.Nb=function(e){F8(this,e)},eUe.Qb=function(){yI()},eUe.Ob=function(){return Ak(this)},eUe.Pb=function(){return My(this.a)?Wx(this.a):Wx(this.b)},Y5(eGU,"LPort/CombineIter/1",201),eTS(1285,1,e$q,tA),eUe.Lb=function(e){return FO(e)},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return eiA(),0!=Pp(e,11).e.c.length},Y5(eGU,"LPort/lambda$0$Type",1285),eTS(1284,1,e$q,tL),eUe.Lb=function(e){return FA(e)},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return eiA(),0!=Pp(e,11).g.c.length},Y5(eGU,"LPort/lambda$1$Type",1284),eTS(1286,1,e$q,tC),eUe.Lb=function(e){return eiA(),Pp(e,11).j==(eYu(),tbw)},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return eiA(),Pp(e,11).j==(eYu(),tbw)},Y5(eGU,"LPort/lambda$2$Type",1286),eTS(1287,1,e$q,tI),eUe.Lb=function(e){return eiA(),Pp(e,11).j==(eYu(),tby)},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return eiA(),Pp(e,11).j==(eYu(),tby)},Y5(eGU,"LPort/lambda$3$Type",1287),eTS(1288,1,e$q,tD),eUe.Lb=function(e){return eiA(),Pp(e,11).j==(eYu(),tbj)},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return eiA(),Pp(e,11).j==(eYu(),tbj)},Y5(eGU,"LPort/lambda$4$Type",1288),eTS(1289,1,e$q,tN),eUe.Lb=function(e){return eiA(),Pp(e,11).j==(eYu(),tbY)},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return eiA(),Pp(e,11).j==(eYu(),tbY)},Y5(eGU,"LPort/lambda$5$Type",1289),eTS(29,299,{3:1,20:1,299:1,29:1,94:1,134:1},By),eUe.Jc=function(e){qX(this,e)},eUe.Kc=function(){return new fz(this.a)},eUe.Ib=function(){return"L_"+QI(this.b.b,this,0)+e_F(this.a)},Y5(eGU,"Layer",29),eTS(1342,1,{},mn),Y5(eG0,eG2,1342),eTS(1346,1,{},tP),eUe.Kb=function(e){return ewH(Pp(e,82))},Y5(eG0,"ElkGraphImporter/0methodref$connectableShapeToNode$Type",1346),eTS(1349,1,{},tR),eUe.Kb=function(e){return ewH(Pp(e,82))},Y5(eG0,"ElkGraphImporter/1methodref$connectableShapeToNode$Type",1349),eTS(1343,1,eUF,dD),eUe.td=function(e){exW(this.a,Pp(e,118))},Y5(eG0,eG3,1343),eTS(1344,1,eUF,dN),eUe.td=function(e){exW(this.a,Pp(e,118))},Y5(eG0,eG4,1344),eTS(1345,1,{},tj),eUe.Kb=function(e){return new R1(null,new Gq(UF(Pp(e,79)),16))},Y5(eG0,eG5,1345),eTS(1347,1,eU8,dP),eUe.Mb=function(e){return TV(this.a,Pp(e,33))},Y5(eG0,eG6,1347),eTS(1348,1,{},tF),eUe.Kb=function(e){return new R1(null,new Gq(UY(Pp(e,79)),16))},Y5(eG0,"ElkGraphImporter/lambda$5$Type",1348),eTS(1350,1,eU8,dR),eUe.Mb=function(e){return Tq(this.a,Pp(e,33))},Y5(eG0,"ElkGraphImporter/lambda$7$Type",1350),eTS(1351,1,eU8,tY),eUe.Mb=function(e){return HH(Pp(e,79))},Y5(eG0,"ElkGraphImporter/lambda$8$Type",1351),eTS(1278,1,{},ch),Y5(eG0,"ElkGraphLayoutTransferrer",1278),eTS(1279,1,eU8,dj),eUe.Mb=function(e){return It(this.a,Pp(e,17))},Y5(eG0,"ElkGraphLayoutTransferrer/lambda$0$Type",1279),eTS(1280,1,eUF,dF),eUe.td=function(e){_k(),P_(this.a,Pp(e,17))},Y5(eG0,"ElkGraphLayoutTransferrer/lambda$1$Type",1280),eTS(1281,1,eU8,dY),eUe.Mb=function(e){return Ca(this.a,Pp(e,17))},Y5(eG0,"ElkGraphLayoutTransferrer/lambda$2$Type",1281),eTS(1282,1,eUF,dB),eUe.td=function(e){_k(),P_(this.a,Pp(e,17))},Y5(eG0,"ElkGraphLayoutTransferrer/lambda$3$Type",1282),eTS(1485,1,eGB,tB),eUe.pf=function(e,t){eiu(Pp(e,37),t)},Y5(eG8,"CommentNodeMarginCalculator",1485),eTS(1486,1,{},tU),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,29).a,16))},Y5(eG8,"CommentNodeMarginCalculator/lambda$0$Type",1486),eTS(1487,1,eUF,tH),eUe.td=function(e){ePO(Pp(e,10))},Y5(eG8,"CommentNodeMarginCalculator/lambda$1$Type",1487),eTS(1488,1,eGB,t$),eUe.pf=function(e,t){eLA(Pp(e,37),t)},Y5(eG8,"CommentPostprocessor",1488),eTS(1489,1,eGB,tz),eUe.pf=function(e,t){eF4(Pp(e,37),t)},Y5(eG8,"CommentPreprocessor",1489),eTS(1490,1,eGB,tG),eUe.pf=function(e,t){eOf(Pp(e,37),t)},Y5(eG8,"ConstraintsPostprocessor",1490),eTS(1491,1,eGB,tW),eUe.pf=function(e,t){eau(Pp(e,37),t)},Y5(eG8,"EdgeAndLayerConstraintEdgeReverser",1491),eTS(1492,1,eGB,tK),eUe.pf=function(e,t){edC(Pp(e,37),t)},Y5(eG8,"EndLabelPostprocessor",1492),eTS(1493,1,{},tV),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,29).a,16))},Y5(eG8,"EndLabelPostprocessor/lambda$0$Type",1493),eTS(1494,1,eU8,tq),eUe.Mb=function(e){return $T(Pp(e,10))},Y5(eG8,"EndLabelPostprocessor/lambda$1$Type",1494),eTS(1495,1,eUF,tZ),eUe.td=function(e){evj(Pp(e,10))},Y5(eG8,"EndLabelPostprocessor/lambda$2$Type",1495),eTS(1496,1,eGB,tX),eUe.pf=function(e,t){eSF(Pp(e,37),t)},Y5(eG8,"EndLabelPreprocessor",1496),eTS(1497,1,{},tJ),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,29).a,16))},Y5(eG8,"EndLabelPreprocessor/lambda$0$Type",1497),eTS(1498,1,eUF,DA),eUe.td=function(e){_$(this.a,this.b,this.c,Pp(e,10))},eUe.a=0,eUe.b=0,eUe.c=!1,Y5(eG8,"EndLabelPreprocessor/lambda$1$Type",1498),eTS(1499,1,eU8,tQ),eUe.Mb=function(e){return xc(e_k(Pp(e,70),(eBy(),tab)))===xc((etT(),tpS))},Y5(eG8,"EndLabelPreprocessor/lambda$2$Type",1499),eTS(1500,1,eUF,dU),eUe.td=function(e){P7(this.a,Pp(e,70))},Y5(eG8,"EndLabelPreprocessor/lambda$3$Type",1500),eTS(1501,1,eU8,t1),eUe.Mb=function(e){return xc(e_k(Pp(e,70),(eBy(),tab)))===xc((etT(),tpE))},Y5(eG8,"EndLabelPreprocessor/lambda$4$Type",1501),eTS(1502,1,eUF,dH),eUe.td=function(e){P7(this.a,Pp(e,70))},Y5(eG8,"EndLabelPreprocessor/lambda$5$Type",1502),eTS(1551,1,eGB,cd),eUe.pf=function(e,t){elP(Pp(e,37),t)},Y5(eG8,"EndLabelSorter",1551),eTS(1552,1,e$C,t0),eUe.ue=function(e,t){return epc(Pp(e,456),Pp(t,456))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eG8,"EndLabelSorter/1",1552),eTS(456,1,{456:1},HP),Y5(eG8,"EndLabelSorter/LabelGroup",456),eTS(1553,1,{},t2),eUe.Kb=function(e){return _O(),new R1(null,new Gq(Pp(e,29).a,16))},Y5(eG8,"EndLabelSorter/lambda$0$Type",1553),eTS(1554,1,eU8,t3),eUe.Mb=function(e){return _O(),Pp(e,10).k==(eEn(),e8N)},Y5(eG8,"EndLabelSorter/lambda$1$Type",1554),eTS(1555,1,eUF,t4),eUe.td=function(e){eEr(Pp(e,10))},Y5(eG8,"EndLabelSorter/lambda$2$Type",1555),eTS(1556,1,eU8,t5),eUe.Mb=function(e){return _O(),xc(e_k(Pp(e,70),(eBy(),tab)))===xc((etT(),tpE))},Y5(eG8,"EndLabelSorter/lambda$3$Type",1556),eTS(1557,1,eU8,t6),eUe.Mb=function(e){return _O(),xc(e_k(Pp(e,70),(eBy(),tab)))===xc((etT(),tpS))},Y5(eG8,"EndLabelSorter/lambda$4$Type",1557),eTS(1503,1,eGB,t9),eUe.pf=function(e,t){eP2(this,Pp(e,37))},eUe.b=0,eUe.c=0,Y5(eG8,"FinalSplineBendpointsCalculator",1503),eTS(1504,1,{},t8),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,29).a,16))},Y5(eG8,"FinalSplineBendpointsCalculator/lambda$0$Type",1504),eTS(1505,1,{},t7),eUe.Kb=function(e){return new R1(null,new YI(new Fa(OH(efc(Pp(e,10)).a.Kc(),new c))))},Y5(eG8,"FinalSplineBendpointsCalculator/lambda$1$Type",1505),eTS(1506,1,eU8,ne),eUe.Mb=function(e){return!q8(Pp(e,17))},Y5(eG8,"FinalSplineBendpointsCalculator/lambda$2$Type",1506),eTS(1507,1,eU8,nt),eUe.Mb=function(e){return Ln(Pp(e,17),(eBU(),tnO))},Y5(eG8,"FinalSplineBendpointsCalculator/lambda$3$Type",1507),eTS(1508,1,eUF,d$),eUe.td=function(e){eIV(this.a,Pp(e,128))},Y5(eG8,"FinalSplineBendpointsCalculator/lambda$4$Type",1508),eTS(1509,1,eUF,nn),eUe.td=function(e){eSj(Pp(e,17).a)},Y5(eG8,"FinalSplineBendpointsCalculator/lambda$5$Type",1509),eTS(792,1,eGB,dz),eUe.pf=function(e,t){ejn(this,Pp(e,37),t)},Y5(eG8,"GraphTransformer",792),eTS(511,22,{3:1,35:1,22:1,511:1},EV);var e4F=enw(eG8,"GraphTransformer/Mode",511,e1G,$8,NF);eTS(1510,1,eGB,nr),eUe.pf=function(e,t){eAP(Pp(e,37),t)},Y5(eG8,"HierarchicalNodeResizingProcessor",1510),eTS(1511,1,eGB,ni),eUe.pf=function(e,t){erP(Pp(e,37),t)},Y5(eG8,"HierarchicalPortConstraintProcessor",1511),eTS(1512,1,e$C,na),eUe.ue=function(e,t){return epZ(Pp(e,10),Pp(t,10))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eG8,"HierarchicalPortConstraintProcessor/NodeComparator",1512),eTS(1513,1,eGB,no),eUe.pf=function(e,t){eN5(Pp(e,37),t)},Y5(eG8,"HierarchicalPortDummySizeProcessor",1513),eTS(1514,1,eGB,ns),eUe.pf=function(e,t){eCf(this,Pp(e,37),t)},eUe.a=0,Y5(eG8,"HierarchicalPortOrthogonalEdgeRouter",1514),eTS(1515,1,e$C,nu),eUe.ue=function(e,t){return Av(Pp(e,10),Pp(t,10))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eG8,"HierarchicalPortOrthogonalEdgeRouter/1",1515),eTS(1516,1,e$C,nc),eUe.ue=function(e,t){return JW(Pp(e,10),Pp(t,10))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eG8,"HierarchicalPortOrthogonalEdgeRouter/2",1516),eTS(1517,1,eGB,nl),eUe.pf=function(e,t){e_O(Pp(e,37),t)},Y5(eG8,"HierarchicalPortPositionProcessor",1517),eTS(1518,1,eGB,cp),eUe.pf=function(e,t){eYG(this,Pp(e,37))},eUe.a=0,eUe.c=0,Y5(eG8,"HighDegreeNodeLayeringProcessor",1518),eTS(571,1,{571:1},nf),eUe.b=-1,eUe.d=-1,Y5(eG8,"HighDegreeNodeLayeringProcessor/HighDegreeNodeInformation",571),eTS(1519,1,{},nd),eUe.Kb=function(e){return DR(),efu(Pp(e,10))},eUe.Fb=function(e){return this===e},Y5(eG8,"HighDegreeNodeLayeringProcessor/lambda$0$Type",1519),eTS(1520,1,{},nh),eUe.Kb=function(e){return DR(),efc(Pp(e,10))},eUe.Fb=function(e){return this===e},Y5(eG8,"HighDegreeNodeLayeringProcessor/lambda$1$Type",1520),eTS(1526,1,eGB,np),eUe.pf=function(e,t){eD8(this,Pp(e,37),t)},Y5(eG8,"HyperedgeDummyMerger",1526),eTS(793,1,{},DL),eUe.a=!1,eUe.b=!1,eUe.c=!1,Y5(eG8,"HyperedgeDummyMerger/MergeState",793),eTS(1527,1,{},nb),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,29).a,16))},Y5(eG8,"HyperedgeDummyMerger/lambda$0$Type",1527),eTS(1528,1,{},nm),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,10).j,16))},Y5(eG8,"HyperedgeDummyMerger/lambda$1$Type",1528),eTS(1529,1,eUF,ng),eUe.td=function(e){Pp(e,11).p=-1},Y5(eG8,"HyperedgeDummyMerger/lambda$2$Type",1529),eTS(1530,1,eGB,nv),eUe.pf=function(e,t){eD6(Pp(e,37),t)},Y5(eG8,"HypernodesProcessor",1530),eTS(1531,1,eGB,ny),eUe.pf=function(e,t){eD9(Pp(e,37),t)},Y5(eG8,"InLayerConstraintProcessor",1531),eTS(1532,1,eGB,nw),eUe.pf=function(e,t){eiW(Pp(e,37),t)},Y5(eG8,"InnermostNodeMarginCalculator",1532),eTS(1533,1,eGB,n_),eUe.pf=function(e,t){eFW(this,Pp(e,37))},eUe.a=eH1,eUe.b=eH1,eUe.c=eHQ,eUe.d=eHQ;var e4Y=Y5(eG8,"InteractiveExternalPortPositioner",1533);eTS(1534,1,{},nE),eUe.Kb=function(e){return Pp(e,17).d.i},eUe.Fb=function(e){return this===e},Y5(eG8,"InteractiveExternalPortPositioner/lambda$0$Type",1534),eTS(1535,1,{},dG),eUe.Kb=function(e){return AE(this.a,LV(e))},eUe.Fb=function(e){return this===e},Y5(eG8,"InteractiveExternalPortPositioner/lambda$1$Type",1535),eTS(1536,1,{},nS),eUe.Kb=function(e){return Pp(e,17).c.i},eUe.Fb=function(e){return this===e},Y5(eG8,"InteractiveExternalPortPositioner/lambda$2$Type",1536),eTS(1537,1,{},dW),eUe.Kb=function(e){return AS(this.a,LV(e))},eUe.Fb=function(e){return this===e},Y5(eG8,"InteractiveExternalPortPositioner/lambda$3$Type",1537),eTS(1538,1,{},dK),eUe.Kb=function(e){return C9(this.a,LV(e))},eUe.Fb=function(e){return this===e},Y5(eG8,"InteractiveExternalPortPositioner/lambda$4$Type",1538),eTS(1539,1,{},dV),eUe.Kb=function(e){return C8(this.a,LV(e))},eUe.Fb=function(e){return this===e},Y5(eG8,"InteractiveExternalPortPositioner/lambda$5$Type",1539),eTS(77,22,{3:1,35:1,22:1,77:1,234:1},Eq),eUe.Kf=function(){switch(this.g){case 15:return new iA;case 22:return new iL;case 47:return new iD;case 28:case 35:return new nN;case 32:return new tB;case 42:return new t$;case 1:return new tz;case 41:return new tG;case 56:return new dz((erq(),e8W));case 0:return new dz((erq(),e8G));case 2:return new tW;case 54:return new tK;case 33:return new tX;case 51:return new t9;case 55:return new nr;case 13:return new ni;case 38:return new no;case 44:return new ns;case 40:return new nl;case 9:return new cp;case 49:return new AU;case 37:return new np;case 43:return new nv;case 27:return new ny;case 30:return new nw;case 3:return new n_;case 18:return new nx;case 29:return new nT;case 5:return new cb;case 50:return new nk;case 34:return new cm;case 36:return new nP;case 52:return new cd;case 11:return new nj;case 7:return new cv;case 39:return new nF;case 45:return new nY;case 16:return new nB;case 10:return new nU;case 48:return new n$;case 21:return new nz;case 23:return new gx((enU(),tui));case 8:return new nW;case 12:return new nV;case 4:return new nq;case 19:return new cE;case 17:return new n5;case 53:return new n6;case 6:return new rc;case 25:return new ms;case 46:return new rn;case 31:return new CV;case 14:return new rg;case 26:return new iB;case 20:return new rE;case 24:return new gx((enU(),tua));default:throw p7(new gL(eWt+(null!=this.f?this.f:""+this.g)))}};var e4B=enw(eG8,eWn,77,e1G,eAn,Nj);eTS(1540,1,eGB,nx),eUe.pf=function(e,t){eFq(Pp(e,37),t)},Y5(eG8,"InvertedPortProcessor",1540),eTS(1541,1,eGB,nT),eUe.pf=function(e,t){eIR(Pp(e,37),t)},Y5(eG8,"LabelAndNodeSizeProcessor",1541),eTS(1542,1,eU8,nM),eUe.Mb=function(e){return Pp(e,10).k==(eEn(),e8N)},Y5(eG8,"LabelAndNodeSizeProcessor/lambda$0$Type",1542),eTS(1543,1,eU8,nO),eUe.Mb=function(e){return Pp(e,10).k==(eEn(),e8C)},Y5(eG8,"LabelAndNodeSizeProcessor/lambda$1$Type",1543),eTS(1544,1,eUF,DC),eUe.td=function(e){_z(this.b,this.a,this.c,Pp(e,10))},eUe.a=!1,eUe.c=!1,Y5(eG8,"LabelAndNodeSizeProcessor/lambda$2$Type",1544),eTS(1545,1,eGB,cb),eUe.pf=function(e,t){eFu(Pp(e,37),t)},Y5(eG8,"LabelDummyInserter",1545),eTS(1546,1,e$q,nA),eUe.Lb=function(e){return xc(e_k(Pp(e,70),(eBy(),tab)))===xc((etT(),tp_))},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return xc(e_k(Pp(e,70),(eBy(),tab)))===xc((etT(),tp_))},Y5(eG8,"LabelDummyInserter/1",1546),eTS(1547,1,eGB,nk),eUe.pf=function(e,t){eRz(Pp(e,37),t)},Y5(eG8,"LabelDummyRemover",1547),eTS(1548,1,eU8,nL),eUe.Mb=function(e){return gN(LK(e_k(Pp(e,70),(eBy(),tap))))},Y5(eG8,"LabelDummyRemover/lambda$0$Type",1548),eTS(1359,1,eGB,cm),eUe.pf=function(e,t){ejC(this,Pp(e,37),t)},eUe.a=null,Y5(eG8,"LabelDummySwitcher",1359),eTS(286,1,{286:1},eIu),eUe.c=0,eUe.d=null,eUe.f=0,Y5(eG8,"LabelDummySwitcher/LabelDummyInfo",286),eTS(1360,1,{},nC),eUe.Kb=function(e){return erJ(),new R1(null,new Gq(Pp(e,29).a,16))},Y5(eG8,"LabelDummySwitcher/lambda$0$Type",1360),eTS(1361,1,eU8,nI),eUe.Mb=function(e){return erJ(),Pp(e,10).k==(eEn(),e8I)},Y5(eG8,"LabelDummySwitcher/lambda$1$Type",1361),eTS(1362,1,{},dX),eUe.Kb=function(e){return Co(this.a,Pp(e,10))},Y5(eG8,"LabelDummySwitcher/lambda$2$Type",1362),eTS(1363,1,eUF,dJ),eUe.td=function(e){BO(this.a,Pp(e,286))},Y5(eG8,"LabelDummySwitcher/lambda$3$Type",1363),eTS(1364,1,e$C,nD),eUe.ue=function(e,t){return FL(Pp(e,286),Pp(t,286))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eG8,"LabelDummySwitcher/lambda$4$Type",1364),eTS(791,1,eGB,nN),eUe.pf=function(e,t){XT(Pp(e,37),t)},Y5(eG8,"LabelManagementProcessor",791),eTS(1549,1,eGB,nP),eUe.pf=function(e,t){eLr(Pp(e,37),t)},Y5(eG8,"LabelSideSelector",1549),eTS(1550,1,eU8,nR),eUe.Mb=function(e){return gN(LK(e_k(Pp(e,70),(eBy(),tap))))},Y5(eG8,"LabelSideSelector/lambda$0$Type",1550),eTS(1558,1,eGB,nj),eUe.pf=function(e,t){eN6(Pp(e,37),t)},Y5(eG8,"LayerConstraintPostprocessor",1558),eTS(1559,1,eGB,cv),eUe.pf=function(e,t){eMr(Pp(e,37),t)},Y5(eG8,"LayerConstraintPreprocessor",1559),eTS(360,22,{3:1,35:1,22:1,360:1},EZ);var e4U=enw(eG8,"LayerConstraintPreprocessor/HiddenNodeConnections",360,e1G,Vs,DF);eTS(1560,1,eGB,nF),eUe.pf=function(e,t){eRB(Pp(e,37),t)},Y5(eG8,"LayerSizeAndGraphHeightCalculator",1560),eTS(1561,1,eGB,nY),eUe.pf=function(e,t){eOw(Pp(e,37),t)},Y5(eG8,"LongEdgeJoiner",1561),eTS(1562,1,eGB,nB),eUe.pf=function(e,t){eRf(Pp(e,37),t)},Y5(eG8,"LongEdgeSplitter",1562),eTS(1563,1,eGB,nU),eUe.pf=function(e,t){ejN(this,Pp(e,37),t)},eUe.d=0,eUe.e=0,eUe.i=0,eUe.j=0,eUe.k=0,eUe.n=0,Y5(eG8,"NodePromotion",1563),eTS(1564,1,{},nH),eUe.Kb=function(e){return Pp(e,46),OQ(),!0},eUe.Fb=function(e){return this===e},Y5(eG8,"NodePromotion/lambda$0$Type",1564),eTS(1565,1,{},dq),eUe.Kb=function(e){return UM(this.a,Pp(e,46))},eUe.Fb=function(e){return this===e},eUe.a=0,Y5(eG8,"NodePromotion/lambda$1$Type",1565),eTS(1566,1,{},dZ),eUe.Kb=function(e){return UO(this.a,Pp(e,46))},eUe.Fb=function(e){return this===e},eUe.a=0,Y5(eG8,"NodePromotion/lambda$2$Type",1566),eTS(1567,1,eGB,n$),eUe.pf=function(e,t){eYN(Pp(e,37),t)},Y5(eG8,"NorthSouthPortPostprocessor",1567),eTS(1568,1,eGB,nz),eUe.pf=function(e,t){eYd(Pp(e,37),t)},Y5(eG8,"NorthSouthPortPreprocessor",1568),eTS(1569,1,e$C,nG),eUe.ue=function(e,t){return ea2(Pp(e,11),Pp(t,11))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eG8,"NorthSouthPortPreprocessor/lambda$0$Type",1569),eTS(1570,1,eGB,nW),eUe.pf=function(e,t){eDx(Pp(e,37),t)},Y5(eG8,"PartitionMidprocessor",1570),eTS(1571,1,eU8,nK),eUe.Mb=function(e){return Ln(Pp(e,10),(eBy(),ton))},Y5(eG8,"PartitionMidprocessor/lambda$0$Type",1571),eTS(1572,1,eUF,dQ),eUe.td=function(e){H$(this.a,Pp(e,10))},Y5(eG8,"PartitionMidprocessor/lambda$1$Type",1572),eTS(1573,1,eGB,nV),eUe.pf=function(e,t){eO3(Pp(e,37),t)},Y5(eG8,"PartitionPostprocessor",1573),eTS(1574,1,eGB,nq),eUe.pf=function(e,t){exQ(Pp(e,37),t)},Y5(eG8,"PartitionPreprocessor",1574),eTS(1575,1,eU8,nZ),eUe.Mb=function(e){return Ln(Pp(e,10),(eBy(),ton))},Y5(eG8,"PartitionPreprocessor/lambda$0$Type",1575),eTS(1576,1,{},nX),eUe.Kb=function(e){return new R1(null,new YI(new Fa(OH(efc(Pp(e,10)).a.Kc(),new c))))},Y5(eG8,"PartitionPreprocessor/lambda$1$Type",1576),eTS(1577,1,eU8,nJ),eUe.Mb=function(e){return epe(Pp(e,17))},Y5(eG8,"PartitionPreprocessor/lambda$2$Type",1577),eTS(1578,1,eUF,nQ),eUe.td=function(e){eoL(Pp(e,17))},Y5(eG8,"PartitionPreprocessor/lambda$3$Type",1578),eTS(1579,1,eGB,cE),eUe.pf=function(e,t){eDe(Pp(e,37),t)},Y5(eG8,"PortListSorter",1579),eTS(1580,1,{},n1),eUe.Kb=function(e){return euv(),Pp(e,11).e},Y5(eG8,"PortListSorter/lambda$0$Type",1580),eTS(1581,1,{},n0),eUe.Kb=function(e){return euv(),Pp(e,11).g},Y5(eG8,"PortListSorter/lambda$1$Type",1581),eTS(1582,1,e$C,n2),eUe.ue=function(e,t){return qy(Pp(e,11),Pp(t,11))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eG8,"PortListSorter/lambda$2$Type",1582),eTS(1583,1,e$C,n3),eUe.ue=function(e,t){return eg_(Pp(e,11),Pp(t,11))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eG8,"PortListSorter/lambda$3$Type",1583),eTS(1584,1,e$C,n4),eUe.ue=function(e,t){return eDK(Pp(e,11),Pp(t,11))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eG8,"PortListSorter/lambda$4$Type",1584),eTS(1585,1,eGB,n5),eUe.pf=function(e,t){eT3(Pp(e,37),t)},Y5(eG8,"PortSideProcessor",1585),eTS(1586,1,eGB,n6),eUe.pf=function(e,t){eCH(Pp(e,37),t)},Y5(eG8,"ReversedEdgeRestorer",1586),eTS(1591,1,eGB,ms),eUe.pf=function(e,t){emJ(this,Pp(e,37),t)},Y5(eG8,"SelfLoopPortRestorer",1591),eTS(1592,1,{},n9),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,29).a,16))},Y5(eG8,"SelfLoopPortRestorer/lambda$0$Type",1592),eTS(1593,1,eU8,n8),eUe.Mb=function(e){return Pp(e,10).k==(eEn(),e8N)},Y5(eG8,"SelfLoopPortRestorer/lambda$1$Type",1593),eTS(1594,1,eU8,n7),eUe.Mb=function(e){return Ln(Pp(e,10),(eBU(),tnk))},Y5(eG8,"SelfLoopPortRestorer/lambda$2$Type",1594),eTS(1595,1,{},re),eUe.Kb=function(e){return Pp(e_k(Pp(e,10),(eBU(),tnk)),403)},Y5(eG8,"SelfLoopPortRestorer/lambda$3$Type",1595),eTS(1596,1,eUF,d1),eUe.td=function(e){eE_(this.a,Pp(e,403))},Y5(eG8,"SelfLoopPortRestorer/lambda$4$Type",1596),eTS(794,1,eUF,rt),eUe.td=function(e){eEq(Pp(e,101))},Y5(eG8,"SelfLoopPortRestorer/lambda$5$Type",794),eTS(1597,1,eGB,rn),eUe.pf=function(e,t){ep1(Pp(e,37),t)},Y5(eG8,"SelfLoopPostProcessor",1597),eTS(1598,1,{},rr),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,29).a,16))},Y5(eG8,"SelfLoopPostProcessor/lambda$0$Type",1598),eTS(1599,1,eU8,ri),eUe.Mb=function(e){return Pp(e,10).k==(eEn(),e8N)},Y5(eG8,"SelfLoopPostProcessor/lambda$1$Type",1599),eTS(1600,1,eU8,ra),eUe.Mb=function(e){return Ln(Pp(e,10),(eBU(),tnk))},Y5(eG8,"SelfLoopPostProcessor/lambda$2$Type",1600),eTS(1601,1,eUF,ro),eUe.td=function(e){eyi(Pp(e,10))},Y5(eG8,"SelfLoopPostProcessor/lambda$3$Type",1601),eTS(1602,1,{},rs),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,101).f,1))},Y5(eG8,"SelfLoopPostProcessor/lambda$4$Type",1602),eTS(1603,1,eUF,d0),eUe.td=function(e){Vf(this.a,Pp(e,409))},Y5(eG8,"SelfLoopPostProcessor/lambda$5$Type",1603),eTS(1604,1,eU8,ru),eUe.Mb=function(e){return!!Pp(e,101).i},Y5(eG8,"SelfLoopPostProcessor/lambda$6$Type",1604),eTS(1605,1,eUF,d2),eUe.td=function(e){gb(this.a,Pp(e,101))},Y5(eG8,"SelfLoopPostProcessor/lambda$7$Type",1605),eTS(1587,1,eGB,rc),eUe.pf=function(e,t){eMJ(Pp(e,37),t)},Y5(eG8,"SelfLoopPreProcessor",1587),eTS(1588,1,{},rl),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,101).f,1))},Y5(eG8,"SelfLoopPreProcessor/lambda$0$Type",1588),eTS(1589,1,{},rf),eUe.Kb=function(e){return Pp(e,409).a},Y5(eG8,"SelfLoopPreProcessor/lambda$1$Type",1589),eTS(1590,1,eUF,rd),eUe.td=function(e){MH(Pp(e,17))},Y5(eG8,"SelfLoopPreProcessor/lambda$2$Type",1590),eTS(1606,1,eGB,CV),eUe.pf=function(e,t){eEi(this,Pp(e,37),t)},Y5(eG8,"SelfLoopRouter",1606),eTS(1607,1,{},rh),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,29).a,16))},Y5(eG8,"SelfLoopRouter/lambda$0$Type",1607),eTS(1608,1,eU8,rp),eUe.Mb=function(e){return Pp(e,10).k==(eEn(),e8N)},Y5(eG8,"SelfLoopRouter/lambda$1$Type",1608),eTS(1609,1,eU8,rb),eUe.Mb=function(e){return Ln(Pp(e,10),(eBU(),tnk))},Y5(eG8,"SelfLoopRouter/lambda$2$Type",1609),eTS(1610,1,{},rm),eUe.Kb=function(e){return Pp(e_k(Pp(e,10),(eBU(),tnk)),403)},Y5(eG8,"SelfLoopRouter/lambda$3$Type",1610),eTS(1611,1,eUF,EX),eUe.td=function(e){Hs(this.a,this.b,Pp(e,403))},Y5(eG8,"SelfLoopRouter/lambda$4$Type",1611),eTS(1612,1,eGB,rg),eUe.pf=function(e,t){eAz(Pp(e,37),t)},Y5(eG8,"SemiInteractiveCrossMinProcessor",1612),eTS(1613,1,eU8,rv),eUe.Mb=function(e){return Pp(e,10).k==(eEn(),e8N)},Y5(eG8,"SemiInteractiveCrossMinProcessor/lambda$0$Type",1613),eTS(1614,1,eU8,ry),eUe.Mb=function(e){return R9(Pp(e,10))._b((eBy(),tog))},Y5(eG8,"SemiInteractiveCrossMinProcessor/lambda$1$Type",1614),eTS(1615,1,e$C,rw),eUe.ue=function(e,t){return erF(Pp(e,10),Pp(t,10))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eG8,"SemiInteractiveCrossMinProcessor/lambda$2$Type",1615),eTS(1616,1,{},r_),eUe.Ce=function(e,t){return H4(Pp(e,10),Pp(t,10))},Y5(eG8,"SemiInteractiveCrossMinProcessor/lambda$3$Type",1616),eTS(1618,1,eGB,rE),eUe.pf=function(e,t){eN8(Pp(e,37),t)},Y5(eG8,"SortByInputModelProcessor",1618),eTS(1619,1,eU8,rS),eUe.Mb=function(e){return 0!=Pp(e,11).g.c.length},Y5(eG8,"SortByInputModelProcessor/lambda$0$Type",1619),eTS(1620,1,eUF,d3),eUe.td=function(e){eE6(this.a,Pp(e,11))},Y5(eG8,"SortByInputModelProcessor/lambda$1$Type",1620),eTS(1693,803,{},erY),eUe.Me=function(e){var t,n,r,i;switch(this.c=e,this.a.g){case 2:t=new p0,_r(UJ(new R1(null,new Gq(this.c.a.b,16)),new rj),new E2(this,t)),eS2(this,new rT),ety(t,new rM),t.c=Je(e1R,eUp,1,0,5,1),_r(UJ(new R1(null,new Gq(this.c.a.b,16)),new rO),new d5(t)),eS2(this,new rA),ety(t,new rL),t.c=Je(e1R,eUp,1,0,5,1),n=M_(eim(U1(new R1(null,new Gq(this.c.a.b,16)),new d6(this))),new rC),_r(new R1(null,new Gq(this.c.a.a,16)),new EQ(n,t)),eS2(this,new rD),ety(t,new rk),t.c=Je(e1R,eUp,1,0,5,1);break;case 3:r=new p0,eS2(this,new rx),i=M_(eim(U1(new R1(null,new Gq(this.c.a.b,16)),new d4(this))),new rI),_r(UJ(new R1(null,new Gq(this.c.a.b,16)),new rN),new E0(i,r)),eS2(this,new rP),ety(r,new rR),r.c=Je(e1R,eUp,1,0,5,1);break;default:throw p7(new bI)}},eUe.b=0,Y5(eWs,"EdgeAwareScanlineConstraintCalculation",1693),eTS(1694,1,e$q,rx),eUe.Lb=function(e){return M4(Pp(e,57).g,145)},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return M4(Pp(e,57).g,145)},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$0$Type",1694),eTS(1695,1,{},d4),eUe.Fe=function(e){return eky(this.a,Pp(e,57))},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$1$Type",1695),eTS(1703,1,eU7,EJ),eUe.Vd=function(){ev_(this.a,this.b,-1)},eUe.b=0,Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$10$Type",1703),eTS(1705,1,e$q,rT),eUe.Lb=function(e){return M4(Pp(e,57).g,145)},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return M4(Pp(e,57).g,145)},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$11$Type",1705),eTS(1706,1,eUF,rM),eUe.td=function(e){Pp(e,365).Vd()},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$12$Type",1706),eTS(1707,1,eU8,rO),eUe.Mb=function(e){return M4(Pp(e,57).g,10)},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$13$Type",1707),eTS(1709,1,eUF,d5),eUe.td=function(e){efw(this.a,Pp(e,57))},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$14$Type",1709),eTS(1708,1,eU7,E9),eUe.Vd=function(){ev_(this.b,this.a,-1)},eUe.a=0,Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$15$Type",1708),eTS(1710,1,e$q,rA),eUe.Lb=function(e){return M4(Pp(e,57).g,10)},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return M4(Pp(e,57).g,10)},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$16$Type",1710),eTS(1711,1,eUF,rL),eUe.td=function(e){Pp(e,365).Vd()},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$17$Type",1711),eTS(1712,1,{},d6),eUe.Fe=function(e){return ekw(this.a,Pp(e,57))},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$18$Type",1712),eTS(1713,1,{},rC),eUe.De=function(){return 0},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$19$Type",1713),eTS(1696,1,{},rI),eUe.De=function(){return 0},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$2$Type",1696),eTS(1715,1,eUF,EQ),eUe.td=function(e){jq(this.a,this.b,Pp(e,307))},eUe.a=0,Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$20$Type",1715),eTS(1714,1,eU7,E1),eUe.Vd=function(){eT4(this.a,this.b,-1)},eUe.b=0,Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$21$Type",1714),eTS(1716,1,e$q,rD),eUe.Lb=function(e){return Pp(e,57),!0},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return Pp(e,57),!0},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$22$Type",1716),eTS(1717,1,eUF,rk),eUe.td=function(e){Pp(e,365).Vd()},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$23$Type",1717),eTS(1697,1,eU8,rN),eUe.Mb=function(e){return M4(Pp(e,57).g,10)},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$3$Type",1697),eTS(1699,1,eUF,E0),eUe.td=function(e){jZ(this.a,this.b,Pp(e,57))},eUe.a=0,Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$4$Type",1699),eTS(1698,1,eU7,E8),eUe.Vd=function(){ev_(this.b,this.a,-1)},eUe.a=0,Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$5$Type",1698),eTS(1700,1,e$q,rP),eUe.Lb=function(e){return Pp(e,57),!0},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return Pp(e,57),!0},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$6$Type",1700),eTS(1701,1,eUF,rR),eUe.td=function(e){Pp(e,365).Vd()},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$7$Type",1701),eTS(1702,1,eU8,rj),eUe.Mb=function(e){return M4(Pp(e,57).g,145)},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$8$Type",1702),eTS(1704,1,eUF,E2),eUe.td=function(e){eth(this.a,this.b,Pp(e,57))},Y5(eWs,"EdgeAwareScanlineConstraintCalculation/lambda$9$Type",1704),eTS(1521,1,eGB,AU),eUe.pf=function(e,t){eRE(this,Pp(e,37),t)},Y5(eWs,"HorizontalGraphCompactor",1521),eTS(1522,1,{},d9),eUe.Oe=function(e,t){var n,r,i;return Q8(e,t)?0:(n=KT(e),r=KT(t),n&&n.k==(eEn(),e8C)||r&&r.k==(eEn(),e8C))?0:(i=Pp(e_k(this.a.a,(eBU(),tnx)),304),Ax(i,n?n.k:(eEn(),e8D),r?r.k:(eEn(),e8D)))},eUe.Pe=function(e,t){var n,r,i;return Q8(e,t)?1:(n=KT(e),r=KT(t),i=Pp(e_k(this.a.a,(eBU(),tnx)),304),AT(i,n?n.k:(eEn(),e8D),r?r.k:(eEn(),e8D)))},Y5(eWs,"HorizontalGraphCompactor/1",1522),eTS(1523,1,{},rF),eUe.Ne=function(e,t){return _L(),0==e.a.i},Y5(eWs,"HorizontalGraphCompactor/lambda$0$Type",1523),eTS(1524,1,{},d8),eUe.Ne=function(e,t){return HZ(this.a,e,t)},Y5(eWs,"HorizontalGraphCompactor/lambda$1$Type",1524),eTS(1664,1,{},QF),Y5(eWs,"LGraphToCGraphTransformer",1664),eTS(1672,1,eU8,rY),eUe.Mb=function(e){return null!=e},Y5(eWs,"LGraphToCGraphTransformer/0methodref$nonNull$Type",1672),eTS(1665,1,{},rB),eUe.Kb=function(e){return Dj(),efF(e_k(Pp(Pp(e,57).g,10),(eBU(),tnc)))},Y5(eWs,"LGraphToCGraphTransformer/lambda$0$Type",1665),eTS(1666,1,{},rU),eUe.Kb=function(e){return Dj(),ecR(Pp(Pp(e,57).g,145))},Y5(eWs,"LGraphToCGraphTransformer/lambda$1$Type",1666),eTS(1675,1,eU8,rH),eUe.Mb=function(e){return Dj(),M4(Pp(e,57).g,10)},Y5(eWs,"LGraphToCGraphTransformer/lambda$10$Type",1675),eTS(1676,1,eUF,r$),eUe.td=function(e){Hq(Pp(e,57))},Y5(eWs,"LGraphToCGraphTransformer/lambda$11$Type",1676),eTS(1677,1,eU8,rz),eUe.Mb=function(e){return Dj(),M4(Pp(e,57).g,145)},Y5(eWs,"LGraphToCGraphTransformer/lambda$12$Type",1677),eTS(1681,1,eUF,rG),eUe.td=function(e){ecP(Pp(e,57))},Y5(eWs,"LGraphToCGraphTransformer/lambda$13$Type",1681),eTS(1678,1,eUF,d7),eUe.td=function(e){Tm(this.a,Pp(e,8))},eUe.a=0,Y5(eWs,"LGraphToCGraphTransformer/lambda$14$Type",1678),eTS(1679,1,eUF,he),eUe.td=function(e){Tv(this.a,Pp(e,110))},eUe.a=0,Y5(eWs,"LGraphToCGraphTransformer/lambda$15$Type",1679),eTS(1680,1,eUF,ht),eUe.td=function(e){Tg(this.a,Pp(e,8))},eUe.a=0,Y5(eWs,"LGraphToCGraphTransformer/lambda$16$Type",1680),eTS(1682,1,{},rW),eUe.Kb=function(e){return Dj(),new R1(null,new YI(new Fa(OH(efc(Pp(e,10)).a.Kc(),new c))))},Y5(eWs,"LGraphToCGraphTransformer/lambda$17$Type",1682),eTS(1683,1,eU8,rK),eUe.Mb=function(e){return Dj(),q8(Pp(e,17))},Y5(eWs,"LGraphToCGraphTransformer/lambda$18$Type",1683),eTS(1684,1,eUF,hn),eUe.td=function(e){eex(this.a,Pp(e,17))},Y5(eWs,"LGraphToCGraphTransformer/lambda$19$Type",1684),eTS(1668,1,eUF,hr),eUe.td=function(e){Wj(this.a,Pp(e,145))},Y5(eWs,"LGraphToCGraphTransformer/lambda$2$Type",1668),eTS(1685,1,{},rV),eUe.Kb=function(e){return Dj(),new R1(null,new Gq(Pp(e,29).a,16))},Y5(eWs,"LGraphToCGraphTransformer/lambda$20$Type",1685),eTS(1686,1,{},rq),eUe.Kb=function(e){return Dj(),new R1(null,new YI(new Fa(OH(efc(Pp(e,10)).a.Kc(),new c))))},Y5(eWs,"LGraphToCGraphTransformer/lambda$21$Type",1686),eTS(1687,1,{},rZ),eUe.Kb=function(e){return Dj(),Pp(e_k(Pp(e,17),(eBU(),tnO)),15)},Y5(eWs,"LGraphToCGraphTransformer/lambda$22$Type",1687),eTS(1688,1,eU8,rX),eUe.Mb=function(e){return AN(Pp(e,15))},Y5(eWs,"LGraphToCGraphTransformer/lambda$23$Type",1688),eTS(1689,1,eUF,hi),eUe.td=function(e){ekn(this.a,Pp(e,15))},Y5(eWs,"LGraphToCGraphTransformer/lambda$24$Type",1689),eTS(1667,1,eUF,E3),eUe.td=function(e){VK(this.a,this.b,Pp(e,145))},Y5(eWs,"LGraphToCGraphTransformer/lambda$3$Type",1667),eTS(1669,1,{},rJ),eUe.Kb=function(e){return Dj(),new R1(null,new Gq(Pp(e,29).a,16))},Y5(eWs,"LGraphToCGraphTransformer/lambda$4$Type",1669),eTS(1670,1,{},rQ),eUe.Kb=function(e){return Dj(),new R1(null,new YI(new Fa(OH(efc(Pp(e,10)).a.Kc(),new c))))},Y5(eWs,"LGraphToCGraphTransformer/lambda$5$Type",1670),eTS(1671,1,{},r1),eUe.Kb=function(e){return Dj(),Pp(e_k(Pp(e,17),(eBU(),tnO)),15)},Y5(eWs,"LGraphToCGraphTransformer/lambda$6$Type",1671),eTS(1673,1,eUF,ha),eUe.td=function(e){exr(this.a,Pp(e,15))},Y5(eWs,"LGraphToCGraphTransformer/lambda$8$Type",1673),eTS(1674,1,eUF,E4),eUe.td=function(e){MN(this.a,this.b,Pp(e,145))},Y5(eWs,"LGraphToCGraphTransformer/lambda$9$Type",1674),eTS(1663,1,{},r0),eUe.Le=function(e){var t,n,r,i,a;for(this.a=e,this.d=new bX,this.c=Je(e24,eUp,121,this.a.a.a.c.length,0,1),this.b=0,n=new fz(this.a.a.a);n.a=b&&(P_(a,ell(l)),v=eB4.Math.max(v,y[l-1]-f),s+=p,m+=y[l-1]-m,f=y[l-1],p=u[l]),p=eB4.Math.max(p,u[l]),++l;s+=p}(h=eB4.Math.min(1/v,1/t.b/s))>r&&(r=h,n=a)}return n},eUe.Wf=function(){return!1},Y5(eWb,"MSDCutIndexHeuristic",802),eTS(1617,1,eGB,iB),eUe.pf=function(e,t){eNZ(Pp(e,37),t)},Y5(eWb,"SingleEdgeGraphWrapper",1617),eTS(227,22,{3:1,35:1,22:1,227:1},Ss);var e4K=enw(eWm,"CenterEdgeLabelPlacementStrategy",227,e1G,Jv,DU);eTS(422,22,{3:1,35:1,22:1,422:1},Su);var e4V=enw(eWm,"ConstraintCalculationStrategy",422,e1G,$G,DH);eTS(314,22,{3:1,35:1,22:1,314:1,246:1,234:1},Sc),eUe.Kf=function(){return ekF(this)},eUe.Xf=function(){return ekF(this)};var e4q=enw(eWm,"CrossingMinimizationStrategy",314,e1G,G5,D$);eTS(337,22,{3:1,35:1,22:1,337:1},Sl);var e4Z=enw(eWm,"CuttingStrategy",337,e1G,G6,DW);eTS(335,22,{3:1,35:1,22:1,335:1,246:1,234:1},Sf),eUe.Kf=function(){return eTW(this)},eUe.Xf=function(){return eTW(this)};var e4X=enw(eWm,"CycleBreakingStrategy",335,e1G,Zv,DK);eTS(419,22,{3:1,35:1,22:1,419:1},Sd);var e4J=enw(eWm,"DirectionCongruency",419,e1G,$z,DV);eTS(450,22,{3:1,35:1,22:1,450:1},Sh);var e4Q=enw(eWm,"EdgeConstraint",450,e1G,G9,Dq);eTS(276,22,{3:1,35:1,22:1,276:1},Sp);var e41=enw(eWm,"EdgeLabelSideSelection",276,e1G,JE,DZ);eTS(479,22,{3:1,35:1,22:1,479:1},Sb);var e40=enw(eWm,"EdgeStraighteningStrategy",479,e1G,$$,DX);eTS(274,22,{3:1,35:1,22:1,274:1},Sm);var e42=enw(eWm,"FixedAlignment",274,e1G,Jw,DJ);eTS(275,22,{3:1,35:1,22:1,275:1},Sg);var e43=enw(eWm,"GraphCompactionStrategy",275,e1G,Jy,DQ);eTS(256,22,{3:1,35:1,22:1,256:1},Sv);var e44=enw(eWm,"GraphProperties",256,e1G,eiT,D1);eTS(292,22,{3:1,35:1,22:1,292:1},Sy);var e45=enw(eWm,"GreedySwitchType",292,e1G,We,D0);eTS(303,22,{3:1,35:1,22:1,303:1},Sw);var e46=enw(eWm,"InLayerConstraint",303,e1G,G7,D2);eTS(420,22,{3:1,35:1,22:1,420:1},S_);var e49=enw(eWm,"InteractiveReferencePoint",420,e1G,$W,D3);eTS(163,22,{3:1,35:1,22:1,163:1},ST);var e48=enw(eWm,"LayerConstraint",163,e1G,Z_,D4);eTS(848,1,e$2,cT),eUe.Qe=function(e){efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWE),""),"Direction Congruency"),"Specifies how drawings of the same graph with different layout directions compare to each other: either a natural reading direction is preserved or the drawings are rotated versions of each other."),trl),(eSd(),tdv)),e4J),el9((epx(),tdh))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWS),""),"Feedback Edges"),"Whether feedback edges should be highlighted by routing around the nodes."),(OQ(),!1)),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWk),""),"Interactive Reference Point"),"Determines which point of a node is considered by interactive layout phases."),trN),tdv),e49),el9(tdh)))),K_(e,eWk,eWI,trR),K_(e,eWk,eWH,trP),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWx),""),"Merge Edges"),"Edges that have no ports are merged so they touch the connected nodes at the same points. When this option is disabled, one port is created for each edge directly connected to a node. When it is enabled, all such incoming edges share an input port, and all outgoing edges share an output port."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWT),""),"Merge Hierarchy-Crossing Edges"),"If hierarchical layout is active, hierarchy-crossing edges use as few hierarchical ports as possible. They are broken by the algorithm, with hierarchical ports inserted as required. Usually, one such port is created for each edge at each hierarchy crossing point. With this option set to true, we try to create as few hierarchical ports as possible in the process. In particular, all edges that form a hyperedge can share a port."),!0),tdm),e11),el9(tdh)))),efO(e,new eE8(v8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWM),""),"Allow Non-Flow Ports To Switch Sides"),"Specifies whether non-flow ports may switch sides if their node's port constraints are either FIXED_SIDE or FIXED_ORDER. A non-flow port is a port on a side that is not part of the currently configured layout flow. For instance, given a left-to-right layout direction, north and south ports would be considered non-flow ports. Further note that the underlying criterium whether to switch sides or not solely relies on the minimization of edge crossings. Hence, edge length and other aesthetics criteria are not addressed."),!1),tdm),e11),el9(tdp)),eow(vx(e17,1),eUP,2,6,["org.eclipse.elk.layered.northOrSouthPort"])))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWO),""),"Port Sorting Strategy"),"Only relevant for nodes with FIXED_SIDE port constraints. Determines the way a node's ports are distributed on the sides of a node if their order is not prescribed. The option is set on parent nodes."),tic),tdv),e5a),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWA),""),"Thoroughness"),"How much effort should be spent to produce a nice layout."),ell(7)),tdw),e15),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWL),""),"Add Unnecessary Bendpoints"),"Adds bend points even if an edge does not change direction. If true, each long edge dummy will contribute a bend point to its edges and hierarchy-crossing edges will always get a bend point where they cross hierarchy boundaries. By default, bend points are only added where an edge changes direction."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWC),""),"Generate Position and Layer IDs"),"If enabled position id and layer id are generated, which are usually only used internally when setting the interactiveLayout option. This option should be specified on the root node."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWI),"cycleBreaking"),"Cycle Breaking Strategy"),"Strategy for cycle breaking. Cycle breaking looks for cycles in the graph and determines which edges to reverse to break the cycles. Reversed edges will end up pointing to the opposite direction of regular edges (that is, reversed edges will point left if edges usually point right)."),tru),tdv),e4X),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWD),eKC),"Node Layering Strategy"),"Strategy for node layering."),trX),tdv),e47),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWN),eKC),"Layer Constraint"),"Determines a constraint on the placement of the node regarding the layering."),trU),tdv),e48),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWP),eKC),"Layer Choice Constraint"),"Allows to set a constraint regarding the layer placement of a node. Let i be the value of teh constraint. Assumed the drawing has n layers and i < n. If set to i, it expresses that the node should be placed in i-th layer. Should i>=n be true then the node is placed in the last layer of the drawing. Note that this option is not part of any of ELK Layered's default configurations but is only evaluated as part of the `InteractiveLayeredGraphVisitor`, which must be applied manually or used via the `DiagramLayoutEngine."),ell(-1)),tdw),e15),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWR),eKC),"Layer ID"),"Layer identifier that was calculated by ELK Layered for a node. This is only generated if interactiveLayot or generatePositionAndLayerIds is set."),ell(-1)),tdw),e15),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWj),eKI),"Upper Bound On Width [MinWidth Layerer]"),"Defines a loose upper bound on the width of the MinWidth layerer. If set to '-1' multiple values are tested and the best result is selected."),ell(4)),tdw),e15),el9(tdh)))),K_(e,eWj,eWD,trz),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWF),eKI),"Upper Layer Estimation Scaling Factor [MinWidth Layerer]"),"Multiplied with Upper Bound On Width for defining an upper bound on the width of layers which haven't been determined yet, but whose maximum width had been (roughly) estimated by the MinWidth algorithm. Compensates for too high estimations. If set to '-1' multiple values are tested and the best result is selected."),ell(2)),tdw),e15),el9(tdh)))),K_(e,eWF,eWD,trW),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWY),eKD),"Node Promotion Strategy"),"Reduces number of dummy nodes after layering phase (if possible)."),trq),tdv),e5r),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWB),eKD),"Max Node Promotion Iterations"),"Limits the number of iterations for node promotion."),ell(0)),tdw),e15),el9(tdh)))),K_(e,eWB,eWY,null),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWU),"layering.coffmanGraham"),"Layer Bound"),"The maximum number of nodes allowed per layer."),ell(eUu)),tdw),e15),el9(tdh)))),K_(e,eWU,eWD,trF),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWH),eKN),"Crossing Minimization Strategy"),"Strategy for crossing minimization."),tro),tdv),e4q),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eW$),eKN),"Force Node Model Order"),"The node order given by the model does not change to produce a better layout. E.g. if node A is before node B in the model this is not changed during crossing minimization. This assumes that the node model order is already respected before crossing minimization. This can be achieved by setting considerModelOrder.strategy to NODES_AND_EDGES."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWz),eKN),"Hierarchical Sweepiness"),"How likely it is to use cross-hierarchy (1) vs bottom-up (-1)."),.1),tdg),e13),el9(tdh)))),K_(e,eWz,eKP,tre),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWG),eKN),"Semi-Interactive Crossing Minimization"),"Preserves the order of nodes within a layer but still minimizes crossings between edges connecting long edge dummies. Derives the desired order from positions specified by the 'org.eclipse.elk.position' layout option. Requires a crossing minimization strategy that is able to process 'in-layer' constraints."),!1),tdm),e11),el9(tdh)))),K_(e,eWG,eWH,tri),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWW),eKN),"Position Choice Constraint"),"Allows to set a constraint regarding the position placement of a node in a layer. Assumed the layer in which the node placed includes n other nodes and i < n. If set to i, it expresses that the node should be placed at the i-th position. Should i>=n be true then the node is placed at the last position in the layer. Note that this option is not part of any of ELK Layered's default configurations but is only evaluated as part of the `InteractiveLayeredGraphVisitor`, which must be applied manually or used via the `DiagramLayoutEngine."),ell(-1)),tdw),e15),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWK),eKN),"Position ID"),"Position within a layer that was determined by ELK Layered for a node. This is only generated if interactiveLayot or generatePositionAndLayerIds is set."),ell(-1)),tdw),e15),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWV),eKR),"Greedy Switch Activation Threshold"),"By default it is decided automatically if the greedy switch is activated or not. The decision is based on whether the size of the input graph (without dummy nodes) is smaller than the value of this option. A '0' enforces the activation."),ell(40)),tdw),e15),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWq),eKR),"Greedy Switch Crossing Minimization"),"Greedy Switch strategy for crossing minimization. The greedy switch heuristic is executed after the regular crossing minimization as a post-processor. Note that if 'hierarchyHandling' is set to 'INCLUDE_CHILDREN', the 'greedySwitchHierarchical.type' option must be used."),tn9),tdv),e45),el9(tdh)))),K_(e,eWq,eWH,tn8),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWZ),"crossingMinimization.greedySwitchHierarchical"),"Greedy Switch Crossing Minimization (hierarchical)"),"Activates the greedy switch heuristic in case hierarchical layout is used. The differences to the non-hierarchical case (see 'greedySwitch.type') are: 1) greedy switch is inactive by default, 3) only the option value set on the node at which hierarchical layout starts is relevant, and 2) if it's activated by the user, it properly addresses hierarchy-crossing edges."),tn3),tdv),e45),el9(tdh)))),K_(e,eWZ,eWH,tn4),K_(e,eWZ,eKP,tn5),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWX),eKj),"Node Placement Strategy"),"Strategy for node placement."),tis),tdv),e5n),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eWJ),eKj),"Favor Straight Edges Over Balancing"),"Favor straight edges over a balanced node placement. The default behavior is determined automatically based on the used 'edgeRouting'. For an orthogonal style it is set to true, for all other styles to false."),tdm),e11),el9(tdh)))),K_(e,eWJ,eWX,tr9),K_(e,eWJ,eWX,tr8),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eWQ),eKF),"BK Edge Straightening"),"Specifies whether the Brandes Koepf node placer tries to increase the number of straight edges at the expense of diagram size. There is a subtle difference to the 'favorStraightEdges' option, which decides whether a balanced placement of the nodes is desired, or not. In bk terms this means combining the four alignments into a single balanced one, or not. This option on the other hand tries to straighten additional edges during the creation of each of the four alignments."),tr0),tdv),e40),el9(tdh)))),K_(e,eWQ,eWX,tr2),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eW1),eKF),"BK Fixed Alignment"),"Tells the BK node placer to use a certain alignment (out of its four) instead of the one producing the smallest height, or the combination of all four."),tr4),tdv),e42),el9(tdh)))),K_(e,eW1,eWX,tr5),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eW0),"nodePlacement.linearSegments"),"Linear Segments Deflection Dampening"),"Dampens the movement of nodes to keep the diagram from getting too large."),.3),tdg),e13),el9(tdh)))),K_(e,eW0,eWX,tie),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eW2),"nodePlacement.networkSimplex"),"Node Flexibility"),"Aims at shorter and straighter edges. Two configurations are possible: (a) allow ports to move freely on the side they are assigned to (the order is always defined beforehand), (b) additionally allow to enlarge a node wherever it helps. If this option is not configured for a node, the 'nodeFlexibility.default' value is used, which is specified for the node's parent."),tdv),e5t),el9(tdd)))),K_(e,eW2,eWX,tia),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eW3),"nodePlacement.networkSimplex.nodeFlexibility"),"Node Flexibility Default"),"Default value of the 'nodeFlexibility' option for the children of a hierarchical node."),tir),tdv),e5t),el9(tdh)))),K_(e,eW3,eWX,tii),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eW4),eKY),"Self-Loop Distribution"),"Alter the distribution of the loops around the node. It only takes effect for PortConstraints.FREE."),trv),tdv),e5s),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eW5),eKY),"Self-Loop Ordering"),"Alter the ordering of the loops they can either be stacked or sequenced. It only takes effect for PortConstraints.FREE."),tr_),tdv),e5u),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eW6),"edgeRouting.splines"),"Spline Routing Mode"),"Specifies the way control points are assembled for each individual edge. CONSERVATIVE ensures that edges are properly routed around the nodes but feels rather orthogonal at times. SLOPPY uses fewer control points to obtain curvier edge routes but may result in edges overlapping nodes."),trS),tdv),e5c),el9(tdh)))),K_(e,eW6,eKB,trk),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eW9),"edgeRouting.splines.sloppy"),"Sloppy Spline Layer Spacing Factor"),"Spacing factor for routing area between layers when using sloppy spline routing."),.2),tdg),e13),el9(tdh)))),K_(e,eW9,eKB,trT),K_(e,eW9,eW6,trM),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eW8),"edgeRouting.polyline"),"Sloped Edge Zone Width"),"Width of the strip to the left and to the right of each layer where the polyline edge router is allowed to refrain from ensuring that edges are routed horizontally. This prevents awkward bend points for nodes that extent almost to the edge of their layer."),2),tdg),e13),el9(tdh)))),K_(e,eW8,eKB,trm),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eW7),eKU),"Spacing Base Value"),"An optional base value for all other layout options of the 'spacing' group. It can be used to conveniently alter the overall 'spaciousness' of the drawing. Whenever an explicit value is set for the other layout options, this base value will have no effect. The base value is not inherited, i.e. it must be set for each hierarchical node."),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKe),eKU),"Edge Node Between Layers Spacing"),"The spacing to be preserved between nodes and edges that are routed next to the node's layer. For the spacing between nodes and edges that cross the node's layer 'spacing.edgeNode' is used."),10),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKt),eKU),"Edge Edge Between Layer Spacing"),"Spacing to be preserved between pairs of edges that are routed between the same pair of layers. Note that 'spacing.edgeEdge' is used for the spacing between pairs of edges crossing the same layer."),10),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKn),eKU),"Node Node Between Layers Spacing"),"The spacing to be preserved between any pair of nodes of two adjacent layers. Note that 'spacing.nodeNode' is used for the spacing between nodes within the layer itself."),20),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKr),eKH),"Direction Priority"),"Defines how important it is to have a certain edge point into the direction of the overall layout. This option is evaluated during the cycle breaking phase."),ell(0)),tdw),e15),el9(tdl)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKi),eKH),"Shortness Priority"),"Defines how important it is to keep an edge as short as possible. This option is evaluated during the layering phase."),ell(0)),tdw),e15),el9(tdl)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKa),eKH),"Straightness Priority"),"Defines how important it is to keep an edge straight, i.e. aligned with one of the two axes. This option is evaluated during node placement."),ell(0)),tdw),e15),el9(tdl)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKo),eK$),ezI),"Tries to further compact components (disconnected sub-graphs)."),!1),tdm),e11),el9(tdh)))),K_(e,eKo,eGs,!0),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKs),eKz),"Post Compaction Strategy"),eKG),tnz),tdv),e43),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKu),eKz),"Post Compaction Constraint Calculation"),eKG),tnH),tdv),e4V),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKc),eKW),"High Degree Node Treatment"),"Makes room around high degree nodes to place leafs and trees."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKl),eKW),"High Degree Node Threshold"),"Whether a node is considered to have a high degree."),ell(16)),tdw),e15),el9(tdh)))),K_(e,eKl,eKc,!0),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKf),eKW),"High Degree Node Maximum Tree Height"),"Maximum height of a subtree connected to a high degree node to be moved to separate layers."),ell(5)),tdw),e15),el9(tdh)))),K_(e,eKf,eKc,!0),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKd),eKK),"Graph Wrapping Strategy"),"For certain graphs and certain prescribed drawing areas it may be desirable to split the laid out graph into chunks that are placed side by side. The edges that connect different chunks are 'wrapped' around from the end of one chunk to the start of the other chunk. The points between the chunks are referred to as 'cuts'."),tiU),tdv),e5f),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKh),eKK),"Additional Wrapped Edges Spacing"),"To visually separate edges that are wrapped from regularly routed edges an additional spacing value can be specified in form of this layout option. The spacing is added to the regular edgeNode spacing."),10),tdg),e13),el9(tdh)))),K_(e,eKh,eKd,tiw),K_(e,eKh,eKd,ti_),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKp),eKK),"Correction Factor for Wrapping"),"At times and for certain types of graphs the executed wrapping may produce results that are consistently biased in the same fashion: either wrapping to often or to rarely. This factor can be used to correct the bias. Internally, it is simply multiplied with the 'aspect ratio' layout option."),1),tdg),e13),el9(tdh)))),K_(e,eKp,eKd,tiS),K_(e,eKp,eKd,tik),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKb),eKV),"Cutting Strategy"),"The strategy by which the layer indexes are determined at which the layering crumbles into chunks."),tiC),tdv),e4Z),el9(tdh)))),K_(e,eKb,eKd,tiI),K_(e,eKb,eKd,tiD),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eKm),eKV),"Manually Specified Cuts"),"Allows the user to specify her own cuts for a certain graph."),td_),e1H),el9(tdh)))),K_(e,eKm,eKb,tiT),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKg),"wrapping.cutting.msd"),"MSD Freedom"),"The MSD cutting strategy starts with an initial guess on the number of chunks the graph should be split into. The freedom specifies how much the strategy may deviate from this guess. E.g. if an initial number of 3 is computed, a freedom of 1 allows 2, 3, and 4 cuts."),tiO),tdw),e15),el9(tdh)))),K_(e,eKg,eKb,tiA),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKv),eKq),"Validification Strategy"),"When wrapping graphs, one can specify indices that are not allowed as split points. The validification strategy makes sure every computed split point is allowed."),tiW),tdv),e5l),el9(tdh)))),K_(e,eKv,eKd,tiK),K_(e,eKv,eKd,tiV),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eKy),eKq),"Valid Indices for Wrapping"),null),td_),e1H),el9(tdh)))),K_(e,eKy,eKd,ti$),K_(e,eKy,eKd,tiz),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKw),eKZ),"Improve Cuts"),"For general graphs it is important that not too many edges wrap backwards. Thus a compromise between evenly-distributed cuts and the total number of cut edges is sought."),!0),tdm),e11),el9(tdh)))),K_(e,eKw,eKd,tij),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eK_),eKZ),"Distance Penalty When Improving Cuts"),null),2),tdg),e13),el9(tdh)))),K_(e,eK_,eKd,tiP),K_(e,eK_,eKw,!0),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKE),eKZ),"Improve Wrapped Edges"),"The initial wrapping is performed in a very simple way. As a consequence, edges that wrap from one chunk to another may be unnecessarily long. Activating this option tries to shorten such edges."),!0),tdm),e11),el9(tdh)))),K_(e,eKE,eKd,tiY),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKS),eKX),"Edge Label Side Selection"),"Method to decide on edge label sides."),trp),tdv),e41),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKk),eKX),"Edge Center Label Placement Strategy"),"Determines in which layer center labels of long edges should be placed."),trd),tdv),e4K),jL(tdh,eow(vx(e5Q,1),eU4,175,0,[tdf]))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKx),eKJ),"Consider Model Order"),"Preserves the order of nodes and edges in the model file if this does not lead to additional edge crossings. Depending on the strategy this is not always possible since the node and edge order might be conflicting."),tnQ),tdv),e5i),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKT),eKJ),"No Model Order"),"Set on a node to not set a model order for this node even though it is a real node."),!1),tdm),e11),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKM),eKJ),"Consider Model Order for Components"),"If set to NONE the usual ordering strategy (by cumulative node priority and size of nodes) is used. INSIDE_PORT_SIDES orders the components with external ports only inside the groups with the same port side. FORCE_MODEL_ORDER enforces the mode order on components. This option might produce bad alignments and sub optimal drawings in terms of used area since the ordering should be respected."),tnW),tdv),e4L),el9(tdh)))),K_(e,eKM,eGs,null),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKO),eKJ),"Long Edge Ordering Strategy"),"Indicates whether long edges are sorted under, over, or equal to nodes that have no connection to a previous layer in a left-to-right or right-to-left layout. Under and over changes to right and left in a vertical layout."),tnZ),tdv),e5e),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKA),eKJ),"Crossing Counter Node Order Influence"),"Indicates with what percentage (1 for 100%) violations of the node model order are weighted against the crossings e.g. a value of 0.5 means two model order violations are as important as on edge crossing. This allows some edge crossings in favor of preserving the model order. It is advised to set this value to a very small positive value (e.g. 0.001) to have minimal crossing and a optimal node order. Defaults to no influence (0)."),0),tdg),e13),el9(tdh)))),K_(e,eKA,eKx,null),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKL),eKJ),"Crossing Counter Port Order Influence"),"Indicates with what percentage (1 for 100%) violations of the port model order are weighted against the crossings e.g. a value of 0.5 means two model order violations are as important as on edge crossing. This allows some edge crossings in favor of preserving the model order. It is advised to set this value to a very small positive value (e.g. 0.001) to have minimal crossing and a optimal port order. Defaults to no influence (0)."),0),tdg),e13),el9(tdh)))),K_(e,eKL,eKx,null),eBq((new cA,e))},Y5(eWm,"LayeredMetaDataProvider",848),eTS(986,1,e$2,cA),eUe.Qe=function(e){eBq(e)},Y5(eWm,"LayeredOptions",986),eTS(987,1,{},iH),eUe.$e=function(){return new b3},eUe._e=function(e){},Y5(eWm,"LayeredOptions/LayeredFactory",987),eTS(1372,1,{}),eUe.a=0,Y5(eVL,"ElkSpacings/AbstractSpacingsBuilder",1372),eTS(779,1372,{},ef4),Y5(eWm,"LayeredSpacings/LayeredSpacingsBuilder",779),eTS(313,22,{3:1,35:1,22:1,313:1,246:1,234:1},SE),eUe.Kf=function(){return eM3(this)},eUe.Xf=function(){return eM3(this)};var e47=enw(eWm,"LayeringStrategy",313,e1G,J_,D5);eTS(378,22,{3:1,35:1,22:1,378:1},SS);var e5e=enw(eWm,"LongEdgeOrderingStrategy",378,e1G,G4,D6);eTS(197,22,{3:1,35:1,22:1,197:1},Sk);var e5t=enw(eWm,"NodeFlexibility",197,e1G,VT,D9);eTS(315,22,{3:1,35:1,22:1,315:1,246:1,234:1},Sx),eUe.Kf=function(){return eTG(this)},eUe.Xf=function(){return eTG(this)};var e5n=enw(eWm,"NodePlacementStrategy",315,e1G,Zg,Nr);eTS(260,22,{3:1,35:1,22:1,260:1},SM);var e5r=enw(eWm,"NodePromotionStrategy",260,e1G,etL,D7);eTS(339,22,{3:1,35:1,22:1,339:1},SO);var e5i=enw(eWm,"OrderingStrategy",339,e1G,Wn,Ne);eTS(421,22,{3:1,35:1,22:1,421:1},SA);var e5a=enw(eWm,"PortSortingStrategy",421,e1G,$K,Nt);eTS(452,22,{3:1,35:1,22:1,452:1},SL);var e5o=enw(eWm,"PortType",452,e1G,Wt,D8);eTS(375,22,{3:1,35:1,22:1,375:1},SC);var e5s=enw(eWm,"SelfLoopDistributionStrategy",375,e1G,Wr,Nn);eTS(376,22,{3:1,35:1,22:1,376:1},SI);var e5u=enw(eWm,"SelfLoopOrderingStrategy",376,e1G,$H,Ni);eTS(304,1,{304:1},ejm),Y5(eWm,"Spacings",304),eTS(336,22,{3:1,35:1,22:1,336:1},SD);var e5c=enw(eWm,"SplineRoutingMode",336,e1G,Wa,Na);eTS(338,22,{3:1,35:1,22:1,338:1},SN);var e5l=enw(eWm,"ValidifyStrategy",338,e1G,Wo,No);eTS(377,22,{3:1,35:1,22:1,377:1},SP);var e5f=enw(eWm,"WrappingStrategy",377,e1G,Wi,Ns);eTS(1383,1,eVD,cL),eUe.Yf=function(e){return Pp(e,37),ts2},eUe.pf=function(e,t){eRb(this,Pp(e,37),t)},Y5(eVN,"DepthFirstCycleBreaker",1383),eTS(782,1,eVD,jG),eUe.Yf=function(e){return Pp(e,37),ts3},eUe.pf=function(e,t){eBS(this,Pp(e,37),t)},eUe.Zf=function(e){return Pp(RJ(e,ebO(this.d,e.c.length)),10)},Y5(eVN,"GreedyCycleBreaker",782),eTS(1386,782,eVD,kQ),eUe.Zf=function(e){var t,n,r,i;for(i=null,t=eUu,r=new fz(e);r.a1&&(gN(LK(e_k(Bq((GK(0,e.c.length),Pp(e.c[0],10))),(eBy(),ti7))))?eMR(e,this.d,Pp(this,660)):(Hj(),Mv(e,this.d)),eaz(this.e,e))},eUe.Sf=function(e,t,n,r){var i,a,o,s,u,c,l;for(t!=ja(n,e.length)&&(a=e[t-(n?1:-1)],Xy(this.f,a,n?(enY(),tsN):(enY(),tsD))),i=e[t][0],l=!r||i.k==(eEn(),e8C),c=ZW(e[t]),this.ag(c,l,!1,n),o=0,u=new fz(c);u.a"),e0?zJ(this.a,e[t-1],e[t]):!n&&t1&&(gN(LK(e_k(Bq((GK(0,e.c.length),Pp(e.c[0],10))),(eBy(),ti7))))?eMR(e,this.d,this):(Hj(),Mv(e,this.d)),gN(LK(e_k(Bq((GK(0,e.c.length),Pp(e.c[0],10))),ti7)))||eaz(this.e,e))},Y5(eVF,"ModelOrderBarycenterHeuristic",660),eTS(1803,1,e$C,hx),eUe.ue=function(e,t){return eED(this.a,Pp(e,10),Pp(t,10))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eVF,"ModelOrderBarycenterHeuristic/lambda$0$Type",1803),eTS(1403,1,eVD,cF),eUe.Yf=function(e){var t;return Pp(e,37),t=TL(tus),RI(t,(e_x(),e8n),(eB$(),e7I)),t},eUe.pf=function(e,t){$w((Pp(e,37),t))},Y5(eVF,"NoCrossingMinimizer",1403),eTS(796,402,eVR,yu),eUe.$f=function(e,t,n){var r,i,a,o,s,u,c,l,f,d,h;switch(f=this.g,n.g){case 1:for(i=0,a=0,l=new fz(e.j);l.a1&&(i.j==(eYu(),tby)?this.b[e]=!0:i.j==tbY&&e>0&&(this.b[e-1]=!0))},eUe.f=0,Y5(eWc,"AllCrossingsCounter",1798),eTS(587,1,{},erH),eUe.b=0,eUe.d=0,Y5(eWc,"BinaryIndexedTree",587),eTS(524,1,{},IQ),Y5(eWc,"CrossingsCounter",524),eTS(1906,1,e$C,hT),eUe.ue=function(e,t){return je(this.a,Pp(e,11),Pp(t,11))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eWc,"CrossingsCounter/lambda$0$Type",1906),eTS(1907,1,e$C,hM),eUe.ue=function(e,t){return jt(this.a,Pp(e,11),Pp(t,11))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eWc,"CrossingsCounter/lambda$1$Type",1907),eTS(1908,1,e$C,hO),eUe.ue=function(e,t){return jn(this.a,Pp(e,11),Pp(t,11))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eWc,"CrossingsCounter/lambda$2$Type",1908),eTS(1909,1,e$C,hA),eUe.ue=function(e,t){return jr(this.a,Pp(e,11),Pp(t,11))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eWc,"CrossingsCounter/lambda$3$Type",1909),eTS(1910,1,eUF,hL),eUe.td=function(e){QT(this.a,Pp(e,11))},Y5(eWc,"CrossingsCounter/lambda$4$Type",1910),eTS(1911,1,eU8,hC),eUe.Mb=function(e){return kq(this.a,Pp(e,11))},Y5(eWc,"CrossingsCounter/lambda$5$Type",1911),eTS(1912,1,eUF,hI),eUe.td=function(e){kV(this,e)},Y5(eWc,"CrossingsCounter/lambda$6$Type",1912),eTS(1913,1,eUF,SF),eUe.td=function(e){var t;Pj(),Vw(this.b,(t=this.a,Pp(e,11),t))},Y5(eWc,"CrossingsCounter/lambda$7$Type",1913),eTS(826,1,e$q,iq),eUe.Lb=function(e){return Pj(),Ln(Pp(e,11),(eBU(),tng))},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return Pj(),Ln(Pp(e,11),(eBU(),tng))},Y5(eWc,"CrossingsCounter/lambda$8$Type",826),eTS(1905,1,{},hD),Y5(eWc,"HyperedgeCrossingsCounter",1905),eTS(467,1,{35:1,467:1},Cq),eUe.wd=function(e){return ehq(this,Pp(e,467))},eUe.b=0,eUe.c=0,eUe.e=0,eUe.f=0;var e5m=Y5(eWc,"HyperedgeCrossingsCounter/Hyperedge",467);eTS(362,1,{35:1,362:1},He),eUe.wd=function(e){return eMf(this,Pp(e,362))},eUe.b=0,eUe.c=0;var e5g=Y5(eWc,"HyperedgeCrossingsCounter/HyperedgeCorner",362);eTS(523,22,{3:1,35:1,22:1,523:1},Sj);var e5v=enw(eWc,"HyperedgeCrossingsCounter/HyperedgeCorner/Type",523,e1G,$V,Nc);eTS(1405,1,eVD,cO),eUe.Yf=function(e){return Pp(e_k(Pp(e,37),(eBU(),tt3)),21).Hc((eLR(),ttw))?tuh:null},eUe.pf=function(e,t){evK(this,Pp(e,37),t)},Y5(eVY,"InteractiveNodePlacer",1405),eTS(1406,1,eVD,cM),eUe.Yf=function(e){return Pp(e_k(Pp(e,37),(eBU(),tt3)),21).Hc((eLR(),ttw))?tup:null},eUe.pf=function(e,t){emS(this,Pp(e,37),t)},Y5(eVY,"LinearSegmentsNodePlacer",1406),eTS(257,1,{35:1,257:1},ma),eUe.wd=function(e){return vH(this,Pp(e,257))},eUe.Fb=function(e){var t;return!!M4(e,257)&&(t=Pp(e,257),this.b==t.b)},eUe.Hb=function(){return this.b},eUe.Ib=function(){return"ls"+e_F(this.e)},eUe.a=0,eUe.b=0,eUe.c=-1,eUe.d=-1,eUe.g=0;var e5y=Y5(eVY,"LinearSegmentsNodePlacer/LinearSegment",257);eTS(1408,1,eVD,jW),eUe.Yf=function(e){return Pp(e_k(Pp(e,37),(eBU(),tt3)),21).Hc((eLR(),ttw))?tug:null},eUe.pf=function(e,t){eBr(this,Pp(e,37),t)},eUe.b=0,eUe.g=0,Y5(eVY,"NetworkSimplexPlacer",1408),eTS(1427,1,e$C,iZ),eUe.ue=function(e,t){return ME(Pp(e,19).a,Pp(t,19).a)},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eVY,"NetworkSimplexPlacer/0methodref$compare$Type",1427),eTS(1429,1,e$C,iX),eUe.ue=function(e,t){return ME(Pp(e,19).a,Pp(t,19).a)},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eVY,"NetworkSimplexPlacer/1methodref$compare$Type",1429),eTS(649,1,{649:1},SY);var e5w=Y5(eVY,"NetworkSimplexPlacer/EdgeRep",649);eTS(401,1,{401:1},Ht),eUe.b=!1;var e5_=Y5(eVY,"NetworkSimplexPlacer/NodeRep",401);eTS(508,12,{3:1,4:1,20:1,28:1,52:1,12:1,14:1,15:1,54:1,508:1},mu),Y5(eVY,"NetworkSimplexPlacer/Path",508),eTS(1409,1,{},iJ),eUe.Kb=function(e){return Pp(e,17).d.i.k},Y5(eVY,"NetworkSimplexPlacer/Path/lambda$0$Type",1409),eTS(1410,1,eU8,iQ),eUe.Mb=function(e){return Pp(e,267)==(eEn(),e8D)},Y5(eVY,"NetworkSimplexPlacer/Path/lambda$1$Type",1410),eTS(1411,1,{},i1),eUe.Kb=function(e){return Pp(e,17).d.i},Y5(eVY,"NetworkSimplexPlacer/Path/lambda$2$Type",1411),eTS(1412,1,eU8,hN),eUe.Mb=function(e){return Ct(edH(Pp(e,10)))},Y5(eVY,"NetworkSimplexPlacer/Path/lambda$3$Type",1412),eTS(1413,1,eU8,i0),eUe.Mb=function(e){return RM(Pp(e,11))},Y5(eVY,"NetworkSimplexPlacer/lambda$0$Type",1413),eTS(1414,1,eUF,SB),eUe.td=function(e){MP(this.a,this.b,Pp(e,11))},Y5(eVY,"NetworkSimplexPlacer/lambda$1$Type",1414),eTS(1423,1,eUF,hP),eUe.td=function(e){ekS(this.a,Pp(e,17))},Y5(eVY,"NetworkSimplexPlacer/lambda$10$Type",1423),eTS(1424,1,{},i2),eUe.Kb=function(e){return GE(),new R1(null,new Gq(Pp(e,29).a,16))},Y5(eVY,"NetworkSimplexPlacer/lambda$11$Type",1424),eTS(1425,1,eUF,hR),eUe.td=function(e){eCe(this.a,Pp(e,10))},Y5(eVY,"NetworkSimplexPlacer/lambda$12$Type",1425),eTS(1426,1,{},i3),eUe.Kb=function(e){return GE(),ell(Pp(e,121).e)},Y5(eVY,"NetworkSimplexPlacer/lambda$13$Type",1426),eTS(1428,1,{},i4),eUe.Kb=function(e){return GE(),ell(Pp(e,121).e)},Y5(eVY,"NetworkSimplexPlacer/lambda$15$Type",1428),eTS(1430,1,eU8,i5),eUe.Mb=function(e){return GE(),Pp(e,401).c.k==(eEn(),e8N)},Y5(eVY,"NetworkSimplexPlacer/lambda$17$Type",1430),eTS(1431,1,eU8,i6),eUe.Mb=function(e){return GE(),Pp(e,401).c.j.c.length>1},Y5(eVY,"NetworkSimplexPlacer/lambda$18$Type",1431),eTS(1432,1,eUF,Hn),eUe.td=function(e){ef2(this.c,this.b,this.d,this.a,Pp(e,401))},eUe.c=0,eUe.d=0,Y5(eVY,"NetworkSimplexPlacer/lambda$19$Type",1432),eTS(1415,1,{},i9),eUe.Kb=function(e){return GE(),new R1(null,new Gq(Pp(e,29).a,16))},Y5(eVY,"NetworkSimplexPlacer/lambda$2$Type",1415),eTS(1433,1,eUF,hj),eUe.td=function(e){MD(this.a,Pp(e,11))},eUe.a=0,Y5(eVY,"NetworkSimplexPlacer/lambda$20$Type",1433),eTS(1434,1,{},i8),eUe.Kb=function(e){return GE(),new R1(null,new Gq(Pp(e,29).a,16))},Y5(eVY,"NetworkSimplexPlacer/lambda$21$Type",1434),eTS(1435,1,eUF,hF),eUe.td=function(e){Oi(this.a,Pp(e,10))},Y5(eVY,"NetworkSimplexPlacer/lambda$22$Type",1435),eTS(1436,1,eU8,i7),eUe.Mb=function(e){return Ct(e)},Y5(eVY,"NetworkSimplexPlacer/lambda$23$Type",1436),eTS(1437,1,{},ae),eUe.Kb=function(e){return GE(),new R1(null,new Gq(Pp(e,29).a,16))},Y5(eVY,"NetworkSimplexPlacer/lambda$24$Type",1437),eTS(1438,1,eU8,hY),eUe.Mb=function(e){return xH(this.a,Pp(e,10))},Y5(eVY,"NetworkSimplexPlacer/lambda$25$Type",1438),eTS(1439,1,eUF,SU),eUe.td=function(e){eSl(this.a,this.b,Pp(e,10))},Y5(eVY,"NetworkSimplexPlacer/lambda$26$Type",1439),eTS(1440,1,eU8,at),eUe.Mb=function(e){return GE(),!q8(Pp(e,17))},Y5(eVY,"NetworkSimplexPlacer/lambda$27$Type",1440),eTS(1441,1,eU8,an),eUe.Mb=function(e){return GE(),!q8(Pp(e,17))},Y5(eVY,"NetworkSimplexPlacer/lambda$28$Type",1441),eTS(1442,1,{},hB),eUe.Ce=function(e,t){return M8(this.a,Pp(e,29),Pp(t,29))},Y5(eVY,"NetworkSimplexPlacer/lambda$29$Type",1442),eTS(1416,1,{},ar),eUe.Kb=function(e){return GE(),new R1(null,new YI(new Fa(OH(efc(Pp(e,10)).a.Kc(),new c))))},Y5(eVY,"NetworkSimplexPlacer/lambda$3$Type",1416),eTS(1417,1,eU8,ai),eUe.Mb=function(e){return GE(),Km(Pp(e,17))},Y5(eVY,"NetworkSimplexPlacer/lambda$4$Type",1417),eTS(1418,1,eUF,hU),eUe.td=function(e){eNB(this.a,Pp(e,17))},Y5(eVY,"NetworkSimplexPlacer/lambda$5$Type",1418),eTS(1419,1,{},aa),eUe.Kb=function(e){return GE(),new R1(null,new Gq(Pp(e,29).a,16))},Y5(eVY,"NetworkSimplexPlacer/lambda$6$Type",1419),eTS(1420,1,eU8,ao),eUe.Mb=function(e){return GE(),Pp(e,10).k==(eEn(),e8N)},Y5(eVY,"NetworkSimplexPlacer/lambda$7$Type",1420),eTS(1421,1,{},as),eUe.Kb=function(e){return GE(),new R1(null,new YI(new Fa(OH(efs(Pp(e,10)).a.Kc(),new c))))},Y5(eVY,"NetworkSimplexPlacer/lambda$8$Type",1421),eTS(1422,1,eU8,au),eUe.Mb=function(e){return GE(),Rc(Pp(e,17))},Y5(eVY,"NetworkSimplexPlacer/lambda$9$Type",1422),eTS(1404,1,eVD,cz),eUe.Yf=function(e){return Pp(e_k(Pp(e,37),(eBU(),tt3)),21).Hc((eLR(),ttw))?tuv:null},eUe.pf=function(e,t){ePV(Pp(e,37),t)},Y5(eVY,"SimpleNodePlacer",1404),eTS(180,1,{180:1},eIW),eUe.Ib=function(){var e;return e="",this.c==(zs(),tuw)?e+=ezn:this.c==tuy&&(e+=ezt),this.o==(zQ(),tuE)?e+=ezh:this.o==tuS?e+="UP":e+="BALANCED",e},Y5(eVH,"BKAlignedLayout",180),eTS(516,22,{3:1,35:1,22:1,516:1},Sz);var e5E=enw(eVH,"BKAlignedLayout/HDirection",516,e1G,$Z,Nl);eTS(515,22,{3:1,35:1,22:1,515:1},S$);var e5S=enw(eVH,"BKAlignedLayout/VDirection",515,e1G,$X,Nf);eTS(1634,1,{},SH),Y5(eVH,"BKAligner",1634),eTS(1637,1,{},eg$),Y5(eVH,"BKCompactor",1637),eTS(654,1,{654:1},ac),eUe.a=0,Y5(eVH,"BKCompactor/ClassEdge",654),eTS(458,1,{458:1},mo),eUe.a=null,eUe.b=0,Y5(eVH,"BKCompactor/ClassNode",458),eTS(1407,1,eVD,kX),eUe.Yf=function(e){return Pp(e_k(Pp(e,37),(eBU(),tt3)),21).Hc((eLR(),ttw))?tux:null},eUe.pf=function(e,t){eBP(this,Pp(e,37),t)},eUe.d=!1,Y5(eVH,"BKNodePlacer",1407),eTS(1635,1,{},al),eUe.d=0,Y5(eVH,"NeighborhoodInformation",1635),eTS(1636,1,e$C,hH),eUe.ue=function(e,t){return etp(this,Pp(e,46),Pp(t,46))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eVH,"NeighborhoodInformation/NeighborComparator",1636),eTS(808,1,{}),Y5(eVH,"ThresholdStrategy",808),eTS(1763,808,{},mm),eUe.bg=function(e,t,n){return this.a.o==(zQ(),tuS)?eHQ:eH1},eUe.cg=function(){},Y5(eVH,"ThresholdStrategy/NullThresholdStrategy",1763),eTS(579,1,{579:1},SG),eUe.c=!1,eUe.d=!1,Y5(eVH,"ThresholdStrategy/Postprocessable",579),eTS(1764,808,{},mg),eUe.bg=function(e,t,n){var r,i,a;return(i=t==n,r=this.a.a[n.p]==t,i||r)?(a=e,this.a.c,zs(),i&&(a=ePX(this,t,!0)),isNaN(a)||isFinite(a)||!r||(a=ePX(this,n,!1)),a):e},eUe.cg=function(){for(var e,t,n,r,i;0!=this.d.b;){if((r=eDJ(this,i=Pp(zv(this.d),579))).a)e=r.a,((n=gN(this.a.f[this.a.g[i.b.p].p]))||q8(e)||e.c.i.c!=e.d.i.c)&&((t=eMd(this,i))||Th(this.e,i))}for(;0!=this.e.a.c.length;)eMd(this,Pp(euO(this.e),579))},Y5(eVH,"ThresholdStrategy/SimpleThresholdStrategy",1764),eTS(635,1,{635:1,246:1,234:1},af),eUe.Kf=function(){return eaM(this)},eUe.Xf=function(){return eaM(this)},Y5(eV$,"EdgeRouterFactory",635),eTS(1458,1,eVD,cG),eUe.Yf=function(e){return eLb(Pp(e,37))},eUe.pf=function(e,t){eP7(Pp(e,37),t)},Y5(eV$,"OrthogonalEdgeRouter",1458),eTS(1451,1,eVD,kJ),eUe.Yf=function(e){return ev4(Pp(e,37))},eUe.pf=function(e,t){eYg(this,Pp(e,37),t)},Y5(eV$,"PolylineEdgeRouter",1451),eTS(1452,1,e$q,ad),eUe.Lb=function(e){return eaQ(Pp(e,10))},eUe.Fb=function(e){return this===e},eUe.Mb=function(e){return eaQ(Pp(e,10))},Y5(eV$,"PolylineEdgeRouter/1",1452),eTS(1809,1,eU8,ah),eUe.Mb=function(e){return Pp(e,129).c==(Xa(),tuU)},Y5(eVz,"HyperEdgeCycleDetector/lambda$0$Type",1809),eTS(1810,1,{},ap),eUe.Ge=function(e){return Pp(e,129).d},Y5(eVz,"HyperEdgeCycleDetector/lambda$1$Type",1810),eTS(1811,1,eU8,ab),eUe.Mb=function(e){return Pp(e,129).c==(Xa(),tuU)},Y5(eVz,"HyperEdgeCycleDetector/lambda$2$Type",1811),eTS(1812,1,{},am),eUe.Ge=function(e){return Pp(e,129).d},Y5(eVz,"HyperEdgeCycleDetector/lambda$3$Type",1812),eTS(1813,1,{},ag),eUe.Ge=function(e){return Pp(e,129).d},Y5(eVz,"HyperEdgeCycleDetector/lambda$4$Type",1813),eTS(1814,1,{},av),eUe.Ge=function(e){return Pp(e,129).d},Y5(eVz,"HyperEdgeCycleDetector/lambda$5$Type",1814),eTS(112,1,{35:1,112:1},ea$),eUe.wd=function(e){return v$(this,Pp(e,112))},eUe.Fb=function(e){var t;return!!M4(e,112)&&(t=Pp(e,112),this.g==t.g)},eUe.Hb=function(){return this.g},eUe.Ib=function(){var e,t,n,r;for(e=new O0("{"),r=new fz(this.n);r.a"+this.b+" ("+AK(this.c)+")"},eUe.d=0,Y5(eVz,"HyperEdgeSegmentDependency",129),eTS(520,22,{3:1,35:1,22:1,520:1},SW);var e5k=enw(eVz,"HyperEdgeSegmentDependency/DependencyType",520,e1G,$q,Nd);eTS(1815,1,{},h$),Y5(eVz,"HyperEdgeSegmentSplitter",1815),eTS(1816,1,{},ym),eUe.a=0,eUe.b=0,Y5(eVz,"HyperEdgeSegmentSplitter/AreaRating",1816),eTS(329,1,{329:1},N4),eUe.a=0,eUe.b=0,eUe.c=0,Y5(eVz,"HyperEdgeSegmentSplitter/FreeArea",329),eTS(1817,1,e$C,aT),eUe.ue=function(e,t){return ID(Pp(e,112),Pp(t,112))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eVz,"HyperEdgeSegmentSplitter/lambda$0$Type",1817),eTS(1818,1,eUF,Hi),eUe.td=function(e){V5(this.a,this.d,this.c,this.b,Pp(e,112))},eUe.b=0,Y5(eVz,"HyperEdgeSegmentSplitter/lambda$1$Type",1818),eTS(1819,1,{},aM),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,112).e,16))},Y5(eVz,"HyperEdgeSegmentSplitter/lambda$2$Type",1819),eTS(1820,1,{},aO),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,112).j,16))},Y5(eVz,"HyperEdgeSegmentSplitter/lambda$3$Type",1820),eTS(1821,1,{},aA),eUe.Fe=function(e){return gP(LV(e))},Y5(eVz,"HyperEdgeSegmentSplitter/lambda$4$Type",1821),eTS(655,1,{},YJ),eUe.a=0,eUe.b=0,eUe.c=0,Y5(eVz,"OrthogonalRoutingGenerator",655),eTS(1638,1,{},aL),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,112).e,16))},Y5(eVz,"OrthogonalRoutingGenerator/lambda$0$Type",1638),eTS(1639,1,{},aC),eUe.Kb=function(e){return new R1(null,new Gq(Pp(e,112).j,16))},Y5(eVz,"OrthogonalRoutingGenerator/lambda$1$Type",1639),eTS(661,1,{}),Y5(eVG,"BaseRoutingDirectionStrategy",661),eTS(1807,661,{},mv),eUe.dg=function(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b;if(!e.r||e.q)for(l=t+e.o*n,c=new fz(e.n);c.aez8&&(a=l,i=e,r=new kl(f,a),P7(o.a,r),eDD(this,o,i,r,!1),(d=e.r)&&(h=gP(LV(ep3(d.e,0))),r=new kl(h,a),P7(o.a,r),eDD(this,o,i,r,!1),a=t+d.o*n,i=d,r=new kl(h,a),P7(o.a,r),eDD(this,o,i,r,!1)),r=new kl(b,a),P7(o.a,r),eDD(this,o,i,r,!1)))},eUe.eg=function(e){return e.i.n.a+e.n.a+e.a.a},eUe.fg=function(){return eYu(),tbj},eUe.gg=function(){return eYu(),tbw},Y5(eVG,"NorthToSouthRoutingStrategy",1807),eTS(1808,661,{},my),eUe.dg=function(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b;if(!e.r||e.q)for(l=t-e.o*n,c=new fz(e.n);c.aez8&&(a=l,i=e,r=new kl(f,a),P7(o.a,r),eDD(this,o,i,r,!1),(d=e.r)&&(h=gP(LV(ep3(d.e,0))),r=new kl(h,a),P7(o.a,r),eDD(this,o,i,r,!1),a=t-d.o*n,i=d,r=new kl(h,a),P7(o.a,r),eDD(this,o,i,r,!1)),r=new kl(b,a),P7(o.a,r),eDD(this,o,i,r,!1)))},eUe.eg=function(e){return e.i.n.a+e.n.a+e.a.a},eUe.fg=function(){return eYu(),tbw},eUe.gg=function(){return eYu(),tbj},Y5(eVG,"SouthToNorthRoutingStrategy",1808),eTS(1806,661,{},mw),eUe.dg=function(e,t,n){var r,i,a,o,s,u,c,l,f,d,h,p,b;if(!e.r||e.q)for(l=t+e.o*n,c=new fz(e.n);c.aez8&&(a=l,i=e,r=new kl(a,f),P7(o.a,r),eDD(this,o,i,r,!0),(d=e.r)&&(h=gP(LV(ep3(d.e,0))),r=new kl(a,h),P7(o.a,r),eDD(this,o,i,r,!0),a=t+d.o*n,i=d,r=new kl(a,h),P7(o.a,r),eDD(this,o,i,r,!0)),r=new kl(a,b),P7(o.a,r),eDD(this,o,i,r,!0)))},eUe.eg=function(e){return e.i.n.b+e.n.b+e.a.b},eUe.fg=function(){return eYu(),tby},eUe.gg=function(){return eYu(),tbY},Y5(eVG,"WestToEastRoutingStrategy",1806),eTS(813,1,{},eNG),eUe.Ib=function(){return e_F(this.a)},eUe.b=0,eUe.c=!1,eUe.d=!1,eUe.f=0,Y5(eVK,"NubSpline",813),eTS(407,1,{407:1},eA2,za),Y5(eVK,"NubSpline/PolarCP",407),eTS(1453,1,eVD,egt),eUe.Yf=function(e){return ewy(Pp(e,37))},eUe.pf=function(e,t){eYW(this,Pp(e,37),t)},Y5(eVK,"SplineEdgeRouter",1453),eTS(268,1,{268:1},Xt),eUe.Ib=function(){return this.a+" ->("+this.c+") "+this.b},eUe.c=0,Y5(eVK,"SplineEdgeRouter/Dependency",268),eTS(455,22,{3:1,35:1,22:1,455:1},SK);var e5x=enw(eVK,"SplineEdgeRouter/SideToProcess",455,e1G,$J,Nh);eTS(1454,1,eU8,ak),eUe.Mb=function(e){return eAq(),!Pp(e,128).o},Y5(eVK,"SplineEdgeRouter/lambda$0$Type",1454),eTS(1455,1,{},aS),eUe.Ge=function(e){return eAq(),Pp(e,128).v+1},Y5(eVK,"SplineEdgeRouter/lambda$1$Type",1455),eTS(1456,1,eUF,SV),eUe.td=function(e){Rw(this.a,this.b,Pp(e,46))},Y5(eVK,"SplineEdgeRouter/lambda$2$Type",1456),eTS(1457,1,eUF,Sq),eUe.td=function(e){R_(this.a,this.b,Pp(e,46))},Y5(eVK,"SplineEdgeRouter/lambda$3$Type",1457),eTS(128,1,{35:1,128:1},eSB,eRM),eUe.wd=function(e){return vz(this,Pp(e,128))},eUe.b=0,eUe.e=!1,eUe.f=0,eUe.g=0,eUe.j=!1,eUe.k=!1,eUe.n=0,eUe.o=!1,eUe.p=!1,eUe.q=!1,eUe.s=0,eUe.u=0,eUe.v=0,eUe.F=0,Y5(eVK,"SplineSegment",128),eTS(459,1,{459:1},ax),eUe.a=0,eUe.b=!1,eUe.c=!1,eUe.d=!1,eUe.e=!1,eUe.f=0,Y5(eVK,"SplineSegment/EdgeInformation",459),eTS(1234,1,{},ay),Y5(eVJ,ezQ,1234),eTS(1235,1,e$C,aw),eUe.ue=function(e,t){return ek4(Pp(e,135),Pp(t,135))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eVJ,ez1,1235),eTS(1233,1,{},y2),Y5(eVJ,"MrTree",1233),eTS(393,22,{3:1,35:1,22:1,393:1,246:1,234:1},SZ),eUe.Kf=function(){return ek6(this)},eUe.Xf=function(){return ek6(this)};var e5T=enw(eVJ,"TreeLayoutPhases",393,e1G,VM,Np);eTS(1130,209,ezL,CJ),eUe.Ze=function(e,t){var n,r,i,a,o,s,u;for(gN(LK(eT8(e,(eTj(),tcA))))||zh(n=new df((_q(),new gM(e)))),o=(eaW(s=new Xn,e),eo3(s,(eR6(),tcl),e),u=new p2,eDf(e,s,u),eDU(e,s,u),s),a=eDO(this.a,o),i=new fz(a);i.a"+WU(this.c):"e_"+esj(this)},Y5(eVQ,"TEdge",188),eTS(135,134,{3:1,135:1,94:1,134:1},Xn),eUe.Ib=function(){var e,t,n,r,i;for(i=null,r=epL(this.b,0);r.b!=r.d.c;)i+=(null==(n=Pp(Vv(r),86)).c||0==n.c.length?"n_"+n.g:"n_"+n.c)+"\n";for(t=epL(this.a,0);t.b!=t.d.c;)i+=((e=Pp(Vv(t),188)).b&&e.c?WU(e.b)+"->"+WU(e.c):"e_"+esj(e))+"\n";return i};var e5M=Y5(eVQ,"TGraph",135);eTS(633,502,{3:1,502:1,633:1,94:1,134:1}),Y5(eVQ,"TShape",633),eTS(86,633,{3:1,502:1,86:1,633:1,94:1,134:1},esH),eUe.Ib=function(){return WU(this)};var e5O=Y5(eVQ,"TNode",86);eTS(255,1,eU$,hz),eUe.Jc=function(e){qX(this,e)},eUe.Kc=function(){var e;return e=epL(this.a.d,0),new hG(e)},Y5(eVQ,"TNode/2",255),eTS(358,1,eUE,hG),eUe.Nb=function(e){F8(this,e)},eUe.Pb=function(){return Pp(Vv(this.a),188).c},eUe.Ob=function(){return yV(this.a)},eUe.Qb=function(){etu(this.a)},Y5(eVQ,"TNode/2/1",358),eTS(1840,1,eGB,CX),eUe.pf=function(e,t){eNv(this,Pp(e,135),t)},Y5(eV1,"FanProcessor",1840),eTS(327,22,{3:1,35:1,22:1,327:1,234:1},SX),eUe.Kf=function(){switch(this.g){case 0:return new mX;case 1:return new CX;case 2:return new aN;case 3:return new aI;case 4:return new aR;case 5:return new aj;default:throw p7(new gL(eWt+(null!=this.f?this.f:""+this.g)))}};var e5A=enw(eV1,eWn,327,e1G,JS,Nb);eTS(1843,1,eGB,aI),eUe.pf=function(e,t){eMo(this,Pp(e,135),t)},eUe.a=0,Y5(eV1,"LevelHeightProcessor",1843),eTS(1844,1,eU$,aD),eUe.Jc=function(e){qX(this,e)},eUe.Kc=function(){return Hj(),wV(),e2o},Y5(eV1,"LevelHeightProcessor/1",1844),eTS(1841,1,eGB,aN),eUe.pf=function(e,t){eSP(this,Pp(e,135),t)},eUe.a=0,Y5(eV1,"NeighborsProcessor",1841),eTS(1842,1,eU$,aP),eUe.Jc=function(e){qX(this,e)},eUe.Kc=function(){return Hj(),wV(),e2o},Y5(eV1,"NeighborsProcessor/1",1842),eTS(1845,1,eGB,aR),eUe.pf=function(e,t){eMa(this,Pp(e,135),t)},eUe.a=0,Y5(eV1,"NodePositionProcessor",1845),eTS(1839,1,eGB,mX),eUe.pf=function(e,t){eRm(this,Pp(e,135))},Y5(eV1,"RootProcessor",1839),eTS(1846,1,eGB,aj),eUe.pf=function(e,t){elE(Pp(e,135))},Y5(eV1,"Untreeifyer",1846),eTS(851,1,e$2,c$),eUe.Qe=function(e){efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eV3),""),"Weighting of Nodes"),"Which weighting to use when computing a node order."),tcE),(eSd(),tdv)),e5L),el9((epx(),tdh))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eV4),""),"Search Order"),"Which search order to use when computing a spanning tree."),tcw),tdv),e5C),el9(tdh)))),ejG((new cH,e))},Y5(eV5,"MrTreeMetaDataProvider",851),eTS(994,1,e$2,cH),eUe.Qe=function(e){ejG(e)},Y5(eV5,"MrTreeOptions",994),eTS(995,1,{},aF),eUe.$e=function(){return new CJ},eUe._e=function(e){},Y5(eV5,"MrTreeOptions/MrtreeFactory",995),eTS(480,22,{3:1,35:1,22:1,480:1},SJ);var e5L=enw(eV5,"OrderWeighting",480,e1G,$1,Nm);eTS(425,22,{3:1,35:1,22:1,425:1},SQ);var e5C=enw(eV5,"TreeifyingOrder",425,e1G,$Q,Nv);eTS(1459,1,eVD,cD),eUe.Yf=function(e){return Pp(e,135),tcz},eUe.pf=function(e,t){eiD(this,Pp(e,135),t)},Y5("org.eclipse.elk.alg.mrtree.p1treeify","DFSTreeifyer",1459),eTS(1460,1,eVD,cN),eUe.Yf=function(e){return Pp(e,135),tcG},eUe.pf=function(e,t){eSZ(this,Pp(e,135),t)},Y5("org.eclipse.elk.alg.mrtree.p2order","NodeOrderer",1460),eTS(1461,1,eVD,cI),eUe.Yf=function(e){return Pp(e,135),tcW},eUe.pf=function(e,t){eCh(this,Pp(e,135),t)},eUe.a=0,Y5("org.eclipse.elk.alg.mrtree.p3place","NodePlacer",1461),eTS(1462,1,eVD,cP),eUe.Yf=function(e){return Pp(e,135),tcK},eUe.pf=function(e,t){evm(Pp(e,135),t)},Y5("org.eclipse.elk.alg.mrtree.p4route","EdgeRouter",1462),eTS(495,22,{3:1,35:1,22:1,495:1,246:1,234:1},S1),eUe.Kf=function(){return ede(this)},eUe.Xf=function(){return ede(this)};var e5I=enw(eV8,"RadialLayoutPhases",495,e1G,$0,Ng);eTS(1131,209,ezL,y0),eUe.Ze=function(e,t){var n,r,i,a,o,s;if(n=eS8(this,e),ewG(t,"Radial layout",n.c.length),gN(LK(eT8(e,(egj(),tlm))))||zh(r=new df((_q(),new gM(e)))),s=ewE(e),ebu(e,(Lj(),tcV),s),!s)throw p7(new gL("The given graph is not a tree!"));for(0==(i=gP(LV(eT8(e,tl_))))&&(i=ekB(e)),ebu(e,tl_,i),o=new fz(eS8(this,e));o.a0&&eu8((GV(t-1,e.length),e.charCodeAt(t-1)),eGq);)--t;if(r>=t)throw p7(new gL("The given string does not contain any numbers."));if(2!=(i=eIk(e.substr(r,t-r),",|;|\r|\n")).length)throw p7(new gL("Exactly two numbers are expected, "+i.length+" were found."));try{this.a=eEu(e_H(i[0])),this.b=eEu(e_H(i[1]))}catch(a){if(a=eoa(a),M4(a,127))throw n=a,p7(new gL(eGZ+n));throw p7(a)}},eUe.Ib=function(){return"("+this.a+","+this.b+")"},eUe.a=0,eUe.b=0;var e50=Y5(eGX,"KVector",8);eTS(74,68,{3:1,4:1,20:1,28:1,52:1,14:1,68:1,15:1,74:1,414:1},mE,yc,Lb),eUe.Pc=function(){return euE(this)},eUe.Jf=function(e){var t,n,r,i,a,o;r=eIk(e,",|;|\\(|\\)|\\[|\\]|\\{|\\}| | |\n"),HC(this);try{for(n=0,a=0,i=0,o=0;n0&&(a%2==0?i=eEu(r[n]):o=eEu(r[n]),a>0&&a%2!=0&&P7(this,new kl(i,o)),++a),++n}catch(s){if(s=eoa(s),M4(s,127))throw t=s,p7(new gL("The given string does not match the expected format for vectors."+t));throw p7(s)}},eUe.Ib=function(){var e,t,n;for(e=new O0("("),t=epL(this,0);t.b!=t.d.c;)xM(e,(n=Pp(Vv(t),8)).a+","+n.b),t.b!=t.d.c&&(e.a+="; ");return(e.a+=")",e).a};var e52=Y5(eGX,"KVectorChain",74);eTS(248,22,{3:1,35:1,22:1,248:1},kf);var e53=enw(eZe,"Alignment",248,e1G,Jg,NP);eTS(979,1,e$2,cq),eUe.Qe=function(e){eDj(e)},Y5(eZe,"BoxLayouterOptions",979),eTS(980,1,{},oA),eUe.$e=function(){return new oF},eUe._e=function(e){},Y5(eZe,"BoxLayouterOptions/BoxFactory",980),eTS(291,22,{3:1,35:1,22:1,291:1},kd);var e54=enw(eZe,"ContentAlignment",291,e1G,Jm,NR);eTS(684,1,e$2,cZ),eUe.Qe=function(e){efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eZi),""),"Layout Algorithm"),"Select a specific layout algorithm."),(eSd(),tdE)),e17),el9((epx(),tdh))))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eZa),""),"Resolved Layout Algorithm"),"Meta data associated with the selected algorithm."),td_),e5X),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVi),""),"Alignment"),"Alignment of the selected node relative to other nodes; the exact meaning depends on the used algorithm."),td0),tdv),e53),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,ezG),""),"Aspect Ratio"),"The desired aspect ratio of the drawing, that is the quotient of width by height."),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eZo),""),"Bend Points"),"A fixed list of bend points for the edge. This is used by the 'Fixed Layout' algorithm to specify a pre-defined routing for an edge. The vector chain must include the source point, any bend points, and the target point, so it must have at least two points."),td_),e52),el9(tdl)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVg),""),"Content Alignment"),"Specifies how the content of a node are aligned. Each node can individually control the alignment of its contents. I.e. if a node should be aligned top left in its parent node, the parent node should specify that option."),td8),tdy),e54),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVr),""),"Debug Mode"),"Whether additional debug information shall be generated."),(OQ(),!1)),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVu),""),ezw),"Overall direction of edges: horizontal (right / left) or vertical (down / up)."),tht),tdv),e55),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKB),""),"Edge Routing"),"What kind of edge routing style should be applied for the content of a parent node. Algorithms may also set this option to single edges in order to mark them as splines. The bend point list of edges with this option set to SPLINES must be interpreted as control points for a piecewise cubic spline."),tho),tdv),e59),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eqC),""),"Expand Nodes"),"If active, nodes are expanded to fill the area of their parent."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKP),""),"Hierarchy Handling"),"Determines whether separate layout runs are triggered for different compound nodes in a hierarchical graph. Setting a node's hierarchy handling to `INCLUDE_CHILDREN` will lay out that node and all of its descendants in a single layout run, until a descendant is encountered which has its hierarchy handling set to `SEPARATE_CHILDREN`. In general, `SEPARATE_CHILDREN` will ensure that a new layout run is triggered for a node with that setting. Including multiple levels of hierarchy in a single layout run may allow cross-hierarchical edges to be laid out properly. If the root node is set to `INHERIT` (or not set at all), the default behavior is `SEPARATE_CHILDREN`."),thf),tdv),e57),jL(tdh,eow(vx(e5Q,1),eU4,175,0,[tdd]))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,ezW),""),"Padding"),"The padding to be left to a parent element's border when placing child elements. This can also serve as an output option of a layout algorithm if node size calculation is setup appropriately."),thP),td_),e4R),jL(tdh,eow(vx(e5Q,1),eU4,175,0,[tdd]))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGu),""),"Interactive"),"Whether the algorithm should be run in interactive mode for the content of a parent node. What this means exactly depends on how the specific algorithm interprets this option. Usually in the interactive mode algorithms try to modify the current layout as little as possible."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVA),""),"interactive Layout"),"Whether the graph should be changeable interactively and by setting constraints"),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGf),""),"Omit Node Micro Layout"),"Node micro layout comprises the computation of node dimensions (if requested), the placement of ports and their labels, and the placement of node labels. The functionality is implemented independent of any specific layout algorithm and shouldn't have any negative impact on the layout algorithm's performance itself. Yet, if any unforeseen behavior occurs, this option allows to deactivate the micro layout."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGc),""),"Port Constraints"),"Defines constraints of the position of the ports of a node."),thq),tdv),e6r),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eVT),""),"Position"),"The position of a node, port, or label. This is used by the 'Fixed Layout' algorithm to specify a pre-defined position."),td_),e50),jL(tdd,eow(vx(e5Q,1),eU4,175,0,[tdp,tdf]))))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eGr),""),"Priority"),"Defines the priority of an object; its meaning depends on the specific layout algorithm and the context where it is used."),tdw),e15),jL(tdd,eow(vx(e5Q,1),eU4,175,0,[tdl]))))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eGo),""),"Randomization Seed"),"Seed used for pseudo-random number generators to control the layout algorithm. If the value is 0, the seed shall be determined pseudo-randomly (e.g. from the system time)."),tdw),e15),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eGs),""),"Separate Connected Components"),"Whether each connected component should be processed separately."),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVv),""),"Junction Points"),"This option is not used as option, but as output of the layout algorithms. It is attached to edges and determines the points where junction symbols should be drawn in order to represent hyperedges with orthogonal routing. Whether such points are computed depends on the chosen layout algorithm and edge routing style. The points are put into the vector chain with no specific order."),thv),td_),e52),el9(tdl)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eV_),""),"Comment Box"),"Whether the node should be regarded as a comment box instead of a regular node. In that case its placement should be similar to how labels are handled. Any edges incident to a comment box specify to which graph elements the comment is related."),!1),tdm),e11),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVE),""),"Hypernode"),"Whether the node should be handled as a hypernode."),!1),tdm),e11),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eZs),""),"Label Manager"),"Label managers can shorten labels upon a layout algorithm's request."),td_),tyO),jL(tdh,eow(vx(e5Q,1),eU4,175,0,[tdf]))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVM),""),"Margins"),"Margins define additional space around the actual bounds of a graph element. For instance, ports or labels being placed on the outside of a node's border might introduce such a margin. The margin is used to guarantee non-overlap of other graph elements with those ports or labels."),thw),td_),e4D),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVt),""),"No Layout"),"No layout is done for the associated element. This is used to mark parts of a diagram to avoid their inclusion in the layout graph, or to mark parts of the layout graph to prevent layout engines from processing them. If you wish to exclude the contents of a compound node from automatic layout, while the node itself is still considered on its own layer, use the 'Fixed Layout' algorithm for that node."),!1),tdm),e11),jL(tdd,eow(vx(e5Q,1),eU4,175,0,[tdl,tdp,tdf]))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZu),""),"Scale Factor"),"The scaling factor to be applied to the corresponding node in recursive layout. It causes the corresponding node's size to be adjusted, and its ports and labels to be sized and placed accordingly after the layout of that node has been determined (and before the node itself and its siblings are arranged). The scaling is not reverted afterwards, so the resulting layout graph contains the adjusted size and position data. This option is currently not supported if 'Layout Hierarchy' is set."),1),tdg),e13),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZc),""),"Animate"),"Whether the shift from the old layout to the new computed layout shall be animated."),!0),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZl),""),"Animation Time Factor"),"Factor for computation of animation time. The higher the value, the longer the animation time. If the value is 0, the resulting time is always equal to the minimum defined by 'Minimal Animation Time'."),ell(100)),tdw),e15),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZf),""),"Layout Ancestors"),"Whether the hierarchy levels on the path from the selected element to the root of the diagram shall be included in the layout process."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZd),""),"Maximal Animation Time"),"The maximal time for animations, in milliseconds."),ell(4e3)),tdw),e15),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZh),""),"Minimal Animation Time"),"The minimal time for animations, in milliseconds."),ell(400)),tdw),e15),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZp),""),"Progress Bar"),"Whether a progress bar shall be displayed during layout computations."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZb),""),"Validate Graph"),"Whether the graph shall be validated before any layout algorithm is applied. If this option is enabled and at least one error is found, the layout process is aborted and a message is shown to the user."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZm),""),"Validate Options"),"Whether layout options shall be validated before any layout algorithm is applied. If this option is enabled and at least one error is found, the layout process is aborted and a message is shown to the user."),!0),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZg),""),"Zoom to Fit"),"Whether the zoom level shall be set to view the whole diagram after layout."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZr),"box"),"Box Layout Mode"),"Configures the packing mode used by the {@link BoxLayoutProvider}. If SIMPLE is not required (neither priorities are used nor the interactive mode), GROUP_DEC can improve the packing and decrease the area. GROUP_MIXED and GROUP_INC may, in very specific scenarios, work better."),td5),tdv),e6u),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eKQ),eKU),"Comment Comment Spacing"),"Spacing to be preserved between a comment box and other comment boxes connected to the same node. The space left between comment boxes of different nodes is controlled by the node-node spacing."),10),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eK1),eKU),"Comment Node Spacing"),"Spacing to be preserved between a node and its connected comment boxes. The space left between a node and the comments of another node is controlled by the node-node spacing."),10),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,ez$),eKU),"Components Spacing"),"Spacing to be preserved between pairs of connected components. This option is only relevant if 'separateConnectedComponents' is activated."),20),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eK0),eKU),"Edge Spacing"),"Spacing to be preserved between any two edges. Note that while this can somewhat easily be satisfied for the segments of orthogonally drawn edges, it is harder for general polylines or splines."),10),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGa),eKU),"Edge Label Spacing"),"The minimal distance to be preserved between a label and the edge it is associated with. Note that the placement of a label is influenced by the 'edgelabels.placement' option."),2),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eK2),eKU),"Edge Node Spacing"),"Spacing to be preserved between nodes and edges."),10),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eK3),eKU),"Label Spacing"),"Determines the amount of space to be left between two labels of the same graph element."),0),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eK6),eKU),"Label Node Spacing"),"Spacing to be preserved between labels and the border of node they are associated with. Note that the placement of a label is influenced by the 'nodelabels.placement' option."),5),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eK4),eKU),"Horizontal spacing between Label and Port"),"Horizontal spacing to be preserved between labels and the ports they are associated with. Note that the placement of a label is influenced by the 'portlabels.placement' option."),1),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eK5),eKU),"Vertical spacing between Label and Port"),"Vertical spacing to be preserved between labels and the ports they are associated with. Note that the placement of a label is influenced by the 'portlabels.placement' option."),1),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGi),eKU),"Node Spacing"),"The minimal distance to be preserved between each two nodes."),20),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eK9),eKU),"Node Self Loop Spacing"),"Spacing to be preserved between a node and its self loops."),10),tdg),e13),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eK8),eKU),"Port Spacing"),"Spacing between pairs of ports of the same node."),10),tdg),e13),jL(tdh,eow(vx(e5Q,1),eU4,175,0,[tdd]))))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eK7),eKU),"Individual Spacing"),"Allows to specify individual spacing values for graph elements that shall be different from the value specified for the element's parent."),td_),e6c),jL(tdd,eow(vx(e5Q,1),eU4,175,0,[tdl,tdp,tdf]))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVO),eKU),"Additional Port Space"),"Additional space around the sets of ports on each node side. For each side of a node, this option can reserve additional space before and after the ports on each side. For example, a top spacing of 20 makes sure that the first port on the western and eastern side is 20 units away from the northern border."),tph),td_),e4D),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eVx),eZ_),"Layout Partition"),"Partition to which the node belongs. This requires Layout Partitioning to be active. Nodes with lower partition IDs will appear to the left of nodes with higher partition IDs (assuming a left-to-right layout direction)."),tdw),e15),jL(tdh,eow(vx(e5Q,1),eU4,175,0,[tdd]))))),K_(e,eVx,eVk,thY),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVk),eZ_),"Layout Partitioning"),"Whether to activate partitioned layout. This will allow to group nodes through the Layout Partition option. a pair of nodes with different partition indices is then placed such that the node with lower index is placed to the left of the other node (with left-to-right layout direction). Depending on the layout algorithm, this may only be guaranteed to work if all nodes have a layout partition configured, or at least if edges that cross partitions are not part of a partition-crossing cycle."),thj),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVc),eZE),"Node Label Padding"),"Define padding for node labels that are placed inside of a node."),thE),td_),e4R),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGp),eZE),"Node Label Placement"),"Hints for where node labels are to be placed; if empty, the node label's position is not modified."),thk),tdy),e6t),jL(tdd,eow(vx(e5Q,1),eU4,175,0,[tdf]))))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVd),eZS),"Port Alignment"),"Defines the default port distribution for a node. May be overridden for each side individually."),thU),tdv),e6n),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eVh),eZS),"Port Alignment (North)"),"Defines how ports on the northern side are placed, overriding the node's general port alignment."),tdv),e6n),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eVp),eZS),"Port Alignment (South)"),"Defines how ports on the southern side are placed, overriding the node's general port alignment."),tdv),e6n),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eVb),eZS),"Port Alignment (West)"),"Defines how ports on the western side are placed, overriding the node's general port alignment."),tdv),e6n),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eVm),eZS),"Port Alignment (East)"),"Defines how ports on the eastern side are placed, overriding the node's general port alignment."),tdv),e6n),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGh),eZk),"Node Size Constraints"),"What should be taken into account when calculating a node's size. Empty size constraints specify that a node's size is already fixed and should not be changed."),thT),tdy),e6o),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGd),eZk),"Node Size Options"),"Options modifying the behavior of the size constraints set on a node. Each member of the set specifies something that should be taken into account when calculating node sizes. The empty set corresponds to no further modifications."),thC),tdy),e6s),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGM),eZk),"Node Size Minimum"),"The minimal size to which a node can be reduced."),thA),td_),e50),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVs),eZk),"Fixed Graph Size"),"By default, the fixed layout provider will enlarge a graph until it is large enough to contain its children. If this option is set, it won't do so."),!1),tdm),e11),el9(tdh)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVy),eKX),"Edge Label Placement"),"Gives a hint on where to put edge labels."),thi),tdv),e56),el9(tdf)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGl),eKX),"Inline Edge Labels"),"If true, an edge label is placed directly on its edge. May only apply to center edge labels. This kind of label placement is only advisable if the label's rendering is such that it is not crossed by its edge and thus stays legible."),!1),tdm),e11),el9(tdf)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eZv),"font"),"Font Name"),"Font name used for a label."),tdE),e17),el9(tdf)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eZy),"font"),"Font Size"),"Font size used for a label."),tdw),e15),el9(tdf)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eVS),eZx),"Port Anchor Offset"),"The offset to the port position where connections shall be attached."),td_),e50),el9(tdp)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eVw),eZx),"Port Index"),"The index of a port in the fixed order around a node. The order is assumed as clockwise, starting with the leftmost port on the top side. This option must be set if 'Port Constraints' is set to FIXED_ORDER and no specific positions are given for the ports. Additionally, the option 'Port Side' must be defined in this case."),tdw),e15),el9(tdp)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVn),eZx),"Port Side"),"The side of a node on which a port is situated. This option must be set if 'Port Constraints' is set to FIXED_SIDE or FIXED_ORDER and no specific positions are given for the ports."),th2),tdv),e6a),el9(tdp)))),efO(e,new eE8(yt(ye(yn(v4(v7(v6(v9(new oN,eVe),eZx),"Port Border Offset"),"The offset of ports on the node border. With a positive offset the port is moved outside of the node, while with a negative offset the port is moved towards the inside. An offset of 0 means that the port is placed directly on the node border, i.e. if the port side is north, the port's south border touches the nodes's north border; if the port side is east, the port's west border touches the nodes's east border; if the port side is south, the port's north border touches the node's south border; if the port side is west, the port's east border touches the node's west border."),tdg),e13),el9(tdp)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eGb),eZT),"Port Label Placement"),"Decides on a placement method for port labels; if empty, the node label's position is not modified."),thQ),tdy),e6i),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVl),eZT),"Port Labels Next to Port"),"Use 'portLabels.placement': NEXT_TO_PORT_OF_POSSIBLE."),!1),tdm),e11),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVf),eZT),"Treat Port Labels as Group"),"If this option is true (default), the labels of a port will be treated as a group when it comes to centering them next to their port. If this option is false, only the first label will be centered next to the port, with the others being placed below. This only applies to labels of eastern and western ports and will have no effect if labels are not placed next to their port."),!0),tdm),e11),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVa),eZM),"Activate Inside Self Loops"),"Whether this node allows to route self loops inside of it instead of around it. If set to true, this will make the node a compound node if it isn't already, and will require the layout algorithm to support compound nodes with hierarchical ports."),!1),tdm),e11),el9(tdd)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eVo),eZM),"Inside Self Loop"),"Whether a self loop should be routed inside a node instead of around that node."),!1),tdm),e11),el9(tdl)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,ezz),"edge"),"Edge Thickness"),"The thickness of an edge. This is a hint on the line width used to draw an edge, possibly requiring more space to be reserved for it."),1),tdg),e13),el9(tdl)))),efO(e,new eE8(yt(ye(yn(v5(v4(v7(v6(v9(new oN,eZw),"edge"),"Edge Type"),"The type of an edge. This is usually used for UML class diagrams, where associations must be handled differently from generalizations."),thu),tdv),e58),el9(tdl)))),_B(e,new GM(v0(v3(v2(new of,eG1),"Layered"),'The layer-based method was introduced by Sugiyama, Tagawa and Toda in 1981. It emphasizes the direction of edges by pointing as many edges as possible into the same direction. The nodes are arranged in layers, which are sometimes called "hierarchies", and then reordered such that the number of edge crossings is minimized. Afterwards, concrete coordinates are computed for the nodes and edge bend points.'))),_B(e,new GM(v0(v3(v2(new of,"org.eclipse.elk.orthogonal"),"Orthogonal"),'Orthogonal methods that follow the "topology-shape-metrics" approach by Batini, Nardelli and Tamassia \'86. The first phase determines the topology of the drawing by applying a planarization technique, which results in a planar representation of the graph. The orthogonal shape is computed in the second phase, which aims at minimizing the number of edge bends, and is called orthogonalization. The third phase leads to concrete coordinates for nodes and edge bend points by applying a compaction method, thus defining the metrics.'))),_B(e,new GM(v0(v3(v2(new of,eGn),"Force"),"Layout algorithms that follow physical analogies by simulating a system of attractive and repulsive forces. The first successful method of this kind was proposed by Eades in 1984."))),_B(e,new GM(v0(v3(v2(new of,"org.eclipse.elk.circle"),"Circle"),"Circular layout algorithms emphasize cycles or biconnected components of a graph by arranging them in circles. This is useful if a drawing is desired where such components are clearly grouped, or where cycles are shown as prominent OPTIONS of the graph."))),_B(e,new GM(v0(v3(v2(new of,eV9),"Tree"),"Specialized layout methods for trees, i.e. acyclic graphs. The regular structure of graphs that have no undirected cycles can be emphasized using an algorithm of this type."))),_B(e,new GM(v0(v3(v2(new of,"org.eclipse.elk.planar"),"Planar"),"Algorithms that require a planar or upward planar graph. Most of these algorithms are theoretically interesting, but not practically usable."))),_B(e,new GM(v0(v3(v2(new of,eqp),"Radial"),"Radial layout algorithms usually position the nodes of the graph on concentric circles."))),eIm((new cX,e)),eDj((new cq,e)),eL6((new cJ,e))},Y5(eZe,"CoreOptions",684),eTS(103,22,{3:1,35:1,22:1,103:1},kh);var e55=enw(eZe,ezw,103,e1G,Zh,NY);eTS(272,22,{3:1,35:1,22:1,272:1},kp);var e56=enw(eZe,"EdgeLabelPlacement",272,e1G,Wp,NB);eTS(218,22,{3:1,35:1,22:1,218:1},kb);var e59=enw(eZe,"EdgeRouting",218,e1G,VC,NU);eTS(312,22,{3:1,35:1,22:1,312:1},km);var e58=enw(eZe,"EdgeType",312,e1G,Jx,NH);eTS(977,1,e$2,cX),eUe.Qe=function(e){eIm(e)},Y5(eZe,"FixedLayouterOptions",977),eTS(978,1,{},o$),eUe.$e=function(){return new oR},eUe._e=function(e){},Y5(eZe,"FixedLayouterOptions/FixedFactory",978),eTS(334,22,{3:1,35:1,22:1,334:1},kg);var e57=enw(eZe,"HierarchyHandling",334,e1G,Wh,N$);eTS(285,22,{3:1,35:1,22:1,285:1},kv);var e6e=enw(eZe,"LabelSide",285,e1G,VL,Nz);eTS(93,22,{3:1,35:1,22:1,93:1},ky);var e6t=enw(eZe,"NodeLabelPlacement",93,e1G,ene,NG);eTS(249,22,{3:1,35:1,22:1,249:1},kw);var e6n=enw(eZe,"PortAlignment",249,e1G,Zp,NW);eTS(98,22,{3:1,35:1,22:1,98:1},k_);var e6r=enw(eZe,"PortConstraints",98,e1G,X0,NK);eTS(273,22,{3:1,35:1,22:1,273:1},kE);var e6i=enw(eZe,"PortLabelPlacement",273,e1G,Jk,NV);eTS(61,22,{3:1,35:1,22:1,61:1},kS);var e6a=enw(eZe,"PortSide",61,e1G,q5,NX);eTS(981,1,e$2,cJ),eUe.Qe=function(e){eL6(e)},Y5(eZe,"RandomLayouterOptions",981),eTS(982,1,{},oz),eUe.$e=function(){return new oV},eUe._e=function(e){},Y5(eZe,"RandomLayouterOptions/RandomFactory",982),eTS(374,22,{3:1,35:1,22:1,374:1},kk);var e6o=enw(eZe,"SizeConstraint",374,e1G,VA,Nq);eTS(259,22,{3:1,35:1,22:1,259:1},kx);var e6s=enw(eZe,"SizeOptions",259,e1G,en2,NZ);eTS(370,1,{1949:1},mV),eUe.b=!1,eUe.c=0,eUe.d=-1,eUe.e=null,eUe.f=null,eUe.g=-1,eUe.j=!1,eUe.k=!1,eUe.n=!1,eUe.o=0,eUe.q=0,eUe.r=0,Y5(eVL,"BasicProgressMonitor",370),eTS(972,209,ezL,oF),eUe.Ze=function(e,t){var n,r,i,a,o,s,u,c,l;(ewG(t,"Box layout",2),i=gR(LV(eT8(e,(e_C(),tdG)))),a=Pp(eT8(e,tdH),116),n=gN(LK(eT8(e,tdj))),r=gN(LK(eT8(e,tdF))),0===Pp(eT8(e,tdP),311).g)?(o=(s=new I4((e.a||(e.a=new FQ(e6k,e,10,11)),e.a)),Hj(),Mv(s,new h3(r)),s),u=eSI(e),(null==(c=LV(eT8(e,tdN)))||(BJ(c),c<=0))&&(c=1.3),l=eYA(o,i,a,u.a,u.b,n,(BJ(c),c)),eYx(e,l.a,l.b,!1,!0)):eRF(e,i,a,n),eEj(t)},Y5(eVL,"BoxLayoutProvider",972),eTS(973,1,e$C,h3),eUe.ue=function(e,t){return eOQ(this,Pp(e,33),Pp(t,33))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},eUe.a=!1,Y5(eVL,"BoxLayoutProvider/1",973),eTS(157,1,{157:1},etD,Lp),eUe.Ib=function(){return this.c?eC4(this.c):e_F(this.b)},Y5(eVL,"BoxLayoutProvider/Group",157),eTS(311,22,{3:1,35:1,22:1,311:1},kT);var e6u=enw(eVL,"BoxLayoutProvider/PackingMode",311,e1G,VI,NJ);eTS(974,1,e$C,oY),eUe.ue=function(e,t){return HK(Pp(e,157),Pp(t,157))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eVL,"BoxLayoutProvider/lambda$0$Type",974),eTS(975,1,e$C,oB),eUe.ue=function(e,t){return Hm(Pp(e,157),Pp(t,157))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eVL,"BoxLayoutProvider/lambda$1$Type",975),eTS(976,1,e$C,oU),eUe.ue=function(e,t){return Hg(Pp(e,157),Pp(t,157))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eVL,"BoxLayoutProvider/lambda$2$Type",976),eTS(1365,1,{831:1},oH),eUe.qg=function(e,t){return _R(),!M4(t,160)||yX((eoM(),Pp(e,160)),t)},Y5(eVL,"ElkSpacings/AbstractSpacingsBuilder/lambda$0$Type",1365),eTS(1366,1,eUF,h4),eUe.td=function(e){eux(this.a,Pp(e,146))},Y5(eVL,"ElkSpacings/AbstractSpacingsBuilder/lambda$1$Type",1366),eTS(1367,1,eUF,oj),eUe.td=function(e){Pp(e,94),_R()},Y5(eVL,"ElkSpacings/AbstractSpacingsBuilder/lambda$2$Type",1367),eTS(1371,1,eUF,h5),eUe.td=function(e){erQ(this.a,Pp(e,94))},Y5(eVL,"ElkSpacings/AbstractSpacingsBuilder/lambda$3$Type",1371),eTS(1369,1,eU8,kM),eUe.Mb=function(e){return esI(this.a,this.b,Pp(e,146))},Y5(eVL,"ElkSpacings/AbstractSpacingsBuilder/lambda$4$Type",1369),eTS(1368,1,eU8,kO),eUe.Mb=function(e){return Lt(this.a,this.b,Pp(e,831))},Y5(eVL,"ElkSpacings/AbstractSpacingsBuilder/lambda$5$Type",1368),eTS(1370,1,eUF,kA),eUe.td=function(e){Fj(this.a,this.b,Pp(e,146))},Y5(eVL,"ElkSpacings/AbstractSpacingsBuilder/lambda$6$Type",1370),eTS(935,1,{},oP),eUe.Kb=function(e){return TA(e)},eUe.Fb=function(e){return this===e},Y5(eVL,"ElkUtil/lambda$0$Type",935),eTS(936,1,eUF,kL),eUe.td=function(e){exS(this.a,this.b,Pp(e,79))},eUe.a=0,eUe.b=0,Y5(eVL,"ElkUtil/lambda$1$Type",936),eTS(937,1,eUF,kC),eUe.td=function(e){gp(this.a,this.b,Pp(e,202))},eUe.a=0,eUe.b=0,Y5(eVL,"ElkUtil/lambda$2$Type",937),eTS(938,1,eUF,kI),eUe.td=function(e){Me(this.a,this.b,Pp(e,137))},eUe.a=0,eUe.b=0,Y5(eVL,"ElkUtil/lambda$3$Type",938),eTS(939,1,eUF,h6),eUe.td=function(e){RE(this.a,Pp(e,469))},Y5(eVL,"ElkUtil/lambda$4$Type",939),eTS(342,1,{35:1,342:1},pQ),eUe.wd=function(e){return Os(this,Pp(e,236))},eUe.Fb=function(e){var t;return!!M4(e,342)&&(t=Pp(e,342),this.a==t.a)},eUe.Hb=function(){return zy(this.a)},eUe.Ib=function(){return this.a+" (exclusive)"},eUe.a=0,Y5(eVL,"ExclusiveBounds/ExclusiveLowerBound",342),eTS(1138,209,ezL,oR),eUe.Ze=function(e,t){var n,r,i,a,o,s,u,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x;for(ewG(t,"Fixed Layout",1),a=Pp(eT8(e,(eBB(),tha)),218),d=0,h=0,y=new Ow((e.a||(e.a=new FQ(e6k,e,10,11)),e.a));y.e!=y.i.gc();){for(g=Pp(epH(y),33),(x=Pp(eT8(g,(euw(),tp$)),8))&&(TP(g,x.a,x.b),Pp(eT8(g,tpF),174).Hc((ed6(),tbW))&&(p=Pp(eT8(g,tpB),8)).a>0&&p.b>0&&eYx(g,p.a,p.b,!0,!0)),d=eB4.Math.max(d,g.i+g.g),h=eB4.Math.max(h,g.j+g.f),l=new Ow((g.n||(g.n=new FQ(e6S,g,1,7)),g.n));l.e!=l.i.gc();)s=Pp(epH(l),137),(x=Pp(eT8(s,tp$),8))&&TP(s,x.a,x.b),d=eB4.Math.max(d,g.i+s.i+s.g),h=eB4.Math.max(h,g.j+s.j+s.f);for(E=new Ow((g.c||(g.c=new FQ(e6x,g,9,9)),g.c));E.e!=E.i.gc();)for(_=Pp(epH(E),118),(x=Pp(eT8(_,tp$),8))&&TP(_,x.a,x.b),S=g.i+_.i,k=g.j+_.j,d=eB4.Math.max(d,S+_.g),h=eB4.Math.max(h,k+_.f),u=new Ow((_.n||(_.n=new FQ(e6S,_,1,7)),_.n));u.e!=u.i.gc();)s=Pp(epH(u),137),(x=Pp(eT8(s,tp$),8))&&TP(s,x.a,x.b),d=eB4.Math.max(d,S+s.i+s.g),h=eB4.Math.max(h,k+s.j+s.f);for(i=new Fa(OH(eOi(g).a.Kc(),new c));eTk(i);)n=Pp(ZC(i),79),f=eYT(n),d=eB4.Math.max(d,f.a),h=eB4.Math.max(h,f.b);for(r=new Fa(OH(eOr(g).a.Kc(),new c));eTk(r);)n=Pp(ZC(r),79),z$(e_I(n))!=e&&(f=eYT(n),d=eB4.Math.max(d,f.a),h=eB4.Math.max(h,f.b))}if(a==(efE(),tpx))for(v=new Ow((e.a||(e.a=new FQ(e6k,e,10,11)),e.a));v.e!=v.i.gc();)for(g=Pp(epH(v),33),r=new Fa(OH(eOi(g).a.Kc(),new c));eTk(r);)n=Pp(ZC(r),79),0==(o=eDX(n)).b?ebu(n,thg,null):ebu(n,thg,o);gN(LK(eT8(e,(euw(),tpY))))||(w=Pp(eT8(e,tpU),116),eYx(e,m=d+w.b+w.c,b=h+w.d+w.a,!0,!0)),eEj(t)},Y5(eVL,"FixedLayoutProvider",1138),eTS(373,134,{3:1,414:1,373:1,94:1,134:1},oG,eer),eUe.Jf=function(e){var t,n,r,i,a,o,s,u,c;if(e)try{for(a=u=eIk(e,";,;"),o=0,s=a.length;o>16&eHd|t^r<<16},eUe.Kc=function(){return new h9(this)},eUe.Ib=function(){return null==this.a&&null==this.b?"pair(null,null)":null==this.a?"pair(null,"+efF(this.b)+")":null==this.b?"pair("+efF(this.a)+",null)":"pair("+efF(this.a)+","+efF(this.b)+")"},Y5(eVL,"Pair",46),eTS(983,1,eUE,h9),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return!this.c&&(!this.b&&null!=this.a.a||null!=this.a.b)},eUe.Pb=function(){if(!this.c&&!this.b&&null!=this.a.a)return this.b=!0,this.a.a;if(!this.c&&null!=this.a.b)return this.c=!0,this.a.b;throw p7(new bC)},eUe.Qb=function(){throw this.c&&null!=this.a.b?this.a.b=null:this.b&&null!=this.a.a&&(this.a.a=null),p7(new bT)},eUe.b=!1,eUe.c=!1,Y5(eVL,"Pair/1",983),eTS(448,1,{448:1},Ho),eUe.Fb=function(e){return UT(this.a,Pp(e,448).a)&&UT(this.c,Pp(e,448).c)&&UT(this.d,Pp(e,448).d)&&UT(this.b,Pp(e,448).b)},eUe.Hb=function(){return euF(eow(vx(e1R,1),eUp,1,5,[this.a,this.c,this.d,this.b]))},eUe.Ib=function(){return"("+this.a+eUd+this.c+eUd+this.d+eUd+this.b+")"},Y5(eVL,"Quadruple",448),eTS(1126,209,ezL,oV),eUe.Ze=function(e,t){var n,r,i,a,o;if(ewG(t,"Random Layout",1),0==(e.a||(e.a=new FQ(e6k,e,10,11)),e.a).i){eEj(t);return}i=(a=Pp(eT8(e,(ed5(),tbz)),19))&&0!=a.a?new qS(a.a):new efo,n=gR(LV(eT8(e,tbU))),o=gR(LV(eT8(e,tbG))),r=Pp(eT8(e,tbH),116),eF1(e,i,n,o,r),eEj(t)},Y5(eVL,"RandomLayoutProvider",1126),eTS(553,1,{}),eUe.qf=function(){return new kl(this.f.i,this.f.j)},eUe.We=function(e){return $k(e,(eBB(),thK))?eT8(this.f,tmu):eT8(this.f,e)},eUe.rf=function(){return new kl(this.f.g,this.f.f)},eUe.sf=function(){return this.g},eUe.Xe=function(e){return X2(this.f,e)},eUe.tf=function(e){eno(this.f,e.a),ens(this.f,e.b)},eUe.uf=function(e){ena(this.f,e.a),eni(this.f,e.b)},eUe.vf=function(e){this.g=e},eUe.g=0,Y5(eZI,"ElkGraphAdapters/AbstractElkGraphElementAdapter",553),eTS(554,1,{839:1},h8),eUe.wf=function(){var e,t;if(!this.b)for(this.b=K$(UB(this.a).i),t=new Ow(UB(this.a));t.e!=t.i.gc();)e=Pp(epH(t),137),P_(this.b,new gO(e));return this.b},eUe.b=null,Y5(eZI,"ElkGraphAdapters/ElkEdgeAdapter",554),eTS(301,553,{},gM),eUe.xf=function(){return em3(this)},eUe.a=null,Y5(eZI,"ElkGraphAdapters/ElkGraphAdapter",301),eTS(630,553,{181:1},gO),Y5(eZI,"ElkGraphAdapters/ElkLabelAdapter",630),eTS(629,553,{680:1},AC),eUe.wf=function(){return em0(this)},eUe.Af=function(){var e;return(e=Pp(eT8(this.f,(eBB(),thy)),142))||(e=new mh),e},eUe.Cf=function(){return em2(this)},eUe.Ef=function(e){var t;t=new Dk(e),ebu(this.f,(eBB(),thy),t)},eUe.Ff=function(e){ebu(this.f,(eBB(),thN),new DS(e))},eUe.yf=function(){return this.d},eUe.zf=function(){var e,t;if(!this.a)for(this.a=new p0,t=new Fa(OH(eOr(Pp(this.f,33)).a.Kc(),new c));eTk(t);)e=Pp(ZC(t),79),P_(this.a,new h8(e));return this.a},eUe.Bf=function(){var e,t;if(!this.c)for(this.c=new p0,t=new Fa(OH(eOi(Pp(this.f,33)).a.Kc(),new c));eTk(t);)e=Pp(ZC(t),79),P_(this.c,new h8(e));return this.c},eUe.Df=function(){return 0!=H8(Pp(this.f,33)).i||gN(LK(Pp(this.f,33).We((eBB(),thh))))},eUe.Gf=function(){QV(this,(_q(),tms))},eUe.a=null,eUe.b=null,eUe.c=null,eUe.d=null,eUe.e=null,Y5(eZI,"ElkGraphAdapters/ElkNodeAdapter",629),eTS(1266,553,{838:1},pA),eUe.wf=function(){return egd(this)},eUe.zf=function(){var e,t;if(!this.a)for(this.a=AH(Pp(this.f,118).xg().i),t=new Ow(Pp(this.f,118).xg());t.e!=t.i.gc();)e=Pp(epH(t),79),P_(this.a,new h8(e));return this.a},eUe.Bf=function(){var e,t;if(!this.c)for(this.c=AH(Pp(this.f,118).yg().i),t=new Ow(Pp(this.f,118).yg());t.e!=t.i.gc();)e=Pp(epH(t),79),P_(this.c,new h8(e));return this.c},eUe.Hf=function(){return Pp(Pp(this.f,118).We((eBB(),th0)),61)},eUe.If=function(){var e,t,n,r,i,a,o,s;for(r=zY(Pp(this.f,118)),n=new Ow(Pp(this.f,118).yg());n.e!=n.i.gc();)for(e=Pp(epH(n),79),s=new Ow((e.c||(e.c=new Ih(e6m,e,5,8)),e.c));s.e!=s.i.gc();)if(o=Pp(epH(s),82),etg(ewH(o),r)||ewH(o)==r&&gN(LK(eT8(e,(eBB(),thp)))))return!0;for(t=new Ow(Pp(this.f,118).xg());t.e!=t.i.gc();)for(e=Pp(epH(t),79),a=new Ow((e.b||(e.b=new Ih(e6m,e,4,7)),e.b));a.e!=a.i.gc();)if(i=Pp(epH(a),82),etg(ewH(i),r))return!0;return!1},eUe.a=null,eUe.b=null,eUe.c=null,Y5(eZI,"ElkGraphAdapters/ElkPortAdapter",1266),eTS(1267,1,e$C,oq),eUe.ue=function(e,t){return eC3(Pp(e,118),Pp(t,118))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(eZI,"ElkGraphAdapters/PortComparator",1267);var e6f=RL(eZD,"EObject"),e6d=RL(eZN,eZP),e6h=RL(eZN,eZR),e6p=RL(eZN,eZj),e6b=RL(eZN,"ElkShape"),e6m=RL(eZN,eZF),e6g=RL(eZN,eZY),e6v=RL(eZN,eZB),e6y=RL(eZD,eZU),e6w=RL(eZD,"EFactory"),e6_=RL(eZD,eZH),e6E=RL(eZD,"EPackage"),e6S=RL(eZN,eZ$),e6k=RL(eZN,eZz),e6x=RL(eZN,eZG);eTS(90,1,eZW),eUe.Jg=function(){return this.Kg(),null},eUe.Kg=function(){return null},eUe.Lg=function(){return this.Kg(),!1},eUe.Mg=function(){return!1},eUe.Ng=function(e){eam(this,e)},Y5(eZK,"BasicNotifierImpl",90),eTS(97,90,eZ0),eUe.nh=function(){return TO(this)},eUe.Og=function(e,t){return e},eUe.Pg=function(){throw p7(new bO)},eUe.Qg=function(e){var t;return t=ebY(Pp(ee2(this.Tg(),this.Vg()),18)),this.eh().ih(this,t.n,t.f,e)},eUe.Rg=function(e,t){throw p7(new bO)},eUe.Sg=function(e,t,n){return eDg(this,e,t,n)},eUe.Tg=function(){var e;return this.Pg()&&(e=this.Pg().ck())?e:this.zh()},eUe.Ug=function(){return eTp(this)},eUe.Vg=function(){throw p7(new bO)},eUe.Wg=function(){var e,t;return(t=this.ph().dk())||this.Pg().ik(t=(_0(),null==(e=zr(eNT(this.Tg())))?tgV:new AA(this,e))),t},eUe.Xg=function(e,t){return e},eUe.Yg=function(e){var t;return(t=e.Gj())?e.aj():edv(this.Tg(),e)},eUe.Zg=function(){var e;return(e=this.Pg())?e.fk():null},eUe.$g=function(){return this.Pg()?this.Pg().ck():null},eUe._g=function(e,t,n){return ebl(this,e,t,n)},eUe.ah=function(e){return JG(this,e)},eUe.bh=function(e,t){return ZN(this,e,t)},eUe.dh=function(){var e;return!!(e=this.Pg())&&e.gk()},eUe.eh=function(){throw p7(new bO)},eUe.fh=function(){return ehO(this)},eUe.gh=function(e,t,n,r){return ep0(this,e,t,r)},eUe.hh=function(e,t,n){var r;return(r=Pp(ee2(this.Tg(),t),66)).Nj().Qj(this,this.yh(),t-this.Ah(),e,n)},eUe.ih=function(e,t,n,r){return $7(this,e,t,r)},eUe.jh=function(e,t,n){var r;return(r=Pp(ee2(this.Tg(),t),66)).Nj().Rj(this,this.yh(),t-this.Ah(),e,n)},eUe.kh=function(){return!!this.Pg()&&!!this.Pg().ek()},eUe.lh=function(e){return epY(this,e)},eUe.mh=function(e){return zz(this,e)},eUe.oh=function(e){return eR2(this,e)},eUe.ph=function(){throw p7(new bO)},eUe.qh=function(){return this.Pg()?this.Pg().ek():null},eUe.rh=function(){return ehO(this)},eUe.sh=function(e,t){eS5(this,e,t)},eUe.th=function(e){this.ph().hk(e)},eUe.uh=function(e){this.ph().kk(e)},eUe.vh=function(e){this.ph().jk(e)},eUe.wh=function(e,t){var n,r,i,a;return(a=this.Zg())&&e&&(t=ep6(a.Vk(),this,t),a.Zk(this)),(r=this.eh())&&((eIy(this,this.eh(),this.Vg()).Bb&eH3)!=0?(i=r.fh())&&(e?a||i.Zk(this):i.Yk(this)):(t=(n=this.Vg())>=0?this.Qg(t):this.eh().ih(this,-1-n,null,t),t=this.Sg(null,-1,t))),this.uh(e),t},eUe.xh=function(e){var t,n,r,i,a,o,s,u;if((a=edv(n=this.Tg(),e))>=(t=this.Ah()))return Pp(e,66).Nj().Uj(this,this.yh(),a-t);if(a<=-1){if(o=eR3((eSp(),tvc),n,e)){if(_4(),Pp(o,66).Oj()||(o=Wk(QZ(tvc,o))),i=Pp((r=this.Yg(o))>=0?this._g(r,!0,!0):exk(this,o,!0),153),(u=o.Zj())>1||-1==u)return Pp(Pp(i,215).hl(e,!1),76)}else throw p7(new gL(eZV+e.ne()+eZX))}else if(e.$j())return Pp((r=this.Yg(e))>=0?this._g(r,!1,!0):exk(this,e,!1),76);return new k4(this,e)},eUe.yh=function(){return Q5(this)},eUe.zh=function(){return(BM(),tgv).S},eUe.Ah=function(){return Y1(this.zh())},eUe.Bh=function(e){eSi(this,e)},eUe.Ib=function(){return eMT(this)},Y5(eZ2,"BasicEObjectImpl",97),eTS(114,97,{105:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1}),eUe.Ch=function(e){var t;return(t=Q6(this))[e]},eUe.Dh=function(e,t){var n;n=Q6(this),Bc(n,e,t)},eUe.Eh=function(e){var t;t=Q6(this),Bc(t,e,null)},eUe.Jg=function(){return Pp(eaS(this,4),126)},eUe.Kg=function(){throw p7(new bO)},eUe.Lg=function(){return(4&this.Db)!=0},eUe.Pg=function(){throw p7(new bO)},eUe.Fh=function(e){ehU(this,2,e)},eUe.Rg=function(e,t){this.Db=t<<16|255&this.Db,this.Fh(e)},eUe.Tg=function(){return $S(this)},eUe.Vg=function(){return this.Db>>16},eUe.Wg=function(){var e,t;return _0(),null==(t=zr(eNT((e=Pp(eaS(this,16),26))||this.zh())))?tgV:new AA(this,t)},eUe.Mg=function(){return(1&this.Db)==0},eUe.Zg=function(){return Pp(eaS(this,128),1935)},eUe.$g=function(){return Pp(eaS(this,16),26)},eUe.dh=function(){return(32&this.Db)!=0},eUe.eh=function(){return Pp(eaS(this,2),49)},eUe.kh=function(){return(64&this.Db)!=0},eUe.ph=function(){throw p7(new bO)},eUe.qh=function(){return Pp(eaS(this,64),281)},eUe.th=function(e){ehU(this,16,e)},eUe.uh=function(e){ehU(this,128,e)},eUe.vh=function(e){ehU(this,64,e)},eUe.yh=function(){return ehH(this)},eUe.Db=0,Y5(eZ2,"MinimalEObjectImpl",114),eTS(115,114,{105:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1}),eUe.Fh=function(e){this.Cb=e},eUe.eh=function(){return this.Cb},Y5(eZ2,"MinimalEObjectImpl/Container",115),eTS(1985,115,{105:1,413:1,94:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1}),eUe._g=function(e,t,n){return egp(this,e,t,n)},eUe.jh=function(e,t,n){return e_9(this,e,t,n)},eUe.lh=function(e){return Wz(this,e)},eUe.sh=function(e,t){esU(this,e,t)},eUe.zh=function(){return eBa(),tm_},eUe.Bh=function(e){eoF(this,e)},eUe.Ve=function(){return epD(this)},eUe.We=function(e){return eT8(this,e)},eUe.Xe=function(e){return X2(this,e)},eUe.Ye=function(e,t){return ebu(this,e,t)},Y5(eZ3,"EMapPropertyHolderImpl",1985),eTS(567,115,{105:1,469:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1},oJ),eUe._g=function(e,t,n){switch(e){case 0:return this.a;case 1:return this.b}return ebl(this,e,t,n)},eUe.lh=function(e){switch(e){case 0:return 0!=this.a;case 1:return 0!=this.b}return epY(this,e)},eUe.sh=function(e,t){switch(e){case 0:ent(this,gP(LV(t)));return;case 1:enn(this,gP(LV(t)));return}eS5(this,e,t)},eUe.zh=function(){return eBa(),tmf},eUe.Bh=function(e){switch(e){case 0:ent(this,0);return;case 1:enn(this,0);return}eSi(this,e)},eUe.Ib=function(){var e;return(64&this.Db)!=0?eMT(this):(e=new O1(eMT(this)),e.a+=" (x: ",y$(e,this.a),e.a+=", y: ",y$(e,this.b),e.a+=")",e.a)},eUe.a=0,eUe.b=0,Y5(eZ3,"ElkBendPointImpl",567),eTS(723,1985,{105:1,413:1,160:1,94:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1}),eUe._g=function(e,t,n){return ec2(this,e,t,n)},eUe.hh=function(e,t,n){return ew0(this,e,t,n)},eUe.jh=function(e,t,n){return ea9(this,e,t,n)},eUe.lh=function(e){return eaT(this,e)},eUe.sh=function(e,t){eyb(this,e,t)},eUe.zh=function(){return eBa(),tmb},eUe.Bh=function(e){ecx(this,e)},eUe.zg=function(){return this.k},eUe.Ag=function(){return UB(this)},eUe.Ib=function(){return el4(this)},eUe.k=null,Y5(eZ3,"ElkGraphElementImpl",723),eTS(724,723,{105:1,413:1,160:1,470:1,94:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1}),eUe._g=function(e,t,n){return efN(this,e,t,n)},eUe.lh=function(e){return ef8(this,e)},eUe.sh=function(e,t){eym(this,e,t)},eUe.zh=function(){return eBa(),tmw},eUe.Bh=function(e){edS(this,e)},eUe.Bg=function(){return this.f},eUe.Cg=function(){return this.g},eUe.Dg=function(){return this.i},eUe.Eg=function(){return this.j},eUe.Fg=function(e,t){TN(this,e,t)},eUe.Gg=function(e,t){TP(this,e,t)},eUe.Hg=function(e){eno(this,e)},eUe.Ig=function(e){ens(this,e)},eUe.Ib=function(){return eEp(this)},eUe.f=0,eUe.g=0,eUe.i=0,eUe.j=0,Y5(eZ3,"ElkShapeImpl",724),eTS(725,724,{105:1,413:1,82:1,160:1,470:1,94:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1}),eUe._g=function(e,t,n){return ebQ(this,e,t,n)},eUe.hh=function(e,t,n){return evZ(this,e,t,n)},eUe.jh=function(e,t,n){return evX(this,e,t,n)},eUe.lh=function(e){return esM(this,e)},eUe.sh=function(e,t){eTH(this,e,t)},eUe.zh=function(){return eBa(),tmd},eUe.Bh=function(e){ep2(this,e)},eUe.xg=function(){return this.d||(this.d=new Ih(e6g,this,8,5)),this.d},eUe.yg=function(){return this.e||(this.e=new Ih(e6g,this,7,4)),this.e},Y5(eZ3,"ElkConnectableShapeImpl",725),eTS(352,723,{105:1,413:1,79:1,160:1,352:1,94:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1},oX),eUe.Qg=function(e){return evo(this,e)},eUe._g=function(e,t,n){switch(e){case 3:return zF(this);case 4:return this.b||(this.b=new Ih(e6m,this,4,7)),this.b;case 5:return this.c||(this.c=new Ih(e6m,this,5,8)),this.c;case 6:return this.a||(this.a=new FQ(e6v,this,6,6)),this.a;case 7:return OQ(),this.b||(this.b=new Ih(e6m,this,4,7)),!(this.b.i<=1)||(this.c||(this.c=new Ih(e6m,this,5,8)),!(this.c.i<=1));case 8:return OQ(),!!eTc(this);case 9:return OQ(),!!exb(this);case 10:return OQ(),this.b||(this.b=new Ih(e6m,this,4,7)),0!=this.b.i&&(this.c||(this.c=new Ih(e6m,this,5,8)),0!=this.c.i)}return ec2(this,e,t,n)},eUe.hh=function(e,t,n){var r;switch(t){case 3:return this.Cb&&(n=(r=this.Db>>16)>=0?evo(this,n):this.Cb.ih(this,-1-r,null,n)),Cu(this,Pp(e,33),n);case 4:return this.b||(this.b=new Ih(e6m,this,4,7)),edF(this.b,e,n);case 5:return this.c||(this.c=new Ih(e6m,this,5,8)),edF(this.c,e,n);case 6:return this.a||(this.a=new FQ(e6v,this,6,6)),edF(this.a,e,n)}return ew0(this,e,t,n)},eUe.jh=function(e,t,n){switch(t){case 3:return Cu(this,null,n);case 4:return this.b||(this.b=new Ih(e6m,this,4,7)),ep6(this.b,e,n);case 5:return this.c||(this.c=new Ih(e6m,this,5,8)),ep6(this.c,e,n);case 6:return this.a||(this.a=new FQ(e6v,this,6,6)),ep6(this.a,e,n)}return ea9(this,e,t,n)},eUe.lh=function(e){switch(e){case 3:return!!zF(this);case 4:return!!this.b&&0!=this.b.i;case 5:return!!this.c&&0!=this.c.i;case 6:return!!this.a&&0!=this.a.i;case 7:return this.b||(this.b=new Ih(e6m,this,4,7)),!(this.b.i<=1&&(this.c||(this.c=new Ih(e6m,this,5,8)),this.c.i<=1));case 8:return eTc(this);case 9:return exb(this);case 10:return this.b||(this.b=new Ih(e6m,this,4,7)),0!=this.b.i&&(this.c||(this.c=new Ih(e6m,this,5,8)),0!=this.c.i)}return eaT(this,e)},eUe.sh=function(e,t){switch(e){case 3:eOC(this,Pp(t,33));return;case 4:this.b||(this.b=new Ih(e6m,this,4,7)),eRT(this.b),this.b||(this.b=new Ih(e6m,this,4,7)),Y4(this.b,Pp(t,14));return;case 5:this.c||(this.c=new Ih(e6m,this,5,8)),eRT(this.c),this.c||(this.c=new Ih(e6m,this,5,8)),Y4(this.c,Pp(t,14));return;case 6:this.a||(this.a=new FQ(e6v,this,6,6)),eRT(this.a),this.a||(this.a=new FQ(e6v,this,6,6)),Y4(this.a,Pp(t,14));return}eyb(this,e,t)},eUe.zh=function(){return eBa(),tmh},eUe.Bh=function(e){switch(e){case 3:eOC(this,null);return;case 4:this.b||(this.b=new Ih(e6m,this,4,7)),eRT(this.b);return;case 5:this.c||(this.c=new Ih(e6m,this,5,8)),eRT(this.c);return;case 6:this.a||(this.a=new FQ(e6v,this,6,6)),eRT(this.a);return}ecx(this,e)},eUe.Ib=function(){return ePY(this)},Y5(eZ3,"ElkEdgeImpl",352),eTS(439,1985,{105:1,413:1,202:1,439:1,94:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1},oQ),eUe.Qg=function(e){return eg1(this,e)},eUe._g=function(e,t,n){switch(e){case 1:return this.j;case 2:return this.k;case 3:return this.b;case 4:return this.c;case 5:return this.a||(this.a=new O_(e6h,this,5)),this.a;case 6:return zB(this);case 7:if(t)return ebF(this);return this.i;case 8:if(t)return ebj(this);return this.f;case 9:return this.g||(this.g=new Ih(e6v,this,9,10)),this.g;case 10:return this.e||(this.e=new Ih(e6v,this,10,9)),this.e;case 11:return this.d}return egp(this,e,t,n)},eUe.hh=function(e,t,n){var r,i,a;switch(t){case 6:return this.Cb&&(n=(i=this.Db>>16)>=0?eg1(this,n):this.Cb.ih(this,-1-i,null,n)),Cc(this,Pp(e,79),n);case 9:return this.g||(this.g=new Ih(e6v,this,9,10)),edF(this.g,e,n);case 10:return this.e||(this.e=new Ih(e6v,this,10,9)),edF(this.e,e,n)}return(a=Pp(ee2((r=Pp(eaS(this,16),26))||(eBa(),tmp),t),66)).Nj().Qj(this,ehH(this),t-Y1((eBa(),tmp)),e,n)},eUe.jh=function(e,t,n){switch(t){case 5:return this.a||(this.a=new O_(e6h,this,5)),ep6(this.a,e,n);case 6:return Cc(this,null,n);case 9:return this.g||(this.g=new Ih(e6v,this,9,10)),ep6(this.g,e,n);case 10:return this.e||(this.e=new Ih(e6v,this,10,9)),ep6(this.e,e,n)}return e_9(this,e,t,n)},eUe.lh=function(e){switch(e){case 1:return 0!=this.j;case 2:return 0!=this.k;case 3:return 0!=this.b;case 4:return 0!=this.c;case 5:return!!this.a&&0!=this.a.i;case 6:return!!zB(this);case 7:return!!this.i;case 8:return!!this.f;case 9:return!!this.g&&0!=this.g.i;case 10:return!!this.e&&0!=this.e.i;case 11:return null!=this.d}return Wz(this,e)},eUe.sh=function(e,t){switch(e){case 1:enu(this,gP(LV(t)));return;case 2:enl(this,gP(LV(t)));return;case 3:enr(this,gP(LV(t)));return;case 4:enc(this,gP(LV(t)));return;case 5:this.a||(this.a=new O_(e6h,this,5)),eRT(this.a),this.a||(this.a=new O_(e6h,this,5)),Y4(this.a,Pp(t,14));return;case 6:eOA(this,Pp(t,79));return;case 7:err(this,Pp(t,82));return;case 8:ern(this,Pp(t,82));return;case 9:this.g||(this.g=new Ih(e6v,this,9,10)),eRT(this.g),this.g||(this.g=new Ih(e6v,this,9,10)),Y4(this.g,Pp(t,14));return;case 10:this.e||(this.e=new Ih(e6v,this,10,9)),eRT(this.e),this.e||(this.e=new Ih(e6v,this,10,9)),Y4(this.e,Pp(t,14));return;case 11:erO(this,Lq(t));return}esU(this,e,t)},eUe.zh=function(){return eBa(),tmp},eUe.Bh=function(e){switch(e){case 1:enu(this,0);return;case 2:enl(this,0);return;case 3:enr(this,0);return;case 4:enc(this,0);return;case 5:this.a||(this.a=new O_(e6h,this,5)),eRT(this.a);return;case 6:eOA(this,null);return;case 7:err(this,null);return;case 8:ern(this,null);return;case 9:this.g||(this.g=new Ih(e6v,this,9,10)),eRT(this.g);return;case 10:this.e||(this.e=new Ih(e6v,this,10,9)),eRT(this.e);return;case 11:erO(this,null);return}eoF(this,e)},eUe.Ib=function(){return ex2(this)},eUe.b=0,eUe.c=0,eUe.d=null,eUe.j=0,eUe.k=0,Y5(eZ3,"ElkEdgeSectionImpl",439),eTS(150,115,{105:1,92:1,90:1,147:1,56:1,108:1,49:1,97:1,150:1,114:1,115:1}),eUe._g=function(e,t,n){var r;return 0==e?(this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab):Qt(this,e-Y1(this.zh()),ee2((r=Pp(eaS(this,16),26))||this.zh(),e),t,n)},eUe.hh=function(e,t,n){var r,i;return 0==t?(this.Ab||(this.Ab=new FQ(tm4,this,0,3)),edF(this.Ab,e,n)):(i=Pp(ee2((r=Pp(eaS(this,16),26))||this.zh(),t),66)).Nj().Qj(this,ehH(this),t-Y1(this.zh()),e,n)},eUe.jh=function(e,t,n){var r,i;return 0==t?(this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n)):(i=Pp(ee2((r=Pp(eaS(this,16),26))||this.zh(),t),66)).Nj().Rj(this,ehH(this),t-Y1(this.zh()),e,n)},eUe.lh=function(e){var t;return 0==e?!!this.Ab&&0!=this.Ab.i:VP(this,e-Y1(this.zh()),ee2((t=Pp(eaS(this,16),26))||this.zh(),e))},eUe.oh=function(e){return eF9(this,e)},eUe.sh=function(e,t){var n;if(0===e){this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return}efL(this,e-Y1(this.zh()),ee2((n=Pp(eaS(this,16),26))||this.zh(),e),t)},eUe.uh=function(e){ehU(this,128,e)},eUe.zh=function(){return eBK(),tgL},eUe.Bh=function(e){var t;if(0===e){this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return}ec6(this,e-Y1(this.zh()),ee2((t=Pp(eaS(this,16),26))||this.zh(),e))},eUe.Gh=function(){this.Bb|=1},eUe.Hh=function(e){return eDM(this,e)},eUe.Bb=0,Y5(eZ2,"EModelElementImpl",150),eTS(704,150,{105:1,92:1,90:1,471:1,147:1,56:1,108:1,49:1,97:1,150:1,114:1,115:1},cQ),eUe.Ih=function(e,t){return ejZ(this,e,t)},eUe.Jh=function(e){var t,n,r,i,a;if(this.a!=etP(e)||(256&e.Bb)!=0)throw p7(new gL(eZ7+e.zb+eZ6));for(r=$E(e);0!=qt(r.a).i;){if(n=Pp(ejc(r,0,(a=(t=Pp(etj(qt(r.a),0),87)).c,M4(a,88)?Pp(a,26):(eBK(),tgI))),26),em4(n))return i=etP(n).Nh().Jh(n),Pp(i,49).th(e),i;r=$E(n)}return(null!=e.D?e.D:e.B)=="java.util.Map$Entry"?new RO(e):new Pq(e)},eUe.Kh=function(e,t){return eBd(this,e,t)},eUe._g=function(e,t,n){var r;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.a}return Qt(this,e-Y1((eBK(),tgM)),ee2((r=Pp(eaS(this,16),26))||tgM,e),t,n)},eUe.hh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),edF(this.Ab,e,n);case 1:return this.a&&(n=Pp(this.a,49).ih(this,4,e6E,n)),ecb(this,Pp(e,235),n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgM),t),66)).Nj().Qj(this,ehH(this),t-Y1((eBK(),tgM)),e,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 1:return ecb(this,null,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgM),t),66)).Nj().Rj(this,ehH(this),t-Y1((eBK(),tgM)),e,n)},eUe.lh=function(e){var t;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return!!this.a}return VP(this,e-Y1((eBK(),tgM)),ee2((t=Pp(eaS(this,16),26))||tgM,e))},eUe.sh=function(e,t){var n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:e_B(this,Pp(t,235));return}efL(this,e-Y1((eBK(),tgM)),ee2((n=Pp(eaS(this,16),26))||tgM,e),t)},eUe.zh=function(){return eBK(),tgM},eUe.Bh=function(e){var t;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:e_B(this,null);return}ec6(this,e-Y1((eBK(),tgM)),ee2((t=Pp(eaS(this,16),26))||tgM,e))},Y5(eZ2,"EFactoryImpl",704),eTS(eXt,704,{105:1,2014:1,92:1,90:1,471:1,147:1,56:1,108:1,49:1,97:1,150:1,114:1,115:1},o1),eUe.Ih=function(e,t){switch(e.yj()){case 12:return Pp(t,146).tg();case 13:return efF(t);default:throw p7(new gL(eZ5+e.ne()+eZ6))}},eUe.Jh=function(e){var t;switch(-1==e.G&&(e.G=(t=etP(e))?ebv(t.Mh(),e):-1),e.G){case 4:return new o0;case 6:return new mS;case 7:return new mk;case 8:return new oX;case 9:return new oJ;case 10:return new oQ;case 11:return new o3;default:throw p7(new gL(eZ7+e.zb+eZ6))}},eUe.Kh=function(e,t){switch(e.yj()){case 13:case 12:return null;default:throw p7(new gL(eZ5+e.ne()+eZ6))}},Y5(eZ3,"ElkGraphFactoryImpl",eXt),eTS(438,150,{105:1,92:1,90:1,147:1,191:1,56:1,108:1,49:1,97:1,150:1,114:1,115:1}),eUe.Wg=function(){var e,t;return null==(t=zr(eNT((e=Pp(eaS(this,16),26))||this.zh())))?(_0(),_0(),tgV):new Lg(this,t)},eUe._g=function(e,t,n){var r;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.ne()}return Qt(this,e-Y1(this.zh()),ee2((r=Pp(eaS(this,16),26))||this.zh(),e),t,n)},eUe.lh=function(e){var t;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb}return VP(this,e-Y1(this.zh()),ee2((t=Pp(eaS(this,16),26))||this.zh(),e))},eUe.sh=function(e,t){var n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:this.Lh(Lq(t));return}efL(this,e-Y1(this.zh()),ee2((n=Pp(eaS(this,16),26))||this.zh(),e),t)},eUe.zh=function(){return eBK(),tgC},eUe.Bh=function(e){var t;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:this.Lh(null);return}ec6(this,e-Y1(this.zh()),ee2((t=Pp(eaS(this,16),26))||this.zh(),e))},eUe.ne=function(){return this.zb},eUe.Lh=function(e){er3(this,e)},eUe.Ib=function(){return ecF(this)},eUe.zb=null,Y5(eZ2,"ENamedElementImpl",438),eTS(179,438,{105:1,92:1,90:1,147:1,191:1,56:1,235:1,108:1,49:1,97:1,150:1,179:1,114:1,115:1,675:1},$y),eUe.Qg=function(e){return eg5(this,e)},eUe._g=function(e,t,n){var r;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.zb;case 2:return this.yb;case 3:return this.xb;case 4:return this.sb;case 5:return this.rb||(this.rb=new Fq(this,tm8,this)),this.rb;case 6:return this.vb||(this.vb=new Ia(e6E,this,6,7)),this.vb;case 7:if(t)return this.Db>>16==7?Pp(this.Cb,235):null;return zU(this)}return Qt(this,e-Y1((eBK(),tgP)),ee2((r=Pp(eaS(this,16),26))||tgP,e),t,n)},eUe.hh=function(e,t,n){var r,i,a;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),edF(this.Ab,e,n);case 4:return this.sb&&(n=Pp(this.sb,49).ih(this,1,e6w,n)),ecY(this,Pp(e,471),n);case 5:return this.rb||(this.rb=new Fq(this,tm8,this)),edF(this.rb,e,n);case 6:return this.vb||(this.vb=new Ia(e6E,this,6,7)),edF(this.vb,e,n);case 7:return this.Cb&&(n=(i=this.Db>>16)>=0?eg5(this,n):this.Cb.ih(this,-1-i,null,n)),eDg(this,e,7,n)}return(a=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgP),t),66)).Nj().Qj(this,ehH(this),t-Y1((eBK(),tgP)),e,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 4:return ecY(this,null,n);case 5:return this.rb||(this.rb=new Fq(this,tm8,this)),ep6(this.rb,e,n);case 6:return this.vb||(this.vb=new Ia(e6E,this,6,7)),ep6(this.vb,e,n);case 7:return eDg(this,null,7,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgP),t),66)).Nj().Rj(this,ehH(this),t-Y1((eBK(),tgP)),e,n)},eUe.lh=function(e){var t;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return null!=this.yb;case 3:return null!=this.xb;case 4:return!!this.sb;case 5:return!!this.rb&&0!=this.rb.i;case 6:return!!this.vb&&0!=this.vb.i;case 7:return!!zU(this)}return VP(this,e-Y1((eBK(),tgP)),ee2((t=Pp(eaS(this,16),26))||tgP,e))},eUe.oh=function(e){var t;return(t=eAd(this,e))||eF9(this,e)},eUe.sh=function(e,t){var n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:er3(this,Lq(t));return;case 2:er5(this,Lq(t));return;case 3:er4(this,Lq(t));return;case 4:e_8(this,Pp(t,471));return;case 5:this.rb||(this.rb=new Fq(this,tm8,this)),eRT(this.rb),this.rb||(this.rb=new Fq(this,tm8,this)),Y4(this.rb,Pp(t,14));return;case 6:this.vb||(this.vb=new Ia(e6E,this,6,7)),eRT(this.vb),this.vb||(this.vb=new Ia(e6E,this,6,7)),Y4(this.vb,Pp(t,14));return}efL(this,e-Y1((eBK(),tgP)),ee2((n=Pp(eaS(this,16),26))||tgP,e),t)},eUe.vh=function(e){var t,n;if(e&&this.rb)for(n=new Ow(this.rb);n.e!=n.i.gc();)t=epH(n),M4(t,351)&&(Pp(t,351).w=null);ehU(this,64,e)},eUe.zh=function(){return eBK(),tgP},eUe.Bh=function(e){var t;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:er3(this,null);return;case 2:er5(this,null);return;case 3:er4(this,null);return;case 4:e_8(this,null);return;case 5:this.rb||(this.rb=new Fq(this,tm8,this)),eRT(this.rb);return;case 6:this.vb||(this.vb=new Ia(e6E,this,6,7)),eRT(this.vb);return}ec6(this,e-Y1((eBK(),tgP)),ee2((t=Pp(eaS(this,16),26))||tgP,e))},eUe.Gh=function(){egb(this)},eUe.Mh=function(){return this.rb||(this.rb=new Fq(this,tm8,this)),this.rb},eUe.Nh=function(){return this.sb},eUe.Oh=function(){return this.ub},eUe.Ph=function(){return this.xb},eUe.Qh=function(){return this.yb},eUe.Rh=function(e){this.ub=e},eUe.Ib=function(){var e;return(64&this.Db)!=0?ecF(this):(e=new O1(ecF(this)),e.a+=" (nsURI: ",xk(e,this.yb),e.a+=", nsPrefix: ",xk(e,this.xb),e.a+=")",e.a)},eUe.xb=null,eUe.yb=null,Y5(eZ2,"EPackageImpl",179),eTS(555,179,{105:1,2016:1,555:1,92:1,90:1,147:1,191:1,56:1,235:1,108:1,49:1,97:1,150:1,179:1,114:1,115:1,675:1},eTv),eUe.q=!1,eUe.r=!1;var e6T=!1;Y5(eZ3,"ElkGraphPackageImpl",555),eTS(354,724,{105:1,413:1,160:1,137:1,470:1,354:1,94:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1},o0),eUe.Qg=function(e){return eg0(this,e)},eUe._g=function(e,t,n){switch(e){case 7:return zH(this);case 8:return this.a}return efN(this,e,t,n)},eUe.hh=function(e,t,n){var r;return 7===t?(this.Cb&&(n=(r=this.Db>>16)>=0?eg0(this,n):this.Cb.ih(this,-1-r,null,n)),j2(this,Pp(e,160),n)):ew0(this,e,t,n)},eUe.jh=function(e,t,n){return 7==t?j2(this,null,n):ea9(this,e,t,n)},eUe.lh=function(e){switch(e){case 7:return!!zH(this);case 8:return!IE("",this.a)}return ef8(this,e)},eUe.sh=function(e,t){switch(e){case 7:eAu(this,Pp(t,160));return;case 8:eri(this,Lq(t));return}eym(this,e,t)},eUe.zh=function(){return eBa(),tmm},eUe.Bh=function(e){switch(e){case 7:eAu(this,null);return;case 8:eri(this,"");return}edS(this,e)},eUe.Ib=function(){return eE1(this)},eUe.a="",Y5(eZ3,"ElkLabelImpl",354),eTS(239,725,{105:1,413:1,82:1,160:1,33:1,470:1,239:1,94:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1},mS),eUe.Qg=function(e){return evs(this,e)},eUe._g=function(e,t,n){switch(e){case 9:return this.c||(this.c=new FQ(e6x,this,9,9)),this.c;case 10:return this.a||(this.a=new FQ(e6k,this,10,11)),this.a;case 11:return z$(this);case 12:return this.b||(this.b=new FQ(e6g,this,12,3)),this.b;case 13:return OQ(),this.a||(this.a=new FQ(e6k,this,10,11)),this.a.i>0}return ebQ(this,e,t,n)},eUe.hh=function(e,t,n){var r;switch(t){case 9:return this.c||(this.c=new FQ(e6x,this,9,9)),edF(this.c,e,n);case 10:return this.a||(this.a=new FQ(e6k,this,10,11)),edF(this.a,e,n);case 11:return this.Cb&&(n=(r=this.Db>>16)>=0?evs(this,n):this.Cb.ih(this,-1-r,null,n)),C4(this,Pp(e,33),n);case 12:return this.b||(this.b=new FQ(e6g,this,12,3)),edF(this.b,e,n)}return evZ(this,e,t,n)},eUe.jh=function(e,t,n){switch(t){case 9:return this.c||(this.c=new FQ(e6x,this,9,9)),ep6(this.c,e,n);case 10:return this.a||(this.a=new FQ(e6k,this,10,11)),ep6(this.a,e,n);case 11:return C4(this,null,n);case 12:return this.b||(this.b=new FQ(e6g,this,12,3)),ep6(this.b,e,n)}return evX(this,e,t,n)},eUe.lh=function(e){switch(e){case 9:return!!this.c&&0!=this.c.i;case 10:return!!this.a&&0!=this.a.i;case 11:return!!z$(this);case 12:return!!this.b&&0!=this.b.i;case 13:return this.a||(this.a=new FQ(e6k,this,10,11)),this.a.i>0}return esM(this,e)},eUe.sh=function(e,t){switch(e){case 9:this.c||(this.c=new FQ(e6x,this,9,9)),eRT(this.c),this.c||(this.c=new FQ(e6x,this,9,9)),Y4(this.c,Pp(t,14));return;case 10:this.a||(this.a=new FQ(e6k,this,10,11)),eRT(this.a),this.a||(this.a=new FQ(e6k,this,10,11)),Y4(this.a,Pp(t,14));return;case 11:eO$(this,Pp(t,33));return;case 12:this.b||(this.b=new FQ(e6g,this,12,3)),eRT(this.b),this.b||(this.b=new FQ(e6g,this,12,3)),Y4(this.b,Pp(t,14));return}eTH(this,e,t)},eUe.zh=function(){return eBa(),tmg},eUe.Bh=function(e){switch(e){case 9:this.c||(this.c=new FQ(e6x,this,9,9)),eRT(this.c);return;case 10:this.a||(this.a=new FQ(e6k,this,10,11)),eRT(this.a);return;case 11:eO$(this,null);return;case 12:this.b||(this.b=new FQ(e6g,this,12,3)),eRT(this.b);return}ep2(this,e)},eUe.Ib=function(){return eC4(this)},Y5(eZ3,"ElkNodeImpl",239),eTS(186,725,{105:1,413:1,82:1,160:1,118:1,470:1,186:1,94:1,92:1,90:1,56:1,108:1,49:1,97:1,114:1,115:1},mk),eUe.Qg=function(e){return eg2(this,e)},eUe._g=function(e,t,n){return 9==e?zY(this):ebQ(this,e,t,n)},eUe.hh=function(e,t,n){var r;return 9===t?(this.Cb&&(n=(r=this.Db>>16)>=0?eg2(this,n):this.Cb.ih(this,-1-r,null,n)),Cl(this,Pp(e,33),n)):evZ(this,e,t,n)},eUe.jh=function(e,t,n){return 9==t?Cl(this,null,n):evX(this,e,t,n)},eUe.lh=function(e){return 9==e?!!zY(this):esM(this,e)},eUe.sh=function(e,t){if(9===e){eOL(this,Pp(t,33));return}eTH(this,e,t)},eUe.zh=function(){return eBa(),tmv},eUe.Bh=function(e){if(9===e){eOL(this,null);return}ep2(this,e)},eUe.Ib=function(){return eC5(this)},Y5(eZ3,"ElkPortImpl",186);var e6M=RL(eX_,"BasicEMap/Entry");eTS(1092,115,{105:1,42:1,92:1,90:1,133:1,56:1,108:1,49:1,97:1,114:1,115:1},o3),eUe.Fb=function(e){return this===e},eUe.cd=function(){return this.b},eUe.Hb=function(){return Ao(this)},eUe.Uh=function(e){era(this,Pp(e,146))},eUe._g=function(e,t,n){switch(e){case 0:return this.b;case 1:return this.c}return ebl(this,e,t,n)},eUe.lh=function(e){switch(e){case 0:return!!this.b;case 1:return null!=this.c}return epY(this,e)},eUe.sh=function(e,t){switch(e){case 0:era(this,Pp(t,146));return;case 1:eru(this,t);return}eS5(this,e,t)},eUe.zh=function(){return eBa(),tmy},eUe.Bh=function(e){switch(e){case 0:era(this,null);return;case 1:eru(this,null);return}eSi(this,e)},eUe.Sh=function(){var e;return -1==this.a&&(e=this.b,this.a=e?esj(e):0),this.a},eUe.dd=function(){return this.c},eUe.Th=function(e){this.a=e},eUe.ed=function(e){var t;return t=this.c,eru(this,e),t},eUe.Ib=function(){var e;return(64&this.Db)!=0?eMT(this):(xM(xM(xM(e=new vc,this.b?this.b.tg():eUg),eGH),Ae(this.c)),e.a)},eUe.a=-1,eUe.c=null;var e6O=Y5(eZ3,"ElkPropertyToValueMapEntryImpl",1092);eTS(984,1,{},o6),Y5(eXk,"JsonAdapter",984),eTS(210,60,eHr,gK),Y5(eXk,"JsonImportException",210),eTS(857,1,{},eg6),Y5(eXk,"JsonImporter",857),eTS(891,1,{},kP),Y5(eXk,"JsonImporter/lambda$0$Type",891),eTS(892,1,{},kR),Y5(eXk,"JsonImporter/lambda$1$Type",892),eTS(900,1,{},h7),Y5(eXk,"JsonImporter/lambda$10$Type",900),eTS(902,1,{},kj),Y5(eXk,"JsonImporter/lambda$11$Type",902),eTS(903,1,{},kF),Y5(eXk,"JsonImporter/lambda$12$Type",903),eTS(909,1,{},HE),Y5(eXk,"JsonImporter/lambda$13$Type",909),eTS(908,1,{},H_),Y5(eXk,"JsonImporter/lambda$14$Type",908),eTS(904,1,{},kY),Y5(eXk,"JsonImporter/lambda$15$Type",904),eTS(905,1,{},kB),Y5(eXk,"JsonImporter/lambda$16$Type",905),eTS(906,1,{},kU),Y5(eXk,"JsonImporter/lambda$17$Type",906),eTS(907,1,{},kH),Y5(eXk,"JsonImporter/lambda$18$Type",907),eTS(912,1,{},pe),Y5(eXk,"JsonImporter/lambda$19$Type",912),eTS(893,1,{},pt),Y5(eXk,"JsonImporter/lambda$2$Type",893),eTS(910,1,{},pn),Y5(eXk,"JsonImporter/lambda$20$Type",910),eTS(911,1,{},pr),Y5(eXk,"JsonImporter/lambda$21$Type",911),eTS(915,1,{},pi),Y5(eXk,"JsonImporter/lambda$22$Type",915),eTS(913,1,{},pa),Y5(eXk,"JsonImporter/lambda$23$Type",913),eTS(914,1,{},po),Y5(eXk,"JsonImporter/lambda$24$Type",914),eTS(917,1,{},ps),Y5(eXk,"JsonImporter/lambda$25$Type",917),eTS(916,1,{},pu),Y5(eXk,"JsonImporter/lambda$26$Type",916),eTS(918,1,eUF,k$),eUe.td=function(e){JH(this.b,this.a,Lq(e))},Y5(eXk,"JsonImporter/lambda$27$Type",918),eTS(919,1,eUF,kz),eUe.td=function(e){J$(this.b,this.a,Lq(e))},Y5(eXk,"JsonImporter/lambda$28$Type",919),eTS(920,1,{},kG),Y5(eXk,"JsonImporter/lambda$29$Type",920),eTS(896,1,{},pc),Y5(eXk,"JsonImporter/lambda$3$Type",896),eTS(921,1,{},kW),Y5(eXk,"JsonImporter/lambda$30$Type",921),eTS(922,1,{},pl),Y5(eXk,"JsonImporter/lambda$31$Type",922),eTS(923,1,{},pf),Y5(eXk,"JsonImporter/lambda$32$Type",923),eTS(924,1,{},pd),Y5(eXk,"JsonImporter/lambda$33$Type",924),eTS(925,1,{},ph),Y5(eXk,"JsonImporter/lambda$34$Type",925),eTS(859,1,{},pp),Y5(eXk,"JsonImporter/lambda$35$Type",859),eTS(929,1,{},N8),Y5(eXk,"JsonImporter/lambda$36$Type",929),eTS(926,1,eUF,pb),eUe.td=function(e){qW(this.a,Pp(e,469))},Y5(eXk,"JsonImporter/lambda$37$Type",926),eTS(927,1,eUF,k0),eUe.td=function(e){xC(this.a,this.b,Pp(e,202))},Y5(eXk,"JsonImporter/lambda$38$Type",927),eTS(928,1,eUF,k2),eUe.td=function(e){xI(this.a,this.b,Pp(e,202))},Y5(eXk,"JsonImporter/lambda$39$Type",928),eTS(894,1,{},pm),Y5(eXk,"JsonImporter/lambda$4$Type",894),eTS(930,1,eUF,pg),eUe.td=function(e){qK(this.a,Pp(e,8))},Y5(eXk,"JsonImporter/lambda$40$Type",930),eTS(895,1,{},pv),Y5(eXk,"JsonImporter/lambda$5$Type",895),eTS(899,1,{},py),Y5(eXk,"JsonImporter/lambda$6$Type",899),eTS(897,1,{},pw),Y5(eXk,"JsonImporter/lambda$7$Type",897),eTS(898,1,{},p_),Y5(eXk,"JsonImporter/lambda$8$Type",898),eTS(901,1,{},pE),Y5(eXk,"JsonImporter/lambda$9$Type",901),eTS(948,1,eUF,pS),eUe.td=function(e){BC(this.a,new B_(Lq(e)))},Y5(eXk,"JsonMetaDataConverter/lambda$0$Type",948),eTS(949,1,eUF,pk),eUe.td=function(e){Bm(this.a,Pp(e,237))},Y5(eXk,"JsonMetaDataConverter/lambda$1$Type",949),eTS(950,1,eUF,px),eUe.td=function(e){GR(this.a,Pp(e,149))},Y5(eXk,"JsonMetaDataConverter/lambda$2$Type",950),eTS(951,1,eUF,pT),eUe.td=function(e){Bg(this.a,Pp(e,175))},Y5(eXk,"JsonMetaDataConverter/lambda$3$Type",951),eTS(237,22,{3:1,35:1,22:1,237:1},k1);var e6A=enw(ezx,"GraphFeature",237,e1G,etM,N1);eTS(13,1,{35:1,146:1},pO,Cm,xX,T2),eUe.wd=function(e){return Oo(this,Pp(e,146))},eUe.Fb=function(e){return $k(this,e)},eUe.wg=function(){return epB(this)},eUe.tg=function(){return this.b},eUe.Hb=function(){return ebA(this.b)},eUe.Ib=function(){return this.b},Y5(ezx,"Property",13),eTS(818,1,e$C,pM),eUe.ue=function(e,t){return elW(this,Pp(e,94),Pp(t,94))},eUe.Fb=function(e){return this===e},eUe.ve=function(){return new fZ(this)},Y5(ezx,"PropertyHolderComparator",818),eTS(695,1,eUE,pL),eUe.Nb=function(e){F8(this,e)},eUe.Pb=function(){return JZ(this)},eUe.Qb=function(){yI()},eUe.Ob=function(){return!!this.a},Y5(eXY,"ElkGraphUtil/AncestorIterator",695);var e6L=RL(eX_,"EList");eTS(67,52,{20:1,28:1,52:1,14:1,15:1,67:1,58:1}),eUe.Vc=function(e,t){elm(this,e,t)},eUe.Fc=function(e){return JL(this,e)},eUe.Wc=function(e,t){return eo0(this,e,t)},eUe.Gc=function(e){return Y4(this,e)},eUe.Zh=function(){return new AY(this)},eUe.$h=function(){return new AB(this)},eUe._h=function(e){return enH(this,e)},eUe.ai=function(){return!0},eUe.bi=function(e,t){},eUe.ci=function(){},eUe.di=function(e,t){X8(this,e,t)},eUe.ei=function(e,t,n){},eUe.fi=function(e,t){},eUe.gi=function(e,t,n){},eUe.Fb=function(e){return eCc(this,e)},eUe.Hb=function(){return eov(this)},eUe.hi=function(){return!1},eUe.Kc=function(){return new Ow(this)},eUe.Yc=function(){return new AF(this)},eUe.Zc=function(e){var t;if(t=this.gc(),e<0||e>t)throw p7(new Ii(e,t));return new YC(this,e)},eUe.ji=function(e,t){this.ii(e,this.Xc(t))},eUe.Mc=function(e){return eeu(this,e)},eUe.li=function(e,t){return t},eUe._c=function(e,t){return eby(this,e,t)},eUe.Ib=function(){return efq(this)},eUe.ni=function(){return!0},eUe.oi=function(e,t){return euu(this,t)},Y5(eX_,"AbstractEList",67),eTS(63,67,eXz,o7,eta,eiP),eUe.Vh=function(e,t){return ew2(this,e,t)},eUe.Wh=function(e){return emp(this,e)},eUe.Xh=function(e,t){ecW(this,e,t)},eUe.Yh=function(e){Zz(this,e)},eUe.pi=function(e){return J5(this,e)},eUe.$b=function(){ZG(this)},eUe.Hc=function(e){return ev9(this,e)},eUe.Xb=function(e){return etj(this,e)},eUe.qi=function(e){var t,n,r;++this.j,e>(n=null==this.g?0:this.g.length)&&(r=this.g,(t=n+(n/2|0)+4)=0&&(this.$c(t),!0)},eUe.mi=function(e,t){return this.Ui(e,this.oi(e,t))},eUe.gc=function(){return this.Vi()},eUe.Pc=function(){return this.Wi()},eUe.Qc=function(e){return this.Xi(e)},eUe.Ib=function(){return this.Yi()},Y5(eX_,"DelegatingEList",1995),eTS(1996,1995,eJk),eUe.Vh=function(e,t){return eD1(this,e,t)},eUe.Wh=function(e){return this.Vh(this.Vi(),e)},eUe.Xh=function(e,t){eTf(this,e,t)},eUe.Yh=function(e){exq(this,e)},eUe.ai=function(){return!this.bj()},eUe.$b=function(){eRP(this)},eUe.Zi=function(e,t,n,r,i){return new $P(this,e,t,n,r,i)},eUe.$i=function(e){eam(this.Ai(),e)},eUe._i=function(){return null},eUe.aj=function(){return -1},eUe.Ai=function(){return null},eUe.bj=function(){return!1},eUe.cj=function(e,t){return t},eUe.dj=function(e,t){return t},eUe.ej=function(){return!1},eUe.fj=function(){return!this.Ri()},eUe.ii=function(e,t){var n,r;return this.ej()?(r=this.fj(),n=e_R(this,e,t),this.$i(this.Zi(7,ell(t),n,e,r)),n):e_R(this,e,t)},eUe.$c=function(e){var t,n,r,i;return this.ej()?(n=null,r=this.fj(),t=this.Zi(4,i=RC(this,e),null,e,r),this.bj()&&i?(n=this.dj(i,n))?(n.Ei(t),n.Fi()):this.$i(t):n?(n.Ei(t),n.Fi()):this.$i(t),i):(i=RC(this,e),this.bj()&&i&&(n=this.dj(i,null))&&n.Fi(),i)},eUe.mi=function(e,t){return eD0(this,e,t)},Y5(eZK,"DelegatingNotifyingListImpl",1996),eTS(143,1,eJx),eUe.Ei=function(e){return ey7(this,e)},eUe.Fi=function(){QU(this)},eUe.xi=function(){return this.d},eUe._i=function(){return null},eUe.gj=function(){return null},eUe.yi=function(e){return -1},eUe.zi=function(){return eLo(this)},eUe.Ai=function(){return null},eUe.Bi=function(){return eLs(this)},eUe.Ci=function(){return this.o<0?this.o<-2?-2-this.o-1:-1:this.o},eUe.hj=function(){return!1},eUe.Di=function(e){var t,n,r,i,a,o,s,u,c,l,f;switch(this.d){case 1:case 2:switch(i=e.xi()){case 1:case 2:if(xc(a=e.Ai())===xc(this.Ai())&&this.yi(null)==e.yi(null))return this.g=e.zi(),1==e.xi()&&(this.d=1),!0}case 4:if(4===(i=e.xi())&&xc(a=e.Ai())===xc(this.Ai())&&this.yi(null)==e.yi(null))return c=eju(this),u=this.o<0?this.o<-2?-2-this.o-1:-1:this.o,o=e.Ci(),this.d=6,f=new eta(2),u<=o?(JL(f,this.n),JL(f,e.Bi()),this.g=eow(vx(ty_,1),eHT,25,15,[this.o=u,o+1])):(JL(f,e.Bi()),JL(f,this.n),this.g=eow(vx(ty_,1),eHT,25,15,[this.o=o,u])),this.n=f,c||(this.o=-2-this.o-1),!0;break;case 6:if(4===(i=e.xi())&&xc(a=e.Ai())===xc(this.Ai())&&this.yi(null)==e.yi(null)){for(c=eju(this),o=e.Ci(),r=Je(ty_,eHT,25,(l=Pp(this.g,48)).length+1,15,1),t=0;t>>0).toString(16)),r.a+=" (eventType: ",this.d){case 1:r.a+="SET";break;case 2:r.a+="UNSET";break;case 3:r.a+="ADD";break;case 5:r.a+="ADD_MANY";break;case 4:r.a+="REMOVE";break;case 6:r.a+="REMOVE_MANY";break;case 7:r.a+="MOVE";break;case 8:r.a+="REMOVING_ADAPTER";break;case 9:r.a+="RESOLVE";break;default:yz(r,this.d)}if(eIb(this)&&(r.a+=", touch: true"),r.a+=", position: ",yz(r,this.o<0?this.o<-2?-2-this.o-1:-1:this.o),r.a+=", notifier: ",xS(r,this.Ai()),r.a+=", feature: ",xS(r,this._i()),r.a+=", oldValue: ",xS(r,eLs(this)),r.a+=", newValue: ",6==this.d&&M4(this.g,48)){for(n=Pp(this.g,48),r.a+="[",e=0;e10?(this.b&&this.c.j==this.a||(this.b=new Rq(this),this.a=this.j),w0(this.b,e)):ev9(this,e)},eUe.ni=function(){return!0},eUe.a=0,Y5(eX_,"AbstractEList/1",953),eTS(295,73,eHZ,Ii),Y5(eX_,"AbstractEList/BasicIndexOutOfBoundsException",295),eTS(40,1,eUE,Ow),eUe.Nb=function(e){F8(this,e)},eUe.mj=function(){if(this.i.j!=this.f)throw p7(new bA)},eUe.nj=function(){return epH(this)},eUe.Ob=function(){return this.e!=this.i.gc()},eUe.Pb=function(){return this.nj()},eUe.Qb=function(){ey_(this)},eUe.e=0,eUe.f=0,eUe.g=-1,Y5(eX_,"AbstractEList/EIterator",40),eTS(278,40,eUC,AF,YC),eUe.Qb=function(){ey_(this)},eUe.Rb=function(e){edq(this,e)},eUe.oj=function(){var e;try{return e=this.d.Xb(--this.e),this.mj(),this.g=this.e,e}catch(t){if(t=eoa(t),M4(t,73))throw this.mj(),p7(new bC);throw p7(t)}},eUe.pj=function(e){emE(this,e)},eUe.Sb=function(){return 0!=this.e},eUe.Tb=function(){return this.e},eUe.Ub=function(){return this.oj()},eUe.Vb=function(){return this.e-1},eUe.Wb=function(e){this.pj(e)},Y5(eX_,"AbstractEList/EListIterator",278),eTS(341,40,eUE,AY),eUe.nj=function(){return ep$(this)},eUe.Qb=function(){throw p7(new bO)},Y5(eX_,"AbstractEList/NonResolvingEIterator",341),eTS(385,278,eUC,AB,IB),eUe.Rb=function(e){throw p7(new bO)},eUe.nj=function(){var e;try{return e=this.c.ki(this.e),this.mj(),this.g=this.e++,e}catch(t){if(t=eoa(t),M4(t,73))throw this.mj(),p7(new bC);throw p7(t)}},eUe.oj=function(){var e;try{return e=this.c.ki(--this.e),this.mj(),this.g=this.e,e}catch(t){if(t=eoa(t),M4(t,73))throw this.mj(),p7(new bC);throw p7(t)}},eUe.Qb=function(){throw p7(new bO)},eUe.Wb=function(e){throw p7(new bO)},Y5(eX_,"AbstractEList/NonResolvingEListIterator",385),eTS(1982,67,eJO),eUe.Vh=function(e,t){var n,r,i,a,o,s,u,c,l,f,d;if(0==(i=t.gc()))return++this.j,!1;for(r=eue(this,d=(l=null==(c=Pp(eaS(this.a,4),126))?0:c.length)+i),(f=l-e)>0&&ePD(c,e,r,e+i,f),u=t.Kc(),o=0;on)throw p7(new Ii(e,n));return new Uu(this,e)},eUe.$b=function(){var e,t;++this.j,t=null==(e=Pp(eaS(this.a,4),126))?0:e.length,eps(this,null),X8(this,t,e)},eUe.Hc=function(e){var t,n,r,i,a;if(null!=(t=Pp(eaS(this.a,4),126))){if(null!=e){for(i=0,a=(r=t).length;i=(n=null==(t=Pp(eaS(this.a,4),126))?0:t.length))throw p7(new Ii(e,n));return t[e]},eUe.Xc=function(e){var t,n,r;if(null!=(t=Pp(eaS(this.a,4),126))){if(null!=e){for(n=0,r=t.length;nn)throw p7(new Ii(e,n));return new Us(this,e)},eUe.ii=function(e,t){var n,r,i;if(i=null==(n=ehc(this))?0:n.length,e>=i)throw p7(new gE(eXU+e+eXH+i));if(t>=i)throw p7(new gE(eX$+t+eXH+i));return r=n[t],e!=t&&(e0&&ePD(e,0,t,0,n),t},eUe.Qc=function(e){var t,n,r;return(r=null==(t=Pp(eaS(this.a,4),126))?0:t.length)>0&&(e.lengthr&&Bc(e,r,null),e},Y5(eX_,"ArrayDelegatingEList",1982),eTS(1038,40,eUE,Zl),eUe.mj=function(){if(this.b.j!=this.f||xc(Pp(eaS(this.b.a,4),126))!==xc(this.a))throw p7(new bA)},eUe.Qb=function(){ey_(this),this.a=Pp(eaS(this.b.a,4),126)},Y5(eX_,"ArrayDelegatingEList/EIterator",1038),eTS(706,278,eUC,FK,Us),eUe.mj=function(){if(this.b.j!=this.f||xc(Pp(eaS(this.b.a,4),126))!==xc(this.a))throw p7(new bA)},eUe.pj=function(e){emE(this,e),this.a=Pp(eaS(this.b.a,4),126)},eUe.Qb=function(){ey_(this),this.a=Pp(eaS(this.b.a,4),126)},Y5(eX_,"ArrayDelegatingEList/EListIterator",706),eTS(1039,341,eUE,Zf),eUe.mj=function(){if(this.b.j!=this.f||xc(Pp(eaS(this.b.a,4),126))!==xc(this.a))throw p7(new bA)},Y5(eX_,"ArrayDelegatingEList/NonResolvingEIterator",1039),eTS(707,385,eUC,FV,Uu),eUe.mj=function(){if(this.b.j!=this.f||xc(Pp(eaS(this.b.a,4),126))!==xc(this.a))throw p7(new bA)},Y5(eX_,"ArrayDelegatingEList/NonResolvingEListIterator",707),eTS(606,295,eHZ,xJ),Y5(eX_,"BasicEList/BasicIndexOutOfBoundsException",606),eTS(696,63,eXz,xt),eUe.Vc=function(e,t){throw p7(new bO)},eUe.Fc=function(e){throw p7(new bO)},eUe.Wc=function(e,t){throw p7(new bO)},eUe.Gc=function(e){throw p7(new bO)},eUe.$b=function(){throw p7(new bO)},eUe.qi=function(e){throw p7(new bO)},eUe.Kc=function(){return this.Zh()},eUe.Yc=function(){return this.$h()},eUe.Zc=function(e){return this._h(e)},eUe.ii=function(e,t){throw p7(new bO)},eUe.ji=function(e,t){throw p7(new bO)},eUe.$c=function(e){throw p7(new bO)},eUe.Mc=function(e){throw p7(new bO)},eUe._c=function(e,t){throw p7(new bO)},Y5(eX_,"BasicEList/UnmodifiableEList",696),eTS(705,1,{3:1,20:1,14:1,15:1,58:1,589:1}),eUe.Vc=function(e,t){Mq(this,e,Pp(t,42))},eUe.Fc=function(e){return LA(this,Pp(e,42))},eUe.Jc=function(e){qX(this,e)},eUe.Xb=function(e){return Pp(etj(this.c,e),133)},eUe.ii=function(e,t){return Pp(this.c.ii(e,t),42)},eUe.ji=function(e,t){MZ(this,e,Pp(t,42))},eUe.Lc=function(){return new R1(null,new Gq(this,16))},eUe.$c=function(e){return Pp(this.c.$c(e),42)},eUe._c=function(e,t){return YV(this,e,Pp(t,42))},eUe.ad=function(e){er8(this,e)},eUe.Nc=function(){return new Gq(this,16)},eUe.Oc=function(){return new R1(null,new Gq(this,16))},eUe.Wc=function(e,t){return this.c.Wc(e,t)},eUe.Gc=function(e){return this.c.Gc(e)},eUe.$b=function(){this.c.$b()},eUe.Hc=function(e){return this.c.Hc(e)},eUe.Ic=function(e){return eot(this.c,e)},eUe.qj=function(){var e,t,n;if(null==this.d){for(this.d=Je(e6C,eJA,63,2*this.f+1,0,1),n=this.e,this.f=0,t=this.c.Kc();t.e!=t.i.gc();)ebB(this,e=Pp(t.nj(),133));this.e=n}},eUe.Fb=function(e){return Ij(this,e)},eUe.Hb=function(){return eov(this.c)},eUe.Xc=function(e){return this.c.Xc(e)},eUe.rj=function(){this.c=new pC(this)},eUe.dc=function(){return 0==this.f},eUe.Kc=function(){return this.c.Kc()},eUe.Yc=function(){return this.c.Yc()},eUe.Zc=function(e){return this.c.Zc(e)},eUe.sj=function(){return X6(this)},eUe.tj=function(e,t,n){return new N7(e,t,n)},eUe.uj=function(){return new st},eUe.Mc=function(e){return en$(this,e)},eUe.gc=function(){return this.f},eUe.bd=function(e,t){return new Gz(this.c,e,t)},eUe.Pc=function(){return this.c.Pc()},eUe.Qc=function(e){return this.c.Qc(e)},eUe.Ib=function(){return efq(this.c)},eUe.e=0,eUe.f=0,Y5(eX_,"BasicEMap",705),eTS(1033,63,eXz,pC),eUe.bi=function(e,t){bH(this,Pp(t,133))},eUe.ei=function(e,t,n){var r;++(r=this,Pp(t,133),r).a.e},eUe.fi=function(e,t){b$(this,Pp(t,133))},eUe.gi=function(e,t,n){AO(this,Pp(t,133),Pp(n,133))},eUe.di=function(e,t){eac(this.a)},Y5(eX_,"BasicEMap/1",1033),eTS(1034,63,eXz,st),eUe.ri=function(e){return Je(e6R,eJL,612,e,0,1)},Y5(eX_,"BasicEMap/2",1034),eTS(1035,eUT,eUM,pI),eUe.$b=function(){this.a.c.$b()},eUe.Hc=function(e){return edG(this.a,e)},eUe.Kc=function(){return 0==this.a.f?(LF(),tmB.a):new yd(this.a)},eUe.Mc=function(e){var t;return t=this.a.f,ehx(this.a,e),this.a.f!=t},eUe.gc=function(){return this.a.f},Y5(eX_,"BasicEMap/3",1035),eTS(1036,28,eUx,pD),eUe.$b=function(){this.a.c.$b()},eUe.Hc=function(e){return eCl(this.a,e)},eUe.Kc=function(){return 0==this.a.f?(LF(),tmB.a):new yh(this.a)},eUe.gc=function(){return this.a.f},Y5(eX_,"BasicEMap/4",1036),eTS(1037,eUT,eUM,pN),eUe.$b=function(){this.a.c.$b()},eUe.Hc=function(e){var t,n,r,i,a,o,s,u,c;if(this.a.f>0&&M4(e,42)&&(this.a.qj(),i=null==(s=(u=Pp(e,42)).cd())?0:esj(s),a=Cb(this.a,i),t=this.a.d[a])){for(o=0,n=Pp(t.g,367),c=t.i;o"+this.c},eUe.a=0;var e6R=Y5(eX_,"BasicEMap/EntryImpl",612);eTS(536,1,{},o2),Y5(eX_,"BasicEMap/View",536),eTS(768,1,{}),eUe.Fb=function(e){return eT$((Hj(),e2r),e)},eUe.Hb=function(){return esS((Hj(),e2r))},eUe.Ib=function(){return e_F((Hj(),e2r))},Y5(eX_,"ECollections/BasicEmptyUnmodifiableEList",768),eTS(1312,1,eUC,sn),eUe.Nb=function(e){F8(this,e)},eUe.Rb=function(e){throw p7(new bO)},eUe.Ob=function(){return!1},eUe.Sb=function(){return!1},eUe.Pb=function(){throw p7(new bC)},eUe.Tb=function(){return 0},eUe.Ub=function(){throw p7(new bC)},eUe.Vb=function(){return -1},eUe.Qb=function(){throw p7(new bO)},eUe.Wb=function(e){throw p7(new bO)},Y5(eX_,"ECollections/BasicEmptyUnmodifiableEList/1",1312),eTS(1310,768,{20:1,14:1,15:1,58:1},mx),eUe.Vc=function(e,t){y5()},eUe.Fc=function(e){return y6()},eUe.Wc=function(e,t){return y9()},eUe.Gc=function(e){return y8()},eUe.$b=function(){y7()},eUe.Hc=function(e){return!1},eUe.Ic=function(e){return!1},eUe.Jc=function(e){qX(this,e)},eUe.Xb=function(e){return xY((Hj(),e)),null},eUe.Xc=function(e){return -1},eUe.dc=function(){return!0},eUe.Kc=function(){return this.a},eUe.Yc=function(){return this.a},eUe.Zc=function(e){return this.a},eUe.ii=function(e,t){return we()},eUe.ji=function(e,t){wt()},eUe.Lc=function(){return new R1(null,new Gq(this,16))},eUe.$c=function(e){return wn()},eUe.Mc=function(e){return wr()},eUe._c=function(e,t){return wi()},eUe.gc=function(){return 0},eUe.ad=function(e){er8(this,e)},eUe.Nc=function(){return new Gq(this,16)},eUe.Oc=function(){return new R1(null,new Gq(this,16))},eUe.bd=function(e,t){return Hj(),new Gz(e2r,e,t)},eUe.Pc=function(){return Fn((Hj(),e2r))},eUe.Qc=function(e){return Hj(),emk(e2r,e)},Y5(eX_,"ECollections/EmptyUnmodifiableEList",1310),eTS(1311,768,{20:1,14:1,15:1,58:1,589:1},mT),eUe.Vc=function(e,t){y5()},eUe.Fc=function(e){return y6()},eUe.Wc=function(e,t){return y9()},eUe.Gc=function(e){return y8()},eUe.$b=function(){y7()},eUe.Hc=function(e){return!1},eUe.Ic=function(e){return!1},eUe.Jc=function(e){qX(this,e)},eUe.Xb=function(e){return xY((Hj(),e)),null},eUe.Xc=function(e){return -1},eUe.dc=function(){return!0},eUe.Kc=function(){return this.a},eUe.Yc=function(){return this.a},eUe.Zc=function(e){return this.a},eUe.ii=function(e,t){return we()},eUe.ji=function(e,t){wt()},eUe.Lc=function(){return new R1(null,new Gq(this,16))},eUe.$c=function(e){return wn()},eUe.Mc=function(e){return wr()},eUe._c=function(e,t){return wi()},eUe.gc=function(){return 0},eUe.ad=function(e){er8(this,e)},eUe.Nc=function(){return new Gq(this,16)},eUe.Oc=function(){return new R1(null,new Gq(this,16))},eUe.bd=function(e,t){return Hj(),new Gz(e2r,e,t)},eUe.Pc=function(){return Fn((Hj(),e2r))},eUe.Qc=function(e){return Hj(),emk(e2r,e)},eUe.sj=function(){return Hj(),Hj(),e2i},Y5(eX_,"ECollections/EmptyUnmodifiableEMap",1311);var e6j=RL(eX_,"Enumerator");eTS(281,1,{281:1},eCg),eUe.Fb=function(e){var t;return this===e||!!M4(e,281)&&(t=Pp(e,281),this.f==t.f&&jx(this.i,t.i)&&jk(this.a,(256&this.f)!=0?(256&t.f)!=0?t.a:null:(256&t.f)!=0?null:t.a)&&jk(this.d,t.d)&&jk(this.g,t.g)&&jk(this.e,t.e)&&epK(this,t))},eUe.Hb=function(){return this.f},eUe.Ib=function(){return eDv(this)},eUe.f=0;var e6F,e6Y,e6B,e6U,e6H,e6$,e6z,e6G,e6W,e6K,e6V,e6q,e6Z,e6X,e6J,e6Q,e61,e60,e62,e63,e64,e65,e66,e69,e68,e67,e9e,e9t,e9n,e9r,e9i,e9a,e9o,e9s,e9u,e9c,e9l,e9f,e9d,e9h,e9p,e9b,e9m,e9g,e9v,e9y,e9w,e9_,e9E,e9S,e9k,e9x,e9T,e9M,e9O,e9A,e9L,e9C,e9I,e9D,e9N,e9P,e9R,e9j,e9F,e9Y,e9B,e9U,e9H,e9$,e9z,e9G,e9W,e9K,e9V,e9q,e9Z,e9X,e9J,e9Q,e91,e90,e92,e93,e94,e95,e96,e99,e98,e97,e8e,e8t,e8n,e8r,e8i,e8a,e8o,e8s,e8u,e8c,e8l,e8f,e8d,e8h,e8p,e8b,e8m,e8g,e8v,e8y,e8w,e8_,e8E,e8S,e8k,e8x,e8T,e8M,e8O,e8A,e8L,e8C,e8I,e8D,e8N,e8P,e8R,e8j,e8F,e8Y,e8B,e8U,e8H,e8$,e8z,e8G,e8W,e8K,e8V,e8q,e8Z,e8X,e8J,e8Q,e81,e80,e82,e83,e84,e85,e86,e89,e88,e87,e7e,e7t,e7n,e7r,e7i,e7a,e7o,e7s,e7u,e7c,e7l,e7f,e7d,e7h,e7p,e7b,e7m,e7g,e7v,e7y,e7w,e7_,e7E,e7S,e7k,e7x,e7T,e7M,e7O,e7A,e7L,e7C,e7I,e7D,e7N,e7P,e7R,e7j,e7F,e7Y,e7B,e7U,e7H,e7$,e7z,e7G,e7W,e7K,e7V,e7q,e7Z,e7X,e7J,e7Q,e71,e70,e72,e73,e74,e75,e76,e79,e78,e77,tee,tet,ten,ter,tei,tea,teo,tes,teu,tec,tel,tef,ted,teh,tep,teb,tem,teg,tev,tey,tew,te_,teE,teS,tek,tex,teT,teM,teO,teA,teL,teC,teI,teD,teN,teP,teR,tej,teF,teY,teB,teU,teH,te$,tez,teG,teW,teK,teV,teq,teZ,teX,teJ,teQ,te1,te0,te2,te3,te4,te5,te6,te9,te8,te7,tte,ttt,ttn,ttr,tti,tta,tto,tts,ttu,ttc,ttl,ttf,ttd,tth,ttp,ttb,ttm,ttg,ttv,tty,ttw,tt_,ttE,ttS,ttk,ttx,ttT,ttM,ttO,ttA,ttL,ttC,ttI,ttD,ttN,ttP,ttR,ttj,ttF,ttY,ttB,ttU,ttH,tt$,ttz,ttG,ttW,ttK,ttV,ttq,ttZ,ttX,ttJ,ttQ,tt1,tt0,tt2,tt3,tt4,tt5,tt6,tt9,tt8,tt7,tne,tnt,tnn,tnr,tni,tna,tno,tns,tnu,tnc,tnl,tnf,tnd,tnh,tnp,tnb,tnm,tng,tnv,tny,tnw,tn_,tnE,tnS,tnk,tnx,tnT,tnM,tnO,tnA,tnL,tnC,tnI,tnD,tnN,tnP,tnR,tnj,tnF,tnY,tnB,tnU,tnH,tn$,tnz,tnG,tnW,tnK,tnV,tnq,tnZ,tnX,tnJ,tnQ,tn1,tn0,tn2,tn3,tn4,tn5,tn6,tn9,tn8,tn7,tre,trt,trn,trr,tri,tra,tro,trs,tru,trc,trl,trf,trd,trh,trp,trb,trm,trg,trv,trw,tr_,trE,trS,trk,trx,trT,trM,trO,trA,trL,trC,trI,trD,trN,trP,trR,trj,trF,trY,trB,trU,trH,tr$,trz,trG,trW,trK,trV,trq,trZ,trX,trJ,trQ,tr1,tr0,tr2,tr3,tr4,tr5,tr6,tr9,tr8,tr7,tie,tit,tin,tir,tii,tia,tio,tis,tiu,tic,til,tif,tid,tih,tip,tib,tim,tig,tiv,tiy,tiw,ti_,tiE,tiS,tik,tix,tiT,tiM,tiO,tiA,tiL,tiC,tiI,tiD,tiN,tiP,tiR,tij,tiF,tiY,tiB,tiU,tiH,ti$,tiz,tiG,tiW,tiK,tiV,tiq,tiZ,tiX,tiJ,tiQ,ti1,ti0,ti2,ti3,ti4,ti5,ti6,ti9,ti8,ti7,tae,tat,tan,tar,tai,taa,tao,tas,tau,tac,tal,taf,tad,tah,tap,tab,tam,tag,tav,tay,taw,ta_,taE,taS,tak,tax,taT,taM,taO,taA,taL,taC,taI,taD,taN,taP,taR,taj,taF,taY,taB,taU,taH,ta$,taz,taG,taW,taK,taV,taq,taZ,taX,taJ,taQ,ta1,ta0,ta2,ta3,ta4,ta5,ta6,ta9,ta8,ta7,toe,tot,ton,tor,toi,toa,too,tos,tou,toc,tol,tof,tod,toh,top,tob,tom,tog,tov,toy,tow,to_,toE,toS,tok,tox,toT,toM,toO,toA,toL,toC,toI,toD,toN,toP,toR,toj,toF,toY,toB,toU,toH,to$,toz,toG,toW,toK,toV,toq,toZ,toX,toJ,toQ,to1,to0,to2,to3,to4,to5,to6,to9,to8,to7,tse,tst,tsn,tsr,tsi,tsa,tso,tss,tsu,tsc,tsl,tsf,tsd,tsh,tsp,tsb,tsm,tsg,tsv,tsy,tsw,ts_,tsE,tsS,tsk,tsx,tsT,tsM,tsO,tsA,tsL,tsC,tsI,tsD,tsN,tsP,tsR,tsj,tsF,tsY,tsB,tsU,tsH,ts$,tsz,tsG,tsW,tsK,tsV,tsq,tsZ,tsX,tsJ,tsQ,ts1,ts0,ts2,ts3,ts4,ts5,ts6,ts9,ts8,ts7,tue,tut,tun,tur,tui,tua,tuo,tus,tuu,tuc,tul,tuf,tud,tuh,tup,tub,tum,tug,tuv,tuy,tuw,tu_,tuE,tuS,tuk,tux,tuT,tuM,tuO,tuA,tuL,tuC,tuI,tuD,tuN,tuP,tuR,tuj,tuF,tuY,tuB,tuU,tuH,tu$,tuz,tuG,tuW,tuK,tuV,tuq,tuZ,tuX,tuJ,tuQ,tu1,tu0,tu2,tu3,tu4,tu5,tu6,tu9,tu8,tu7,tce,tct,tcn,tcr,tci,tca,tco,tcs,tcu,tcc,tcl,tcf,tcd,tch,tcp,tcb,tcm,tcg,tcv,tcy,tcw,tc_,tcE,tcS,tck,tcx,tcT,tcM,tcO,tcA,tcL,tcC,tcI,tcD,tcN,tcP,tcR,tcj,tcF,tcY,tcB,tcU,tcH,tc$,tcz,tcG,tcW,tcK,tcV,tcq,tcZ,tcX,tcJ,tcQ,tc1,tc0,tc2,tc3,tc4,tc5,tc6,tc9,tc8,tc7,tle,tlt,tln,tlr,tli,tla,tlo,tls,tlu,tlc,tll,tlf,tld,tlh,tlp,tlb,tlm,tlg,tlv,tly,tlw,tl_,tlE,tlS,tlk,tlx,tlT,tlM,tlO,tlA,tlL,tlC,tlI,tlD,tlN,tlP,tlR,tlj,tlF,tlY,tlB,tlU,tlH,tl$,tlz,tlG,tlW,tlK,tlV,tlq,tlZ,tlX,tlJ,tlQ,tl1,tl0,tl2,tl3,tl4,tl5,tl6,tl9,tl8,tl7,tfe,tft,tfn,tfr,tfi,tfa,tfo,tfs,tfu,tfc,tfl,tff,tfd,tfh,tfp,tfb,tfm,tfg,tfv,tfy,tfw,tf_,tfE,tfS,tfk,tfx,tfT,tfM,tfO,tfA,tfL,tfC,tfI,tfD,tfN,tfP,tfR,tfj,tfF,tfY,tfB,tfU,tfH,tf$,tfz,tfG,tfW,tfK,tfV,tfq,tfZ,tfX,tfJ,tfQ,tf1,tf0,tf2,tf3,tf4,tf5,tf6,tf9,tf8,tf7,tde,tdt,tdn,tdr,tdi,tda,tdo,tds,tdu,tdc,tdl,tdf,tdd,tdh,tdp,tdb,tdm,tdg,tdv,tdy,tdw,td_,tdE,tdS,tdk,tdx,tdT,tdM,tdO,tdA,tdL,tdC,tdI,tdD,tdN,tdP,tdR,tdj,tdF,tdY,tdB,tdU,tdH,td$,tdz,tdG,tdW,tdK,tdV,tdq,tdZ,tdX,tdJ,tdQ,td1,td0,td2,td3,td4,td5,td6,td9,td8,td7,the,tht,thn,thr,thi,tha,tho,ths,thu,thc,thl,thf,thd,thh,thp,thb,thm,thg,thv,thy,thw,th_,thE,thS,thk,thx,thT,thM,thO,thA,thL,thC,thI,thD,thN,thP,thR,thj,thF,thY,thB,thU,thH,th$,thz,thG,thW,thK,thV,thq,thZ,thX,thJ,thQ,th1,th0,th2,th3,th4,th5,th6,th9,th8,th7,tpe,tpt,tpn,tpr,tpi,tpa,tpo,tps,tpu,tpc,tpl,tpf,tpd,tph,tpp,tpb,tpm,tpg,tpv,tpy,tpw,tp_,tpE,tpS,tpk,tpx,tpT,tpM,tpO,tpA,tpL,tpC,tpI,tpD,tpN,tpP,tpR,tpj,tpF,tpY,tpB,tpU,tpH,tp$,tpz,tpG,tpW,tpK,tpV,tpq,tpZ,tpX,tpJ,tpQ,tp1,tp0,tp2,tp3,tp4,tp5,tp6,tp9,tp8,tp7,tbe,tbt,tbn,tbr,tbi,tba,tbo,tbs,tbu,tbc,tbl,tbf,tbd,tbh,tbp,tbb,tbm,tbg,tbv,tby,tbw,tb_,tbE,tbS,tbk,tbx,tbT,tbM,tbO,tbA,tbL,tbC,tbI,tbD,tbN,tbP,tbR,tbj,tbF,tbY,tbB,tbU,tbH,tb$,tbz,tbG,tbW,tbK,tbV,tbq,tbZ,tbX,tbJ,tbQ,tb1,tb0,tb2,tb3,tb4,tb5,tb6,tb9,tb8,tb7,tme,tmt,tmn,tmr,tmi,tma,tmo,tms,tmu,tmc,tml,tmf,tmd,tmh,tmp,tmb,tmm,tmg,tmv,tmy,tmw,tm_,tmE,tmS,tmk,tmx,tmT,tmM,tmO,tmA,tmL,tmC,tmI,tmD,tmN,tmP,tmR,tmj,tmF,tmY,tmB,tmU,tmH,tm$,tmz,tmG=0,tmW=0,tmK=0,tmV=0,tmq=0,tmZ=0,tmX=0,tmJ=0,tmQ=0,tm1=0,tm0=0,tm2=0,tm3=0;Y5(eX_,"URI",281),eTS(1091,43,e$s,mM),eUe.zc=function(e,t){return Pp(Ge(this,Lq(e),Pp(t,281)),281)},Y5(eX_,"URI/URICache",1091),eTS(497,63,eXz,o5,jf),eUe.hi=function(){return!0},Y5(eX_,"UniqueEList",497),eTS(581,60,eHr,QH),Y5(eX_,"WrappedException",581);var tm4=RL(eZD,eJD),tm5=RL(eZD,eJN),tm6=RL(eZD,eJP),tm9=RL(eZD,eJR),tm8=RL(eZD,eJj),tm7=RL(eZD,"EClass"),tge=RL(eZD,"EDataType");eTS(1183,43,e$s,mO),eUe.xc=function(e){return xd(e)?zg(this,e):xu($I(this.f,e))},Y5(eZD,"EDataType/Internal/ConversionDelegate/Factory/Registry/Impl",1183);var tgt=RL(eZD,"EEnum"),tgn=RL(eZD,eJF),tgr=RL(eZD,eJY),tgi=RL(eZD,eJB),tga=RL(eZD,eJU),tgo=RL(eZD,eJH);eTS(1029,1,{},o4),eUe.Ib=function(){return"NIL"},Y5(eZD,"EStructuralFeature/Internal/DynamicValueHolder/1",1029),eTS(1028,43,e$s,mA),eUe.xc=function(e){return xd(e)?zg(this,e):xu($I(this.f,e))},Y5(eZD,"EStructuralFeature/Internal/SettingDelegate/Factory/Registry/Impl",1028);var tgs=RL(eZD,eJ$),tgu=RL(eZD,"EValidator/PatternMatcher"),tgc=RL(eJz,"FeatureMap/Entry");eTS(535,1,{72:1},k3),eUe.ak=function(){return this.a},eUe.dd=function(){return this.b},Y5(eZ2,"BasicEObjectImpl/1",535),eTS(1027,1,eJG,k4),eUe.Wj=function(e){return ZN(this.a,this.b,e)},eUe.fj=function(){return zz(this.a,this.b)},eUe.Wb=function(e){zx(this.a,this.b,e)},eUe.Xj=function(){B4(this.a,this.b)},Y5(eZ2,"BasicEObjectImpl/4",1027),eTS(1983,1,{108:1}),eUe.bk=function(e){this.e=0==e?tgH:Je(e1R,eUp,1,e,5,1)},eUe.Ch=function(e){return this.e[e]},eUe.Dh=function(e,t){this.e[e]=t},eUe.Eh=function(e){this.e[e]=null},eUe.ck=function(){return this.c},eUe.dk=function(){throw p7(new bO)},eUe.ek=function(){throw p7(new bO)},eUe.fk=function(){return this.d},eUe.gk=function(){return null!=this.e},eUe.hk=function(e){this.c=e},eUe.ik=function(e){throw p7(new bO)},eUe.jk=function(e){throw p7(new bO)},eUe.kk=function(e){this.d=e},Y5(eZ2,"BasicEObjectImpl/EPropertiesHolderBaseImpl",1983),eTS(185,1983,{108:1},c1),eUe.dk=function(){return this.a},eUe.ek=function(){return this.b},eUe.ik=function(e){this.a=e},eUe.jk=function(e){this.b=e},Y5(eZ2,"BasicEObjectImpl/EPropertiesHolderImpl",185),eTS(506,97,eZ0,sr),eUe.Kg=function(){return this.f},eUe.Pg=function(){return this.k},eUe.Rg=function(e,t){this.g=e,this.i=t},eUe.Tg=function(){return(2&this.j)==0?this.zh():this.ph().ck()},eUe.Vg=function(){return this.i},eUe.Mg=function(){return(1&this.j)!=0},eUe.eh=function(){return this.g},eUe.kh=function(){return(4&this.j)!=0},eUe.ph=function(){return this.k||(this.k=new c1),this.k},eUe.th=function(e){this.ph().hk(e),e?this.j|=2:this.j&=-3},eUe.vh=function(e){this.ph().jk(e),e?this.j|=4:this.j&=-5},eUe.zh=function(){return(BM(),tgv).S},eUe.i=0,eUe.j=1,Y5(eZ2,"EObjectImpl",506),eTS(780,506,{105:1,92:1,90:1,56:1,108:1,49:1,97:1},Pq),eUe.Ch=function(e){return this.e[e]},eUe.Dh=function(e,t){this.e[e]=t},eUe.Eh=function(e){this.e[e]=null},eUe.Tg=function(){return this.d},eUe.Yg=function(e){return edv(this.d,e)},eUe.$g=function(){return this.d},eUe.dh=function(){return null!=this.e},eUe.ph=function(){return this.k||(this.k=new si),this.k},eUe.th=function(e){this.d=e},eUe.yh=function(){var e;return null==this.e&&(e=Y1(this.d),this.e=0==e?tg$:Je(e1R,eUp,1,e,5,1)),this},eUe.Ah=function(){return 0},Y5(eZ2,"DynamicEObjectImpl",780),eTS(1376,780,{105:1,42:1,92:1,90:1,133:1,56:1,108:1,49:1,97:1},RO),eUe.Fb=function(e){return this===e},eUe.Hb=function(){return Ao(this)},eUe.th=function(e){this.d=e,this.b=eAh(e,"key"),this.c=eAh(e,eXr)},eUe.Sh=function(){var e;return -1==this.a&&(e=Q9(this,this.b),this.a=null==e?0:esj(e)),this.a},eUe.cd=function(){return Q9(this,this.b)},eUe.dd=function(){return Q9(this,this.c)},eUe.Th=function(e){this.a=e},eUe.Uh=function(e){zx(this,this.b,e)},eUe.ed=function(e){var t;return t=Q9(this,this.c),zx(this,this.c,e),t},eUe.a=0,Y5(eZ2,"DynamicEObjectImpl/BasicEMapEntry",1376),eTS(1377,1,{108:1},si),eUe.bk=function(e){throw p7(new bO)},eUe.Ch=function(e){throw p7(new bO)},eUe.Dh=function(e,t){throw p7(new bO)},eUe.Eh=function(e){throw p7(new bO)},eUe.ck=function(){throw p7(new bO)},eUe.dk=function(){return this.a},eUe.ek=function(){return this.b},eUe.fk=function(){return this.c},eUe.gk=function(){throw p7(new bO)},eUe.hk=function(e){throw p7(new bO)},eUe.ik=function(e){this.a=e},eUe.jk=function(e){this.b=e},eUe.kk=function(e){this.c=e},Y5(eZ2,"DynamicEObjectImpl/DynamicEPropertiesHolderImpl",1377),eTS(510,150,{105:1,92:1,90:1,590:1,147:1,56:1,108:1,49:1,97:1,510:1,150:1,114:1,115:1},sa),eUe.Qg=function(e){return eg4(this,e)},eUe._g=function(e,t,n){var r;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.d;case 2:return n?(this.b||(this.b=new L_((eBK(),tgF),tgf,this)),this.b):(this.b||(this.b=new L_((eBK(),tgF),tgf,this)),X6(this.b));case 3:return z4(this);case 4:return this.a||(this.a=new O_(e6f,this,4)),this.a;case 5:return this.c||(this.c=new OT(e6f,this,5)),this.c}return Qt(this,e-Y1((eBK(),tgy)),ee2((r=Pp(eaS(this,16),26))||tgy,e),t,n)},eUe.hh=function(e,t,n){var r,i,a;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),edF(this.Ab,e,n);case 3:return this.Cb&&(n=(i=this.Db>>16)>=0?eg4(this,n):this.Cb.ih(this,-1-i,null,n)),j3(this,Pp(e,147),n)}return(a=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgy),t),66)).Nj().Qj(this,ehH(this),t-Y1((eBK(),tgy)),e,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 2:return this.b||(this.b=new L_((eBK(),tgF),tgf,this)),Iz(this.b,e,n);case 3:return j3(this,null,n);case 4:return this.a||(this.a=new O_(e6f,this,4)),ep6(this.a,e,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgy),t),66)).Nj().Rj(this,ehH(this),t-Y1((eBK(),tgy)),e,n)},eUe.lh=function(e){var t;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.d;case 2:return!!this.b&&0!=this.b.f;case 3:return!!z4(this);case 4:return!!this.a&&0!=this.a.i;case 5:return!!this.c&&0!=this.c.i}return VP(this,e-Y1((eBK(),tgy)),ee2((t=Pp(eaS(this,16),26))||tgy,e))},eUe.sh=function(e,t){var n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:RN(this,Lq(t));return;case 2:this.b||(this.b=new L_((eBK(),tgF),tgf,this)),eai(this.b,t);return;case 3:eAc(this,Pp(t,147));return;case 4:this.a||(this.a=new O_(e6f,this,4)),eRT(this.a),this.a||(this.a=new O_(e6f,this,4)),Y4(this.a,Pp(t,14));return;case 5:this.c||(this.c=new OT(e6f,this,5)),eRT(this.c),this.c||(this.c=new OT(e6f,this,5)),Y4(this.c,Pp(t,14));return}efL(this,e-Y1((eBK(),tgy)),ee2((n=Pp(eaS(this,16),26))||tgy,e),t)},eUe.zh=function(){return eBK(),tgy},eUe.Bh=function(e){var t;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:erl(this,null);return;case 2:this.b||(this.b=new L_((eBK(),tgF),tgf,this)),this.b.c.$b();return;case 3:eAc(this,null);return;case 4:this.a||(this.a=new O_(e6f,this,4)),eRT(this.a);return;case 5:this.c||(this.c=new OT(e6f,this,5)),eRT(this.c);return}ec6(this,e-Y1((eBK(),tgy)),ee2((t=Pp(eaS(this,16),26))||tgy,e))},eUe.Ib=function(){return eln(this)},eUe.d=null,Y5(eZ2,"EAnnotationImpl",510),eTS(151,705,eJW,JY),eUe.Xh=function(e,t){T7(this,e,Pp(t,42))},eUe.lk=function(e,t){return I$(this,Pp(e,42),t)},eUe.pi=function(e){return Pp(Pp(this.c,69).pi(e),133)},eUe.Zh=function(){return Pp(this.c,69).Zh()},eUe.$h=function(){return Pp(this.c,69).$h()},eUe._h=function(e){return Pp(this.c,69)._h(e)},eUe.mk=function(e,t){return Iz(this,e,t)},eUe.Wj=function(e){return Pp(this.c,76).Wj(e)},eUe.rj=function(){},eUe.fj=function(){return Pp(this.c,76).fj()},eUe.tj=function(e,t,n){var r;return(r=Pp(etP(this.b).Nh().Jh(this.b),133)).Th(e),r.Uh(t),r.ed(n),r},eUe.uj=function(){return new pZ(this)},eUe.Wb=function(e){eai(this,e)},eUe.Xj=function(){Pp(this.c,76).Xj()},Y5(eJz,"EcoreEMap",151),eTS(158,151,eJW,L_),eUe.qj=function(){var e,t,n,r,i,a;if(null==this.d){for(a=Je(e6C,eJA,63,2*this.f+1,0,1),n=this.c.Kc();n.e!=n.i.gc();)(e=a[i=((r=(t=Pp(n.nj(),133)).Sh())&eUu)%a.length])||(e=a[i]=new pZ(this)),e.Fc(t);this.d=a}},Y5(eZ2,"EAnnotationImpl/1",158),eTS(284,438,{105:1,92:1,90:1,147:1,191:1,56:1,108:1,472:1,49:1,97:1,150:1,284:1,114:1,115:1}),eUe._g=function(e,t,n){var r,i;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.zb;case 2:return OQ(),(256&this.Bb)!=0;case 3:return OQ(),(512&this.Bb)!=0;case 4:return ell(this.s);case 5:return ell(this.t);case 6:return OQ(),!!this.$j();case 7:return OQ(),(i=this.s)>=1;case 8:if(t)return evl(this);return this.r;case 9:return this.q}return Qt(this,e-Y1(this.zh()),ee2((r=Pp(eaS(this,16),26))||this.zh(),e),t,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 9:return Y3(this,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||this.zh(),t),66)).Nj().Rj(this,ehH(this),t-Y1(this.zh()),e,n)},eUe.lh=function(e){var t,n;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return(256&this.Bb)==0;case 3:return(512&this.Bb)==0;case 4:return 0!=this.s;case 5:return 1!=this.t;case 6:return this.$j();case 7:return(n=this.s)>=1;case 8:return!!this.r&&!this.q.e&&0==BX(this.q).i;case 9:return!!this.q&&!(this.r&&!this.q.e&&0==BX(this.q).i)}return VP(this,e-Y1(this.zh()),ee2((t=Pp(eaS(this,16),26))||this.zh(),e))},eUe.sh=function(e,t){var n,r;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:this.Lh(Lq(t));return;case 2:eli(this,gN(LK(t)));return;case 3:els(this,gN(LK(t)));return;case 4:end(this,Pp(t,19).a);return;case 5:this.ok(Pp(t,19).a);return;case 8:eu2(this,Pp(t,138));return;case 9:(r=ew3(this,Pp(t,87),null))&&r.Fi();return}efL(this,e-Y1(this.zh()),ee2((n=Pp(eaS(this,16),26))||this.zh(),e),t)},eUe.zh=function(){return eBK(),tgB},eUe.Bh=function(e){var t,n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:this.Lh(null);return;case 2:eli(this,!0);return;case 3:els(this,!0);return;case 4:end(this,0);return;case 5:this.ok(1);return;case 8:eu2(this,null);return;case 9:(n=ew3(this,null,null))&&n.Fi();return}ec6(this,e-Y1(this.zh()),ee2((t=Pp(eaS(this,16),26))||this.zh(),e))},eUe.Gh=function(){evl(this),this.Bb|=1},eUe.Yj=function(){return evl(this)},eUe.Zj=function(){return this.t},eUe.$j=function(){var e;return(e=this.t)>1||-1==e},eUe.hi=function(){return(512&this.Bb)!=0},eUe.nk=function(e,t){return ecz(this,e,t)},eUe.ok=function(e){enh(this,e)},eUe.Ib=function(){return ex3(this)},eUe.s=0,eUe.t=1,Y5(eZ2,"ETypedElementImpl",284),eTS(449,284,{105:1,92:1,90:1,147:1,191:1,56:1,170:1,66:1,108:1,472:1,49:1,97:1,150:1,449:1,284:1,114:1,115:1,677:1}),eUe.Qg=function(e){return egx(this,e)},eUe._g=function(e,t,n){var r,i;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.zb;case 2:return OQ(),(256&this.Bb)!=0;case 3:return OQ(),(512&this.Bb)!=0;case 4:return ell(this.s);case 5:return ell(this.t);case 6:return OQ(),!!this.$j();case 7:return OQ(),(i=this.s)>=1;case 8:if(t)return evl(this);return this.r;case 9:return this.q;case 10:return OQ(),(this.Bb&eXt)!=0;case 11:return OQ(),(this.Bb&eJq)!=0;case 12:return OQ(),(this.Bb&eH0)!=0;case 13:return this.j;case 14:return eOI(this);case 15:return OQ(),(this.Bb&eJV)!=0;case 16:return OQ(),(this.Bb&eUR)!=0;case 17:return z6(this)}return Qt(this,e-Y1(this.zh()),ee2((r=Pp(eaS(this,16),26))||this.zh(),e),t,n)},eUe.hh=function(e,t,n){var r,i,a;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),edF(this.Ab,e,n);case 17:return this.Cb&&(n=(i=this.Db>>16)>=0?egx(this,n):this.Cb.ih(this,-1-i,null,n)),eDg(this,e,17,n)}return(a=Pp(ee2((r=Pp(eaS(this,16),26))||this.zh(),t),66)).Nj().Qj(this,ehH(this),t-Y1(this.zh()),e,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 9:return Y3(this,n);case 17:return eDg(this,null,17,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||this.zh(),t),66)).Nj().Rj(this,ehH(this),t-Y1(this.zh()),e,n)},eUe.lh=function(e){var t,n;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return(256&this.Bb)==0;case 3:return(512&this.Bb)==0;case 4:return 0!=this.s;case 5:return 1!=this.t;case 6:return this.$j();case 7:return(n=this.s)>=1;case 8:return!!this.r&&!this.q.e&&0==BX(this.q).i;case 9:return!!this.q&&!(this.r&&!this.q.e&&0==BX(this.q).i);case 10:return(this.Bb&eXt)==0;case 11:return(this.Bb&eJq)!=0;case 12:return(this.Bb&eH0)!=0;case 13:return null!=this.j;case 14:return null!=eOI(this);case 15:return(this.Bb&eJV)!=0;case 16:return(this.Bb&eUR)!=0;case 17:return!!z6(this)}return VP(this,e-Y1(this.zh()),ee2((t=Pp(eaS(this,16),26))||this.zh(),e))},eUe.sh=function(e,t){var n,r;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:GD(this,Lq(t));return;case 2:eli(this,gN(LK(t)));return;case 3:els(this,gN(LK(t)));return;case 4:end(this,Pp(t,19).a);return;case 5:this.ok(Pp(t,19).a);return;case 8:eu2(this,Pp(t,138));return;case 9:(r=ew3(this,Pp(t,87),null))&&r.Fi();return;case 10:elF(this,gN(LK(t)));return;case 11:elU(this,gN(LK(t)));return;case 12:elY(this,gN(LK(t)));return;case 13:xi(this,Lq(t));return;case 15:elB(this,gN(LK(t)));return;case 16:elZ(this,gN(LK(t)));return}efL(this,e-Y1(this.zh()),ee2((n=Pp(eaS(this,16),26))||this.zh(),e),t)},eUe.zh=function(){return eBK(),tgY},eUe.Bh=function(e){var t,n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:M4(this.Cb,88)&&eko(Zd(Pp(this.Cb,88)),4),er3(this,null);return;case 2:eli(this,!0);return;case 3:els(this,!0);return;case 4:end(this,0);return;case 5:this.ok(1);return;case 8:eu2(this,null);return;case 9:(n=ew3(this,null,null))&&n.Fi();return;case 10:elF(this,!0);return;case 11:elU(this,!1);return;case 12:elY(this,!1);return;case 13:this.i=null,erA(this,null);return;case 15:elB(this,!1);return;case 16:elZ(this,!1);return}ec6(this,e-Y1(this.zh()),ee2((t=Pp(eaS(this,16),26))||this.zh(),e))},eUe.Gh=function(){UH(QZ((eSp(),tvc),this)),evl(this),this.Bb|=1},eUe.Gj=function(){return this.f},eUe.zj=function(){return eOI(this)},eUe.Hj=function(){return z6(this)},eUe.Lj=function(){return null},eUe.pk=function(){return this.k},eUe.aj=function(){return this.n},eUe.Mj=function(){return eyD(this)},eUe.Nj=function(){var e,t,n,r,i,a,o,s,u;return this.p||((null==(n=z6(this)).i&&eNT(n),n.i).length,(r=this.Lj())&&Y1(z6(r)),e=(o=(i=evl(this)).Bj())?(1&o.i)!=0?o==tyE?e11:o==ty_?e15:o==tyT?e14:o==tyx?e13:o==tyS?e16:o==tyM?e19:o==tyk?e10:e12:o:null,t=eOI(this),s=i.zj(),efl(this),(this.Bb&eUR)!=0&&((a=ev1((eSp(),tvc),n))&&a!=this||(a=Wk(QZ(tvc,this))))?this.p=new k6(this,a):this.$j()?this.rk()?r?(this.Bb&eJV)!=0?e?this.sk()?this.p=new HS(47,e,this,r):this.p=new HS(5,e,this,r):this.sk()?this.p=new qc(46,this,r):this.p=new qc(4,this,r):e?this.sk()?this.p=new HS(49,e,this,r):this.p=new HS(7,e,this,r):this.sk()?this.p=new qc(48,this,r):this.p=new qc(6,this,r):(this.Bb&eJV)!=0?e?e==e1$?this.p=new Pe(50,e6M,this):this.sk()?this.p=new Pe(43,e,this):this.p=new Pe(1,e,this):this.sk()?this.p=new $F(42,this):this.p=new $F(0,this):e?e==e1$?this.p=new Pe(41,e6M,this):this.sk()?this.p=new Pe(45,e,this):this.p=new Pe(3,e,this):this.sk()?this.p=new $F(44,this):this.p=new $F(2,this):M4(i,148)?e==tgc?this.p=new $F(40,this):(512&this.Bb)!=0?(this.Bb&eJV)!=0?e?this.p=new Pe(9,e,this):this.p=new $F(8,this):e?this.p=new Pe(11,e,this):this.p=new $F(10,this):(this.Bb&eJV)!=0?e?this.p=new Pe(13,e,this):this.p=new $F(12,this):e?this.p=new Pe(15,e,this):this.p=new $F(14,this):r?(u=r.t)>1||-1==u?this.sk()?(this.Bb&eJV)!=0?e?this.p=new HS(25,e,this,r):this.p=new qc(24,this,r):e?this.p=new HS(27,e,this,r):this.p=new qc(26,this,r):(this.Bb&eJV)!=0?e?this.p=new HS(29,e,this,r):this.p=new qc(28,this,r):e?this.p=new HS(31,e,this,r):this.p=new qc(30,this,r):this.sk()?(this.Bb&eJV)!=0?e?this.p=new HS(33,e,this,r):this.p=new qc(32,this,r):e?this.p=new HS(35,e,this,r):this.p=new qc(34,this,r):(this.Bb&eJV)!=0?e?this.p=new HS(37,e,this,r):this.p=new qc(36,this,r):e?this.p=new HS(39,e,this,r):this.p=new qc(38,this,r):this.sk()?(this.Bb&eJV)!=0?e?this.p=new Pe(17,e,this):this.p=new $F(16,this):e?this.p=new Pe(19,e,this):this.p=new $F(18,this):(this.Bb&eJV)!=0?e?this.p=new Pe(21,e,this):this.p=new $F(20,this):e?this.p=new Pe(23,e,this):this.p=new $F(22,this):this.qk()?this.sk()?this.p=new Pt(Pp(i,26),this,r):this.p=new zl(Pp(i,26),this,r):M4(i,148)?e==tgc?this.p=new $F(40,this):(this.Bb&eJV)!=0?e?this.p=new j9(t,s,this,(edO(),o==ty_?tg2:o==tyE?tgX:o==tyS?tg3:o==tyT?tg0:o==tyx?tg1:o==tyM?tg5:o==tyk?tgJ:o==tyw?tgQ:tg4)):this.p=new HT(Pp(i,148),t,s,this):e?this.p=new j6(t,s,this,(edO(),o==ty_?tg2:o==tyE?tgX:o==tyS?tg3:o==tyT?tg0:o==tyx?tg1:o==tyM?tg5:o==tyk?tgJ:o==tyw?tgQ:tg4)):this.p=new Hx(Pp(i,148),t,s,this):this.rk()?r?(this.Bb&eJV)!=0?this.sk()?this.p=new Ps(Pp(i,26),this,r):this.p=new Po(Pp(i,26),this,r):this.sk()?this.p=new Pa(Pp(i,26),this,r):this.p=new Pn(Pp(i,26),this,r):(this.Bb&eJV)!=0?this.sk()?this.p=new Lx(Pp(i,26),this):this.p=new Lk(Pp(i,26),this):this.sk()?this.p=new LS(Pp(i,26),this):this.p=new LE(Pp(i,26),this):this.sk()?r?(this.Bb&eJV)!=0?this.p=new Pu(Pp(i,26),this,r):this.p=new Pr(Pp(i,26),this,r):(this.Bb&eJV)!=0?this.p=new LM(Pp(i,26),this):this.p=new LT(Pp(i,26),this):r?(this.Bb&eJV)!=0?this.p=new Pc(Pp(i,26),this,r):this.p=new Pi(Pp(i,26),this,r):(this.Bb&eJV)!=0?this.p=new LO(Pp(i,26),this):this.p=new jd(Pp(i,26),this)),this.p},eUe.Ij=function(){return(this.Bb&eXt)!=0},eUe.qk=function(){return!1},eUe.rk=function(){return!1},eUe.Jj=function(){return(this.Bb&eUR)!=0},eUe.Oj=function(){return eec(this)},eUe.sk=function(){return!1},eUe.Kj=function(){return(this.Bb&eJV)!=0},eUe.tk=function(e){this.k=e},eUe.Lh=function(e){GD(this,e)},eUe.Ib=function(){return eCR(this)},eUe.e=!1,eUe.n=0,Y5(eZ2,"EStructuralFeatureImpl",449),eTS(322,449,{105:1,92:1,90:1,34:1,147:1,191:1,56:1,170:1,66:1,108:1,472:1,49:1,97:1,322:1,150:1,449:1,284:1,114:1,115:1,677:1},mC),eUe._g=function(e,t,n){var r,i;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.zb;case 2:return OQ(),(256&this.Bb)!=0;case 3:return OQ(),(512&this.Bb)!=0;case 4:return ell(this.s);case 5:return ell(this.t);case 6:return OQ(),!!ek7(this);case 7:return OQ(),(i=this.s)>=1;case 8:if(t)return evl(this);return this.r;case 9:return this.q;case 10:return OQ(),(this.Bb&eXt)!=0;case 11:return OQ(),(this.Bb&eJq)!=0;case 12:return OQ(),(this.Bb&eH0)!=0;case 13:return this.j;case 14:return eOI(this);case 15:return OQ(),(this.Bb&eJV)!=0;case 16:return OQ(),(this.Bb&eUR)!=0;case 17:return z6(this);case 18:return OQ(),(this.Bb&eZ1)!=0;case 19:if(t)return eoe(this);return Xl(this)}return Qt(this,e-Y1((eBK(),tgw)),ee2((r=Pp(eaS(this,16),26))||tgw,e),t,n)},eUe.lh=function(e){var t,n;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return(256&this.Bb)==0;case 3:return(512&this.Bb)==0;case 4:return 0!=this.s;case 5:return 1!=this.t;case 6:return ek7(this);case 7:return(n=this.s)>=1;case 8:return!!this.r&&!this.q.e&&0==BX(this.q).i;case 9:return!!this.q&&!(this.r&&!this.q.e&&0==BX(this.q).i);case 10:return(this.Bb&eXt)==0;case 11:return(this.Bb&eJq)!=0;case 12:return(this.Bb&eH0)!=0;case 13:return null!=this.j;case 14:return null!=eOI(this);case 15:return(this.Bb&eJV)!=0;case 16:return(this.Bb&eUR)!=0;case 17:return!!z6(this);case 18:return(this.Bb&eZ1)!=0;case 19:return!!Xl(this)}return VP(this,e-Y1((eBK(),tgw)),ee2((t=Pp(eaS(this,16),26))||tgw,e))},eUe.sh=function(e,t){var n,r;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:GD(this,Lq(t));return;case 2:eli(this,gN(LK(t)));return;case 3:els(this,gN(LK(t)));return;case 4:end(this,Pp(t,19).a);return;case 5:yg(this,Pp(t,19).a);return;case 8:eu2(this,Pp(t,138));return;case 9:(r=ew3(this,Pp(t,87),null))&&r.Fi();return;case 10:elF(this,gN(LK(t)));return;case 11:elU(this,gN(LK(t)));return;case 12:elY(this,gN(LK(t)));return;case 13:xi(this,Lq(t));return;case 15:elB(this,gN(LK(t)));return;case 16:elZ(this,gN(LK(t)));return;case 18:elX(this,gN(LK(t)));return}efL(this,e-Y1((eBK(),tgw)),ee2((n=Pp(eaS(this,16),26))||tgw,e),t)},eUe.zh=function(){return eBK(),tgw},eUe.Bh=function(e){var t,n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:M4(this.Cb,88)&&eko(Zd(Pp(this.Cb,88)),4),er3(this,null);return;case 2:eli(this,!0);return;case 3:els(this,!0);return;case 4:end(this,0);return;case 5:this.b=0,enh(this,1);return;case 8:eu2(this,null);return;case 9:(n=ew3(this,null,null))&&n.Fi();return;case 10:elF(this,!0);return;case 11:elU(this,!1);return;case 12:elY(this,!1);return;case 13:this.i=null,erA(this,null);return;case 15:elB(this,!1);return;case 16:elZ(this,!1);return;case 18:elX(this,!1);return}ec6(this,e-Y1((eBK(),tgw)),ee2((t=Pp(eaS(this,16),26))||tgw,e))},eUe.Gh=function(){eoe(this),UH(QZ((eSp(),tvc),this)),evl(this),this.Bb|=1},eUe.$j=function(){return ek7(this)},eUe.nk=function(e,t){return this.b=0,this.a=null,ecz(this,e,t)},eUe.ok=function(e){yg(this,e)},eUe.Ib=function(){var e;return(64&this.Db)!=0?eCR(this):(e=new O1(eCR(this)),e.a+=" (iD: ",yG(e,(this.Bb&eZ1)!=0),e.a+=")",e.a)},eUe.b=0,Y5(eZ2,"EAttributeImpl",322),eTS(351,438,{105:1,92:1,90:1,138:1,147:1,191:1,56:1,108:1,49:1,97:1,351:1,150:1,114:1,115:1,676:1}),eUe.uk=function(e){return e.Tg()==this},eUe.Qg=function(e){return egn(this,e)},eUe.Rg=function(e,t){this.w=null,this.Db=t<<16|255&this.Db,this.Cb=e},eUe._g=function(e,t,n){var r;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.zb;case 2:return null!=this.D?this.D:this.B;case 3:return em4(this);case 4:return this.zj();case 5:return this.F;case 6:if(t)return etP(this);return z5(this);case 7:return this.A||(this.A=new OS(tgs,this,7)),this.A}return Qt(this,e-Y1(this.zh()),ee2((r=Pp(eaS(this,16),26))||this.zh(),e),t,n)},eUe.hh=function(e,t,n){var r,i,a;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),edF(this.Ab,e,n);case 6:return this.Cb&&(n=(i=this.Db>>16)>=0?egn(this,n):this.Cb.ih(this,-1-i,null,n)),eDg(this,e,6,n)}return(a=Pp(ee2((r=Pp(eaS(this,16),26))||this.zh(),t),66)).Nj().Qj(this,ehH(this),t-Y1(this.zh()),e,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 6:return eDg(this,null,6,n);case 7:return this.A||(this.A=new OS(tgs,this,7)),ep6(this.A,e,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||this.zh(),t),66)).Nj().Rj(this,ehH(this),t-Y1(this.zh()),e,n)},eUe.lh=function(e){var t;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return null!=this.D&&this.D==this.F;case 3:return!!em4(this);case 4:return null!=this.zj();case 5:return null!=this.F&&this.F!=this.D&&this.F!=this.B;case 6:return!!z5(this);case 7:return!!this.A&&0!=this.A.i}return VP(this,e-Y1(this.zh()),ee2((t=Pp(eaS(this,16),26))||this.zh(),e))},eUe.sh=function(e,t){var n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:GN(this,Lq(t));return;case 2:TF(this,Lq(t));return;case 5:eji(this,Lq(t));return;case 7:this.A||(this.A=new OS(tgs,this,7)),eRT(this.A),this.A||(this.A=new OS(tgs,this,7)),Y4(this.A,Pp(t,14));return}efL(this,e-Y1(this.zh()),ee2((n=Pp(eaS(this,16),26))||this.zh(),e),t)},eUe.zh=function(){return eBK(),tgE},eUe.Bh=function(e){var t;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:M4(this.Cb,179)&&(Pp(this.Cb,179).tb=null),er3(this,null);return;case 2:euc(this,null),enp(this,this.D);return;case 5:eji(this,null);return;case 7:this.A||(this.A=new OS(tgs,this,7)),eRT(this.A);return}ec6(this,e-Y1(this.zh()),ee2((t=Pp(eaS(this,16),26))||this.zh(),e))},eUe.yj=function(){var e;return -1==this.G&&(this.G=(e=etP(this))?ebv(e.Mh(),this):-1),this.G},eUe.zj=function(){return null},eUe.Aj=function(){return etP(this)},eUe.vk=function(){return this.v},eUe.Bj=function(){return em4(this)},eUe.Cj=function(){return null!=this.D?this.D:this.B},eUe.Dj=function(){return this.F},eUe.wj=function(e){return eNc(this,e)},eUe.wk=function(e){this.v=e},eUe.xk=function(e){eia(this,e)},eUe.yk=function(e){this.C=e},eUe.Lh=function(e){GN(this,e)},eUe.Ib=function(){return edb(this)},eUe.C=null,eUe.D=null,eUe.G=-1,Y5(eZ2,"EClassifierImpl",351),eTS(88,351,{105:1,92:1,90:1,26:1,138:1,147:1,191:1,56:1,108:1,49:1,97:1,88:1,351:1,150:1,473:1,114:1,115:1,676:1},c0),eUe.uk=function(e){return C7(this,e.Tg())},eUe._g=function(e,t,n){var r;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.zb;case 2:return null!=this.D?this.D:this.B;case 3:return em4(this);case 4:return null;case 5:return this.F;case 6:if(t)return etP(this);return z5(this);case 7:return this.A||(this.A=new OS(tgs,this,7)),this.A;case 8:return OQ(),(256&this.Bb)!=0;case 9:return OQ(),(512&this.Bb)!=0;case 10:return $E(this);case 11:return this.q||(this.q=new FQ(tgi,this,11,10)),this.q;case 12:return ePk(this);case 13:return ePl(this);case 14:return ePl(this),this.r;case 15:return ePk(this),this.k;case 16:return eSD(this);case 17:return eNQ(this);case 18:return eNT(this);case 19:return eOg(this);case 20:return ePk(this),this.o;case 21:return this.s||(this.s=new FQ(tm6,this,21,17)),this.s;case 22:return qt(this);case 23:return eCt(this)}return Qt(this,e-Y1((eBK(),tg_)),ee2((r=Pp(eaS(this,16),26))||tg_,e),t,n)},eUe.hh=function(e,t,n){var r,i,a;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),edF(this.Ab,e,n);case 6:return this.Cb&&(n=(i=this.Db>>16)>=0?egn(this,n):this.Cb.ih(this,-1-i,null,n)),eDg(this,e,6,n);case 11:return this.q||(this.q=new FQ(tgi,this,11,10)),edF(this.q,e,n);case 21:return this.s||(this.s=new FQ(tm6,this,21,17)),edF(this.s,e,n)}return(a=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tg_),t),66)).Nj().Qj(this,ehH(this),t-Y1((eBK(),tg_)),e,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 6:return eDg(this,null,6,n);case 7:return this.A||(this.A=new OS(tgs,this,7)),ep6(this.A,e,n);case 11:return this.q||(this.q=new FQ(tgi,this,11,10)),ep6(this.q,e,n);case 21:return this.s||(this.s=new FQ(tm6,this,21,17)),ep6(this.s,e,n);case 22:return ep6(qt(this),e,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tg_),t),66)).Nj().Rj(this,ehH(this),t-Y1((eBK(),tg_)),e,n)},eUe.lh=function(e){var t;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return null!=this.D&&this.D==this.F;case 3:return!!em4(this);case 4:return!1;case 5:return null!=this.F&&this.F!=this.D&&this.F!=this.B;case 6:return!!z5(this);case 7:return!!this.A&&0!=this.A.i;case 8:return(256&this.Bb)!=0;case 9:return(512&this.Bb)!=0;case 10:return!!this.u&&0!=qt(this.u.a).i&&!(this.n&&ebV(this.n));case 11:return!!this.q&&0!=this.q.i;case 12:return 0!=ePk(this).i;case 13:return 0!=ePl(this).i;case 14:return ePl(this),0!=this.r.i;case 15:return ePk(this),0!=this.k.i;case 16:return 0!=eSD(this).i;case 17:return 0!=eNQ(this).i;case 18:return 0!=eNT(this).i;case 19:return 0!=eOg(this).i;case 20:return ePk(this),!!this.o;case 21:return!!this.s&&0!=this.s.i;case 22:return!!this.n&&ebV(this.n);case 23:return 0!=eCt(this).i}return VP(this,e-Y1((eBK(),tg_)),ee2((t=Pp(eaS(this,16),26))||tg_,e))},eUe.oh=function(e){var t;return(t=null==this.i||this.q&&0!=this.q.i?null:eAh(this,e))||eF9(this,e)},eUe.sh=function(e,t){var n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:GN(this,Lq(t));return;case 2:TF(this,Lq(t));return;case 5:eji(this,Lq(t));return;case 7:this.A||(this.A=new OS(tgs,this,7)),eRT(this.A),this.A||(this.A=new OS(tgs,this,7)),Y4(this.A,Pp(t,14));return;case 8:ela(this,gN(LK(t)));return;case 9:elu(this,gN(LK(t)));return;case 10:eRP($E(this)),Y4($E(this),Pp(t,14));return;case 11:this.q||(this.q=new FQ(tgi,this,11,10)),eRT(this.q),this.q||(this.q=new FQ(tgi,this,11,10)),Y4(this.q,Pp(t,14));return;case 21:this.s||(this.s=new FQ(tm6,this,21,17)),eRT(this.s),this.s||(this.s=new FQ(tm6,this,21,17)),Y4(this.s,Pp(t,14));return;case 22:eRT(qt(this)),Y4(qt(this),Pp(t,14));return}efL(this,e-Y1((eBK(),tg_)),ee2((n=Pp(eaS(this,16),26))||tg_,e),t)},eUe.zh=function(){return eBK(),tg_},eUe.Bh=function(e){var t;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:M4(this.Cb,179)&&(Pp(this.Cb,179).tb=null),er3(this,null);return;case 2:euc(this,null),enp(this,this.D);return;case 5:eji(this,null);return;case 7:this.A||(this.A=new OS(tgs,this,7)),eRT(this.A);return;case 8:ela(this,!1);return;case 9:elu(this,!1);return;case 10:this.u&&eRP(this.u);return;case 11:this.q||(this.q=new FQ(tgi,this,11,10)),eRT(this.q);return;case 21:this.s||(this.s=new FQ(tm6,this,21,17)),eRT(this.s);return;case 22:this.n&&eRT(this.n);return}ec6(this,e-Y1((eBK(),tg_)),ee2((t=Pp(eaS(this,16),26))||tg_,e))},eUe.Gh=function(){var e,t;if(ePk(this),ePl(this),eSD(this),eNQ(this),eNT(this),eOg(this),eCt(this),ZG(Pw(Zd(this))),this.s)for(e=0,t=this.s.i;e=0;--t)etj(this,t);return edj(this,e)},eUe.Xj=function(){eRT(this)},eUe.oi=function(e,t){return env(this,e,t)},Y5(eJz,"EcoreEList",622),eTS(496,622,eJ9,PK),eUe.ai=function(){return!1},eUe.aj=function(){return this.c},eUe.bj=function(){return!1},eUe.Fk=function(){return!0},eUe.hi=function(){return!0},eUe.li=function(e,t){return t},eUe.ni=function(){return!1},eUe.c=0,Y5(eJz,"EObjectEList",496),eTS(85,496,eJ9,O_),eUe.bj=function(){return!0},eUe.Dk=function(){return!1},eUe.rk=function(){return!0},Y5(eJz,"EObjectContainmentEList",85),eTS(545,85,eJ9,OE),eUe.ci=function(){this.b=!0},eUe.fj=function(){return this.b},eUe.Xj=function(){var e;eRT(this),TO(this.e)?(e=this.b,this.b=!1,eam(this.e,new ZB(this.e,2,this.c,e,!1))):this.b=!1},eUe.b=!1,Y5(eJz,"EObjectContainmentEList/Unsettable",545),eTS(1140,545,eJ9,j4),eUe.ii=function(e,t){var n,r;return n=Pp(elR(this,e,t),87),TO(this.e)&&bz(this,new JU(this.a,7,(eBK(),tgS),ell(t),M4(r=n.c,88)?Pp(r,26):tgI,e)),n},eUe.jj=function(e,t){return edB(this,Pp(e,87),t)},eUe.kj=function(e,t){return edY(this,Pp(e,87),t)},eUe.lj=function(e,t,n){return eyl(this,Pp(e,87),Pp(t,87),n)},eUe.Zi=function(e,t,n,r,i){switch(e){case 3:return Gt(this,e,t,n,r,this.i>1);case 5:return Gt(this,e,t,n,r,this.i-Pp(n,15).gc()>0);default:return new Q$(this.e,e,this.c,t,n,r,!0)}},eUe.ij=function(){return!0},eUe.fj=function(){return ebV(this)},eUe.Xj=function(){eRT(this)},Y5(eZ2,"EClassImpl/1",1140),eTS(1154,1153,eJS),eUe.ui=function(e){var t,n,r,i,a,o,s;if(8!=(n=e.xi())){if(0==(r=epM(e)))switch(n){case 1:case 9:null!=(s=e.Bi())&&((t=Zd(Pp(s,473))).c||(t.c=new sk),eeu(t.c,e.Ai())),null!=(o=e.zi())&&(1&(i=Pp(o,473)).Bb)==0&&((t=Zd(i)).c||(t.c=new sk),JL(t.c,Pp(e.Ai(),26)));break;case 3:null!=(o=e.zi())&&(1&(i=Pp(o,473)).Bb)==0&&((t=Zd(i)).c||(t.c=new sk),JL(t.c,Pp(e.Ai(),26)));break;case 5:if(null!=(o=e.zi()))for(a=Pp(o,14).Kc();a.Ob();)(1&(i=Pp(a.Pb(),473)).Bb)==0&&((t=Zd(i)).c||(t.c=new sk),JL(t.c,Pp(e.Ai(),26)));break;case 4:null!=(s=e.Bi())&&(1&(i=Pp(s,473)).Bb)==0&&((t=Zd(i)).c||(t.c=new sk),eeu(t.c,e.Ai()));break;case 6:if(null!=(s=e.Bi()))for(a=Pp(s,14).Kc();a.Ob();)(1&(i=Pp(a.Pb(),473)).Bb)==0&&((t=Zd(i)).c||(t.c=new sk),eeu(t.c,e.Ai()))}this.Hk(r)}},eUe.Hk=function(e){eCO(this,e)},eUe.b=63,Y5(eZ2,"ESuperAdapter",1154),eTS(1155,1154,eJS,pR),eUe.Hk=function(e){eko(this,e)},Y5(eZ2,"EClassImpl/10",1155),eTS(1144,696,eJ9),eUe.Vh=function(e,t){return ew2(this,e,t)},eUe.Wh=function(e){return emp(this,e)},eUe.Xh=function(e,t){ecW(this,e,t)},eUe.Yh=function(e){Zz(this,e)},eUe.pi=function(e){return J5(this,e)},eUe.mi=function(e,t){return ees(this,e,t)},eUe.lk=function(e,t){throw p7(new bO)},eUe.Zh=function(){return new AY(this)},eUe.$h=function(){return new AB(this)},eUe._h=function(e){return enH(this,e)},eUe.mk=function(e,t){throw p7(new bO)},eUe.Wj=function(e){return this},eUe.fj=function(){return 0!=this.i},eUe.Wb=function(e){throw p7(new bO)},eUe.Xj=function(){throw p7(new bO)},Y5(eJz,"EcoreEList/UnmodifiableEList",1144),eTS(319,1144,eJ9,xQ),eUe.ni=function(){return!1},Y5(eJz,"EcoreEList/UnmodifiableEList/FastCompare",319),eTS(1147,319,eJ9,eo8),eUe.Xc=function(e){var t,n,r;if(M4(e,170)&&-1!=(n=(t=Pp(e,170)).aj())){for(r=this.i;n4){if(!this.wj(e))return!1;if(this.rk()){if(s=(n=(r=Pp(e,49)).Ug())==this.b&&(this.Dk()?r.Og(r.Vg(),Pp(ee2($S(this.b),this.aj()).Yj(),26).Bj())==ebY(Pp(ee2($S(this.b),this.aj()),18)).n:-1-r.Vg()==this.aj()),this.Ek()&&!s&&!n&&r.Zg()){for(i=0;i1||-1==r)},eUe.Dk=function(){var e,t,n;return t=ee2($S(this.b),this.aj()),!!M4(t,99)&&!!(n=ebY(e=Pp(t,18)))},eUe.Ek=function(){var e,t;return t=ee2($S(this.b),this.aj()),!!M4(t,99)&&((e=Pp(t,18)).Bb&eH3)!=0},eUe.Xc=function(e){var t,n,r,i;if((r=this.Qi(e))>=0)return r;if(this.Fk()){for(n=0,i=this.Vi();n=0;--e)ejc(this,e,this.Oi(e));return this.Wi()},eUe.Qc=function(e){var t;if(this.Ek())for(t=this.Vi()-1;t>=0;--t)ejc(this,t,this.Oi(t));return this.Xi(e)},eUe.Xj=function(){eRP(this)},eUe.oi=function(e,t){return J6(this,e,t)},Y5(eJz,"DelegatingEcoreEList",742),eTS(1150,742,eQn,Cw),eUe.Hi=function(e,t){LP(this,e,Pp(t,26))},eUe.Ii=function(e){Mt(this,Pp(e,26))},eUe.Oi=function(e){var t,n;return n=(t=Pp(etj(qt(this.a),e),87)).c,M4(n,88)?Pp(n,26):(eBK(),tgI)},eUe.Ti=function(e){var t,n;return n=(t=Pp(eLN(qt(this.a),e),87)).c,M4(n,88)?Pp(n,26):(eBK(),tgI)},eUe.Ui=function(e,t){return emm(this,e,Pp(t,26))},eUe.ai=function(){return!1},eUe.Zi=function(e,t,n,r,i){return null},eUe.Ji=function(){return new pF(this)},eUe.Ki=function(){eRT(qt(this.a))},eUe.Li=function(e){return ec7(this,e)},eUe.Mi=function(e){var t,n;for(n=e.Kc();n.Ob();)if(!ec7(this,t=n.Pb()))return!1;return!0},eUe.Ni=function(e){var t,n,r;if(M4(e,15)&&(r=Pp(e,15)).gc()==qt(this.a).i){for(t=r.Kc(),n=new Ow(this);t.Ob();)if(xc(t.Pb())!==xc(epH(n)))return!1;return!0}return!1},eUe.Pi=function(){var e,t,n,r,i;for(n=1,t=new Ow(qt(this.a));t.e!=t.i.gc();)e=Pp(epH(t),87),r=M4(i=e.c,88)?Pp(i,26):(eBK(),tgI),n=31*n+(r?Ao(r):0);return n},eUe.Qi=function(e){var t,n,r,i;for(r=0,n=new Ow(qt(this.a));n.e!=n.i.gc();){if(t=Pp(epH(n),87),xc(e)===xc(M4(i=t.c,88)?Pp(i,26):(eBK(),tgI)))return r;++r}return -1},eUe.Ri=function(){return 0==qt(this.a).i},eUe.Si=function(){return null},eUe.Vi=function(){return qt(this.a).i},eUe.Wi=function(){var e,t,n,r,i,a;for(a=qt(this.a).i,i=Je(e1R,eUp,1,a,5,1),n=0,t=new Ow(qt(this.a));t.e!=t.i.gc();)e=Pp(epH(t),87),i[n++]=M4(r=e.c,88)?Pp(r,26):(eBK(),tgI);return i},eUe.Xi=function(e){var t,n,r,i,a,o,s;for(s=qt(this.a).i,e.lengths&&Bc(e,s,null),r=0,n=new Ow(qt(this.a));n.e!=n.i.gc();)t=Pp(epH(n),87),a=M4(o=t.c,88)?Pp(o,26):(eBK(),tgI),Bc(e,r++,a);return e},eUe.Yi=function(){var e,t,n,r,i;for(i=new vs,i.a+="[",e=qt(this.a),t=0,r=qt(this.a).i;t>16)>=0?egn(this,n):this.Cb.ih(this,-1-i,null,n)),eDg(this,e,6,n);case 9:return this.a||(this.a=new FQ(tgn,this,9,5)),edF(this.a,e,n)}return(a=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgx),t),66)).Nj().Qj(this,ehH(this),t-Y1((eBK(),tgx)),e,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 6:return eDg(this,null,6,n);case 7:return this.A||(this.A=new OS(tgs,this,7)),ep6(this.A,e,n);case 9:return this.a||(this.a=new FQ(tgn,this,9,5)),ep6(this.a,e,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgx),t),66)).Nj().Rj(this,ehH(this),t-Y1((eBK(),tgx)),e,n)},eUe.lh=function(e){var t;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return null!=this.D&&this.D==this.F;case 3:return!!em4(this);case 4:return!!euS(this);case 5:return null!=this.F&&this.F!=this.D&&this.F!=this.B;case 6:return!!z5(this);case 7:return!!this.A&&0!=this.A.i;case 8:return(256&this.Bb)==0;case 9:return!!this.a&&0!=this.a.i}return VP(this,e-Y1((eBK(),tgx)),ee2((t=Pp(eaS(this,16),26))||tgx,e))},eUe.sh=function(e,t){var n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:GN(this,Lq(t));return;case 2:TF(this,Lq(t));return;case 5:eji(this,Lq(t));return;case 7:this.A||(this.A=new OS(tgs,this,7)),eRT(this.A),this.A||(this.A=new OS(tgs,this,7)),Y4(this.A,Pp(t,14));return;case 8:elo(this,gN(LK(t)));return;case 9:this.a||(this.a=new FQ(tgn,this,9,5)),eRT(this.a),this.a||(this.a=new FQ(tgn,this,9,5)),Y4(this.a,Pp(t,14));return}efL(this,e-Y1((eBK(),tgx)),ee2((n=Pp(eaS(this,16),26))||tgx,e),t)},eUe.zh=function(){return eBK(),tgx},eUe.Bh=function(e){var t;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:M4(this.Cb,179)&&(Pp(this.Cb,179).tb=null),er3(this,null);return;case 2:euc(this,null),enp(this,this.D);return;case 5:eji(this,null);return;case 7:this.A||(this.A=new OS(tgs,this,7)),eRT(this.A);return;case 8:elo(this,!0);return;case 9:this.a||(this.a=new FQ(tgn,this,9,5)),eRT(this.a);return}ec6(this,e-Y1((eBK(),tgx)),ee2((t=Pp(eaS(this,16),26))||tgx,e))},eUe.Gh=function(){var e,t;if(this.a)for(e=0,t=this.a.i;e>16==5?Pp(this.Cb,671):null}return Qt(this,e-Y1((eBK(),tgT)),ee2((r=Pp(eaS(this,16),26))||tgT,e),t,n)},eUe.hh=function(e,t,n){var r,i,a;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),edF(this.Ab,e,n);case 5:return this.Cb&&(n=(i=this.Db>>16)>=0?eg3(this,n):this.Cb.ih(this,-1-i,null,n)),eDg(this,e,5,n)}return(a=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgT),t),66)).Nj().Qj(this,ehH(this),t-Y1((eBK(),tgT)),e,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 5:return eDg(this,null,5,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgT),t),66)).Nj().Rj(this,ehH(this),t-Y1((eBK(),tgT)),e,n)},eUe.lh=function(e){var t;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return 0!=this.d;case 3:return!!this.b;case 4:return null!=this.c;case 5:return!!(this.Db>>16==5?Pp(this.Cb,671):null)}return VP(this,e-Y1((eBK(),tgT)),ee2((t=Pp(eaS(this,16),26))||tgT,e))},eUe.sh=function(e,t){var n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:er3(this,Lq(t));return;case 2:enf(this,Pp(t,19).a);return;case 3:exP(this,Pp(t,1940));return;case 4:erc(this,Lq(t));return}efL(this,e-Y1((eBK(),tgT)),ee2((n=Pp(eaS(this,16),26))||tgT,e),t)},eUe.zh=function(){return eBK(),tgT},eUe.Bh=function(e){var t;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:er3(this,null);return;case 2:enf(this,0);return;case 3:exP(this,null);return;case 4:erc(this,null);return}ec6(this,e-Y1((eBK(),tgT)),ee2((t=Pp(eaS(this,16),26))||tgT,e))},eUe.Ib=function(){var e;return null==(e=this.c)?this.zb:e},eUe.b=null,eUe.c=null,eUe.d=0,Y5(eZ2,"EEnumLiteralImpl",573);var tgl=RL(eZ2,"EFactoryImpl/InternalEDateTimeFormat");eTS(489,1,{2015:1},pY),Y5(eZ2,"EFactoryImpl/1ClientInternalEDateTimeFormat",489),eTS(241,115,{105:1,92:1,90:1,87:1,56:1,108:1,49:1,97:1,241:1,114:1,115:1},p5),eUe.Sg=function(e,t,n){var r;return n=eDg(this,e,t,n),this.e&&M4(e,170)&&(r=eOl(this,this.e))!=this.c&&(n=eFr(this,r,n)),n},eUe._g=function(e,t,n){var r;switch(e){case 0:return this.f;case 1:return this.d||(this.d=new O_(tgr,this,1)),this.d;case 2:if(t)return eD5(this);return this.c;case 3:return this.b;case 4:return this.e;case 5:if(t)return eb1(this);return this.a}return Qt(this,e-Y1((eBK(),tgO)),ee2((r=Pp(eaS(this,16),26))||tgO,e),t,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return ecg(this,null,n);case 1:return this.d||(this.d=new O_(tgr,this,1)),ep6(this.d,e,n);case 3:return ecm(this,null,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgO),t),66)).Nj().Rj(this,ehH(this),t-Y1((eBK(),tgO)),e,n)},eUe.lh=function(e){var t;switch(e){case 0:return!!this.f;case 1:return!!this.d&&0!=this.d.i;case 2:return!!this.c;case 3:return!!this.b;case 4:return!!this.e;case 5:return!!this.a}return VP(this,e-Y1((eBK(),tgO)),ee2((t=Pp(eaS(this,16),26))||tgO,e))},eUe.sh=function(e,t){var n;switch(e){case 0:eyK(this,Pp(t,87));return;case 1:this.d||(this.d=new O_(tgr,this,1)),eRT(this.d),this.d||(this.d=new O_(tgr,this,1)),Y4(this.d,Pp(t,14));return;case 3:eyW(this,Pp(t,87));return;case 4:e_U(this,Pp(t,836));return;case 5:etV(this,Pp(t,138));return}efL(this,e-Y1((eBK(),tgO)),ee2((n=Pp(eaS(this,16),26))||tgO,e),t)},eUe.zh=function(){return eBK(),tgO},eUe.Bh=function(e){var t;switch(e){case 0:eyK(this,null);return;case 1:this.d||(this.d=new O_(tgr,this,1)),eRT(this.d);return;case 3:eyW(this,null);return;case 4:e_U(this,null);return;case 5:etV(this,null);return}ec6(this,e-Y1((eBK(),tgO)),ee2((t=Pp(eaS(this,16),26))||tgO,e))},eUe.Ib=function(){var e;return e=new O0(eMT(this)),e.a+=" (expression: ",ePB(this,e),e.a+=")",e.a},Y5(eZ2,"EGenericTypeImpl",241),eTS(1969,1964,eQr),eUe.Xh=function(e,t){Ch(this,e,t)},eUe.lk=function(e,t){return Ch(this,this.gc(),e),t},eUe.pi=function(e){return ep3(this.Gi(),e)},eUe.Zh=function(){return this.$h()},eUe.Gi=function(){return new pV(this)},eUe.$h=function(){return this._h(0)},eUe._h=function(e){return this.Gi().Zc(e)},eUe.mk=function(e,t){return eds(this,e,!0),t},eUe.ii=function(e,t){var n,r;return r=egW(this,t),(n=this.Zc(e)).Rb(r),r},eUe.ji=function(e,t){var n;eds(this,t,!0),(n=this.Zc(e)).Rb(t)},Y5(eJz,"AbstractSequentialInternalEList",1969),eTS(486,1969,eQr,AA),eUe.pi=function(e){return ep3(this.Gi(),e)},eUe.Zh=function(){return null==this.b?(_2(),_2(),tgq):this.Jk()},eUe.Gi=function(){return new x0(this.a,this.b)},eUe.$h=function(){return null==this.b?(_2(),_2(),tgq):this.Jk()},eUe._h=function(e){var t,n;if(null==this.b){if(e<0||e>1)throw p7(new gE(eJT+e+", size=0"));return _2(),_2(),tgq}for(t=0,n=this.Jk();t0;)if(t=this.c[--this.d],(!this.e||t.Gj()!=e6d||0!=t.aj())&&(!this.Mk()||this.b.mh(t))){if(a=this.b.bh(t,this.Lk()),this.f=(_4(),Pp(t,66).Oj()),this.f||t.$j()){if(this.Lk()?(r=Pp(a,15),this.k=r):(r=Pp(a,69),this.k=this.j=r),M4(this.k,54)?(this.o=this.k.gc(),this.n=this.o):this.p=this.j?this.j._h(this.k.gc()):this.k.Zc(this.k.gc()),this.p?eSs(this,this.p):eSQ(this))return i=this.p?this.p.Ub():this.j?this.j.pi(--this.n):this.k.Xb(--this.n),this.f?((e=Pp(i,72)).ak(),n=e.dd(),this.i=n):(n=i,this.i=n),this.g=-3,!0}else if(null!=a)return this.k=null,this.p=null,n=a,this.i=n,this.g=-2,!0}return this.k=null,this.p=null,this.g=-1,!1}},eUe.Pb=function(){return eaO(this)},eUe.Tb=function(){return this.a},eUe.Ub=function(){var e;if(this.g<-1||this.Sb())return--this.a,this.g=0,e=this.i,this.Sb(),e;throw p7(new bC)},eUe.Vb=function(){return this.a-1},eUe.Qb=function(){throw p7(new bO)},eUe.Lk=function(){return!1},eUe.Wb=function(e){throw p7(new bO)},eUe.Mk=function(){return!0},eUe.a=0,eUe.d=0,eUe.f=!1,eUe.g=0,eUe.n=0,eUe.o=0,Y5(eJz,"EContentsEList/FeatureIteratorImpl",279),eTS(697,279,eQi,Lv),eUe.Lk=function(){return!0},Y5(eJz,"EContentsEList/ResolvingFeatureIteratorImpl",697),eTS(1157,697,eQi,Lw),eUe.Mk=function(){return!1},Y5(eZ2,"ENamedElementImpl/1/1",1157),eTS(1158,279,eQi,Ly),eUe.Mk=function(){return!1},Y5(eZ2,"ENamedElementImpl/1/2",1158),eTS(36,143,eJx,qo,qs,FX,JB,Q$,ZB,en_,WX,enE,WJ,Zj,WQ,enx,W1,ZF,W0,enS,W2,FJ,JU,H0,enk,W3,ZY,W4),eUe._i=function(){return JA(this)},eUe.gj=function(){var e;return(e=JA(this))?e.zj():null},eUe.yi=function(e){return -1==this.b&&this.a&&(this.b=this.c.Xg(this.a.aj(),this.a.Gj())),this.c.Og(this.b,e)},eUe.Ai=function(){return this.c},eUe.hj=function(){var e;return!!(e=JA(this))&&e.Kj()},eUe.b=-1,Y5(eZ2,"ENotificationImpl",36),eTS(399,284,{105:1,92:1,90:1,147:1,191:1,56:1,59:1,108:1,472:1,49:1,97:1,150:1,399:1,284:1,114:1,115:1},mD),eUe.Qg=function(e){return evu(this,e)},eUe._g=function(e,t,n){var r,i,a;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.zb;case 2:return OQ(),(256&this.Bb)!=0;case 3:return OQ(),(512&this.Bb)!=0;case 4:return ell(this.s);case 5:return ell(this.t);case 6:return OQ(),(a=this.t)>1||-1==a;case 7:return OQ(),(i=this.s)>=1;case 8:if(t)return evl(this);return this.r;case 9:return this.q;case 10:return this.Db>>16==10?Pp(this.Cb,26):null;case 11:return this.d||(this.d=new OS(tgs,this,11)),this.d;case 12:return this.c||(this.c=new FQ(tga,this,12,10)),this.c;case 13:return this.a||(this.a=new C_(this,this)),this.a;case 14:return QX(this)}return Qt(this,e-Y1((eBK(),tgD)),ee2((r=Pp(eaS(this,16),26))||tgD,e),t,n)},eUe.hh=function(e,t,n){var r,i,a;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),edF(this.Ab,e,n);case 10:return this.Cb&&(n=(i=this.Db>>16)>=0?evu(this,n):this.Cb.ih(this,-1-i,null,n)),eDg(this,e,10,n);case 12:return this.c||(this.c=new FQ(tga,this,12,10)),edF(this.c,e,n)}return(a=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgD),t),66)).Nj().Qj(this,ehH(this),t-Y1((eBK(),tgD)),e,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 9:return Y3(this,n);case 10:return eDg(this,null,10,n);case 11:return this.d||(this.d=new OS(tgs,this,11)),ep6(this.d,e,n);case 12:return this.c||(this.c=new FQ(tga,this,12,10)),ep6(this.c,e,n);case 14:return ep6(QX(this),e,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgD),t),66)).Nj().Rj(this,ehH(this),t-Y1((eBK(),tgD)),e,n)},eUe.lh=function(e){var t,n,r;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return(256&this.Bb)==0;case 3:return(512&this.Bb)==0;case 4:return 0!=this.s;case 5:return 1!=this.t;case 6:return(r=this.t)>1||-1==r;case 7:return(n=this.s)>=1;case 8:return!!this.r&&!this.q.e&&0==BX(this.q).i;case 9:return!!this.q&&!(this.r&&!this.q.e&&0==BX(this.q).i);case 10:return!!(this.Db>>16==10?Pp(this.Cb,26):null);case 11:return!!this.d&&0!=this.d.i;case 12:return!!this.c&&0!=this.c.i;case 13:return!!this.a&&0!=QX(this.a.a).i&&!(this.b&&ebq(this.b));case 14:return!!this.b&&ebq(this.b)}return VP(this,e-Y1((eBK(),tgD)),ee2((t=Pp(eaS(this,16),26))||tgD,e))},eUe.sh=function(e,t){var n,r;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:er3(this,Lq(t));return;case 2:eli(this,gN(LK(t)));return;case 3:els(this,gN(LK(t)));return;case 4:end(this,Pp(t,19).a);return;case 5:enh(this,Pp(t,19).a);return;case 8:eu2(this,Pp(t,138));return;case 9:(r=ew3(this,Pp(t,87),null))&&r.Fi();return;case 11:this.d||(this.d=new OS(tgs,this,11)),eRT(this.d),this.d||(this.d=new OS(tgs,this,11)),Y4(this.d,Pp(t,14));return;case 12:this.c||(this.c=new FQ(tga,this,12,10)),eRT(this.c),this.c||(this.c=new FQ(tga,this,12,10)),Y4(this.c,Pp(t,14));return;case 13:this.a||(this.a=new C_(this,this)),eRP(this.a),this.a||(this.a=new C_(this,this)),Y4(this.a,Pp(t,14));return;case 14:eRT(QX(this)),Y4(QX(this),Pp(t,14));return}efL(this,e-Y1((eBK(),tgD)),ee2((n=Pp(eaS(this,16),26))||tgD,e),t)},eUe.zh=function(){return eBK(),tgD},eUe.Bh=function(e){var t,n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:er3(this,null);return;case 2:eli(this,!0);return;case 3:els(this,!0);return;case 4:end(this,0);return;case 5:enh(this,1);return;case 8:eu2(this,null);return;case 9:(n=ew3(this,null,null))&&n.Fi();return;case 11:this.d||(this.d=new OS(tgs,this,11)),eRT(this.d);return;case 12:this.c||(this.c=new FQ(tga,this,12,10)),eRT(this.c);return;case 13:this.a&&eRP(this.a);return;case 14:this.b&&eRT(this.b);return}ec6(this,e-Y1((eBK(),tgD)),ee2((t=Pp(eaS(this,16),26))||tgD,e))},eUe.Gh=function(){var e,t;if(this.c)for(e=0,t=this.c.i;es&&Bc(e,s,null),r=0,n=new Ow(QX(this.a));n.e!=n.i.gc();)a=(o=(t=Pp(epH(n),87)).c)||(eBK(),tgA),Bc(e,r++,a);return e},eUe.Yi=function(){var e,t,n,r,i;for(i=new vs,i.a+="[",e=QX(this.a),t=0,r=QX(this.a).i;t1);case 5:return Gt(this,e,t,n,r,this.i-Pp(n,15).gc()>0);default:return new Q$(this.e,e,this.c,t,n,r,!0)}},eUe.ij=function(){return!0},eUe.fj=function(){return ebq(this)},eUe.Xj=function(){eRT(this)},Y5(eZ2,"EOperationImpl/2",1341),eTS(498,1,{1938:1,498:1},k5),Y5(eZ2,"EPackageImpl/1",498),eTS(16,85,eJ9,FQ),eUe.zk=function(){return this.d},eUe.Ak=function(){return this.b},eUe.Dk=function(){return!0},eUe.b=0,Y5(eJz,"EObjectContainmentWithInverseEList",16),eTS(353,16,eJ9,Ia),eUe.Ek=function(){return!0},eUe.li=function(e,t){return ex7(this,e,Pp(t,56))},Y5(eJz,"EObjectContainmentWithInverseEList/Resolving",353),eTS(298,353,eJ9,Fq),eUe.ci=function(){this.a.tb=null},Y5(eZ2,"EPackageImpl/2",298),eTS(1228,1,{},sh),Y5(eZ2,"EPackageImpl/3",1228),eTS(718,43,e$s,mP),eUe._b=function(e){return xd(e)?$r(this,e):!!$I(this.f,e)},Y5(eZ2,"EPackageRegistryImpl",718),eTS(509,284,{105:1,92:1,90:1,147:1,191:1,56:1,2017:1,108:1,472:1,49:1,97:1,150:1,509:1,284:1,114:1,115:1},mN),eUe.Qg=function(e){return evc(this,e)},eUe._g=function(e,t,n){var r,i,a;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.zb;case 2:return OQ(),(256&this.Bb)!=0;case 3:return OQ(),(512&this.Bb)!=0;case 4:return ell(this.s);case 5:return ell(this.t);case 6:return OQ(),(a=this.t)>1||-1==a;case 7:return OQ(),(i=this.s)>=1;case 8:if(t)return evl(this);return this.r;case 9:return this.q;case 10:return this.Db>>16==10?Pp(this.Cb,59):null}return Qt(this,e-Y1((eBK(),tgR)),ee2((r=Pp(eaS(this,16),26))||tgR,e),t,n)},eUe.hh=function(e,t,n){var r,i,a;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),edF(this.Ab,e,n);case 10:return this.Cb&&(n=(i=this.Db>>16)>=0?evc(this,n):this.Cb.ih(this,-1-i,null,n)),eDg(this,e,10,n)}return(a=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgR),t),66)).Nj().Qj(this,ehH(this),t-Y1((eBK(),tgR)),e,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 9:return Y3(this,n);case 10:return eDg(this,null,10,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgR),t),66)).Nj().Rj(this,ehH(this),t-Y1((eBK(),tgR)),e,n)},eUe.lh=function(e){var t,n,r;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return(256&this.Bb)==0;case 3:return(512&this.Bb)==0;case 4:return 0!=this.s;case 5:return 1!=this.t;case 6:return(r=this.t)>1||-1==r;case 7:return(n=this.s)>=1;case 8:return!!this.r&&!this.q.e&&0==BX(this.q).i;case 9:return!!this.q&&!(this.r&&!this.q.e&&0==BX(this.q).i);case 10:return!!(this.Db>>16==10?Pp(this.Cb,59):null)}return VP(this,e-Y1((eBK(),tgR)),ee2((t=Pp(eaS(this,16),26))||tgR,e))},eUe.zh=function(){return eBK(),tgR},Y5(eZ2,"EParameterImpl",509),eTS(99,449,{105:1,92:1,90:1,147:1,191:1,56:1,18:1,170:1,66:1,108:1,472:1,49:1,97:1,150:1,99:1,449:1,284:1,114:1,115:1,677:1},LB),eUe._g=function(e,t,n){var r,i,a,o;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.zb;case 2:return OQ(),(256&this.Bb)!=0;case 3:return OQ(),(512&this.Bb)!=0;case 4:return ell(this.s);case 5:return ell(this.t);case 6:return OQ(),(o=this.t)>1||-1==o;case 7:return OQ(),(i=this.s)>=1;case 8:if(t)return evl(this);return this.r;case 9:return this.q;case 10:return OQ(),(this.Bb&eXt)!=0;case 11:return OQ(),(this.Bb&eJq)!=0;case 12:return OQ(),(this.Bb&eH0)!=0;case 13:return this.j;case 14:return eOI(this);case 15:return OQ(),(this.Bb&eJV)!=0;case 16:return OQ(),(this.Bb&eUR)!=0;case 17:return z6(this);case 18:return OQ(),(this.Bb&eZ1)!=0;case 19:return OQ(),!!(a=ebY(this))&&(a.Bb&eZ1)!=0;case 20:return OQ(),(this.Bb&eH3)!=0;case 21:if(t)return ebY(this);return this.b;case 22:if(t)return esd(this);return ZS(this);case 23:return this.a||(this.a=new OT(tm9,this,23)),this.a}return Qt(this,e-Y1((eBK(),tgj)),ee2((r=Pp(eaS(this,16),26))||tgj,e),t,n)},eUe.lh=function(e){var t,n,r,i;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return(256&this.Bb)==0;case 3:return(512&this.Bb)==0;case 4:return 0!=this.s;case 5:return 1!=this.t;case 6:return(i=this.t)>1||-1==i;case 7:return(n=this.s)>=1;case 8:return!!this.r&&!this.q.e&&0==BX(this.q).i;case 9:return!!this.q&&!(this.r&&!this.q.e&&0==BX(this.q).i);case 10:return(this.Bb&eXt)==0;case 11:return(this.Bb&eJq)!=0;case 12:return(this.Bb&eH0)!=0;case 13:return null!=this.j;case 14:return null!=eOI(this);case 15:return(this.Bb&eJV)!=0;case 16:return(this.Bb&eUR)!=0;case 17:return!!z6(this);case 18:return(this.Bb&eZ1)!=0;case 19:return!!(r=ebY(this))&&(r.Bb&eZ1)!=0;case 20:return(this.Bb&eH3)==0;case 21:return!!this.b;case 22:return!!ZS(this);case 23:return!!this.a&&0!=this.a.i}return VP(this,e-Y1((eBK(),tgj)),ee2((t=Pp(eaS(this,16),26))||tgj,e))},eUe.sh=function(e,t){var n,r;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:GD(this,Lq(t));return;case 2:eli(this,gN(LK(t)));return;case 3:els(this,gN(LK(t)));return;case 4:end(this,Pp(t,19).a);return;case 5:enh(this,Pp(t,19).a);return;case 8:eu2(this,Pp(t,138));return;case 9:(r=ew3(this,Pp(t,87),null))&&r.Fi();return;case 10:elF(this,gN(LK(t)));return;case 11:elU(this,gN(LK(t)));return;case 12:elY(this,gN(LK(t)));return;case 13:xi(this,Lq(t));return;case 15:elB(this,gN(LK(t)));return;case 16:elZ(this,gN(LK(t)));return;case 18:GI(this,gN(LK(t)));return;case 20:elQ(this,gN(LK(t)));return;case 21:erM(this,Pp(t,18));return;case 23:this.a||(this.a=new OT(tm9,this,23)),eRT(this.a),this.a||(this.a=new OT(tm9,this,23)),Y4(this.a,Pp(t,14));return}efL(this,e-Y1((eBK(),tgj)),ee2((n=Pp(eaS(this,16),26))||tgj,e),t)},eUe.zh=function(){return eBK(),tgj},eUe.Bh=function(e){var t,n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:M4(this.Cb,88)&&eko(Zd(Pp(this.Cb,88)),4),er3(this,null);return;case 2:eli(this,!0);return;case 3:els(this,!0);return;case 4:end(this,0);return;case 5:enh(this,1);return;case 8:eu2(this,null);return;case 9:(n=ew3(this,null,null))&&n.Fi();return;case 10:elF(this,!0);return;case 11:elU(this,!1);return;case 12:elY(this,!1);return;case 13:this.i=null,erA(this,null);return;case 15:elB(this,!1);return;case 16:elZ(this,!1);return;case 18:elJ(this,!1),M4(this.Cb,88)&&eko(Zd(Pp(this.Cb,88)),2);return;case 20:elQ(this,!0);return;case 21:erM(this,null);return;case 23:this.a||(this.a=new OT(tm9,this,23)),eRT(this.a);return}ec6(this,e-Y1((eBK(),tgj)),ee2((t=Pp(eaS(this,16),26))||tgj,e))},eUe.Gh=function(){esd(this),UH(QZ((eSp(),tvc),this)),evl(this),this.Bb|=1},eUe.Lj=function(){return ebY(this)},eUe.qk=function(){var e;return!!(e=ebY(this))&&(e.Bb&eZ1)!=0},eUe.rk=function(){return(this.Bb&eZ1)!=0},eUe.sk=function(){return(this.Bb&eH3)!=0},eUe.nk=function(e,t){return this.c=null,ecz(this,e,t)},eUe.Ib=function(){var e;return(64&this.Db)!=0?eCR(this):(e=new O1(eCR(this)),e.a+=" (containment: ",yG(e,(this.Bb&eZ1)!=0),e.a+=", resolveProxies: ",yG(e,(this.Bb&eH3)!=0),e.a+=")",e.a)},Y5(eZ2,"EReferenceImpl",99),eTS(548,115,{105:1,42:1,92:1,90:1,133:1,56:1,108:1,49:1,97:1,548:1,114:1,115:1},sp),eUe.Fb=function(e){return this===e},eUe.cd=function(){return this.b},eUe.dd=function(){return this.c},eUe.Hb=function(){return Ao(this)},eUe.Uh=function(e){RP(this,Lq(e))},eUe.ed=function(e){return P5(this,Lq(e))},eUe._g=function(e,t,n){var r;switch(e){case 0:return this.b;case 1:return this.c}return Qt(this,e-Y1((eBK(),tgF)),ee2((r=Pp(eaS(this,16),26))||tgF,e),t,n)},eUe.lh=function(e){var t;switch(e){case 0:return null!=this.b;case 1:return null!=this.c}return VP(this,e-Y1((eBK(),tgF)),ee2((t=Pp(eaS(this,16),26))||tgF,e))},eUe.sh=function(e,t){var n;switch(e){case 0:RR(this,Lq(t));return;case 1:ers(this,Lq(t));return}efL(this,e-Y1((eBK(),tgF)),ee2((n=Pp(eaS(this,16),26))||tgF,e),t)},eUe.zh=function(){return eBK(),tgF},eUe.Bh=function(e){var t;switch(e){case 0:ero(this,null);return;case 1:ers(this,null);return}ec6(this,e-Y1((eBK(),tgF)),ee2((t=Pp(eaS(this,16),26))||tgF,e))},eUe.Sh=function(){var e;return -1==this.a&&(e=this.b,this.a=null==e?0:ebA(e)),this.a},eUe.Th=function(e){this.a=e},eUe.Ib=function(){var e;return(64&this.Db)!=0?eMT(this):(e=new O1(eMT(this)),e.a+=" (key: ",xk(e,this.b),e.a+=", value: ",xk(e,this.c),e.a+=")",e.a)},eUe.a=-1,eUe.b=null,eUe.c=null;var tgf=Y5(eZ2,"EStringToStringMapEntryImpl",548),tgd=RL(eJz,"FeatureMap/Entry/Internal");eTS(565,1,eQa),eUe.Ok=function(e){return this.Pk(Pp(e,49))},eUe.Pk=function(e){return this.Ok(e)},eUe.Fb=function(e){var t,n;return this===e||!!M4(e,72)&&(t=Pp(e,72)).ak()==this.c&&(null==(n=this.dd())?null==t.dd():ecX(n,t.dd()))},eUe.ak=function(){return this.c},eUe.Hb=function(){var e;return e=this.dd(),esj(this.c)^(null==e?0:esj(e))},eUe.Ib=function(){var e,t;return t=etP((e=this.c).Hj()).Ph(),e.ne(),(null!=t&&0!=t.length?t+":"+e.ne():e.ne())+"="+this.dd()},Y5(eZ2,"EStructuralFeatureImpl/BasicFeatureMapEntry",565),eTS(776,565,eQa,Cg),eUe.Pk=function(e){return new Cg(this.c,e)},eUe.dd=function(){return this.a},eUe.Qk=function(e,t,n){return eiY(this,e,this.a,t,n)},eUe.Rk=function(e,t,n){return eiB(this,e,this.a,t,n)},Y5(eZ2,"EStructuralFeatureImpl/ContainmentUpdatingFeatureMapEntry",776),eTS(1314,1,{},k6),eUe.Pj=function(e,t,n,r,i){var a;return(a=Pp(JG(e,this.b),215)).nl(this.a).Wj(r)},eUe.Qj=function(e,t,n,r,i){var a;return(a=Pp(JG(e,this.b),215)).el(this.a,r,i)},eUe.Rj=function(e,t,n,r,i){var a;return(a=Pp(JG(e,this.b),215)).fl(this.a,r,i)},eUe.Sj=function(e,t,n){var r;return(r=Pp(JG(e,this.b),215)).nl(this.a).fj()},eUe.Tj=function(e,t,n,r){var i;(i=Pp(JG(e,this.b),215)).nl(this.a).Wb(r)},eUe.Uj=function(e,t,n){return Pp(JG(e,this.b),215).nl(this.a)},eUe.Vj=function(e,t,n){var r;(r=Pp(JG(e,this.b),215)).nl(this.a).Xj()},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateFeatureMapDelegator",1314),eTS(89,1,{},Pe,HS,$F,qc),eUe.Pj=function(e,t,n,r,i){var a;if(null==(a=t.Ch(n))&&t.Dh(n,a=eBN(this,e)),!i)switch(this.e){case 50:case 41:return Pp(a,589).sj();case 40:return Pp(a,215).kl()}return a},eUe.Qj=function(e,t,n,r,i){var a,o;return null==(o=t.Ch(n))&&t.Dh(n,o=eBN(this,e)),a=Pp(o,69).lk(r,i)},eUe.Rj=function(e,t,n,r,i){var a;return null!=(a=t.Ch(n))&&(i=Pp(a,69).mk(r,i)),i},eUe.Sj=function(e,t,n){var r;return null!=(r=t.Ch(n))&&Pp(r,76).fj()},eUe.Tj=function(e,t,n,r){var i;(i=Pp(t.Ch(n),76))||t.Dh(n,i=eBN(this,e)),i.Wb(r)},eUe.Uj=function(e,t,n){var r,i;return(null==(i=t.Ch(n))&&t.Dh(n,i=eBN(this,e)),M4(i,76))?Pp(i,76):(r=Pp(t.Ch(n),15),new pz(r))},eUe.Vj=function(e,t,n){var r;(r=Pp(t.Ch(n),76))||t.Dh(n,r=eBN(this,e)),r.Xj()},eUe.b=0,eUe.e=0,Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateMany",89),eTS(504,1,{}),eUe.Qj=function(e,t,n,r,i){throw p7(new bO)},eUe.Rj=function(e,t,n,r,i){throw p7(new bO)},eUe.Uj=function(e,t,n){return new Hk(this,e,t,n)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingle",504),eTS(1331,1,eJG,Hk),eUe.Wj=function(e){return this.a.Pj(this.c,this.d,this.b,e,!0)},eUe.fj=function(){return this.a.Sj(this.c,this.d,this.b)},eUe.Wb=function(e){this.a.Tj(this.c,this.d,this.b,e)},eUe.Xj=function(){this.a.Vj(this.c,this.d,this.b)},eUe.b=0,Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingle/1",1331),eTS(769,504,{},zl),eUe.Pj=function(e,t,n,r,i){return eIy(e,e.eh(),e.Vg())==this.b?this.sk()&&r?eTp(e):e.eh():null},eUe.Qj=function(e,t,n,r,i){var a,o;return e.eh()&&(i=(a=e.Vg())>=0?e.Qg(i):e.eh().ih(e,-1-a,null,i)),o=edv(e.Tg(),this.e),e.Sg(r,o,i)},eUe.Rj=function(e,t,n,r,i){var a;return a=edv(e.Tg(),this.e),e.Sg(null,a,i)},eUe.Sj=function(e,t,n){var r;return r=edv(e.Tg(),this.e),!!e.eh()&&e.Vg()==r},eUe.Tj=function(e,t,n,r){var i,a,o,s,u;if(null!=r&&!eNc(this.a,r))throw p7(new gA(eQo+(M4(r,56)?eyB(Pp(r,56).Tg()):ee6(esF(r)))+eQs+this.a+"'"));if(i=e.eh(),o=edv(e.Tg(),this.e),xc(r)!==xc(i)||e.Vg()!=o&&null!=r){if(eg7(e,Pp(r,56)))throw p7(new gL(eZ4+e.Ib()));u=null,i&&(u=(a=e.Vg())>=0?e.Qg(u):e.eh().ih(e,-1-a,null,u)),(s=Pp(r,49))&&(u=s.gh(e,edv(s.Tg(),this.b),null,u)),(u=e.Sg(s,o,u))&&u.Fi()}else e.Lg()&&e.Mg()&&eam(e,new FX(e,1,o,r,r))},eUe.Vj=function(e,t,n){var r,i,a,o;(r=e.eh())?(o=(i=e.Vg())>=0?e.Qg(null):e.eh().ih(e,-1-i,null,null),a=edv(e.Tg(),this.e),(o=e.Sg(null,a,o))&&o.Fi()):e.Lg()&&e.Mg()&&eam(e,new FJ(e,1,this.e,null,null))},eUe.sk=function(){return!1},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleContainer",769),eTS(1315,769,{},Pt),eUe.sk=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleContainerResolving",1315),eTS(563,504,{}),eUe.Pj=function(e,t,n,r,i){var a;return null==(a=t.Ch(n))?this.b:xc(a)===xc(tgZ)?null:a},eUe.Sj=function(e,t,n){var r;return null!=(r=t.Ch(n))&&(xc(r)===xc(tgZ)||!ecX(r,this.b))},eUe.Tj=function(e,t,n,r){var i,a;e.Lg()&&e.Mg()?(i=null==(a=t.Ch(n))?this.b:xc(a)===xc(tgZ)?null:a,null==r?null!=this.c?(t.Dh(n,null),r=this.b):null!=this.b?t.Dh(n,tgZ):t.Dh(n,null):(this.Sk(r),t.Dh(n,r)),eam(e,this.d.Tk(e,1,this.e,i,r))):null==r?null!=this.c?t.Dh(n,null):null!=this.b?t.Dh(n,tgZ):t.Dh(n,null):(this.Sk(r),t.Dh(n,r))},eUe.Vj=function(e,t,n){var r,i;e.Lg()&&e.Mg()?(r=null==(i=t.Ch(n))?this.b:xc(i)===xc(tgZ)?null:i,t.Eh(n),eam(e,this.d.Tk(e,1,this.e,r,this.b))):t.Eh(n)},eUe.Sk=function(e){throw p7(new bk)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleData",563),eTS(eQu,1,{},sb),eUe.Tk=function(e,t,n,r,i){return new FJ(e,t,n,r,i)},eUe.Uk=function(e,t,n,r,i,a){return new H0(e,t,n,r,i,a)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleData/NotificationCreator",eQu),eTS(1332,eQu,{},sm),eUe.Tk=function(e,t,n,r,i){return new ZY(e,t,n,gN(LK(r)),gN(LK(i)))},eUe.Uk=function(e,t,n,r,i,a){return new W4(e,t,n,gN(LK(r)),gN(LK(i)),a)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleData/NotificationCreator/1",1332),eTS(1333,eQu,{},sg),eUe.Tk=function(e,t,n,r,i){return new en_(e,t,n,Pp(r,217).a,Pp(i,217).a)},eUe.Uk=function(e,t,n,r,i,a){return new WX(e,t,n,Pp(r,217).a,Pp(i,217).a,a)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleData/NotificationCreator/2",1333),eTS(1334,eQu,{},sv),eUe.Tk=function(e,t,n,r,i){return new enE(e,t,n,Pp(r,172).a,Pp(i,172).a)},eUe.Uk=function(e,t,n,r,i,a){return new WJ(e,t,n,Pp(r,172).a,Pp(i,172).a,a)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleData/NotificationCreator/3",1334),eTS(1335,eQu,{},sy),eUe.Tk=function(e,t,n,r,i){return new Zj(e,t,n,gP(LV(r)),gP(LV(i)))},eUe.Uk=function(e,t,n,r,i,a){return new WQ(e,t,n,gP(LV(r)),gP(LV(i)),a)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleData/NotificationCreator/4",1335),eTS(1336,eQu,{},sw),eUe.Tk=function(e,t,n,r,i){return new enx(e,t,n,Pp(r,155).a,Pp(i,155).a)},eUe.Uk=function(e,t,n,r,i,a){return new W1(e,t,n,Pp(r,155).a,Pp(i,155).a,a)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleData/NotificationCreator/5",1336),eTS(1337,eQu,{},s_),eUe.Tk=function(e,t,n,r,i){return new ZF(e,t,n,Pp(r,19).a,Pp(i,19).a)},eUe.Uk=function(e,t,n,r,i,a){return new W0(e,t,n,Pp(r,19).a,Pp(i,19).a,a)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleData/NotificationCreator/6",1337),eTS(1338,eQu,{},sE),eUe.Tk=function(e,t,n,r,i){return new enS(e,t,n,Pp(r,162).a,Pp(i,162).a)},eUe.Uk=function(e,t,n,r,i,a){return new W2(e,t,n,Pp(r,162).a,Pp(i,162).a,a)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleData/NotificationCreator/7",1338),eTS(1339,eQu,{},sS),eUe.Tk=function(e,t,n,r,i){return new enk(e,t,n,Pp(r,184).a,Pp(i,184).a)},eUe.Uk=function(e,t,n,r,i,a){return new W3(e,t,n,Pp(r,184).a,Pp(i,184).a,a)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleData/NotificationCreator/8",1339),eTS(1317,563,{},Hx),eUe.Sk=function(e){if(!this.a.wj(e))throw p7(new gA(eQo+esF(e)+eQs+this.a+"'"))},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleDataDynamic",1317),eTS(1318,563,{},j6),eUe.Sk=function(e){},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleDataStatic",1318),eTS(770,563,{}),eUe.Sj=function(e,t,n){var r;return null!=(r=t.Ch(n))},eUe.Tj=function(e,t,n,r){var i,a;e.Lg()&&e.Mg()?(i=!0,null==(a=t.Ch(n))?(i=!1,a=this.b):xc(a)===xc(tgZ)&&(a=null),null==r?null!=this.c?(t.Dh(n,null),r=this.b):t.Dh(n,tgZ):(this.Sk(r),t.Dh(n,r)),eam(e,this.d.Uk(e,1,this.e,a,r,!i))):null==r?null!=this.c?t.Dh(n,null):t.Dh(n,tgZ):(this.Sk(r),t.Dh(n,r))},eUe.Vj=function(e,t,n){var r,i;e.Lg()&&e.Mg()?(r=!0,null==(i=t.Ch(n))?(r=!1,i=this.b):xc(i)===xc(tgZ)&&(i=null),t.Eh(n),eam(e,this.d.Uk(e,2,this.e,i,this.b,r))):t.Eh(n)},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleDataUnsettable",770),eTS(1319,770,{},HT),eUe.Sk=function(e){if(!this.a.wj(e))throw p7(new gA(eQo+esF(e)+eQs+this.a+"'"))},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleDataUnsettableDynamic",1319),eTS(1320,770,{},j9),eUe.Sk=function(e){},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleDataUnsettableStatic",1320),eTS(398,504,{},jd),eUe.Pj=function(e,t,n,r,i){var a,o,s,u,c;if(c=t.Ch(n),this.Kj()&&xc(c)===xc(tgZ))return null;if(!this.sk()||!r||null==c)return c;if((s=Pp(c,49)).kh()&&(u=ecv(e,s),s!=u)){if(!eNc(this.a,u))throw p7(new gA(eQo+esF(u)+eQs+this.a+"'"));t.Dh(n,c=u),this.rk()&&(a=Pp(u,49),o=s.ih(e,this.b?edv(s.Tg(),this.b):-1-edv(e.Tg(),this.e),null,null),a.eh()||(o=a.gh(e,this.b?edv(a.Tg(),this.b):-1-edv(e.Tg(),this.e),null,o)),o&&o.Fi()),e.Lg()&&e.Mg()&&eam(e,new FJ(e,9,this.e,s,u))}return c},eUe.Qj=function(e,t,n,r,i){var a,o;return xc(o=t.Ch(n))===xc(tgZ)&&(o=null),t.Dh(n,r),this.bj()?xc(o)!==xc(r)&&null!=o&&(i=(a=Pp(o,49)).ih(e,edv(a.Tg(),this.b),null,i)):this.rk()&&null!=o&&(i=Pp(o,49).ih(e,-1-edv(e.Tg(),this.e),null,i)),e.Lg()&&e.Mg()&&(i||(i=new yf(4)),i.Ei(new FJ(e,1,this.e,o,r))),i},eUe.Rj=function(e,t,n,r,i){var a;return xc(a=t.Ch(n))===xc(tgZ)&&(a=null),t.Eh(n),e.Lg()&&e.Mg()&&(i||(i=new yf(4)),this.Kj()?i.Ei(new FJ(e,2,this.e,a,null)):i.Ei(new FJ(e,1,this.e,a,null))),i},eUe.Sj=function(e,t,n){var r;return null!=(r=t.Ch(n))},eUe.Tj=function(e,t,n,r){var i,a,o,s,u;if(null!=r&&!eNc(this.a,r))throw p7(new gA(eQo+(M4(r,56)?eyB(Pp(r,56).Tg()):ee6(esF(r)))+eQs+this.a+"'"));s=null!=(u=t.Ch(n)),this.Kj()&&xc(u)===xc(tgZ)&&(u=null),o=null,this.bj()?xc(u)!==xc(r)&&(null!=u&&(o=(i=Pp(u,49)).ih(e,edv(i.Tg(),this.b),null,o)),null!=r&&(o=(i=Pp(r,49)).gh(e,edv(i.Tg(),this.b),null,o))):this.rk()&&xc(u)!==xc(r)&&(null!=u&&(o=Pp(u,49).ih(e,-1-edv(e.Tg(),this.e),null,o)),null!=r&&(o=Pp(r,49).gh(e,-1-edv(e.Tg(),this.e),null,o))),null==r&&this.Kj()?t.Dh(n,tgZ):t.Dh(n,r),e.Lg()&&e.Mg()?(a=new H0(e,1,this.e,u,r,this.Kj()&&!s),o?(o.Ei(a),o.Fi()):eam(e,a)):o&&o.Fi()},eUe.Vj=function(e,t,n){var r,i,a,o,s;o=null!=(s=t.Ch(n)),this.Kj()&&xc(s)===xc(tgZ)&&(s=null),a=null,null!=s&&(this.bj()?a=(r=Pp(s,49)).ih(e,edv(r.Tg(),this.b),null,a):this.rk()&&(a=Pp(s,49).ih(e,-1-edv(e.Tg(),this.e),null,a))),t.Eh(n),e.Lg()&&e.Mg()?(i=new H0(e,this.Kj()?2:1,this.e,s,null,o),a?(a.Ei(i),a.Fi()):eam(e,i)):a&&a.Fi()},eUe.bj=function(){return!1},eUe.rk=function(){return!1},eUe.sk=function(){return!1},eUe.Kj=function(){return!1},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObject",398),eTS(564,398,{},LE),eUe.rk=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectContainment",564),eTS(1323,564,{},LS),eUe.sk=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectContainmentResolving",1323),eTS(772,564,{},Lk),eUe.Kj=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectContainmentUnsettable",772),eTS(1325,772,{},Lx),eUe.sk=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectContainmentUnsettableResolving",1325),eTS(640,564,{},Pn),eUe.bj=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectContainmentWithInverse",640),eTS(1324,640,{},Pa),eUe.sk=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectContainmentWithInverseResolving",1324),eTS(773,640,{},Po),eUe.Kj=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectContainmentWithInverseUnsettable",773),eTS(1326,773,{},Ps),eUe.sk=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectContainmentWithInverseUnsettableResolving",1326),eTS(641,398,{},LT),eUe.sk=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectResolving",641),eTS(1327,641,{},LM),eUe.Kj=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectResolvingUnsettable",1327),eTS(774,641,{},Pr),eUe.bj=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectResolvingWithInverse",774),eTS(1328,774,{},Pu),eUe.Kj=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectResolvingWithInverseUnsettable",1328),eTS(1321,398,{},LO),eUe.Kj=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectUnsettable",1321),eTS(771,398,{},Pi),eUe.bj=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectWithInverse",771),eTS(1322,771,{},Pc),eUe.Kj=function(){return!0},Y5(eZ2,"EStructuralFeatureImpl/InternalSettingDelegateSingleEObjectWithInverseUnsettable",1322),eTS(775,565,eQa,Bj),eUe.Pk=function(e){return new Bj(this.a,this.c,e)},eUe.dd=function(){return this.b},eUe.Qk=function(e,t,n){return Jt(this,e,this.b,n)},eUe.Rk=function(e,t,n){return Jn(this,e,this.b,n)},Y5(eZ2,"EStructuralFeatureImpl/InverseUpdatingFeatureMapEntry",775),eTS(1329,1,eJG,pz),eUe.Wj=function(e){return this.a},eUe.fj=function(){return M4(this.a,95)?Pp(this.a,95).fj():!this.a.dc()},eUe.Wb=function(e){this.a.$b(),this.a.Gc(Pp(e,15))},eUe.Xj=function(){M4(this.a,95)?Pp(this.a,95).Xj():this.a.$b()},Y5(eZ2,"EStructuralFeatureImpl/SettingMany",1329),eTS(1330,565,eQa,qf),eUe.Ok=function(e){return new Cv((eR7(),tvK),this.b.Ih(this.a,e))},eUe.dd=function(){return null},eUe.Qk=function(e,t,n){return n},eUe.Rk=function(e,t,n){return n},Y5(eZ2,"EStructuralFeatureImpl/SimpleContentFeatureMapEntry",1330),eTS(642,565,eQa,Cv),eUe.Ok=function(e){return new Cv(this.c,e)},eUe.dd=function(){return this.a},eUe.Qk=function(e,t,n){return n},eUe.Rk=function(e,t,n){return n},Y5(eZ2,"EStructuralFeatureImpl/SimpleFeatureMapEntry",642),eTS(391,497,eXz,sk),eUe.ri=function(e){return Je(tm7,eUp,26,e,0,1)},eUe.ni=function(){return!1},Y5(eZ2,"ESuperAdapter/1",391),eTS(444,438,{105:1,92:1,90:1,147:1,191:1,56:1,108:1,836:1,49:1,97:1,150:1,444:1,114:1,115:1},sx),eUe._g=function(e,t,n){var r;switch(e){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),this.Ab;case 1:return this.zb;case 2:return this.a||(this.a=new jh(this,tgr,this)),this.a}return Qt(this,e-Y1((eBK(),tgU)),ee2((r=Pp(eaS(this,16),26))||tgU,e),t,n)},eUe.jh=function(e,t,n){var r,i;switch(t){case 0:return this.Ab||(this.Ab=new FQ(tm4,this,0,3)),ep6(this.Ab,e,n);case 2:return this.a||(this.a=new jh(this,tgr,this)),ep6(this.a,e,n)}return(i=Pp(ee2((r=Pp(eaS(this,16),26))||(eBK(),tgU),t),66)).Nj().Rj(this,ehH(this),t-Y1((eBK(),tgU)),e,n)},eUe.lh=function(e){var t;switch(e){case 0:return!!this.Ab&&0!=this.Ab.i;case 1:return null!=this.zb;case 2:return!!this.a&&0!=this.a.i}return VP(this,e-Y1((eBK(),tgU)),ee2((t=Pp(eaS(this,16),26))||tgU,e))},eUe.sh=function(e,t){var n;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab),this.Ab||(this.Ab=new FQ(tm4,this,0,3)),Y4(this.Ab,Pp(t,14));return;case 1:er3(this,Lq(t));return;case 2:this.a||(this.a=new jh(this,tgr,this)),eRT(this.a),this.a||(this.a=new jh(this,tgr,this)),Y4(this.a,Pp(t,14));return}efL(this,e-Y1((eBK(),tgU)),ee2((n=Pp(eaS(this,16),26))||tgU,e),t)},eUe.zh=function(){return eBK(),tgU},eUe.Bh=function(e){var t;switch(e){case 0:this.Ab||(this.Ab=new FQ(tm4,this,0,3)),eRT(this.Ab);return;case 1:er3(this,null);return;case 2:this.a||(this.a=new jh(this,tgr,this)),eRT(this.a);return}ec6(this,e-Y1((eBK(),tgU)),ee2((t=Pp(eaS(this,16),26))||tgU,e))},Y5(eZ2,"ETypeParameterImpl",444),eTS(445,85,eJ9,jh),eUe.cj=function(e,t){return ewV(this,Pp(e,87),t)},eUe.dj=function(e,t){return ewq(this,Pp(e,87),t)},Y5(eZ2,"ETypeParameterImpl/1",445),eTS(634,43,e$s,mR),eUe.ec=function(){return new pG(this)},Y5(eZ2,"ETypeParameterImpl/2",634),eTS(556,eUT,eUM,pG),eUe.Fc=function(e){return Ie(this,Pp(e,87))},eUe.Gc=function(e){var t,n,r;for(r=!1,n=e.Kc();n.Ob();)t=Pp(n.Pb(),87),null==Um(this.a,t,"")&&(r=!0);return r},eUe.$b=function(){Yy(this.a)},eUe.Hc=function(e){return F9(this.a,e)},eUe.Kc=function(){var e;return e=new esz(new fS(this.a).a),new pW(e)},eUe.Mc=function(e){return Xp(this,e)},eUe.gc=function(){return wq(this.a)},Y5(eZ2,"ETypeParameterImpl/2/1",556),eTS(557,1,eUE,pW),eUe.Nb=function(e){F8(this,e)},eUe.Pb=function(){return Pp(etz(this.a).cd(),87)},eUe.Ob=function(){return this.a.b},eUe.Qb=function(){JM(this.a)},Y5(eZ2,"ETypeParameterImpl/2/1/1",557),eTS(1276,43,e$s,mj),eUe._b=function(e){return xd(e)?$r(this,e):!!$I(this.f,e)},eUe.xc=function(e){var t,n;return M4(t=xd(e)?zg(this,e):xu($I(this.f,e)),837)?(t=(n=Pp(t,837))._j(),Um(this,Pp(e,235),t),t):null!=t?t:null==e?(_3(),tvh):null},Y5(eZ2,"EValidatorRegistryImpl",1276),eTS(1313,704,{105:1,92:1,90:1,471:1,147:1,56:1,108:1,1941:1,49:1,97:1,150:1,114:1,115:1},sT),eUe.Ih=function(e,t){switch(e.yj()){case 21:case 22:case 23:case 24:case 26:case 31:case 32:case 37:case 38:case 39:case 40:case 43:case 44:case 48:case 49:case 20:return null==t?null:efF(t);case 25:return etR(t);case 27:return Qn(t);case 28:return Qr(t);case 29:return null==t?null:MU(tmS[0],Pp(t,199));case 41:return null==t?"":yx(Pp(t,290));case 42:return efF(t);case 50:return Lq(t);default:throw p7(new gL(eZ5+e.ne()+eZ6))}},eUe.Jh=function(e){var t;switch(-1==e.G&&(e.G=(t=etP(e))?ebv(t.Mh(),e):-1),e.G){case 0:return new mC;case 1:return new sa;case 2:return new c0;case 4:return new bN;case 5:return new mI;case 6:return new bD;case 7:return new cQ;case 10:return new sr;case 11:return new mD;case 12:return new $y;case 13:return new mN;case 14:return new LB;case 17:return new sp;case 18:return new p5;case 19:return new sx;default:throw p7(new gL(eZ7+e.zb+eZ6))}},eUe.Kh=function(e,t){switch(e.yj()){case 20:return null==t?null:new yY(t);case 21:return null==t?null:new TU(t);case 23:case 22:return null==t?null:ehL(t);case 26:case 24:return null==t?null:eeT(eDa(t,-128,127)<<24>>24);case 25:return eMp(t);case 27:return egg(t);case 28:return egv(t);case 29:return e__(t);case 32:case 31:return null==t?null:eEu(t);case 38:case 37:return null==t?null:new bK(t);case 40:case 39:return null==t?null:ell(eDa(t,eHt,eUu));case 41:case 42:return null;case 44:case 43:return null==t?null:ehQ(eF0(t));case 49:case 48:return null==t?null:elf(eDa(t,eQl,32767)<<16>>16);case 50:return t;default:throw p7(new gL(eZ5+e.ne()+eZ6))}},Y5(eZ2,"EcoreFactoryImpl",1313),eTS(547,179,{105:1,92:1,90:1,147:1,191:1,56:1,235:1,108:1,1939:1,49:1,97:1,150:1,179:1,547:1,114:1,115:1,675:1},Uh),eUe.gb=!1,eUe.hb=!1;var tgh,tgp,tgb,tgm,tgg,tgv,tgy,tgw,tg_,tgE,tgS,tgk,tgx,tgT,tgM,tgO,tgA,tgL,tgC,tgI,tgD,tgN,tgP,tgR,tgj,tgF,tgY,tgB,tgU,tgH,tg$,tgz,tgG,tgW,tgK,tgV,tgq,tgZ,tgX,tgJ,tgQ,tg1,tg0,tg2,tg3,tg4,tg5,tg6,tg9=!1;Y5(eZ2,"EcorePackageImpl",547),eTS(1184,1,{837:1},sM),eUe._j=function(){return OJ(),tvp},Y5(eZ2,"EcorePackageImpl/1",1184),eTS(1193,1,eQS,sO),eUe.wj=function(e){return M4(e,147)},eUe.xj=function(e){return Je(e6y,eUp,147,e,0,1)},Y5(eZ2,"EcorePackageImpl/10",1193),eTS(1194,1,eQS,sA),eUe.wj=function(e){return M4(e,191)},eUe.xj=function(e){return Je(e6_,eUp,191,e,0,1)},Y5(eZ2,"EcorePackageImpl/11",1194),eTS(1195,1,eQS,sL),eUe.wj=function(e){return M4(e,56)},eUe.xj=function(e){return Je(e6f,eUp,56,e,0,1)},Y5(eZ2,"EcorePackageImpl/12",1195),eTS(1196,1,eQS,sC),eUe.wj=function(e){return M4(e,399)},eUe.xj=function(e){return Je(tgi,eJ5,59,e,0,1)},Y5(eZ2,"EcorePackageImpl/13",1196),eTS(1197,1,eQS,sI),eUe.wj=function(e){return M4(e,235)},eUe.xj=function(e){return Je(e6E,eUp,235,e,0,1)},Y5(eZ2,"EcorePackageImpl/14",1197),eTS(1198,1,eQS,sD),eUe.wj=function(e){return M4(e,509)},eUe.xj=function(e){return Je(tga,eUp,2017,e,0,1)},Y5(eZ2,"EcorePackageImpl/15",1198),eTS(1199,1,eQS,sN),eUe.wj=function(e){return M4(e,99)},eUe.xj=function(e){return Je(tgo,eJ4,18,e,0,1)},Y5(eZ2,"EcorePackageImpl/16",1199),eTS(1200,1,eQS,sP),eUe.wj=function(e){return M4(e,170)},eUe.xj=function(e){return Je(tm6,eJ4,170,e,0,1)},Y5(eZ2,"EcorePackageImpl/17",1200),eTS(1201,1,eQS,sR),eUe.wj=function(e){return M4(e,472)},eUe.xj=function(e){return Je(tm5,eUp,472,e,0,1)},Y5(eZ2,"EcorePackageImpl/18",1201),eTS(1202,1,eQS,sj),eUe.wj=function(e){return M4(e,548)},eUe.xj=function(e){return Je(tgf,eJL,548,e,0,1)},Y5(eZ2,"EcorePackageImpl/19",1202),eTS(1185,1,eQS,sF),eUe.wj=function(e){return M4(e,322)},eUe.xj=function(e){return Je(tm9,eJ4,34,e,0,1)},Y5(eZ2,"EcorePackageImpl/2",1185),eTS(1203,1,eQS,sY),eUe.wj=function(e){return M4(e,241)},eUe.xj=function(e){return Je(tgr,eQt,87,e,0,1)},Y5(eZ2,"EcorePackageImpl/20",1203),eTS(1204,1,eQS,sB),eUe.wj=function(e){return M4(e,444)},eUe.xj=function(e){return Je(tgs,eUp,836,e,0,1)},Y5(eZ2,"EcorePackageImpl/21",1204),eTS(1205,1,eQS,sU),eUe.wj=function(e){return xl(e)},eUe.xj=function(e){return Je(e11,eUP,476,e,8,1)},Y5(eZ2,"EcorePackageImpl/22",1205),eTS(1206,1,eQS,sH),eUe.wj=function(e){return M4(e,190)},eUe.xj=function(e){return Je(tyk,eUP,190,e,0,2)},Y5(eZ2,"EcorePackageImpl/23",1206),eTS(1207,1,eQS,s$),eUe.wj=function(e){return M4(e,217)},eUe.xj=function(e){return Je(e10,eUP,217,e,0,1)},Y5(eZ2,"EcorePackageImpl/24",1207),eTS(1208,1,eQS,sz),eUe.wj=function(e){return M4(e,172)},eUe.xj=function(e){return Je(e12,eUP,172,e,0,1)},Y5(eZ2,"EcorePackageImpl/25",1208),eTS(1209,1,eQS,sG),eUe.wj=function(e){return M4(e,199)},eUe.xj=function(e){return Je(e1Q,eUP,199,e,0,1)},Y5(eZ2,"EcorePackageImpl/26",1209),eTS(1210,1,eQS,sW),eUe.wj=function(e){return!1},eUe.xj=function(e){return Je(tyA,eUp,2110,e,0,1)},Y5(eZ2,"EcorePackageImpl/27",1210),eTS(1211,1,eQS,sK),eUe.wj=function(e){return xf(e)},eUe.xj=function(e){return Je(e13,eUP,333,e,7,1)},Y5(eZ2,"EcorePackageImpl/28",1211),eTS(1212,1,eQS,sV),eUe.wj=function(e){return M4(e,58)},eUe.xj=function(e){return Je(e6L,ezZ,58,e,0,1)},Y5(eZ2,"EcorePackageImpl/29",1212),eTS(1186,1,eQS,sq),eUe.wj=function(e){return M4(e,510)},eUe.xj=function(e){return Je(tm4,{3:1,4:1,5:1,1934:1},590,e,0,1)},Y5(eZ2,"EcorePackageImpl/3",1186),eTS(1213,1,eQS,sZ),eUe.wj=function(e){return M4(e,573)},eUe.xj=function(e){return Je(e6j,eUp,1940,e,0,1)},Y5(eZ2,"EcorePackageImpl/30",1213),eTS(1214,1,eQS,sX),eUe.wj=function(e){return M4(e,153)},eUe.xj=function(e){return Je(tg7,ezZ,153,e,0,1)},Y5(eZ2,"EcorePackageImpl/31",1214),eTS(1215,1,eQS,sJ),eUe.wj=function(e){return M4(e,72)},eUe.xj=function(e){return Je(tgc,eQk,72,e,0,1)},Y5(eZ2,"EcorePackageImpl/32",1215),eTS(1216,1,eQS,sQ),eUe.wj=function(e){return M4(e,155)},eUe.xj=function(e){return Je(e14,eUP,155,e,0,1)},Y5(eZ2,"EcorePackageImpl/33",1216),eTS(1217,1,eQS,s1),eUe.wj=function(e){return M4(e,19)},eUe.xj=function(e){return Je(e15,eUP,19,e,0,1)},Y5(eZ2,"EcorePackageImpl/34",1217),eTS(1218,1,eQS,s0),eUe.wj=function(e){return M4(e,290)},eUe.xj=function(e){return Je(e1j,eUp,290,e,0,1)},Y5(eZ2,"EcorePackageImpl/35",1218),eTS(1219,1,eQS,s2),eUe.wj=function(e){return M4(e,162)},eUe.xj=function(e){return Je(e16,eUP,162,e,0,1)},Y5(eZ2,"EcorePackageImpl/36",1219),eTS(1220,1,eQS,s3),eUe.wj=function(e){return M4(e,83)},eUe.xj=function(e){return Je(e1Y,eUp,83,e,0,1)},Y5(eZ2,"EcorePackageImpl/37",1220),eTS(1221,1,eQS,s4),eUe.wj=function(e){return M4(e,591)},eUe.xj=function(e){return Je(tg8,eUp,591,e,0,1)},Y5(eZ2,"EcorePackageImpl/38",1221),eTS(1222,1,eQS,s5),eUe.wj=function(e){return!1},eUe.xj=function(e){return Je(tyL,eUp,2111,e,0,1)},Y5(eZ2,"EcorePackageImpl/39",1222),eTS(1187,1,eQS,s6),eUe.wj=function(e){return M4(e,88)},eUe.xj=function(e){return Je(tm7,eUp,26,e,0,1)},Y5(eZ2,"EcorePackageImpl/4",1187),eTS(1223,1,eQS,s9),eUe.wj=function(e){return M4(e,184)},eUe.xj=function(e){return Je(e19,eUP,184,e,0,1)},Y5(eZ2,"EcorePackageImpl/40",1223),eTS(1224,1,eQS,s8),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eZ2,"EcorePackageImpl/41",1224),eTS(1225,1,eQS,s7),eUe.wj=function(e){return M4(e,588)},eUe.xj=function(e){return Je(e6I,eUp,588,e,0,1)},Y5(eZ2,"EcorePackageImpl/42",1225),eTS(1226,1,eQS,ue),eUe.wj=function(e){return!1},eUe.xj=function(e){return Je(tyC,eUP,2112,e,0,1)},Y5(eZ2,"EcorePackageImpl/43",1226),eTS(1227,1,eQS,ut),eUe.wj=function(e){return M4(e,42)},eUe.xj=function(e){return Je(e1$,eUK,42,e,0,1)},Y5(eZ2,"EcorePackageImpl/44",1227),eTS(1188,1,eQS,un),eUe.wj=function(e){return M4(e,138)},eUe.xj=function(e){return Je(tm8,eUp,138,e,0,1)},Y5(eZ2,"EcorePackageImpl/5",1188),eTS(1189,1,eQS,ur),eUe.wj=function(e){return M4(e,148)},eUe.xj=function(e){return Je(tge,eUp,148,e,0,1)},Y5(eZ2,"EcorePackageImpl/6",1189),eTS(1190,1,eQS,ui),eUe.wj=function(e){return M4(e,457)},eUe.xj=function(e){return Je(tgt,eUp,671,e,0,1)},Y5(eZ2,"EcorePackageImpl/7",1190),eTS(1191,1,eQS,ua),eUe.wj=function(e){return M4(e,573)},eUe.xj=function(e){return Je(tgn,eUp,678,e,0,1)},Y5(eZ2,"EcorePackageImpl/8",1191),eTS(1192,1,eQS,uo),eUe.wj=function(e){return M4(e,471)},eUe.xj=function(e){return Je(e6w,eUp,471,e,0,1)},Y5(eZ2,"EcorePackageImpl/9",1192),eTS(1025,1982,eJO,gT),eUe.bi=function(e,t){ecV(this,Pp(t,415))},eUe.fi=function(e,t){eSU(this,e,Pp(t,415))},Y5(eZ2,"MinimalEObjectImpl/1ArrayDelegatingAdapterList",1025),eTS(1026,143,eJx,BF),eUe.Ai=function(){return this.a.a},Y5(eZ2,"MinimalEObjectImpl/1ArrayDelegatingAdapterList/1",1026),eTS(1053,1052,{},Ms),Y5("org.eclipse.emf.ecore.plugin","EcorePlugin",1053);var tg8=RL(eQx,"Resource");eTS(781,1378,eQT),eUe.Yk=function(e){},eUe.Zk=function(e){},eUe.Vk=function(){return this.a||(this.a=new pK(this)),this.a},eUe.Wk=function(e){var t,n,r,i,a;if((r=e.length)>0){if(GV(0,e.length),47==e.charCodeAt(0)){for(t=1,a=new XM(4),i=1;t0&&(e=e.substr(0,n))}return ekX(this,e)},eUe.Xk=function(){return this.c},eUe.Ib=function(){var e;return yx(this.gm)+"@"+(e=esj(this)>>>0).toString(16)+" uri='"+this.d+"'"},eUe.b=!1,Y5(eQM,"ResourceImpl",781),eTS(1379,781,eQT,pq),Y5(eQM,"BinaryResourceImpl",1379),eTS(1169,694,eXG),eUe.si=function(e){return M4(e,56)?$x(this,Pp(e,56)):M4(e,591)?new Ow(Pp(e,591).Vk()):xc(e)===xc(this.f)?Pp(e,14).Kc():(LF(),tmB.a)},eUe.Ob=function(){return exI(this)},eUe.a=!1,Y5(eJz,"EcoreUtil/ContentTreeIterator",1169),eTS(1380,1169,eXG,F0),eUe.si=function(e){return xc(e)===xc(this.f)?Pp(e,15).Kc():new K0(Pp(e,56))},Y5(eQM,"ResourceImpl/5",1380),eTS(648,1994,eJ6,pK),eUe.Hc=function(e){return this.i<=4?ev9(this,e):M4(e,49)&&Pp(e,49).Zg()==this.a},eUe.bi=function(e,t){e==this.i-1&&(this.a.b||(this.a.b=!0))},eUe.di=function(e,t){0==e?this.a.b||(this.a.b=!0):X8(this,e,t)},eUe.fi=function(e,t){},eUe.gi=function(e,t,n){},eUe.aj=function(){return 2},eUe.Ai=function(){return this.a},eUe.bj=function(){return!0},eUe.cj=function(e,t){var n;return t=(n=Pp(e,49)).wh(this.a,t)},eUe.dj=function(e,t){var n;return(n=Pp(e,49)).wh(null,t)},eUe.ej=function(){return!1},eUe.hi=function(){return!0},eUe.ri=function(e){return Je(e6f,eUp,56,e,0,1)},eUe.ni=function(){return!1},Y5(eQM,"ResourceImpl/ContentsEList",648),eTS(957,1964,eU5,pV),eUe.Zc=function(e){return this.a._h(e)},eUe.gc=function(){return this.a.gc()},Y5(eJz,"AbstractSequentialInternalEList/1",957),eTS(624,1,{},PQ),Y5(eJz,"BasicExtendedMetaData",624),eTS(1160,1,{},k9),eUe.$k=function(){return null},eUe._k=function(){return -2==this.a&&fi(this,e_f(this.d,this.b)),this.a},eUe.al=function(){return null},eUe.bl=function(){return Hj(),Hj(),e2r},eUe.ne=function(){return this.c==eQH&&fo(this,eh1(this.d,this.b)),this.c},eUe.cl=function(){return 0},eUe.a=-2,eUe.c=eQH,Y5(eJz,"BasicExtendedMetaData/EClassExtendedMetaDataImpl",1160),eTS(1161,1,{},Ke),eUe.$k=function(){return this.a==(ZE(),tvf)&&fa(this,eO9(this.f,this.b)),this.a},eUe._k=function(){return 0},eUe.al=function(){return this.c==(ZE(),tvf)&&fs(this,eO8(this.f,this.b)),this.c},eUe.bl=function(){return this.d||fu(this,eIA(this.f,this.b)),this.d},eUe.ne=function(){return this.e==eQH&&fc(this,eh1(this.f,this.b)),this.e},eUe.cl=function(){return -2==this.g&&fl(this,ewd(this.f,this.b)),this.g},eUe.e=eQH,eUe.g=-2,Y5(eJz,"BasicExtendedMetaData/EDataTypeExtendedMetaDataImpl",1161),eTS(1159,1,{},xn),eUe.b=!1,eUe.c=!1,Y5(eJz,"BasicExtendedMetaData/EPackageExtendedMetaDataImpl",1159),eTS(1162,1,{},W7),eUe.c=-2,eUe.e=eQH,eUe.f=eQH,Y5(eJz,"BasicExtendedMetaData/EStructuralFeatureExtendedMetaDataImpl",1162),eTS(585,622,eJ9,PJ),eUe.aj=function(){return this.c},eUe.Fk=function(){return!1},eUe.li=function(e,t){return t},eUe.c=0,Y5(eJz,"EDataTypeEList",585);var tg7=RL(eJz,"FeatureMap");eTS(75,585,{3:1,4:1,20:1,28:1,52:1,14:1,15:1,54:1,67:1,63:1,58:1,76:1,153:1,215:1,1937:1,69:1,95:1},eiR),eUe.Vc=function(e,t){eO0(this,e,Pp(t,72))},eUe.Fc=function(e){return eM6(this,Pp(e,72))},eUe.Yh=function(e){Y2(this,Pp(e,72))},eUe.cj=function(e,t){return IG(this,Pp(e,72),t)},eUe.dj=function(e,t){return IW(this,Pp(e,72),t)},eUe.ii=function(e,t){return eI7(this,e,t)},eUe.li=function(e,t){return ejg(this,e,Pp(t,72))},eUe._c=function(e,t){return eA6(this,e,Pp(t,72))},eUe.jj=function(e,t){return IK(this,Pp(e,72),t)},eUe.kj=function(e,t){return IV(this,Pp(e,72),t)},eUe.lj=function(e,t,n){return eyU(this,Pp(e,72),Pp(t,72),n)},eUe.oi=function(e,t){return ewk(this,e,Pp(t,72))},eUe.dl=function(e,t){return eIF(this,e,t)},eUe.Wc=function(e,t){var n,r,i,a,o,s,u,c,l;for(c=new eta(t.gc()),i=t.Kc();i.Ob();)if(a=(r=Pp(i.Pb(),72)).ak(),eLt(this.e,a))a.hi()&&(Vq(this,a,r.dd())||ev9(c,r))||JL(c,r);else{for(s=0,l=eAY(this.e.Tg(),a),n=Pp(this.g,119),o=!0;s=0;)if(t=e[this.c],this.k.rl(t.ak()))return this.j=this.f?t:t.dd(),this.i=-2,!0;return this.i=-1,this.g=-1,!1},Y5(eJz,"BasicFeatureMap/FeatureEIterator",410),eTS(662,410,eUC,x1),eUe.Lk=function(){return!0},Y5(eJz,"BasicFeatureMap/ResolvingFeatureEIterator",662),eTS(955,486,eQr,Mz),eUe.Gi=function(){return this},Y5(eJz,"EContentsEList/1",955),eTS(956,486,eQr,x0),eUe.Lk=function(){return!1},Y5(eJz,"EContentsEList/2",956),eTS(954,279,eQi,MG),eUe.Nk=function(e){},eUe.Ob=function(){return!1},eUe.Sb=function(){return!1},Y5(eJz,"EContentsEList/FeatureIteratorImpl/1",954),eTS(825,585,eJ9,OM),eUe.ci=function(){this.a=!0},eUe.fj=function(){return this.a},eUe.Xj=function(){var e;eRT(this),TO(this.e)?(e=this.a,this.a=!1,eam(this.e,new ZB(this.e,2,this.c,e,!1))):this.a=!1},eUe.a=!1,Y5(eJz,"EDataTypeEList/Unsettable",825),eTS(1849,585,eJ9,OO),eUe.hi=function(){return!0},Y5(eJz,"EDataTypeUniqueEList",1849),eTS(1850,825,eJ9,OA),eUe.hi=function(){return!0},Y5(eJz,"EDataTypeUniqueEList/Unsettable",1850),eTS(139,85,eJ9,OS),eUe.Ek=function(){return!0},eUe.li=function(e,t){return ex7(this,e,Pp(t,56))},Y5(eJz,"EObjectContainmentEList/Resolving",139),eTS(1163,545,eJ9,Ok),eUe.Ek=function(){return!0},eUe.li=function(e,t){return ex7(this,e,Pp(t,56))},Y5(eJz,"EObjectContainmentEList/Unsettable/Resolving",1163),eTS(748,16,eJ9,Io),eUe.ci=function(){this.a=!0},eUe.fj=function(){return this.a},eUe.Xj=function(){var e;eRT(this),TO(this.e)?(e=this.a,this.a=!1,eam(this.e,new ZB(this.e,2,this.c,e,!1))):this.a=!1},eUe.a=!1,Y5(eJz,"EObjectContainmentWithInverseEList/Unsettable",748),eTS(1173,748,eJ9,Is),eUe.Ek=function(){return!0},eUe.li=function(e,t){return ex7(this,e,Pp(t,56))},Y5(eJz,"EObjectContainmentWithInverseEList/Unsettable/Resolving",1173),eTS(743,496,eJ9,Ox),eUe.ci=function(){this.a=!0},eUe.fj=function(){return this.a},eUe.Xj=function(){var e;eRT(this),TO(this.e)?(e=this.a,this.a=!1,eam(this.e,new ZB(this.e,2,this.c,e,!1))):this.a=!1},eUe.a=!1,Y5(eJz,"EObjectEList/Unsettable",743),eTS(328,496,eJ9,OT),eUe.Ek=function(){return!0},eUe.li=function(e,t){return ex7(this,e,Pp(t,56))},Y5(eJz,"EObjectResolvingEList",328),eTS(1641,743,eJ9,OL),eUe.Ek=function(){return!0},eUe.li=function(e,t){return ex7(this,e,Pp(t,56))},Y5(eJz,"EObjectResolvingEList/Unsettable",1641),eTS(1381,1,{},us),Y5(eJz,"EObjectValidator",1381),eTS(546,496,eJ9,F1),eUe.zk=function(){return this.d},eUe.Ak=function(){return this.b},eUe.bj=function(){return!0},eUe.Dk=function(){return!0},eUe.b=0,Y5(eJz,"EObjectWithInverseEList",546),eTS(1176,546,eJ9,Iu),eUe.Ck=function(){return!0},Y5(eJz,"EObjectWithInverseEList/ManyInverse",1176),eTS(625,546,eJ9,Ic),eUe.ci=function(){this.a=!0},eUe.fj=function(){return this.a},eUe.Xj=function(){var e;eRT(this),TO(this.e)?(e=this.a,this.a=!1,eam(this.e,new ZB(this.e,2,this.c,e,!1))):this.a=!1},eUe.a=!1,Y5(eJz,"EObjectWithInverseEList/Unsettable",625),eTS(1175,625,eJ9,If),eUe.Ck=function(){return!0},Y5(eJz,"EObjectWithInverseEList/Unsettable/ManyInverse",1175),eTS(749,546,eJ9,Il),eUe.Ek=function(){return!0},eUe.li=function(e,t){return ex7(this,e,Pp(t,56))},Y5(eJz,"EObjectWithInverseResolvingEList",749),eTS(31,749,eJ9,Ih),eUe.Ck=function(){return!0},Y5(eJz,"EObjectWithInverseResolvingEList/ManyInverse",31),eTS(750,625,eJ9,Id),eUe.Ek=function(){return!0},eUe.li=function(e,t){return ex7(this,e,Pp(t,56))},Y5(eJz,"EObjectWithInverseResolvingEList/Unsettable",750),eTS(1174,750,eJ9,Ip),eUe.Ck=function(){return!0},Y5(eJz,"EObjectWithInverseResolvingEList/Unsettable/ManyInverse",1174),eTS(1164,622,eJ9),eUe.ai=function(){return(1792&this.b)==0},eUe.ci=function(){this.b|=1},eUe.Bk=function(){return(4&this.b)!=0},eUe.bj=function(){return(40&this.b)!=0},eUe.Ck=function(){return(16&this.b)!=0},eUe.Dk=function(){return(8&this.b)!=0},eUe.Ek=function(){return(this.b&eJq)!=0},eUe.rk=function(){return(32&this.b)!=0},eUe.Fk=function(){return(this.b&eXt)!=0},eUe.wj=function(e){return this.d?VB(this.d,e):this.ak().Yj().wj(e)},eUe.fj=function(){return(2&this.b)!=0?(1&this.b)!=0:0!=this.i},eUe.hi=function(){return(128&this.b)!=0},eUe.Xj=function(){var e;eRT(this),(2&this.b)!=0&&(TO(this.e)?(e=(1&this.b)!=0,this.b&=-2,bz(this,new ZB(this.e,2,edv(this.e.Tg(),this.ak()),e,!1))):this.b&=-2)},eUe.ni=function(){return(1536&this.b)==0},eUe.b=0,Y5(eJz,"EcoreEList/Generic",1164),eTS(1165,1164,eJ9,H2),eUe.ak=function(){return this.a},Y5(eJz,"EcoreEList/Dynamic",1165),eTS(747,63,eXz,pZ),eUe.ri=function(e){return enb(this.a.a,e)},Y5(eJz,"EcoreEMap/1",747),eTS(746,85,eJ9,FZ),eUe.bi=function(e,t){ebB(this.b,Pp(t,133))},eUe.di=function(e,t){eac(this.b)},eUe.ei=function(e,t,n){var r;++(r=this.b,Pp(t,133),r).e},eUe.fi=function(e,t){elj(this.b,Pp(t,133))},eUe.gi=function(e,t,n){elj(this.b,Pp(n,133)),xc(n)===xc(t)&&Pp(n,133).Th(Mi(Pp(t,133).cd())),ebB(this.b,Pp(t,133))},Y5(eJz,"EcoreEMap/DelegateEObjectContainmentEList",746),eTS(1171,151,eJW,enQ),Y5(eJz,"EcoreEMap/Unsettable",1171),eTS(1172,746,eJ9,Ib),eUe.ci=function(){this.a=!0},eUe.fj=function(){return this.a},eUe.Xj=function(){var e;eRT(this),TO(this.e)?(e=this.a,this.a=!1,eam(this.e,new ZB(this.e,2,this.c,e,!1))):this.a=!1},eUe.a=!1,Y5(eJz,"EcoreEMap/Unsettable/UnsettableDelegateEObjectContainmentEList",1172),eTS(1168,228,e$s,YQ),eUe.a=!1,eUe.b=!1,Y5(eJz,"EcoreUtil/Copier",1168),eTS(745,1,eUE,K0),eUe.Nb=function(e){F8(this,e)},eUe.Ob=function(){return edV(this)},eUe.Pb=function(){var e;return edV(this),e=this.b,this.b=null,e},eUe.Qb=function(){this.a.Qb()},Y5(eJz,"EcoreUtil/ProperContentIterator",745),eTS(1382,1381,{},c2),Y5(eJz,"EcoreValidator",1382),RL(eJz,"FeatureMapUtil/Validator"),eTS(1260,1,{1942:1},uu),eUe.rl=function(e){return!0},Y5(eJz,"FeatureMapUtil/1",1260),eTS(757,1,{1942:1},eF2),eUe.rl=function(e){var t;return this.c==e||(null!=(t=LK(Bp(this.a,e)))?t==(OQ(),e0P):eCV(this,e)?(Z$(this.a,e,(OQ(),e0P)),!0):(Z$(this.a,e,(OQ(),e0N)),!1))},eUe.e=!1,Y5(eJz,"FeatureMapUtil/BasicValidator",757),eTS(758,43,e$s,MW),Y5(eJz,"FeatureMapUtil/BasicValidator/Cache",758),eTS(501,52,{20:1,28:1,52:1,14:1,15:1,58:1,76:1,69:1,95:1},xe),eUe.Vc=function(e,t){eLe(this.c,this.b,e,t)},eUe.Fc=function(e){return eIF(this.c,this.b,e)},eUe.Wc=function(e,t){return ePq(this.c,this.b,e,t)},eUe.Gc=function(e){return MJ(this,e)},eUe.Xh=function(e,t){ee7(this.c,this.b,e,t)},eUe.lk=function(e,t){return eCB(this.c,this.b,e,t)},eUe.pi=function(e){return ePL(this.c,this.b,e,!1)},eUe.Zh=function(){return TC(this.c,this.b)},eUe.$h=function(){return TI(this.c,this.b)},eUe._h=function(e){return X9(this.c,this.b,e)},eUe.mk=function(e,t){return Cp(this,e,t)},eUe.$b=function(){bG(this)},eUe.Hc=function(e){return Vq(this.c,this.b,e)},eUe.Ic=function(e){return eiF(this.c,this.b,e)},eUe.Xb=function(e){return ePL(this.c,this.b,e,!0)},eUe.Wj=function(e){return this},eUe.Xc=function(e){return VZ(this.c,this.b,e)},eUe.dc=function(){return xs(this)},eUe.fj=function(){return!edK(this.c,this.b)},eUe.Kc=function(){return eei(this.c,this.b)},eUe.Yc=function(){return eea(this.c,this.b)},eUe.Zc=function(e){return ely(this.c,this.b,e)},eUe.ii=function(e,t){return eNn(this.c,this.b,e,t)},eUe.ji=function(e,t){Xx(this.c,this.b,e,t)},eUe.$c=function(e){return eE0(this.c,this.b,e)},eUe.Mc=function(e){return eIC(this.c,this.b,e)},eUe._c=function(e,t){return eNL(this.c,this.b,e,t)},eUe.Wb=function(e){exZ(this.c,this.b),MJ(this,Pp(e,15))},eUe.gc=function(){return elG(this.c,this.b)},eUe.Pc=function(){return Wb(this.c,this.b)},eUe.Qc=function(e){return VX(this.c,this.b,e)},eUe.Ib=function(){var e,t;for(t=new vs,t.a+="[",e=TC(this.c,this.b);euf(e);)xk(t,Ae(ebm(e))),euf(e)&&(t.a+=eUd);return t.a+="]",t.a},eUe.Xj=function(){exZ(this.c,this.b)},Y5(eJz,"FeatureMapUtil/FeatureEList",501),eTS(627,36,eJx,qu),eUe.yi=function(e){return elc(this,e)},eUe.Di=function(e){var t,n,r,i,a,o,s;switch(this.d){case 1:case 2:if(xc(a=e.Ai())===xc(this.c)&&elc(this,null)==e.yi(null))return this.g=e.zi(),1==e.xi()&&(this.d=1),!0;break;case 3:if(3===(i=e.xi())&&xc(a=e.Ai())===xc(this.c)&&elc(this,null)==e.yi(null))return this.d=5,JL(t=new eta(2),this.g),JL(t,e.zi()),this.g=t,!0;break;case 5:if(3===(i=e.xi())&&xc(a=e.Ai())===xc(this.c)&&elc(this,null)==e.yi(null))return(n=Pp(this.g,14)).Fc(e.zi()),!0;break;case 4:switch(i=e.xi()){case 3:if(xc(a=e.Ai())===xc(this.c)&&elc(this,null)==e.yi(null))return this.d=1,this.g=e.zi(),!0;break;case 4:if(xc(a=e.Ai())===xc(this.c)&&elc(this,null)==e.yi(null))return this.d=6,JL(s=new eta(2),this.n),JL(s,e.Bi()),this.n=s,o=eow(vx(ty_,1),eHT,25,15,[this.o,e.Ci()]),this.g=o,!0}break;case 6:if(4===(i=e.xi())&&xc(a=e.Ai())===xc(this.c)&&elc(this,null)==e.yi(null))return(n=Pp(this.n,14)).Fc(e.Bi()),r=Je(ty_,eHT,25,(o=Pp(this.g,48)).length+1,15,1),ePD(o,0,r,0,o.length),r[o.length]=e.Ci(),this.g=r,!0}return!1},Y5(eJz,"FeatureMapUtil/FeatureENotificationImpl",627),eTS(552,501,{20:1,28:1,52:1,14:1,15:1,58:1,76:1,153:1,215:1,1937:1,69:1,95:1},RA),eUe.dl=function(e,t){return eIF(this.c,e,t)},eUe.el=function(e,t,n){return eCB(this.c,e,t,n)},eUe.fl=function(e,t,n){return ePT(this.c,e,t,n)},eUe.gl=function(){return this},eUe.hl=function(e,t){return ePC(this.c,e,t)},eUe.il=function(e){return Pp(ePL(this.c,this.b,e,!1),72).ak()},eUe.jl=function(e){return Pp(ePL(this.c,this.b,e,!1),72).dd()},eUe.kl=function(){return this.a},eUe.ll=function(e){return!edK(this.c,e)},eUe.ml=function(e,t){ePJ(this.c,e,t)},eUe.nl=function(e){return erp(this.c,e)},eUe.ol=function(e){emY(this.c,e)},Y5(eJz,"FeatureMapUtil/FeatureFeatureMap",552),eTS(1259,1,eJG,xr),eUe.Wj=function(e){return ePL(this.b,this.a,-1,e)},eUe.fj=function(){return!edK(this.b,this.a)},eUe.Wb=function(e){ePJ(this.b,this.a,e)},eUe.Xj=function(){exZ(this.b,this.a)},Y5(eJz,"FeatureMapUtil/FeatureValue",1259);var tve=RL(eQz,"AnyType");eTS(666,60,eHr,gV),Y5(eQz,"InvalidDatatypeValueException",666);var tvt=RL(eQz,eQG),tvn=RL(eQz,eQW),tvr=RL(eQz,eQK);eTS(830,506,{105:1,92:1,90:1,56:1,49:1,97:1,843:1},mF),eUe._g=function(e,t,n){switch(e){case 0:if(n)return this.c||(this.c=new eiR(this,0)),this.c;return this.c||(this.c=new eiR(this,0)),this.c.b;case 1:if(n)return this.c||(this.c=new eiR(this,0)),Pp(GP(this.c,(eR7(),tvx)),153);return(this.c||(this.c=new eiR(this,0)),Pp(Pp(GP(this.c,(eR7(),tvx)),153),215)).kl();case 2:if(n)return this.b||(this.b=new eiR(this,2)),this.b;return this.b||(this.b=new eiR(this,2)),this.b.b}return Qt(this,e-Y1(this.zh()),ee2((2&this.j)==0?this.zh():(this.k||(this.k=new c1),this.k).ck(),e),t,n)},eUe.jh=function(e,t,n){var r;switch(t){case 0:return this.c||(this.c=new eiR(this,0)),eIM(this.c,e,n);case 1:return(this.c||(this.c=new eiR(this,0)),Pp(Pp(GP(this.c,(eR7(),tvx)),153),69)).mk(e,n);case 2:return this.b||(this.b=new eiR(this,2)),eIM(this.b,e,n)}return(r=Pp(ee2((2&this.j)==0?this.zh():(this.k||(this.k=new c1),this.k).ck(),t),66)).Nj().Rj(this,Q5(this),t-Y1(this.zh()),e,n)},eUe.lh=function(e){switch(e){case 0:return!!this.c&&0!=this.c.i;case 1:return!(this.c||(this.c=new eiR(this,0)),Pp(GP(this.c,(eR7(),tvx)),153)).dc();case 2:return!!this.b&&0!=this.b.i}return VP(this,e-Y1(this.zh()),ee2((2&this.j)==0?this.zh():(this.k||(this.k=new c1),this.k).ck(),e))},eUe.sh=function(e,t){switch(e){case 0:this.c||(this.c=new eiR(this,0)),YH(this.c,t);return;case 1:(this.c||(this.c=new eiR(this,0)),Pp(Pp(GP(this.c,(eR7(),tvx)),153),215)).Wb(t);return;case 2:this.b||(this.b=new eiR(this,2)),YH(this.b,t);return}efL(this,e-Y1(this.zh()),ee2((2&this.j)==0?this.zh():(this.k||(this.k=new c1),this.k).ck(),e),t)},eUe.zh=function(){return eR7(),tvk},eUe.Bh=function(e){switch(e){case 0:this.c||(this.c=new eiR(this,0)),eRT(this.c);return;case 1:(this.c||(this.c=new eiR(this,0)),Pp(GP(this.c,(eR7(),tvx)),153)).$b();return;case 2:this.b||(this.b=new eiR(this,2)),eRT(this.b);return}ec6(this,e-Y1(this.zh()),ee2((2&this.j)==0?this.zh():(this.k||(this.k=new c1),this.k).ck(),e))},eUe.Ib=function(){var e;return(4&this.j)!=0?eMT(this):(e=new O1(eMT(this)),e.a+=" (mixed: ",xS(e,this.c),e.a+=", anyAttribute: ",xS(e,this.b),e.a+=")",e.a)},Y5(eQV,"AnyTypeImpl",830),eTS(667,506,{105:1,92:1,90:1,56:1,49:1,97:1,2021:1,667:1},ul),eUe._g=function(e,t,n){switch(e){case 0:return this.a;case 1:return this.b}return Qt(this,e-Y1((eR7(),tvj)),ee2((2&this.j)==0?tvj:(this.k||(this.k=new c1),this.k).ck(),e),t,n)},eUe.lh=function(e){switch(e){case 0:return null!=this.a;case 1:return null!=this.b}return VP(this,e-Y1((eR7(),tvj)),ee2((2&this.j)==0?tvj:(this.k||(this.k=new c1),this.k).ck(),e))},eUe.sh=function(e,t){switch(e){case 0:fg(this,Lq(t));return;case 1:fv(this,Lq(t));return}efL(this,e-Y1((eR7(),tvj)),ee2((2&this.j)==0?tvj:(this.k||(this.k=new c1),this.k).ck(),e),t)},eUe.zh=function(){return eR7(),tvj},eUe.Bh=function(e){switch(e){case 0:this.a=null;return;case 1:this.b=null;return}ec6(this,e-Y1((eR7(),tvj)),ee2((2&this.j)==0?tvj:(this.k||(this.k=new c1),this.k).ck(),e))},eUe.Ib=function(){var e;return(4&this.j)!=0?eMT(this):(e=new O1(eMT(this)),e.a+=" (data: ",xk(e,this.a),e.a+=", target: ",xk(e,this.b),e.a+=")",e.a)},eUe.a=null,eUe.b=null,Y5(eQV,"ProcessingInstructionImpl",667),eTS(668,830,{105:1,92:1,90:1,56:1,49:1,97:1,843:1,2022:1,668:1},mB),eUe._g=function(e,t,n){switch(e){case 0:if(n)return this.c||(this.c=new eiR(this,0)),this.c;return this.c||(this.c=new eiR(this,0)),this.c.b;case 1:if(n)return this.c||(this.c=new eiR(this,0)),Pp(GP(this.c,(eR7(),tvx)),153);return(this.c||(this.c=new eiR(this,0)),Pp(Pp(GP(this.c,(eR7(),tvx)),153),215)).kl();case 2:if(n)return this.b||(this.b=new eiR(this,2)),this.b;return this.b||(this.b=new eiR(this,2)),this.b.b;case 3:return this.c||(this.c=new eiR(this,0)),Lq(ePC(this.c,(eR7(),tvB),!0));case 4:return Iy(this.a,(this.c||(this.c=new eiR(this,0)),Lq(ePC(this.c,(eR7(),tvB),!0))));case 5:return this.a}return Qt(this,e-Y1((eR7(),tvY)),ee2((2&this.j)==0?tvY:(this.k||(this.k=new c1),this.k).ck(),e),t,n)},eUe.lh=function(e){switch(e){case 0:return!!this.c&&0!=this.c.i;case 1:return!(this.c||(this.c=new eiR(this,0)),Pp(GP(this.c,(eR7(),tvx)),153)).dc();case 2:return!!this.b&&0!=this.b.i;case 3:return this.c||(this.c=new eiR(this,0)),null!=Lq(ePC(this.c,(eR7(),tvB),!0));case 4:return null!=Iy(this.a,(this.c||(this.c=new eiR(this,0)),Lq(ePC(this.c,(eR7(),tvB),!0))));case 5:return!!this.a}return VP(this,e-Y1((eR7(),tvY)),ee2((2&this.j)==0?tvY:(this.k||(this.k=new c1),this.k).ck(),e))},eUe.sh=function(e,t){switch(e){case 0:this.c||(this.c=new eiR(this,0)),YH(this.c,t);return;case 1:(this.c||(this.c=new eiR(this,0)),Pp(Pp(GP(this.c,(eR7(),tvx)),153),215)).Wb(t);return;case 2:this.b||(this.b=new eiR(this,2)),YH(this.b,t);return;case 3:Kt(this,Lq(t));return;case 4:Kt(this,Iw(this.a,t));return;case 5:fy(this,Pp(t,148));return}efL(this,e-Y1((eR7(),tvY)),ee2((2&this.j)==0?tvY:(this.k||(this.k=new c1),this.k).ck(),e),t)},eUe.zh=function(){return eR7(),tvY},eUe.Bh=function(e){switch(e){case 0:this.c||(this.c=new eiR(this,0)),eRT(this.c);return;case 1:(this.c||(this.c=new eiR(this,0)),Pp(GP(this.c,(eR7(),tvx)),153)).$b();return;case 2:this.b||(this.b=new eiR(this,2)),eRT(this.b);return;case 3:this.c||(this.c=new eiR(this,0)),ePJ(this.c,(eR7(),tvB),null);return;case 4:Kt(this,Iw(this.a,null));return;case 5:this.a=null;return}ec6(this,e-Y1((eR7(),tvY)),ee2((2&this.j)==0?tvY:(this.k||(this.k=new c1),this.k).ck(),e))},Y5(eQV,"SimpleAnyTypeImpl",668),eTS(669,506,{105:1,92:1,90:1,56:1,49:1,97:1,2023:1,669:1},mY),eUe._g=function(e,t,n){switch(e){case 0:if(n)return this.a||(this.a=new eiR(this,0)),this.a;return this.a||(this.a=new eiR(this,0)),this.a.b;case 1:return n?(this.b||(this.b=new JY((eBK(),tgF),tgf,this,1)),this.b):(this.b||(this.b=new JY((eBK(),tgF),tgf,this,1)),X6(this.b));case 2:return n?(this.c||(this.c=new JY((eBK(),tgF),tgf,this,2)),this.c):(this.c||(this.c=new JY((eBK(),tgF),tgf,this,2)),X6(this.c));case 3:return this.a||(this.a=new eiR(this,0)),GP(this.a,(eR7(),tv$));case 4:return this.a||(this.a=new eiR(this,0)),GP(this.a,(eR7(),tvz));case 5:return this.a||(this.a=new eiR(this,0)),GP(this.a,(eR7(),tvW));case 6:return this.a||(this.a=new eiR(this,0)),GP(this.a,(eR7(),tvK))}return Qt(this,e-Y1((eR7(),tvH)),ee2((2&this.j)==0?tvH:(this.k||(this.k=new c1),this.k).ck(),e),t,n)},eUe.jh=function(e,t,n){var r;switch(t){case 0:return this.a||(this.a=new eiR(this,0)),eIM(this.a,e,n);case 1:return this.b||(this.b=new JY((eBK(),tgF),tgf,this,1)),Iz(this.b,e,n);case 2:return this.c||(this.c=new JY((eBK(),tgF),tgf,this,2)),Iz(this.c,e,n);case 5:return this.a||(this.a=new eiR(this,0)),Cp(GP(this.a,(eR7(),tvW)),e,n)}return(r=Pp(ee2((2&this.j)==0?(eR7(),tvH):(this.k||(this.k=new c1),this.k).ck(),t),66)).Nj().Rj(this,Q5(this),t-Y1((eR7(),tvH)),e,n)},eUe.lh=function(e){switch(e){case 0:return!!this.a&&0!=this.a.i;case 1:return!!this.b&&0!=this.b.f;case 2:return!!this.c&&0!=this.c.f;case 3:return this.a||(this.a=new eiR(this,0)),!xs(GP(this.a,(eR7(),tv$)));case 4:return this.a||(this.a=new eiR(this,0)),!xs(GP(this.a,(eR7(),tvz)));case 5:return this.a||(this.a=new eiR(this,0)),!xs(GP(this.a,(eR7(),tvW)));case 6:return this.a||(this.a=new eiR(this,0)),!xs(GP(this.a,(eR7(),tvK)))}return VP(this,e-Y1((eR7(),tvH)),ee2((2&this.j)==0?tvH:(this.k||(this.k=new c1),this.k).ck(),e))},eUe.sh=function(e,t){switch(e){case 0:this.a||(this.a=new eiR(this,0)),YH(this.a,t);return;case 1:this.b||(this.b=new JY((eBK(),tgF),tgf,this,1)),eai(this.b,t);return;case 2:this.c||(this.c=new JY((eBK(),tgF),tgf,this,2)),eai(this.c,t);return;case 3:this.a||(this.a=new eiR(this,0)),bG(GP(this.a,(eR7(),tv$))),this.a||(this.a=new eiR(this,0)),MJ(GP(this.a,tv$),Pp(t,14));return;case 4:this.a||(this.a=new eiR(this,0)),bG(GP(this.a,(eR7(),tvz))),this.a||(this.a=new eiR(this,0)),MJ(GP(this.a,tvz),Pp(t,14));return;case 5:this.a||(this.a=new eiR(this,0)),bG(GP(this.a,(eR7(),tvW))),this.a||(this.a=new eiR(this,0)),MJ(GP(this.a,tvW),Pp(t,14));return;case 6:this.a||(this.a=new eiR(this,0)),bG(GP(this.a,(eR7(),tvK))),this.a||(this.a=new eiR(this,0)),MJ(GP(this.a,tvK),Pp(t,14));return}efL(this,e-Y1((eR7(),tvH)),ee2((2&this.j)==0?tvH:(this.k||(this.k=new c1),this.k).ck(),e),t)},eUe.zh=function(){return eR7(),tvH},eUe.Bh=function(e){switch(e){case 0:this.a||(this.a=new eiR(this,0)),eRT(this.a);return;case 1:this.b||(this.b=new JY((eBK(),tgF),tgf,this,1)),this.b.c.$b();return;case 2:this.c||(this.c=new JY((eBK(),tgF),tgf,this,2)),this.c.c.$b();return;case 3:this.a||(this.a=new eiR(this,0)),bG(GP(this.a,(eR7(),tv$)));return;case 4:this.a||(this.a=new eiR(this,0)),bG(GP(this.a,(eR7(),tvz)));return;case 5:this.a||(this.a=new eiR(this,0)),bG(GP(this.a,(eR7(),tvW)));return;case 6:this.a||(this.a=new eiR(this,0)),bG(GP(this.a,(eR7(),tvK)));return}ec6(this,e-Y1((eR7(),tvH)),ee2((2&this.j)==0?tvH:(this.k||(this.k=new c1),this.k).ck(),e))},eUe.Ib=function(){var e;return(4&this.j)!=0?eMT(this):(e=new O1(eMT(this)),e.a+=" (mixed: ",xS(e,this.a),e.a+=")",e.a)},Y5(eQV,"XMLTypeDocumentRootImpl",669),eTS(1919,704,{105:1,92:1,90:1,471:1,147:1,56:1,108:1,49:1,97:1,150:1,114:1,115:1,2024:1},uc),eUe.Ih=function(e,t){switch(e.yj()){case 7:case 8:case 9:case 10:case 16:case 22:case 23:case 24:case 25:case 26:case 32:case 33:case 34:case 36:case 37:case 44:case 45:case 50:case 51:case 53:case 55:case 56:case 57:case 58:case 60:case 61:case 4:return null==t?null:efF(t);case 19:case 28:case 29:case 35:case 38:case 39:case 41:case 46:case 52:case 54:case 5:return Lq(t);case 6:return LH(Pp(t,190));case 12:case 47:case 49:case 11:return ejZ(this,e,t);case 13:return null==t?null:ePg(Pp(t,240));case 15:case 14:return null==t?null:Yk(gP(LV(t)));case 17:return eyV((eR7(),t));case 18:return eyV(t);case 21:case 20:return null==t?null:Yx(Pp(t,155).a);case 27:return L$(Pp(t,190));case 30:return emB((eR7(),Pp(t,15)));case 31:return emB(Pp(t,15));case 40:return LG((eR7(),t));case 42:return eyq((eR7(),t));case 43:return eyq(t);case 59:case 48:return Lz((eR7(),t));default:throw p7(new gL(eZ5+e.ne()+eZ6))}},eUe.Jh=function(e){var t;switch(-1==e.G&&(e.G=(t=etP(e))?ebv(t.Mh(),e):-1),e.G){case 0:return new mF;case 1:return new ul;case 2:return new mB;case 3:return new mY;default:throw p7(new gL(eZ7+e.zb+eZ6))}},eUe.Kh=function(e,t){var n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g;switch(e.yj()){case 5:case 52:case 4:return t;case 6:return epU(t);case 8:case 7:return null==t?null:ewe(t);case 9:return null==t?null:eeT(eDa((r=ePh(t,!0)).length>0&&(GV(0,r.length),43==r.charCodeAt(0))?r.substr(1):r,-128,127)<<24>>24);case 10:return null==t?null:eeT(eDa((i=ePh(t,!0)).length>0&&(GV(0,i.length),43==i.charCodeAt(0))?i.substr(1):i,-128,127)<<24>>24);case 11:return Lq(eBd(this,(eR7(),tvO),t));case 12:return Lq(eBd(this,(eR7(),tvA),t));case 13:return null==t?null:new yY(ePh(t,!0));case 15:case 14:return eOa(t);case 16:return Lq(eBd(this,(eR7(),tvL),t));case 17:return ehy((eR7(),t));case 18:return ehy(t);case 28:case 29:case 35:case 38:case 39:case 41:case 54:case 19:return ePh(t,!0);case 21:case 20:return eOv(t);case 22:return Lq(eBd(this,(eR7(),tvC),t));case 23:return Lq(eBd(this,(eR7(),tvI),t));case 24:return Lq(eBd(this,(eR7(),tvD),t));case 25:return Lq(eBd(this,(eR7(),tvN),t));case 26:return Lq(eBd(this,(eR7(),tvP),t));case 27:return epw(t);case 30:return ehw((eR7(),t));case 31:return ehw(t);case 32:return null==t?null:ell(eDa((l=ePh(t,!0)).length>0&&(GV(0,l.length),43==l.charCodeAt(0))?l.substr(1):l,eHt,eUu));case 33:return null==t?null:new TU((f=ePh(t,!0)).length>0&&(GV(0,f.length),43==f.charCodeAt(0))?f.substr(1):f);case 34:return null==t?null:ell(eDa((d=ePh(t,!0)).length>0&&(GV(0,d.length),43==d.charCodeAt(0))?d.substr(1):d,eHt,eUu));case 36:return null==t?null:ehQ(eF0((h=ePh(t,!0)).length>0&&(GV(0,h.length),43==h.charCodeAt(0))?h.substr(1):h));case 37:return null==t?null:ehQ(eF0((p=ePh(t,!0)).length>0&&(GV(0,p.length),43==p.charCodeAt(0))?p.substr(1):p));case 40:return edR((eR7(),t));case 42:return eh_((eR7(),t));case 43:return eh_(t);case 44:return null==t?null:new TU((b=ePh(t,!0)).length>0&&(GV(0,b.length),43==b.charCodeAt(0))?b.substr(1):b);case 45:return null==t?null:new TU((m=ePh(t,!0)).length>0&&(GV(0,m.length),43==m.charCodeAt(0))?m.substr(1):m);case 46:return ePh(t,!1);case 47:return Lq(eBd(this,(eR7(),tvR),t));case 59:case 48:return edP((eR7(),t));case 49:return Lq(eBd(this,(eR7(),tvF),t));case 50:return null==t?null:elf(eDa((g=ePh(t,!0)).length>0&&(GV(0,g.length),43==g.charCodeAt(0))?g.substr(1):g,eQl,32767)<<16>>16);case 51:return null==t?null:elf(eDa((a=ePh(t,!0)).length>0&&(GV(0,a.length),43==a.charCodeAt(0))?a.substr(1):a,eQl,32767)<<16>>16);case 53:return Lq(eBd(this,(eR7(),tvU),t));case 55:return null==t?null:elf(eDa((o=ePh(t,!0)).length>0&&(GV(0,o.length),43==o.charCodeAt(0))?o.substr(1):o,eQl,32767)<<16>>16);case 56:return null==t?null:elf(eDa((s=ePh(t,!0)).length>0&&(GV(0,s.length),43==s.charCodeAt(0))?s.substr(1):s,eQl,32767)<<16>>16);case 57:return null==t?null:ehQ(eF0((u=ePh(t,!0)).length>0&&(GV(0,u.length),43==u.charCodeAt(0))?u.substr(1):u));case 58:return null==t?null:ehQ(eF0((c=ePh(t,!0)).length>0&&(GV(0,c.length),43==c.charCodeAt(0))?c.substr(1):c));case 60:return null==t?null:ell(eDa((n=ePh(t,!0)).length>0&&(GV(0,n.length),43==n.charCodeAt(0))?n.substr(1):n,eHt,eUu));case 61:return null==t?null:ell(eDa(ePh(t,!0),eHt,eUu));default:throw p7(new gL(eZ5+e.ne()+eZ6))}},Y5(eQV,"XMLTypeFactoryImpl",1919),eTS(586,179,{105:1,92:1,90:1,147:1,191:1,56:1,235:1,108:1,49:1,97:1,150:1,179:1,114:1,115:1,675:1,1945:1,586:1},Ud),eUe.N=!1,eUe.O=!1;var tvi=!1;Y5(eQV,"XMLTypePackageImpl",586),eTS(1852,1,{837:1},uf),eUe._j=function(){return eD4(),eB2},Y5(eQV,"XMLTypePackageImpl/1",1852),eTS(1861,1,eQS,ud),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/10",1861),eTS(1862,1,eQS,uh),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/11",1862),eTS(1863,1,eQS,up),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/12",1863),eTS(1864,1,eQS,ub),eUe.wj=function(e){return xf(e)},eUe.xj=function(e){return Je(e13,eUP,333,e,7,1)},Y5(eQV,"XMLTypePackageImpl/13",1864),eTS(1865,1,eQS,um),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/14",1865),eTS(1866,1,eQS,ug),eUe.wj=function(e){return M4(e,15)},eUe.xj=function(e){return Je(e1H,ezZ,15,e,0,1)},Y5(eQV,"XMLTypePackageImpl/15",1866),eTS(1867,1,eQS,uv),eUe.wj=function(e){return M4(e,15)},eUe.xj=function(e){return Je(e1H,ezZ,15,e,0,1)},Y5(eQV,"XMLTypePackageImpl/16",1867),eTS(1868,1,eQS,uy),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/17",1868),eTS(1869,1,eQS,uw),eUe.wj=function(e){return M4(e,155)},eUe.xj=function(e){return Je(e14,eUP,155,e,0,1)},Y5(eQV,"XMLTypePackageImpl/18",1869),eTS(1870,1,eQS,u_),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/19",1870),eTS(1853,1,eQS,uE),eUe.wj=function(e){return M4(e,843)},eUe.xj=function(e){return Je(tve,eUp,843,e,0,1)},Y5(eQV,"XMLTypePackageImpl/2",1853),eTS(1871,1,eQS,uS),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/20",1871),eTS(1872,1,eQS,uk),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/21",1872),eTS(1873,1,eQS,ux),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/22",1873),eTS(1874,1,eQS,uT),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/23",1874),eTS(1875,1,eQS,uM),eUe.wj=function(e){return M4(e,190)},eUe.xj=function(e){return Je(tyk,eUP,190,e,0,2)},Y5(eQV,"XMLTypePackageImpl/24",1875),eTS(1876,1,eQS,uO),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/25",1876),eTS(1877,1,eQS,uA),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/26",1877),eTS(1878,1,eQS,uL),eUe.wj=function(e){return M4(e,15)},eUe.xj=function(e){return Je(e1H,ezZ,15,e,0,1)},Y5(eQV,"XMLTypePackageImpl/27",1878),eTS(1879,1,eQS,uC),eUe.wj=function(e){return M4(e,15)},eUe.xj=function(e){return Je(e1H,ezZ,15,e,0,1)},Y5(eQV,"XMLTypePackageImpl/28",1879),eTS(1880,1,eQS,uI),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/29",1880),eTS(1854,1,eQS,uD),eUe.wj=function(e){return M4(e,667)},eUe.xj=function(e){return Je(tvt,eUp,2021,e,0,1)},Y5(eQV,"XMLTypePackageImpl/3",1854),eTS(1881,1,eQS,uN),eUe.wj=function(e){return M4(e,19)},eUe.xj=function(e){return Je(e15,eUP,19,e,0,1)},Y5(eQV,"XMLTypePackageImpl/30",1881),eTS(1882,1,eQS,uP),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/31",1882),eTS(1883,1,eQS,uR),eUe.wj=function(e){return M4(e,162)},eUe.xj=function(e){return Je(e16,eUP,162,e,0,1)},Y5(eQV,"XMLTypePackageImpl/32",1883),eTS(1884,1,eQS,uj),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/33",1884),eTS(1885,1,eQS,uF),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/34",1885),eTS(1886,1,eQS,uY),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/35",1886),eTS(1887,1,eQS,uB),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/36",1887),eTS(1888,1,eQS,uU),eUe.wj=function(e){return M4(e,15)},eUe.xj=function(e){return Je(e1H,ezZ,15,e,0,1)},Y5(eQV,"XMLTypePackageImpl/37",1888),eTS(1889,1,eQS,uH),eUe.wj=function(e){return M4(e,15)},eUe.xj=function(e){return Je(e1H,ezZ,15,e,0,1)},Y5(eQV,"XMLTypePackageImpl/38",1889),eTS(1890,1,eQS,u$),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/39",1890),eTS(1855,1,eQS,uz),eUe.wj=function(e){return M4(e,668)},eUe.xj=function(e){return Je(tvn,eUp,2022,e,0,1)},Y5(eQV,"XMLTypePackageImpl/4",1855),eTS(1891,1,eQS,uG),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/40",1891),eTS(1892,1,eQS,uW),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/41",1892),eTS(1893,1,eQS,uK),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/42",1893),eTS(1894,1,eQS,uV),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/43",1894),eTS(1895,1,eQS,uq),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/44",1895),eTS(1896,1,eQS,uZ),eUe.wj=function(e){return M4(e,184)},eUe.xj=function(e){return Je(e19,eUP,184,e,0,1)},Y5(eQV,"XMLTypePackageImpl/45",1896),eTS(1897,1,eQS,uX),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/46",1897),eTS(1898,1,eQS,uJ),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/47",1898),eTS(1899,1,eQS,uQ),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/48",1899),eTS(eHx,1,eQS,u1),eUe.wj=function(e){return M4(e,184)},eUe.xj=function(e){return Je(e19,eUP,184,e,0,1)},Y5(eQV,"XMLTypePackageImpl/49",eHx),eTS(1856,1,eQS,u0),eUe.wj=function(e){return M4(e,669)},eUe.xj=function(e){return Je(tvr,eUp,2023,e,0,1)},Y5(eQV,"XMLTypePackageImpl/5",1856),eTS(1901,1,eQS,u2),eUe.wj=function(e){return M4(e,162)},eUe.xj=function(e){return Je(e16,eUP,162,e,0,1)},Y5(eQV,"XMLTypePackageImpl/50",1901),eTS(1902,1,eQS,u3),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/51",1902),eTS(1903,1,eQS,u4),eUe.wj=function(e){return M4(e,19)},eUe.xj=function(e){return Je(e15,eUP,19,e,0,1)},Y5(eQV,"XMLTypePackageImpl/52",1903),eTS(1857,1,eQS,u5),eUe.wj=function(e){return xd(e)},eUe.xj=function(e){return Je(e17,eUP,2,e,6,1)},Y5(eQV,"XMLTypePackageImpl/6",1857),eTS(1858,1,eQS,u6),eUe.wj=function(e){return M4(e,190)},eUe.xj=function(e){return Je(tyk,eUP,190,e,0,2)},Y5(eQV,"XMLTypePackageImpl/7",1858),eTS(1859,1,eQS,u9),eUe.wj=function(e){return xl(e)},eUe.xj=function(e){return Je(e11,eUP,476,e,8,1)},Y5(eQV,"XMLTypePackageImpl/8",1859),eTS(1860,1,eQS,u8),eUe.wj=function(e){return M4(e,217)},eUe.xj=function(e){return Je(e10,eUP,217,e,0,1)},Y5(eQV,"XMLTypePackageImpl/9",1860),eTS(50,60,eHr,gX),Y5(e1l,"RegEx/ParseException",50),eTS(820,1,{},u7),eUe.sl=function(e){return e16*n)throw p7(new gX(eBJ((Mo(),eJd))));n=16*n+i}if(125!=this.a)throw p7(new gX(eBJ((Mo(),eJh))));if(n>e1f)throw p7(new gX(eBJ((Mo(),eJp))));e=n}else{if(i=0,0!=this.c||(i=eb0(this.a))<0||(n=i,eBM(this),0!=this.c||(i=eb0(this.a))<0))throw p7(new gX(eBJ((Mo(),eJf))));e=n=16*n+i}break;case 117:if(r=0,eBM(this),0!=this.c||(r=eb0(this.a))<0||(t=r,eBM(this),0!=this.c||(r=eb0(this.a))<0)||(t=16*t+r,eBM(this),0!=this.c||(r=eb0(this.a))<0)||(t=16*t+r,eBM(this),0!=this.c||(r=eb0(this.a))<0))throw p7(new gX(eBJ((Mo(),eJf))));e=t=16*t+r;break;case 118:if(eBM(this),0!=this.c||(r=eb0(this.a))<0||(t=r,eBM(this),0!=this.c||(r=eb0(this.a))<0)||(t=16*t+r,eBM(this),0!=this.c||(r=eb0(this.a))<0)||(t=16*t+r,eBM(this),0!=this.c||(r=eb0(this.a))<0)||(t=16*t+r,eBM(this),0!=this.c||(r=eb0(this.a))<0)||(t=16*t+r,eBM(this),0!=this.c||(r=eb0(this.a))<0))throw p7(new gX(eBJ((Mo(),eJf))));if((t=16*t+r)>e1f)throw p7(new gX(eBJ((Mo(),"parser.descappe.4"))));e=t;break;case 65:case 90:case 122:throw p7(new gX(eBJ((Mo(),eJb))))}return e},eUe.ul=function(e){var t,n;switch(e){case 100:n=(32&this.e)==32?eYB("Nd",!0):(eBG(),tv8);break;case 68:n=(32&this.e)==32?eYB("Nd",!1):(eBG(),tyr);break;case 119:n=(32&this.e)==32?eYB("IsWord",!0):(eBG(),tyd);break;case 87:n=(32&this.e)==32?eYB("IsWord",!1):(eBG(),tya);break;case 115:n=(32&this.e)==32?eYB("IsSpace",!0):(eBG(),tys);break;case 83:n=(32&this.e)==32?eYB("IsSpace",!1):(eBG(),tyi);break;default:throw p7(new go(e1d+(t=e).toString(16)))}return n},eUe.vl=function(e){var t,n,r,i,a,o,s,u,c,l,f,d;for(this.b=1,eBM(this),t=null,0==this.c&&94==this.a?(eBM(this),e?l=(eBG(),eBG(),++tyv,new WZ(5)):(t=(eBG(),eBG(),++tyv,new WZ(4)),eLw(t,0,e1f),l=(++tyv,new WZ(4)))):l=(eBG(),eBG(),++tyv,new WZ(4)),i=!0;1!=(d=this.c)&&(0!=d||93!=this.a||i);){if(i=!1,n=this.a,r=!1,10==d)switch(n){case 100:case 68:case 119:case 87:case 115:case 83:ePR(l,this.ul(n)),r=!0;break;case 105:case 73:case 99:case 67:(n=this.Ll(l,n))<0&&(r=!0);break;case 112:case 80:if(!(f=ext(this,n)))throw p7(new gX(eBJ((Mo(),eJe))));ePR(l,f),r=!0;break;default:n=this.tl()}else if(20==d){if((o=AG(this.i,58,this.d))<0)throw p7(new gX(eBJ((Mo(),eJt))));if(s=!0,94==UI(this.i,this.d)&&(++this.d,s=!1),!(u=JI(a=Az(this.i,this.d,o),s,(512&this.e)==512)))throw p7(new gX(eBJ((Mo(),eJr))));if(ePR(l,u),r=!0,o+1>=this.j||93!=UI(this.i,o+1))throw p7(new gX(eBJ((Mo(),eJt))));this.d=o+2}if(eBM(this),!r){if(0!=this.c||45!=this.a)eLw(l,n,n);else{if(eBM(this),1==(d=this.c))throw p7(new gX(eBJ((Mo(),eJn))));0==d&&93==this.a?(eLw(l,n,n),eLw(l,45,45)):(c=this.a,10==d&&(c=this.tl()),eBM(this),eLw(l,n,c))}}(this.e&eXt)==eXt&&0==this.c&&44==this.a&&eBM(this)}if(1==this.c)throw p7(new gX(eBJ((Mo(),eJn))));return t&&(ej0(t,l),l=t),eMS(l),eRo(l),this.b=0,eBM(this),l},eUe.wl=function(){var e,t,n,r;for(n=this.vl(!1);7!=(r=this.c);)if(e=this.a,0==r&&(45==e||38==e)||4==r){if(eBM(this),9!=this.c)throw p7(new gX(eBJ((Mo(),eJu))));if(t=this.vl(!1),4==r)ePR(n,t);else if(45==e)ej0(n,t);else if(38==e)ejO(n,t);else throw p7(new go("ASSERT"))}else throw p7(new gX(eBJ((Mo(),eJc))));return eBM(this),n},eUe.xl=function(){var e,t;return e=this.a-48,t=(eBG(),eBG(),++tyv,new zc(12,null,e)),this.g||(this.g=new bZ),bY(this.g,new pX(e)),eBM(this),t},eUe.yl=function(){return eBM(this),eBG(),tyu},eUe.zl=function(){return eBM(this),eBG(),tyo},eUe.Al=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Bl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Cl=function(){return eBM(this),esV()},eUe.Dl=function(){return eBM(this),eBG(),tyl},eUe.El=function(){return eBM(this),eBG(),tyh},eUe.Fl=function(){var e;if(this.d>=this.j||(65504&(e=UI(this.i,this.d++)))!=64)throw p7(new gX(eBJ((Mo(),eX6))));return eBM(this),eBG(),eBG(),++tyv,new jb(0,e-64)},eUe.Gl=function(){return eBM(this),eNw()},eUe.Hl=function(){return eBM(this),eBG(),typ},eUe.Il=function(){var e;return e=(eBG(),eBG(),++tyv,new jb(0,105)),eBM(this),e},eUe.Jl=function(){return eBM(this),eBG(),tyf},eUe.Kl=function(){return eBM(this),eBG(),tyc},eUe.Ll=function(e,t){return this.tl()},eUe.Ml=function(){return eBM(this),eBG(),tyt},eUe.Nl=function(){var e,t,n,r,i;if(this.d+1>=this.j)throw p7(new gX(eBJ((Mo(),eX3))));if(r=-1,t=null,49<=(e=UI(this.i,this.d))&&e<=57){if(r=e-48,this.g||(this.g=new bZ),bY(this.g,new pX(r)),++this.d,41!=UI(this.i,this.d))throw p7(new gX(eBJ((Mo(),eX1))));++this.d}else switch(63==e&&--this.d,eBM(this),(t=eBs(this)).e){case 20:case 21:case 22:case 23:break;case 8:if(7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));break;default:throw p7(new gX(eBJ((Mo(),eX4))))}if(eBM(this),i=ehT(this),n=null,2==i.e){if(2!=i.em())throw p7(new gX(eBJ((Mo(),eX5))));n=i.am(1),i=i.am(0)}if(7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));return eBM(this),eBG(),eBG(),++tyv,new ee_(r,t,i,n)},eUe.Ol=function(){return eBM(this),eBG(),tyn},eUe.Pl=function(){var e;if(eBM(this),e=F4(24,ehT(this)),7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));return eBM(this),e},eUe.Ql=function(){var e;if(eBM(this),e=F4(20,ehT(this)),7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));return eBM(this),e},eUe.Rl=function(){var e;if(eBM(this),e=F4(22,ehT(this)),7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));return eBM(this),e},eUe.Sl=function(){var e,t,n,r,i;for(e=0,n=0,t=-1;this.d=this.j)throw p7(new gX(eBJ((Mo(),eX0))));if(45==t){for(++this.d;this.d=this.j)throw p7(new gX(eBJ((Mo(),eX0))))}if(58==t){if(++this.d,eBM(this),r=Bu(ehT(this),e,n),7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));eBM(this)}else if(41==t)++this.d,eBM(this),r=Bu(ehT(this),e,n);else throw p7(new gX(eBJ((Mo(),eX2))));return r},eUe.Tl=function(){var e;if(eBM(this),e=F4(21,ehT(this)),7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));return eBM(this),e},eUe.Ul=function(){var e;if(eBM(this),e=F4(23,ehT(this)),7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));return eBM(this),e},eUe.Vl=function(){var e,t;if(eBM(this),e=this.f++,t=F5(ehT(this),e),7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));return eBM(this),t},eUe.Wl=function(){var e;if(eBM(this),e=F5(ehT(this),0),7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));return eBM(this),e},eUe.Xl=function(e){return(eBM(this),5==this.c)?(eBM(this),jS(e,(eBG(),eBG(),++tyv,new qa(9,e)))):jS(e,(eBG(),eBG(),++tyv,new qa(3,e)))},eUe.Yl=function(e){var t;return eBM(this),t=(eBG(),eBG(),++tyv,new Mr(2)),5==this.c?(eBM(this),eRv(t,tye),eRv(t,e)):(eRv(t,e),eRv(t,tye)),t},eUe.Zl=function(e){return(eBM(this),5==this.c)?(eBM(this),eBG(),eBG(),++tyv,new qa(9,e)):(eBG(),eBG(),++tyv,new qa(3,e))},eUe.a=0,eUe.b=0,eUe.c=0,eUe.d=0,eUe.e=0,eUe.f=1,eUe.g=null,eUe.j=0,Y5(e1l,"RegEx/RegexParser",820),eTS(1824,820,{},mU),eUe.sl=function(e){return!1},eUe.tl=function(){return eCn(this)},eUe.ul=function(e){return eDu(e)},eUe.vl=function(e){return eBL(this)},eUe.wl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.xl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.yl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.zl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Al=function(){return eBM(this),eDu(67)},eUe.Bl=function(){return eBM(this),eDu(73)},eUe.Cl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Dl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.El=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Fl=function(){return eBM(this),eDu(99)},eUe.Gl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Hl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Il=function(){return eBM(this),eDu(105)},eUe.Jl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Kl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Ll=function(e,t){return ePR(e,eDu(t)),-1},eUe.Ml=function(){return eBM(this),eBG(),eBG(),++tyv,new jb(0,94)},eUe.Nl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Ol=function(){return eBM(this),eBG(),eBG(),++tyv,new jb(0,36)},eUe.Pl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Ql=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Rl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Sl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Tl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Ul=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Vl=function(){var e;if(eBM(this),e=F5(ehT(this),0),7!=this.c)throw p7(new gX(eBJ((Mo(),eX1))));return eBM(this),e},eUe.Wl=function(){throw p7(new gX(eBJ((Mo(),eJm))))},eUe.Xl=function(e){return eBM(this),jS(e,(eBG(),eBG(),++tyv,new qa(3,e)))},eUe.Yl=function(e){var t;return eBM(this),t=(eBG(),eBG(),++tyv,new Mr(2)),eRv(t,e),eRv(t,tye),t},eUe.Zl=function(e){return eBM(this),eBG(),eBG(),++tyv,new qa(3,e)};var tva=null,tvo=null;Y5(e1l,"RegEx/ParserForXMLSchema",1824),eTS(117,1,e1k,pJ),eUe.$l=function(e){throw p7(new go("Not supported."))},eUe._l=function(){return -1},eUe.am=function(e){return null},eUe.bm=function(){return null},eUe.cm=function(e){},eUe.dm=function(e){},eUe.em=function(){return 0},eUe.Ib=function(){return this.fm(0)},eUe.fm=function(e){return 11==this.e?".":""},eUe.e=0;var tvs,tvu,tvc,tvl,tvf,tvd,tvh,tvp,tvb,tvm,tvg,tvv,tvy,tvw,tv_,tvE,tvS,tvk,tvx,tvT,tvM,tvO,tvA,tvL,tvC,tvI,tvD,tvN,tvP,tvR,tvj,tvF,tvY,tvB,tvU,tvH,tv$,tvz,tvG,tvW,tvK,tvV,tvq,tvZ,tvX,tvJ,tvQ,tv1,tv0,tv2,tv3,tv4,tv5,tv6,tv9,tv8,tv7,tye,tyt,tyn,tyr,tyi,tya,tyo,tys,tyu,tyc,tyl,tyf,tyd,tyh,typ,tyb=null,tym=null,tyg=null,tyv=0,tyy=Y5(e1l,"RegEx/Token",117);eTS(136,117,{3:1,136:1,117:1},WZ),eUe.fm=function(e){var t,n,r;if(4==this.e){if(this==tv7)n=".";else if(this==tv8)n="\\d";else if(this==tyd)n="\\w";else if(this==tys)n="\\s";else{for(r=new vs,r.a+="[",t=0;t0&&(r.a+=","),this.b[t]===this.b[t+1]?xk(r,eN$(this.b[t])):(xk(r,eN$(this.b[t])),r.a+="-",xk(r,eN$(this.b[t+1])));r.a+="]",n=r.a}}else if(this==tyr)n="\\D";else if(this==tya)n="\\W";else if(this==tyi)n="\\S";else{for(r=new vs,r.a+="[^",t=0;t0&&(r.a+=","),this.b[t]===this.b[t+1]?xk(r,eN$(this.b[t])):(xk(r,eN$(this.b[t])),r.a+="-",xk(r,eN$(this.b[t+1])));r.a+="]",n=r.a}return n},eUe.a=!1,eUe.c=!1,Y5(e1l,"RegEx/RangeToken",136),eTS(584,1,{584:1},pX),eUe.a=0,Y5(e1l,"RegEx/RegexParser/ReferencePosition",584),eTS(583,1,{3:1,583:1},wu),eUe.Fb=function(e){var t;return!!(null!=e&&M4(e,583))&&(t=Pp(e,583),IE(this.b,t.b)&&this.a==t.a)},eUe.Hb=function(){return ebA(this.b+"/"+eAN(this.a))},eUe.Ib=function(){return this.c.fm(this.a)},eUe.a=0,Y5(e1l,"RegEx/RegularExpression",583),eTS(223,117,e1k,jb),eUe._l=function(){return this.a},eUe.fm=function(e){var t,n,r;switch(this.e){case 0:switch(this.a){case 124:case 42:case 43:case 63:case 40:case 41:case 46:case 91:case 123:case 92:r="\\"+CB(this.a&eHd);break;case 12:r="\\f";break;case 10:r="\\n";break;case 13:r="\\r";break;case 9:r="\\t";break;case 27:r="\\e";break;default:r=this.a>=eH3?"\\v"+Az(n="0"+(t=this.a>>>0).toString(16),n.length-6,n.length):""+CB(this.a&eHd)}break;case 8:r=this==tyt||this==tyn?""+CB(this.a&eHd):"\\"+CB(this.a&eHd);break;default:r=null}return r},eUe.a=0,Y5(e1l,"RegEx/Token/CharToken",223),eTS(309,117,e1k,qa),eUe.am=function(e){return this.a},eUe.cm=function(e){this.b=e},eUe.dm=function(e){this.c=e},eUe.em=function(){return 1},eUe.fm=function(e){var t;if(3==this.e){if(this.c<0&&this.b<0)t=this.a.fm(e)+"*";else if(this.c==this.b)t=this.a.fm(e)+"{"+this.c+"}";else if(this.c>=0&&this.b>=0)t=this.a.fm(e)+"{"+this.c+","+this.b+"}";else if(this.c>=0&&this.b<0)t=this.a.fm(e)+"{"+this.c+",}";else throw p7(new go("Token#toString(): CLOSURE "+this.c+eUd+this.b))}else if(this.c<0&&this.b<0)t=this.a.fm(e)+"*?";else if(this.c==this.b)t=this.a.fm(e)+"{"+this.c+"}?";else if(this.c>=0&&this.b>=0)t=this.a.fm(e)+"{"+this.c+","+this.b+"}?";else if(this.c>=0&&this.b<0)t=this.a.fm(e)+"{"+this.c+",}?";else throw p7(new go("Token#toString(): NONGREEDYCLOSURE "+this.c+eUd+this.b));return t},eUe.b=0,eUe.c=0,Y5(e1l,"RegEx/Token/ClosureToken",309),eTS(821,117,e1k,YD),eUe.am=function(e){return 0==e?this.a:this.b},eUe.em=function(){return 2},eUe.fm=function(e){var t;return 3==this.b.e&&this.b.am(0)==this.a?this.a.fm(e)+"+":9==this.b.e&&this.b.am(0)==this.a?this.a.fm(e)+"+?":this.a.fm(e)+""+this.b.fm(e)},Y5(e1l,"RegEx/Token/ConcatToken",821),eTS(1822,117,e1k,ee_),eUe.am=function(e){if(0==e)return this.d;if(1==e)return this.b;throw p7(new go("Internal Error: "+e))},eUe.em=function(){return this.b?2:1},eUe.fm=function(e){var t;return t=this.c>0?"(?("+this.c+")":8==this.a.e?"(?("+this.a+")":"(?"+this.a,this.b?t+=this.d+"|"+this.b+")":t+=this.d+")",t},eUe.c=0,Y5(e1l,"RegEx/Token/ConditionToken",1822),eTS(1823,117,e1k,Wq),eUe.am=function(e){return this.b},eUe.em=function(){return 1},eUe.fm=function(e){return"(?"+(0==this.a?"":eAN(this.a))+(0==this.c?"":eAN(this.c))+":"+this.b.fm(e)+")"},eUe.a=0,eUe.c=0,Y5(e1l,"RegEx/Token/ModifierToken",1823),eTS(822,117,e1k,BR),eUe.am=function(e){return this.a},eUe.em=function(){return 1},eUe.fm=function(e){var t;switch(t=null,this.e){case 6:t=0==this.b?"(?:"+this.a.fm(e)+")":"("+this.a.fm(e)+")";break;case 20:t="(?="+this.a.fm(e)+")";break;case 21:t="(?!"+this.a.fm(e)+")";break;case 22:t="(?<="+this.a.fm(e)+")";break;case 23:t="(?"+this.a.fm(e)+")"}return t},eUe.b=0,Y5(e1l,"RegEx/Token/ParenToken",822),eTS(521,117,{3:1,117:1,521:1},zc),eUe.bm=function(){return this.b},eUe.fm=function(e){return 12==this.e?"\\"+this.a:eTd(this.b)},eUe.a=0,Y5(e1l,"RegEx/Token/StringToken",521),eTS(465,117,e1k,Mr),eUe.$l=function(e){eRv(this,e)},eUe.am=function(e){return Pp(Bz(this.a,e),117)},eUe.em=function(){return this.a?this.a.a.c.length:0},eUe.fm=function(e){var t,n,r,i,a;if(1==this.e){if(2==this.a.a.c.length)t=Pp(Bz(this.a,0),117),i=3==(n=Pp(Bz(this.a,1),117)).e&&n.am(0)==t?t.fm(e)+"+":9==n.e&&n.am(0)==t?t.fm(e)+"+?":t.fm(e)+""+n.fm(e);else{for(r=0,a=new vs;r=this.c.b:this.a<=this.c.b},eUe.Sb=function(){return this.b>0},eUe.Tb=function(){return this.b},eUe.Vb=function(){return this.b-1},eUe.Qb=function(){throw p7(new gW(e1L))},eUe.a=0,eUe.b=0,Y5(e1M,"ExclusiveRange/RangeIterator",254);var tyw=Ui(eJX,"C"),ty_=Ui(eJ1,"I"),tyE=Ui(eUi,"Z"),tyS=Ui(eJ0,"J"),tyk=Ui(eJZ,"B"),tyx=Ui(eJJ,"D"),tyT=Ui(eJQ,"F"),tyM=Ui(eJ2,"S"),tyO=RL("org.eclipse.elk.core.labels","ILabelManager"),tyA=RL(eX_,"DiagnosticChain"),tyL=RL(eQx,"ResourceSet"),tyC=Y5(eX_,"InvocationTargetException",null),tyI=(vg(),q6),tyD=tyD=eyP;enI(bs),eiE("permProps",[[[e1C,e1I],[e1D,"gecko1_8"]],[[e1C,e1I],[e1D,"ie10"]],[[e1C,e1I],[e1D,"ie8"]],[[e1C,e1I],[e1D,"ie9"]],[[e1C,e1I],[e1D,"safari"]]]),tyD(null,"elk",null)},3379(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return t&&("object"==typeof t||"function"==typeof t)?t:e}function a(e,t){if("function"!=typeof t&&null!==t)throw TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var o=function(e){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};r(this,t);var a=Object.assign({},e),o=!1;try{o=!0}catch(s){}if(e.workerUrl){if(o){var u=n(84763);a.workerFactory=function(e){return new u(e)}}else console.warn("Web worker requested but 'web-worker' package not installed. \nConsider installing the package or pass your own 'workerFactory' to ELK's constructor.\n... Falling back to non-web worker version.")}if(!a.workerFactory){var c=n(55273).Worker;a.workerFactory=function(e){return new c(e)}}return i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,a))}return a(t,e),t}(n(4005).default);Object.defineProperty(e.exports,"__esModule",{value:!0}),e.exports=o,o.default=o},17187(e){"use strict";var t,n="object"==typeof Reflect?Reflect:null,r=n&&"function"==typeof n.apply?n.apply:function(e,t,n){return Function.prototype.apply.call(e,t,n)};function i(e){console&&console.warn&&console.warn(e)}t=n&&"function"==typeof n.ownKeys?n.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var a=Number.isNaN||function(e){return e!=e};function o(){o.init.call(this)}e.exports=o,e.exports.once=v,o.EventEmitter=o,o.prototype._events=void 0,o.prototype._eventsCount=0,o.prototype._maxListeners=void 0;var s=10;function u(e){if("function"!=typeof e)throw TypeError('The "listener" argument must be of type Function. Received type '+typeof e)}function c(e){return void 0===e._maxListeners?o.defaultMaxListeners:e._maxListeners}function l(e,t,n,r){if(u(n),void 0===(o=e._events)?(o=e._events=Object.create(null),e._eventsCount=0):(void 0!==o.newListener&&(e.emit("newListener",t,n.listener?n.listener:n),o=e._events),s=o[t]),void 0===s)s=o[t]=n,++e._eventsCount;else if("function"==typeof s?s=o[t]=r?[n,s]:[s,n]:r?s.unshift(n):s.push(n),(a=c(e))>0&&s.length>a&&!s.warned){s.warned=!0;var a,o,s,l=Error("Possible EventEmitter memory leak detected. "+s.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");l.name="MaxListenersExceededWarning",l.emitter=e,l.type=t,l.count=s.length,i(l)}return e}function f(){if(!this.fired)return(this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length)?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function d(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},i=f.bind(r);return i.listener=n,r.wrapFn=i,i}function h(e,t,n){var r=e._events;if(void 0===r)return[];var i=r[t];return void 0===i?[]:"function"==typeof i?n?[i.listener||i]:[i]:n?g(i):b(i,i.length)}function p(e){var t=this._events;if(void 0!==t){var n=t[e];if("function"==typeof n)return 1;if(void 0!==n)return n.length}return 0}function b(e,t){for(var n=Array(t),r=0;r0&&(o=t[0]),o instanceof Error)throw o;var o,s=Error("Unhandled error."+(o?" ("+o.message+")":""));throw s.context=o,s}var u=a[e];if(void 0===u)return!1;if("function"==typeof u)r(u,this,t);else for(var c=u.length,l=b(u,c),n=0;n=0;a--)if(n[a]===t||n[a].listener===t){o=n[a].listener,i=a;break}if(i<0)return this;0===i?n.shift():m(n,i),1===n.length&&(r[e]=n[0]),void 0!==r.removeListener&&this.emit("removeListener",e,o||t)}return this},o.prototype.off=o.prototype.removeListener,o.prototype.removeAllListeners=function(e){var t,n,r;if(void 0===(n=this._events))return this;if(void 0===n.removeListener)return 0===arguments.length?(this._events=Object.create(null),this._eventsCount=0):void 0!==n[e]&&(0==--this._eventsCount?this._events=Object.create(null):delete n[e]),this;if(0===arguments.length){var i,a=Object.keys(n);for(r=0;r=0;r--)this.removeListener(e,t[r]);return this},o.prototype.listeners=function(e){return h(this,e,!0)},o.prototype.rawListeners=function(e){return h(this,e,!1)},o.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):p.call(e,t)},o.prototype.listenerCount=p,o.prototype.eventNames=function(){return this._eventsCount>0?t(this._events):[]}},16839(e,t,n){var r=n(25323),i=n(31744),a=n(98361),o=n(4514);e.exports={graphlib:n(32478),read:r,readMany:i,write:a,version:o,type:"dot",buffer:!1}},11100(e,t,n){"use strict";var r=n(47755),i=n(32478).Graph;function a(e){var t="graph"!==e.type,n=!e.strict,a=[{node:{},edge:{}}],s=e.id,u=new i({directed:t,multigraph:n,compound:!0});return u.setGraph(null===s?{}:{id:s}),r.each(e.stmts,function(e){o(u,e,a)}),u}function o(e,t,n,r){switch(t.type){case"node":s(e,t,n,r);break;case"edge":u(e,t,n,r);break;case"subgraph":c(e,t,n,r);break;case"attr":l(e,t,n);break;case"inlineAttr":f(e,t,n,r)}}function s(e,t,n,i){var a=t.id,o=t.attrs;h(e,a,n,i),r.merge(e.node(a),o)}function u(e,t,n,i){var a,s,u=t.attrs;r.each(t.elems,function(t){switch(o(e,t,n,i),t.type){case"node":s=[t.id];break;case"subgraph":s=p(t)}r.each(a,function(t){r.each(s,function(i){var a;e.hasEdge(t,i)&&e.isMultigraph()&&(a=r.uniqueId("edge")),e.hasEdge(t,i,a)||e.setEdge(t,i,r.clone(r.last(n).edge),a),r.merge(e.edge(t,i,a),u)})}),a=s})}function c(e,t,n,i){var a=t.id;void 0===a&&(a=d(e)),n.push(r.clone(r.last(n))),h(e,a,n,i),r.each(t.stmts,function(t){o(e,t,n,a)}),e.children(a).length||e.removeNode(a),n.pop()}function l(e,t,n){r.merge(r.last(n)[t.attrType],t.attrs)}function f(e,t,n,i){r.merge(i?e.node(i):e.graph(),t.attrs)}function d(e){var t;do t=r.uniqueId("sg");while(e.hasNode(t))return t}function h(e,t,n,i){e.hasNode(t)||(e.setNode(t,r.clone(r.last(n).node)),e.setParent(t,i))}function p(e){var t,n={},i=[],a=i.push.bind(i);for(a(e);i.length;)switch((t=i.pop()).type){case"node":n[t.id]=!0;break;case"edge":r.each(t.elems,a);break;case"subgraph":r.each(t.stmts,a)}return r.keys(n)}e.exports=a},4644(e,t,n){e.exports=function(){function e(e,t){function n(){this.constructor=e}n.prototype=t.prototype,e.prototype=new n}function t(e,t,n,r,i,a){this.message=e,this.expected=t,this.found=n,this.offset=r,this.line=i,this.column=a,this.name="SyntaxError"}function r(e){var r,i,a=arguments.length>1?arguments[1]:{},o={},s={start:tf,graphStmt:td},u=tf,c=o,l=null,f="{",d={type:"literal",value:"{",description:'"{"'},h="}",p={type:"literal",value:"}",description:'"}"'},b=function(e,t,n,r){return{type:t,id:n,strict:null!==e,stmts:r}},m=";",g={type:"literal",value:";",description:'";"'},v=function(e,t){for(var n=[e],r=0;r",description:'"->"'},U=function(e,t){var n=[e];if(t)for(var r=0;rt&&(tr=0,ti={line:1,column:1,seenCR:!1}),n(ti,tr,t),tr=t),ti}function tc(e){!(ttta&&(ta=tt,to=[]),to.push(e))}function tl(n,r,i){function a(e){var t=1;for(e.sort(function(e,t){return e.descriptiont.description?1:0});t1?o.slice(0,-1).join(", ")+" or "+o[e.length-1]:o[0])+" but "+(i=t?'"'+n(t)+'"':"end of input")+" found."}var s=tu(i),u=itt?(s=e.charAt(tt),tt++):(s=o,0===ts&&tc(te)),s!==o?i=a=[a,s]:(tt=i,i=c)):(tt=i,i=c);i!==o;)r.push(i),i=tt,a=tt,ts++,e.substr(tt,2)===e8?(s=e8,tt+=2):(s=o,0===ts&&tc(e7)),ts--,s===o?a=F:(tt=a,a=c),a!==o?(e.length>tt?(s=e.charAt(tt),tt++):(s=o,0===ts&&tc(te)),s!==o?i=a=[a,s]:(tt=i,i=c)):(tt=i,i=c);r!==o?(e.substr(tt,2)===e8?(i=e8,tt+=2):(i=o,0===ts&&tc(e7)),i!==o?t=n=[n,r,i]:(tt=t,t=c)):(tt=t,t=c)}else tt=t,t=c}return ts--,t===o&&(n=o,0===ts&&tc(e0)),t}function tY(){var e;return(e=tj())===o&&(e=tF()),e}var tB=n(47755);if((i=u())!==o&&tt===e.length)return i;throw i!==o&&tt":"--",n=new f;e.isMultigraph()||n.write("strict "),n.writeLine((e.isDirected()?"digraph":"graph")+" {"),n.indent();var i=e.graph();return r.isObject(i)&&r.each(i,function(e,t){n.writeLine(l(t)+"="+l(e)+";")}),o(e,void 0,n),e.edges().forEach(function(r){u(e,r,t,n)}),n.unindent(),n.writeLine("}"),n.toString()}function o(e,t,n){var i=e.isCompound()?e.children(t):e.nodes();r.each(i,function(t){e.isCompound()&&e.children(t).length?(n.writeLine("subgraph "+l(t)+" {"),n.indent(),r.isObject(e.node(t))&&r.map(e.node(t),function(e,t){n.writeLine(l(t)+"="+l(e)+";")}),o(e,t,n),n.unindent(),n.writeLine("}")):s(e,t,n)})}function s(e,t,n){n.write(l(t)),c(e.node(t),n),n.writeLine()}function u(e,t,n,r){var i=t.v,a=t.w,o=e.edge(t);r.write(l(i)+" "+n+" "+l(a)),c(o,r),r.writeLine()}function c(e,t){if(r.isObject(e)){var n=r.map(e,function(e,t){return l(t)+"="+l(e)});n.length&&t.write(" ["+n.join(",")+"]")}}function l(e){return"number"==typeof e||e.toString().match(i)?e:'"'+e.toString().replace(/"/g,'\\"')+'"'}function f(){this._indent="",this._content="",this._shouldIndent=!0}f.prototype.INDENT=" ",f.prototype.indent=function(){this._indent+=this.INDENT},f.prototype.unindent=function(){this._indent=this._indent.slice(this.INDENT.length)},f.prototype.writeLine=function(e){this.write((e||"")+"\n"),this._shouldIndent=!0},f.prototype.write=function(e){this._shouldIndent&&(this._shouldIndent=!1,this._content+=this._indent),this._content+=e},f.prototype.toString=function(){return this._content}},28282(e,t,n){var r=n(82354);e.exports={Graph:r.Graph,json:n(28974),alg:n(12440),version:r.version}},2842(e,t,n){var r=n(89126);function i(e){var t,n={},i=[];function a(i){r.has(n,i)||(n[i]=!0,t.push(i),r.each(e.successors(i),a),r.each(e.predecessors(i),a))}return r.each(e.nodes(),function(e){t=[],a(e),t.length&&i.push(t)}),i}e.exports=i},53984(e,t,n){var r=n(89126);function i(e,t,n){r.isArray(t)||(t=[t]);var i=(e.isDirected()?e.successors:e.neighbors).bind(e),o=[],s={};return r.each(t,function(t){if(!e.hasNode(t))throw Error("Graph does not have node: "+t);a(e,t,"post"===n,s,i,o)}),o}function a(e,t,n,i,o,s){!r.has(i,t)&&(i[t]=!0,n||s.push(t),r.each(o(t),function(t){a(e,t,n,i,o,s)}),n&&s.push(t))}e.exports=i},84847(e,t,n){var r=n(63763),i=n(89126);function a(e,t,n){return i.transform(e.nodes(),function(i,a){i[a]=r(e,a,t,n)},{})}e.exports=a},63763(e,t,n){var r=n(89126),i=n(75639);e.exports=o;var a=r.constant(1);function o(e,t,n,r){return s(e,String(t),n||a,r||function(t){return e.outEdges(t)})}function s(e,t,n,r){var a,o,s={},u=new i,c=function(e){var t=e.v!==a?e.v:e.w,r=s[t],i=n(e),c=o.distance+i;if(i<0)throw Error("dijkstra does not allow negative edge weights. Bad edge: "+e+" Weight: "+i);c0&&(o=s[a=u.removeMin()]).distance!==Number.POSITIVE_INFINITY;)r(a).forEach(c);return s}},9096(e,t,n){var r=n(89126),i=n(5023);function a(e){return r.filter(i(e),function(t){return t.length>1||1===t.length&&e.hasEdge(t[0],t[0])})}e.exports=a},38924(e,t,n){var r=n(89126);e.exports=a;var i=r.constant(1);function a(e,t,n){return o(e,t||i,n||function(t){return e.outEdges(t)})}function o(e,t,n){var r={},i=e.nodes();return i.forEach(function(e){r[e]={},r[e][e]={distance:0},i.forEach(function(t){e!==t&&(r[e][t]={distance:Number.POSITIVE_INFINITY})}),n(e).forEach(function(n){var i=n.v===e?n.w:n.v,a=t(n);r[e][i]={distance:a,predecessor:e}})}),i.forEach(function(e){var t=r[e];i.forEach(function(n){var a=r[n];i.forEach(function(n){var r=a[e],i=t[n],o=a[n],s=r.distance+i.distance;s0;){if(n=u.removeMin(),r.has(s,n))o.setEdge(n,s[n]);else if(l)throw Error("Input graph is not connected: "+e);else l=!0;e.nodeEdges(n).forEach(c)}return o}e.exports=o},5023(e,t,n){var r=n(89126);function i(e){var t=0,n=[],i={},a=[];function o(s){var u=i[s]={onStack:!0,lowlink:t,index:t++};if(n.push(s),e.successors(s).forEach(function(e){r.has(i,e)?i[e].onStack&&(u.lowlink=Math.min(u.lowlink,i[e].index)):(o(e),u.lowlink=Math.min(u.lowlink,i[e].lowlink))}),u.lowlink===u.index){var c,l=[];do i[c=n.pop()].onStack=!1,l.push(c);while(s!==c)a.push(l)}}return e.nodes().forEach(function(e){r.has(i,e)||o(e)}),a}e.exports=i},2166(e,t,n){var r=n(89126);function i(e){var t={},n={},i=[];function o(s){if(r.has(n,s))throw new a;r.has(t,s)||(n[s]=!0,t[s]=!0,r.each(e.predecessors(s),o),delete n[s],i.push(s))}if(r.each(e.sinks(),o),r.size(t)!==e.nodeCount())throw new a;return i}function a(){}e.exports=i,i.CycleException=a,a.prototype=Error()},75639(e,t,n){var r=n(89126);function i(){this._arr=[],this._keyIndices={}}e.exports=i,i.prototype.size=function(){return this._arr.length},i.prototype.keys=function(){return this._arr.map(function(e){return e.key})},i.prototype.has=function(e){return r.has(this._keyIndices,e)},i.prototype.priority=function(e){var t=this._keyIndices[e];if(void 0!==t)return this._arr[t].priority},i.prototype.min=function(){if(0===this.size())throw Error("Queue underflow");return this._arr[0].key},i.prototype.add=function(e,t){var n=this._keyIndices;if(e=String(e),!r.has(n,e)){var i=this._arr,a=i.length;return n[e]=a,i.push({key:e,priority:t}),this._decrease(a),!0}return!1},i.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var e=this._arr.pop();return delete this._keyIndices[e.key],this._heapify(0),e.key},i.prototype.decrease=function(e,t){var n=this._keyIndices[e];if(t>this._arr[n].priority)throw Error("New priority is greater than current priority. Key: "+e+" Old: "+this._arr[n].priority+" New: "+t);this._arr[n].priority=t,this._decrease(n)},i.prototype._heapify=function(e){var t=this._arr,n=2*e,r=n+1,i=e;n>1].priorityu){var c=s;s=u,u=c}return s+o+u+o+(r.isUndefined(a)?i:a)}function f(e,t,n,r){var i=""+t,a=""+n;if(!e&&i>a){var o=i;i=a,a=o}var s={v:i,w:a};return r&&(s.name=r),s}function d(e,t){return l(e,t.v,t.w,t.name)}s.prototype._nodeCount=0,s.prototype._edgeCount=0,s.prototype.isDirected=function(){return this._isDirected},s.prototype.isMultigraph=function(){return this._isMultigraph},s.prototype.isCompound=function(){return this._isCompound},s.prototype.setGraph=function(e){return this._label=e,this},s.prototype.graph=function(){return this._label},s.prototype.setDefaultNodeLabel=function(e){return r.isFunction(e)||(e=r.constant(e)),this._defaultNodeLabelFn=e,this},s.prototype.nodeCount=function(){return this._nodeCount},s.prototype.nodes=function(){return r.keys(this._nodes)},s.prototype.sources=function(){var e=this;return r.filter(this.nodes(),function(t){return r.isEmpty(e._in[t])})},s.prototype.sinks=function(){var e=this;return r.filter(this.nodes(),function(t){return r.isEmpty(e._out[t])})},s.prototype.setNodes=function(e,t){var n=arguments,i=this;return r.each(e,function(e){n.length>1?i.setNode(e,t):i.setNode(e)}),this},s.prototype.setNode=function(e,t){return r.has(this._nodes,e)?(arguments.length>1&&(this._nodes[e]=t),this):(this._nodes[e]=arguments.length>1?t:this._defaultNodeLabelFn(e),this._isCompound&&(this._parent[e]=a,this._children[e]={},this._children[a][e]=!0),this._in[e]={},this._preds[e]={},this._out[e]={},this._sucs[e]={},++this._nodeCount,this)},s.prototype.node=function(e){return this._nodes[e]},s.prototype.hasNode=function(e){return r.has(this._nodes,e)},s.prototype.removeNode=function(e){var t=this;if(r.has(this._nodes,e)){var n=function(e){t.removeEdge(t._edgeObjs[e])};delete this._nodes[e],this._isCompound&&(this._removeFromParentsChildList(e),delete this._parent[e],r.each(this.children(e),function(e){t.setParent(e)}),delete this._children[e]),r.each(r.keys(this._in[e]),n),delete this._in[e],delete this._preds[e],r.each(r.keys(this._out[e]),n),delete this._out[e],delete this._sucs[e],--this._nodeCount}return this},s.prototype.setParent=function(e,t){if(!this._isCompound)throw Error("Cannot set parent in a non-compound graph");if(r.isUndefined(t))t=a;else{t+="";for(var n=t;!r.isUndefined(n);n=this.parent(n))if(n===e)throw Error("Setting "+t+" as parent of "+e+" would create a cycle");this.setNode(t)}return this.setNode(e),this._removeFromParentsChildList(e),this._parent[e]=t,this._children[t][e]=!0,this},s.prototype._removeFromParentsChildList=function(e){delete this._children[this._parent[e]][e]},s.prototype.parent=function(e){if(this._isCompound){var t=this._parent[e];if(t!==a)return t}},s.prototype.children=function(e){if(r.isUndefined(e)&&(e=a),this._isCompound){var t=this._children[e];if(t)return r.keys(t)}else if(e===a)return this.nodes();else if(this.hasNode(e))return[]},s.prototype.predecessors=function(e){var t=this._preds[e];if(t)return r.keys(t)},s.prototype.successors=function(e){var t=this._sucs[e];if(t)return r.keys(t)},s.prototype.neighbors=function(e){var t=this.predecessors(e);if(t)return r.union(t,this.successors(e))},s.prototype.isLeaf=function(e){var t;return 0===(t=this.isDirected()?this.successors(e):this.neighbors(e)).length},s.prototype.filterNodes=function(e){var t=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});t.setGraph(this.graph());var n=this;r.each(this._nodes,function(n,r){e(r)&&t.setNode(r,n)}),r.each(this._edgeObjs,function(e){t.hasNode(e.v)&&t.hasNode(e.w)&&t.setEdge(e,n.edge(e))});var i={};function a(e){var r=n.parent(e);return void 0===r||t.hasNode(r)?(i[e]=r,r):r in i?i[r]:a(r)}return this._isCompound&&r.each(t.nodes(),function(e){t.setParent(e,a(e))}),t},s.prototype.setDefaultEdgeLabel=function(e){return r.isFunction(e)||(e=r.constant(e)),this._defaultEdgeLabelFn=e,this},s.prototype.edgeCount=function(){return this._edgeCount},s.prototype.edges=function(){return r.values(this._edgeObjs)},s.prototype.setPath=function(e,t){var n=this,i=arguments;return r.reduce(e,function(e,r){return i.length>1?n.setEdge(e,r,t):n.setEdge(e,r),r}),this},s.prototype.setEdge=function(){var e,t,n,i,a=!1,o=arguments[0];"object"==typeof o&&null!==o&&"v"in o?(e=o.v,t=o.w,n=o.name,2===arguments.length&&(i=arguments[1],a=!0)):(e=o,t=arguments[1],n=arguments[3],arguments.length>2&&(i=arguments[2],a=!0)),e=""+e,t=""+t,r.isUndefined(n)||(n=""+n);var s=l(this._isDirected,e,t,n);if(r.has(this._edgeLabels,s))return a&&(this._edgeLabels[s]=i),this;if(!r.isUndefined(n)&&!this._isMultigraph)throw Error("Cannot set a named edge when isMultigraph = false");this.setNode(e),this.setNode(t),this._edgeLabels[s]=a?i:this._defaultEdgeLabelFn(e,t,n);var c=f(this._isDirected,e,t,n);return e=c.v,t=c.w,Object.freeze(c),this._edgeObjs[s]=c,u(this._preds[t],e),u(this._sucs[e],t),this._in[t][s]=c,this._out[e][s]=c,this._edgeCount++,this},s.prototype.edge=function(e,t,n){var r=1===arguments.length?d(this._isDirected,arguments[0]):l(this._isDirected,e,t,n);return this._edgeLabels[r]},s.prototype.hasEdge=function(e,t,n){var i=1===arguments.length?d(this._isDirected,arguments[0]):l(this._isDirected,e,t,n);return r.has(this._edgeLabels,i)},s.prototype.removeEdge=function(e,t,n){var r=1===arguments.length?d(this._isDirected,arguments[0]):l(this._isDirected,e,t,n),i=this._edgeObjs[r];return i&&(e=i.v,t=i.w,delete this._edgeLabels[r],delete this._edgeObjs[r],c(this._preds[t],e),c(this._sucs[e],t),delete this._in[t][r],delete this._out[e][r],this._edgeCount--),this},s.prototype.inEdges=function(e,t){var n=this._in[e];if(n){var i=r.values(n);return t?r.filter(i,function(e){return e.v===t}):i}},s.prototype.outEdges=function(e,t){var n=this._out[e];if(n){var i=r.values(n);return t?r.filter(i,function(e){return e.w===t}):i}},s.prototype.nodeEdges=function(e,t){var n=this.inEdges(e,t);if(n)return n.concat(this.outEdges(e,t))}},82354(e,t,n){e.exports={Graph:n(30771),version:n(49631)}},28974(e,t,n){var r=n(89126),i=n(30771);function a(e){var t={options:{directed:e.isDirected(),multigraph:e.isMultigraph(),compound:e.isCompound()},nodes:o(e),edges:s(e)};return r.isUndefined(e.graph())||(t.value=r.clone(e.graph())),t}function o(e){return r.map(e.nodes(),function(t){var n=e.node(t),i=e.parent(t),a={v:t};return r.isUndefined(n)||(a.value=n),r.isUndefined(i)||(a.parent=i),a})}function s(e){return r.map(e.edges(),function(t){var n=e.edge(t),i={v:t.v,w:t.w};return r.isUndefined(t.name)||(i.name=t.name),r.isUndefined(n)||(i.value=n),i})}function u(e){var t=new i(e.options).setGraph(e.value);return r.each(e.nodes,function(e){t.setNode(e.v,e.value),e.parent&&t.setParent(e.v,e.parent)}),r.each(e.edges,function(e){t.setEdge({v:e.v,w:e.w,name:e.name},e.value)}),t}e.exports={write:a,read:u}},89126(e,t,n){var r;try{r={clone:n(66678),constant:n(75703),each:n(66073),filter:n(63105),has:n(18721),isArray:n(1469),isEmpty:n(41609),isFunction:n(23560),isUndefined:n(52353),keys:n(3674),map:n(35161),reduce:n(54061),size:n(84238),transform:n(68718),union:n(93386),values:n(52628)}}catch(i){}r||(r=window._),e.exports=r},49631(e){e.exports="2.1.8"},78892(e){"use strict";e.exports=n;var t=/[#.]/g;function n(e,n){for(var r,i,a,o=e||"",s=n||"div",u={},c=0;cC,q_:()=>F,ob:()=>y,PP:()=>B,Ep:()=>v,Hp:()=>w});var r=n(87462);function i(e){return"/"===e.charAt(0)}function a(e,t){for(var n=t,r=n+1,i=e.length;r=0;d--){var h=o[d];"."===h?a(o,d):".."===h?(a(o,d),f++):f&&(a(o,d),f--)}if(!c)for(;f--;f)o.unshift("..");!c||""===o[0]||o[0]&&i(o[0])||o.unshift("");var p=o.join("/");return n&&"/"!==p.substr(-1)&&(p+="/"),p}let s=o;function u(e){return e.valueOf?e.valueOf():Object.prototype.valueOf.call(e)}function c(e,t){if(e===t)return!0;if(null==e||null==t)return!1;if(Array.isArray(e))return Array.isArray(t)&&e.length===t.length&&e.every(function(e,n){return c(e,t[n])});if("object"==typeof e||"object"==typeof t){var n=u(e),r=u(t);return n!==e||r!==t?c(n,r):Object.keys(Object.assign({},e,t)).every(function(n){return c(e[n],t[n])})}return!1}let l=c;var f=n(2177);function d(e){return"/"===e.charAt(0)?e:"/"+e}function h(e){return"/"===e.charAt(0)?e.substr(1):e}function p(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}function b(e,t){return p(e,t)?e.substr(t.length):e}function m(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function g(e){var t=e||"/",n="",r="",i=t.indexOf("#");-1!==i&&(r=t.substr(i),t=t.substr(0,i));var a=t.indexOf("?");return -1!==a&&(n=t.substr(a),t=t.substr(0,a)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}}function v(e){var t=e.pathname,n=e.search,r=e.hash,i=t||"/";return n&&"?"!==n&&(i+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(i+="#"===r.charAt(0)?r:"#"+r),i}function y(e,t,n,i){var a;"string"==typeof e?(a=g(e)).state=t:(void 0===(a=(0,r.Z)({},e)).pathname&&(a.pathname=""),a.search?"?"!==a.search.charAt(0)&&(a.search="?"+a.search):a.search="",a.hash?"#"!==a.hash.charAt(0)&&(a.hash="#"+a.hash):a.hash="",void 0!==t&&void 0===a.state&&(a.state=t));try{a.pathname=decodeURI(a.pathname)}catch(o){if(o instanceof URIError)throw URIError('Pathname "'+a.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.');throw o}return n&&(a.key=n),i?a.pathname?"/"!==a.pathname.charAt(0)&&(a.pathname=s(a.pathname,i.pathname)):a.pathname=i.pathname:a.pathname||(a.pathname="/"),a}function w(e,t){return e.pathname===t.pathname&&e.search===t.search&&e.hash===t.hash&&e.key===t.key&&l(e.state,t.state)}function _(){var e=null;function t(t){return e=t,function(){e===t&&(e=null)}}function n(t,n,r,i){if(null!=e){var a="function"==typeof e?e(t,n):e;"string"==typeof a?"function"==typeof r?r(a,i):i(!0):i(!1!==a)}else i(!0)}var r=[];function i(e){var t=!0;function n(){t&&e.apply(void 0,arguments)}return r.push(n),function(){t=!1,r=r.filter(function(e){return e!==n})}}function a(){for(var e=arguments.length,t=Array(e),n=0;nn?a.splice(n,a.length-n,i):a.push(i),f({action:r,location:i,index:n,entries:a})}})}function g(e,t){var r="REPLACE",i=y(e,t,d(),M.location);l.confirmTransitionTo(i,r,n,function(e){e&&(M.entries[M.index]=i,f({action:r,location:i}))})}function w(e){var t=Y(M.index+e,0,M.entries.length-1),r="POP",i=M.entries[t];l.confirmTransitionTo(i,r,n,function(e){e?f({action:r,location:i,index:t}):f()})}function E(){w(-1)}function S(){w(1)}function k(e){var t=M.index+e;return t>=0&&tu});var r=/[A-Z]/g,i=/^ms-/,a={};function o(e){return"-"+e.toLowerCase()}function s(e){if(a.hasOwnProperty(e))return a[e];var t=e.replace(r,o);return a[e]=i.test(t)?"-"+t:t}let u=s},80645(e,t){/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ t.read=function(e,t,n,r,i){var a,o,s=8*i-r-1,u=(1<>1,l=-7,f=n?i-1:0,d=n?-1:1,h=e[t+f];for(f+=d,a=h&(1<<-l)-1,h>>=-l,l+=s;l>0;a=256*a+e[t+f],f+=d,l-=8);for(o=a&(1<<-l)-1,a>>=-l,l+=r;l>0;o=256*o+e[t+f],f+=d,l-=8);if(0===a)a=1-c;else{if(a===u)return o?NaN:(h?-1:1)*(1/0);o+=Math.pow(2,r),a-=c}return(h?-1:1)*o*Math.pow(2,a-r)},t.write=function(e,t,n,r,i,a){var o,s,u,c=8*a-i-1,l=(1<>1,d=23===i?5960464477539062e-23:0,h=r?0:a-1,p=r?1:-1,b=t<0||0===t&&1/t<0?1:0;for(isNaN(t=Math.abs(t))||t===1/0?(s=isNaN(t)?1:0,o=l):(o=Math.floor(Math.log(t)/Math.LN2),t*(u=Math.pow(2,-o))<1&&(o--,u*=2),o+f>=1?t+=d/u:t+=d*Math.pow(2,1-f),t*u>=2&&(o++,u/=2),o+f>=l?(s=0,o=l):o+f>=1?(s=(t*u-1)*Math.pow(2,i),o+=f):(s=t*Math.pow(2,f-1)*Math.pow(2,i),o=0));i>=8;e[n+h]=255&s,h+=p,s/=256,i-=8);for(o=o<0;e[n+h]=255&o,h+=p,o/=256,c-=8);e[n+h-p]|=128*b}},35717(e){"function"==typeof Object.create?e.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function(e,t){if(t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}}},46260(e){"use strict";function t(e){var t="string"==typeof e?e.charCodeAt(0):e;return t>=97&&t<=122||t>=65&&t<=90}e.exports=t},7961(e,t,n){"use strict";var r=n(46260),i=n(46195);function a(e){return r(e)||i(e)}e.exports=a},46195(e){"use strict";function t(e){var t="string"==typeof e?e.charCodeAt(0):e;return t>=48&&t<=57}e.exports=t},79480(e){"use strict";function t(e){var t="string"==typeof e?e.charCodeAt(0):e;return t>=97&&t<=102||t>=65&&t<=70||t>=48&&t<=57}e.exports=t},33827(e,t,n){"use strict";n.r(t),n.d(t,{default:()=>a,isBrowser:()=>i});var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=("undefined"==typeof window?"undefined":r(window))==="object"&&("undefined"==typeof document?"undefined":r(document))==="object"&&9===document.nodeType;let a=i},5826(e){e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},47798(e){"use strict";/*! + * isobject + * + * Copyright (c) 2014-2017, Jon Schlinkert. + * Released under the MIT License. + */ e.exports=function(e){return null!=e&&"object"==typeof e&&!1===Array.isArray(e)}},80204(e,t,n){e.exports=self.fetch||(self.fetch=n(25869).default||n(25869))},5690(e,t,n){e.exports=n(67946)},8126(e,t,n){"use strict";n.d(t,{Z:()=>tl});var r,i="en",a={},o={};function s(){return i}function u(e){i=e}function c(e){return a[e]}function l(e){if(!e)throw Error("No locale data passed");a[e.locale]=e,o[e.locale.toLowerCase()]=e.locale}function f(e){return a[e]?e:o[e.toLowerCase()]?o[e.toLowerCase()]:void 0}function d(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.localeMatcher||"lookup";switch(n){case"lookup":case"best fit":return h(e);default:throw RangeError('Invalid "localeMatcher" option: '.concat(n))}}function h(e){var t=f(e);if(t)return t;for(var n=e.split("-");e.length>1;){n.pop();var r=f(e=n.join("-"));if(r)return r}}var p={af:function(e){return 1==e?"one":"other"},am:function(e){return e>=0&&e<=1?"one":"other"},ar:function(e){var t=String(e).split("."),n=Number(t[0])==e&&t[0].slice(-2);return 0==e?"zero":1==e?"one":2==e?"two":n>=3&&n<=10?"few":n>=11&&n<=99?"many":"other"},ast:function(e){var t=!String(e).split(".")[1];return 1==e&&t?"one":"other"},be:function(e){var t=String(e).split("."),n=Number(t[0])==e,r=n&&t[0].slice(-1),i=n&&t[0].slice(-2);return 1==r&&11!=i?"one":r>=2&&r<=4&&(i<12||i>14)?"few":n&&0==r||r>=5&&r<=9||i>=11&&i<=14?"many":"other"},br:function(e){var t=String(e).split("."),n=Number(t[0])==e,r=n&&t[0].slice(-1),i=n&&t[0].slice(-2),a=n&&t[0].slice(-6);return 1==r&&11!=i&&71!=i&&91!=i?"one":2==r&&12!=i&&72!=i&&92!=i?"two":(3==r||4==r||9==r)&&(i<10||i>19)&&(i<70||i>79)&&(i<90||i>99)?"few":0!=e&&n&&0==a?"many":"other"},bs:function(e){var t=String(e).split("."),n=t[0],r=t[1]||"",i=!t[1],a=n.slice(-1),o=n.slice(-2),s=r.slice(-1),u=r.slice(-2);return i&&1==a&&11!=o||1==s&&11!=u?"one":i&&a>=2&&a<=4&&(o<12||o>14)||s>=2&&s<=4&&(u<12||u>14)?"few":"other"},cs:function(e){var t=String(e).split("."),n=t[0],r=!t[1];return 1==e&&r?"one":n>=2&&n<=4&&r?"few":r?"other":"many"},cy:function(e){return 0==e?"zero":1==e?"one":2==e?"two":3==e?"few":6==e?"many":"other"},da:function(e){var t=String(e).split("."),n=t[0],r=Number(t[0])==e;return 1!=e&&(r||0!=n&&1!=n)?"other":"one"},dsb:function(e){var t=String(e).split("."),n=t[0],r=t[1]||"",i=!t[1],a=n.slice(-2),o=r.slice(-2);return i&&1==a||1==o?"one":i&&2==a||2==o?"two":i&&(3==a||4==a)||3==o||4==o?"few":"other"},dz:function(e){return"other"},fil:function(e){var t=String(e).split("."),n=t[0],r=t[1]||"",i=!t[1],a=n.slice(-1),o=r.slice(-1);return i&&(1==n||2==n||3==n)||i&&4!=a&&6!=a&&9!=a||!i&&4!=o&&6!=o&&9!=o?"one":"other"},fr:function(e){return e>=0&&e<2?"one":"other"},ga:function(e){var t=Number(String(e).split(".")[0])==e;return 1==e?"one":2==e?"two":t&&e>=3&&e<=6?"few":t&&e>=7&&e<=10?"many":"other"},gd:function(e){var t=Number(String(e).split(".")[0])==e;return 1==e||11==e?"one":2==e||12==e?"two":t&&e>=3&&e<=10||t&&e>=13&&e<=19?"few":"other"},he:function(e){var t=String(e).split("."),n=t[0],r=!t[1],i=Number(t[0])==e,a=i&&t[0].slice(-1);return 1==e&&r?"one":2==n&&r?"two":r&&(e<0||e>10)&&i&&0==a?"many":"other"},is:function(e){var t=String(e).split("."),n=t[0],r=Number(t[0])==e,i=n.slice(-1),a=n.slice(-2);return r&&1==i&&11!=a||!r?"one":"other"},ksh:function(e){return 0==e?"zero":1==e?"one":"other"},lt:function(e){var t=String(e).split("."),n=t[1]||"",r=Number(t[0])==e,i=r&&t[0].slice(-1),a=r&&t[0].slice(-2);return 1==i&&(a<11||a>19)?"one":i>=2&&i<=9&&(a<11||a>19)?"few":0!=n?"many":"other"},lv:function(e){var t=String(e).split("."),n=t[1]||"",r=n.length,i=Number(t[0])==e,a=i&&t[0].slice(-1),o=i&&t[0].slice(-2),s=n.slice(-2),u=n.slice(-1);return i&&0==a||o>=11&&o<=19||2==r&&s>=11&&s<=19?"zero":1==a&&11!=o||2==r&&1==u&&11!=s||2!=r&&1==u?"one":"other"},mk:function(e){var t=String(e).split("."),n=t[0],r=t[1]||"",i=!t[1],a=n.slice(-1),o=n.slice(-2),s=r.slice(-1),u=r.slice(-2);return i&&1==a&&11!=o||1==s&&11!=u?"one":"other"},mt:function(e){var t=String(e).split("."),n=Number(t[0])==e&&t[0].slice(-2);return 1==e?"one":0==e||n>=2&&n<=10?"few":n>=11&&n<=19?"many":"other"},pa:function(e){return 0==e||1==e?"one":"other"},pl:function(e){var t=String(e).split("."),n=t[0],r=!t[1],i=n.slice(-1),a=n.slice(-2);return 1==e&&r?"one":r&&i>=2&&i<=4&&(a<12||a>14)?"few":r&&1!=n&&(0==i||1==i)||r&&i>=5&&i<=9||r&&a>=12&&a<=14?"many":"other"},pt:function(e){var t=String(e).split(".")[0];return 0==t||1==t?"one":"other"},ro:function(e){var t=String(e).split("."),n=!t[1],r=Number(t[0])==e&&t[0].slice(-2);return 1==e&&n?"one":!n||0==e||1!=e&&r>=1&&r<=19?"few":"other"},ru:function(e){var t=String(e).split("."),n=t[0],r=!t[1],i=n.slice(-1),a=n.slice(-2);return r&&1==i&&11!=a?"one":r&&i>=2&&i<=4&&(a<12||a>14)?"few":r&&0==i||r&&i>=5&&i<=9||r&&a>=11&&a<=14?"many":"other"},se:function(e){return 1==e?"one":2==e?"two":"other"},si:function(e){var t=String(e).split("."),n=t[0],r=t[1]||"";return 0==e||1==e||0==n&&1==r?"one":"other"},sl:function(e){var t=String(e).split("."),n=t[0],r=!t[1],i=n.slice(-2);return r&&1==i?"one":r&&2==i?"two":r&&(3==i||4==i)||!r?"few":"other"}};p.as=p.am,p.az=p.af,p.bg=p.af,p.bn=p.am,p.ca=p.ast,p.ce=p.af,p.chr=p.af,p.de=p.ast,p.ee=p.af,p.el=p.af,p.en=p.ast,p.es=p.af,p.et=p.ast,p.eu=p.af,p.fa=p.am,p.fi=p.ast,p.fo=p.af,p.fur=p.af,p.fy=p.ast,p.gl=p.ast,p.gu=p.am,p.hi=p.am,p.hr=p.bs,p.hsb=p.dsb,p.hu=p.af,p.hy=p.fr,p.ia=p.ast,p.id=p.dz,p.it=p.ast,p.ja=p.dz,p.jgo=p.af,p.jv=p.dz,p.ka=p.af,p.kea=p.dz,p.kk=p.af,p.kl=p.af,p.km=p.dz,p.kn=p.am,p.ko=p.dz,p.ku=p.af,p.ky=p.af,p.lb=p.af,p.lkt=p.dz,p.lo=p.dz,p.ml=p.af,p.mn=p.af,p.mr=p.am,p.ms=p.dz,p.my=p.dz,p.nb=p.af,p.ne=p.af,p.nl=p.ast,p.nn=p.af,p.or=p.af,p.ps=p.af,p["pt-PT"]=p.ast,p.sah=p.dz,p.sd=p.af,p.sk=p.cs,p.so=p.af,p.sq=p.af,p.sr=p.bs,p.sv=p.ast,p.sw=p.ast,p.ta=p.af,p.te=p.af,p.th=p.dz,p.ti=p.pa,p.tk=p.af,p.to=p.dz,p.tr=p.af,p.ug=p.af,p.uk=p.ru,p.ur=p.ast,p.uz=p.af,p.vi=p.dz,p.wae=p.af,p.yi=p.ast,p.yue=p.dz,p.zh=p.dz,p.zu=p.am;let b=p;function m(e){return"pt-PT"===e?e:v(e)}var g=/^([a-z0-9]+)/i;function v(e){var t=e.match(g);if(!t)throw TypeError("Invalid locale: ".concat(e));return t[1]}function y(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function w(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:[],n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};A(this,e),I(this,"numeric","always"),I(this,"style","long"),I(this,"localeMatcher","lookup");var r=n.numeric,i=n.style,a=n.localeMatcher;if(void 0!==r){if(0>N.indexOf(r))throw RangeError('Invalid "numeric" option: '.concat(r));this.numeric=r}if(void 0!==i){if(0>P.indexOf(i))throw RangeError('Invalid "style" option: '.concat(i));this.style=i}if(void 0!==a){if(0>R.indexOf(a))throw RangeError('Invalid "localeMatcher" option: '.concat(a));this.localeMatcher=a}if("string"==typeof t&&(t=[t]),t.push(s()),this.locale=e.supportedLocalesOf(t,{localeMatcher:this.localeMatcher})[0],!this.locale)throw Error("No supported locale was found");E.supportedLocalesOf(this.locale).length>0?this.pluralRules=new E(this.locale):console.warn('"'.concat(this.locale,'" locale is not supported')),"undefined"!=typeof Intl&&Intl.NumberFormat?(this.numberFormat=new Intl.NumberFormat(this.locale),this.numberingSystem=this.numberFormat.resolvedOptions().numberingSystem):this.numberingSystem="latn",this.locale=d(this.locale,{localeMatcher:this.localeMatcher})}return C(e,[{key:"format",value:function(){var e=z(arguments),t=x(e,2),n=t[0],r=t[1];return this.getRule(n,r).replace("{0}",this.formatNumber(Math.abs(n)))}},{key:"formatToParts",value:function(){var e=z(arguments),t=x(e,2),n=t[0],r=t[1],i=this.getRule(n,r),a=i.indexOf("{0}");if(a<0)return[{type:"literal",value:i}];var o=[];return a>0&&o.push({type:"literal",value:i.slice(0,a)}),o=o.concat(this.formatNumberToParts(Math.abs(n)).map(function(e){return k({},e,{unit:r})})),a+31&&void 0!==arguments[1]?arguments[1]:{};if("string"==typeof e)e=[e];else if(!Array.isArray(e))throw TypeError('Invalid "locales" argument');return e.filter(function(e){return d(e,t)})},j.addLocale=l,j.setDefaultLocale=u,j.getDefaultLocale=s,j.PluralRules=E;var F='Invalid "unit" argument';function Y(e){if("symbol"===S(e))throw TypeError(F);if("string"!=typeof e||("s"===e[e.length-1]&&(e=e.slice(0,e.length-1)),0>D.indexOf(e)))throw RangeError("".concat(F,": ").concat(e));return e}var B='Invalid "number" argument';function U(e){if(e=Number(e),Number.isFinite&&!Number.isFinite(e))throw RangeError("".concat(B,": ").concat(e));return e}function H(e){return 1/e==-1/0}function $(e){return e<0||0===e&&H(e)}function z(e){if(e.length<2)throw TypeError('"unit" argument is required');return[U(e[0]),Y(e[1])]}function G(e){return(G="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function W(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function K(e,t){for(var n=0;n=n.length)break;a=n[i++]}else{if((i=n.next()).done)break;a=i.value}var a,o=a;if(t(o))return o;for(var s=o.split("-");s.length>1;)if(s.pop(),t(o=s.join("-")))return o}throw Error("No locale data has been registered for any of the locales: ".concat(e.join(", ")))}function Q(){return("undefined"==typeof Intl?"undefined":X(Intl))==="object"&&"function"==typeof Intl.DateTimeFormat}var ee=60,et=60*ee,en=24*et,er=7*en,ei=30.44*en,ea=365.2425*en;function eo(e){switch(e){case"second":return 1;case"minute":return ee;case"hour":return et;case"day":return en;case"week":return er;case"month":return ei;case"year":return ea}}function es(e){return void 0!==e.factor?e.factor:eo(e.unit||e.formatAs)||1}function eu(e){return"floor"===e?Math.floor:(0,Math.round)}function ec(e){return"floor"===e?1:.5}function el(e){return(el="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function ef(e,t){var n,r=t.prevStep,i=t.timestamp,a=t.now,o=t.future,s=t.round;return r&&(r.id||r.unit)&&(n=e["threshold_for_".concat(r.id||r.unit)]),void 0===n&&void 0!==e.threshold&&"function"==typeof(n=e.threshold)&&(n=n(a,o)),void 0===n&&(n=e.minTime),"object"===el(n)&&(n=r&&r.id&&void 0!==n[r.id]?n[r.id]:n.default),"function"==typeof n&&(n=n(i,{future:o,getMinTimeForUnit:function(e,t){return ed(e,t||r&&r.formatAs,{round:s})}})),void 0===n&&e.test&&(n=e.test(i,{now:a,future:o})?0:9007199254740991),void 0===n&&(r?e.formatAs&&r.formatAs&&(n=ed(e.formatAs,r.formatAs,{round:s})):n=0),void 0===n&&console.warn("[javascript-time-ago] A step should specify `minTime`:\n"+JSON.stringify(e,null,2)),n}function ed(e,t,n){var r,i=n.round,a=eo(e);if(r="now"===t?eo(e):eo(t),void 0!==a&&void 0!==r)return a-r*(1-ec(i))}function eh(e){for(var t=1;t0?e[o-1]:s}}}function eg(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,i=ef(e[r],eh({prevStep:e[r-1],timestamp:n.now-1e3*t},n));return void 0===i||Math.abs(t)=0})}function ey(e,t,n){var r=n.now,i=n.round;if(eo(e)){var a=1e3*eo(e),o=t>r,s=Math.abs(t-r),u=eu(i)(s/a)*a;return o?u>0?s-u+e_(i,a):s-u+1:-(s-u)+ew(i,a)}}function ew(e,t){return ec(e)*t}function e_(e,t){return(1-ec(e))*t+1}var eE=31536e9;function eS(e,t,n){var r,i=n.prevStep,a=n.nextStep,o=n.now,s=n.future,u=n.round,c=e.getTime?e.getTime():e,l=function(e){return ey(e,c,{now:o,round:u})},f=ex(s?t:a,c,{future:s,now:o,round:u,prevStep:s?i:t});if(void 0!==f){if(t&&(t.getTimeToNextUpdate&&(r=t.getTimeToNextUpdate(c,{getTimeToNextUpdateForUnit:l,getRoundFunction:eu,now:o,future:s,round:u})),void 0===r)){var d=t.unit||t.formatAs;d&&(r=l(d))}return void 0===r?f:Math.min(r,f)}}function ek(e,t,n){var r,i=n.now,a=n.future,o=ef(e,{timestamp:t,now:i,future:a,round:n.round,prevStep:n.prevStep});return void 0===o?void 0:a?t-1e3*o+1:0===o&&t===i?eE:t+1e3*o}function ex(e,t,n){var r=n.now,i=n.future,a=n.round,o=n.prevStep;if(e){var s=ek(e,t,{now:r,future:i,round:a,prevStep:o});if(void 0===s)return;return s-r}return i?t-r+1:eE}var eT={};function eM(e){return eT[e]}function eO(e){if(!e)throw Error("[javascript-time-ago] No locale data passed.");eT[e.locale]=e}let eA=[{formatAs:"now"},{formatAs:"second"},{formatAs:"minute"},{formatAs:"hour"},{formatAs:"day"},{formatAs:"week"},{formatAs:"month"},{formatAs:"year"}],eL={steps:eA,labels:"long"};function eC(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:[],n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=n.polyfill;ts(this,e),"string"==typeof t&&(t=[t]),this.locale=J(t.concat(e.getDefaultLocale()),eM),"undefined"!=typeof Intl&&Intl.NumberFormat&&(this.numberFormat=new Intl.NumberFormat(this.locale)),!1===r?(this.IntlRelativeTimeFormat=Intl.RelativeTimeFormat,this.IntlPluralRules=Intl.PluralRules):(this.IntlRelativeTimeFormat=j,this.IntlPluralRules=j.PluralRules),this.relativeTimeFormatCache=new Z,this.pluralRulesCache=new Z}return tc(e,[{key:"format",value:function(e,t,n){n||(t&&!tv(t)?(n=t,t=void 0):n={}),t||(t=eD),"string"==typeof t&&(t=tt(t));var r,i=td(e),a=this.getLabels(t.flavour||t.labels),o=a.labels,s=a.labelsType;void 0!==t.now&&(r=t.now),void 0===r&&void 0!==n.now&&(r=n.now),void 0===r&&(r=Date.now());var u=(r-i)/1e3,c=n.future||u<0,l=tb(o,eM(this.locale).now,eM(this.locale).long,c);if(t.custom){var f=t.custom({now:r,date:new Date(i),time:i,elapsed:u,locale:this.locale});if(void 0!==f)return f}var d=tp(t.units,o,l),h=n.round||t.round,p=eb(t.gradation||t.steps||eD.steps,u,{now:r,units:d,round:h,future:c,getNextStep:!0}),b=tr(p,3),m=b[0],g=b[1],v=b[2],y=this.formatDateForStep(i,g,u,{labels:o,labelsType:s,nowLabel:l,now:r,future:c,round:h})||"";if(n.getTimeToNextUpdate){var w=eS(i,g,{nextStep:v,prevStep:m,now:r,future:c,round:h});return[y,w]}return y}},{key:"formatDateForStep",value:function(e,t,n,r){var i=this,a=r.labels,o=r.labelsType,s=r.nowLabel,u=r.now,c=r.future,l=r.round;if(t){if(t.format)return t.format(e,this.locale,{formatAs:function(e,t){return i.formatValue(t,e,{labels:a,future:c})},now:u,future:c});var f=t.unit||t.formatAs;if(!f)throw Error("[javascript-time-ago] Each step must define either `formatAs` or `format()`. Step: ".concat(JSON.stringify(t)));if("now"===f)return s;var d=Math.abs(n)/es(t);t.granularity&&(d=eu(l)(d/t.granularity)*t.granularity);var h=-1*Math.sign(n)*eu(l)(d);switch(0===h&&(h=0),o){case"long":case"short":case"narrow":return this.getFormatter(o).format(h,f);default:return this.formatValue(h,f,{labels:a,future:c})}}}},{key:"formatValue",value:function(e,t,n){var r=n.labels,i=n.future;return this.getFormattingRule(r,t,e,{future:i}).replace("{0}",this.formatNumber(Math.abs(e)))}},{key:"getFormattingRule",value:function(e,t,n,r){var i=r.future;if(this.locale,"string"==typeof(e=e[t]))return e;var a=e[0===n?i?"future":"past":n<0?"past":"future"]||e;return"string"==typeof a?a:a[this.getPluralRules().select(Math.abs(n))]||a.other}},{key:"formatNumber",value:function(e){return this.numberFormat?this.numberFormat.format(e):String(e)}},{key:"getFormatter",value:function(e){return this.relativeTimeFormatCache.get(this.locale,e)||this.relativeTimeFormatCache.put(this.locale,e,new this.IntlRelativeTimeFormat(this.locale,{style:e}))}},{key:"getPluralRules",value:function(){return this.pluralRulesCache.get(this.locale)||this.pluralRulesCache.put(this.locale,new this.IntlPluralRules(this.locale))}},{key:"getLabels",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];"string"==typeof e&&(e=[e]),e=(e=e.map(function(e){switch(e){case"tiny":case"mini-time":return"mini";default:return e}})).concat("long");for(var t=eM(this.locale),n=e,r=Array.isArray(n),i=0,n=r?n:n[Symbol.iterator]();;){if(r){if(i>=n.length)break;a=n[i++]}else{if((i=n.next()).done)break;a=i.value}var a,o=a;if(t[o])return{labelsType:o,labels:t[o]}}}}]),e}(),tf="en";function td(e){if(e.constructor===Date||th(e))return e.getTime();if("number"==typeof e)return e;throw Error("Unsupported relative time formatter input: ".concat(tn(e),", ").concat(e))}function th(e){return"object"===tn(e)&&"function"==typeof e.getTime}function tp(e,t,n){var r=Object.keys(t);return n&&r.push("now"),e&&(r=e.filter(function(e){return"now"===e||r.indexOf(e)>=0})),r}function tb(e,t,n,r){var i=e.now||t&&t.now;return i?"string"==typeof i?i:r?i.future:i.past:n&&n.second&&n.second.current?n.second.current:void 0}tl.getDefaultLocale=function(){return tf},tl.setDefaultLocale=function(e){return tf=e},tl.addDefaultLocale=function(e){if(r)throw Error("[javascript-time-ago] `TimeAgo.addDefaultLocale()` can only be called once. To add other locales, use `TimeAgo.addLocale()`.");r=!0,tl.setDefaultLocale(e.locale),tl.addLocale(e)},tl.addLocale=function(e){eO(e),j.addLocale(e)},tl.locale=tl.addLocale,tl.addLabels=function(e,t,n){var r=eM(e);r||(eO({locale:e}),r=eM(e)),r[t]=n};var tm={}.constructor;function tg(e){return void 0!==tn(e)&&null!==e&&e.constructor===tm}function tv(e){return"string"==typeof e||ty(e)}function ty(e){return tg(e)&&(Array.isArray(e.steps)||Array.isArray(e.gradation)||Array.isArray(e.flavour)||"string"==typeof e.flavour||Array.isArray(e.labels)||"string"==typeof e.labels||Array.isArray(e.units)||"function"==typeof e.custom)}},41800(e,t,n){e.exports=function(){"use strict";var e={121:function(e,t,r){r.r(t),r.d(t,{default:function(){return E}}),n(41539),n(21249),n(54747),n(15306),n(74916),n(47042),n(82526),n(41817),n(32165),n(78783),n(66992),n(33948),n(81486);var i=n(68929),a=r.n(i),o=n(1469),s=r.n(o),u=n(45220),c=r.n(u),l=n(3674),f=r.n(l),d=n(82492),h=r.n(d);function p(e){return(p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function b(e){return s()(e)?e:[e]}function m(e){if(null===e||"object"!==p(e)||(t=e,"[object Date]"===Object.prototype.toString.call(t)))return e;if(s()(e))return e.map(m);var t,n={};return f()(e).forEach(function(t){n[a()(t)]=m(e[t])}),n}function g(e,t){var n=t.camelizeKeys,r=t.camelizeTypeValues,i={};return f()(e).forEach(function(t){var o=e[t],u=n?a()(t):t;i[u]={},void 0!==o.data&&(s()(o.data)?i[u].data=o.data.map(function(e){return{id:e.id,type:r?a()(e.type):e.type}}):c()(o.data)?i[u].data=o.data:i[u].data={id:o.data.id,type:r?a()(o.data.type):o.data.type}),o.links&&(i[u].links=n?m(o.links):o.links),o.meta&&(i[u].meta=n?m(o.meta):o.meta)}),i}function v(e,t){if(t.camelizeKeys){var n={};return f()(e).forEach(function(t){n[a()(t)]=m(e[t])}),n}return e}function y(e,t){var n=t.camelizeKeys,r=t.camelizeTypeValues,i={};return b(e).forEach(function(e){var t=n?a()(e.type):e.type;i[t]=i[t]||{},i[t][e.id]=i[t][e.id]||{id:e.id},i[t][e.id].type=r?a()(e.type):e.type,n?(i[t][e.id].attributes={},f()(e.attributes).forEach(function(n){i[t][e.id].attributes[a()(n)]=m(e.attributes[n])})):i[t][e.id].attributes=e.attributes,e.links&&(i[t][e.id].links={},f()(e.links).forEach(function(r){var o=n?a()(r):r;i[t][e.id].links[o]=e.links[r]})),e.relationships&&(i[t][e.id].relationships=g(e.relationships,{camelizeKeys:n,camelizeTypeValues:r})),e.meta&&(i[t][e.id].meta=v(e.meta,{camelizeKeys:n}))}),i}function w(e){return e.replace(/\?.*$/,"")}function _(e,t,n){var r,i=n.camelizeKeys,o=n.camelizeTypeValues,s={meta:{}};if(n.filterEndpoint)s.meta[t]={},r=s.meta[t];else{var u=w(t);s.meta[u]={},s.meta[u][t.slice(u.length)]={},r=s.meta[u][t.slice(u.length)]}if(r.data={},e.data){var c=[];b(e.data).forEach(function(e){var t={id:e.id,type:o?a()(e.type):e.type};e.relationships&&(t.relationships=g(e.relationships,{camelizeKeys:i,camelizeTypeValues:o})),c.push(t)}),r.data=c}return e.links&&(r.links=e.links,s.meta[w(t)].links=e.links),e.meta&&(r.meta=v(e.meta,{camelizeKeys:i})),s}function E(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.filterEndpoint,r=void 0===n||n,i=t.camelizeKeys,a=void 0===i||i,o=t.camelizeTypeValues,s=void 0===o||o,u=t.endpoint,c={};if(e.data&&h()(c,y(e.data,{camelizeKeys:a,camelizeTypeValues:s})),e.included&&h()(c,y(e.included,{camelizeKeys:a,camelizeTypeValues:s})),u){var l=r?w(u):u;h()(c,_(e,l,{camelizeKeys:a,camelizeTypeValues:s,filterEndpoint:r}))}return c}}},t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}return r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r(121)}()},63731:function(e){var t,n;t="undefined"!=typeof self?self:this,n=function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t||4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,(function(t){return e[t]}).bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=/["'&<>]/,a=function(e){var t=i.exec(e);if(null!==t){var n,r="",a=void 0,o=0;for(a=t.index;a")},e.prototype.space=function(){this.buffer.push(" ")},e.prototype.indent=function(e){if(e>0){for(var t="",n=0;n'+a(e)+""),this.buffer.push('"')},e.prototype.printString=function(e){this.buffer.push('"'),this.buffer.push(''+a(e)+""),this.buffer.push('"')},e.prototype.printBoolean=function(e){this.buffer.push(''+e+"")},e.prototype.printNumber=function(e){this.buffer.push(''+e+"")},e.prototype.printSelectionStart=function(){this.buffer.push(""),this.buffer.push('
')},e.prototype.printSelectionEnd=function(){this.buffer.push("
"),this.buffer.push('
')},Object.defineProperty(e.prototype,"printSelectionEndAtNewLine",{set:function(e){this._printSelectionEndAtNewLine=e},enumerable:!0,configurable:!0}),e.prototype.toString=function(){return this.buffer.join("")},e}(),s=function(e,t,n,r,i){t.checkCircular(e),t.print("{"),t.newLine();for(var a=Object.keys(e),o=0;o'):a.print('
'),Array.isArray(e)?u(e,a,0,t,i):s(e,a,0,t,i),a.print("
"),a.toString()}return""}},function(e,t,n){"use strict";n.r(t),n.d(t,"__extends",function(){return i}),n.d(t,"__assign",function(){return a}),n.d(t,"__rest",function(){return o}),n.d(t,"__decorate",function(){return s}),n.d(t,"__param",function(){return u}),n.d(t,"__metadata",function(){return c}),n.d(t,"__awaiter",function(){return l}),n.d(t,"__generator",function(){return f}),n.d(t,"__exportStar",function(){return d}),n.d(t,"__values",function(){return h}),n.d(t,"__read",function(){return p}),n.d(t,"__spread",function(){return b}),n.d(t,"__await",function(){return m}),n.d(t,"__asyncGenerator",function(){return g}),n.d(t,"__asyncDelegator",function(){return v}),n.d(t,"__asyncValues",function(){return y}),n.d(t,"__makeTemplateObject",function(){return w}),n.d(t,"__importStar",function(){return _}),n.d(t,"__importDefault",function(){return E});/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ var r=Object.setPrototypeOf||({__proto__:[]})instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])};function i(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var a=Object.assign||function(e){for(var t,n=1,r=arguments.length;nt.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);it.indexOf(r[i])&&(n[r[i]]=e[r[i]])}return n}function s(e,t,n,r){var i,a=arguments.length,o=a<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(i=e[s])&&(o=(a<3?i(o):a>3?i(t,n,o):i(t,n))||o);return a>3&&o&&Object.defineProperty(t,n,o),o}function u(e,t){return function(n,r){t(n,r,e)}}function c(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}function l(e,t,n,r){return new(n||(n=Promise))(function(i,a){function o(e){try{u(r.next(e))}catch(t){a(t)}}function s(e){try{u(r.throw(e))}catch(t){a(t)}}function u(e){e.done?i(e.value):new n(function(t){t(e.value)}).then(o,s)}u((r=r.apply(e,t||[])).next())})}function f(e,t){var n,r,i,a,o={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return a={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function s(a){return function(s){return function(a){if(n)throw TypeError("Generator is already executing.");for(;o;)try{if(n=1,r&&(i=r[2&a[0]?"return":a[0]?"throw":"next"])&&!(i=i.call(r,a[1])).done)return i;switch(r=0,i&&(a=[0,i.value]),a[0]){case 0:case 1:i=a;break;case 4:return o.label++,{value:a[1],done:!1};case 5:o.label++,r=a[1],a=[0];continue;case 7:a=o.ops.pop(),o.trys.pop();continue;default:if(!(i=(i=o.trys).length>0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}}}function p(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,a=n.call(e),o=[];try{for(;(void 0===t||t-- >0)&&!(r=a.next()).done;)o.push(r.value)}catch(s){i={error:s}}finally{try{r&&!r.done&&(n=a.return)&&n.call(a)}finally{if(i)throw i.error}}return o}function b(){for(var e=[],t=0;t1||s(e,t)})})}function s(e,t){try{var n;(n=i[e](t)).value instanceof m?Promise.resolve(n.value.v).then(u,c):l(a[0][2],n)}catch(r){l(a[0][3],r)}}function u(e){s("next",e)}function c(e){s("throw",e)}function l(e,t){e(t),a.shift(),a.length&&s(a[0][0],a[0][1])}}function v(e){var t,n;return t={},r("next"),r("throw",function(e){throw e}),r("return"),t[Symbol.iterator]=function(){return this},t;function r(r,i){e[r]&&(t[r]=function(t){return(n=!n)?{value:m(e[r](t)),done:"return"===r}:i?i(t):t})}}function y(e){if(!Symbol.asyncIterator)throw TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator];return t?t.call(e):h(e)}function w(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e}function _(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}function E(e){return e&&e.__esModule?e:{default:e}}}])},e.exports=n()},35828(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=s;var r=n(25477),i=a(r);function a(e){return e&&e.__esModule?e:{default:e}}function o(e){var t={};for(var n in e)t[(0,i.default)(n)]=e[n];return e.fallbacks&&(Array.isArray(e.fallbacks)?t.fallbacks=e.fallbacks.map(o):t.fallbacks=o(e.fallbacks)),t}function s(){function e(e){if(Array.isArray(e)){for(var t=0;t0&&void 0!==arguments[0]?arguments[0]:{},t=s(e);function n(e,n){if("style"!==n.type)return e;for(var r in e)e[r]=c(r,e[r],t);return e}function r(e,n){return c(n,e,t)}return{onProcessStyle:n,onChangeValue:r}}},29059(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{};return e.createGenerateClassName&&(this.options.createGenerateClassName=e.createGenerateClassName,this.generateClassName=e.createGenerateClassName()),null!=e.insertionPoint&&(this.options.insertionPoint=e.insertionPoint),(e.virtual||e.Renderer)&&(this.options.Renderer=e.Renderer||(e.virtual?A.default:M.default)),e.plugins&&this.use.apply(this,e.plugins),this}},{key:"createStyleSheet",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.index;"number"!=typeof n&&(n=0===y.default.index?0:y.default.index+1);var r=new c.default(e,i({},t,{jss:this,generateClassName:t.generateClassName||this.generateClassName,insertionPoint:this.options.insertionPoint,Renderer:this.options.Renderer,index:n}));return this.plugins.onProcessSheet(r),r}},{key:"removeStyleSheet",value:function(e){return e.detach(),y.default.remove(e),this}},{key:"createRule",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};(void 0===e?"undefined":r(e))==="object"&&(n=t,t=e,e=void 0);var i=n;i.jss=this,i.Renderer=this.options.Renderer,i.generateClassName||(i.generateClassName=this.generateClassName),i.classes||(i.classes={});var a=(0,x.default)(e,t,i);return!i.selector&&a instanceof _.default&&(a.selector="."+i.generateClassName(a)),this.plugins.onProcessRule(a),a}},{key:"use",value:function(){for(var e=this,t=arguments.length,n=Array(t),r=0;r0&&(this.refs[t]--,0===this.refs[t]&&this.sheets[t].detach())}},{key:"size",get:function(){return this.keys.length}}]),e}();t.default=u},92122(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(e,t){for(var n=0;n=this.index){t.push(e);return}for(var r=0;rn){t.splice(r,0,e);return}}}},{key:"reset",value:function(){this.registry=[]}},{key:"remove",value:function(e){var t=this.registry.indexOf(e);this.registry.splice(t,1)}},{key:"toString",value:function(e){return this.registry.filter(function(e){return e.attached}).map(function(t){return t.toString(e)}).join("\n")}},{key:"index",get:function(){return 0===this.registry.length?0:this.registry[this.registry.length-1].options.index}}]),e}();t.default=i},26899(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:0;return e.substr(t,e.indexOf("{")-1)},function(e){if(e.type===y.STYLE_RULE)return e.selectorText;if(e.type===y.KEYFRAMES_RULE){var t=e.name;if(t)return"@keyframes "+t;var n=e.cssText;return"@"+v(n,n.indexOf("keyframes"))}return v(e.cssText)});function _(e,t){return e.selectorText=t,e.selectorText===t}var E,S,k=p(function(){return document.head||document.getElementsByTagName("head")[0]}),x=(E=void 0,S=!1,function(e){var t={};E||(E=document.createElement("style"));for(var n=0;nt.index&&r.options.insertionPoint===t.insertionPoint)return r}return null}function M(e,t){for(var n=e.length-1;n>=0;n--){var r=e[n];if(r.attached&&r.options.insertionPoint===t.insertionPoint)return r}return null}function O(e){for(var t=k(),n=0;n0){var n=T(t,e);if(n)return n.renderer.element;if(n=M(t,e))return n.renderer.element.nextElementSibling}var r=e.insertionPoint;if(r&&"string"==typeof r){var i=O(r);if(i)return i.nextSibling;(0,a.default)("jss"===r,'[JSS] Insertion point "%s" not found.',r)}return null}function L(e,t){var n=t.insertionPoint,r=A(t);if(r){var i=r.parentNode;i&&i.insertBefore(e,r);return}if(n&&"number"==typeof n.nodeType){var o=n,s=o.parentNode;s?s.insertBefore(e,o.nextSibling):(0,a.default)(!1,"[JSS] Insertion point is not in the DOM.");return}k().insertBefore(e,r)}var C=p(function(){var e=document.querySelector('meta[property="csp-nonce"]');return e?e.getAttribute("content"):null}),I=function(){function e(t){h(this,e),this.getPropertyValue=b,this.setProperty=m,this.removeProperty=g,this.setSelector=_,this.getKey=w,this.getUnescapedKeysMap=x,this.hasInsertedRules=!1,t&&s.default.add(t),this.sheet=t;var n=this.sheet?this.sheet.options:{},r=n.media,i=n.meta,a=n.element;this.element=a||document.createElement("style"),this.element.setAttribute("data-jss",""),r&&this.element.setAttribute("media",r),i&&this.element.setAttribute("data-meta",i);var o=C();o&&this.element.setAttribute("nonce",o)}return r(e,[{key:"attach",value:function(){!this.element.parentNode&&this.sheet&&(this.hasInsertedRules&&(this.deploy(),this.hasInsertedRules=!1),L(this.element,this.sheet.options))}},{key:"detach",value:function(){this.element.parentNode.removeChild(this.element)}},{key:"deploy",value:function(){this.sheet&&(this.element.textContent="\n"+this.sheet.toString()+"\n")}},{key:"insertRule",value:function(e,t){var n=this.element.sheet,r=n.cssRules,i=e.toString();if(t||(t=r.length),!i)return!1;try{n.insertRule(i,t)}catch(o){return(0,a.default)(!1,"[JSS] Can not insert an unsupported rule \n\r%s",e),!1}return this.hasInsertedRules=!0,r[t]}},{key:"deleteRule",value:function(e){var t=this.element.sheet,n=this.indexOf(e);return -1!==n&&(t.deleteRule(n),!0)}},{key:"indexOf",value:function(e){for(var t=this.element.sheet.cssRules,n=0;n0&&void 0!==arguments[0]?arguments[0]:{indent:1},t=this.rules.toString(e);return t?this.key+" {\n"+t+"\n}":""}}]),e}();t.default=c},12398(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{indent:1},t=this.rules.toString(e);return t&&(t+="\n"),this.key+" {\n"+t+"}"}}]),e}();t.default=c},3486(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(e,t){for(var n=0;nc&&(0,i.default)(!1,"[JSS] You might have a memory leak. Rule counter is at %s.",e);var a=t,o="";return(r&&(a=r.options.classNamePrefix||t,null!=r.options.jss.id&&(o+=r.options.jss.id)),"production"===l)?""+a+s.default+o+e:a+n.key+"-"+s.default+(o&&"-"+o)+"-"+e}}},89380(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=l;var r=n(63189),i=c(r),a=n(15803),o=c(a),s=n(2808),u=c(s);function c(e){return e&&e.__esModule?e:{default:e}}function l(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"unnamed",t=arguments[1],n=arguments[2],r=n.jss,a=(0,u.default)(t),s=r.plugins.onCreateRule(e,a,n);return s||("@"===e[0]&&(0,i.default)(!1,"[JSS] Unknown at-rule %s",e),new o.default(e,a,n))}},55878(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n.g.CSS,i="production",a=/([[\].#*$><+~=|^:(),"'`])/g;t.default=function(e){return"production"===i?e:r&&r.escape?r.escape(e):e.replace(a,"\\$1")}},27343(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function r(e){var t=null;for(var i in e){var a=e[i],o=void 0===a?"undefined":n(a);if("function"===o)t||(t={}),t[i]=a;else if("object"===o&&null!==a&&!Array.isArray(a)){var s=r(a);s&&(t||(t={}),t[i]=s)}}return t}t.default=r},97628(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(67121),i=a(r);function a(e){return e&&e.__esModule?e:{default:e}}t.default=function(e){return e&&e[i.default]&&e===e[i.default]()}},94229(e,t){"use strict";function n(e,t){e.renderable=t,e.rules&&t.cssRules&&e.rules.link(t.cssRules)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},141(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="2f1acc6c3a606b082e5eef5e54414ffb";null==n.g[r]&&(n.g[r]=0),t.default=n.g[r]++},70084(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=s;var r=n(16229),i=a(r);function a(e){return e&&e.__esModule?e:{default:e}}function o(e,t){for(var n="",r=0;r2&&void 0!==arguments[2]?arguments[2]:{},r="";if(!t)return r;var a=n.indent,s=void 0===a?0:a,u=t.fallbacks;if(s++,u){if(Array.isArray(u))for(var c=0;c1&&void 0!==arguments[1]&&arguments[1];if(!Array.isArray(e))return e;var r="";if(Array.isArray(e[0]))for(var i=0;i0&&void 0!==arguments[0]?arguments[0]:{};s(this,e),this.cookieOptions=Object.assign({path:"/"},t),u=void 0===t.prefix?u:t.prefix}return r(e,[{key:"getItem",value:function(e){var t=a.default.parse(document.cookie);return t&&t.hasOwnProperty(u+e)?t[u+e]:null}},{key:"setItem",value:function(e,t){return document.cookie=a.default.serialize(u+e,t,this.cookieOptions),t}},{key:"removeItem",value:function(e){var t=Object.assign({},this.cookieOptions,{maxAge:-1});return document.cookie=a.default.serialize(u+e,"",t),null}},{key:"clear",value:function(){var e=a.default.parse(document.cookie);for(var t in e)0===t.indexOf(u)&&this.removeItem(t.substr(u.length));return null}}]),e}();function l(){var e=new c;try{var t="__test";e.setItem(t,"1");var n=e.getItem(t);return e.removeItem(t),"1"===n}catch(r){return!1}}t.default=c},90145(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:"localStorage",t=String(e).replace(/storage$/i,"").toLowerCase();if("local"===t)return a("localStorage");if("session"===t)return a("sessionStorage");if("cookie"===t)return(0,r.hasCookies)();if("memory"===t)return!0;throw Error("Storage method `"+e+"` is not available.\n Please use one of the following: localStorage, sessionStorage, cookieStorage, memoryStorage.")}},72426(e,t){"use strict";/*! + * cookie + * Copyright(c) 2012-2014 Roman Shtylman + * Copyright(c) 2015 Douglas Christopher Wilson + * MIT Licensed + */ t.parse=o,t.serialize=s;var n=decodeURIComponent,r=encodeURIComponent,i=/; */,a=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function o(e,t){if("string"!=typeof e)throw TypeError("argument str must be a string");for(var r={},a=t||{},o=e.split(i),s=a.decode||n,c=0;cc});var r=n(56169);e=n.hmd(e);var i="object"==typeof exports&&exports&&!exports.nodeType&&exports,a=i&&e&&!e.nodeType&&e,o=a&&a.exports===i?r.Z.Buffer:void 0,s=o?o.allocUnsafe:void 0;function u(e,t){if(t)return e.slice();var n=e.length,r=s?s(n):new e.constructor(n);return e.copy(r),r}let c=u},48277(e,t,n){"use strict";n.d(t,{Z:()=>i});var r="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g;let i=r},79730(e,t,n){"use strict";n.d(t,{Z:()=>u});var r=n(48277);e=n.hmd(e);var i="object"==typeof exports&&exports&&!exports.nodeType&&exports,a=i&&e&&!e.nodeType&&e,o=a&&a.exports===i&&r.Z.process,s=function(){try{var e=a&&a.require&&a.require("util").types;if(e)return e;return o&&o.binding&&o.binding("util")}catch(t){}}();let u=s},56169(e,t,n){"use strict";n.d(t,{Z:()=>o});var r=n(48277),i="object"==typeof self&&self&&self.Object===Object&&self,a=r.Z||i||Function("return this")();let o=a},29710(e,t,n){"use strict";n.d(t,{Z:()=>l});var r=n(56169);function i(){return!1}let a=i;e=n.hmd(e);var o="object"==typeof exports&&exports&&!exports.nodeType&&exports,s=o&&e&&!e.nodeType&&e,u=s&&s.exports===o?r.Z.Buffer:void 0,c=(u?u.isBuffer:void 0)||a;let l=c},18552(e,t,n){var r=n(10852),i=n(55639),a=r(i,"DataView");e.exports=a},1989(e,t,n){var r=n(51789),i=n(80401),a=n(57667),o=n(21327),s=n(81866);function u(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t-1}e.exports=i},1196(e){function t(e,t,n){for(var r=-1,i=null==e?0:e.length;++r0&&n(l)?t>1?a(l,t-1,n,o,s):r(s,l):o||(s[s.length]=l)}return s}e.exports=a},28483(e,t,n){var r=n(25063)();e.exports=r},47816(e,t,n){var r=n(28483),i=n(3674);function a(e,t){return e&&r(e,t,i)}e.exports=a},97786(e,t,n){var r=n(71811),i=n(40327);function a(e,t){t=r(t,e);for(var n=0,a=t.length;null!=e&&ni?0:i+t),(n=n>i?i:n)<0&&(n+=i),i=t>n?0:n-t>>>0,t>>>=0;for(var a=Array(i);++r=c){var m=t?null:s(e);if(m)return u(m);h=!1,f=o,b=new r}else b=t?[]:p;outer:for(;++l=i?e:r(e,t,n)}e.exports=i},74318(e,t,n){var r=n(11149);function i(e){var t=new e.constructor(e.byteLength);return new r(t).set(new r(e)),t}e.exports=i},64626(e,t,n){e=n.nmd(e);var r=n(55639),i=t&&!t.nodeType&&t,a=i&&e&&!e.nodeType&&e,o=a&&a.exports===i?r.Buffer:void 0,s=o?o.allocUnsafe:void 0;function u(e,t){if(t)return e.slice();var n=e.length,r=s?s(n):new e.constructor(n);return e.copy(r),r}e.exports=u},57157(e,t,n){var r=n(74318);function i(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}e.exports=i},93147(e){var t=/\w*$/;function n(e){var n=new e.constructor(e.source,t.exec(e));return n.lastIndex=e.lastIndex,n}e.exports=n},40419(e,t,n){var r=n(62705),i=r?r.prototype:void 0,a=i?i.valueOf:void 0;function o(e){return a?Object(a.call(e)):{}}e.exports=o},77133(e,t,n){var r=n(74318);function i(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}e.exports=i},278(e){function t(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n1?n[a-1]:void 0,s=a>2?n[2]:void 0;for(o=e.length>3&&"function"==typeof o?(a--,o):void 0,s&&i(n[0],n[1],s)&&(o=a<3?void 0:o,a=1),t=Object(t);++rd))return!1;var p=l.get(e),b=l.get(t);if(p&&b)return p==t&&b==e;var m=-1,g=!0,v=n&s?new r:void 0;for(l.set(e,t),l.set(t,e);++m-1&&e%1==0&&e-1}e.exports=i},13399(e,t,n){var r=n(18470);function i(e,t){var n=this.__data__,i=r(n,e);return i<0?(++this.size,n.push([e,t])):n[i][1]=t,this}e.exports=i},24785(e,t,n){var r=n(1989),i=n(38407),a=n(57071);function o(){this.size=0,this.__data__={hash:new r,map:new(a||i),string:new r}}e.exports=o},11285(e,t,n){var r=n(45050);function i(e){var t=r(this,e).delete(e);return this.size-=t?1:0,t}e.exports=i},96e3(e,t,n){var r=n(45050);function i(e){return r(this,e).get(e)}e.exports=i},49916(e,t,n){var r=n(45050);function i(e){return r(this,e).has(e)}e.exports=i},95265(e,t,n){var r=n(45050);function i(e,t){var n=r(this,e),i=n.size;return n.set(e,t),this.size+=n.size==i?0:1,this}e.exports=i},68776(e){function t(e){var t=-1,n=Array(e.size);return e.forEach(function(e,r){n[++t]=[r,e]}),n}e.exports=t},42634(e){function t(e,t){return function(n){return null!=n&&n[e]===t&&(void 0!==t||e in Object(n))}}e.exports=t},24523(e,t,n){var r=n(88306),i=500;function a(e){var t=r(e,function(e){return n.size===i&&n.clear(),e}),n=t.cache;return t}e.exports=a},94536(e,t,n){var r=n(10852)(Object,"create");e.exports=r},86916(e,t,n){var r=n(5569)(Object.keys,Object);e.exports=r},33498(e){function t(e){var t=[];if(null!=e)for(var n in Object(e))t.push(n);return t}e.exports=t},31167(e,t,n){e=n.nmd(e);var r=n(31957),i=t&&!t.nodeType&&t,a=i&&e&&!e.nodeType&&e,o=a&&a.exports===i&&r.process,s=function(){try{var e=a&&a.require&&a.require("util").types;if(e)return e;return o&&o.binding&&o.binding("util")}catch(t){}}();e.exports=s},2333(e){var t=Object.prototype.toString;function n(e){return t.call(e)}e.exports=n},5569(e){function t(e,t){return function(n){return e(t(n))}}e.exports=t},45357(e,t,n){var r=n(96874),i=Math.max;function a(e,t,n){return t=i(void 0===t?e.length-1:t,0),function(){for(var a=arguments,o=-1,s=i(a.length-t,0),u=Array(s);++o0){if(++i>=t)return arguments[0]}else i=0;return e.apply(void 0,arguments)}}e.exports=i},37465(e,t,n){var r=n(38407);function i(){this.__data__=new r,this.size=0}e.exports=i},63779(e){function t(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}e.exports=t},67599(e){function t(e){return this.__data__.get(e)}e.exports=t},44758(e){function t(e){return this.__data__.has(e)}e.exports=t},34309(e,t,n){var r=n(38407),i=n(57071),a=n(83369),o=200;function s(e,t){var n=this.__data__;if(n instanceof r){var s=n.__data__;if(!i||s.length=t||n<0||g&&r>=f}function S(){var e=i();if(E(e))return k(e);h=setTimeout(S,_(e))}function k(e){return(h=void 0,v&&c)?y(e):(c=l=void 0,d)}function x(){void 0!==h&&clearTimeout(h),b=0,c=p=l=h=void 0}function T(){return void 0===h?d:k(i())}function M(){var e=i(),n=E(e);if(c=arguments,l=this,p=e,n){if(void 0===h)return w(p);if(g)return clearTimeout(h),h=setTimeout(S,t),y(p)}return void 0===h&&(h=setTimeout(S,t)),d}return t=a(t)||0,r(n)&&(m=!!n.leading,f=(g="maxWait"in n)?s(a(n.maxWait)||0,t):f,v="trailing"in n?!!n.trailing:v),M.cancel=x,M.flush=T,M}e.exports=c},53816(e,t,n){var r=n(69389),i=n(79833),a=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,o=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");function s(e){return(e=i(e))&&e.replace(a,r).replace(o,"")}e.exports=s},66073(e,t,n){e.exports=n(84486)},77813(e){function t(e,t){return e===t||e!=e&&t!=t}e.exports=t},63105(e,t,n){var r=n(34963),i=n(80760),a=n(67206),o=n(1469);function s(e,t){return(o(e)?r:i)(e,a(t,3))}e.exports=s},85564(e,t,n){var r=n(21078);function i(e){return(null==e?0:e.length)?r(e,1):[]}e.exports=i},84486(e,t,n){var r=n(77412),i=n(89881),a=n(54290),o=n(1469);function s(e,t){return(o(e)?r:i)(e,a(t))}e.exports=s},27361(e,t,n){var r=n(97786);function i(e,t,n){var i=null==e?void 0:r(e,t);return void 0===i?n:i}e.exports=i},18721(e,t,n){var r=n(78565),i=n(222);function a(e,t){return null!=e&&i(e,t,r)}e.exports=a},79095(e,t,n){var r=n(13),i=n(222);function a(e,t){return null!=e&&i(e,t,r)}e.exports=a},6557(e){function t(e){return e}e.exports=t},35694(e,t,n){var r=n(9454),i=n(37005),a=Object.prototype,o=a.hasOwnProperty,s=a.propertyIsEnumerable,u=r(function(){return arguments}())?r:function(e){return i(e)&&o.call(e,"callee")&&!s.call(e,"callee")};e.exports=u},1469(e){var t=Array.isArray;e.exports=t},98612(e,t,n){var r=n(23560),i=n(41780);function a(e){return null!=e&&i(e.length)&&!r(e)}e.exports=a},29246(e,t,n){var r=n(98612),i=n(37005);function a(e){return i(e)&&r(e)}e.exports=a},44144(e,t,n){e=n.nmd(e);var r=n(55639),i=n(95062),a=t&&!t.nodeType&&t,o=a&&e&&!e.nodeType&&e,s=o&&o.exports===a?r.Buffer:void 0,u=(s?s.isBuffer:void 0)||i;e.exports=u},41609(e,t,n){var r=n(280),i=n(64160),a=n(35694),o=n(1469),s=n(98612),u=n(44144),c=n(25726),l=n(36719),f="[object Map]",d="[object Set]",h=Object.prototype.hasOwnProperty;function p(e){if(null==e)return!0;if(s(e)&&(o(e)||"string"==typeof e||"function"==typeof e.splice||u(e)||l(e)||a(e)))return!e.length;var t=i(e);if(t==f||t==d)return!e.size;if(c(e))return!r(e).length;for(var n in e)if(h.call(e,n))return!1;return!0}e.exports=p},23560(e,t,n){var r=n(44239),i=n(13218),a="[object AsyncFunction]",o="[object Function]",s="[object GeneratorFunction]",u="[object Proxy]";function c(e){if(!i(e))return!1;var t=r(e);return t==o||t==s||t==a||t==u}e.exports=c},41780(e){var t=9007199254740991;function n(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=t}e.exports=n},56688(e,t,n){var r=n(25588),i=n(7518),a=n(31167),o=a&&a.isMap,s=o?i(o):r;e.exports=s},45220(e){function t(e){return null===e}e.exports=t},13218(e){function t(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}e.exports=t},37005(e){function t(e){return null!=e&&"object"==typeof e}e.exports=t},68630(e,t,n){var r=n(44239),i=n(85924),a=n(37005),o="[object Object]",s=Function.prototype,u=Object.prototype,c=s.toString,l=u.hasOwnProperty,f=c.call(Object);function d(e){if(!a(e)||r(e)!=o)return!1;var t=i(e);if(null===t)return!0;var n=l.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&c.call(n)==f}e.exports=d},72928(e,t,n){var r=n(29221),i=n(7518),a=n(31167),o=a&&a.isSet,s=o?i(o):r;e.exports=s},47037(e,t,n){var r=n(44239),i=n(1469),a=n(37005),o="[object String]";function s(e){return"string"==typeof e||!i(e)&&a(e)&&r(e)==o}e.exports=s},33448(e,t,n){var r=n(44239),i=n(37005),a="[object Symbol]";function o(e){return"symbol"==typeof e||i(e)&&r(e)==a}e.exports=o},36719(e,t,n){var r=n(38749),i=n(7518),a=n(31167),o=a&&a.isTypedArray,s=o?i(o):r;e.exports=s},52353(e){function t(e){return void 0===e}e.exports=t},3674(e,t,n){var r=n(14636),i=n(280),a=n(98612);function o(e){return a(e)?r(e):i(e)}e.exports=o},81704(e,t,n){var r=n(14636),i=n(35014),a=n(98612);function o(e){return a(e)?r(e,!0):i(e)}e.exports=o},96486:function(e,t,n){var r;e=n.nmd(e),(function(){var i,a="4.17.21",o=200,s="Unsupported core-js use. Try https://npms.io/search?q=ponyfill.",u="Expected a function",c="Invalid `variable` option passed into `_.template`",l="__lodash_hash_undefined__",f=500,d="__lodash_placeholder__",h=1,p=2,b=4,m=1,g=2,v=1,y=2,w=4,_=8,E=16,S=32,k=64,x=128,T=256,M=512,O=30,A="...",L=800,C=16,I=1,D=2,N=3,P=1/0,R=9007199254740991,j=17976931348623157e292,F=0/0,Y=4294967295,B=Y-1,U=Y>>>1,H=[["ary",x],["bind",v],["bindKey",y],["curry",_],["curryRight",E],["flip",M],["partial",S],["partialRight",k],["rearg",T]],$="[object Arguments]",z="[object Array]",G="[object AsyncFunction]",W="[object Boolean]",K="[object Date]",V="[object DOMException]",q="[object Error]",Z="[object Function]",X="[object GeneratorFunction]",J="[object Map]",Q="[object Number]",ee="[object Null]",et="[object Object]",en="[object Promise]",er="[object Proxy]",ei="[object RegExp]",ea="[object Set]",eo="[object String]",es="[object Symbol]",eu="[object Undefined]",ec="[object WeakMap]",el="[object WeakSet]",ef="[object ArrayBuffer]",ed="[object DataView]",eh="[object Float32Array]",ep="[object Float64Array]",eb="[object Int8Array]",em="[object Int16Array]",eg="[object Int32Array]",ev="[object Uint8Array]",ey="[object Uint8ClampedArray]",ew="[object Uint16Array]",e_="[object Uint32Array]",eE=/\b__p \+= '';/g,eS=/\b(__p \+=) '' \+/g,ek=/(__e\(.*?\)|\b__t\)) \+\n'';/g,ex=/&(?:amp|lt|gt|quot|#39);/g,eT=/[&<>"']/g,eM=RegExp(ex.source),eO=RegExp(eT.source),eA=/<%-([\s\S]+?)%>/g,eL=/<%([\s\S]+?)%>/g,eC=/<%=([\s\S]+?)%>/g,eI=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,eD=/^\w*$/,eN=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,eP=/[\\^$.*+?()[\]{}|]/g,eR=RegExp(eP.source),ej=/^\s+/,eF=/\s/,eY=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,eB=/\{\n\/\* \[wrapped with (.+)\] \*/,eU=/,? & /,eH=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,e$=/[()=,{}\[\]\/\s]/,ez=/\\(\\)?/g,eG=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,eW=/\w*$/,eK=/^[-+]0x[0-9a-f]+$/i,eV=/^0b[01]+$/i,eq=/^\[object .+?Constructor\]$/,eZ=/^0o[0-7]+$/i,eX=/^(?:0|[1-9]\d*)$/,eJ=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,eQ=/($^)/,e1=/['\n\r\u2028\u2029\\]/g,e0="\ud800-\udfff",e2="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",e3="\\u2700-\\u27bf",e4="a-z\\xdf-\\xf6\\xf8-\\xff",e5="A-Z\\xc0-\\xd6\\xd8-\\xde",e6="\\ufe0e\\ufe0f",e9="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",e8="['’]",e7="["+e0+"]",te="["+e9+"]",tt="["+e2+"]",tn="\\d+",tr="["+e3+"]",ti="["+e4+"]",ta="[^"+e0+e9+tn+e3+e4+e5+"]",to="\ud83c[\udffb-\udfff]",ts="[^"+e0+"]",tu="(?:\ud83c[\udde6-\uddff]){2}",tc="[\ud800-\udbff][\udc00-\udfff]",tl="["+e5+"]",tf="\\u200d",td="(?:"+ti+"|"+ta+")",th="(?:"+tl+"|"+ta+")",tp="(?:"+e8+"(?:d|ll|m|re|s|t|ve))?",tb="(?:"+e8+"(?:D|LL|M|RE|S|T|VE))?",tm="(?:"+tt+"|"+to+")?",tg="["+e6+"]?",tv="(?:"+tf+"(?:"+[ts,tu,tc].join("|")+")"+tg+tm+")*",ty="\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",tw="\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])",t_=tg+tm+tv,tE="(?:"+[tr,tu,tc].join("|")+")"+t_,tS="(?:"+[ts+tt+"?",tt,tu,tc,e7].join("|")+")",tk=RegExp(e8,"g"),tx=RegExp(tt,"g"),tT=RegExp(to+"(?="+to+")|"+tS+t_,"g"),tM=RegExp([tl+"?"+ti+"+"+tp+"(?="+[te,tl,"$"].join("|")+")",th+"+"+tb+"(?="+[te,tl+td,"$"].join("|")+")",tl+"?"+td+"+"+tp,tl+"+"+tb,tw,ty,tn,tE].join("|"),"g"),tO=RegExp("["+tf+e0+e2+e6+"]"),tA=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,tL=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],tC=-1,tI={};tI[eh]=tI[ep]=tI[eb]=tI[em]=tI[eg]=tI[ev]=tI[ey]=tI[ew]=tI[e_]=!0,tI[$]=tI[z]=tI[ef]=tI[W]=tI[ed]=tI[K]=tI[q]=tI[Z]=tI[J]=tI[Q]=tI[et]=tI[ei]=tI[ea]=tI[eo]=tI[ec]=!1;var tD={};tD[$]=tD[z]=tD[ef]=tD[ed]=tD[W]=tD[K]=tD[eh]=tD[ep]=tD[eb]=tD[em]=tD[eg]=tD[J]=tD[Q]=tD[et]=tD[ei]=tD[ea]=tD[eo]=tD[es]=tD[ev]=tD[ey]=tD[ew]=tD[e_]=!0,tD[q]=tD[Z]=tD[ec]=!1;var tN={À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"},tP={"&":"&","<":"<",">":">",'"':""","'":"'"},tR={"&":"&","<":"<",">":">",""":'"',"'":"'"},tj={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},tF=parseFloat,tY=parseInt,tB="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g,tU="object"==typeof self&&self&&self.Object===Object&&self,tH=tB||tU||Function("return this")(),t$=t&&!t.nodeType&&t,tz=t$&&e&&!e.nodeType&&e,tG=tz&&tz.exports===t$,tW=tG&&tB.process,tK=function(){try{var e=tz&&tz.require&&tz.require("util").types;if(e)return e;return tW&&tW.binding&&tW.binding("util")}catch(t){}}(),tV=tK&&tK.isArrayBuffer,tq=tK&&tK.isDate,tZ=tK&&tK.isMap,tX=tK&&tK.isRegExp,tJ=tK&&tK.isSet,tQ=tK&&tK.isTypedArray;function t1(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}function t0(e,t,n,r){for(var i=-1,a=null==e?0:e.length;++i-1}function t9(e,t,n){for(var r=-1,i=null==e?0:e.length;++r-1;);return n}function nk(e,t){for(var n=e.length;n--&&nu(t,e[n],0)>-1;);return n}function nx(e,t){for(var n=e.length,r=0;n--;)e[n]===t&&++r;return r}var nT=nh(tN),nM=nh(tP);function nO(e){return"\\"+tj[e]}function nA(e,t){return null==e?i:e[t]}function nL(e){return tO.test(e)}function nC(e){return tA.test(e)}function nI(e){for(var t,n=[];!(t=e.next()).done;)n.push(t.value);return n}function nD(e){var t=-1,n=Array(e.size);return e.forEach(function(e,r){n[++t]=[r,e]}),n}function nN(e,t){return function(n){return e(t(n))}}function nP(e,t){for(var n=-1,r=e.length,i=0,a=[];++n-1}function rh(e,t){var n=this.__data__,r=rP(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this}function rp(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t=t?e:t)),e}function rH(e,t,n,r,a,o){var s,u=t&h,c=t&p,l=t&b;if(n&&(s=a?n(e,r,a,o):n(e)),s!==i)return s;if(!u1(e))return e;var f=uF(e);if(f){if(s=a9(e),!u)return al(e,s)}else{var d=a3(e),m=d==Z||d==X;if(u$(e))return ae(e,u);if(d==et||d==$||m&&!a){if(s=c||m?{}:a8(e),!u)return c?ah(e,rF(s,e)):ad(e,rj(s,e))}else{if(!tD[d])return a?e:{};s=a7(e,d,u)}}o||(o=new rS);var g=o.get(e);if(g)return g;o.set(e,s),cr(e)?e.forEach(function(r){s.add(rH(r,t,n,r,e,o))}):u2(e)&&e.forEach(function(r,i){s.set(i,rH(r,t,n,i,e,o))});var v=l?c?aW:aG:c?c$:cH,y=f?i:v(e);return t2(y||e,function(r,i){y&&(r=e[i=r]),rN(s,i,rH(r,t,n,i,e,o))}),s}function r$(e){var t=cH(e);return function(n){return rz(n,e,t)}}function rz(e,t,n){var r=n.length;if(null==e)return!r;for(e=e4(e);r--;){var a=n[r],o=t[a],s=e[a];if(s===i&&!(a in e)||!o(s))return!1}return!0}function rG(e,t,n){if("function"!=typeof e)throw new e9(u);return o_(function(){e.apply(i,n)},t)}function rW(e,t,n,r){var i=-1,a=t6,s=!0,u=e.length,c=[],l=t.length;if(!u)return c;n&&(t=t8(t,nw(n))),r?(a=t9,s=!1):t.length>=o&&(a=nE,s=!1,t=new rw(t));outer:for(;++ia?0:a+n),(r=r===i||r>a?a:cp(r))<0&&(r+=a),r=n>r?0:cb(r);n0&&n(s)?t>1?rQ(s,t-1,n,r,i):t7(i,s):r||(i[i.length]=s)}return i}var r1=ag(),r0=ag(!0);function r2(e,t){return e&&r1(e,t,cH)}function r3(e,t){return e&&r0(e,t,cH)}function r4(e,t){return t5(t,function(t){return uX(e[t])})}function r5(e,t){t=i6(t,e);for(var n=0,r=t.length;null!=e&&nt}function r7(e,t){return null!=e&&tr.call(e,t)}function ie(e,t){return null!=e&&t in e4(e)}function it(e,t,n){return e>=tU(t,n)&&e=120&&f.length>=120)?new rw(s&&f):i}f=e[0];var d=-1,h=u[0];outer:for(;++d-1;)s!==e&&tg.call(s,u,1),tg.call(e,u,1);return e}function iD(e,t){for(var n=e?t.length:0,r=n-1;n--;){var i=t[n];if(n==r||i!==a){var a=i;on(i)?tg.call(e,i,1):iJ(e,i)}}return e}function iN(e,t){return e+tO(tW()*(t-e+1))}function iP(e,t,n,r){for(var i=-1,a=tB(tM((t-e)/(n||1)),0),o=eF(a);a--;)o[r?a:++i]=e,e+=n;return o}function iR(e,t){var n="";if(!e||t<1||t>R)return n;do t%2&&(n+=e),(t=tO(t/2))&&(e+=e);while(t)return n}function ij(e,t){return oE(om(e,t,lB),e+"")}function iF(e){return rL(c9(e))}function iY(e,t){var n=c9(e);return ox(n,rU(t,0,n.length))}function iB(e,t,n,r){if(!u1(e))return e;t=i6(t,e);for(var a=-1,o=t.length,s=o-1,u=e;null!=u&&++ai?0:i+t),(n=n>i?i:n)<0&&(n+=i),i=t>n?0:n-t>>>0,t>>>=0;for(var a=eF(i);++r>>1,o=e[a];null!==o&&!ca(o)&&(n?o<=t:o=o){var l=t?null:aP(e);if(l)return nR(l);s=!1,i=nE,c=new rw}else c=t?[]:u;outer:for(;++r=r?e:iz(e,t,n)}var i7=tE||function(e){return tH.clearTimeout(e)};function ae(e,t){if(t)return e.slice();var n=e.length,r=th?th(n):new e.constructor(n);return e.copy(r),r}function at(e){var t=new e.constructor(e.byteLength);return new td(t).set(new td(e)),t}function an(e,t){var n=t?at(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}function ar(e){var t=new e.constructor(e.source,eW.exec(e));return t.lastIndex=e.lastIndex,t}function ai(e){return n2?e4(n2.call(e)):{}}function aa(e,t){var n=t?at(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}function ao(e,t){if(e!==t){var n=e!==i,r=null===e,a=e==e,o=ca(e),s=t!==i,u=null===t,c=t==t,l=ca(t);if(!u&&!l&&!o&&e>t||o&&s&&c&&!u&&!l||r&&s&&c||!n&&c||!a)return 1;if(!r&&!o&&!l&&e=s)return u;return u*("desc"==n[r]?-1:1)}}return e.index-t.index}function au(e,t,n,r){for(var i=-1,a=e.length,o=n.length,s=-1,u=t.length,c=tB(a-o,0),l=eF(u+c),f=!r;++s1?n[a-1]:i,s=a>2?n[2]:i;for(o=e.length>3&&"function"==typeof o?(a--,o):i,s&&or(n[0],n[1],s)&&(o=a<3?i:o,a=1),t=e4(t);++r-1?a[o?t[s]:s]:i}}function ak(e){return az(function(t){var n=t.length,r=n,a=n9.prototype.thru;for(e&&t.reverse();r--;){var o=t[r];if("function"!=typeof o)throw new e9(u);if(a&&!s&&"wrapper"==aV(o))var s=new n9([],!0)}for(r=s?r:n;++r1&&v.reverse(),f&&cu))return!1;var l=o.get(e),f=o.get(t);if(l&&f)return l==t&&f==e;var d=-1,h=!0,p=n&g?new rw:i;for(o.set(e,t),o.set(t,e);++d1?"& ":"")+t[r],t=t.join(n>2?", ":" "),e.replace(eY,"{\n/* [wrapped with "+t+"] */\n")}function ot(e){return uF(e)||uj(e)||!!(tv&&e&&e[tv])}function on(e,t){var n=typeof e;return!!(t=null==t?R:t)&&("number"==n||"symbol"!=n&&eX.test(e))&&e>-1&&e%1==0&&e0){if(++t>=L)return arguments[0]}else t=0;return e.apply(i,arguments)}}function ox(e,t){var n=-1,r=e.length,a=r-1;for(t=t===i?r:t;++n1?e[t-1]:i;return n="function"==typeof n?(e.pop(),n):i,sE(e,n)});function sC(e){var t=n4(e);return t.__chain__=!0,t}function sI(e,t){return t(e),e}function sD(e,t){return t(e)}var sN=az(function(e){var t=e.length,n=t?e[0]:0,r=this.__wrapped__,a=function(t){return rB(t,e)};return!(t>1)&&!this.__actions__.length&&r instanceof n8&&on(n)?((r=r.slice(n,+n+(t?1:0))).__actions__.push({func:sD,args:[a],thisArg:i}),new n9(r,this.__chain__).thru(function(e){return t&&!e.length&&e.push(i),e})):this.thru(a)});function sP(){return sC(this)}function sR(){return new n9(this.value(),this.__chain__)}function sj(){i===this.__values__&&(this.__values__=cd(this.value()));var e=this.__index__>=this.__values__.length,t=e?i:this.__values__[this.__index__++];return{done:e,value:t}}function sF(){return this}function sY(e){for(var t,n=this;n instanceof n6;){var r=oL(n);r.__index__=0,r.__values__=i,t?a.__wrapped__=r:t=r;var a=r;n=n.__wrapped__}return a.__wrapped__=e,t}function sB(){var e=this.__wrapped__;if(e instanceof n8){var t=e;return this.__actions__.length&&(t=new n8(this)),(t=t.reverse()).__actions__.push({func:sD,args:[se],thisArg:i}),new n9(t,this.__chain__)}return this.thru(se)}function sU(){return i0(this.__wrapped__,this.__actions__)}var sH=ap(function(e,t,n){tr.call(e,n)?++e[n]:rY(e,n,1)});function s$(e,t,n){var r=uF(e)?t4:rq;return n&&or(e,t,n)&&(t=i),r(e,aZ(t,3))}function sz(e,t){return(uF(e)?t5:rJ)(e,aZ(t,3))}var sG=aS(oH),sW=aS(o$);function sK(e,t){return rQ(s2(e,t),1)}function sV(e,t){return rQ(s2(e,t),P)}function sq(e,t,n){return n=n===i?1:cp(n),rQ(s2(e,t),n)}function sZ(e,t){return(uF(e)?t2:rK)(e,aZ(t,3))}function sX(e,t){return(uF(e)?t3:rV)(e,aZ(t,3))}var sJ=ap(function(e,t,n){tr.call(e,n)?e[n].push(t):rY(e,n,[t])});function sQ(e,t,n,r){e=uB(e)?e:c9(e),n=n&&!r?cp(n):0;var i=e.length;return n<0&&(n=tB(i+n,0)),ci(e)?n<=i&&e.indexOf(t,n)>-1:!!i&&nu(e,t,n)>-1}var s1=ij(function(e,t,n){var r=-1,i="function"==typeof t,a=uB(e)?eF(e.length):[];return rK(e,function(e){a[++r]=i?t1(t,e,n):ia(e,t,n)}),a}),s0=ap(function(e,t,n){rY(e,n,t)});function s2(e,t){return(uF(e)?t8:iE)(e,aZ(t,3))}function s3(e,t,n,r){return null==e?[]:(uF(t)||(t=null==t?[]:[t]),n=r?i:n,uF(n)||(n=null==n?[]:[n]),iO(e,t,n))}var s4=ap(function(e,t,n){e[n?0:1].push(t)},function(){return[[],[]]});function s5(e,t,n){var r=uF(e)?ne:np,i=arguments.length<3;return r(e,aZ(t,4),n,i,rK)}function s6(e,t,n){var r=uF(e)?nt:np,i=arguments.length<3;return r(e,aZ(t,4),n,i,rV)}function s9(e,t){return(uF(e)?t5:rJ)(e,ug(aZ(t,3)))}function s8(e){return(uF(e)?rL:iF)(e)}function s7(e,t,n){return t=(n?or(e,t,n):t===i)?1:cp(t),(uF(e)?rC:iY)(e,t)}function ue(e){return(uF(e)?rI:i$)(e)}function ut(e){if(null==e)return 0;if(uB(e))return ci(e)?nB(e):e.length;var t=a3(e);return t==J||t==ea?e.size:iy(e).length}function un(e,t,n){var r=uF(e)?nn:iG;return n&&or(e,t,n)&&(t=i),r(e,aZ(t,3))}var ur=ij(function(e,t){if(null==e)return[];var n=t.length;return n>1&&or(e,t[0],t[1])?t=[]:n>2&&or(t[0],t[1],t[2])&&(t=[t[0]]),iO(e,rQ(t,1),[])}),ui=tS||function(){return tH.Date.now()};function ua(e,t){if("function"!=typeof t)throw new e9(u);return e=cp(e),function(){if(--e<1)return t.apply(this,arguments)}}function uo(e,t,n){return t=n?i:t,t=e&&null==t?e.length:t,aj(e,x,i,i,i,i,t)}function us(e,t){var n;if("function"!=typeof t)throw new e9(u);return e=cp(e),function(){return--e>0&&(n=t.apply(this,arguments)),e<=1&&(t=i),n}}var uu=ij(function(e,t,n){var r=v;if(n.length){var i=nP(n,aq(uu));r|=S}return aj(e,r,t,n,i)}),uc=ij(function(e,t,n){var r=v|y;if(n.length){var i=nP(n,aq(uc));r|=S}return aj(t,r,e,n,i)});function ul(e,t,n){t=n?i:t;var r=aj(e,_,i,i,i,i,i,t);return r.placeholder=ul.placeholder,r}function uf(e,t,n){t=n?i:t;var r=aj(e,E,i,i,i,i,i,t);return r.placeholder=uf.placeholder,r}function ud(e,t,n){var r,a,o,s,c,l,f=0,d=!1,h=!1,p=!0;if("function"!=typeof e)throw new e9(u);function b(t){var n=r,o=a;return r=a=i,f=t,s=e.apply(o,n)}function m(e){return f=e,c=o_(y,t),d?b(e):s}function g(e){var n=e-l,r=e-f,i=t-n;return h?tU(i,o-r):i}function v(e){var n=e-l,r=e-f;return l===i||n>=t||n<0||h&&r>=o}function y(){var e=ui();if(v(e))return w(e);c=o_(y,g(e))}function w(e){return(c=i,p&&r)?b(e):(r=a=i,s)}function _(){c!==i&&i7(c),f=0,r=l=a=c=i}function E(){return c===i?s:w(ui())}function S(){var e=ui(),n=v(e);if(r=arguments,a=this,l=e,n){if(c===i)return m(l);if(h)return i7(c),c=o_(y,t),b(l)}return c===i&&(c=o_(y,t)),s}return t=cm(t)||0,u1(n)&&(d=!!n.leading,o=(h="maxWait"in n)?tB(cm(n.maxWait)||0,t):o,p="trailing"in n?!!n.trailing:p),S.cancel=_,S.flush=E,S}var uh=ij(function(e,t){return rG(e,1,t)}),up=ij(function(e,t,n){return rG(e,cm(t)||0,n)});function ub(e){return aj(e,M)}function um(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new e9(u);var n=function(){var r=arguments,i=t?t.apply(this,r):r[0],a=n.cache;if(a.has(i))return a.get(i);var o=e.apply(this,r);return n.cache=a.set(i,o)||a,o};return n.cache=new(um.Cache||rp),n}function ug(e){if("function"!=typeof e)throw new e9(u);return function(){var t=arguments;switch(t.length){case 0:return!e.call(this);case 1:return!e.call(this,t[0]);case 2:return!e.call(this,t[0],t[1]);case 3:return!e.call(this,t[0],t[1],t[2])}return!e.apply(this,t)}}function uv(e){return us(2,e)}um.Cache=rp;var uy=i9(function(e,t){var n=(t=1==t.length&&uF(t[0])?t8(t[0],nw(aZ())):t8(rQ(t,1),nw(aZ()))).length;return ij(function(r){for(var i=-1,a=tU(r.length,n);++i=t}),uj=io(function(){return arguments}())?io:function(e){return u0(e)&&tr.call(e,"callee")&&!tm.call(e,"callee")},uF=eF.isArray,uY=tV?nw(tV):is;function uB(e){return null!=e&&uQ(e.length)&&!uX(e)}function uU(e){return u0(e)&&uB(e)}function uH(e){return!0===e||!1===e||u0(e)&&r9(e)==W}var u$=tN||l4,uz=tq?nw(tq):iu;function uG(e){return u0(e)&&1===e.nodeType&&!ce(e)}function uW(e){if(null==e)return!0;if(uB(e)&&(uF(e)||"string"==typeof e||"function"==typeof e.splice||u$(e)||co(e)||uj(e)))return!e.length;var t=a3(e);if(t==J||t==ea)return!e.size;if(oc(e))return!iy(e).length;for(var n in e)if(tr.call(e,n))return!1;return!0}function uK(e,t){return ic(e,t)}function uV(e,t,n){var r=(n="function"==typeof n?n:i)?n(e,t):i;return r===i?ic(e,t,i,n):!!r}function uq(e){if(!u0(e))return!1;var t=r9(e);return t==q||t==V||"string"==typeof e.message&&"string"==typeof e.name&&!ce(e)}function uZ(e){return"number"==typeof e&&tP(e)}function uX(e){if(!u1(e))return!1;var t=r9(e);return t==Z||t==X||t==G||t==er}function uJ(e){return"number"==typeof e&&e==cp(e)}function uQ(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=R}function u1(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}function u0(e){return null!=e&&"object"==typeof e}var u2=tZ?nw(tZ):id;function u3(e,t){return e===t||ih(e,t,aJ(t))}function u4(e,t,n){return n="function"==typeof n?n:i,ih(e,t,aJ(t),n)}function u5(e){return u7(e)&&e!=+e}function u6(e){if(ou(e))throw new e0(s);return ip(e)}function u9(e){return null===e}function u8(e){return null==e}function u7(e){return"number"==typeof e||u0(e)&&r9(e)==Q}function ce(e){if(!u0(e)||r9(e)!=et)return!1;var t=tp(e);if(null===t)return!0;var n=tr.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&tn.call(n)==ts}var ct=tX?nw(tX):ib;function cn(e){return uJ(e)&&e>=-R&&e<=R}var cr=tJ?nw(tJ):im;function ci(e){return"string"==typeof e||!uF(e)&&u0(e)&&r9(e)==eo}function ca(e){return"symbol"==typeof e||u0(e)&&r9(e)==es}var co=tQ?nw(tQ):ig;function cs(e){return e===i}function cu(e){return u0(e)&&a3(e)==ec}function cc(e){return u0(e)&&r9(e)==el}var cl=aI(i_),cf=aI(function(e,t){return e<=t});function cd(e){if(!e)return[];if(uB(e))return ci(e)?nU(e):al(e);if(ty&&e[ty])return nI(e[ty]());var t=a3(e);return(t==J?nD:t==ea?nR:c9)(e)}function ch(e){return e?(e=cm(e))===P||e===-P?(e<0?-1:1)*j:e==e?e:0:0===e?e:0}function cp(e){var t=ch(e),n=t%1;return t==t?n?t-n:t:0}function cb(e){return e?rU(cp(e),0,Y):0}function cm(e){if("number"==typeof e)return e;if(ca(e))return F;if(u1(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=u1(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=ny(e);var n=eV.test(e);return n||eZ.test(e)?tY(e.slice(2),n?2:8):eK.test(e)?F:+e}function cg(e){return af(e,c$(e))}function cv(e){return e?rU(cp(e),-R,R):0===e?e:0}function cy(e){return null==e?"":iZ(e)}var cw=ab(function(e,t){if(oc(t)||uB(t)){af(t,cH(t),e);return}for(var n in t)tr.call(t,n)&&rN(e,n,t[n])}),c_=ab(function(e,t){af(t,c$(t),e)}),cE=ab(function(e,t,n,r){af(t,c$(t),e,r)}),cS=ab(function(e,t,n,r){af(t,cH(t),e,r)}),ck=az(rB);function cx(e,t){var n=n5(e);return null==t?n:rj(n,t)}var cT=ij(function(e,t){e=e4(e);var n=-1,r=t.length,a=r>2?t[2]:i;for(a&&or(t[0],t[1],a)&&(r=1);++n1),t}),af(e,aW(e),n),r&&(n=rH(n,h|p|b,aB));for(var i=t.length;i--;)iJ(n,t[i]);return n});function cq(e,t){return cX(e,ug(aZ(t)))}var cZ=az(function(e,t){return null==e?{}:iA(e,t)});function cX(e,t){if(null==e)return{};var n=t8(aW(e),function(e){return[e]});return t=aZ(t),iL(e,n,function(e,n){return t(e,n[0])})}function cJ(e,t,n){t=i6(t,e);var r=-1,a=t.length;for(a||(a=1,e=i);++rt){var r=e;e=t,t=r}if(n||e%1||t%1){var a=tW();return tU(e+a*(t-e+tF("1e-"+((a+"").length-1))),t)}return iN(e,t)}var ln=aw(function(e,t,n){return t=t.toLowerCase(),e+(n?lr(t):t)});function lr(e){return lL(cy(e).toLowerCase())}function li(e){return(e=cy(e))&&e.replace(eJ,nT).replace(tx,"")}function la(e,t,n){e=cy(e),t=iZ(t);var r=e.length,a=n=n===i?r:rU(cp(n),0,r);return(n-=t.length)>=0&&e.slice(n,a)==t}function lo(e){return(e=cy(e))&&eO.test(e)?e.replace(eT,nM):e}function ls(e){return(e=cy(e))&&eR.test(e)?e.replace(eP,"\\$&"):e}var lu=aw(function(e,t,n){return e+(n?"-":"")+t.toLowerCase()}),lc=aw(function(e,t,n){return e+(n?" ":"")+t.toLowerCase()}),ll=ay("toLowerCase");function lf(e,t,n){e=cy(e);var r=(t=cp(t))?nB(e):0;if(!t||r>=t)return e;var i=(t-r)/2;return aA(tO(i),n)+e+aA(tM(i),n)}function ld(e,t,n){e=cy(e);var r=(t=cp(t))?nB(e):0;return t&&r>>0)?(e=cy(e))&&("string"==typeof t||null!=t&&!ct(t))&&!(t=iZ(t))&&nL(e)?i8(nU(e),0,n):e.split(t,n):[]}var ly=aw(function(e,t,n){return e+(n?" ":"")+lL(t)});function lw(e,t,n){return e=cy(e),n=null==n?0:rU(cp(n),0,e.length),t=iZ(t),e.slice(n,n+t.length)==t}function l_(e,t,n){var r=n4.templateSettings;n&&or(e,t,n)&&(t=i),e=cy(e),t=cE({},t,r,aF);var a,o,s=cE({},t.imports,r.imports,aF),u=cH(s),l=n_(s,u),f=0,d=t.interpolate||eQ,h="__p += '",p=e5((t.escape||eQ).source+"|"+d.source+"|"+(d===eC?eG:eQ).source+"|"+(t.evaluate||eQ).source+"|$","g"),b="//# sourceURL="+(tr.call(t,"sourceURL")?(t.sourceURL+"").replace(/\s/g," "):"lodash.templateSources["+ ++tC+"]")+"\n";e.replace(p,function(t,n,r,i,s,u){return r||(r=i),h+=e.slice(f,u).replace(e1,nO),n&&(a=!0,h+="' +\n__e("+n+") +\n'"),s&&(o=!0,h+="';\n"+s+";\n__p += '"),r&&(h+="' +\n((__t = ("+r+")) == null ? '' : __t) +\n'"),f=u+t.length,t}),h+="';\n";var m=tr.call(t,"variable")&&t.variable;if(m){if(e$.test(m))throw new e0(c)}else h="with (obj) {\n"+h+"\n}\n";h=(o?h.replace(eE,""):h).replace(eS,"$1").replace(ek,"$1;"),h="function("+(m||"obj")+") {\n"+(m?"":"obj || (obj = {});\n")+"var __t, __p = ''"+(a?", __e = _.escape":"")+(o?", __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n":";\n")+h+"return __p\n}";var g=lI(function(){return e2(u,b+"return "+h).apply(i,l)});if(g.source=h,uq(g))throw g;return g}function lE(e){return cy(e).toLowerCase()}function lS(e){return cy(e).toUpperCase()}function lk(e,t,n){if((e=cy(e))&&(n||t===i))return ny(e);if(!e||!(t=iZ(t)))return e;var r=nU(e),a=nU(t),o=nS(r,a),s=nk(r,a)+1;return i8(r,o,s).join("")}function lx(e,t,n){if((e=cy(e))&&(n||t===i))return e.slice(0,nH(e)+1);if(!e||!(t=iZ(t)))return e;var r=nU(e),a=nk(r,nU(t))+1;return i8(r,0,a).join("")}function lT(e,t,n){if((e=cy(e))&&(n||t===i))return e.replace(ej,"");if(!e||!(t=iZ(t)))return e;var r=nU(e),a=nS(r,nU(t));return i8(r,a).join("")}function lM(e,t){var n=O,r=A;if(u1(t)){var a="separator"in t?t.separator:a;n="length"in t?cp(t.length):n,r="omission"in t?iZ(t.omission):r}var o=(e=cy(e)).length;if(nL(e)){var s=nU(e);o=s.length}if(n>=o)return e;var u=n-nB(r);if(u<1)return r;var c=s?i8(s,0,u).join(""):e.slice(0,u);if(a===i)return c+r;if(s&&(u+=c.length-u),ct(a)){if(e.slice(u).search(a)){var l,f=c;for(a.global||(a=e5(a.source,cy(eW.exec(a))+"g")),a.lastIndex=0;l=a.exec(f);)var d=l.index;c=c.slice(0,d===i?u:d)}}else if(e.indexOf(iZ(a),u)!=u){var h=c.lastIndexOf(a);h>-1&&(c=c.slice(0,h))}return c+r}function lO(e){return(e=cy(e))&&eM.test(e)?e.replace(ex,n$):e}var lA=aw(function(e,t,n){return e+(n?" ":"")+t.toUpperCase()}),lL=ay("toUpperCase");function lC(e,t,n){return(e=cy(e),i===(t=n?i:t))?nC(e)?nW(e):na(e):e.match(t)||[]}var lI=ij(function(e,t){try{return t1(e,i,t)}catch(n){return uq(n)?n:new e0(n)}}),lD=az(function(e,t){return t2(t,function(t){t=oM(t),rY(e,t,uu(e[t],e))}),e});function lN(e){var t=null==e?0:e.length,n=aZ();return e=t?t8(e,function(e){if("function"!=typeof e[1])throw new e9(u);return[n(e[0]),e[1]]}):[],ij(function(n){for(var r=-1;++rR)return[];var n=Y,r=tU(e,Y);t=aZ(t),e-=Y;for(var i=ng(r,t);++n0||t<0)?new n8(n):(e<0?n=n.takeRight(-e):e&&(n=n.drop(e)),t!==i&&(n=(t=cp(t))<0?n.dropRight(-t):n.take(t-e)),n)},n8.prototype.takeRightWhile=function(e){return this.reverse().takeWhile(e).reverse()},n8.prototype.toArray=function(){return this.take(Y)},r2(n8.prototype,function(e,t){var n=/^(?:filter|find|map|reject)|While$/.test(t),r=/^(?:head|last)$/.test(t),a=n4[r?"take"+("last"==t?"Right":""):t],o=r||/^find/.test(t);a&&(n4.prototype[t]=function(){var t=this.__wrapped__,s=r?[1]:arguments,u=t instanceof n8,c=s[0],l=u||uF(t),f=function(e){var t=a.apply(n4,t7([e],s));return r&&d?t[0]:t};l&&n&&"function"==typeof c&&1!=c.length&&(u=l=!1);var d=this.__chain__,h=!!this.__actions__.length,p=o&&!d,b=u&&!h;if(!o&&l){t=b?t:new n8(this);var m=e.apply(t,s);return m.__actions__.push({func:sD,args:[f],thisArg:i}),new n9(m,d)}return p&&b?e.apply(this,s):(m=this.thru(f),p?r?m.value()[0]:m.value():m)})}),t2(["pop","push","shift","sort","splice","unshift"],function(e){var t=e8[e],n=/^(?:push|sort|unshift)$/.test(e)?"tap":"thru",r=/^(?:pop|shift)$/.test(e);n4.prototype[e]=function(){var e=arguments;if(r&&!this.__chain__){var i=this.value();return t.apply(uF(i)?i:[],e)}return this[n](function(n){return t.apply(uF(n)?n:[],e)})}}),r2(n8.prototype,function(e,t){var n=n4[t];if(n){var r=n.name+"";tr.call(nq,r)||(nq[r]=[]),nq[r].push({name:t,func:n})}}),nq[ax(i,y).name]=[{name:"wrapper",func:i}],n8.prototype.clone=n7,n8.prototype.reverse=re,n8.prototype.value=rt,n4.prototype.at=sN,n4.prototype.chain=sP,n4.prototype.commit=sR,n4.prototype.next=sj,n4.prototype.plant=sY,n4.prototype.reverse=sB,n4.prototype.toJSON=n4.prototype.valueOf=n4.prototype.value=sU,n4.prototype.first=n4.prototype.head,ty&&(n4.prototype[ty]=sF),n4}();tH._=nK,i!==(r=(function(){return nK}).call(t,n,t,e))&&(e.exports=r)}).call(this)},35161(e,t,n){var r=n(29932),i=n(67206),a=n(69199),o=n(1469);function s(e,t){return(o(e)?r:a)(e,i(t,3))}e.exports=s},67523(e,t,n){var r=n(89465),i=n(47816),a=n(67206);function o(e,t){var n={};return t=a(t,3),i(e,function(e,i,a){r(n,t(e,i,a),e)}),n}e.exports=o},66604(e,t,n){var r=n(89465),i=n(47816),a=n(67206);function o(e,t){var n={};return t=a(t,3),i(e,function(e,i,a){r(n,i,t(e,i,a))}),n}e.exports=o},88306(e,t,n){var r=n(83369),i="Expected a function";function a(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw TypeError(i);var n=function(){var r=arguments,i=t?t.apply(this,r):r[0],a=n.cache;if(a.has(i))return a.get(i);var o=e.apply(this,r);return n.cache=a.set(i,o)||a,o};return n.cache=new(a.Cache||r),n}a.Cache=r,e.exports=a},82492(e,t,n){var r=n(42980),i=n(21463)(function(e,t,n){r(e,t,n)});e.exports=i},50308(e){function t(){}e.exports=t},7771(e,t,n){var r=n(55639),i=function(){return r.Date.now()};e.exports=i},78718(e,t,n){var r=n(25970),i=n(99021)(function(e,t){return null==e?{}:r(e,t)});e.exports=i},39601(e,t,n){var r=n(40371),i=n(79152),a=n(15403),o=n(40327);function s(e){return a(e)?r(o(e)):i(e)}e.exports=s},54061(e,t,n){var r=n(62663),i=n(89881),a=n(67206),o=n(10107),s=n(1469);function u(e,t,n){var u=s(e)?r:o,c=arguments.length<3;return u(e,a(t,4),n,c,i)}e.exports=u},84238(e,t,n){var r=n(280),i=n(64160),a=n(98612),o=n(47037),s=n(88016),u="[object Map]",c="[object Set]";function l(e){if(null==e)return 0;if(a(e))return o(e)?s(e):e.length;var t=i(e);return t==u||t==c?e.size:r(e).length}e.exports=l},11865(e,t,n){var r=n(35393)(function(e,t,n){return e+(n?"_":"")+t.toLowerCase()});e.exports=r},70479(e){function t(){return[]}e.exports=t},95062(e){function t(){return!1}e.exports=t},14841(e,t,n){var r=n(27561),i=n(13218),a=n(33448),o=0/0,s=/^[-+]0x[0-9a-f]+$/i,u=/^0b[01]+$/i,c=/^0o[0-7]+$/i,l=parseInt;function f(e){if("number"==typeof e)return e;if(a(e))return o;if(i(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=i(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var n=u.test(e);return n||c.test(e)?l(e.slice(2),n?2:8):s.test(e)?o:+e}e.exports=f},59881(e,t,n){var r=n(98363),i=n(81704);function a(e){return r(e,i(e))}e.exports=a},79833(e,t,n){var r=n(80531);function i(e){return null==e?"":r(e)}e.exports=i},68718(e,t,n){var r=n(77412),i=n(3118),a=n(47816),o=n(67206),s=n(85924),u=n(1469),c=n(44144),l=n(23560),f=n(13218),d=n(36719);function h(e,t,n){var h=u(e),p=h||c(e)||d(e);if(t=o(t,4),null==n){var b=e&&e.constructor;n=p?h?new b:[]:f(e)&&l(b)?i(s(e)):{}}return(p?r:a)(e,function(e,r,i){return t(n,e,r,i)}),n}e.exports=h},93386(e,t,n){var r=n(21078),i=n(5976),a=n(45652),o=n(29246),s=i(function(e){return a(r(e,1,o,!0))});e.exports=s},11700(e,t,n){var r=n(98805)("toUpperCase");e.exports=r},52628(e,t,n){var r=n(47415),i=n(3674);function a(e){return null==e?[]:r(e,i(e))}e.exports=a},58748(e,t,n){var r=n(49029),i=n(93157),a=n(79833),o=n(2757);function s(e,t,n){return(e=a(e),void 0===(t=n?void 0:t))?i(e)?o(e):r(e):e.match(t)||[]}e.exports=s},42786:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("af",{months:"Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des".split("_"),weekdays:"Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag".split("_"),weekdaysShort:"Son_Maa_Din_Woe_Don_Vry_Sat".split("_"),weekdaysMin:"So_Ma_Di_Wo_Do_Vr_Sa".split("_"),meridiemParse:/vm|nm/i,isPM:function(e){return/^nm$/i.test(e)},meridiem:function(e,t,n){return e<12?n?"vm":"VM":n?"nm":"NM"},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Vandag om] LT",nextDay:"[M\xf4re om] LT",nextWeek:"dddd [om] LT",lastDay:"[Gister om] LT",lastWeek:"[Laas] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oor %s",past:"%s gelede",s:"'n paar sekondes",ss:"%d sekondes",m:"'n minuut",mm:"%d minute",h:"'n uur",hh:"%d ure",d:"'n dag",dd:"%d dae",M:"'n maand",MM:"%d maande",y:"'n jaar",yy:"%d jaar"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}})})(n(30381))},14130:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t=function(e){return 0===e?0:1===e?1:2===e?2:e%100>=3&&e%100<=10?3:e%100>=11?4:5},n={s:["أقل من ثانية","ثانية واحدة",["ثانيتان","ثانيتين"],"%d ثوان","%d ثانية","%d ثانية",],m:["أقل من دقيقة","دقيقة واحدة",["دقيقتان","دقيقتين"],"%d دقائق","%d دقيقة","%d دقيقة",],h:["أقل من ساعة","ساعة واحدة",["ساعتان","ساعتين"],"%d ساعات","%d ساعة","%d ساعة",],d:["أقل من يوم","يوم واحد",["يومان","يومين"],"%d أيام","%d يومًا","%d يوم",],M:["أقل من شهر","شهر واحد",["شهران","شهرين"],"%d أشهر","%d شهرا","%d شهر",],y:["أقل من عام","عام واحد",["عامان","عامين"],"%d أعوام","%d عامًا","%d عام",]},r=function(e){return function(r,i,a,o){var s=t(r),u=n[e][t(r)];return 2===s&&(u=u[i?0:1]),u.replace(/%d/i,r)}},i=["جانفي","فيفري","مارس","أفريل","ماي","جوان","جويلية","أوت","سبتمبر","أكتوبر","نوفمبر","ديسمبر",];return e.defineLocale("ar-dz",{months:i,monthsShort:i,weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/‏M/‏YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/ص|م/,isPM:function(e){return"م"===e},meridiem:function(e,t,n){return e<12?"ص":"م"},calendar:{sameDay:"[اليوم عند الساعة] LT",nextDay:"[غدًا عند الساعة] LT",nextWeek:"dddd [عند الساعة] LT",lastDay:"[أمس عند الساعة] LT",lastWeek:"dddd [عند الساعة] LT",sameElse:"L"},relativeTime:{future:"بعد %s",past:"منذ %s",s:r("s"),ss:r("s"),m:r("m"),mm:r("m"),h:r("h"),hh:r("h"),d:r("d"),dd:r("d"),M:r("M"),MM:r("M"),y:r("y"),yy:r("y")},postformat:function(e){return e.replace(/,/g,"،")},week:{dow:0,doy:4}})})(n(30381))},96135:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ar-kw",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:0,doy:12}})})(n(30381))},56440:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",0:"0"},n=function(e){return 0===e?0:1===e?1:2===e?2:e%100>=3&&e%100<=10?3:e%100>=11?4:5},r={s:["أقل من ثانية","ثانية واحدة",["ثانيتان","ثانيتين"],"%d ثوان","%d ثانية","%d ثانية",],m:["أقل من دقيقة","دقيقة واحدة",["دقيقتان","دقيقتين"],"%d دقائق","%d دقيقة","%d دقيقة",],h:["أقل من ساعة","ساعة واحدة",["ساعتان","ساعتين"],"%d ساعات","%d ساعة","%d ساعة",],d:["أقل من يوم","يوم واحد",["يومان","يومين"],"%d أيام","%d يومًا","%d يوم",],M:["أقل من شهر","شهر واحد",["شهران","شهرين"],"%d أشهر","%d شهرا","%d شهر",],y:["أقل من عام","عام واحد",["عامان","عامين"],"%d أعوام","%d عامًا","%d عام",]},i=function(e){return function(t,i,a,o){var s=n(t),u=r[e][n(t)];return 2===s&&(u=u[i?0:1]),u.replace(/%d/i,t)}},a=["يناير","فبراير","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوفمبر","ديسمبر",];return e.defineLocale("ar-ly",{months:a,monthsShort:a,weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/‏M/‏YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/ص|م/,isPM:function(e){return"م"===e},meridiem:function(e,t,n){return e<12?"ص":"م"},calendar:{sameDay:"[اليوم عند الساعة] LT",nextDay:"[غدًا عند الساعة] LT",nextWeek:"dddd [عند الساعة] LT",lastDay:"[أمس عند الساعة] LT",lastWeek:"dddd [عند الساعة] LT",sameElse:"L"},relativeTime:{future:"بعد %s",past:"منذ %s",s:i("s"),ss:i("s"),m:i("m"),mm:i("m"),h:i("h"),hh:i("h"),d:i("d"),dd:i("d"),M:i("M"),MM:i("M"),y:i("y"),yy:i("y")},preparse:function(e){return e.replace(/،/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]}).replace(/,/g,"،")},week:{dow:6,doy:12}})})(n(30381))},47702:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ar-ma",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اثنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:1,doy:4}})})(n(30381))},16040:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"١",2:"٢",3:"٣",4:"٤",5:"٥",6:"٦",7:"٧",8:"٨",9:"٩",0:"٠"},n={"١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9","٠":"0"};return e.defineLocale("ar-sa",{months:"يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"),weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/ص|م/,isPM:function(e){return"م"===e},meridiem:function(e,t,n){return e<12?"ص":"م"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},preparse:function(e){return e.replace(/[١٢٣٤٥٦٧٨٩٠]/g,function(e){return n[e]}).replace(/،/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]}).replace(/,/g,"،")},week:{dow:0,doy:6}})})(n(30381))},37100:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ar-tn",{months:"جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"),monthsShort:"جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"),weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:1,doy:4}})})(n(30381))},30867:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"١",2:"٢",3:"٣",4:"٤",5:"٥",6:"٦",7:"٧",8:"٨",9:"٩",0:"٠"},n={"١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9","٠":"0"},r=function(e){return 0===e?0:1===e?1:2===e?2:e%100>=3&&e%100<=10?3:e%100>=11?4:5},i={s:["أقل من ثانية","ثانية واحدة",["ثانيتان","ثانيتين"],"%d ثوان","%d ثانية","%d ثانية",],m:["أقل من دقيقة","دقيقة واحدة",["دقيقتان","دقيقتين"],"%d دقائق","%d دقيقة","%d دقيقة",],h:["أقل من ساعة","ساعة واحدة",["ساعتان","ساعتين"],"%d ساعات","%d ساعة","%d ساعة",],d:["أقل من يوم","يوم واحد",["يومان","يومين"],"%d أيام","%d يومًا","%d يوم",],M:["أقل من شهر","شهر واحد",["شهران","شهرين"],"%d أشهر","%d شهرا","%d شهر",],y:["أقل من عام","عام واحد",["عامان","عامين"],"%d أعوام","%d عامًا","%d عام",]},a=function(e){return function(t,n,a,o){var s=r(t),u=i[e][r(t)];return 2===s&&(u=u[n?0:1]),u.replace(/%d/i,t)}},o=["يناير","فبراير","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوفمبر","ديسمبر",];return e.defineLocale("ar",{months:o,monthsShort:o,weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/‏M/‏YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/ص|م/,isPM:function(e){return"م"===e},meridiem:function(e,t,n){return e<12?"ص":"م"},calendar:{sameDay:"[اليوم عند الساعة] LT",nextDay:"[غدًا عند الساعة] LT",nextWeek:"dddd [عند الساعة] LT",lastDay:"[أمس عند الساعة] LT",lastWeek:"dddd [عند الساعة] LT",sameElse:"L"},relativeTime:{future:"بعد %s",past:"منذ %s",s:a("s"),ss:a("s"),m:a("m"),mm:a("m"),h:a("h"),hh:a("h"),d:a("d"),dd:a("d"),M:a("M"),MM:a("M"),y:a("y"),yy:a("y")},preparse:function(e){return e.replace(/[١٢٣٤٥٦٧٨٩٠]/g,function(e){return n[e]}).replace(/،/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]}).replace(/,/g,"،")},week:{dow:6,doy:12}})})(n(30381))},31083:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"-inci",5:"-inci",8:"-inci",70:"-inci",80:"-inci",2:"-nci",7:"-nci",20:"-nci",50:"-nci",3:"-\xfcnc\xfc",4:"-\xfcnc\xfc",100:"-\xfcnc\xfc",6:"-ncı",9:"-uncu",10:"-uncu",30:"-uncu",60:"-ıncı",90:"-ıncı"};return e.defineLocale("az",{months:"yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr".split("_"),monthsShort:"yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek".split("_"),weekdays:"Bazar_Bazar ertəsi_\xc7ərşənbə axşamı_\xc7ərşənbə_C\xfcmə axşamı_C\xfcmə_Şənbə".split("_"),weekdaysShort:"Baz_BzE_\xc7Ax_\xc7ər_CAx_C\xfcm_Şən".split("_"),weekdaysMin:"Bz_BE_\xc7A_\xc7ə_CA_C\xfc_Şə".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bug\xfcn saat] LT",nextDay:"[sabah saat] LT",nextWeek:"[gələn həftə] dddd [saat] LT",lastDay:"[d\xfcnən] LT",lastWeek:"[ke\xe7ən həftə] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s əvvəl",s:"bir ne\xe7ə saniyə",ss:"%d saniyə",m:"bir dəqiqə",mm:"%d dəqiqə",h:"bir saat",hh:"%d saat",d:"bir g\xfcn",dd:"%d g\xfcn",M:"bir ay",MM:"%d ay",y:"bir il",yy:"%d il"},meridiemParse:/gecə|səhər|gündüz|axşam/,isPM:function(e){return/^(gündüz|axşam)$/.test(e)},meridiem:function(e,t,n){return e<4?"gecə":e<12?"səhər":e<17?"g\xfcnd\xfcz":"axşam"},dayOfMonthOrdinalParse:/\d{1,2}-(ıncı|inci|nci|üncü|ncı|uncu)/,ordinal:function(e){if(0===e)return e+"-ıncı";var n=e%10,r=e%100-n,i=e>=100?100:null;return e+(t[n]||t[r]||t[i])},week:{dow:1,doy:7}})})(n(30381))},9808:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t){var n=e.split("_");return t%10==1&&t%100!=11?n[0]:t%10>=2&&t%10<=4&&(t%100<10||t%100>=20)?n[1]:n[2]}function n(e,n,r){var i={ss:n?"секунда_секунды_секунд":"секунду_секунды_секунд",mm:n?"хвіліна_хвіліны_хвілін":"хвіліну_хвіліны_хвілін",hh:n?"гадзіна_гадзіны_гадзін":"гадзіну_гадзіны_гадзін",dd:"дзень_дні_дзён",MM:"месяц_месяцы_месяцаў",yy:"год_гады_гадоў"};return"m"===r?n?"хвіліна":"хвіліну":"h"===r?n?"гадзіна":"гадзіну":e+" "+t(i[r],+e)}return e.defineLocale("be",{months:{format:"студзеня_лютага_сакавіка_красавіка_траўня_чэрвеня_ліпеня_жніўня_верасня_кастрычніка_лістапада_снежня".split("_"),standalone:"студзень_люты_сакавік_красавік_травень_чэрвень_ліпень_жнівень_верасень_кастрычнік_лістапад_снежань".split("_")},monthsShort:"студ_лют_сак_крас_трав_чэрв_ліп_жнів_вер_каст_ліст_снеж".split("_"),weekdays:{format:"нядзелю_панядзелак_аўторак_сераду_чацвер_пятніцу_суботу".split("_"),standalone:"нядзеля_панядзелак_аўторак_серада_чацвер_пятніца_субота".split("_"),isFormat:/\[ ?[Ууў] ?(?:мінулую|наступную)? ?\] ?dddd/},weekdaysShort:"нд_пн_ат_ср_чц_пт_сб".split("_"),weekdaysMin:"нд_пн_ат_ср_чц_пт_сб".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., HH:mm",LLLL:"dddd, D MMMM YYYY г., HH:mm"},calendar:{sameDay:"[Сёння ў] LT",nextDay:"[Заўтра ў] LT",lastDay:"[Учора ў] LT",nextWeek:function(){return"[У] dddd [ў] LT"},lastWeek:function(){switch(this.day()){case 0:case 3:case 5:case 6:return"[У мінулую] dddd [ў] LT";case 1:case 2:case 4:return"[У мінулы] dddd [ў] LT"}},sameElse:"L"},relativeTime:{future:"праз %s",past:"%s таму",s:"некалькі секунд",m:n,mm:n,h:n,hh:n,d:"дзень",dd:n,M:"месяц",MM:n,y:"год",yy:n},meridiemParse:/ночы|раніцы|дня|вечара/,isPM:function(e){return/^(дня|вечара)$/.test(e)},meridiem:function(e,t,n){return e<4?"ночы":e<12?"раніцы":e<17?"дня":"вечара"},dayOfMonthOrdinalParse:/\d{1,2}-(і|ы|га)/,ordinal:function(e,t){switch(t){case"M":case"d":case"DDD":case"w":case"W":return(e%10==2||e%10==3)&&e%100!=12&&e%100!=13?e+"-і":e+"-ы";case"D":return e+"-га";default:return e}},week:{dow:1,doy:7}})})(n(30381))},68338:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("bg",{months:"януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември".split("_"),monthsShort:"яну_фев_мар_апр_май_юни_юли_авг_сеп_окт_ное_дек".split("_"),weekdays:"неделя_понеделник_вторник_сряда_четвъртък_петък_събота".split("_"),weekdaysShort:"нед_пон_вто_сря_чет_пет_съб".split("_"),weekdaysMin:"нд_пн_вт_ср_чт_пт_сб".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[Днес в] LT",nextDay:"[Утре в] LT",nextWeek:"dddd [в] LT",lastDay:"[Вчера в] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[Миналата] dddd [в] LT";case 1:case 2:case 4:case 5:return"[Миналия] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"след %s",past:"преди %s",s:"няколко секунди",ss:"%d секунди",m:"минута",mm:"%d минути",h:"час",hh:"%d часа",d:"ден",dd:"%d дена",w:"седмица",ww:"%d седмици",M:"месец",MM:"%d месеца",y:"година",yy:"%d години"},dayOfMonthOrdinalParse:/\d{1,2}-(ев|ен|ти|ви|ри|ми)/,ordinal:function(e){var t=e%10,n=e%100;if(0===e)return e+"-ев";if(0===n)return e+"-ен";if(n>10&&n<20)return e+"-ти";if(1===t)return e+"-ви";if(2===t)return e+"-ри";else if(7===t||8===t)return e+"-ми";else return e+"-ти"},week:{dow:1,doy:7}})})(n(30381))},67438:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("bm",{months:"Zanwuyekalo_Fewuruyekalo_Marisikalo_Awirilikalo_Mɛkalo_Zuwɛnkalo_Zuluyekalo_Utikalo_Sɛtanburukalo_ɔkutɔburukalo_Nowanburukalo_Desanburukalo".split("_"),monthsShort:"Zan_Few_Mar_Awi_Mɛ_Zuw_Zul_Uti_Sɛt_ɔku_Now_Des".split("_"),weekdays:"Kari_Ntɛnɛn_Tarata_Araba_Alamisa_Juma_Sibiri".split("_"),weekdaysShort:"Kar_Ntɛ_Tar_Ara_Ala_Jum_Sib".split("_"),weekdaysMin:"Ka_Nt_Ta_Ar_Al_Ju_Si".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"MMMM [tile] D [san] YYYY",LLL:"MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm",LLLL:"dddd MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm"},calendar:{sameDay:"[Bi lɛrɛ] LT",nextDay:"[Sini lɛrɛ] LT",nextWeek:"dddd [don lɛrɛ] LT",lastDay:"[Kunu lɛrɛ] LT",lastWeek:"dddd [tɛmɛnen lɛrɛ] LT",sameElse:"L"},relativeTime:{future:"%s kɔnɔ",past:"a bɛ %s bɔ",s:"sanga dama dama",ss:"sekondi %d",m:"miniti kelen",mm:"miniti %d",h:"lɛrɛ kelen",hh:"lɛrɛ %d",d:"tile kelen",dd:"tile %d",M:"kalo kelen",MM:"kalo %d",y:"san kelen",yy:"san %d"},week:{dow:1,doy:4}})})(n(30381))},76225:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"১",2:"২",3:"৩",4:"৪",5:"৫",6:"৬",7:"৭",8:"৮",9:"৯",0:"০"},n={"১":"1","২":"2","৩":"3","৪":"4","৫":"5","৬":"6","৭":"7","৮":"8","৯":"9","০":"0"};return e.defineLocale("bn-bd",{months:"জানুয়ারি_ফেব্রুয়ারি_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর".split("_"),monthsShort:"জানু_ফেব্রু_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্ট_অক্টো_নভে_ডিসে".split("_"),weekdays:"রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পতিবার_শুক্রবার_শনিবার".split("_"),weekdaysShort:"রবি_সোম_মঙ্গল_বুধ_বৃহস্পতি_শুক্র_শনি".split("_"),weekdaysMin:"রবি_সোম_মঙ্গল_বুধ_বৃহ_শুক্র_শনি".split("_"),longDateFormat:{LT:"A h:mm সময়",LTS:"A h:mm:ss সময়",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm সময়",LLLL:"dddd, D MMMM YYYY, A h:mm সময়"},calendar:{sameDay:"[আজ] LT",nextDay:"[আগামীকাল] LT",nextWeek:"dddd, LT",lastDay:"[গতকাল] LT",lastWeek:"[গত] dddd, LT",sameElse:"L"},relativeTime:{future:"%s পরে",past:"%s আগে",s:"কয়েক সেকেন্ড",ss:"%d সেকেন্ড",m:"এক মিনিট",mm:"%d মিনিট",h:"এক ঘন্টা",hh:"%d ঘন্টা",d:"এক দিন",dd:"%d দিন",M:"এক মাস",MM:"%d মাস",y:"এক বছর",yy:"%d বছর"},preparse:function(e){return e.replace(/[১২৩৪৫৬৭৮৯০]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/রাত|ভোর|সকাল|দুপুর|বিকাল|সন্ধ্যা|রাত/,meridiemHour:function(e,t){if(12===e&&(e=0),"রাত"===t)return e<4?e:e+12;if("ভোর"===t)return e;if("সকাল"===t)return e;if("দুপুর"===t)return e>=3?e:e+12;if("বিকাল"===t)return e+12;else if("সন্ধ্যা"===t)return e+12},meridiem:function(e,t,n){if(e<4)return"রাত";if(e<6)return"ভোর";if(e<12)return"সকাল";if(e<15)return"দুপুর";if(e<18)return"বিকাল";else if(e<20)return"সন্ধ্যা";else return"রাত"},week:{dow:0,doy:6}})})(n(30381))},8905:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"১",2:"২",3:"৩",4:"৪",5:"৫",6:"৬",7:"৭",8:"৮",9:"৯",0:"০"},n={"১":"1","২":"2","৩":"3","৪":"4","৫":"5","৬":"6","৭":"7","৮":"8","৯":"9","০":"0"};return e.defineLocale("bn",{months:"জানুয়ারি_ফেব্রুয়ারি_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর".split("_"),monthsShort:"জানু_ফেব্রু_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্ট_অক্টো_নভে_ডিসে".split("_"),weekdays:"রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পতিবার_শুক্রবার_শনিবার".split("_"),weekdaysShort:"রবি_সোম_মঙ্গল_বুধ_বৃহস্পতি_শুক্র_শনি".split("_"),weekdaysMin:"রবি_সোম_মঙ্গল_বুধ_বৃহ_শুক্র_শনি".split("_"),longDateFormat:{LT:"A h:mm সময়",LTS:"A h:mm:ss সময়",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm সময়",LLLL:"dddd, D MMMM YYYY, A h:mm সময়"},calendar:{sameDay:"[আজ] LT",nextDay:"[আগামীকাল] LT",nextWeek:"dddd, LT",lastDay:"[গতকাল] LT",lastWeek:"[গত] dddd, LT",sameElse:"L"},relativeTime:{future:"%s পরে",past:"%s আগে",s:"কয়েক সেকেন্ড",ss:"%d সেকেন্ড",m:"এক মিনিট",mm:"%d মিনিট",h:"এক ঘন্টা",hh:"%d ঘন্টা",d:"এক দিন",dd:"%d দিন",M:"এক মাস",MM:"%d মাস",y:"এক বছর",yy:"%d বছর"},preparse:function(e){return e.replace(/[১২৩৪৫৬৭৮৯০]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/রাত|সকাল|দুপুর|বিকাল|রাত/,meridiemHour:function(e,t){return(12===e&&(e=0),"রাত"===t&&e>=4||"দুপুর"===t&&e<5||"বিকাল"===t)?e+12:e},meridiem:function(e,t,n){return e<4?"রাত":e<10?"সকাল":e<17?"দুপুর":e<20?"বিকাল":"রাত"},week:{dow:0,doy:6}})})(n(30381))},11560:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"༡",2:"༢",3:"༣",4:"༤",5:"༥",6:"༦",7:"༧",8:"༨",9:"༩",0:"༠"},n={"༡":"1","༢":"2","༣":"3","༤":"4","༥":"5","༦":"6","༧":"7","༨":"8","༩":"9","༠":"0"};return e.defineLocale("bo",{months:"ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ".split("_"),monthsShort:"ཟླ་1_ཟླ་2_ཟླ་3_ཟླ་4_ཟླ་5_ཟླ་6_ཟླ་7_ཟླ་8_ཟླ་9_ཟླ་10_ཟླ་11_ཟླ་12".split("_"),monthsShortRegex:/^(ཟླ་\d{1,2})/,monthsParseExact:!0,weekdays:"གཟའ་ཉི་མ་_གཟའ་ཟླ་བ་_གཟའ་མིག་དམར་_གཟའ་ལྷག་པ་_གཟའ་ཕུར་བུ_གཟའ་པ་སངས་_གཟའ་སྤེན་པ་".split("_"),weekdaysShort:"ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་".split("_"),weekdaysMin:"ཉི_ཟླ_མིག_ལྷག_ཕུར_སངས_སྤེན".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[དི་རིང] LT",nextDay:"[སང་ཉིན] LT",nextWeek:"[བདུན་ཕྲག་རྗེས་མ], LT",lastDay:"[ཁ་སང] LT",lastWeek:"[བདུན་ཕྲག་མཐའ་མ] dddd, LT",sameElse:"L"},relativeTime:{future:"%s ལ་",past:"%s སྔན་ལ",s:"ལམ་སང",ss:"%d སྐར་ཆ།",m:"སྐར་མ་གཅིག",mm:"%d སྐར་མ",h:"ཆུ་ཚོད་གཅིག",hh:"%d ཆུ་ཚོད",d:"ཉིན་གཅིག",dd:"%d ཉིན་",M:"ཟླ་བ་གཅིག",MM:"%d ཟླ་བ",y:"ལོ་གཅིག",yy:"%d ལོ"},preparse:function(e){return e.replace(/[༡༢༣༤༥༦༧༨༩༠]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/མཚན་མོ|ཞོགས་ཀས|ཉིན་གུང|དགོང་དག|མཚན་མོ/,meridiemHour:function(e,t){return(12===e&&(e=0),"མཚན་མོ"===t&&e>=4||"ཉིན་གུང"===t&&e<5||"དགོང་དག"===t)?e+12:e},meridiem:function(e,t,n){return e<4?"མཚན་མོ":e<10?"ཞོགས་ཀས":e<17?"ཉིན་གུང":e<20?"དགོང་དག":"མཚན་མོ"},week:{dow:0,doy:6}})})(n(30381))},1278:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n){return e+" "+i({mm:"munutenn",MM:"miz",dd:"devezh"}[n],e)}function n(e){switch(r(e)){case 1:case 3:case 4:case 5:case 9:return e+" bloaz";default:return e+" vloaz"}}function r(e){return e>9?r(e%10):e}function i(e,t){return 2===t?a(e):e}function a(e){var t={m:"v",b:"v",d:"z"};return void 0===t[e.charAt(0)]?e:t[e.charAt(0)]+e.substring(1)}var o=[/^gen/i,/^c[ʼ\']hwe/i,/^meu/i,/^ebr/i,/^mae/i,/^(mez|eve)/i,/^gou/i,/^eos/i,/^gwe/i,/^her/i,/^du/i,/^ker/i,],s=/^(genver|c[ʼ\']hwevrer|meurzh|ebrel|mae|mezheven|gouere|eost|gwengolo|here|du|kerzu|gen|c[ʼ\']hwe|meu|ebr|mae|eve|gou|eos|gwe|her|du|ker)/i,u=/^(genver|c[ʼ\']hwevrer|meurzh|ebrel|mae|mezheven|gouere|eost|gwengolo|here|du|kerzu)/i,c=/^(gen|c[ʼ\']hwe|meu|ebr|mae|eve|gou|eos|gwe|her|du|ker)/i,l=[/^sul/i,/^lun/i,/^meurzh/i,/^merc[ʼ\']her/i,/^yaou/i,/^gwener/i,/^sadorn/i,],f=[/^Sul/i,/^Lun/i,/^Meu/i,/^Mer/i,/^Yao/i,/^Gwe/i,/^Sad/i,],d=[/^Su/i,/^Lu/i,/^Me([^r]|$)/i,/^Mer/i,/^Ya/i,/^Gw/i,/^Sa/i,];return e.defineLocale("br",{months:"Genver_Cʼhwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu".split("_"),monthsShort:"Gen_Cʼhwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker".split("_"),weekdays:"Sul_Lun_Meurzh_Mercʼher_Yaou_Gwener_Sadorn".split("_"),weekdaysShort:"Sul_Lun_Meu_Mer_Yao_Gwe_Sad".split("_"),weekdaysMin:"Su_Lu_Me_Mer_Ya_Gw_Sa".split("_"),weekdaysParse:d,fullWeekdaysParse:l,shortWeekdaysParse:f,minWeekdaysParse:d,monthsRegex:s,monthsShortRegex:s,monthsStrictRegex:u,monthsShortStrictRegex:c,monthsParse:o,longMonthsParse:o,shortMonthsParse:o,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [a viz] MMMM YYYY",LLL:"D [a viz] MMMM YYYY HH:mm",LLLL:"dddd, D [a viz] MMMM YYYY HH:mm"},calendar:{sameDay:"[Hiziv da] LT",nextDay:"[Warcʼhoazh da] LT",nextWeek:"dddd [da] LT",lastDay:"[Decʼh da] LT",lastWeek:"dddd [paset da] LT",sameElse:"L"},relativeTime:{future:"a-benn %s",past:"%s ʼzo",s:"un nebeud segondenno\xf9",ss:"%d eilenn",m:"ur vunutenn",mm:t,h:"un eur",hh:"%d eur",d:"un devezh",dd:t,M:"ur miz",MM:t,y:"ur bloaz",yy:n},dayOfMonthOrdinalParse:/\d{1,2}(añ|vet)/,ordinal:function(e){var t=1===e?"a\xf1":"vet";return e+t},week:{dow:1,doy:4},meridiemParse:/a.m.|g.m./,isPM:function(e){return"g.m."===e},meridiem:function(e,t,n){return e<12?"a.m.":"g.m."}})})(n(30381))},80622:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n){var r=e+" ";switch(n){case"ss":return 1===e?r+="sekunda":2===e||3===e||4===e?r+="sekunde":r+="sekundi",r;case"m":return t?"jedna minuta":"jedne minute";case"mm":return 1===e?r+="minuta":2===e||3===e||4===e?r+="minute":r+="minuta",r;case"h":return t?"jedan sat":"jednog sata";case"hh":return 1===e?r+="sat":2===e||3===e||4===e?r+="sata":r+="sati",r;case"dd":return 1===e?r+="dan":r+="dana",r;case"MM":return 1===e?r+="mjesec":2===e||3===e||4===e?r+="mjeseca":r+="mjeseci",r;case"yy":return 1===e?r+="godina":2===e||3===e||4===e?r+="godine":r+="godina",r}}return e.defineLocale("bs",{months:"januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[jučer u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[prošlu] dddd [u] LT";case 6:return"[prošle] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[prošli] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",ss:t,m:t,mm:t,h:t,hh:t,d:"dan",dd:t,M:"mjesec",MM:t,y:"godinu",yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})(n(30381))},2468:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ca",{months:{standalone:"gener_febrer_mar\xe7_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre".split("_"),format:"de gener_de febrer_de mar\xe7_d'abril_de maig_de juny_de juliol_d'agost_de setembre_d'octubre_de novembre_de desembre".split("_"),isFormat:/D[oD]?(\s)+MMMM/},monthsShort:"gen._febr._mar\xe7_abr._maig_juny_jul._ag._set._oct._nov._des.".split("_"),monthsParseExact:!0,weekdays:"diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte".split("_"),weekdaysShort:"dg._dl._dt._dc._dj._dv._ds.".split("_"),weekdaysMin:"dg_dl_dt_dc_dj_dv_ds".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM [de] YYYY",ll:"D MMM YYYY",LLL:"D MMMM [de] YYYY [a les] H:mm",lll:"D MMM YYYY, H:mm",LLLL:"dddd D MMMM [de] YYYY [a les] H:mm",llll:"ddd D MMM YYYY, H:mm"},calendar:{sameDay:function(){return"[avui a "+(1!==this.hours()?"les":"la")+"] LT"},nextDay:function(){return"[dem\xe0 a "+(1!==this.hours()?"les":"la")+"] LT"},nextWeek:function(){return"dddd [a "+(1!==this.hours()?"les":"la")+"] LT"},lastDay:function(){return"[ahir a "+(1!==this.hours()?"les":"la")+"] LT"},lastWeek:function(){return"[el] dddd [passat a "+(1!==this.hours()?"les":"la")+"] LT"},sameElse:"L"},relativeTime:{future:"d'aqu\xed %s",past:"fa %s",s:"uns segons",ss:"%d segons",m:"un minut",mm:"%d minuts",h:"una hora",hh:"%d hores",d:"un dia",dd:"%d dies",M:"un mes",MM:"%d mesos",y:"un any",yy:"%d anys"},dayOfMonthOrdinalParse:/\d{1,2}(r|n|t|è|a)/,ordinal:function(e,t){var n=1===e?"r":2===e?"n":3===e?"r":4===e?"t":"\xe8";return("w"===t||"W"===t)&&(n="a"),e+n},week:{dow:1,doy:4}})})(n(30381))},5822:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="leden_\xfanor_březen_duben_květen_červen_červenec_srpen_z\xe1ř\xed_ř\xedjen_listopad_prosinec".split("_"),n="led_\xfano_bře_dub_kvě_čvn_čvc_srp_z\xe1ř_ř\xedj_lis_pro".split("_"),r=[/^led/i,/^úno/i,/^bře/i,/^dub/i,/^kvě/i,/^(čvn|červen$|června)/i,/^(čvc|červenec|července)/i,/^srp/i,/^zář/i,/^říj/i,/^lis/i,/^pro/i,],i=/^(leden|únor|březen|duben|květen|červenec|července|červen|června|srpen|září|říjen|listopad|prosinec|led|úno|bře|dub|kvě|čvn|čvc|srp|zář|říj|lis|pro)/i;function a(e){return e>1&&e<5&&1!=~~(e/10)}function o(e,t,n,r){var i=e+" ";switch(n){case"s":return t||r?"p\xe1r sekund":"p\xe1r sekundami";case"ss":if(t||r)return i+(a(e)?"sekundy":"sekund");return i+"sekundami";case"m":return t?"minuta":r?"minutu":"minutou";case"mm":if(t||r)return i+(a(e)?"minuty":"minut");return i+"minutami";case"h":return t?"hodina":r?"hodinu":"hodinou";case"hh":if(t||r)return i+(a(e)?"hodiny":"hodin");return i+"hodinami";case"d":return t||r?"den":"dnem";case"dd":if(t||r)return i+(a(e)?"dny":"dn\xed");return i+"dny";case"M":return t||r?"měs\xedc":"měs\xedcem";case"MM":if(t||r)return i+(a(e)?"měs\xedce":"měs\xedců");return i+"měs\xedci";case"y":return t||r?"rok":"rokem";case"yy":if(t||r)return i+(a(e)?"roky":"let");return i+"lety"}}return e.defineLocale("cs",{months:t,monthsShort:n,monthsRegex:i,monthsShortRegex:i,monthsStrictRegex:/^(leden|ledna|února|únor|březen|března|duben|dubna|květen|května|červenec|července|červen|června|srpen|srpna|září|říjen|října|listopadu|listopad|prosinec|prosince)/i,monthsShortStrictRegex:/^(led|úno|bře|dub|kvě|čvn|čvc|srp|zář|říj|lis|pro)/i,monthsParse:r,longMonthsParse:r,shortMonthsParse:r,weekdays:"neděle_ponděl\xed_\xfater\xfd_středa_čtvrtek_p\xe1tek_sobota".split("_"),weekdaysShort:"ne_po_\xfat_st_čt_p\xe1_so".split("_"),weekdaysMin:"ne_po_\xfat_st_čt_p\xe1_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd D. MMMM YYYY H:mm",l:"D. M. YYYY"},calendar:{sameDay:"[dnes v] LT",nextDay:"[z\xedtra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v neděli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve středu v] LT";case 4:return"[ve čtvrtek v] LT";case 5:return"[v p\xe1tek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[včera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou neděli v] LT";case 1:case 2:return"[minul\xe9] dddd [v] LT";case 3:return"[minulou středu v] LT";case 4:case 5:return"[minul\xfd] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"před %s",s:o,ss:o,m:o,mm:o,h:o,hh:o,d:o,dd:o,M:o,MM:o,y:o,yy:o},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},50877:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("cv",{months:"кӑрлач_нарӑс_пуш_ака_май_ҫӗртме_утӑ_ҫурла_авӑн_юпа_чӳк_раштав".split("_"),monthsShort:"кӑр_нар_пуш_ака_май_ҫӗр_утӑ_ҫур_авн_юпа_чӳк_раш".split("_"),weekdays:"вырсарникун_тунтикун_ытларикун_юнкун_кӗҫнерникун_эрнекун_шӑматкун".split("_"),weekdaysShort:"выр_тун_ытл_юн_кӗҫ_эрн_шӑм".split("_"),weekdaysMin:"вр_тн_ыт_юн_кҫ_эр_шм".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ]",LLL:"YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm",LLLL:"dddd, YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm"},calendar:{sameDay:"[Паян] LT [сехетре]",nextDay:"[Ыран] LT [сехетре]",lastDay:"[Ӗнер] LT [сехетре]",nextWeek:"[Ҫитес] dddd LT [сехетре]",lastWeek:"[Иртнӗ] dddd LT [сехетре]",sameElse:"L"},relativeTime:{future:function(e){var t=/сехет$/i.exec(e)?"рен":/ҫул$/i.exec(e)?"тан":"ран";return e+t},past:"%s каялла",s:"пӗр-ик ҫеккунт",ss:"%d ҫеккунт",m:"пӗр минут",mm:"%d минут",h:"пӗр сехет",hh:"%d сехет",d:"пӗр кун",dd:"%d кун",M:"пӗр уйӑх",MM:"%d уйӑх",y:"пӗр ҫул",yy:"%d ҫул"},dayOfMonthOrdinalParse:/\d{1,2}-мӗш/,ordinal:"%d-мӗш",week:{dow:1,doy:7}})})(n(30381))},47373:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("cy",{months:"Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr".split("_"),monthsShort:"Ion_Chwe_Maw_Ebr_Mai_Meh_Gor_Aws_Med_Hyd_Tach_Rhag".split("_"),weekdays:"Dydd Sul_Dydd Llun_Dydd Mawrth_Dydd Mercher_Dydd Iau_Dydd Gwener_Dydd Sadwrn".split("_"),weekdaysShort:"Sul_Llun_Maw_Mer_Iau_Gwe_Sad".split("_"),weekdaysMin:"Su_Ll_Ma_Me_Ia_Gw_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Heddiw am] LT",nextDay:"[Yfory am] LT",nextWeek:"dddd [am] LT",lastDay:"[Ddoe am] LT",lastWeek:"dddd [diwethaf am] LT",sameElse:"L"},relativeTime:{future:"mewn %s",past:"%s yn \xf4l",s:"ychydig eiliadau",ss:"%d eiliad",m:"munud",mm:"%d munud",h:"awr",hh:"%d awr",d:"diwrnod",dd:"%d diwrnod",M:"mis",MM:"%d mis",y:"blwyddyn",yy:"%d flynedd"},dayOfMonthOrdinalParse:/\d{1,2}(fed|ain|af|il|ydd|ed|eg)/,ordinal:function(e){var t=e,n="",r=["","af","il","ydd","ydd","ed","ed","ed","fed","fed","fed","eg","fed","eg","eg","fed","eg","eg","fed","eg","fed"];return t>20?n=40===t||50===t||60===t||80===t||100===t?"fed":"ain":t>0&&(n=r[t]),e+n},week:{dow:1,doy:4}})})(n(30381))},24780:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"s\xf8ndag_mandag_tirsdag_onsdag_torsdag_fredag_l\xf8rdag".split("_"),weekdaysShort:"s\xf8n_man_tir_ons_tor_fre_l\xf8r".split("_"),weekdaysMin:"s\xf8_ma_ti_on_to_fr_l\xf8".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd [d.] D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"p\xe5 dddd [kl.] LT",lastDay:"[i g\xe5r kl.] LT",lastWeek:"[i] dddd[s kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"f\xe5 sekunder",ss:"%d sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en m\xe5ned",MM:"%d m\xe5neder",y:"et \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},60217:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n,r){var i={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],w:["eine Woche","einer Woche"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return t?i[n][0]:i[n][1]}return e.defineLocale("de-at",{months:"J\xe4nner_Februar_M\xe4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"J\xe4n._Feb._M\xe4rz_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},calendar:{sameDay:"[heute um] LT [Uhr]",sameElse:"L",nextDay:"[morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",ss:"%d Sekunden",m:t,mm:"%d Minuten",h:t,hh:"%d Stunden",d:t,dd:t,w:t,ww:"%d Wochen",M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},60894:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n,r){var i={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],w:["eine Woche","einer Woche"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return t?i[n][0]:i[n][1]}return e.defineLocale("de-ch",{months:"Januar_Februar_M\xe4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._M\xe4rz_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},calendar:{sameDay:"[heute um] LT [Uhr]",sameElse:"L",nextDay:"[morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",ss:"%d Sekunden",m:t,mm:"%d Minuten",h:t,hh:"%d Stunden",d:t,dd:t,w:t,ww:"%d Wochen",M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},59740:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n,r){var i={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],w:["eine Woche","einer Woche"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return t?i[n][0]:i[n][1]}return e.defineLocale("de",{months:"Januar_Februar_M\xe4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._M\xe4rz_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},calendar:{sameDay:"[heute um] LT [Uhr]",sameElse:"L",nextDay:"[morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",ss:"%d Sekunden",m:t,mm:"%d Minuten",h:t,hh:"%d Stunden",d:t,dd:t,w:t,ww:"%d Wochen",M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},5300:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t=["ޖެނުއަރީ","ފެބްރުއަރީ","މާރިޗު","އޭޕްރީލު","މޭ","ޖޫން","ޖުލައި","އޯގަސްޓު","ސެޕްޓެމްބަރު","އޮކްޓޯބަރު","ނޮވެމްބަރު","ޑިސެމްބަރު",],n=["އާދިއްތަ","ހޯމަ","އަންގާރަ","ބުދަ","ބުރާސްފަތި","ހުކުރު","ހޮނިހިރު",];return e.defineLocale("dv",{months:t,monthsShort:t,weekdays:n,weekdaysShort:n,weekdaysMin:"އާދި_ހޯމަ_އަން_ބުދަ_ބުރާ_ހުކު_ހޮނި".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/M/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/މކ|މފ/,isPM:function(e){return"މފ"===e},meridiem:function(e,t,n){return e<12?"މކ":"މފ"},calendar:{sameDay:"[މިއަދު] LT",nextDay:"[މާދަމާ] LT",nextWeek:"dddd LT",lastDay:"[އިއްޔެ] LT",lastWeek:"[ފާއިތުވި] dddd LT",sameElse:"L"},relativeTime:{future:"ތެރޭގައި %s",past:"ކުރިން %s",s:"ސިކުންތުކޮޅެއް",ss:"d% ސިކުންތު",m:"މިނިޓެއް",mm:"މިނިޓު %d",h:"ގަޑިއިރެއް",hh:"ގަޑިއިރު %d",d:"ދުވަހެއް",dd:"ދުވަސް %d",M:"މަހެއް",MM:"މަސް %d",y:"އަހަރެއް",yy:"އަހަރު %d"},preparse:function(e){return e.replace(/،/g,",")},postformat:function(e){return e.replace(/,/g,"،")},week:{dow:7,doy:12}})})(n(30381))},50837:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e){return"undefined"!=typeof Function&&e instanceof Function||"[object Function]"===Object.prototype.toString.call(e)}return e.defineLocale("el",{monthsNominativeEl:"Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος".split("_"),monthsGenitiveEl:"Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου".split("_"),months:function(e,t){return e?"string"==typeof t&&/D/.test(t.substring(0,t.indexOf("MMMM")))?this._monthsGenitiveEl[e.month()]:this._monthsNominativeEl[e.month()]:this._monthsNominativeEl},monthsShort:"Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ".split("_"),weekdays:"Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο".split("_"),weekdaysShort:"Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ".split("_"),weekdaysMin:"Κυ_Δε_Τρ_Τε_Πε_Πα_Σα".split("_"),meridiem:function(e,t,n){return e>11?n?"μμ":"ΜΜ":n?"πμ":"ΠΜ"},isPM:function(e){return"μ"===(e+"").toLowerCase()[0]},meridiemParse:/[ΠΜ]\.?Μ?\.?/i,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendarEl:{sameDay:"[Σήμερα {}] LT",nextDay:"[Αύριο {}] LT",nextWeek:"dddd [{}] LT",lastDay:"[Χθες {}] LT",lastWeek:function(){return 6===this.day()?"[το προηγούμενο] dddd [{}] LT":"[την προηγούμενη] dddd [{}] LT"},sameElse:"L"},calendar:function(e,n){var r=this._calendarEl[e],i=n&&n.hours();return t(r)&&(r=r.apply(n)),r.replace("{}",i%12==1?"στη":"στις")},relativeTime:{future:"σε %s",past:"%s πριν",s:"λίγα δευτερόλεπτα",ss:"%d δευτερόλεπτα",m:"ένα λεπτό",mm:"%d λεπτά",h:"μία ώρα",hh:"%d ώρες",d:"μία μέρα",dd:"%d μέρες",M:"ένας μήνας",MM:"%d μήνες",y:"ένας χρόνος",yy:"%d χρόνια"},dayOfMonthOrdinalParse:/\d{1,2}η/,ordinal:"%dη",week:{dow:1,doy:4}})})(n(30381))},78348:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("en-au",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n},week:{dow:0,doy:4}})})(n(30381))},77925:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("en-ca",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"YYYY-MM-DD",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n}})})(n(30381))},22243:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n},week:{dow:1,doy:4}})})(n(30381))},46436:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("en-ie",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n},week:{dow:1,doy:4}})})(n(30381))},47207:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("en-il",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n}})})(n(30381))},44175:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("en-in",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n},week:{dow:0,doy:6}})})(n(30381))},76319:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("en-nz",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n},week:{dow:1,doy:4}})})(n(30381))},31662:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("en-sg",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n},week:{dow:1,doy:4}})})(n(30381))},92915:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("eo",{months:"januaro_februaro_marto_aprilo_majo_junio_julio_aŭgusto_septembro_oktobro_novembro_decembro".split("_"),monthsShort:"jan_feb_mart_apr_maj_jun_jul_aŭg_sept_okt_nov_dec".split("_"),weekdays:"dimanĉo_lundo_mardo_merkredo_ĵaŭdo_vendredo_sabato".split("_"),weekdaysShort:"dim_lun_mard_merk_ĵaŭ_ven_sab".split("_"),weekdaysMin:"di_lu_ma_me_ĵa_ve_sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"[la] D[-an de] MMMM, YYYY",LLL:"[la] D[-an de] MMMM, YYYY HH:mm",LLLL:"dddd[n], [la] D[-an de] MMMM, YYYY HH:mm",llll:"ddd, [la] D[-an de] MMM, YYYY HH:mm"},meridiemParse:/[ap]\.t\.m/i,isPM:function(e){return"p"===e.charAt(0).toLowerCase()},meridiem:function(e,t,n){return e>11?n?"p.t.m.":"P.T.M.":n?"a.t.m.":"A.T.M."},calendar:{sameDay:"[Hodiaŭ je] LT",nextDay:"[Morgaŭ je] LT",nextWeek:"dddd[n je] LT",lastDay:"[Hieraŭ je] LT",lastWeek:"[pasintan] dddd[n je] LT",sameElse:"L"},relativeTime:{future:"post %s",past:"antaŭ %s",s:"kelkaj sekundoj",ss:"%d sekundoj",m:"unu minuto",mm:"%d minutoj",h:"unu horo",hh:"%d horoj",d:"unu tago",dd:"%d tagoj",M:"unu monato",MM:"%d monatoj",y:"unu jaro",yy:"%d jaroj"},dayOfMonthOrdinalParse:/\d{1,2}a/,ordinal:"%da",week:{dow:1,doy:7}})})(n(30381))},55251:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),n="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),r=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i,],i=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;return e.defineLocale("es-do",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,r){return e?/-MMM-/.test(r)?n[e.month()]:t[e.month()]:t},monthsRegex:i,monthsShortRegex:i,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:r,longMonthsParse:r,shortMonthsParse:r,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY h:mm A",LLLL:"dddd, D [de] MMMM [de] YYYY h:mm A"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",w:"una semana",ww:"%d semanas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",week:{dow:1,doy:4}})})(n(30381))},96112:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),n="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),r=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i,],i=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;return e.defineLocale("es-mx",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,r){return e?/-MMM-/.test(r)?n[e.month()]:t[e.month()]:t},monthsRegex:i,monthsShortRegex:i,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:r,longMonthsParse:r,shortMonthsParse:r,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",w:"una semana",ww:"%d semanas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",week:{dow:0,doy:4},invalidDate:"Fecha inv\xe1lida"})})(n(30381))},71146:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),n="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),r=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i,],i=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;return e.defineLocale("es-us",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,r){return e?/-MMM-/.test(r)?n[e.month()]:t[e.month()]:t},monthsRegex:i,monthsShortRegex:i,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:r,longMonthsParse:r,shortMonthsParse:r,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"MM/DD/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY h:mm A",LLLL:"dddd, D [de] MMMM [de] YYYY h:mm A"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",w:"una semana",ww:"%d semanas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",week:{dow:0,doy:6}})})(n(30381))},55655:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),n="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),r=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i,],i=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;return e.defineLocale("es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,r){return e?/-MMM-/.test(r)?n[e.month()]:t[e.month()]:t},monthsRegex:i,monthsShortRegex:i,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:r,longMonthsParse:r,shortMonthsParse:r,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",w:"una semana",ww:"%d semanas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",week:{dow:1,doy:4},invalidDate:"Fecha inv\xe1lida"})})(n(30381))},5603:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n,r){var i={s:["m\xf5ne sekundi","m\xf5ni sekund","paar sekundit"],ss:[e+"sekundi",e+"sekundit"],m:["\xfche minuti","\xfcks minut"],mm:[e+" minuti",e+" minutit"],h:["\xfche tunni","tund aega","\xfcks tund"],hh:[e+" tunni",e+" tundi"],d:["\xfche p\xe4eva","\xfcks p\xe4ev"],M:["kuu aja","kuu aega","\xfcks kuu"],MM:[e+" kuu",e+" kuud"],y:["\xfche aasta","aasta","\xfcks aasta"],yy:[e+" aasta",e+" aastat"]};return t?i[n][2]?i[n][2]:i[n][1]:r?i[n][0]:i[n][1]}return e.defineLocale("et",{months:"jaanuar_veebruar_m\xe4rts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember".split("_"),monthsShort:"jaan_veebr_m\xe4rts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets".split("_"),weekdays:"p\xfchap\xe4ev_esmasp\xe4ev_teisip\xe4ev_kolmap\xe4ev_neljap\xe4ev_reede_laup\xe4ev".split("_"),weekdaysShort:"P_E_T_K_N_R_L".split("_"),weekdaysMin:"P_E_T_K_N_R_L".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[T\xe4na,] LT",nextDay:"[Homme,] LT",nextWeek:"[J\xe4rgmine] dddd LT",lastDay:"[Eile,] LT",lastWeek:"[Eelmine] dddd LT",sameElse:"L"},relativeTime:{future:"%s p\xe4rast",past:"%s tagasi",s:t,ss:t,m:t,mm:t,h:t,hh:t,d:t,dd:"%d p\xe4eva",M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},77763:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("eu",{months:"urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua".split("_"),monthsShort:"urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.".split("_"),monthsParseExact:!0,weekdays:"igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata".split("_"),weekdaysShort:"ig._al._ar._az._og._ol._lr.".split("_"),weekdaysMin:"ig_al_ar_az_og_ol_lr".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY[ko] MMMM[ren] D[a]",LLL:"YYYY[ko] MMMM[ren] D[a] HH:mm",LLLL:"dddd, YYYY[ko] MMMM[ren] D[a] HH:mm",l:"YYYY-M-D",ll:"YYYY[ko] MMM D[a]",lll:"YYYY[ko] MMM D[a] HH:mm",llll:"ddd, YYYY[ko] MMM D[a] HH:mm"},calendar:{sameDay:"[gaur] LT[etan]",nextDay:"[bihar] LT[etan]",nextWeek:"dddd LT[etan]",lastDay:"[atzo] LT[etan]",lastWeek:"[aurreko] dddd LT[etan]",sameElse:"L"},relativeTime:{future:"%s barru",past:"duela %s",s:"segundo batzuk",ss:"%d segundo",m:"minutu bat",mm:"%d minutu",h:"ordu bat",hh:"%d ordu",d:"egun bat",dd:"%d egun",M:"hilabete bat",MM:"%d hilabete",y:"urte bat",yy:"%d urte"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})(n(30381))},76959:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"۱",2:"۲",3:"۳",4:"۴",5:"۵",6:"۶",7:"۷",8:"۸",9:"۹",0:"۰"},n={"۱":"1","۲":"2","۳":"3","۴":"4","۵":"5","۶":"6","۷":"7","۸":"8","۹":"9","۰":"0"};return e.defineLocale("fa",{months:"ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر".split("_"),monthsShort:"ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر".split("_"),weekdays:"یک‌شنبه_دوشنبه_سه‌شنبه_چهارشنبه_پنج‌شنبه_جمعه_شنبه".split("_"),weekdaysShort:"یک‌شنبه_دوشنبه_سه‌شنبه_چهارشنبه_پنج‌شنبه_جمعه_شنبه".split("_"),weekdaysMin:"ی_د_س_چ_پ_ج_ش".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},meridiemParse:/قبل از ظهر|بعد از ظهر/,isPM:function(e){return/بعد از ظهر/.test(e)},meridiem:function(e,t,n){return e<12?"قبل از ظهر":"بعد از ظهر"},calendar:{sameDay:"[امروز ساعت] LT",nextDay:"[فردا ساعت] LT",nextWeek:"dddd [ساعت] LT",lastDay:"[دیروز ساعت] LT",lastWeek:"dddd [پیش] [ساعت] LT",sameElse:"L"},relativeTime:{future:"در %s",past:"%s پیش",s:"چند ثانیه",ss:"%d ثانیه",m:"یک دقیقه",mm:"%d دقیقه",h:"یک ساعت",hh:"%d ساعت",d:"یک روز",dd:"%d روز",M:"یک ماه",MM:"%d ماه",y:"یک سال",yy:"%d سال"},preparse:function(e){return e.replace(/[۰-۹]/g,function(e){return n[e]}).replace(/،/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]}).replace(/,/g,"،")},dayOfMonthOrdinalParse:/\d{1,2}م/,ordinal:"%dم",week:{dow:6,doy:12}})})(n(30381))},11897:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="nolla yksi kaksi kolme nelj\xe4 viisi kuusi seitsem\xe4n kahdeksan yhdeks\xe4n".split(" "),n=["nolla","yhden","kahden","kolmen","nelj\xe4n","viiden","kuuden",t[7],t[8],t[9],];function r(e,t,n,r){var a="";switch(n){case"s":return r?"muutaman sekunnin":"muutama sekunti";case"ss":a=r?"sekunnin":"sekuntia";break;case"m":return r?"minuutin":"minuutti";case"mm":a=r?"minuutin":"minuuttia";break;case"h":return r?"tunnin":"tunti";case"hh":a=r?"tunnin":"tuntia";break;case"d":return r?"p\xe4iv\xe4n":"p\xe4iv\xe4";case"dd":a=r?"p\xe4iv\xe4n":"p\xe4iv\xe4\xe4";break;case"M":return r?"kuukauden":"kuukausi";case"MM":a=r?"kuukauden":"kuukautta";break;case"y":return r?"vuoden":"vuosi";case"yy":a=r?"vuoden":"vuotta"}return i(e,r)+" "+a}function i(e,r){return e<10?r?n[e]:t[e]:e}return e.defineLocale("fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kes\xe4kuu_hein\xe4kuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kes\xe4_hein\xe4_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] HH.mm",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] HH.mm",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] HH.mm",llll:"ddd, Do MMM YYYY, [klo] HH.mm"},calendar:{sameDay:"[t\xe4n\xe4\xe4n] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s p\xe4\xe4st\xe4",past:"%s sitten",s:r,ss:r,m:r,mm:r,h:r,hh:r,d:r,dd:r,M:r,MM:r,y:r,yy:r},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},42549:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("fil",{months:"Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre".split("_"),monthsShort:"Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis".split("_"),weekdays:"Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado".split("_"),weekdaysShort:"Lin_Lun_Mar_Miy_Huw_Biy_Sab".split("_"),weekdaysMin:"Li_Lu_Ma_Mi_Hu_Bi_Sab".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"MM/D/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY HH:mm",LLLL:"dddd, MMMM DD, YYYY HH:mm"},calendar:{sameDay:"LT [ngayong araw]",nextDay:"[Bukas ng] LT",nextWeek:"LT [sa susunod na] dddd",lastDay:"LT [kahapon]",lastWeek:"LT [noong nakaraang] dddd",sameElse:"L"},relativeTime:{future:"sa loob ng %s",past:"%s ang nakalipas",s:"ilang segundo",ss:"%d segundo",m:"isang minuto",mm:"%d minuto",h:"isang oras",hh:"%d oras",d:"isang araw",dd:"%d araw",M:"isang buwan",MM:"%d buwan",y:"isang taon",yy:"%d taon"},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:function(e){return e},week:{dow:1,doy:4}})})(n(30381))},94694:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("fo",{months:"januar_februar_mars_apr\xedl_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"sunnudagur_m\xe1nadagur_t\xfdsdagur_mikudagur_h\xf3sdagur_fr\xedggjadagur_leygardagur".split("_"),weekdaysShort:"sun_m\xe1n_t\xfds_mik_h\xf3s_fr\xed_ley".split("_"),weekdaysMin:"su_m\xe1_t\xfd_mi_h\xf3_fr_le".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D. MMMM, YYYY HH:mm"},calendar:{sameDay:"[\xcd dag kl.] LT",nextDay:"[\xcd morgin kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[\xcd gj\xe1r kl.] LT",lastWeek:"[s\xed\xf0stu] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"um %s",past:"%s s\xed\xf0ani",s:"f\xe1 sekund",ss:"%d sekundir",m:"ein minuttur",mm:"%d minuttir",h:"ein t\xedmi",hh:"%d t\xedmar",d:"ein dagur",dd:"%d dagar",M:"ein m\xe1na\xf0ur",MM:"%d m\xe1na\xf0ir",y:"eitt \xe1r",yy:"%d \xe1r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},63049:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("fr-ca",{months:"janvier_f\xe9vrier_mars_avril_mai_juin_juillet_ao\xfbt_septembre_octobre_novembre_d\xe9cembre".split("_"),monthsShort:"janv._f\xe9vr._mars_avr._mai_juin_juil._ao\xfbt_sept._oct._nov._d\xe9c.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd’hui \xe0] LT",nextDay:"[Demain \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[Hier \xe0] LT",lastWeek:"dddd [dernier \xe0] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|e)/,ordinal:function(e,t){switch(t){default:case"M":case"Q":case"D":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}}})})(n(30381))},52330:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("fr-ch",{months:"janvier_f\xe9vrier_mars_avril_mai_juin_juillet_ao\xfbt_septembre_octobre_novembre_d\xe9cembre".split("_"),monthsShort:"janv._f\xe9vr._mars_avr._mai_juin_juil._ao\xfbt_sept._oct._nov._d\xe9c.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd’hui \xe0] LT",nextDay:"[Demain \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[Hier \xe0] LT",lastWeek:"dddd [dernier \xe0] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|e)/,ordinal:function(e,t){switch(t){default:case"M":case"Q":case"D":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}},week:{dow:1,doy:4}})})(n(30381))},94470:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t=/^(janvier|février|mars|avril|mai|juin|juillet|août|septembre|octobre|novembre|décembre)/i,n=/(janv\.?|févr\.?|mars|avr\.?|mai|juin|juil\.?|août|sept\.?|oct\.?|nov\.?|déc\.?)/i,r=/(janv\.?|févr\.?|mars|avr\.?|mai|juin|juil\.?|août|sept\.?|oct\.?|nov\.?|déc\.?|janvier|février|mars|avril|mai|juin|juillet|août|septembre|octobre|novembre|décembre)/i,i=[/^janv/i,/^févr/i,/^mars/i,/^avr/i,/^mai/i,/^juin/i,/^juil/i,/^août/i,/^sept/i,/^oct/i,/^nov/i,/^déc/i,];return e.defineLocale("fr",{months:"janvier_f\xe9vrier_mars_avril_mai_juin_juillet_ao\xfbt_septembre_octobre_novembre_d\xe9cembre".split("_"),monthsShort:"janv._f\xe9vr._mars_avr._mai_juin_juil._ao\xfbt_sept._oct._nov._d\xe9c.".split("_"),monthsRegex:r,monthsShortRegex:r,monthsStrictRegex:t,monthsShortStrictRegex:n,monthsParse:i,longMonthsParse:i,shortMonthsParse:i,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd’hui \xe0] LT",nextDay:"[Demain \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[Hier \xe0] LT",lastWeek:"dddd [dernier \xe0] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",w:"une semaine",ww:"%d semaines",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|)/,ordinal:function(e,t){switch(t){case"D":return e+(1===e?"er":"");default:case"M":case"Q":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}},week:{dow:1,doy:4}})})(n(30381))},5044:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="jan._feb._mrt._apr._mai_jun._jul._aug._sep._okt._nov._des.".split("_"),n="jan_feb_mrt_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_");return e.defineLocale("fy",{months:"jannewaris_febrewaris_maart_april_maaie_juny_july_augustus_septimber_oktober_novimber_desimber".split("_"),monthsShort:function(e,r){return e?/-MMM-/.test(r)?n[e.month()]:t[e.month()]:t},monthsParseExact:!0,weekdays:"snein_moandei_tiisdei_woansdei_tongersdei_freed_sneon".split("_"),weekdaysShort:"si._mo._ti._wo._to._fr._so.".split("_"),weekdaysMin:"Si_Mo_Ti_Wo_To_Fr_So".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[hjoed om] LT",nextDay:"[moarn om] LT",nextWeek:"dddd [om] LT",lastDay:"[juster om] LT",lastWeek:"[\xf4fr\xfbne] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oer %s",past:"%s lyn",s:"in pear sekonden",ss:"%d sekonden",m:"ien min\xfat",mm:"%d minuten",h:"ien oere",hh:"%d oeren",d:"ien dei",dd:"%d dagen",M:"ien moanne",MM:"%d moannen",y:"ien jier",yy:"%d jierren"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}})})(n(30381))},29295:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t=["Ean\xe1ir","Feabhra","M\xe1rta","Aibre\xe1n","Bealtaine","Meitheamh","I\xfail","L\xfanasa","Me\xe1n F\xf3mhair","Deireadh F\xf3mhair","Samhain","Nollaig",],n=["Ean","Feabh","M\xe1rt","Aib","Beal","Meith","I\xfail","L\xfan","M.F.","D.F.","Samh","Noll",],r=["D\xe9 Domhnaigh","D\xe9 Luain","D\xe9 M\xe1irt","D\xe9 C\xe9adaoin","D\xe9ardaoin","D\xe9 hAoine","D\xe9 Sathairn",],i=["Domh","Luan","M\xe1irt","C\xe9ad","D\xe9ar","Aoine","Sath"],a=["Do","Lu","M\xe1","C\xe9","D\xe9","A","Sa"];return e.defineLocale("ga",{months:t,monthsShort:n,monthsParseExact:!0,weekdays:r,weekdaysShort:i,weekdaysMin:a,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Inniu ag] LT",nextDay:"[Am\xe1rach ag] LT",nextWeek:"dddd [ag] LT",lastDay:"[Inn\xe9 ag] LT",lastWeek:"dddd [seo caite] [ag] LT",sameElse:"L"},relativeTime:{future:"i %s",past:"%s \xf3 shin",s:"c\xfapla soicind",ss:"%d soicind",m:"n\xf3im\xe9ad",mm:"%d n\xf3im\xe9ad",h:"uair an chloig",hh:"%d uair an chloig",d:"l\xe1",dd:"%d l\xe1",M:"m\xed",MM:"%d m\xedonna",y:"bliain",yy:"%d bliain"},dayOfMonthOrdinalParse:/\d{1,2}(d|na|mh)/,ordinal:function(e){var t=1===e?"d":e%10==2?"na":"mh";return e+t},week:{dow:1,doy:4}})})(n(30381))},2101:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t=["Am Faoilleach","An Gearran","Am M\xe0rt","An Giblean","An C\xe8itean","An t-\xd2gmhios","An t-Iuchar","An L\xf9nastal","An t-Sultain","An D\xe0mhair","An t-Samhain","An D\xf9bhlachd",],n=["Faoi","Gear","M\xe0rt","Gibl","C\xe8it","\xd2gmh","Iuch","L\xf9n","Sult","D\xe0mh","Samh","D\xf9bh",],r=["Did\xf2mhnaich","Diluain","Dim\xe0irt","Diciadain","Diardaoin","Dihaoine","Disathairne",],i=["Did","Dil","Dim","Dic","Dia","Dih","Dis"],a=["D\xf2","Lu","M\xe0","Ci","Ar","Ha","Sa"];return e.defineLocale("gd",{months:t,monthsShort:n,monthsParseExact:!0,weekdays:r,weekdaysShort:i,weekdaysMin:a,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[An-diugh aig] LT",nextDay:"[A-m\xe0ireach aig] LT",nextWeek:"dddd [aig] LT",lastDay:"[An-d\xe8 aig] LT",lastWeek:"dddd [seo chaidh] [aig] LT",sameElse:"L"},relativeTime:{future:"ann an %s",past:"bho chionn %s",s:"beagan diogan",ss:"%d diogan",m:"mionaid",mm:"%d mionaidean",h:"uair",hh:"%d uairean",d:"latha",dd:"%d latha",M:"m\xecos",MM:"%d m\xecosan",y:"bliadhna",yy:"%d bliadhna"},dayOfMonthOrdinalParse:/\d{1,2}(d|na|mh)/,ordinal:function(e){var t=1===e?"d":e%10==2?"na":"mh";return e+t},week:{dow:1,doy:4}})})(n(30381))},38794:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("gl",{months:"xaneiro_febreiro_marzo_abril_maio_xu\xf1o_xullo_agosto_setembro_outubro_novembro_decembro".split("_"),monthsShort:"xan._feb._mar._abr._mai._xu\xf1._xul._ago._set._out._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"domingo_luns_martes_m\xe9rcores_xoves_venres_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._m\xe9r._xov._ven._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_m\xe9_xo_ve_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},calendar:{sameDay:function(){return"[hoxe "+(1!==this.hours()?"\xe1s":"\xe1")+"] LT"},nextDay:function(){return"[ma\xf1\xe1 "+(1!==this.hours()?"\xe1s":"\xe1")+"] LT"},nextWeek:function(){return"dddd ["+(1!==this.hours()?"\xe1s":"a")+"] LT"},lastDay:function(){return"[onte "+(1!==this.hours()?"\xe1":"a")+"] LT"},lastWeek:function(){return"[o] dddd [pasado "+(1!==this.hours()?"\xe1s":"a")+"] LT"},sameElse:"L"},relativeTime:{future:function(e){return 0===e.indexOf("un")?"n"+e:"en "+e},past:"hai %s",s:"uns segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"unha hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",M:"un mes",MM:"%d meses",y:"un ano",yy:"%d anos"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",week:{dow:1,doy:4}})})(n(30381))},27884:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n,r){var i={s:["थोडया सॅकंडांनी","थोडे सॅकंड"],ss:[e+" सॅकंडांनी",e+" सॅकंड"],m:["एका मिणटान","एक मिनूट"],mm:[e+" मिणटांनी",e+" मिणटां"],h:["एका वरान","एक वर"],hh:[e+" वरांनी",e+" वरां"],d:["एका दिसान","एक दीस"],dd:[e+" दिसांनी",e+" दीस"],M:["एका म्हयन्यान","एक म्हयनो"],MM:[e+" म्हयन्यानी",e+" म्हयने"],y:["एका वर्सान","एक वर्स"],yy:[e+" वर्सांनी",e+" वर्सां"]};return r?i[n][0]:i[n][1]}return e.defineLocale("gom-deva",{months:{standalone:"जानेवारी_फेब्रुवारी_मार्च_एप्रील_मे_जून_जुलय_ऑगस्ट_सप्टेंबर_ऑक्टोबर_नोव्हेंबर_डिसेंबर".split("_"),format:"जानेवारीच्या_फेब्रुवारीच्या_मार्चाच्या_एप्रीलाच्या_मेयाच्या_जूनाच्या_जुलयाच्या_ऑगस्टाच्या_सप्टेंबराच्या_ऑक्टोबराच्या_नोव्हेंबराच्या_डिसेंबराच्या".split("_"),isFormat:/MMMM(\s)+D[oD]?/},monthsShort:"जाने._फेब्रु._मार्च_एप्री._मे_जून_जुल._ऑग._सप्टें._ऑक्टो._नोव्हें._डिसें.".split("_"),monthsParseExact:!0,weekdays:"आयतार_सोमार_मंगळार_बुधवार_बिरेस्तार_सुक्रार_शेनवार".split("_"),weekdaysShort:"आयत._सोम._मंगळ._बुध._ब्रेस्त._सुक्र._शेन.".split("_"),weekdaysMin:"आ_सो_मं_बु_ब्रे_सु_शे".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"A h:mm [वाजतां]",LTS:"A h:mm:ss [वाजतां]",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY A h:mm [वाजतां]",LLLL:"dddd, MMMM Do, YYYY, A h:mm [वाजतां]",llll:"ddd, D MMM YYYY, A h:mm [वाजतां]"},calendar:{sameDay:"[आयज] LT",nextDay:"[फाल्यां] LT",nextWeek:"[फुडलो] dddd[,] LT",lastDay:"[काल] LT",lastWeek:"[फाटलो] dddd[,] LT",sameElse:"L"},relativeTime:{future:"%s",past:"%s आदीं",s:t,ss:t,m:t,mm:t,h:t,hh:t,d:t,dd:t,M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}(वेर)/,ordinal:function(e,t){return"D"===t?e+"वेर":e},week:{dow:0,doy:3},meridiemParse:/राती|सकाळीं|दनपारां|सांजे/,meridiemHour:function(e,t){return(12===e&&(e=0),"राती"===t)?e<4?e:e+12:"सकाळीं"===t?e:"दनपारां"===t?e>12?e:e+12:"सांजे"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"राती":e<12?"सकाळीं":e<16?"दनपारां":e<20?"सांजे":"राती"}})})(n(30381))},23168:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n,r){var i={s:["thoddea sekondamni","thodde sekond"],ss:[e+" sekondamni",e+" sekond"],m:["eka mintan","ek minut"],mm:[e+" mintamni",e+" mintam"],h:["eka voran","ek vor"],hh:[e+" voramni",e+" voram"],d:["eka disan","ek dis"],dd:[e+" disamni",e+" dis"],M:["eka mhoinean","ek mhoino"],MM:[e+" mhoineamni",e+" mhoine"],y:["eka vorsan","ek voros"],yy:[e+" vorsamni",e+" vorsam"]};return r?i[n][0]:i[n][1]}return e.defineLocale("gom-latn",{months:{standalone:"Janer_Febrer_Mars_Abril_Mai_Jun_Julai_Agost_Setembr_Otubr_Novembr_Dezembr".split("_"),format:"Janerachea_Febrerachea_Marsachea_Abrilachea_Maiachea_Junachea_Julaiachea_Agostachea_Setembrachea_Otubrachea_Novembrachea_Dezembrachea".split("_"),isFormat:/MMMM(\s)+D[oD]?/},monthsShort:"Jan._Feb._Mars_Abr._Mai_Jun_Jul._Ago._Set._Otu._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Aitar_Somar_Mongllar_Budhvar_Birestar_Sukrar_Son'var".split("_"),weekdaysShort:"Ait._Som._Mon._Bud._Bre._Suk._Son.".split("_"),weekdaysMin:"Ai_Sm_Mo_Bu_Br_Su_Sn".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"A h:mm [vazta]",LTS:"A h:mm:ss [vazta]",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY A h:mm [vazta]",LLLL:"dddd, MMMM Do, YYYY, A h:mm [vazta]",llll:"ddd, D MMM YYYY, A h:mm [vazta]"},calendar:{sameDay:"[Aiz] LT",nextDay:"[Faleam] LT",nextWeek:"[Fuddlo] dddd[,] LT",lastDay:"[Kal] LT",lastWeek:"[Fattlo] dddd[,] LT",sameElse:"L"},relativeTime:{future:"%s",past:"%s adim",s:t,ss:t,m:t,mm:t,h:t,hh:t,d:t,dd:t,M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}(er)/,ordinal:function(e,t){return"D"===t?e+"er":e},week:{dow:0,doy:3},meridiemParse:/rati|sokallim|donparam|sanje/,meridiemHour:function(e,t){return(12===e&&(e=0),"rati"===t)?e<4?e:e+12:"sokallim"===t?e:"donparam"===t?e>12?e:e+12:"sanje"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"rati":e<12?"sokallim":e<16?"donparam":e<20?"sanje":"rati"}})})(n(30381))},95349:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"૧",2:"૨",3:"૩",4:"૪",5:"૫",6:"૬",7:"૭",8:"૮",9:"૯",0:"૦"},n={"૧":"1","૨":"2","૩":"3","૪":"4","૫":"5","૬":"6","૭":"7","૮":"8","૯":"9","૦":"0"};return e.defineLocale("gu",{months:"જાન્યુઆરી_ફેબ્રુઆરી_માર્ચ_એપ્રિલ_મે_જૂન_જુલાઈ_ઑગસ્ટ_સપ્ટેમ્બર_ઑક્ટ્બર_નવેમ્બર_ડિસેમ્બર".split("_"),monthsShort:"જાન્યુ._ફેબ્રુ._માર્ચ_એપ્રિ._મે_જૂન_જુલા._ઑગ._સપ્ટે._ઑક્ટ્._નવે._ડિસે.".split("_"),monthsParseExact:!0,weekdays:"રવિવાર_સોમવાર_મંગળવાર_બુધ્વાર_ગુરુવાર_શુક્રવાર_શનિવાર".split("_"),weekdaysShort:"રવિ_સોમ_મંગળ_બુધ્_ગુરુ_શુક્ર_શનિ".split("_"),weekdaysMin:"ર_સો_મં_બુ_ગુ_શુ_શ".split("_"),longDateFormat:{LT:"A h:mm વાગ્યે",LTS:"A h:mm:ss વાગ્યે",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm વાગ્યે",LLLL:"dddd, D MMMM YYYY, A h:mm વાગ્યે"},calendar:{sameDay:"[આજ] LT",nextDay:"[કાલે] LT",nextWeek:"dddd, LT",lastDay:"[ગઇકાલે] LT",lastWeek:"[પાછલા] dddd, LT",sameElse:"L"},relativeTime:{future:"%s મા",past:"%s પહેલા",s:"અમુક પળો",ss:"%d સેકંડ",m:"એક મિનિટ",mm:"%d મિનિટ",h:"એક કલાક",hh:"%d કલાક",d:"એક દિવસ",dd:"%d દિવસ",M:"એક મહિનો",MM:"%d મહિનો",y:"એક વર્ષ",yy:"%d વર્ષ"},preparse:function(e){return e.replace(/[૧૨૩૪૫૬૭૮૯૦]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/રાત|બપોર|સવાર|સાંજ/,meridiemHour:function(e,t){return(12===e&&(e=0),"રાત"===t)?e<4?e:e+12:"સવાર"===t?e:"બપોર"===t?e>=10?e:e+12:"સાંજ"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"રાત":e<10?"સવાર":e<17?"બપોર":e<20?"સાંજ":"રાત"},week:{dow:0,doy:6}})})(n(30381))},24206:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("he",{months:"ינואר_פברואר_מרץ_אפריל_מאי_יוני_יולי_אוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר".split("_"),monthsShort:"ינו׳_פבר׳_מרץ_אפר׳_מאי_יוני_יולי_אוג׳_ספט׳_אוק׳_נוב׳_דצמ׳".split("_"),weekdays:"ראשון_שני_שלישי_רביעי_חמישי_שישי_שבת".split("_"),weekdaysShort:"א׳_ב׳_ג׳_ד׳_ה׳_ו׳_ש׳".split("_"),weekdaysMin:"א_ב_ג_ד_ה_ו_ש".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [ב]MMMM YYYY",LLL:"D [ב]MMMM YYYY HH:mm",LLLL:"dddd, D [ב]MMMM YYYY HH:mm",l:"D/M/YYYY",ll:"D MMM YYYY",lll:"D MMM YYYY HH:mm",llll:"ddd, D MMM YYYY HH:mm"},calendar:{sameDay:"[היום ב־]LT",nextDay:"[מחר ב־]LT",nextWeek:"dddd [בשעה] LT",lastDay:"[אתמול ב־]LT",lastWeek:"[ביום] dddd [האחרון בשעה] LT",sameElse:"L"},relativeTime:{future:"בעוד %s",past:"לפני %s",s:"מספר שניות",ss:"%d שניות",m:"דקה",mm:"%d דקות",h:"שעה",hh:function(e){return 2===e?"שעתיים":e+" שעות"},d:"יום",dd:function(e){return 2===e?"יומיים":e+" ימים"},M:"חודש",MM:function(e){return 2===e?"חודשיים":e+" חודשים"},y:"שנה",yy:function(e){return 2===e?"שנתיים":e%10==0&&10!==e?e+" שנה":e+" שנים"}},meridiemParse:/אחה"צ|לפנה"צ|אחרי הצהריים|לפני הצהריים|לפנות בוקר|בבוקר|בערב/i,isPM:function(e){return/^(אחה"צ|אחרי הצהריים|בערב)$/.test(e)},meridiem:function(e,t,n){return e<5?"לפנות בוקר":e<10?"בבוקר":e<12?n?'לפנה"צ':"לפני הצהריים":e<18?n?'אחה"צ':"אחרי הצהריים":"בערב"}})})(n(30381))},30094:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"१",2:"२",3:"३",4:"४",5:"५",6:"६",7:"७",8:"८",9:"९",0:"०"},n={"१":"1","२":"2","३":"3","४":"4","५":"5","६":"6","७":"7","८":"8","९":"9","०":"0"},r=[/^जन/i,/^फ़र|फर/i,/^मार्च/i,/^अप्रै/i,/^मई/i,/^जून/i,/^जुल/i,/^अग/i,/^सितं|सित/i,/^अक्टू/i,/^नव|नवं/i,/^दिसं|दिस/i,],i=[/^जन/i,/^फ़र/i,/^मार्च/i,/^अप्रै/i,/^मई/i,/^जून/i,/^जुल/i,/^अग/i,/^सित/i,/^अक्टू/i,/^नव/i,/^दिस/i,];return e.defineLocale("hi",{months:{format:"जनवरी_फ़रवरी_मार्च_अप्रैल_मई_जून_जुलाई_अगस्त_सितम्बर_अक्टूबर_नवम्बर_दिसम्बर".split("_"),standalone:"जनवरी_फरवरी_मार्च_अप्रैल_मई_जून_जुलाई_अगस्त_सितंबर_अक्टूबर_नवंबर_दिसंबर".split("_")},monthsShort:"जन._फ़र._मार्च_अप्रै._मई_जून_जुल._अग._सित._अक्टू._नव._दिस.".split("_"),weekdays:"रविवार_सोमवार_मंगलवार_बुधवार_गुरूवार_शुक्रवार_शनिवार".split("_"),weekdaysShort:"रवि_सोम_मंगल_बुध_गुरू_शुक्र_शनि".split("_"),weekdaysMin:"र_सो_मं_बु_गु_शु_श".split("_"),longDateFormat:{LT:"A h:mm बजे",LTS:"A h:mm:ss बजे",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm बजे",LLLL:"dddd, D MMMM YYYY, A h:mm बजे"},monthsParse:r,longMonthsParse:r,shortMonthsParse:i,monthsRegex:/^(जनवरी|जन\.?|फ़रवरी|फरवरी|फ़र\.?|मार्च?|अप्रैल|अप्रै\.?|मई?|जून?|जुलाई|जुल\.?|अगस्त|अग\.?|सितम्बर|सितंबर|सित\.?|अक्टूबर|अक्टू\.?|नवम्बर|नवंबर|नव\.?|दिसम्बर|दिसंबर|दिस\.?)/i,monthsShortRegex:/^(जनवरी|जन\.?|फ़रवरी|फरवरी|फ़र\.?|मार्च?|अप्रैल|अप्रै\.?|मई?|जून?|जुलाई|जुल\.?|अगस्त|अग\.?|सितम्बर|सितंबर|सित\.?|अक्टूबर|अक्टू\.?|नवम्बर|नवंबर|नव\.?|दिसम्बर|दिसंबर|दिस\.?)/i,monthsStrictRegex:/^(जनवरी?|फ़रवरी|फरवरी?|मार्च?|अप्रैल?|मई?|जून?|जुलाई?|अगस्त?|सितम्बर|सितंबर|सित?\.?|अक्टूबर|अक्टू\.?|नवम्बर|नवंबर?|दिसम्बर|दिसंबर?)/i,monthsShortStrictRegex:/^(जन\.?|फ़र\.?|मार्च?|अप्रै\.?|मई?|जून?|जुल\.?|अग\.?|सित\.?|अक्टू\.?|नव\.?|दिस\.?)/i,calendar:{sameDay:"[आज] LT",nextDay:"[कल] LT",nextWeek:"dddd, LT",lastDay:"[कल] LT",lastWeek:"[पिछले] dddd, LT",sameElse:"L"},relativeTime:{future:"%s में",past:"%s पहले",s:"कुछ ही क्षण",ss:"%d सेकंड",m:"एक मिनट",mm:"%d मिनट",h:"एक घंटा",hh:"%d घंटे",d:"एक दिन",dd:"%d दिन",M:"एक महीने",MM:"%d महीने",y:"एक वर्ष",yy:"%d वर्ष"},preparse:function(e){return e.replace(/[१२३४५६७८९०]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/रात|सुबह|दोपहर|शाम/,meridiemHour:function(e,t){return(12===e&&(e=0),"रात"===t)?e<4?e:e+12:"सुबह"===t?e:"दोपहर"===t?e>=10?e:e+12:"शाम"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"रात":e<10?"सुबह":e<17?"दोपहर":e<20?"शाम":"रात"},week:{dow:0,doy:6}})})(n(30381))},30316:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n){var r=e+" ";switch(n){case"ss":return 1===e?r+="sekunda":2===e||3===e||4===e?r+="sekunde":r+="sekundi",r;case"m":return t?"jedna minuta":"jedne minute";case"mm":return 1===e?r+="minuta":2===e||3===e||4===e?r+="minute":r+="minuta",r;case"h":return t?"jedan sat":"jednog sata";case"hh":return 1===e?r+="sat":2===e||3===e||4===e?r+="sata":r+="sati",r;case"dd":return 1===e?r+="dan":r+="dana",r;case"MM":return 1===e?r+="mjesec":2===e||3===e||4===e?r+="mjeseca":r+="mjeseci",r;case"yy":return 1===e?r+="godina":2===e||3===e||4===e?r+="godine":r+="godina",r}}return e.defineLocale("hr",{months:{format:"siječnja_veljače_ožujka_travnja_svibnja_lipnja_srpnja_kolovoza_rujna_listopada_studenoga_prosinca".split("_"),standalone:"siječanj_veljača_ožujak_travanj_svibanj_lipanj_srpanj_kolovoz_rujan_listopad_studeni_prosinac".split("_")},monthsShort:"sij._velj._ožu._tra._svi._lip._srp._kol._ruj._lis._stu._pro.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"Do MMMM YYYY",LLL:"Do MMMM YYYY H:mm",LLLL:"dddd, Do MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[jučer u] LT",lastWeek:function(){switch(this.day()){case 0:return"[prošlu] [nedjelju] [u] LT";case 3:return"[prošlu] [srijedu] [u] LT";case 6:return"[prošle] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[prošli] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",ss:t,m:t,mm:t,h:t,hh:t,d:"dan",dd:t,M:"mjesec",MM:t,y:"godinu",yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})(n(30381))},22138:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="vas\xe1rnap h\xe9tfőn kedden szerd\xe1n cs\xfct\xf6rt\xf6k\xf6n p\xe9nteken szombaton".split(" ");function n(e,t,n,r){var i=e;switch(n){case"s":return r||t?"n\xe9h\xe1ny m\xe1sodperc":"n\xe9h\xe1ny m\xe1sodperce";case"ss":return i+(r||t)?" m\xe1sodperc":" m\xe1sodperce";case"m":return"egy"+(r||t?" perc":" perce");case"mm":return i+(r||t?" perc":" perce");case"h":return"egy"+(r||t?" \xf3ra":" \xf3r\xe1ja");case"hh":return i+(r||t?" \xf3ra":" \xf3r\xe1ja");case"d":return"egy"+(r||t?" nap":" napja");case"dd":return i+(r||t?" nap":" napja");case"M":return"egy"+(r||t?" h\xf3nap":" h\xf3napja");case"MM":return i+(r||t?" h\xf3nap":" h\xf3napja");case"y":return"egy"+(r||t?" \xe9v":" \xe9ve");case"yy":return i+(r||t?" \xe9v":" \xe9ve")}return""}function r(e){return(e?"":"[m\xfalt] ")+"["+t[this.day()]+"] LT[-kor]"}return e.defineLocale("hu",{months:"janu\xe1r_febru\xe1r_m\xe1rcius_\xe1prilis_m\xe1jus_j\xfanius_j\xfalius_augusztus_szeptember_okt\xf3ber_november_december".split("_"),monthsShort:"jan._feb._m\xe1rc._\xe1pr._m\xe1j._j\xfan._j\xfal._aug._szept._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"vas\xe1rnap_h\xe9tfő_kedd_szerda_cs\xfct\xf6rt\xf6k_p\xe9ntek_szombat".split("_"),weekdaysShort:"vas_h\xe9t_kedd_sze_cs\xfct_p\xe9n_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D. H:mm",LLLL:"YYYY. MMMM D., dddd H:mm"},meridiemParse:/de|du/i,isPM:function(e){return"u"===e.charAt(1).toLowerCase()},meridiem:function(e,t,n){return e<12?!0===n?"de":"DE":!0===n?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return r.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return r.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s m\xfalva",past:"%s",s:n,ss:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},11423:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("hy-am",{months:{format:"հունվարի_փետրվարի_մարտի_ապրիլի_մայիսի_հունիսի_հուլիսի_օգոստոսի_սեպտեմբերի_հոկտեմբերի_նոյեմբերի_դեկտեմբերի".split("_"),standalone:"հունվար_փետրվար_մարտ_ապրիլ_մայիս_հունիս_հուլիս_օգոստոս_սեպտեմբեր_հոկտեմբեր_նոյեմբեր_դեկտեմբեր".split("_")},monthsShort:"հնվ_փտր_մրտ_ապր_մյս_հնս_հլս_օգս_սպտ_հկտ_նմբ_դկտ".split("_"),weekdays:"կիրակի_երկուշաբթի_երեքշաբթի_չորեքշաբթի_հինգշաբթի_ուրբաթ_շաբաթ".split("_"),weekdaysShort:"կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ".split("_"),weekdaysMin:"կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY թ.",LLL:"D MMMM YYYY թ., HH:mm",LLLL:"dddd, D MMMM YYYY թ., HH:mm"},calendar:{sameDay:"[այսօր] LT",nextDay:"[վաղը] LT",lastDay:"[երեկ] LT",nextWeek:function(){return"dddd [օրը ժամը] LT"},lastWeek:function(){return"[անցած] dddd [օրը ժամը] LT"},sameElse:"L"},relativeTime:{future:"%s հետո",past:"%s առաջ",s:"մի քանի վայրկյան",ss:"%d վայրկյան",m:"րոպե",mm:"%d րոպե",h:"ժամ",hh:"%d ժամ",d:"օր",dd:"%d օր",M:"ամիս",MM:"%d ամիս",y:"տարի",yy:"%d տարի"},meridiemParse:/գիշերվա|առավոտվա|ցերեկվա|երեկոյան/,isPM:function(e){return/^(ցերեկվա|երեկոյան)$/.test(e)},meridiem:function(e){return e<4?"գիշերվա":e<12?"առավոտվա":e<17?"ցերեկվա":"երեկոյան"},dayOfMonthOrdinalParse:/\d{1,2}|\d{1,2}-(ին|րդ)/,ordinal:function(e,t){switch(t){case"DDD":case"w":case"W":case"DDDo":if(1===e)return e+"-ին";return e+"-րդ";default:return e}},week:{dow:1,doy:7}})})(n(30381))},29218:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Agt_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/pagi|siang|sore|malam/,meridiemHour:function(e,t){return(12===e&&(e=0),"pagi"===t)?e:"siang"===t?e>=11?e:e+12:"sore"===t||"malam"===t?e+12:void 0},meridiem:function(e,t,n){return e<11?"pagi":e<15?"siang":e<19?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",ss:"%d detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:0,doy:6}})})(n(30381))},90135:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e){if(e%100==11);else if(e%10==1)return!1;return!0}function n(e,n,r,i){var a=e+" ";switch(r){case"s":return n||i?"nokkrar sek\xfandur":"nokkrum sek\xfandum";case"ss":if(t(e))return a+(n||i?"sek\xfandur":"sek\xfandum");return a+"sek\xfanda";case"m":return n?"m\xedn\xfata":"m\xedn\xfatu";case"mm":if(t(e))return a+(n||i?"m\xedn\xfatur":"m\xedn\xfatum");if(n)return a+"m\xedn\xfata";return a+"m\xedn\xfatu";case"hh":if(t(e))return a+(n||i?"klukkustundir":"klukkustundum");return a+"klukkustund";case"d":if(n)return"dagur";return i?"dag":"degi";case"dd":if(t(e)){if(n)return a+"dagar";return a+(i?"daga":"d\xf6gum")}if(n)return a+"dagur";return a+(i?"dag":"degi");case"M":if(n)return"m\xe1nu\xf0ur";return i?"m\xe1nu\xf0":"m\xe1nu\xf0i";case"MM":if(t(e)){if(n)return a+"m\xe1nu\xf0ir";return a+(i?"m\xe1nu\xf0i":"m\xe1nu\xf0um")}if(n)return a+"m\xe1nu\xf0ur";return a+(i?"m\xe1nu\xf0":"m\xe1nu\xf0i");case"y":return n||i?"\xe1r":"\xe1ri";case"yy":if(t(e))return a+(n||i?"\xe1r":"\xe1rum");return a+(n||i?"\xe1r":"\xe1ri")}}return e.defineLocale("is",{months:"jan\xfaar_febr\xfaar_mars_apr\xedl_ma\xed_j\xfan\xed_j\xfal\xed_\xe1g\xfast_september_okt\xf3ber_n\xf3vember_desember".split("_"),monthsShort:"jan_feb_mar_apr_ma\xed_j\xfan_j\xfal_\xe1g\xfa_sep_okt_n\xf3v_des".split("_"),weekdays:"sunnudagur_m\xe1nudagur_\xferi\xf0judagur_mi\xf0vikudagur_fimmtudagur_f\xf6studagur_laugardagur".split("_"),weekdaysShort:"sun_m\xe1n_\xferi_mi\xf0_fim_f\xf6s_lau".split("_"),weekdaysMin:"Su_M\xe1_\xder_Mi_Fi_F\xf6_La".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] H:mm",LLLL:"dddd, D. MMMM YYYY [kl.] H:mm"},calendar:{sameDay:"[\xed dag kl.] LT",nextDay:"[\xe1 morgun kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[\xed g\xe6r kl.] LT",lastWeek:"[s\xed\xf0asta] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"eftir %s",past:"fyrir %s s\xed\xf0an",s:n,ss:n,m:n,mm:n,h:"klukkustund",hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},10150:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("it-ch",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"domenica_luned\xec_marted\xec_mercoled\xec_gioved\xec_venerd\xec_sabato".split("_"),weekdaysShort:"dom_lun_mar_mer_gio_ven_sab".split("_"),weekdaysMin:"do_lu_ma_me_gi_ve_sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){return 0===this.day()?"[la scorsa] dddd [alle] LT":"[lo scorso] dddd [alle] LT"},sameElse:"L"},relativeTime:{future:function(e){return(/^[0-9].+$/.test(e)?"tra":"in")+" "+e},past:"%s fa",s:"alcuni secondi",ss:"%d secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",week:{dow:1,doy:4}})})(n(30381))},90626:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"domenica_luned\xec_marted\xec_mercoled\xec_gioved\xec_venerd\xec_sabato".split("_"),weekdaysShort:"dom_lun_mar_mer_gio_ven_sab".split("_"),weekdaysMin:"do_lu_ma_me_gi_ve_sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:function(){return"[Oggi a"+(this.hours()>1?"lle ":0===this.hours()?" ":"ll'")+"]LT"},nextDay:function(){return"[Domani a"+(this.hours()>1?"lle ":0===this.hours()?" ":"ll'")+"]LT"},nextWeek:function(){return"dddd [a"+(this.hours()>1?"lle ":0===this.hours()?" ":"ll'")+"]LT"},lastDay:function(){return"[Ieri a"+(this.hours()>1?"lle ":0===this.hours()?" ":"ll'")+"]LT"},lastWeek:function(){return 0===this.day()?"[La scorsa] dddd [a"+(this.hours()>1?"lle ":0===this.hours()?" ":"ll'")+"]LT":"[Lo scorso] dddd [a"+(this.hours()>1?"lle ":0===this.hours()?" ":"ll'")+"]LT"},sameElse:"L"},relativeTime:{future:"tra %s",past:"%s fa",s:"alcuni secondi",ss:"%d secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",w:"una settimana",ww:"%d settimane",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",week:{dow:1,doy:4}})})(n(30381))},39183:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ja",{eras:[{since:"2019-05-01",offset:1,name:"令和",narrow:"㋿",abbr:"R"},{since:"1989-01-08",until:"2019-04-30",offset:1,name:"平成",narrow:"㍻",abbr:"H"},{since:"1926-12-25",until:"1989-01-07",offset:1,name:"昭和",narrow:"㍼",abbr:"S"},{since:"1912-07-30",until:"1926-12-24",offset:1,name:"大正",narrow:"㍽",abbr:"T"},{since:"1873-01-01",until:"1912-07-29",offset:6,name:"明治",narrow:"㍾",abbr:"M"},{since:"0001-01-01",until:"1873-12-31",offset:1,name:"西暦",narrow:"AD",abbr:"AD"},{since:"0000-12-31",until:-1/0,offset:1,name:"紀元前",narrow:"BC",abbr:"BC"},],eraYearOrdinalRegex:/(元|\d+)年/,eraYearOrdinalParse:function(e,t){return"元"===t[1]?1:parseInt(t[1]||e,10)},months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日 HH:mm",LLLL:"YYYY年M月D日 dddd HH:mm",l:"YYYY/MM/DD",ll:"YYYY年M月D日",lll:"YYYY年M月D日 HH:mm",llll:"YYYY年M月D日(ddd) HH:mm"},meridiemParse:/午前|午後/i,isPM:function(e){return"午後"===e},meridiem:function(e,t,n){return e<12?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:function(e){return e.week()!==this.week()?"[来週]dddd LT":"dddd LT"},lastDay:"[昨日] LT",lastWeek:function(e){return this.week()!==e.week()?"[先週]dddd LT":"dddd LT"},sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}日/,ordinal:function(e,t){switch(t){case"y":return 1===e?"元年":e+"年";case"d":case"D":case"DDD":return e+"日";default:return e}},relativeTime:{future:"%s後",past:"%s前",s:"数秒",ss:"%d秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}})})(n(30381))},24286:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("jv",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_Nopember_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nop_Des".split("_"),weekdays:"Minggu_Senen_Seloso_Rebu_Kemis_Jemuwah_Septu".split("_"),weekdaysShort:"Min_Sen_Sel_Reb_Kem_Jem_Sep".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sp".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/enjing|siyang|sonten|ndalu/,meridiemHour:function(e,t){return(12===e&&(e=0),"enjing"===t)?e:"siyang"===t?e>=11?e:e+12:"sonten"===t||"ndalu"===t?e+12:void 0},meridiem:function(e,t,n){return e<11?"enjing":e<15?"siyang":e<19?"sonten":"ndalu"},calendar:{sameDay:"[Dinten puniko pukul] LT",nextDay:"[Mbenjang pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kala wingi pukul] LT",lastWeek:"dddd [kepengker pukul] LT",sameElse:"L"},relativeTime:{future:"wonten ing %s",past:"%s ingkang kepengker",s:"sawetawis detik",ss:"%d detik",m:"setunggal menit",mm:"%d menit",h:"setunggal jam",hh:"%d jam",d:"sedinten",dd:"%d dinten",M:"sewulan",MM:"%d wulan",y:"setaun",yy:"%d taun"},week:{dow:1,doy:7}})})(n(30381))},12105:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ka",{months:"იანვარი_თებერვალი_მარტი_აპრილი_მაისი_ივნისი_ივლისი_აგვისტო_სექტემბერი_ოქტომბერი_ნოემბერი_დეკემბერი".split("_"),monthsShort:"იან_თებ_მარ_აპრ_მაი_ივნ_ივლ_აგვ_სექ_ოქტ_ნოე_დეკ".split("_"),weekdays:{standalone:"კვირა_ორშაბათი_სამშაბათი_ოთხშაბათი_ხუთშაბათი_პარასკევი_შაბათი".split("_"),format:"კვირას_ორშაბათს_სამშაბათს_ოთხშაბათს_ხუთშაბათს_პარასკევს_შაბათს".split("_"),isFormat:/(წინა|შემდეგ)/},weekdaysShort:"კვი_ორშ_სამ_ოთხ_ხუთ_პარ_შაბ".split("_"),weekdaysMin:"კვ_ორ_სა_ოთ_ხუ_პა_შა".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[დღეს] LT[-ზე]",nextDay:"[ხვალ] LT[-ზე]",lastDay:"[გუშინ] LT[-ზე]",nextWeek:"[შემდეგ] dddd LT[-ზე]",lastWeek:"[წინა] dddd LT-ზე",sameElse:"L"},relativeTime:{future:function(e){return e.replace(/(წამ|წუთ|საათ|წელ|დღ|თვ)(ი|ე)/,function(e,t,n){return"ი"===n?t+"ში":t+n+"ში"})},past:function(e){return/(წამი|წუთი|საათი|დღე|თვე)/.test(e)?e.replace(/(ი|ე)$/,"ის წინ"):/წელი/.test(e)?e.replace(/წელი$/,"წლის წინ"):e},s:"რამდენიმე წამი",ss:"%d წამი",m:"წუთი",mm:"%d წუთი",h:"საათი",hh:"%d საათი",d:"დღე",dd:"%d დღე",M:"თვე",MM:"%d თვე",y:"წელი",yy:"%d წელი"},dayOfMonthOrdinalParse:/0|1-ლი|მე-\d{1,2}|\d{1,2}-ე/,ordinal:function(e){return 0===e?e:1===e?e+"-ლი":e<20||e<=100&&e%20==0||e%100==0?"მე-"+e:e+"-ე"},week:{dow:1,doy:7}})})(n(30381))},47772:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={0:"-ші",1:"-ші",2:"-ші",3:"-ші",4:"-ші",5:"-ші",6:"-шы",7:"-ші",8:"-ші",9:"-шы",10:"-шы",20:"-шы",30:"-шы",40:"-шы",50:"-ші",60:"-шы",70:"-ші",80:"-ші",90:"-шы",100:"-ші"};return e.defineLocale("kk",{months:"қаңтар_ақпан_наурыз_сәуір_мамыр_маусым_шілде_тамыз_қыркүйек_қазан_қараша_желтоқсан".split("_"),monthsShort:"қаң_ақп_нау_сәу_мам_мау_шіл_там_қыр_қаз_қар_жел".split("_"),weekdays:"жексенбі_дүйсенбі_сейсенбі_сәрсенбі_бейсенбі_жұма_сенбі".split("_"),weekdaysShort:"жек_дүй_сей_сәр_бей_жұм_сен".split("_"),weekdaysMin:"жк_дй_сй_ср_бй_жм_сн".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Бүгін сағат] LT",nextDay:"[Ертең сағат] LT",nextWeek:"dddd [сағат] LT",lastDay:"[Кеше сағат] LT",lastWeek:"[Өткен аптаның] dddd [сағат] LT",sameElse:"L"},relativeTime:{future:"%s ішінде",past:"%s бұрын",s:"бірнеше секунд",ss:"%d секунд",m:"бір минут",mm:"%d минут",h:"бір сағат",hh:"%d сағат",d:"бір күн",dd:"%d күн",M:"бір ай",MM:"%d ай",y:"бір жыл",yy:"%d жыл"},dayOfMonthOrdinalParse:/\d{1,2}-(ші|шы)/,ordinal:function(e){var n=e%10,r=e>=100?100:null;return e+(t[e]||t[n]||t[r])},week:{dow:1,doy:7}})})(n(30381))},18758:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"១",2:"២",3:"៣",4:"៤",5:"៥",6:"៦",7:"៧",8:"៨",9:"៩",0:"០"},n={"១":"1","២":"2","៣":"3","៤":"4","៥":"5","៦":"6","៧":"7","៨":"8","៩":"9","០":"0"};return e.defineLocale("km",{months:"មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ".split("_"),monthsShort:"មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ".split("_"),weekdays:"អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍".split("_"),weekdaysShort:"អា_ច_អ_ព_ព្រ_សុ_ស".split("_"),weekdaysMin:"អា_ច_អ_ព_ព្រ_សុ_ស".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},meridiemParse:/ព្រឹក|ល្ងាច/,isPM:function(e){return"ល្ងាច"===e},meridiem:function(e,t,n){return e<12?"ព្រឹក":"ល្ងាច"},calendar:{sameDay:"[ថ្ងៃនេះ ម៉ោង] LT",nextDay:"[ស្អែក ម៉ោង] LT",nextWeek:"dddd [ម៉ោង] LT",lastDay:"[ម្សិលមិញ ម៉ោង] LT",lastWeek:"dddd [សប្តាហ៍មុន] [ម៉ោង] LT",sameElse:"L"},relativeTime:{future:"%sទៀត",past:"%sមុន",s:"ប៉ុន្មានវិនាទី",ss:"%d វិនាទី",m:"មួយនាទី",mm:"%d នាទី",h:"មួយម៉ោង",hh:"%d ម៉ោង",d:"មួយថ្ងៃ",dd:"%d ថ្ងៃ",M:"មួយខែ",MM:"%d ខែ",y:"មួយឆ្នាំ",yy:"%d ឆ្នាំ"},dayOfMonthOrdinalParse:/ទី\d{1,2}/,ordinal:"ទី%d",preparse:function(e){return e.replace(/[១២៣៤៥៦៧៨៩០]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},week:{dow:1,doy:4}})})(n(30381))},79282:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"೧",2:"೨",3:"೩",4:"೪",5:"೫",6:"೬",7:"೭",8:"೮",9:"೯",0:"೦"},n={"೧":"1","೨":"2","೩":"3","೪":"4","೫":"5","೬":"6","೭":"7","೮":"8","೯":"9","೦":"0"};return e.defineLocale("kn",{months:"ಜನವರಿ_ಫೆಬ್ರವರಿ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂಬರ್_ಅಕ್ಟೋಬರ್_ನವೆಂಬರ್_ಡಿಸೆಂಬರ್".split("_"),monthsShort:"ಜನ_ಫೆಬ್ರ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂ_ಅಕ್ಟೋ_ನವೆಂ_ಡಿಸೆಂ".split("_"),monthsParseExact:!0,weekdays:"ಭಾನುವಾರ_ಸೋಮವಾರ_ಮಂಗಳವಾರ_ಬುಧವಾರ_ಗುರುವಾರ_ಶುಕ್ರವಾರ_ಶನಿವಾರ".split("_"),weekdaysShort:"ಭಾನು_ಸೋಮ_ಮಂಗಳ_ಬುಧ_ಗುರು_ಶುಕ್ರ_ಶನಿ".split("_"),weekdaysMin:"ಭಾ_ಸೋ_ಮಂ_ಬು_ಗು_ಶು_ಶ".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[ಇಂದು] LT",nextDay:"[ನಾಳೆ] LT",nextWeek:"dddd, LT",lastDay:"[ನಿನ್ನೆ] LT",lastWeek:"[ಕೊನೆಯ] dddd, LT",sameElse:"L"},relativeTime:{future:"%s ನಂತರ",past:"%s ಹಿಂದೆ",s:"ಕೆಲವು ಕ್ಷಣಗಳು",ss:"%d ಸೆಕೆಂಡುಗಳು",m:"ಒಂದು ನಿಮಿಷ",mm:"%d ನಿಮಿಷ",h:"ಒಂದು ಗಂಟೆ",hh:"%d ಗಂಟೆ",d:"ಒಂದು ದಿನ",dd:"%d ದಿನ",M:"ಒಂದು ತಿಂಗಳು",MM:"%d ತಿಂಗಳು",y:"ಒಂದು ವರ್ಷ",yy:"%d ವರ್ಷ"},preparse:function(e){return e.replace(/[೧೨೩೪೫೬೭೮೯೦]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/ರಾತ್ರಿ|ಬೆಳಿಗ್ಗೆ|ಮಧ್ಯಾಹ್ನ|ಸಂಜೆ/,meridiemHour:function(e,t){return(12===e&&(e=0),"ರಾತ್ರಿ"===t)?e<4?e:e+12:"ಬೆಳಿಗ್ಗೆ"===t?e:"ಮಧ್ಯಾಹ್ನ"===t?e>=10?e:e+12:"ಸಂಜೆ"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"ರಾತ್ರಿ":e<10?"ಬೆಳಿಗ್ಗೆ":e<17?"ಮಧ್ಯಾಹ್ನ":e<20?"ಸಂಜೆ":"ರಾತ್ರಿ"},dayOfMonthOrdinalParse:/\d{1,2}(ನೇ)/,ordinal:function(e){return e+"ನೇ"},week:{dow:0,doy:6}})})(n(30381))},33730:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ko",{months:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),monthsShort:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),weekdays:"일요일_월요일_화요일_수요일_목요일_금요일_토요일".split("_"),weekdaysShort:"일_월_화_수_목_금_토".split("_"),weekdaysMin:"일_월_화_수_목_금_토".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY년 MMMM D일",LLL:"YYYY년 MMMM D일 A h:mm",LLLL:"YYYY년 MMMM D일 dddd A h:mm",l:"YYYY.MM.DD.",ll:"YYYY년 MMMM D일",lll:"YYYY년 MMMM D일 A h:mm",llll:"YYYY년 MMMM D일 dddd A h:mm"},calendar:{sameDay:"오늘 LT",nextDay:"내일 LT",nextWeek:"dddd LT",lastDay:"어제 LT",lastWeek:"지난주 dddd LT",sameElse:"L"},relativeTime:{future:"%s 후",past:"%s 전",s:"몇 초",ss:"%d초",m:"1분",mm:"%d분",h:"한 시간",hh:"%d시간",d:"하루",dd:"%d일",M:"한 달",MM:"%d달",y:"일 년",yy:"%d년"},dayOfMonthOrdinalParse:/\d{1,2}(일|월|주)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"일";case"M":return e+"월";case"w":case"W":return e+"주";default:return e}},meridiemParse:/오전|오후/,isPM:function(e){return"오후"===e},meridiem:function(e,t,n){return e<12?"오전":"오후"}})})(n(30381))},1408:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"١",2:"٢",3:"٣",4:"٤",5:"٥",6:"٦",7:"٧",8:"٨",9:"٩",0:"٠"},n={"١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9","٠":"0"},r=["کانونی دووەم","شوبات","ئازار","نیسان","ئایار","حوزەیران","تەمموز","ئاب","ئەیلوول","تشرینی یەكەم","تشرینی دووەم","كانونی یەکەم",];return e.defineLocale("ku",{months:r,monthsShort:r,weekdays:"یه‌كشه‌ممه‌_دووشه‌ممه‌_سێشه‌ممه‌_چوارشه‌ممه‌_پێنجشه‌ممه‌_هه‌ینی_شه‌ممه‌".split("_"),weekdaysShort:"یه‌كشه‌م_دووشه‌م_سێشه‌م_چوارشه‌م_پێنجشه‌م_هه‌ینی_شه‌ممه‌".split("_"),weekdaysMin:"ی_د_س_چ_پ_ه_ش".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},meridiemParse:/ئێواره‌|به‌یانی/,isPM:function(e){return/ئێواره‌/.test(e)},meridiem:function(e,t,n){return e<12?"به‌یانی":"ئێواره‌"},calendar:{sameDay:"[ئه‌مرۆ كاتژمێر] LT",nextDay:"[به‌یانی كاتژمێر] LT",nextWeek:"dddd [كاتژمێر] LT",lastDay:"[دوێنێ كاتژمێر] LT",lastWeek:"dddd [كاتژمێر] LT",sameElse:"L"},relativeTime:{future:"له‌ %s",past:"%s",s:"چه‌ند چركه‌یه‌ك",ss:"چركه‌ %d",m:"یه‌ك خوله‌ك",mm:"%d خوله‌ك",h:"یه‌ك كاتژمێر",hh:"%d كاتژمێر",d:"یه‌ك ڕۆژ",dd:"%d ڕۆژ",M:"یه‌ك مانگ",MM:"%d مانگ",y:"یه‌ك ساڵ",yy:"%d ساڵ"},preparse:function(e){return e.replace(/[١٢٣٤٥٦٧٨٩٠]/g,function(e){return n[e]}).replace(/،/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]}).replace(/,/g,"،")},week:{dow:6,doy:12}})})(n(30381))},33291:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={0:"-чү",1:"-чи",2:"-чи",3:"-чү",4:"-чү",5:"-чи",6:"-чы",7:"-чи",8:"-чи",9:"-чу",10:"-чу",20:"-чы",30:"-чу",40:"-чы",50:"-чү",60:"-чы",70:"-чи",80:"-чи",90:"-чу",100:"-чү"};return e.defineLocale("ky",{months:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),monthsShort:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),weekdays:"Жекшемби_Дүйшөмбү_Шейшемби_Шаршемби_Бейшемби_Жума_Ишемби".split("_"),weekdaysShort:"Жек_Дүй_Шей_Шар_Бей_Жум_Ише".split("_"),weekdaysMin:"Жк_Дй_Шй_Шр_Бй_Жм_Иш".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Бүгүн саат] LT",nextDay:"[Эртең саат] LT",nextWeek:"dddd [саат] LT",lastDay:"[Кечээ саат] LT",lastWeek:"[Өткөн аптанын] dddd [күнү] [саат] LT",sameElse:"L"},relativeTime:{future:"%s ичинде",past:"%s мурун",s:"бирнече секунд",ss:"%d секунд",m:"бир мүнөт",mm:"%d мүнөт",h:"бир саат",hh:"%d саат",d:"бир күн",dd:"%d күн",M:"бир ай",MM:"%d ай",y:"бир жыл",yy:"%d жыл"},dayOfMonthOrdinalParse:/\d{1,2}-(чи|чы|чү|чу)/,ordinal:function(e){var n=e%10,r=e>=100?100:null;return e+(t[e]||t[n]||t[r])},week:{dow:1,doy:7}})})(n(30381))},36841:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n,r){var i={m:["eng Minutt","enger Minutt"],h:["eng Stonn","enger Stonn"],d:["een Dag","engem Dag"],M:["ee Mount","engem Mount"],y:["ee Joer","engem Joer"]};return t?i[n][0]:i[n][1]}function n(e){return i(e.substr(0,e.indexOf(" ")))?"a "+e:"an "+e}function r(e){return i(e.substr(0,e.indexOf(" ")))?"viru "+e:"virun "+e}function i(e){if(e=parseInt(e,10),isNaN(e))return!1;if(e<0)return!0;if(e<10)return!!(4<=e)&&!!(e<=7);if(e<100){var t=e%10,n=e/10;return 0===t?i(n):i(t)}if(!(e<1e4))return i(e/=1e3);for(;e>=10;)e/=10;return i(e)}return e.defineLocale("lb",{months:"Januar_Februar_M\xe4erz_Abr\xebll_Mee_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Abr._Mee_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonndeg_M\xe9indeg_D\xebnschdeg_M\xebttwoch_Donneschdeg_Freideg_Samschdeg".split("_"),weekdaysShort:"So._M\xe9._D\xeb._M\xeb._Do._Fr._Sa.".split("_"),weekdaysMin:"So_M\xe9_D\xeb_M\xeb_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm [Auer]",LTS:"H:mm:ss [Auer]",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm [Auer]",LLLL:"dddd, D. MMMM YYYY H:mm [Auer]"},calendar:{sameDay:"[Haut um] LT",sameElse:"L",nextDay:"[Muer um] LT",nextWeek:"dddd [um] LT",lastDay:"[G\xebschter um] LT",lastWeek:function(){switch(this.day()){case 2:case 4:return"[Leschten] dddd [um] LT";default:return"[Leschte] dddd [um] LT"}}},relativeTime:{future:n,past:r,s:"e puer Sekonnen",ss:"%d Sekonnen",m:t,mm:"%d Minutten",h:t,hh:"%d Stonnen",d:t,dd:"%d Deeg",M:t,MM:"%d M\xe9int",y:t,yy:"%d Joer"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},55466:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("lo",{months:"ມັງກອນ_ກຸມພາ_ມີນາ_ເມສາ_ພຶດສະພາ_ມິຖຸນາ_ກໍລະກົດ_ສິງຫາ_ກັນຍາ_ຕຸລາ_ພະຈິກ_ທັນວາ".split("_"),monthsShort:"ມັງກອນ_ກຸມພາ_ມີນາ_ເມສາ_ພຶດສະພາ_ມິຖຸນາ_ກໍລະກົດ_ສິງຫາ_ກັນຍາ_ຕຸລາ_ພະຈິກ_ທັນວາ".split("_"),weekdays:"ອາທິດ_ຈັນ_ອັງຄານ_ພຸດ_ພະຫັດ_ສຸກ_ເສົາ".split("_"),weekdaysShort:"ທິດ_ຈັນ_ອັງຄານ_ພຸດ_ພະຫັດ_ສຸກ_ເສົາ".split("_"),weekdaysMin:"ທ_ຈ_ອຄ_ພ_ພຫ_ສກ_ສ".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"ວັນdddd D MMMM YYYY HH:mm"},meridiemParse:/ຕອນເຊົ້າ|ຕອນແລງ/,isPM:function(e){return"ຕອນແລງ"===e},meridiem:function(e,t,n){return e<12?"ຕອນເຊົ້າ":"ຕອນແລງ"},calendar:{sameDay:"[ມື້ນີ້ເວລາ] LT",nextDay:"[ມື້ອື່ນເວລາ] LT",nextWeek:"[ວັນ]dddd[ໜ້າເວລາ] LT",lastDay:"[ມື້ວານນີ້ເວລາ] LT",lastWeek:"[ວັນ]dddd[ແລ້ວນີ້ເວລາ] LT",sameElse:"L"},relativeTime:{future:"ອີກ %s",past:"%sຜ່ານມາ",s:"ບໍ່ເທົ່າໃດວິນາທີ",ss:"%d ວິນາທີ",m:"1 ນາທີ",mm:"%d ນາທີ",h:"1 ຊົ່ວໂມງ",hh:"%d ຊົ່ວໂມງ",d:"1 ມື້",dd:"%d ມື້",M:"1 ເດືອນ",MM:"%d ເດືອນ",y:"1 ປີ",yy:"%d ປີ"},dayOfMonthOrdinalParse:/(ທີ່)\d{1,2}/,ordinal:function(e){return"ທີ່"+e}})})(n(30381))},57010:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={ss:"sekundė_sekundžių_sekundes",m:"minutė_minutės_minutę",mm:"minutės_minučių_minutes",h:"valanda_valandos_valandą",hh:"valandos_valandų_valandas",d:"diena_dienos_dieną",dd:"dienos_dienų_dienas",M:"mėnuo_mėnesio_mėnesį",MM:"mėnesiai_mėnesių_mėnesius",y:"metai_metų_metus",yy:"metai_metų_metus"};function n(e,t,n,r){return t?"kelios sekundės":r?"kelių sekundžių":"kelias sekundes"}function r(e,t,n,r){return t?a(n)[0]:r?a(n)[1]:a(n)[2]}function i(e){return e%10==0||e>10&&e<20}function a(e){return t[e].split("_")}function o(e,t,n,o){var s=e+" ";return 1===e?s+r(e,t,n[0],o):t?s+(i(e)?a(n)[1]:a(n)[0]):o?s+a(n)[1]:s+(i(e)?a(n)[1]:a(n)[2])}return e.defineLocale("lt",{months:{format:"sausio_vasario_kovo_balandžio_gegužės_birželio_liepos_rugpjūčio_rugsėjo_spalio_lapkričio_gruodžio".split("_"),standalone:"sausis_vasaris_kovas_balandis_gegužė_birželis_liepa_rugpjūtis_rugsėjis_spalis_lapkritis_gruodis".split("_"),isFormat:/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?/},monthsShort:"sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd".split("_"),weekdays:{format:"sekmadienį_pirmadienį_antradienį_trečiadienį_ketvirtadienį_penktadienį_šeštadienį".split("_"),standalone:"sekmadienis_pirmadienis_antradienis_trečiadienis_ketvirtadienis_penktadienis_šeštadienis".split("_"),isFormat:/dddd HH:mm/},weekdaysShort:"Sek_Pir_Ant_Tre_Ket_Pen_Šeš".split("_"),weekdaysMin:"S_P_A_T_K_Pn_Š".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY [m.] MMMM D [d.]",LLL:"YYYY [m.] MMMM D [d.], HH:mm [val.]",LLLL:"YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]",l:"YYYY-MM-DD",ll:"YYYY [m.] MMMM D [d.]",lll:"YYYY [m.] MMMM D [d.], HH:mm [val.]",llll:"YYYY [m.] MMMM D [d.], ddd, HH:mm [val.]"},calendar:{sameDay:"[Šiandien] LT",nextDay:"[Rytoj] LT",nextWeek:"dddd LT",lastDay:"[Vakar] LT",lastWeek:"[Praėjusį] dddd LT",sameElse:"L"},relativeTime:{future:"po %s",past:"prieš %s",s:n,ss:o,m:r,mm:o,h:r,hh:o,d:r,dd:o,M:r,MM:o,y:r,yy:o},dayOfMonthOrdinalParse:/\d{1,2}-oji/,ordinal:function(e){return e+"-oji"},week:{dow:1,doy:4}})})(n(30381))},37595:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={ss:"sekundes_sekundēm_sekunde_sekundes".split("_"),m:"minūtes_minūtēm_minūte_minūtes".split("_"),mm:"minūtes_minūtēm_minūte_minūtes".split("_"),h:"stundas_stundām_stunda_stundas".split("_"),hh:"stundas_stundām_stunda_stundas".split("_"),d:"dienas_dienām_diena_dienas".split("_"),dd:"dienas_dienām_diena_dienas".split("_"),M:"mēneša_mēnešiem_mēnesis_mēneši".split("_"),MM:"mēneša_mēnešiem_mēnesis_mēneši".split("_"),y:"gada_gadiem_gads_gadi".split("_"),yy:"gada_gadiem_gads_gadi".split("_")};function n(e,t,n){return n?t%10==1&&t%100!=11?e[2]:e[3]:t%10==1&&t%100!=11?e[0]:e[1]}function r(e,r,i){return e+" "+n(t[i],e,r)}function i(e,r,i){return n(t[i],e,r)}function a(e,t){return t?"dažas sekundes":"dažām sekundēm"}return e.defineLocale("lv",{months:"janvāris_februāris_marts_aprīlis_maijs_jūnijs_jūlijs_augusts_septembris_oktobris_novembris_decembris".split("_"),monthsShort:"jan_feb_mar_apr_mai_jūn_jūl_aug_sep_okt_nov_dec".split("_"),weekdays:"svētdiena_pirmdiena_otrdiena_trešdiena_ceturtdiena_piektdiena_sestdiena".split("_"),weekdaysShort:"Sv_P_O_T_C_Pk_S".split("_"),weekdaysMin:"Sv_P_O_T_C_Pk_S".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY.",LL:"YYYY. [gada] D. MMMM",LLL:"YYYY. [gada] D. MMMM, HH:mm",LLLL:"YYYY. [gada] D. MMMM, dddd, HH:mm"},calendar:{sameDay:"[Šodien pulksten] LT",nextDay:"[Rīt pulksten] LT",nextWeek:"dddd [pulksten] LT",lastDay:"[Vakar pulksten] LT",lastWeek:"[Pagājušā] dddd [pulksten] LT",sameElse:"L"},relativeTime:{future:"pēc %s",past:"pirms %s",s:a,ss:r,m:i,mm:r,h:i,hh:r,d:i,dd:r,M:i,MM:r,y:i,yy:r},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},39861:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={words:{ss:["sekund","sekunda","sekundi"],m:["jedan minut","jednog minuta"],mm:["minut","minuta","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mjesec","mjeseca","mjeseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,r){var i=t.words[r];return 1===r.length?n?i[0]:i[1]:e+" "+t.correctGrammaticalCase(e,i)}};return e.defineLocale("me",{months:"januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sjutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){return["[prošle] [nedjelje] [u] LT","[prošlog] [ponedjeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srijede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT",][this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"nekoliko sekundi",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"dan",dd:t.translate,M:"mjesec",MM:t.translate,y:"godinu",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})(n(30381))},35493:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("mi",{months:"Kohi-tāte_Hui-tanguru_Poutū-te-rangi_Paenga-whāwhā_Haratua_Pipiri_Hōngoingoi_Here-turi-kōkā_Mahuru_Whiringa-ā-nuku_Whiringa-ā-rangi_Hakihea".split("_"),monthsShort:"Kohi_Hui_Pou_Pae_Hara_Pipi_Hōngoi_Here_Mahu_Whi-nu_Whi-ra_Haki".split("_"),monthsRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,2}/i,weekdays:"Rātapu_Mane_Tūrei_Wenerei_Tāite_Paraire_Hātarei".split("_"),weekdaysShort:"Ta_Ma_Tū_We_Tāi_Pa_Hā".split("_"),weekdaysMin:"Ta_Ma_Tū_We_Tāi_Pa_Hā".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [i] HH:mm",LLLL:"dddd, D MMMM YYYY [i] HH:mm"},calendar:{sameDay:"[i teie mahana, i] LT",nextDay:"[apopo i] LT",nextWeek:"dddd [i] LT",lastDay:"[inanahi i] LT",lastWeek:"dddd [whakamutunga i] LT",sameElse:"L"},relativeTime:{future:"i roto i %s",past:"%s i mua",s:"te hēkona ruarua",ss:"%d hēkona",m:"he meneti",mm:"%d meneti",h:"te haora",hh:"%d haora",d:"he ra",dd:"%d ra",M:"he marama",MM:"%d marama",y:"he tau",yy:"%d tau"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",week:{dow:1,doy:4}})})(n(30381))},95966:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("mk",{months:"јануари_февруари_март_април_мај_јуни_јули_август_септември_октомври_ноември_декември".split("_"),monthsShort:"јан_фев_мар_апр_мај_јун_јул_авг_сеп_окт_ное_дек".split("_"),weekdays:"недела_понеделник_вторник_среда_четврток_петок_сабота".split("_"),weekdaysShort:"нед_пон_вто_сре_чет_пет_саб".split("_"),weekdaysMin:"нe_пo_вт_ср_че_пе_сa".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[Денес во] LT",nextDay:"[Утре во] LT",nextWeek:"[Во] dddd [во] LT",lastDay:"[Вчера во] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[Изминатата] dddd [во] LT";case 1:case 2:case 4:case 5:return"[Изминатиот] dddd [во] LT"}},sameElse:"L"},relativeTime:{future:"за %s",past:"пред %s",s:"неколку секунди",ss:"%d секунди",m:"една минута",mm:"%d минути",h:"еден час",hh:"%d часа",d:"еден ден",dd:"%d дена",M:"еден месец",MM:"%d месеци",y:"една година",yy:"%d години"},dayOfMonthOrdinalParse:/\d{1,2}-(ев|ен|ти|ви|ри|ми)/,ordinal:function(e){var t=e%10,n=e%100;if(0===e)return e+"-ев";if(0===n)return e+"-ен";if(n>10&&n<20)return e+"-ти";if(1===t)return e+"-ви";if(2===t)return e+"-ри";else if(7===t||8===t)return e+"-ми";else return e+"-ти"},week:{dow:1,doy:7}})})(n(30381))},87341:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ml",{months:"ജനുവരി_ഫെബ്രുവരി_മാർച്ച്_ഏപ്രിൽ_മേയ്_ജൂൺ_ജൂലൈ_ഓഗസ്റ്റ്_സെപ്റ്റംബർ_ഒക്ടോബർ_നവംബർ_ഡിസംബർ".split("_"),monthsShort:"ജനു._ഫെബ്രു._മാർ._ഏപ്രി._മേയ്_ജൂൺ_ജൂലൈ._ഓഗ._സെപ്റ്റ._ഒക്ടോ._നവം._ഡിസം.".split("_"),monthsParseExact:!0,weekdays:"ഞായറാഴ്ച_തിങ്കളാഴ്ച_ചൊവ്വാഴ്ച_ബുധനാഴ്ച_വ്യാഴാഴ്ച_വെള്ളിയാഴ്ച_ശനിയാഴ്ച".split("_"),weekdaysShort:"ഞായർ_തിങ്കൾ_ചൊവ്വ_ബുധൻ_വ്യാഴം_വെള്ളി_ശനി".split("_"),weekdaysMin:"ഞാ_തി_ചൊ_ബു_വ്യാ_വെ_ശ".split("_"),longDateFormat:{LT:"A h:mm -നു",LTS:"A h:mm:ss -നു",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm -നു",LLLL:"dddd, D MMMM YYYY, A h:mm -നു"},calendar:{sameDay:"[ഇന്ന്] LT",nextDay:"[നാളെ] LT",nextWeek:"dddd, LT",lastDay:"[ഇന്നലെ] LT",lastWeek:"[കഴിഞ്ഞ] dddd, LT",sameElse:"L"},relativeTime:{future:"%s കഴിഞ്ഞ്",past:"%s മുൻപ്",s:"അൽപ നിമിഷങ്ങൾ",ss:"%d സെക്കൻഡ്",m:"ഒരു മിനിറ്റ്",mm:"%d മിനിറ്റ്",h:"ഒരു മണിക്കൂർ",hh:"%d മണിക്കൂർ",d:"ഒരു ദിവസം",dd:"%d ദിവസം",M:"ഒരു മാസം",MM:"%d മാസം",y:"ഒരു വർഷം",yy:"%d വർഷം"},meridiemParse:/രാത്രി|രാവിലെ|ഉച്ച കഴിഞ്ഞ്|വൈകുന്നേരം|രാത്രി/i,meridiemHour:function(e,t){return(12===e&&(e=0),"രാത്രി"===t&&e>=4||"ഉച്ച കഴിഞ്ഞ്"===t||"വൈകുന്നേരം"===t)?e+12:e},meridiem:function(e,t,n){return e<4?"രാത്രി":e<12?"രാവിലെ":e<17?"ഉച്ച കഴിഞ്ഞ്":e<20?"വൈകുന്നേരം":"രാത്രി"}})})(n(30381))},5115:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n,r){switch(n){case"s":return t?"хэдхэн секунд":"хэдхэн секундын";case"ss":return e+(t?" секунд":" секундын");case"m":case"mm":return e+(t?" минут":" минутын");case"h":case"hh":return e+(t?" цаг":" цагийн");case"d":case"dd":return e+(t?" өдөр":" өдрийн");case"M":case"MM":return e+(t?" сар":" сарын");case"y":case"yy":return e+(t?" жил":" жилийн");default:return e}}return e.defineLocale("mn",{months:"Нэгдүгээр сар_Хоёрдугаар сар_Гуравдугаар сар_Дөрөвдүгээр сар_Тавдугаар сар_Зургадугаар сар_Долдугаар сар_Наймдугаар сар_Есдүгээр сар_Аравдугаар сар_Арван нэгдүгээр сар_Арван хоёрдугаар сар".split("_"),monthsShort:"1 сар_2 сар_3 сар_4 сар_5 сар_6 сар_7 сар_8 сар_9 сар_10 сар_11 сар_12 сар".split("_"),monthsParseExact:!0,weekdays:"Ням_Даваа_Мягмар_Лхагва_Пүрэв_Баасан_Бямба".split("_"),weekdaysShort:"Ням_Дав_Мяг_Лха_Пүр_Баа_Бям".split("_"),weekdaysMin:"Ня_Да_Мя_Лх_Пү_Ба_Бя".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY оны MMMMын D",LLL:"YYYY оны MMMMын D HH:mm",LLLL:"dddd, YYYY оны MMMMын D HH:mm"},meridiemParse:/ҮӨ|ҮХ/i,isPM:function(e){return"ҮХ"===e},meridiem:function(e,t,n){return e<12?"ҮӨ":"ҮХ"},calendar:{sameDay:"[Өнөөдөр] LT",nextDay:"[Маргааш] LT",nextWeek:"[Ирэх] dddd LT",lastDay:"[Өчигдөр] LT",lastWeek:"[Өнгөрсөн] dddd LT",sameElse:"L"},relativeTime:{future:"%s дараа",past:"%s өмнө",s:t,ss:t,m:t,mm:t,h:t,hh:t,d:t,dd:t,M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2} өдөр/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+" өдөр";default:return e}}})})(n(30381))},10370:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"१",2:"२",3:"३",4:"४",5:"५",6:"६",7:"७",8:"८",9:"९",0:"०"},n={"१":"1","२":"2","३":"3","४":"4","५":"5","६":"6","७":"7","८":"8","९":"9","०":"0"};function r(e,t,n,r){var i="";if(t)switch(n){case"s":i="काही सेकंद";break;case"ss":i="%d सेकंद";break;case"m":i="एक मिनिट";break;case"mm":i="%d मिनिटे";break;case"h":i="एक तास";break;case"hh":i="%d तास";break;case"d":i="एक दिवस";break;case"dd":i="%d दिवस";break;case"M":i="एक महिना";break;case"MM":i="%d महिने";break;case"y":i="एक वर्ष";break;case"yy":i="%d वर्षे"}else switch(n){case"s":i="काही सेकंदां";break;case"ss":i="%d सेकंदां";break;case"m":i="एका मिनिटा";break;case"mm":i="%d मिनिटां";break;case"h":i="एका तासा";break;case"hh":i="%d तासां";break;case"d":i="एका दिवसा";break;case"dd":i="%d दिवसां";break;case"M":i="एका महिन्या";break;case"MM":i="%d महिन्यां";break;case"y":i="एका वर्षा";break;case"yy":i="%d वर्षां"}return i.replace(/%d/i,e)}return e.defineLocale("mr",{months:"जानेवारी_फेब्रुवारी_मार्च_एप्रिल_मे_जून_जुलै_ऑगस्ट_सप्टेंबर_ऑक्टोबर_नोव्हेंबर_डिसेंबर".split("_"),monthsShort:"जाने._फेब्रु._मार्च._एप्रि._मे._जून._जुलै._ऑग._सप्टें._ऑक्टो._नोव्हें._डिसें.".split("_"),monthsParseExact:!0,weekdays:"रविवार_सोमवार_मंगळवार_बुधवार_गुरूवार_शुक्रवार_शनिवार".split("_"),weekdaysShort:"रवि_सोम_मंगळ_बुध_गुरू_शुक्र_शनि".split("_"),weekdaysMin:"र_सो_मं_बु_गु_शु_श".split("_"),longDateFormat:{LT:"A h:mm वाजता",LTS:"A h:mm:ss वाजता",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm वाजता",LLLL:"dddd, D MMMM YYYY, A h:mm वाजता"},calendar:{sameDay:"[आज] LT",nextDay:"[उद्या] LT",nextWeek:"dddd, LT",lastDay:"[काल] LT",lastWeek:"[मागील] dddd, LT",sameElse:"L"},relativeTime:{future:"%sमध्ये",past:"%sपूर्वी",s:r,ss:r,m:r,mm:r,h:r,hh:r,d:r,dd:r,M:r,MM:r,y:r,yy:r},preparse:function(e){return e.replace(/[१२३४५६७८९०]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/पहाटे|सकाळी|दुपारी|सायंकाळी|रात्री/,meridiemHour:function(e,t){return(12===e&&(e=0),"पहाटे"===t||"सकाळी"===t)?e:"दुपारी"===t||"सायंकाळी"===t||"रात्री"===t?e>=12?e:e+12:void 0},meridiem:function(e,t,n){return e>=0&&e<6?"पहाटे":e<12?"सकाळी":e<17?"दुपारी":e<20?"सायंकाळी":"रात्री"},week:{dow:0,doy:6}})})(n(30381))},41237:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ms-my",{months:"Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis".split("_"),weekdays:"Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu".split("_"),weekdaysShort:"Ahd_Isn_Sel_Rab_Kha_Jum_Sab".split("_"),weekdaysMin:"Ah_Is_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/pagi|tengahari|petang|malam/,meridiemHour:function(e,t){return(12===e&&(e=0),"pagi"===t)?e:"tengahari"===t?e>=11?e:e+12:"petang"===t||"malam"===t?e+12:void 0},meridiem:function(e,t,n){return e<11?"pagi":e<15?"tengahari":e<19?"petang":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Esok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kelmarin pukul] LT",lastWeek:"dddd [lepas pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lepas",s:"beberapa saat",ss:"%d saat",m:"seminit",mm:"%d minit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}})})(n(30381))},9847:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ms",{months:"Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis".split("_"),weekdays:"Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu".split("_"),weekdaysShort:"Ahd_Isn_Sel_Rab_Kha_Jum_Sab".split("_"),weekdaysMin:"Ah_Is_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/pagi|tengahari|petang|malam/,meridiemHour:function(e,t){return(12===e&&(e=0),"pagi"===t)?e:"tengahari"===t?e>=11?e:e+12:"petang"===t||"malam"===t?e+12:void 0},meridiem:function(e,t,n){return e<11?"pagi":e<15?"tengahari":e<19?"petang":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Esok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kelmarin pukul] LT",lastWeek:"dddd [lepas pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lepas",s:"beberapa saat",ss:"%d saat",m:"seminit",mm:"%d minit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}})})(n(30381))},72126:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("mt",{months:"Jannar_Frar_Marzu_April_Mejju_Ġunju_Lulju_Awwissu_Settembru_Ottubru_Novembru_Diċembru".split("_"),monthsShort:"Jan_Fra_Mar_Apr_Mej_Ġun_Lul_Aww_Set_Ott_Nov_Diċ".split("_"),weekdays:"Il-Ħadd_It-Tnejn_It-Tlieta_L-Erbgħa_Il-Ħamis_Il-Ġimgħa_Is-Sibt".split("_"),weekdaysShort:"Ħad_Tne_Tli_Erb_Ħam_Ġim_Sib".split("_"),weekdaysMin:"Ħa_Tn_Tl_Er_Ħa_Ġi_Si".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Illum fil-]LT",nextDay:"[Għada fil-]LT",nextWeek:"dddd [fil-]LT",lastDay:"[Il-bieraħ fil-]LT",lastWeek:"dddd [li għadda] [fil-]LT",sameElse:"L"},relativeTime:{future:"f’ %s",past:"%s ilu",s:"ftit sekondi",ss:"%d sekondi",m:"minuta",mm:"%d minuti",h:"siegħa",hh:"%d siegħat",d:"ġurnata",dd:"%d ġranet",M:"xahar",MM:"%d xhur",y:"sena",yy:"%d sni"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",week:{dow:1,doy:4}})})(n(30381))},56165:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"၁",2:"၂",3:"၃",4:"၄",5:"၅",6:"၆",7:"၇",8:"၈",9:"၉",0:"၀"},n={"၁":"1","၂":"2","၃":"3","၄":"4","၅":"5","၆":"6","၇":"7","၈":"8","၉":"9","၀":"0"};return e.defineLocale("my",{months:"ဇန်နဝါရီ_ဖေဖော်ဝါရီ_မတ်_ဧပြီ_မေ_ဇွန်_ဇူလိုင်_သြဂုတ်_စက်တင်ဘာ_အောက်တိုဘာ_နိုဝင်ဘာ_ဒီဇင်ဘာ".split("_"),monthsShort:"ဇန်_ဖေ_မတ်_ပြီ_မေ_ဇွန်_လိုင်_သြ_စက်_အောက်_နို_ဒီ".split("_"),weekdays:"တနင်္ဂနွေ_တနင်္လာ_အင်္ဂါ_ဗုဒ္ဓဟူး_ကြာသပတေး_သောကြာ_စနေ".split("_"),weekdaysShort:"နွေ_လာ_ဂါ_ဟူး_ကြာ_သော_နေ".split("_"),weekdaysMin:"နွေ_လာ_ဂါ_ဟူး_ကြာ_သော_နေ".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[ယနေ.] LT [မှာ]",nextDay:"[မနက်ဖြန်] LT [မှာ]",nextWeek:"dddd LT [မှာ]",lastDay:"[မနေ.က] LT [မှာ]",lastWeek:"[ပြီးခဲ့သော] dddd LT [မှာ]",sameElse:"L"},relativeTime:{future:"လာမည့် %s မှာ",past:"လွန်ခဲ့သော %s က",s:"စက္ကန်.အနည်းငယ်",ss:"%d စက္ကန့်",m:"တစ်မိနစ်",mm:"%d မိနစ်",h:"တစ်နာရီ",hh:"%d နာရီ",d:"တစ်ရက်",dd:"%d ရက်",M:"တစ်လ",MM:"%d လ",y:"တစ်နှစ်",yy:"%d နှစ်"},preparse:function(e){return e.replace(/[၁၂၃၄၅၆၇၈၉၀]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},week:{dow:1,doy:4}})})(n(30381))},64924:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan._feb._mars_apr._mai_juni_juli_aug._sep._okt._nov._des.".split("_"),monthsParseExact:!0,weekdays:"s\xf8ndag_mandag_tirsdag_onsdag_torsdag_fredag_l\xf8rdag".split("_"),weekdaysShort:"s\xf8._ma._ti._on._to._fr._l\xf8.".split("_"),weekdaysMin:"s\xf8_ma_ti_on_to_fr_l\xf8".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] HH:mm",LLLL:"dddd D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i g\xe5r kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"noen sekunder",ss:"%d sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",w:"en uke",ww:"%d uker",M:"en m\xe5ned",MM:"%d m\xe5neder",y:"ett \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},16744:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"१",2:"२",3:"३",4:"४",5:"५",6:"६",7:"७",8:"८",9:"९",0:"०"},n={"१":"1","२":"2","३":"3","४":"4","५":"5","६":"6","७":"7","८":"8","९":"9","०":"0"};return e.defineLocale("ne",{months:"जनवरी_फेब्रुवरी_मार्च_अप्रिल_मई_जुन_जुलाई_अगष्ट_सेप्टेम्बर_अक्टोबर_नोभेम्बर_डिसेम्बर".split("_"),monthsShort:"जन._फेब्रु._मार्च_अप्रि._मई_जुन_जुलाई._अग._सेप्ट._अक्टो._नोभे._डिसे.".split("_"),monthsParseExact:!0,weekdays:"आइतबार_सोमबार_मङ्गलबार_बुधबार_बिहिबार_शुक्रबार_शनिबार".split("_"),weekdaysShort:"आइत._सोम._मङ्गल._बुध._बिहि._शुक्र._शनि.".split("_"),weekdaysMin:"आ._सो._मं._बु._बि._शु._श.".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"Aको h:mm बजे",LTS:"Aको h:mm:ss बजे",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, Aको h:mm बजे",LLLL:"dddd, D MMMM YYYY, Aको h:mm बजे"},preparse:function(e){return e.replace(/[१२३४५६७८९०]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/राति|बिहान|दिउँसो|साँझ/,meridiemHour:function(e,t){return(12===e&&(e=0),"राति"===t)?e<4?e:e+12:"बिहान"===t?e:"दिउँसो"===t?e>=10?e:e+12:"साँझ"===t?e+12:void 0},meridiem:function(e,t,n){return e<3?"राति":e<12?"बिहान":e<16?"दिउँसो":e<20?"साँझ":"राति"},calendar:{sameDay:"[आज] LT",nextDay:"[भोलि] LT",nextWeek:"[आउँदो] dddd[,] LT",lastDay:"[हिजो] LT",lastWeek:"[गएको] dddd[,] LT",sameElse:"L"},relativeTime:{future:"%sमा",past:"%s अगाडि",s:"केही क्षण",ss:"%d सेकेण्ड",m:"एक मिनेट",mm:"%d मिनेट",h:"एक घण्टा",hh:"%d घण्टा",d:"एक दिन",dd:"%d दिन",M:"एक महिना",MM:"%d महिना",y:"एक बर्ष",yy:"%d बर्ष"},week:{dow:0,doy:6}})})(n(30381))},59814:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),n="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_"),r=[/^jan/i,/^feb/i,/^maart|mrt.?$/i,/^apr/i,/^mei$/i,/^jun[i.]?$/i,/^jul[i.]?$/i,/^aug/i,/^sep/i,/^okt/i,/^nov/i,/^dec/i,],i=/^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i;return e.defineLocale("nl-be",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(e,r){return e?/-MMM-/.test(r)?n[e.month()]:t[e.month()]:t},monthsRegex:i,monthsShortRegex:i,monthsStrictRegex:/^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december)/i,monthsShortStrictRegex:/^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,monthsParse:r,longMonthsParse:r,shortMonthsParse:r,weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"zo_ma_di_wo_do_vr_za".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",ss:"%d seconden",m:"\xe9\xe9n minuut",mm:"%d minuten",h:"\xe9\xe9n uur",hh:"%d uur",d:"\xe9\xe9n dag",dd:"%d dagen",M:"\xe9\xe9n maand",MM:"%d maanden",y:"\xe9\xe9n jaar",yy:"%d jaar"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}})})(n(30381))},93901:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),n="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_"),r=[/^jan/i,/^feb/i,/^maart|mrt.?$/i,/^apr/i,/^mei$/i,/^jun[i.]?$/i,/^jul[i.]?$/i,/^aug/i,/^sep/i,/^okt/i,/^nov/i,/^dec/i,],i=/^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i;return e.defineLocale("nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(e,r){return e?/-MMM-/.test(r)?n[e.month()]:t[e.month()]:t},monthsRegex:i,monthsShortRegex:i,monthsStrictRegex:/^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december)/i,monthsShortStrictRegex:/^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,monthsParse:r,longMonthsParse:r,shortMonthsParse:r,weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"zo_ma_di_wo_do_vr_za".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",ss:"%d seconden",m:"\xe9\xe9n minuut",mm:"%d minuten",h:"\xe9\xe9n uur",hh:"%d uur",d:"\xe9\xe9n dag",dd:"%d dagen",w:"\xe9\xe9n week",ww:"%d weken",M:"\xe9\xe9n maand",MM:"%d maanden",y:"\xe9\xe9n jaar",yy:"%d jaar"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}})})(n(30381))},83877:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("nn",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan._feb._mars_apr._mai_juni_juli_aug._sep._okt._nov._des.".split("_"),monthsParseExact:!0,weekdays:"sundag_m\xe5ndag_tysdag_onsdag_torsdag_fredag_laurdag".split("_"),weekdaysShort:"su._m\xe5._ty._on._to._fr._lau.".split("_"),weekdaysMin:"su_m\xe5_ty_on_to_fr_la".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] H:mm",LLLL:"dddd D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[I dag klokka] LT",nextDay:"[I morgon klokka] LT",nextWeek:"dddd [klokka] LT",lastDay:"[I g\xe5r klokka] LT",lastWeek:"[F\xf8reg\xe5ande] dddd [klokka] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s sidan",s:"nokre sekund",ss:"%d sekund",m:"eit minutt",mm:"%d minutt",h:"ein time",hh:"%d timar",d:"ein dag",dd:"%d dagar",w:"ei veke",ww:"%d veker",M:"ein m\xe5nad",MM:"%d m\xe5nader",y:"eit \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},92135:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("oc-lnc",{months:{standalone:"geni\xe8r_febri\xe8r_mar\xe7_abril_mai_junh_julhet_agost_setembre_oct\xf2bre_novembre_decembre".split("_"),format:"de geni\xe8r_de febri\xe8r_de mar\xe7_d'abril_de mai_de junh_de julhet_d'agost_de setembre_d'oct\xf2bre_de novembre_de decembre".split("_"),isFormat:/D[oD]?(\s)+MMMM/},monthsShort:"gen._febr._mar\xe7_abr._mai_junh_julh._ago._set._oct._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"dimenge_diluns_dimars_dim\xe8cres_dij\xf2us_divendres_dissabte".split("_"),weekdaysShort:"dg._dl._dm._dc._dj._dv._ds.".split("_"),weekdaysMin:"dg_dl_dm_dc_dj_dv_ds".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM [de] YYYY",ll:"D MMM YYYY",LLL:"D MMMM [de] YYYY [a] H:mm",lll:"D MMM YYYY, H:mm",LLLL:"dddd D MMMM [de] YYYY [a] H:mm",llll:"ddd D MMM YYYY, H:mm"},calendar:{sameDay:"[u\xe8i a] LT",nextDay:"[deman a] LT",nextWeek:"dddd [a] LT",lastDay:"[i\xe8r a] LT",lastWeek:"dddd [passat a] LT",sameElse:"L"},relativeTime:{future:"d'aqu\xed %s",past:"fa %s",s:"unas segondas",ss:"%d segondas",m:"una minuta",mm:"%d minutas",h:"una ora",hh:"%d oras",d:"un jorn",dd:"%d jorns",M:"un mes",MM:"%d meses",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(r|n|t|è|a)/,ordinal:function(e,t){var n=1===e?"r":2===e?"n":3===e?"r":4===e?"t":"\xe8";return("w"===t||"W"===t)&&(n="a"),e+n},week:{dow:1,doy:4}})})(n(30381))},15858:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"੧",2:"੨",3:"੩",4:"੪",5:"੫",6:"੬",7:"੭",8:"੮",9:"੯",0:"੦"},n={"੧":"1","੨":"2","੩":"3","੪":"4","੫":"5","੬":"6","੭":"7","੮":"8","੯":"9","੦":"0"};return e.defineLocale("pa-in",{months:"ਜਨਵਰੀ_ਫ਼ਰਵਰੀ_ਮਾਰਚ_ਅਪ੍ਰੈਲ_ਮਈ_ਜੂਨ_ਜੁਲਾਈ_ਅਗਸਤ_ਸਤੰਬਰ_ਅਕਤੂਬਰ_ਨਵੰਬਰ_ਦਸੰਬਰ".split("_"),monthsShort:"ਜਨਵਰੀ_ਫ਼ਰਵਰੀ_ਮਾਰਚ_ਅਪ੍ਰੈਲ_ਮਈ_ਜੂਨ_ਜੁਲਾਈ_ਅਗਸਤ_ਸਤੰਬਰ_ਅਕਤੂਬਰ_ਨਵੰਬਰ_ਦਸੰਬਰ".split("_"),weekdays:"ਐਤਵਾਰ_ਸੋਮਵਾਰ_ਮੰਗਲਵਾਰ_ਬੁਧਵਾਰ_ਵੀਰਵਾਰ_ਸ਼ੁੱਕਰਵਾਰ_ਸ਼ਨੀਚਰਵਾਰ".split("_"),weekdaysShort:"ਐਤ_ਸੋਮ_ਮੰਗਲ_ਬੁਧ_ਵੀਰ_ਸ਼ੁਕਰ_ਸ਼ਨੀ".split("_"),weekdaysMin:"ਐਤ_ਸੋਮ_ਮੰਗਲ_ਬੁਧ_ਵੀਰ_ਸ਼ੁਕਰ_ਸ਼ਨੀ".split("_"),longDateFormat:{LT:"A h:mm ਵਜੇ",LTS:"A h:mm:ss ਵਜੇ",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm ਵਜੇ",LLLL:"dddd, D MMMM YYYY, A h:mm ਵਜੇ"},calendar:{sameDay:"[ਅਜ] LT",nextDay:"[ਕਲ] LT",nextWeek:"[ਅਗਲਾ] dddd, LT",lastDay:"[ਕਲ] LT",lastWeek:"[ਪਿਛਲੇ] dddd, LT",sameElse:"L"},relativeTime:{future:"%s ਵਿੱਚ",past:"%s ਪਿਛਲੇ",s:"ਕੁਝ ਸਕਿੰਟ",ss:"%d ਸਕਿੰਟ",m:"ਇਕ ਮਿੰਟ",mm:"%d ਮਿੰਟ",h:"ਇੱਕ ਘੰਟਾ",hh:"%d ਘੰਟੇ",d:"ਇੱਕ ਦਿਨ",dd:"%d ਦਿਨ",M:"ਇੱਕ ਮਹੀਨਾ",MM:"%d ਮਹੀਨੇ",y:"ਇੱਕ ਸਾਲ",yy:"%d ਸਾਲ"},preparse:function(e){return e.replace(/[੧੨੩੪੫੬੭੮੯੦]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/ਰਾਤ|ਸਵੇਰ|ਦੁਪਹਿਰ|ਸ਼ਾਮ/,meridiemHour:function(e,t){return(12===e&&(e=0),"ਰਾਤ"===t)?e<4?e:e+12:"ਸਵੇਰ"===t?e:"ਦੁਪਹਿਰ"===t?e>=10?e:e+12:"ਸ਼ਾਮ"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"ਰਾਤ":e<10?"ਸਵੇਰ":e<17?"ਦੁਪਹਿਰ":e<20?"ਸ਼ਾਮ":"ਰਾਤ"},week:{dow:0,doy:6}})})(n(30381))},64495:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),n="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_"),r=[/^sty/i,/^lut/i,/^mar/i,/^kwi/i,/^maj/i,/^cze/i,/^lip/i,/^sie/i,/^wrz/i,/^paź/i,/^lis/i,/^gru/i,];function i(e){return e%10<5&&e%10>1&&~~(e/10)%10!=1}function a(e,t,n){var r=e+" ";switch(n){case"ss":return r+(i(e)?"sekundy":"sekund");case"m":return t?"minuta":"minutę";case"mm":return r+(i(e)?"minuty":"minut");case"h":return t?"godzina":"godzinę";case"hh":return r+(i(e)?"godziny":"godzin");case"ww":return r+(i(e)?"tygodnie":"tygodni");case"MM":return r+(i(e)?"miesiące":"miesięcy");case"yy":return r+(i(e)?"lata":"lat")}}return e.defineLocale("pl",{months:function(e,r){return e?/D MMMM/.test(r)?n[e.month()]:t[e.month()]:t},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),monthsParse:r,longMonthsParse:r,shortMonthsParse:r,weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"ndz_pon_wt_śr_czw_pt_sob".split("_"),weekdaysMin:"Nd_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:function(){switch(this.day()){case 0:return"[W niedzielę o] LT";case 2:return"[We wtorek o] LT";case 3:return"[W środę o] LT";case 6:return"[W sobotę o] LT";default:return"[W] dddd [o] LT"}},lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",ss:a,m:a,mm:a,h:a,hh:a,d:"1 dzień",dd:"%d dni",w:"tydzień",ww:a,M:"miesiąc",MM:a,y:"rok",yy:a},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},57971:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("pt-br",{months:"janeiro_fevereiro_mar\xe7o_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_ter\xe7a-feira_quarta-feira_quinta-feira_sexta-feira_s\xe1bado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_s\xe1b".split("_"),weekdaysMin:"do_2\xaa_3\xaa_4\xaa_5\xaa_6\xaa_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [\xe0s] HH:mm",LLLL:"dddd, D [de] MMMM [de] YYYY [\xe0s] HH:mm"},calendar:{sameDay:"[Hoje \xe0s] LT",nextDay:"[Amanh\xe3 \xe0s] LT",nextWeek:"dddd [\xe0s] LT",lastDay:"[Ontem \xe0s] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[\xdaltimo] dddd [\xe0s] LT":"[\xdaltima] dddd [\xe0s] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"h\xe1 %s",s:"poucos segundos",ss:"%d segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um m\xeas",MM:"%d meses",y:"um ano",yy:"%d anos"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",invalidDate:"Data inv\xe1lida"})})(n(30381))},89520:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("pt",{months:"janeiro_fevereiro_mar\xe7o_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"Domingo_Segunda-feira_Ter\xe7a-feira_Quarta-feira_Quinta-feira_Sexta-feira_S\xe1bado".split("_"),weekdaysShort:"Dom_Seg_Ter_Qua_Qui_Sex_S\xe1b".split("_"),weekdaysMin:"Do_2\xaa_3\xaa_4\xaa_5\xaa_6\xaa_S\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY HH:mm",LLLL:"dddd, D [de] MMMM [de] YYYY HH:mm"},calendar:{sameDay:"[Hoje \xe0s] LT",nextDay:"[Amanh\xe3 \xe0s] LT",nextWeek:"dddd [\xe0s] LT",lastDay:"[Ontem \xe0s] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[\xdaltimo] dddd [\xe0s] LT":"[\xdaltima] dddd [\xe0s] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"h\xe1 %s",s:"segundos",ss:"%d segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",w:"uma semana",ww:"%d semanas",M:"um m\xeas",MM:"%d meses",y:"um ano",yy:"%d anos"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%d\xba",week:{dow:1,doy:4}})})(n(30381))},96459:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n){var r=" ";return(e%100>=20||e>=100&&e%100==0)&&(r=" de "),e+r+({ss:"secunde",mm:"minute",hh:"ore",dd:"zile",ww:"săptăm\xe2ni",MM:"luni",yy:"ani"})[n]}return e.defineLocale("ro",{months:"ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie".split("_"),monthsShort:"ian._feb._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"duminică_luni_marți_miercuri_joi_vineri_s\xe2mbătă".split("_"),weekdaysShort:"Dum_Lun_Mar_Mie_Joi_Vin_S\xe2m".split("_"),weekdaysMin:"Du_Lu_Ma_Mi_Jo_Vi_S\xe2".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[azi la] LT",nextDay:"[m\xe2ine la] LT",nextWeek:"dddd [la] LT",lastDay:"[ieri la] LT",lastWeek:"[fosta] dddd [la] LT",sameElse:"L"},relativeTime:{future:"peste %s",past:"%s \xeen urmă",s:"c\xe2teva secunde",ss:t,m:"un minut",mm:t,h:"o oră",hh:t,d:"o zi",dd:t,w:"o săptăm\xe2nă",ww:t,M:"o lună",MM:t,y:"un an",yy:t},week:{dow:1,doy:7}})})(n(30381))},21793:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t){var n=e.split("_");return t%10==1&&t%100!=11?n[0]:t%10>=2&&t%10<=4&&(t%100<10||t%100>=20)?n[1]:n[2]}function n(e,n,r){var i={ss:n?"секунда_секунды_секунд":"секунду_секунды_секунд",mm:n?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",ww:"неделя_недели_недель",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===r?n?"минута":"минуту":e+" "+t(i[r],+e)}var r=[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[йя]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i,];return e.defineLocale("ru",{months:{format:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_"),standalone:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_")},monthsShort:{format:"янв._февр._мар._апр._мая_июня_июля_авг._сент._окт._нояб._дек.".split("_"),standalone:"янв._февр._март_апр._май_июнь_июль_авг._сент._окт._нояб._дек.".split("_")},weekdays:{standalone:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),format:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_"),isFormat:/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?] ?dddd/},weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:r,longMonthsParse:r,shortMonthsParse:r,monthsRegex:/^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i,monthsShortRegex:/^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i,monthsStrictRegex:/^(январ[яь]|феврал[яь]|марта?|апрел[яь]|ма[яй]|июн[яь]|июл[яь]|августа?|сентябр[яь]|октябр[яь]|ноябр[яь]|декабр[яь])/i,monthsShortStrictRegex:/^(янв\.|февр?\.|мар[т.]|апр\.|ма[яй]|июн[ья.]|июл[ья.]|авг\.|сент?\.|окт\.|нояб?\.|дек\.)/i,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., H:mm",LLLL:"dddd, D MMMM YYYY г., H:mm"},calendar:{sameDay:"[Сегодня, в] LT",nextDay:"[Завтра, в] LT",lastDay:"[Вчера, в] LT",nextWeek:function(e){if(e.week()===this.week())return 2===this.day()?"[Во] dddd, [в] LT":"[В] dddd, [в] LT";switch(this.day()){case 0:return"[В следующее] dddd, [в] LT";case 1:case 2:case 4:return"[В следующий] dddd, [в] LT";case 3:case 5:case 6:return"[В следующую] dddd, [в] LT"}},lastWeek:function(e){if(e.week()===this.week())return 2===this.day()?"[Во] dddd, [в] LT":"[В] dddd, [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd, [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd, [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd, [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",ss:n,m:n,mm:n,h:"час",hh:n,d:"день",dd:n,w:"неделя",ww:n,M:"месяц",MM:n,y:"год",yy:n},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(e){return/^(дня|вечера)$/.test(e)},meridiem:function(e,t,n){return e<4?"ночи":e<12?"утра":e<17?"дня":"вечера"},dayOfMonthOrdinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(e,t){switch(t){case"M":case"d":case"DDD":return e+"-й";case"D":return e+"-го";case"w":case"W":return e+"-я";default:return e}},week:{dow:1,doy:4}})})(n(30381))},40950:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t=["جنوري","فيبروري","مارچ","اپريل","مئي","جون","جولاءِ","آگسٽ","سيپٽمبر","آڪٽوبر","نومبر","ڊسمبر",],n=["آچر","سومر","اڱارو","اربع","خميس","جمع","ڇنڇر"];return e.defineLocale("sd",{months:t,monthsShort:t,weekdays:n,weekdaysShort:n,weekdaysMin:n,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd، D MMMM YYYY HH:mm"},meridiemParse:/صبح|شام/,isPM:function(e){return"شام"===e},meridiem:function(e,t,n){return e<12?"صبح":"شام"},calendar:{sameDay:"[اڄ] LT",nextDay:"[سڀاڻي] LT",nextWeek:"dddd [اڳين هفتي تي] LT",lastDay:"[ڪالهه] LT",lastWeek:"[گزريل هفتي] dddd [تي] LT",sameElse:"L"},relativeTime:{future:"%s پوء",past:"%s اڳ",s:"چند سيڪنڊ",ss:"%d سيڪنڊ",m:"هڪ منٽ",mm:"%d منٽ",h:"هڪ ڪلاڪ",hh:"%d ڪلاڪ",d:"هڪ ڏينهن",dd:"%d ڏينهن",M:"هڪ مهينو",MM:"%d مهينا",y:"هڪ سال",yy:"%d سال"},preparse:function(e){return e.replace(/،/g,",")},postformat:function(e){return e.replace(/,/g,"،")},week:{dow:1,doy:4}})})(n(30381))},10490:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("se",{months:"ođđajagem\xe1nnu_guovvam\xe1nnu_njukčam\xe1nnu_cuoŋom\xe1nnu_miessem\xe1nnu_geassem\xe1nnu_suoidnem\xe1nnu_borgem\xe1nnu_čakčam\xe1nnu_golggotm\xe1nnu_sk\xe1bmam\xe1nnu_juovlam\xe1nnu".split("_"),monthsShort:"ođđj_guov_njuk_cuo_mies_geas_suoi_borg_čakč_golg_sk\xe1b_juov".split("_"),weekdays:"sotnabeaivi_vuoss\xe1rga_maŋŋeb\xe1rga_gaskavahkku_duorastat_bearjadat_l\xe1vvardat".split("_"),weekdaysShort:"sotn_vuos_maŋ_gask_duor_bear_l\xe1v".split("_"),weekdaysMin:"s_v_m_g_d_b_L".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"MMMM D. [b.] YYYY",LLL:"MMMM D. [b.] YYYY [ti.] HH:mm",LLLL:"dddd, MMMM D. [b.] YYYY [ti.] HH:mm"},calendar:{sameDay:"[otne ti] LT",nextDay:"[ihttin ti] LT",nextWeek:"dddd [ti] LT",lastDay:"[ikte ti] LT",lastWeek:"[ovddit] dddd [ti] LT",sameElse:"L"},relativeTime:{future:"%s geažes",past:"maŋit %s",s:"moadde sekunddat",ss:"%d sekunddat",m:"okta minuhta",mm:"%d minuhtat",h:"okta diimmu",hh:"%d diimmut",d:"okta beaivi",dd:"%d beaivvit",M:"okta m\xe1nnu",MM:"%d m\xe1nut",y:"okta jahki",yy:"%d jagit"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},90124:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("si",{months:"ජනවාරි_පෙබරවාරි_මාර්තු_අප්‍රේල්_මැයි_ජූනි_ජූලි_අගෝස්තු_සැප්තැම්බර්_ඔක්තෝබර්_නොවැම්බර්_දෙසැම්බර්".split("_"),monthsShort:"ජන_පෙබ_මාර්_අප්_මැයි_ජූනි_ජූලි_අගෝ_සැප්_ඔක්_නොවැ_දෙසැ".split("_"),weekdays:"ඉරිදා_සඳුදා_අඟහරුවාදා_බදාදා_බ්‍රහස්පතින්දා_සිකුරාදා_සෙනසුරාදා".split("_"),weekdaysShort:"ඉරි_සඳු_අඟ_බදා_බ්‍රහ_සිකු_සෙන".split("_"),weekdaysMin:"ඉ_ස_අ_බ_බ්‍ර_සි_සෙ".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"a h:mm",LTS:"a h:mm:ss",L:"YYYY/MM/DD",LL:"YYYY MMMM D",LLL:"YYYY MMMM D, a h:mm",LLLL:"YYYY MMMM D [වැනි] dddd, a h:mm:ss"},calendar:{sameDay:"[අද] LT[ට]",nextDay:"[හෙට] LT[ට]",nextWeek:"dddd LT[ට]",lastDay:"[ඊයේ] LT[ට]",lastWeek:"[පසුගිය] dddd LT[ට]",sameElse:"L"},relativeTime:{future:"%sකින්",past:"%sකට පෙර",s:"තත්පර කිහිපය",ss:"තත්පර %d",m:"මිනිත්තුව",mm:"මිනිත්තු %d",h:"පැය",hh:"පැය %d",d:"දිනය",dd:"දින %d",M:"මාසය",MM:"මාස %d",y:"වසර",yy:"වසර %d"},dayOfMonthOrdinalParse:/\d{1,2} වැනි/,ordinal:function(e){return e+" වැනි"},meridiemParse:/පෙර වරු|පස් වරු|පෙ.ව|ප.ව./,isPM:function(e){return"ප.ව."===e||"පස් වරු"===e},meridiem:function(e,t,n){return e>11?n?"ප.ව.":"පස් වරු":n?"පෙ.ව.":"පෙර වරු"}})})(n(30381))},64249:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="janu\xe1r_febru\xe1r_marec_apr\xedl_m\xe1j_j\xfan_j\xfal_august_september_okt\xf3ber_november_december".split("_"),n="jan_feb_mar_apr_m\xe1j_j\xfan_j\xfal_aug_sep_okt_nov_dec".split("_");function r(e){return e>1&&e<5}function i(e,t,n,i){var a=e+" ";switch(n){case"s":return t||i?"p\xe1r sek\xfand":"p\xe1r sekundami";case"ss":if(t||i)return a+(r(e)?"sekundy":"sek\xfand");return a+"sekundami";case"m":return t?"min\xfata":i?"min\xfatu":"min\xfatou";case"mm":if(t||i)return a+(r(e)?"min\xfaty":"min\xfat");return a+"min\xfatami";case"h":return t?"hodina":i?"hodinu":"hodinou";case"hh":if(t||i)return a+(r(e)?"hodiny":"hod\xedn");return a+"hodinami";case"d":return t||i?"deň":"dňom";case"dd":if(t||i)return a+(r(e)?"dni":"dn\xed");return a+"dňami";case"M":return t||i?"mesiac":"mesiacom";case"MM":if(t||i)return a+(r(e)?"mesiace":"mesiacov");return a+"mesiacmi";case"y":return t||i?"rok":"rokom";case"yy":if(t||i)return a+(r(e)?"roky":"rokov");return a+"rokmi"}}return e.defineLocale("sk",{months:t,monthsShort:n,weekdays:"nedeľa_pondelok_utorok_streda_štvrtok_piatok_sobota".split("_"),weekdaysShort:"ne_po_ut_st_št_pi_so".split("_"),weekdaysMin:"ne_po_ut_st_št_pi_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd D. MMMM YYYY H:mm"},calendar:{sameDay:"[dnes o] LT",nextDay:"[zajtra o] LT",nextWeek:function(){switch(this.day()){case 0:return"[v nedeľu o] LT";case 1:case 2:return"[v] dddd [o] LT";case 3:return"[v stredu o] LT";case 4:return"[vo štvrtok o] LT";case 5:return"[v piatok o] LT";case 6:return"[v sobotu o] LT"}},lastDay:"[včera o] LT",lastWeek:function(){switch(this.day()){case 0:return"[minul\xfa nedeľu o] LT";case 1:case 2:case 4:case 5:return"[minul\xfd] dddd [o] LT";case 3:return"[minul\xfa stredu o] LT";case 6:return"[minul\xfa sobotu o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"pred %s",s:i,ss:i,m:i,mm:i,h:i,hh:i,d:i,dd:i,M:i,MM:i,y:i,yy:i},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},14985:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t,n,r){var i=e+" ";switch(n){case"s":return t||r?"nekaj sekund":"nekaj sekundami";case"ss":return 1===e?i+=t?"sekundo":"sekundi":2===e?i+=t||r?"sekundi":"sekundah":e<5?i+=t||r?"sekunde":"sekundah":i+="sekund",i;case"m":return t?"ena minuta":"eno minuto";case"mm":return 1===e?i+=t?"minuta":"minuto":2===e?i+=t||r?"minuti":"minutama":e<5?i+=t||r?"minute":"minutami":i+=t||r?"minut":"minutami",i;case"h":return t?"ena ura":"eno uro";case"hh":return 1===e?i+=t?"ura":"uro":2===e?i+=t||r?"uri":"urama":e<5?i+=t||r?"ure":"urami":i+=t||r?"ur":"urami",i;case"d":return t||r?"en dan":"enim dnem";case"dd":return 1===e?i+=t||r?"dan":"dnem":2===e?i+=t||r?"dni":"dnevoma":i+=t||r?"dni":"dnevi",i;case"M":return t||r?"en mesec":"enim mesecem";case"MM":return 1===e?i+=t||r?"mesec":"mesecem":2===e?i+=t||r?"meseca":"mesecema":e<5?i+=t||r?"mesece":"meseci":i+=t||r?"mesecev":"meseci",i;case"y":return t||r?"eno leto":"enim letom";case"yy":return 1===e?i+=t||r?"leto":"letom":2===e?i+=t||r?"leti":"letoma":e<5?i+=t||r?"leta":"leti":i+=t||r?"let":"leti",i}}return e.defineLocale("sl",{months:"januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljek_torek_sreda_četrtek_petek_sobota".split("_"),weekdaysShort:"ned._pon._tor._sre._čet._pet._sob.".split("_"),weekdaysMin:"ne_po_to_sr_če_pe_so".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danes ob] LT",nextDay:"[jutri ob] LT",nextWeek:function(){switch(this.day()){case 0:return"[v] [nedeljo] [ob] LT";case 3:return"[v] [sredo] [ob] LT";case 6:return"[v] [soboto] [ob] LT";case 1:case 2:case 4:case 5:return"[v] dddd [ob] LT"}},lastDay:"[včeraj ob] LT",lastWeek:function(){switch(this.day()){case 0:return"[prejšnjo] [nedeljo] [ob] LT";case 3:return"[prejšnjo] [sredo] [ob] LT";case 6:return"[prejšnjo] [soboto] [ob] LT";case 1:case 2:case 4:case 5:return"[prejšnji] dddd [ob] LT"}},sameElse:"L"},relativeTime:{future:"čez %s",past:"pred %s",s:t,ss:t,m:t,mm:t,h:t,hh:t,d:t,dd:t,M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})(n(30381))},51104:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("sq",{months:"Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_N\xebntor_Dhjetor".split("_"),monthsShort:"Jan_Shk_Mar_Pri_Maj_Qer_Kor_Gus_Sht_Tet_N\xebn_Dhj".split("_"),weekdays:"E Diel_E H\xebn\xeb_E Mart\xeb_E M\xebrkur\xeb_E Enjte_E Premte_E Shtun\xeb".split("_"),weekdaysShort:"Die_H\xebn_Mar_M\xebr_Enj_Pre_Sht".split("_"),weekdaysMin:"D_H_Ma_M\xeb_E_P_Sh".split("_"),weekdaysParseExact:!0,meridiemParse:/PD|MD/,isPM:function(e){return"M"===e.charAt(0)},meridiem:function(e,t,n){return e<12?"PD":"MD"},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Sot n\xeb] LT",nextDay:"[Nes\xebr n\xeb] LT",nextWeek:"dddd [n\xeb] LT",lastDay:"[Dje n\xeb] LT",lastWeek:"dddd [e kaluar n\xeb] LT",sameElse:"L"},relativeTime:{future:"n\xeb %s",past:"%s m\xeb par\xeb",s:"disa sekonda",ss:"%d sekonda",m:"nj\xeb minut\xeb",mm:"%d minuta",h:"nj\xeb or\xeb",hh:"%d or\xeb",d:"nj\xeb dit\xeb",dd:"%d dit\xeb",M:"nj\xeb muaj",MM:"%d muaj",y:"nj\xeb vit",yy:"%d vite"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},79915:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={words:{ss:["секунда","секунде","секунди"],m:["један минут","једне минуте"],mm:["минут","минуте","минута"],h:["један сат","једног сата"],hh:["сат","сата","сати"],dd:["дан","дана","дана"],MM:["месец","месеца","месеци"],yy:["година","године","година"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,r){var i=t.words[r];return 1===r.length?n?i[0]:i[1]:e+" "+t.correctGrammaticalCase(e,i)}};return e.defineLocale("sr-cyrl",{months:"јануар_фебруар_март_април_мај_јун_јул_август_септембар_октобар_новембар_децембар".split("_"),monthsShort:"јан._феб._мар._апр._мај_јун_јул_авг._сеп._окт._нов._дец.".split("_"),monthsParseExact:!0,weekdays:"недеља_понедељак_уторак_среда_четвртак_петак_субота".split("_"),weekdaysShort:"нед._пон._уто._сре._чет._пет._суб.".split("_"),weekdaysMin:"не_по_ут_ср_че_пе_су".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D. M. YYYY.",LL:"D. MMMM YYYY.",LLL:"D. MMMM YYYY. H:mm",LLLL:"dddd, D. MMMM YYYY. H:mm"},calendar:{sameDay:"[данас у] LT",nextDay:"[сутра у] LT",nextWeek:function(){switch(this.day()){case 0:return"[у] [недељу] [у] LT";case 3:return"[у] [среду] [у] LT";case 6:return"[у] [суботу] [у] LT";case 1:case 2:case 4:case 5:return"[у] dddd [у] LT"}},lastDay:"[јуче у] LT",lastWeek:function(){return["[прошле] [недеље] [у] LT","[прошлог] [понедељка] [у] LT","[прошлог] [уторка] [у] LT","[прошле] [среде] [у] LT","[прошлог] [четвртка] [у] LT","[прошлог] [петка] [у] LT","[прошле] [суботе] [у] LT",][this.day()]},sameElse:"L"},relativeTime:{future:"за %s",past:"пре %s",s:"неколико секунди",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"дан",dd:t.translate,M:"месец",MM:t.translate,y:"годину",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})(n(30381))},49131:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={words:{ss:["sekunda","sekunde","sekundi"],m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,r){var i=t.words[r];return 1===r.length?n?i[0]:i[1]:e+" "+t.correctGrammaticalCase(e,i)}};return e.defineLocale("sr",{months:"januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljak_utorak_sreda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sre._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D. M. YYYY.",LL:"D. MMMM YYYY.",LLL:"D. MMMM YYYY. H:mm",LLLL:"dddd, D. MMMM YYYY. H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){return["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT",][this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"dan",dd:t.translate,M:"mesec",MM:t.translate,y:"godinu",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})(n(30381))},85893:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ss",{months:"Bhimbidvwane_Indlovana_Indlov'lenkhulu_Mabasa_Inkhwekhweti_Inhlaba_Kholwane_Ingci_Inyoni_Imphala_Lweti_Ingongoni".split("_"),monthsShort:"Bhi_Ina_Inu_Mab_Ink_Inh_Kho_Igc_Iny_Imp_Lwe_Igo".split("_"),weekdays:"Lisontfo_Umsombuluko_Lesibili_Lesitsatfu_Lesine_Lesihlanu_Umgcibelo".split("_"),weekdaysShort:"Lis_Umb_Lsb_Les_Lsi_Lsh_Umg".split("_"),weekdaysMin:"Li_Us_Lb_Lt_Ls_Lh_Ug".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Namuhla nga] LT",nextDay:"[Kusasa nga] LT",nextWeek:"dddd [nga] LT",lastDay:"[Itolo nga] LT",lastWeek:"dddd [leliphelile] [nga] LT",sameElse:"L"},relativeTime:{future:"nga %s",past:"wenteka nga %s",s:"emizuzwana lomcane",ss:"%d mzuzwana",m:"umzuzu",mm:"%d emizuzu",h:"lihora",hh:"%d emahora",d:"lilanga",dd:"%d emalanga",M:"inyanga",MM:"%d tinyanga",y:"umnyaka",yy:"%d iminyaka"},meridiemParse:/ekuseni|emini|entsambama|ebusuku/,meridiem:function(e,t,n){return e<11?"ekuseni":e<15?"emini":e<19?"entsambama":"ebusuku"},meridiemHour:function(e,t){return(12===e&&(e=0),"ekuseni"===t)?e:"emini"===t?e>=11?e:e+12:"entsambama"===t||"ebusuku"===t?0===e?0:e+12:void 0},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:"%d",week:{dow:1,doy:4}})})(n(30381))},98760:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"s\xf6ndag_m\xe5ndag_tisdag_onsdag_torsdag_fredag_l\xf6rdag".split("_"),weekdaysShort:"s\xf6n_m\xe5n_tis_ons_tor_fre_l\xf6r".split("_"),weekdaysMin:"s\xf6_m\xe5_ti_on_to_fr_l\xf6".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [kl.] HH:mm",LLLL:"dddd D MMMM YYYY [kl.] HH:mm",lll:"D MMM YYYY HH:mm",llll:"ddd D MMM YYYY HH:mm"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Ig\xe5r] LT",nextWeek:"[P\xe5] dddd LT",lastWeek:"[I] dddd[s] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"f\xf6r %s sedan",s:"n\xe5gra sekunder",ss:"%d sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en m\xe5nad",MM:"%d m\xe5nader",y:"ett \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}(\:e|\:a)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?":e":1===t?":a":2===t?":a":":e";return e+n},week:{dow:1,doy:4}})})(n(30381))},91172:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("sw",{months:"Januari_Februari_Machi_Aprili_Mei_Juni_Julai_Agosti_Septemba_Oktoba_Novemba_Desemba".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ago_Sep_Okt_Nov_Des".split("_"),weekdays:"Jumapili_Jumatatu_Jumanne_Jumatano_Alhamisi_Ijumaa_Jumamosi".split("_"),weekdaysShort:"Jpl_Jtat_Jnne_Jtan_Alh_Ijm_Jmos".split("_"),weekdaysMin:"J2_J3_J4_J5_Al_Ij_J1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"hh:mm A",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[leo saa] LT",nextDay:"[kesho saa] LT",nextWeek:"[wiki ijayo] dddd [saat] LT",lastDay:"[jana] LT",lastWeek:"[wiki iliyopita] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s baadaye",past:"tokea %s",s:"hivi punde",ss:"sekunde %d",m:"dakika moja",mm:"dakika %d",h:"saa limoja",hh:"masaa %d",d:"siku moja",dd:"siku %d",M:"mwezi mmoja",MM:"miezi %d",y:"mwaka mmoja",yy:"miaka %d"},week:{dow:1,doy:7}})})(n(30381))},27333:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"௧",2:"௨",3:"௩",4:"௪",5:"௫",6:"௬",7:"௭",8:"௮",9:"௯",0:"௦"},n={"௧":"1","௨":"2","௩":"3","௪":"4","௫":"5","௬":"6","௭":"7","௮":"8","௯":"9","௦":"0"};return e.defineLocale("ta",{months:"ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்".split("_"),monthsShort:"ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்".split("_"),weekdays:"ஞாயிற்றுக்கிழமை_திங்கட்கிழமை_செவ்வாய்கிழமை_புதன்கிழமை_வியாழக்கிழமை_வெள்ளிக்கிழமை_சனிக்கிழமை".split("_"),weekdaysShort:"ஞாயிறு_திங்கள்_செவ்வாய்_புதன்_வியாழன்_வெள்ளி_சனி".split("_"),weekdaysMin:"ஞா_தி_செ_பு_வி_வெ_ச".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, HH:mm",LLLL:"dddd, D MMMM YYYY, HH:mm"},calendar:{sameDay:"[இன்று] LT",nextDay:"[நாளை] LT",nextWeek:"dddd, LT",lastDay:"[நேற்று] LT",lastWeek:"[கடந்த வாரம்] dddd, LT",sameElse:"L"},relativeTime:{future:"%s இல்",past:"%s முன்",s:"ஒரு சில விநாடிகள்",ss:"%d விநாடிகள்",m:"ஒரு நிமிடம்",mm:"%d நிமிடங்கள்",h:"ஒரு மணி நேரம்",hh:"%d மணி நேரம்",d:"ஒரு நாள்",dd:"%d நாட்கள்",M:"ஒரு மாதம்",MM:"%d மாதங்கள்",y:"ஒரு வருடம்",yy:"%d ஆண்டுகள்"},dayOfMonthOrdinalParse:/\d{1,2}வது/,ordinal:function(e){return e+"வது"},preparse:function(e){return e.replace(/[௧௨௩௪௫௬௭௮௯௦]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/யாமம்|வைகறை|காலை|நண்பகல்|எற்பாடு|மாலை/,meridiem:function(e,t,n){if(e<2)return" யாமம்";if(e<6)return" வைகறை";if(e<10)return" காலை";if(e<14)return" நண்பகல்";if(e<18)return" எற்பாடு";else if(e<22)return" மாலை";else return" யாமம்"},meridiemHour:function(e,t){return(12===e&&(e=0),"யாமம்"===t)?e<2?e:e+12:"வைகறை"===t||"காலை"===t?e:"நண்பகல்"===t?e>=10?e:e+12:e+12},week:{dow:0,doy:6}})})(n(30381))},23110:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("te",{months:"జనవరి_ఫిబ్రవరి_మార్చి_ఏప్రిల్_మే_జూన్_జులై_ఆగస్టు_సెప్టెంబర్_అక్టోబర్_నవంబర్_డిసెంబర్".split("_"),monthsShort:"జన._ఫిబ్ర._మార్చి_ఏప్రి._మే_జూన్_జులై_ఆగ._సెప్._అక్టో._నవ._డిసె.".split("_"),monthsParseExact:!0,weekdays:"ఆదివారం_సోమవారం_మంగళవారం_బుధవారం_గురువారం_శుక్రవారం_శనివారం".split("_"),weekdaysShort:"ఆది_సోమ_మంగళ_బుధ_గురు_శుక్ర_శని".split("_"),weekdaysMin:"ఆ_సో_మం_బు_గు_శు_శ".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[నేడు] LT",nextDay:"[రేపు] LT",nextWeek:"dddd, LT",lastDay:"[నిన్న] LT",lastWeek:"[గత] dddd, LT",sameElse:"L"},relativeTime:{future:"%s లో",past:"%s క్రితం",s:"కొన్ని క్షణాలు",ss:"%d సెకన్లు",m:"ఒక నిమిషం",mm:"%d నిమిషాలు",h:"ఒక గంట",hh:"%d గంటలు",d:"ఒక రోజు",dd:"%d రోజులు",M:"ఒక నెల",MM:"%d నెలలు",y:"ఒక సంవత్సరం",yy:"%d సంవత్సరాలు"},dayOfMonthOrdinalParse:/\d{1,2}వ/,ordinal:"%dవ",meridiemParse:/రాత్రి|ఉదయం|మధ్యాహ్నం|సాయంత్రం/,meridiemHour:function(e,t){return(12===e&&(e=0),"రాత్రి"===t)?e<4?e:e+12:"ఉదయం"===t?e:"మధ్యాహ్నం"===t?e>=10?e:e+12:"సాయంత్రం"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"రాత్రి":e<10?"ఉదయం":e<17?"మధ్యాహ్నం":e<20?"సాయంత్రం":"రాత్రి"},week:{dow:0,doy:6}})})(n(30381))},52095:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("tet",{months:"Janeiru_Fevereiru_Marsu_Abril_Maiu_Ju\xf1u_Jullu_Agustu_Setembru_Outubru_Novembru_Dezembru".split("_"),monthsShort:"Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez".split("_"),weekdays:"Domingu_Segunda_Tersa_Kuarta_Kinta_Sesta_Sabadu".split("_"),weekdaysShort:"Dom_Seg_Ters_Kua_Kint_Sest_Sab".split("_"),weekdaysMin:"Do_Seg_Te_Ku_Ki_Ses_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Ohin iha] LT",nextDay:"[Aban iha] LT",nextWeek:"dddd [iha] LT",lastDay:"[Horiseik iha] LT",lastWeek:"dddd [semana kotuk] [iha] LT",sameElse:"L"},relativeTime:{future:"iha %s",past:"%s liuba",s:"segundu balun",ss:"segundu %d",m:"minutu ida",mm:"minutu %d",h:"oras ida",hh:"oras %d",d:"loron ida",dd:"loron %d",M:"fulan ida",MM:"fulan %d",y:"tinan ida",yy:"tinan %d"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n},week:{dow:1,doy:4}})})(n(30381))},27321:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={0:"-ум",1:"-ум",2:"-юм",3:"-юм",4:"-ум",5:"-ум",6:"-ум",7:"-ум",8:"-ум",9:"-ум",10:"-ум",12:"-ум",13:"-ум",20:"-ум",30:"-юм",40:"-ум",50:"-ум",60:"-ум",70:"-ум",80:"-ум",90:"-ум",100:"-ум"};return e.defineLocale("tg",{months:{format:"январи_феврали_марти_апрели_майи_июни_июли_августи_сентябри_октябри_ноябри_декабри".split("_"),standalone:"январ_феврал_март_апрел_май_июн_июл_август_сентябр_октябр_ноябр_декабр".split("_")},monthsShort:"янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек".split("_"),weekdays:"якшанбе_душанбе_сешанбе_чоршанбе_панҷшанбе_ҷумъа_шанбе".split("_"),weekdaysShort:"яшб_дшб_сшб_чшб_пшб_ҷум_шнб".split("_"),weekdaysMin:"яш_дш_сш_чш_пш_ҷм_шб".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Имрӯз соати] LT",nextDay:"[Фардо соати] LT",lastDay:"[Дирӯз соати] LT",nextWeek:"dddd[и] [ҳафтаи оянда соати] LT",lastWeek:"dddd[и] [ҳафтаи гузашта соати] LT",sameElse:"L"},relativeTime:{future:"баъди %s",past:"%s пеш",s:"якчанд сония",m:"як дақиқа",mm:"%d дақиқа",h:"як соат",hh:"%d соат",d:"як рӯз",dd:"%d рӯз",M:"як моҳ",MM:"%d моҳ",y:"як сол",yy:"%d сол"},meridiemParse:/шаб|субҳ|рӯз|бегоҳ/,meridiemHour:function(e,t){return(12===e&&(e=0),"шаб"===t)?e<4?e:e+12:"субҳ"===t?e:"рӯз"===t?e>=11?e:e+12:"бегоҳ"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"шаб":e<11?"субҳ":e<16?"рӯз":e<19?"бегоҳ":"шаб"},dayOfMonthOrdinalParse:/\d{1,2}-(ум|юм)/,ordinal:function(e){var n=e%10,r=e>=100?100:null;return e+(t[e]||t[n]||t[r])},week:{dow:1,doy:7}})})(n(30381))},9041:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"ม.ค._ก.พ._มี.ค._เม.ย._พ.ค._มิ.ย._ก.ค._ส.ค._ก.ย._ต.ค._พ.ย._ธ.ค.".split("_"),monthsParseExact:!0,weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา H:mm",LLLL:"วันddddที่ D MMMM YYYY เวลา H:mm"},meridiemParse:/ก่อนเที่ยง|หลังเที่ยง/,isPM:function(e){return"หลังเที่ยง"===e},meridiem:function(e,t,n){return e<12?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",ss:"%d วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",w:"1 สัปดาห์",ww:"%d สัปดาห์",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}})})(n(30381))},19005:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"'inji",5:"'inji",8:"'inji",70:"'inji",80:"'inji",2:"'nji",7:"'nji",20:"'nji",50:"'nji",3:"'\xfcnji",4:"'\xfcnji",100:"'\xfcnji",6:"'njy",9:"'unjy",10:"'unjy",30:"'unjy",60:"'ynjy",90:"'ynjy"};return e.defineLocale("tk",{months:"\xddanwar_Fewral_Mart_Aprel_Ma\xfd_I\xfdun_I\xfdul_Awgust_Sent\xfdabr_Okt\xfdabr_No\xfdabr_Dekabr".split("_"),monthsShort:"\xddan_Few_Mar_Apr_Ma\xfd_I\xfdn_I\xfdl_Awg_Sen_Okt_No\xfd_Dek".split("_"),weekdays:"\xddekşenbe_Duşenbe_Sişenbe_\xc7arşenbe_Penşenbe_Anna_Şenbe".split("_"),weekdaysShort:"\xddek_Duş_Siş_\xc7ar_Pen_Ann_Şen".split("_"),weekdaysMin:"\xddk_Dş_Sş_\xc7r_Pn_An_Şn".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bug\xfcn sagat] LT",nextDay:"[ertir sagat] LT",nextWeek:"[indiki] dddd [sagat] LT",lastDay:"[d\xfc\xfdn] LT",lastWeek:"[ge\xe7en] dddd [sagat] LT",sameElse:"L"},relativeTime:{future:"%s soň",past:"%s \xf6ň",s:"birn\xe4\xe7e sekunt",m:"bir minut",mm:"%d minut",h:"bir sagat",hh:"%d sagat",d:"bir g\xfcn",dd:"%d g\xfcn",M:"bir a\xfd",MM:"%d a\xfd",y:"bir \xfdyl",yy:"%d \xfdyl"},ordinal:function(e,n){switch(n){case"d":case"D":case"Do":case"DD":return e;default:if(0===e)return e+"'unjy";var r=e%10,i=e%100-r,a=e>=100?100:null;return e+(t[r]||t[i]||t[a])}},week:{dow:1,doy:7}})})(n(30381))},75768:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("tl-ph",{months:"Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre".split("_"),monthsShort:"Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis".split("_"),weekdays:"Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado".split("_"),weekdaysShort:"Lin_Lun_Mar_Miy_Huw_Biy_Sab".split("_"),weekdaysMin:"Li_Lu_Ma_Mi_Hu_Bi_Sab".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"MM/D/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY HH:mm",LLLL:"dddd, MMMM DD, YYYY HH:mm"},calendar:{sameDay:"LT [ngayong araw]",nextDay:"[Bukas ng] LT",nextWeek:"LT [sa susunod na] dddd",lastDay:"LT [kahapon]",lastWeek:"LT [noong nakaraang] dddd",sameElse:"L"},relativeTime:{future:"sa loob ng %s",past:"%s ang nakalipas",s:"ilang segundo",ss:"%d segundo",m:"isang minuto",mm:"%d minuto",h:"isang oras",hh:"%d oras",d:"isang araw",dd:"%d araw",M:"isang buwan",MM:"%d buwan",y:"isang taon",yy:"%d taon"},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:function(e){return e},week:{dow:1,doy:4}})})(n(30381))},89444:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t="pagh_wa’_cha’_wej_loS_vagh_jav_Soch_chorgh_Hut".split("_");function n(e){var t=e;return -1!==e.indexOf("jaj")?t.slice(0,-3)+"leS":-1!==e.indexOf("jar")?t.slice(0,-3)+"waQ":-1!==e.indexOf("DIS")?t.slice(0,-3)+"nem":t+" pIq"}function r(e){var t=e;return -1!==e.indexOf("jaj")?t.slice(0,-3)+"Hu’":-1!==e.indexOf("jar")?t.slice(0,-3)+"wen":-1!==e.indexOf("DIS")?t.slice(0,-3)+"ben":t+" ret"}function i(e,t,n,r){var i=a(e);switch(n){case"ss":return i+" lup";case"mm":return i+" tup";case"hh":return i+" rep";case"dd":return i+" jaj";case"MM":return i+" jar";case"yy":return i+" DIS"}}function a(e){var n=Math.floor(e%1e3/100),r=Math.floor(e%100/10),i=e%10,a="";return n>0&&(a+=t[n]+"vatlh"),r>0&&(a+=(""!==a?" ":"")+t[r]+"maH"),i>0&&(a+=(""!==a?" ":"")+t[i]),""===a?"pagh":a}return e.defineLocale("tlh",{months:"tera’ jar wa’_tera’ jar cha’_tera’ jar wej_tera’ jar loS_tera’ jar vagh_tera’ jar jav_tera’ jar Soch_tera’ jar chorgh_tera’ jar Hut_tera’ jar wa’maH_tera’ jar wa’maH wa’_tera’ jar wa’maH cha’".split("_"),monthsShort:"jar wa’_jar cha’_jar wej_jar loS_jar vagh_jar jav_jar Soch_jar chorgh_jar Hut_jar wa’maH_jar wa’maH wa’_jar wa’maH cha’".split("_"),monthsParseExact:!0,weekdays:"lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj".split("_"),weekdaysShort:"lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj".split("_"),weekdaysMin:"lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[DaHjaj] LT",nextDay:"[wa’leS] LT",nextWeek:"LLL",lastDay:"[wa’Hu’] LT",lastWeek:"LLL",sameElse:"L"},relativeTime:{future:n,past:r,s:"puS lup",ss:i,m:"wa’ tup",mm:i,h:"wa’ rep",hh:i,d:"wa’ jaj",dd:i,M:"wa’ jar",MM:i,y:"wa’ DIS",yy:i},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})(n(30381))},72397:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'\xfcnc\xfc",4:"'\xfcnc\xfc",100:"'\xfcnc\xfc",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};return e.defineLocale("tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eyl\xfcl_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_\xc7arşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_\xc7ar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_\xc7a_Pe_Cu_Ct".split("_"),meridiem:function(e,t,n){return e<12?n?"\xf6\xf6":"\xd6\xd6":n?"\xf6s":"\xd6S"},meridiemParse:/öö|ÖÖ|ös|ÖS/,isPM:function(e){return"\xf6s"===e||"\xd6S"===e},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bug\xfcn saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[gelecek] dddd [saat] LT",lastDay:"[d\xfcn] LT",lastWeek:"[ge\xe7en] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s \xf6nce",s:"birka\xe7 saniye",ss:"%d saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir g\xfcn",dd:"%d g\xfcn",w:"bir hafta",ww:"%d hafta",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinal:function(e,n){switch(n){case"d":case"D":case"Do":case"DD":return e;default:if(0===e)return e+"'ıncı";var r=e%10,i=e%100-r,a=e>=100?100:null;return e+(t[r]||t[i]||t[a])}},week:{dow:1,doy:7}})})(n(30381))},28254:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t=e.defineLocale("tzl",{months:"Januar_Fevraglh_Mar\xe7_Avr\xefu_Mai_G\xfcn_Julia_Guscht_Setemvar_Listop\xe4ts_Noemvar_Zecemvar".split("_"),monthsShort:"Jan_Fev_Mar_Avr_Mai_G\xfcn_Jul_Gus_Set_Lis_Noe_Zec".split("_"),weekdays:"S\xfaladi_L\xfane\xe7i_Maitzi_M\xe1rcuri_Xh\xfaadi_Vi\xe9ner\xe7i_S\xe1turi".split("_"),weekdaysShort:"S\xfal_L\xfan_Mai_M\xe1r_Xh\xfa_Vi\xe9_S\xe1t".split("_"),weekdaysMin:"S\xfa_L\xfa_Ma_M\xe1_Xh_Vi_S\xe1".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"D. MMMM [dallas] YYYY",LLL:"D. MMMM [dallas] YYYY HH.mm",LLLL:"dddd, [li] D. MMMM [dallas] YYYY HH.mm"},meridiemParse:/d\'o|d\'a/i,isPM:function(e){return"d'o"===e.toLowerCase()},meridiem:function(e,t,n){return e>11?n?"d'o":"D'O":n?"d'a":"D'A"},calendar:{sameDay:"[oxhi \xe0] LT",nextDay:"[dem\xe0 \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[ieiri \xe0] LT",lastWeek:"[s\xfcr el] dddd [lasteu \xe0] LT",sameElse:"L"},relativeTime:{future:"osprei %s",past:"ja%s",s:n,ss:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});function n(e,t,n,r){var i={s:["viensas secunds","'iensas secunds"],ss:[e+" secunds",""+e+" secunds"],m:["'n m\xedut","'iens m\xedut"],mm:[e+" m\xeduts",""+e+" m\xeduts"],h:["'n \xfeora","'iensa \xfeora"],hh:[e+" \xfeoras",""+e+" \xfeoras"],d:["'n ziua","'iensa ziua"],dd:[e+" ziuas",""+e+" ziuas"],M:["'n mes","'iens mes"],MM:[e+" mesen",""+e+" mesen"],y:["'n ar","'iens ar"],yy:[e+" ars",""+e+" ars"]};return r?i[n][0]:t?i[n][0]:i[n][1]}return t})(n(30381))},30699:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("tzm-latn",{months:"innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir".split("_"),monthsShort:"innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir".split("_"),weekdays:"asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas".split("_"),weekdaysShort:"asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas".split("_"),weekdaysMin:"asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[asdkh g] LT",nextDay:"[aska g] LT",nextWeek:"dddd [g] LT",lastDay:"[assant g] LT",lastWeek:"dddd [g] LT",sameElse:"L"},relativeTime:{future:"dadkh s yan %s",past:"yan %s",s:"imik",ss:"%d imik",m:"minuḍ",mm:"%d minuḍ",h:"saɛa",hh:"%d tassaɛin",d:"ass",dd:"%d ossan",M:"ayowr",MM:"%d iyyirn",y:"asgas",yy:"%d isgasn"},week:{dow:6,doy:12}})})(n(30381))},51106:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("tzm",{months:"ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ".split("_"),monthsShort:"ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ".split("_"),weekdays:"ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ".split("_"),weekdaysShort:"ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ".split("_"),weekdaysMin:"ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[ⴰⵙⴷⵅ ⴴ] LT",nextDay:"[ⴰⵙⴽⴰ ⴴ] LT",nextWeek:"dddd [ⴴ] LT",lastDay:"[ⴰⵚⴰⵏⵜ ⴴ] LT",lastWeek:"dddd [ⴴ] LT",sameElse:"L"},relativeTime:{future:"ⴷⴰⴷⵅ ⵙ ⵢⴰⵏ %s",past:"ⵢⴰⵏ %s",s:"ⵉⵎⵉⴽ",ss:"%d ⵉⵎⵉⴽ",m:"ⵎⵉⵏⵓⴺ",mm:"%d ⵎⵉⵏⵓⴺ",h:"ⵙⴰⵄⴰ",hh:"%d ⵜⴰⵙⵙⴰⵄⵉⵏ",d:"ⴰⵙⵙ",dd:"%d oⵙⵙⴰⵏ",M:"ⴰⵢoⵓⵔ",MM:"%d ⵉⵢⵢⵉⵔⵏ",y:"ⴰⵙⴳⴰⵙ",yy:"%d ⵉⵙⴳⴰⵙⵏ"},week:{dow:6,doy:12}})})(n(30381))},9288:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("ug-cn",{months:"يانۋار_فېۋرال_مارت_ئاپرېل_ماي_ئىيۇن_ئىيۇل_ئاۋغۇست_سېنتەبىر_ئۆكتەبىر_نويابىر_دېكابىر".split("_"),monthsShort:"يانۋار_فېۋرال_مارت_ئاپرېل_ماي_ئىيۇن_ئىيۇل_ئاۋغۇست_سېنتەبىر_ئۆكتەبىر_نويابىر_دېكابىر".split("_"),weekdays:"يەكشەنبە_دۈشەنبە_سەيشەنبە_چارشەنبە_پەيشەنبە_جۈمە_شەنبە".split("_"),weekdaysShort:"يە_دۈ_سە_چا_پە_جۈ_شە".split("_"),weekdaysMin:"يە_دۈ_سە_چا_پە_جۈ_شە".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY-يىلىM-ئاينىڭD-كۈنى",LLL:"YYYY-يىلىM-ئاينىڭD-كۈنى، HH:mm",LLLL:"dddd، YYYY-يىلىM-ئاينىڭD-كۈنى، HH:mm"},meridiemParse:/يېرىم كېچە|سەھەر|چۈشتىن بۇرۇن|چۈش|چۈشتىن كېيىن|كەچ/,meridiemHour:function(e,t){return(12===e&&(e=0),"يېرىم كېچە"===t||"سەھەر"===t||"چۈشتىن بۇرۇن"===t)?e:"چۈشتىن كېيىن"===t||"كەچ"===t?e+12:e>=11?e:e+12},meridiem:function(e,t,n){var r=100*e+t;if(r<600)return"يېرىم كېچە";if(r<900)return"سەھەر";if(r<1130)return"چۈشتىن بۇرۇن";if(r<1230)return"چۈش";if(r<1800)return"چۈشتىن كېيىن";else return"كەچ"},calendar:{sameDay:"[بۈگۈن سائەت] LT",nextDay:"[ئەتە سائەت] LT",nextWeek:"[كېلەركى] dddd [سائەت] LT",lastDay:"[تۆنۈگۈن] LT",lastWeek:"[ئالدىنقى] dddd [سائەت] LT",sameElse:"L"},relativeTime:{future:"%s كېيىن",past:"%s بۇرۇن",s:"نەچچە سېكونت",ss:"%d سېكونت",m:"بىر مىنۇت",mm:"%d مىنۇت",h:"بىر سائەت",hh:"%d سائەت",d:"بىر كۈن",dd:"%d كۈن",M:"بىر ئاي",MM:"%d ئاي",y:"بىر يىل",yy:"%d يىل"},dayOfMonthOrdinalParse:/\d{1,2}(-كۈنى|-ئاي|-ھەپتە)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"-كۈنى";case"w":case"W":return e+"-ھەپتە";default:return e}},preparse:function(e){return e.replace(/،/g,",")},postformat:function(e){return e.replace(/,/g,"،")},week:{dow:1,doy:7}})})(n(30381))},67691:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +function t(e,t){var n=e.split("_");return t%10==1&&t%100!=11?n[0]:t%10>=2&&t%10<=4&&(t%100<10||t%100>=20)?n[1]:n[2]}function n(e,n,r){var i={ss:n?"секунда_секунди_секунд":"секунду_секунди_секунд",mm:n?"хвилина_хвилини_хвилин":"хвилину_хвилини_хвилин",hh:n?"година_години_годин":"годину_години_годин",dd:"день_дні_днів",MM:"місяць_місяці_місяців",yy:"рік_роки_років"};return"m"===r?n?"хвилина":"хвилину":"h"===r?n?"година":"годину":e+" "+t(i[r],+e)}function r(e,t){var n,r={nominative:"неділя_понеділок_вівторок_середа_четвер_п’ятниця_субота".split("_"),accusative:"неділю_понеділок_вівторок_середу_четвер_п’ятницю_суботу".split("_"),genitive:"неділі_понеділка_вівторка_середи_четверга_п’ятниці_суботи".split("_")};return!0===e?r.nominative.slice(1,7).concat(r.nominative.slice(0,1)):e?r[n=/(\[[ВвУу]\]) ?dddd/.test(t)?"accusative":/\[?(?:минулої|наступної)? ?\] ?dddd/.test(t)?"genitive":"nominative"][e.day()]:r.nominative}function i(e){return function(){return e+"о"+(11===this.hours()?"б":"")+"] LT"}}return e.defineLocale("uk",{months:{format:"січня_лютого_березня_квітня_травня_червня_липня_серпня_вересня_жовтня_листопада_грудня".split("_"),standalone:"січень_лютий_березень_квітень_травень_червень_липень_серпень_вересень_жовтень_листопад_грудень".split("_")},monthsShort:"січ_лют_бер_квіт_трав_черв_лип_серп_вер_жовт_лист_груд".split("_"),weekdays:r,weekdaysShort:"нд_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"нд_пн_вт_ср_чт_пт_сб".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY р.",LLL:"D MMMM YYYY р., HH:mm",LLLL:"dddd, D MMMM YYYY р., HH:mm"},calendar:{sameDay:i("[Сьогодні "),nextDay:i("[Завтра "),lastDay:i("[Вчора "),nextWeek:i("[У] dddd ["),lastWeek:function(){switch(this.day()){case 0:case 3:case 5:case 6:return i("[Минулої] dddd [").call(this);case 1:case 2:case 4:return i("[Минулого] dddd [").call(this)}},sameElse:"L"},relativeTime:{future:"за %s",past:"%s тому",s:"декілька секунд",ss:n,m:n,mm:n,h:"годину",hh:n,d:"день",dd:n,M:"місяць",MM:n,y:"рік",yy:n},meridiemParse:/ночі|ранку|дня|вечора/,isPM:function(e){return/^(дня|вечора)$/.test(e)},meridiem:function(e,t,n){return e<4?"ночі":e<12?"ранку":e<17?"дня":"вечора"},dayOfMonthOrdinalParse:/\d{1,2}-(й|го)/,ordinal:function(e,t){switch(t){case"M":case"d":case"DDD":case"w":case"W":return e+"-й";case"D":return e+"-го";default:return e}},week:{dow:1,doy:7}})})(n(30381))},13795:function(e,t,n){var r,i;r=this,(i=function(e){"use strict";//! moment.js locale configuration +var t=["جنوری","فروری","مارچ","اپریل","مئی","جون","جولائی","اگست","ستمبر","اکتوبر","نومبر","دسمبر",],n=["اتوار","پیر","منگل","بدھ","جمعرات","جمعہ","ہفتہ"];return e.defineLocale("ur",{months:t,monthsShort:t,weekdays:n,weekdaysShort:n,weekdaysMin:n,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd، D MMMM YYYY HH:mm"},meridiemParse:/صبح|شام/,isPM:function(e){return"شام"===e},meridiem:function(e,t,n){return e<12?"صبح":"شام"},calendar:{sameDay:"[آج بوقت] LT",nextDay:"[کل بوقت] LT",nextWeek:"dddd [بوقت] LT",lastDay:"[گذشتہ روز بوقت] LT",lastWeek:"[گذشتہ] dddd [بوقت] LT",sameElse:"L"},relativeTime:{future:"%s بعد",past:"%s قبل",s:"چند سیکنڈ",ss:"%d سیکنڈ",m:"ایک منٹ",mm:"%d منٹ",h:"ایک گھنٹہ",hh:"%d گھنٹے",d:"ایک دن",dd:"%d دن",M:"ایک ماہ",MM:"%d ماہ",y:"ایک سال",yy:"%d سال"},preparse:function(e){return e.replace(/،/g,",")},postformat:function(e){return e.replace(/,/g,"،")},week:{dow:1,doy:4}})})(n(30381))},60588:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("uz-latn",{months:"Yanvar_Fevral_Mart_Aprel_May_Iyun_Iyul_Avgust_Sentabr_Oktabr_Noyabr_Dekabr".split("_"),monthsShort:"Yan_Fev_Mar_Apr_May_Iyun_Iyul_Avg_Sen_Okt_Noy_Dek".split("_"),weekdays:"Yakshanba_Dushanba_Seshanba_Chorshanba_Payshanba_Juma_Shanba".split("_"),weekdaysShort:"Yak_Dush_Sesh_Chor_Pay_Jum_Shan".split("_"),weekdaysMin:"Ya_Du_Se_Cho_Pa_Ju_Sha".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"D MMMM YYYY, dddd HH:mm"},calendar:{sameDay:"[Bugun soat] LT [da]",nextDay:"[Ertaga] LT [da]",nextWeek:"dddd [kuni soat] LT [da]",lastDay:"[Kecha soat] LT [da]",lastWeek:"[O'tgan] dddd [kuni soat] LT [da]",sameElse:"L"},relativeTime:{future:"Yaqin %s ichida",past:"Bir necha %s oldin",s:"soniya",ss:"%d soniya",m:"bir daqiqa",mm:"%d daqiqa",h:"bir soat",hh:"%d soat",d:"bir kun",dd:"%d kun",M:"bir oy",MM:"%d oy",y:"bir yil",yy:"%d yil"},week:{dow:1,doy:7}})})(n(30381))},6791:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("uz",{months:"январ_феврал_март_апрел_май_июн_июл_август_сентябр_октябр_ноябр_декабр".split("_"),monthsShort:"янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек".split("_"),weekdays:"Якшанба_Душанба_Сешанба_Чоршанба_Пайшанба_Жума_Шанба".split("_"),weekdaysShort:"Якш_Душ_Сеш_Чор_Пай_Жум_Шан".split("_"),weekdaysMin:"Як_Ду_Се_Чо_Па_Жу_Ша".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"D MMMM YYYY, dddd HH:mm"},calendar:{sameDay:"[Бугун соат] LT [да]",nextDay:"[Эртага] LT [да]",nextWeek:"dddd [куни соат] LT [да]",lastDay:"[Кеча соат] LT [да]",lastWeek:"[Утган] dddd [куни соат] LT [да]",sameElse:"L"},relativeTime:{future:"Якин %s ичида",past:"Бир неча %s олдин",s:"фурсат",ss:"%d фурсат",m:"бир дакика",mm:"%d дакика",h:"бир соат",hh:"%d соат",d:"бир кун",dd:"%d кун",M:"бир ой",MM:"%d ой",y:"бир йил",yy:"%d йил"},week:{dow:1,doy:7}})})(n(30381))},65666:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("vi",{months:"th\xe1ng 1_th\xe1ng 2_th\xe1ng 3_th\xe1ng 4_th\xe1ng 5_th\xe1ng 6_th\xe1ng 7_th\xe1ng 8_th\xe1ng 9_th\xe1ng 10_th\xe1ng 11_th\xe1ng 12".split("_"),monthsShort:"Thg 01_Thg 02_Thg 03_Thg 04_Thg 05_Thg 06_Thg 07_Thg 08_Thg 09_Thg 10_Thg 11_Thg 12".split("_"),monthsParseExact:!0,weekdays:"chủ nhật_thứ hai_thứ ba_thứ tư_thứ năm_thứ s\xe1u_thứ bảy".split("_"),weekdaysShort:"CN_T2_T3_T4_T5_T6_T7".split("_"),weekdaysMin:"CN_T2_T3_T4_T5_T6_T7".split("_"),weekdaysParseExact:!0,meridiemParse:/sa|ch/i,isPM:function(e){return/^ch$/i.test(e)},meridiem:function(e,t,n){return e<12?n?"sa":"SA":n?"ch":"CH"},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM [năm] YYYY",LLL:"D MMMM [năm] YYYY HH:mm",LLLL:"dddd, D MMMM [năm] YYYY HH:mm",l:"DD/M/YYYY",ll:"D MMM YYYY",lll:"D MMM YYYY HH:mm",llll:"ddd, D MMM YYYY HH:mm"},calendar:{sameDay:"[H\xf4m nay l\xfac] LT",nextDay:"[Ng\xe0y mai l\xfac] LT",nextWeek:"dddd [tuần tới l\xfac] LT",lastDay:"[H\xf4m qua l\xfac] LT",lastWeek:"dddd [tuần trước l\xfac] LT",sameElse:"L"},relativeTime:{future:"%s tới",past:"%s trước",s:"v\xe0i gi\xe2y",ss:"%d gi\xe2y",m:"một ph\xfat",mm:"%d ph\xfat",h:"một giờ",hh:"%d giờ",d:"một ng\xe0y",dd:"%d ng\xe0y",w:"một tuần",ww:"%d tuần",M:"một th\xe1ng",MM:"%d th\xe1ng",y:"một năm",yy:"%d năm"},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:function(e){return e},week:{dow:1,doy:4}})})(n(30381))},14378:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("x-pseudo",{months:"J~\xe1\xf1\xfa\xe1~r\xfd_F~\xe9br\xfa~\xe1r\xfd_~M\xe1rc~h_\xc1p~r\xedl_~M\xe1\xfd_~J\xfa\xf1\xe9~_J\xfal~\xfd_\xc1\xfa~g\xfast~_S\xe9p~t\xe9mb~\xe9r_\xd3~ct\xf3b~\xe9r_\xd1~\xf3v\xe9m~b\xe9r_~D\xe9c\xe9~mb\xe9r".split("_"),monthsShort:"J~\xe1\xf1_~F\xe9b_~M\xe1r_~\xc1pr_~M\xe1\xfd_~J\xfa\xf1_~J\xfal_~\xc1\xfag_~S\xe9p_~\xd3ct_~\xd1\xf3v_~D\xe9c".split("_"),monthsParseExact:!0,weekdays:"S~\xfa\xf1d\xe1~\xfd_M\xf3~\xf1d\xe1\xfd~_T\xfa\xe9~sd\xe1\xfd~_W\xe9d~\xf1\xe9sd~\xe1\xfd_T~h\xfars~d\xe1\xfd_~Fr\xedd~\xe1\xfd_S~\xe1t\xfar~d\xe1\xfd".split("_"),weekdaysShort:"S~\xfa\xf1_~M\xf3\xf1_~T\xfa\xe9_~W\xe9d_~Th\xfa_~Fr\xed_~S\xe1t".split("_"),weekdaysMin:"S~\xfa_M\xf3~_T\xfa_~W\xe9_T~h_Fr~_S\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[T~\xf3d\xe1~\xfd \xe1t] LT",nextDay:"[T~\xf3m\xf3~rr\xf3~w \xe1t] LT",nextWeek:"dddd [\xe1t] LT",lastDay:"[\xdd~\xe9st~\xe9rd\xe1~\xfd \xe1t] LT",lastWeek:"[L~\xe1st] dddd [\xe1t] LT",sameElse:"L"},relativeTime:{future:"\xed~\xf1 %s",past:"%s \xe1~g\xf3",s:"\xe1 ~f\xe9w ~s\xe9c\xf3~\xf1ds",ss:"%d s~\xe9c\xf3\xf1~ds",m:"\xe1 ~m\xed\xf1~\xfat\xe9",mm:"%d m~\xed\xf1\xfa~t\xe9s",h:"\xe1~\xf1 h\xf3~\xfar",hh:"%d h~\xf3\xfars",d:"\xe1 ~d\xe1\xfd",dd:"%d d~\xe1\xfds",M:"\xe1 ~m\xf3\xf1~th",MM:"%d m~\xf3\xf1t~hs",y:"\xe1 ~\xfd\xe9\xe1r",yy:"%d \xfd~\xe9\xe1rs"},dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10,n=1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n},week:{dow:1,doy:4}})})(n(30381))},75805:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("yo",{months:"Sẹ́rẹ́_Èrèlè_Ẹrẹ̀nà_Ìgbé_Èbibi_Òkùdu_Agẹmo_Ògún_Owewe_Ọ̀wàrà_Bélú_Ọ̀pẹ̀̀".split("_"),monthsShort:"Sẹ́r_Èrl_Ẹrn_Ìgb_Èbi_Òkù_Agẹ_Ògú_Owe_Ọ̀wà_Bél_Ọ̀pẹ̀̀".split("_"),weekdays:"Àìkú_Ajé_Ìsẹ́gun_Ọjọ́rú_Ọjọ́bọ_Ẹtì_Àbámẹ́ta".split("_"),weekdaysShort:"Àìk_Ajé_Ìsẹ́_Ọjr_Ọjb_Ẹtì_Àbá".split("_"),weekdaysMin:"Àì_Aj_Ìs_Ọr_Ọb_Ẹt_Àb".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Ònì ni] LT",nextDay:"[Ọ̀la ni] LT",nextWeek:"dddd [Ọsẹ̀ tón'bọ] [ni] LT",lastDay:"[Àna ni] LT",lastWeek:"dddd [Ọsẹ̀ tólọ́] [ni] LT",sameElse:"L"},relativeTime:{future:"ní %s",past:"%s kọjá",s:"ìsẹjú aayá die",ss:"aayá %d",m:"ìsẹjú kan",mm:"ìsẹjú %d",h:"wákati kan",hh:"wákati %d",d:"ọjọ́ kan",dd:"ọjọ́ %d",M:"osù kan",MM:"osù %d",y:"ọdún kan",yy:"ọdún %d"},dayOfMonthOrdinalParse:/ọjọ́\s\d{1,2}/,ordinal:"ọjọ́ %d",week:{dow:1,doy:4}})})(n(30381))},83839:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日Ah点mm分",LLLL:"YYYY年M月D日ddddAh点mm分",l:"YYYY/M/D",ll:"YYYY年M月D日",lll:"YYYY年M月D日 HH:mm",llll:"YYYY年M月D日dddd HH:mm"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(e,t){return(12===e&&(e=0),"凌晨"===t||"早上"===t||"上午"===t)?e:"下午"===t||"晚上"===t?e+12:e>=11?e:e+12},meridiem:function(e,t,n){var r=100*e+t;if(r<600)return"凌晨";if(r<900)return"早上";if(r<1130)return"上午";if(r<1230)return"中午";if(r<1800)return"下午";else return"晚上"},calendar:{sameDay:"[今天]LT",nextDay:"[明天]LT",nextWeek:function(e){return e.week()!==this.week()?"[下]dddLT":"[本]dddLT"},lastDay:"[昨天]LT",lastWeek:function(e){return this.week()!==e.week()?"[上]dddLT":"[本]dddLT"},sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(日|月|周)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"周";default:return e}},relativeTime:{future:"%s后",past:"%s前",s:"几秒",ss:"%d 秒",m:"1 分钟",mm:"%d 分钟",h:"1 小时",hh:"%d 小时",d:"1 天",dd:"%d 天",w:"1 周",ww:"%d 周",M:"1 个月",MM:"%d 个月",y:"1 年",yy:"%d 年"},week:{dow:1,doy:4}})})(n(30381))},55726:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("zh-hk",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"週日_週一_週二_週三_週四_週五_週六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日 HH:mm",LLLL:"YYYY年M月D日dddd HH:mm",l:"YYYY/M/D",ll:"YYYY年M月D日",lll:"YYYY年M月D日 HH:mm",llll:"YYYY年M月D日dddd HH:mm"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(e,t){return(12===e&&(e=0),"凌晨"===t||"早上"===t||"上午"===t)?e:"中午"===t?e>=11?e:e+12:"下午"===t||"晚上"===t?e+12:void 0},meridiem:function(e,t,n){var r=100*e+t;if(r<600)return"凌晨";if(r<900)return"早上";if(r<1200)return"上午";if(1200===r)return"中午";if(r<1800)return"下午";else return"晚上"},calendar:{sameDay:"[今天]LT",nextDay:"[明天]LT",nextWeek:"[下]ddddLT",lastDay:"[昨天]LT",lastWeek:"[上]ddddLT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(日|月|週)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"週";default:return e}},relativeTime:{future:"%s後",past:"%s前",s:"幾秒",ss:"%d 秒",m:"1 分鐘",mm:"%d 分鐘",h:"1 小時",hh:"%d 小時",d:"1 天",dd:"%d 天",M:"1 個月",MM:"%d 個月",y:"1 年",yy:"%d 年"}})})(n(30381))},99807:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("zh-mo",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"週日_週一_週二_週三_週四_週五_週六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"YYYY年M月D日",LLL:"YYYY年M月D日 HH:mm",LLLL:"YYYY年M月D日dddd HH:mm",l:"D/M/YYYY",ll:"YYYY年M月D日",lll:"YYYY年M月D日 HH:mm",llll:"YYYY年M月D日dddd HH:mm"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(e,t){return(12===e&&(e=0),"凌晨"===t||"早上"===t||"上午"===t)?e:"中午"===t?e>=11?e:e+12:"下午"===t||"晚上"===t?e+12:void 0},meridiem:function(e,t,n){var r=100*e+t;if(r<600)return"凌晨";if(r<900)return"早上";if(r<1130)return"上午";if(r<1230)return"中午";if(r<1800)return"下午";else return"晚上"},calendar:{sameDay:"[今天] LT",nextDay:"[明天] LT",nextWeek:"[下]dddd LT",lastDay:"[昨天] LT",lastWeek:"[上]dddd LT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(日|月|週)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"週";default:return e}},relativeTime:{future:"%s內",past:"%s前",s:"幾秒",ss:"%d 秒",m:"1 分鐘",mm:"%d 分鐘",h:"1 小時",hh:"%d 小時",d:"1 天",dd:"%d 天",M:"1 個月",MM:"%d 個月",y:"1 年",yy:"%d 年"}})})(n(30381))},74152:function(e,t,n){var r,i;r=this,(i=function(e){return e.defineLocale("zh-tw",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"週日_週一_週二_週三_週四_週五_週六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日 HH:mm",LLLL:"YYYY年M月D日dddd HH:mm",l:"YYYY/M/D",ll:"YYYY年M月D日",lll:"YYYY年M月D日 HH:mm",llll:"YYYY年M月D日dddd HH:mm"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(e,t){return(12===e&&(e=0),"凌晨"===t||"早上"===t||"上午"===t)?e:"中午"===t?e>=11?e:e+12:"下午"===t||"晚上"===t?e+12:void 0},meridiem:function(e,t,n){var r=100*e+t;if(r<600)return"凌晨";if(r<900)return"早上";if(r<1130)return"上午";if(r<1230)return"中午";if(r<1800)return"下午";else return"晚上"},calendar:{sameDay:"[今天] LT",nextDay:"[明天] LT",nextWeek:"[下]dddd LT",lastDay:"[昨天] LT",lastWeek:"[上]dddd LT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(日|月|週)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"週";default:return e}},relativeTime:{future:"%s後",past:"%s前",s:"幾秒",ss:"%d 秒",m:"1 分鐘",mm:"%d 分鐘",h:"1 小時",hh:"%d 小時",d:"1 天",dd:"%d 天",M:"1 個月",MM:"%d 個月",y:"1 年",yy:"%d 年"}})})(n(30381))},46700(e,t,n){var r={"./af":42786,"./af.js":42786,"./ar":30867,"./ar-dz":14130,"./ar-dz.js":14130,"./ar-kw":96135,"./ar-kw.js":96135,"./ar-ly":56440,"./ar-ly.js":56440,"./ar-ma":47702,"./ar-ma.js":47702,"./ar-sa":16040,"./ar-sa.js":16040,"./ar-tn":37100,"./ar-tn.js":37100,"./ar.js":30867,"./az":31083,"./az.js":31083,"./be":9808,"./be.js":9808,"./bg":68338,"./bg.js":68338,"./bm":67438,"./bm.js":67438,"./bn":8905,"./bn-bd":76225,"./bn-bd.js":76225,"./bn.js":8905,"./bo":11560,"./bo.js":11560,"./br":1278,"./br.js":1278,"./bs":80622,"./bs.js":80622,"./ca":2468,"./ca.js":2468,"./cs":5822,"./cs.js":5822,"./cv":50877,"./cv.js":50877,"./cy":47373,"./cy.js":47373,"./da":24780,"./da.js":24780,"./de":59740,"./de-at":60217,"./de-at.js":60217,"./de-ch":60894,"./de-ch.js":60894,"./de.js":59740,"./dv":5300,"./dv.js":5300,"./el":50837,"./el.js":50837,"./en-au":78348,"./en-au.js":78348,"./en-ca":77925,"./en-ca.js":77925,"./en-gb":22243,"./en-gb.js":22243,"./en-ie":46436,"./en-ie.js":46436,"./en-il":47207,"./en-il.js":47207,"./en-in":44175,"./en-in.js":44175,"./en-nz":76319,"./en-nz.js":76319,"./en-sg":31662,"./en-sg.js":31662,"./eo":92915,"./eo.js":92915,"./es":55655,"./es-do":55251,"./es-do.js":55251,"./es-mx":96112,"./es-mx.js":96112,"./es-us":71146,"./es-us.js":71146,"./es.js":55655,"./et":5603,"./et.js":5603,"./eu":77763,"./eu.js":77763,"./fa":76959,"./fa.js":76959,"./fi":11897,"./fi.js":11897,"./fil":42549,"./fil.js":42549,"./fo":94694,"./fo.js":94694,"./fr":94470,"./fr-ca":63049,"./fr-ca.js":63049,"./fr-ch":52330,"./fr-ch.js":52330,"./fr.js":94470,"./fy":5044,"./fy.js":5044,"./ga":29295,"./ga.js":29295,"./gd":2101,"./gd.js":2101,"./gl":38794,"./gl.js":38794,"./gom-deva":27884,"./gom-deva.js":27884,"./gom-latn":23168,"./gom-latn.js":23168,"./gu":95349,"./gu.js":95349,"./he":24206,"./he.js":24206,"./hi":30094,"./hi.js":30094,"./hr":30316,"./hr.js":30316,"./hu":22138,"./hu.js":22138,"./hy-am":11423,"./hy-am.js":11423,"./id":29218,"./id.js":29218,"./is":90135,"./is.js":90135,"./it":90626,"./it-ch":10150,"./it-ch.js":10150,"./it.js":90626,"./ja":39183,"./ja.js":39183,"./jv":24286,"./jv.js":24286,"./ka":12105,"./ka.js":12105,"./kk":47772,"./kk.js":47772,"./km":18758,"./km.js":18758,"./kn":79282,"./kn.js":79282,"./ko":33730,"./ko.js":33730,"./ku":1408,"./ku.js":1408,"./ky":33291,"./ky.js":33291,"./lb":36841,"./lb.js":36841,"./lo":55466,"./lo.js":55466,"./lt":57010,"./lt.js":57010,"./lv":37595,"./lv.js":37595,"./me":39861,"./me.js":39861,"./mi":35493,"./mi.js":35493,"./mk":95966,"./mk.js":95966,"./ml":87341,"./ml.js":87341,"./mn":5115,"./mn.js":5115,"./mr":10370,"./mr.js":10370,"./ms":9847,"./ms-my":41237,"./ms-my.js":41237,"./ms.js":9847,"./mt":72126,"./mt.js":72126,"./my":56165,"./my.js":56165,"./nb":64924,"./nb.js":64924,"./ne":16744,"./ne.js":16744,"./nl":93901,"./nl-be":59814,"./nl-be.js":59814,"./nl.js":93901,"./nn":83877,"./nn.js":83877,"./oc-lnc":92135,"./oc-lnc.js":92135,"./pa-in":15858,"./pa-in.js":15858,"./pl":64495,"./pl.js":64495,"./pt":89520,"./pt-br":57971,"./pt-br.js":57971,"./pt.js":89520,"./ro":96459,"./ro.js":96459,"./ru":21793,"./ru.js":21793,"./sd":40950,"./sd.js":40950,"./se":10490,"./se.js":10490,"./si":90124,"./si.js":90124,"./sk":64249,"./sk.js":64249,"./sl":14985,"./sl.js":14985,"./sq":51104,"./sq.js":51104,"./sr":49131,"./sr-cyrl":79915,"./sr-cyrl.js":79915,"./sr.js":49131,"./ss":85893,"./ss.js":85893,"./sv":98760,"./sv.js":98760,"./sw":91172,"./sw.js":91172,"./ta":27333,"./ta.js":27333,"./te":23110,"./te.js":23110,"./tet":52095,"./tet.js":52095,"./tg":27321,"./tg.js":27321,"./th":9041,"./th.js":9041,"./tk":19005,"./tk.js":19005,"./tl-ph":75768,"./tl-ph.js":75768,"./tlh":89444,"./tlh.js":89444,"./tr":72397,"./tr.js":72397,"./tzl":28254,"./tzl.js":28254,"./tzm":51106,"./tzm-latn":30699,"./tzm-latn.js":30699,"./tzm.js":51106,"./ug-cn":9288,"./ug-cn.js":9288,"./uk":67691,"./uk.js":67691,"./ur":13795,"./ur.js":13795,"./uz":6791,"./uz-latn":60588,"./uz-latn.js":60588,"./uz.js":6791,"./vi":65666,"./vi.js":65666,"./x-pseudo":14378,"./x-pseudo.js":14378,"./yo":75805,"./yo.js":75805,"./zh-cn":83839,"./zh-cn.js":83839,"./zh-hk":55726,"./zh-hk.js":55726,"./zh-mo":99807,"./zh-mo.js":99807,"./zh-tw":74152,"./zh-tw.js":74152};function i(e){return n(a(e))}function a(e){if(!n.o(r,e)){var t=Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}i.keys=function(){return Object.keys(r)},i.resolve=a,e.exports=i,i.id=46700},30381:function(e,t,n){var r,i;e=n.nmd(e),r=this,i=function(){"use strict";function t(){return em.apply(null,arguments)}function r(e){em=e}function i(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function a(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function o(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function s(e){var t;if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(t in e)if(o(e,t))return!1;return!0}function u(e){return void 0===e}function c(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function l(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function f(e,t){var n,r=[];for(n=0;n>>0;for(t=0;t0)for(n=0;n=0?n?"+":"":"-")+Math.pow(10,Math.max(0,t-i.length)).toString().substr(1)+i}var R=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,j=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,F={},Y={};function B(e,t,n,r){var i=r;"string"==typeof r&&(i=function(){return this[r]()}),e&&(Y[e]=i),t&&(Y[t[0]]=function(){return P(i.apply(this,arguments),t[1],t[2])}),n&&(Y[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),e)})}function U(e){return e.match(/\[[\s\S]/)?e.replace(/^\[|\]$/g,""):e.replace(/\\/g,"")}function H(e){var t,n,r=e.match(R);for(t=0,n=r.length;t=0&&j.test(e);)e=e.replace(j,r),j.lastIndex=0,n-=1;return e}var G={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"};function W(e){var t=this._longDateFormat[e],n=this._longDateFormat[e.toUpperCase()];return t||!n?t:(this._longDateFormat[e]=n.match(R).map(function(e){return"MMMM"===e||"MM"===e||"DD"===e||"dddd"===e?e.slice(1):e}).join(""),this._longDateFormat[e])}var K="Invalid date";function V(){return this._invalidDate}var q="%d",Z=/\d{1,2}/;function X(e){return this._ordinal.replace("%d",e)}var J={future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"};function Q(e,t,n,r){var i=this._relativeTime[n];return A(i)?i(e,t,n,r):i.replace(/%d/i,e)}function ee(e,t){var n=this._relativeTime[e>0?"future":"past"];return A(n)?n(t):n.replace(/%s/i,t)}var et={};function en(e,t){var n=e.toLowerCase();et[n]=et[n+"s"]=et[t]=e}function er(e){return"string"==typeof e?et[e]||et[e.toLowerCase()]:void 0}function ei(e){var t,n,r={};for(n in e)o(e,n)&&(t=er(n))&&(r[t]=e[n]);return r}var ea={};function eo(e,t){ea[e]=t}function es(e){var t,n=[];for(t in e)o(e,t)&&n.push({unit:t,priority:ea[t]});return n.sort(function(e,t){return e.priority-t.priority}),n}function eu(e){return e%4==0&&e%100!=0||e%400==0}function ec(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function el(e){var t=+e,n=0;return 0!==t&&isFinite(t)&&(n=ec(t)),n}function ef(e,n){return function(r){return null!=r?(eh(this,e,r),t.updateOffset(this,n),this):ed(this,e)}}function ed(e,t){return e.isValid()?e._d["get"+(e._isUTC?"UTC":"")+t]():NaN}function eh(e,t,n){e.isValid()&&!isNaN(n)&&("FullYear"===t&&eu(e.year())&&1===e.month()&&29===e.date()?(n=el(n),e._d["set"+(e._isUTC?"UTC":"")+t](n,e.month(),e0(n,e.month()))):e._d["set"+(e._isUTC?"UTC":"")+t](n))}function ep(e){return A(this[e=er(e)])?this[e]():this}function eb(e,t){if("object"==typeof e){e=ei(e);var n,r=es(e);for(n=0;n68?1900:2e3)};var tu=ef("FullYear",!0);function tc(){return eu(this.year())}function tl(e,t,n,r,i,a,o){var s;return e<100&&e>=0?(s=new Date(e+400,t,n,r,i,a,o),isFinite(s.getFullYear())&&s.setFullYear(e)):s=new Date(e,t,n,r,i,a,o),s}function tf(e){var t,n;return e<100&&e>=0?(n=Array.prototype.slice.call(arguments),n[0]=e+400,t=new Date(Date.UTC.apply(null,n)),isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e)):t=new Date(Date.UTC.apply(null,arguments)),t}function td(e,t,n){var r=7+t-n;return-((7+tf(e,0,r).getUTCDay()-t)%7)+r-1}function th(e,t,n,r,i){var a,o,s=(7+n-r)%7,u=td(e,r,i),c=1+7*(t-1)+s+u;return c<=0?o=ts(a=e-1)+c:c>ts(e)?(a=e+1,o=c-ts(e)):(a=e,o=c),{year:a,dayOfYear:o}}function tp(e,t,n){var r,i,a=td(e.year(),t,n),o=Math.floor((e.dayOfYear()-a-1)/7)+1;return o<1?r=o+tb(i=e.year()-1,t,n):o>tb(e.year(),t,n)?(r=o-tb(e.year(),t,n),i=e.year()+1):(i=e.year(),r=o),{week:r,year:i}}function tb(e,t,n){var r=td(e,t,n),i=td(e+1,t,n);return(ts(e)-r+i)/7}function tm(e){return tp(e,this._week.dow,this._week.doy).week}B("w",["ww",2],"wo","week"),B("W",["WW",2],"Wo","isoWeek"),en("week","w"),en("isoWeek","W"),eo("week",5),eo("isoWeek",5),ej("w",ex),ej("ww",ex,e_),ej("W",ex),ej("WW",ex,e_),e$(["w","ww","W","WW"],function(e,t,n,r){t[r.substr(0,1)]=el(e)});var tg={dow:0,doy:6};function tv(){return this._week.dow}function ty(){return this._week.doy}function tw(e){var t=this.localeData().week(this);return null==e?t:this.add((e-t)*7,"d")}function t_(e){var t=tp(this,1,4).week;return null==e?t:this.add((e-t)*7,"d")}function tE(e,t){return"string"!=typeof e?e:isNaN(e)?"number"==typeof(e=t.weekdaysParse(e))?e:null:parseInt(e,10)}function tS(e,t){return"string"==typeof e?t.weekdaysParse(e)%7||7:isNaN(e)?null:e}function tk(e,t){return e.slice(t,7).concat(e.slice(0,t))}B("d",0,"do","day"),B("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),B("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),B("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),B("e",0,0,"weekday"),B("E",0,0,"isoWeekday"),en("day","d"),en("weekday","e"),en("isoWeekday","E"),eo("day",11),eo("weekday",11),eo("isoWeekday",11),ej("d",ex),ej("e",ex),ej("E",ex),ej("dd",function(e,t){return t.weekdaysMinRegex(e)}),ej("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ej("dddd",function(e,t){return t.weekdaysRegex(e)}),e$(["dd","ddd","dddd"],function(e,t,n,r){var i=n._locale.weekdaysParse(e,r,n._strict);null!=i?t.d=i:b(n).invalidWeekday=e}),e$(["d","e","E"],function(e,t,n,r){t[r]=el(e)});var tx="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),tT="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),tM="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),tO=eR,tA=eR,tL=eR;function tC(e,t){var n=i(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(t)?"format":"standalone"];return!0===e?tk(n,this._week.dow):e?n[e.day()]:n}function tI(e){return!0===e?tk(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort}function tD(e){return!0===e?tk(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin}function tN(e,t,n){var r,i,a,o=e.toLocaleLowerCase();if(!this._weekdaysParse)for(r=0,this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[];r<7;++r)a=h([2e3,1]).day(r),this._minWeekdaysParse[r]=this.weekdaysMin(a,"").toLocaleLowerCase(),this._shortWeekdaysParse[r]=this.weekdaysShort(a,"").toLocaleLowerCase(),this._weekdaysParse[r]=this.weekdays(a,"").toLocaleLowerCase();return n?"dddd"===t?-1!==(i=tX.call(this._weekdaysParse,o))?i:null:"ddd"===t?-1!==(i=tX.call(this._shortWeekdaysParse,o))?i:null:-1!==(i=tX.call(this._minWeekdaysParse,o))?i:null:"dddd"===t?-1!==(i=tX.call(this._weekdaysParse,o))||-1!==(i=tX.call(this._shortWeekdaysParse,o))?i:-1!==(i=tX.call(this._minWeekdaysParse,o))?i:null:"ddd"===t?-1!==(i=tX.call(this._shortWeekdaysParse,o))||-1!==(i=tX.call(this._weekdaysParse,o))?i:-1!==(i=tX.call(this._minWeekdaysParse,o))?i:null:-1!==(i=tX.call(this._minWeekdaysParse,o))||-1!==(i=tX.call(this._weekdaysParse,o))?i:-1!==(i=tX.call(this._shortWeekdaysParse,o))?i:null}function tP(e,t,n){var r,i,a;if(this._weekdaysParseExact)return tN.call(this,e,t,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),r=0;r<7;r++){if(i=h([2e3,1]).day(r),n&&!this._fullWeekdaysParse[r]&&(this._fullWeekdaysParse[r]=RegExp("^"+this.weekdays(i,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[r]=RegExp("^"+this.weekdaysShort(i,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[r]=RegExp("^"+this.weekdaysMin(i,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[r]||(a="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[r]=RegExp(a.replace(".",""),"i")),n&&"dddd"===t&&this._fullWeekdaysParse[r].test(e))return r;if(n&&"ddd"===t&&this._shortWeekdaysParse[r].test(e))return r;if(n&&"dd"===t&&this._minWeekdaysParse[r].test(e))return r;else if(!n&&this._weekdaysParse[r].test(e))return r}}function tR(e){if(!this.isValid())return null!=e?this:NaN;var t=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(e=tE(e,this.localeData()),this.add(e-t,"d")):t}function tj(e){if(!this.isValid())return null!=e?this:NaN;var t=(this.day()+7-this.localeData()._week.dow)%7;return null==e?t:this.add(e-t,"d")}function tF(e){if(!this.isValid())return null!=e?this:NaN;if(null==e)return this.day()||7;var t=tS(e,this.localeData());return this.day(this.day()%7?t:t-7)}function tY(e){return this._weekdaysParseExact?(o(this,"_weekdaysRegex")||tH.call(this),e)?this._weekdaysStrictRegex:this._weekdaysRegex:(o(this,"_weekdaysRegex")||(this._weekdaysRegex=tO),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)}function tB(e){return this._weekdaysParseExact?(o(this,"_weekdaysRegex")||tH.call(this),e)?this._weekdaysShortStrictRegex:this._weekdaysShortRegex:(o(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=tA),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function tU(e){return this._weekdaysParseExact?(o(this,"_weekdaysRegex")||tH.call(this),e)?this._weekdaysMinStrictRegex:this._weekdaysMinRegex:(o(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=tL),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function tH(){function e(e,t){return t.length-e.length}var t,n,r,i,a,o=[],s=[],u=[],c=[];for(t=0;t<7;t++)n=h([2e3,1]).day(t),r=eB(this.weekdaysMin(n,"")),i=eB(this.weekdaysShort(n,"")),a=eB(this.weekdays(n,"")),o.push(r),s.push(i),u.push(a),c.push(r),c.push(i),c.push(a);o.sort(e),s.sort(e),u.sort(e),c.sort(e),this._weekdaysRegex=RegExp("^("+c.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=RegExp("^("+o.join("|")+")","i")}function t$(){return this.hours()%12||12}function tz(){return this.hours()||24}function tG(e,t){B(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function tW(e,t){return t._meridiemParse}function tK(e){return"p"===(e+"").toLowerCase().charAt(0)}B("H",["HH",2],0,"hour"),B("h",["hh",2],0,t$),B("k",["kk",2],0,tz),B("hmm",0,0,function(){return""+t$.apply(this)+P(this.minutes(),2)}),B("hmmss",0,0,function(){return""+t$.apply(this)+P(this.minutes(),2)+P(this.seconds(),2)}),B("Hmm",0,0,function(){return""+this.hours()+P(this.minutes(),2)}),B("Hmmss",0,0,function(){return""+this.hours()+P(this.minutes(),2)+P(this.seconds(),2)}),tG("a",!0),tG("A",!1),en("hour","h"),eo("hour",13),ej("a",tW),ej("A",tW),ej("H",ex),ej("h",ex),ej("k",ex),ej("HH",ex,e_),ej("hh",ex,e_),ej("kk",ex,e_),ej("hmm",eT),ej("hmmss",eM),ej("Hmm",eT),ej("Hmmss",eM),eH(["H","HH"],eV),eH(["k","kk"],function(e,t,n){var r=el(e);t[eV]=24===r?0:r}),eH(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),eH(["h","hh"],function(e,t,n){t[eV]=el(e),b(n).bigHour=!0}),eH("hmm",function(e,t,n){var r=e.length-2;t[eV]=el(e.substr(0,r)),t[eq]=el(e.substr(r)),b(n).bigHour=!0}),eH("hmmss",function(e,t,n){var r=e.length-4,i=e.length-2;t[eV]=el(e.substr(0,r)),t[eq]=el(e.substr(r,2)),t[eZ]=el(e.substr(i)),b(n).bigHour=!0}),eH("Hmm",function(e,t,n){var r=e.length-2;t[eV]=el(e.substr(0,r)),t[eq]=el(e.substr(r))}),eH("Hmmss",function(e,t,n){var r=e.length-4,i=e.length-2;t[eV]=el(e.substr(0,r)),t[eq]=el(e.substr(r,2)),t[eZ]=el(e.substr(i))});var tV=/[ap]\.?m?\.?/i,tq=ef("Hours",!0);function tZ(e,t,n){return e>11?n?"pm":"PM":n?"am":"AM"}var tX,tJ,tQ={calendar:D,longDateFormat:G,invalidDate:K,ordinal:q,dayOfMonthOrdinalParse:Z,relativeTime:J,months:e2,monthsShort:e3,week:tg,weekdays:tx,weekdaysMin:tM,weekdaysShort:tT,meridiemParse:tV},t1={},t0={};function t2(e,t){var n,r=Math.min(e.length,t.length);for(n=0;n0;){if(r=t5(i.slice(0,t).join("-")))return r;if(n&&n.length>=t&&t2(i,n)>=t-1)break;t--}a++}return tJ}function t5(t){var r,i=null;if(void 0===t1[t]&&e&&e.exports)try{i=tJ._abbr,r=void 0,n(46700)("./"+t),t6(i)}catch(a){t1[t]=null}return t1[t]}function t6(e,t){var n;return e&&((n=u(t)?t7(e):t9(e,t))?tJ=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),tJ._abbr}function t9(e,t){if(null===t)return delete t1[e],null;var n,r=tQ;if(t.abbr=e,null!=t1[e])O("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),r=t1[e]._config;else if(null!=t.parentLocale){if(null!=t1[t.parentLocale])r=t1[t.parentLocale]._config;else{if(null==(n=t5(t.parentLocale)))return t0[t.parentLocale]||(t0[t.parentLocale]=[]),t0[t.parentLocale].push({name:e,config:t}),null;r=n._config}}return t1[e]=new I(C(r,t)),t0[e]&&t0[e].forEach(function(e){t9(e.name,e.config)}),t6(e),t1[e]}function t8(e,t){if(null!=t){var n,r,i=tQ;null!=t1[e]&&null!=t1[e].parentLocale?t1[e].set(C(t1[e]._config,t)):(null!=(r=t5(e))&&(i=r._config),t=C(i,t),null==r&&(t.abbr=e),(n=new I(t)).parentLocale=t1[e],t1[e]=n),t6(e)}else null!=t1[e]&&(null!=t1[e].parentLocale?(t1[e]=t1[e].parentLocale,e===t6()&&t6(e)):null!=t1[e]&&delete t1[e]);return t1[e]}function t7(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return tJ;if(!i(e)){if(t=t5(e))return t;e=[e]}return t4(e)}function ne(){return ev(t1)}function nt(e){var t,n=e._a;return n&&-2===b(e).overflow&&(t=n[eW]<0||n[eW]>11?eW:n[eK]<1||n[eK]>e0(n[eG],n[eW])?eK:n[eV]<0||n[eV]>24||24===n[eV]&&(0!==n[eq]||0!==n[eZ]||0!==n[eX])?eV:n[eq]<0||n[eq]>59?eq:n[eZ]<0||n[eZ]>59?eZ:n[eX]<0||n[eX]>999?eX:-1,b(e)._overflowDayOfYear&&(teK)&&(t=eK),b(e)._overflowWeeks&&-1===t&&(t=eJ),b(e)._overflowWeekday&&-1===t&&(t=eQ),b(e).overflow=t),e}var nn=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,nr=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ni=/Z|[+-]\d\d(?::?\d\d)?/,na=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/],["YYYYMM",/\d{6}/,!1],["YYYY",/\d{4}/,!1],],no=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/],],ns=/^\/?Date\((-?\d+)/i,nu=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,nc={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function nl(e){var t,n,r,i,a,o,s=e._i,u=nn.exec(s)||nr.exec(s);if(u){for(t=0,b(e).iso=!0,n=na.length;tts(a)||0===e._dayOfYear)&&(b(e)._overflowDayOfYear=!0),n=tf(a,0,e._dayOfYear),e._a[eW]=n.getUTCMonth(),e._a[eK]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=o[t]=r[t];for(;t<7;t++)e._a[t]=o[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[eV]&&0===e._a[eq]&&0===e._a[eZ]&&0===e._a[eX]&&(e._nextDay=!0,e._a[eV]=0),e._d=(e._useUTC?tf:tl).apply(null,o),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[eV]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(b(e).weekdayMismatch=!0)}}function n_(e){var t,n,r,i,a,o,s,u,c;null!=(t=e._w).GG||null!=t.W||null!=t.E?(a=1,o=4,n=nv(t.GG,e._a[eG],tp(nL(),1,4).year),r=nv(t.W,1),((i=nv(t.E,1))<1||i>7)&&(u=!0)):(a=e._locale._week.dow,o=e._locale._week.doy,c=tp(nL(),a,o),n=nv(t.gg,e._a[eG],c.year),r=nv(t.w,c.week),null!=t.d?((i=t.d)<0||i>6)&&(u=!0):null!=t.e?(i=t.e+a,(t.e<0||t.e>6)&&(u=!0)):i=a),r<1||r>tb(n,a,o)?b(e)._overflowWeeks=!0:null!=u?b(e)._overflowWeekday=!0:(s=th(n,r,i,a,o),e._a[eG]=s.year,e._dayOfYear=s.dayOfYear)}function nE(e){if(e._f===t.ISO_8601){nl(e);return}if(e._f===t.RFC_2822){nm(e);return}e._a=[],b(e).empty=!0;var n,r,i,a,o,s,u=""+e._i,c=u.length,l=0;for(n=0,i=z(e._f,e._locale).match(R)||[];n0&&b(e).unusedInput.push(o),u=u.slice(u.indexOf(r)+r.length),l+=r.length),Y[a]?(r?b(e).empty=!1:b(e).unusedTokens.push(a),ez(a,r,e)):e._strict&&!r&&b(e).unusedTokens.push(a);b(e).charsLeftOver=c-l,u.length>0&&b(e).unusedInput.push(u),e._a[eV]<=12&&!0===b(e).bigHour&&e._a[eV]>0&&(b(e).bigHour=void 0),b(e).parsedDateParts=e._a.slice(0),b(e).meridiem=e._meridiem,e._a[eV]=nS(e._locale,e._a[eV],e._meridiem),null!==(s=b(e).era)&&(e._a[eG]=e._locale.erasConvertYear(s,e._a[eG])),nw(e),nt(e)}function nS(e,t,n){var r;return null==n?t:null!=e.meridiemHour?e.meridiemHour(t,n):(null!=e.isPM&&((r=e.isPM(n))&&t<12&&(t+=12),r||12!==t||(t=0)),t)}function nk(e){var t,n,r,i,a,o,s=!1;if(0===e._f.length){b(e).invalidFormat=!0,e._d=new Date(NaN);return}for(i=0;ithis?this:e:g()});function nD(e,t){var n,r;if(1===t.length&&i(t[0])&&(t=t[0]),!t.length)return nL();for(r=1,n=t[0];rMath.abs(e)&&!r&&(e*=60);return!this._isUTC&&n&&(i=nq(this)),this._offset=e,this._isUTC=!0,null!=i&&this.add(i,"m"),a===e||(!n||this._changeInProgress?ri(this,n7(e-a,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,t.updateOffset(this,!0),this._changeInProgress=null)),this}function nX(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}function nJ(e){return this.utcOffset(0,e)}function nQ(e){return this._isUTC&&(this.utcOffset(0,e),this._isUTC=!1,e&&this.subtract(nq(this),"m")),this}function n1(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if("string"==typeof this._i){var e=nK(eD,this._i);null!=e?this.utcOffset(e):this.utcOffset(0,!0)}return this}function n0(e){return!!this.isValid()&&(e=e?nL(e).utcOffset():0,(this.utcOffset()-e)%60==0)}function n2(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function n3(){if(!u(this._isDSTShifted))return this._isDSTShifted;var e,t={};return E(t,this),(t=nM(t))._a?(e=t._isUTC?h(t._a):nL(t._a),this._isDSTShifted=this.isValid()&&nz(t._a,e.toArray())>0):this._isDSTShifted=!1,this._isDSTShifted}function n4(){return!!this.isValid()&&!this._isUTC}function n5(){return!!this.isValid()&&this._isUTC}function n6(){return!!this.isValid()&&this._isUTC&&0===this._offset}t.updateOffset=function(){};var n9=/^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,n8=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function n7(e,t){var n,r,i,a=e,s=null;return nH(e)?a={ms:e._milliseconds,d:e._days,M:e._months}:c(e)||!isNaN(+e)?(a={},t?a[t]=+e:a.milliseconds=+e):(s=n9.exec(e))?(n="-"===s[1]?-1:1,a={y:0,d:el(s[eK])*n,h:el(s[eV])*n,m:el(s[eq])*n,s:el(s[eZ])*n,ms:el(n$(1e3*s[eX]))*n}):(s=n8.exec(e))?(n="-"===s[1]?-1:1,a={y:re(s[2],n),M:re(s[3],n),w:re(s[4],n),d:re(s[5],n),h:re(s[6],n),m:re(s[7],n),s:re(s[8],n)}):null==a?a={}:"object"==typeof a&&("from"in a||"to"in a)&&(i=rn(nL(a.from),nL(a.to)),(a={}).ms=i.milliseconds,a.M=i.months),r=new nU(a),nH(e)&&o(e,"_locale")&&(r._locale=e._locale),nH(e)&&o(e,"_isValid")&&(r._isValid=e._isValid),r}function re(e,t){var n=e&&parseFloat(e.replace(",","."));return(isNaN(n)?0:n)*t}function rt(e,t){var n={};return n.months=t.month()-e.month()+(t.year()-e.year())*12,e.clone().add(n.months,"M").isAfter(t)&&--n.months,n.milliseconds=+t-+e.clone().add(n.months,"M"),n}function rn(e,t){var n;return e.isValid()&&t.isValid()?(t=nV(t,e),e.isBefore(t)?n=rt(e,t):((n=rt(t,e)).milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function rr(e,t){return function(n,r){var i,a;return null===r||isNaN(+r)||(O(t,"moment()."+t+"(period, number) is deprecated. Please use moment()."+t+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),a=n,n=r,r=a),i=n7(n,r),ri(this,i,e),this}}function ri(e,n,r,i){var a=n._milliseconds,o=n$(n._days),s=n$(n._months);e.isValid()&&(i=null==i||i,s&&tt(e,ed(e,"Month")+s*r),o&&eh(e,"Date",ed(e,"Date")+o*r),a&&e._d.setTime(e._d.valueOf()+a*r),i&&t.updateOffset(e,o||s))}n7.fn=nU.prototype,n7.invalid=nB;var ra=rr(1,"add"),ro=rr(-1,"subtract");function rs(e){return"string"==typeof e||e instanceof String}function ru(e){return k(e)||l(e)||rs(e)||c(e)||rl(e)||rc(e)||null==e}function rc(e){var t,n,r=a(e)&&!s(e),i=!1,u=["years","year","y","months","month","M","days","day","d","dates","date","D","hours","hour","h","minutes","minute","m","seconds","second","s","milliseconds","millisecond","ms",];for(t=0;tn.valueOf():n.valueOf()n.year()||n.year()>9999?$(n,t?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):A(Date.prototype.toISOString)?t?this.toDate().toISOString():new Date(this.valueOf()+6e4*this.utcOffset()).toISOString().replace("Z",$(n,"Z")):$(n,t?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")}function rx(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e,t,n,r,i="moment",a="";return this.isLocal()||(i=0===this.utcOffset()?"moment.utc":"moment.parseZone",a="Z"),e="["+i+'("]',t=0<=this.year()&&9999>=this.year()?"YYYY":"YYYYYY",n="-MM-DD[T]HH:mm:ss.SSS",r=a+'[")]',this.format(e+t+n+r)}function rT(e){e||(e=this.isUtc()?t.defaultFormatUtc:t.defaultFormat);var n=$(this,e);return this.localeData().postformat(n)}function rM(e,t){return this.isValid()&&(k(e)&&e.isValid()||nL(e).isValid())?n7({to:this,from:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()}function rO(e){return this.from(nL(),e)}function rA(e,t){return this.isValid()&&(k(e)&&e.isValid()||nL(e).isValid())?n7({from:this,to:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()}function rL(e){return this.to(nL(),e)}function rC(e){var t;return void 0===e?this._locale._abbr:(null!=(t=t7(e))&&(this._locale=t),this)}t.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",t.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var rI=T("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(e){return void 0===e?this.localeData():this.locale(e)});function rD(){return this._locale}var rN=1e3,rP=60*rN,rR=60*rP,rj=3506328*rR;function rF(e,t){return(e%t+t)%t}function rY(e,t,n){return e<100&&e>=0?new Date(e+400,t,n)-rj:new Date(e,t,n).valueOf()}function rB(e,t,n){return e<100&&e>=0?Date.UTC(e+400,t,n)-rj:Date.UTC(e,t,n)}function rU(e){var n,r;if(void 0===(e=er(e))||"millisecond"===e||!this.isValid())return this;switch(r=this._isUTC?rB:rY,e){case"year":n=r(this.year(),0,1);break;case"quarter":n=r(this.year(),this.month()-this.month()%3,1);break;case"month":n=r(this.year(),this.month(),1);break;case"week":n=r(this.year(),this.month(),this.date()-this.weekday());break;case"isoWeek":n=r(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case"day":case"date":n=r(this.year(),this.month(),this.date());break;case"hour":n=this._d.valueOf(),n-=rF(n+(this._isUTC?0:this.utcOffset()*rP),rR);break;case"minute":n=this._d.valueOf(),n-=rF(n,rP);break;case"second":n=this._d.valueOf(),n-=rF(n,rN)}return this._d.setTime(n),t.updateOffset(this,!0),this}function rH(e){var n,r;if(void 0===(e=er(e))||"millisecond"===e||!this.isValid())return this;switch(r=this._isUTC?rB:rY,e){case"year":n=r(this.year()+1,0,1)-1;break;case"quarter":n=r(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":n=r(this.year(),this.month()+1,1)-1;break;case"week":n=r(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":n=r(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":n=r(this.year(),this.month(),this.date()+1)-1;break;case"hour":n=this._d.valueOf(),n+=rR-rF(n+(this._isUTC?0:this.utcOffset()*rP),rR)-1;break;case"minute":n=this._d.valueOf(),n+=rP-rF(n,rP)-1;break;case"second":n=this._d.valueOf(),n+=rN-rF(n,rN)-1}return this._d.setTime(n),t.updateOffset(this,!0),this}function r$(){return this._d.valueOf()-6e4*(this._offset||0)}function rz(){return Math.floor(this.valueOf()/1e3)}function rG(){return new Date(this.valueOf())}function rW(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond(),]}function rK(){var e=this;return{years:e.year(),months:e.month(),date:e.date(),hours:e.hours(),minutes:e.minutes(),seconds:e.seconds(),milliseconds:e.milliseconds()}}function rV(){return this.isValid()?this.toISOString():null}function rq(){return m(this)}function rZ(){return d({},b(this))}function rX(){return b(this).overflow}function rJ(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function rQ(e,n){var r,i,a,o=this._eras||t7("en")._eras;for(r=0,i=o.length;r=0)return u[r]}function r0(e,n){var r=e.since<=e.until?1:-1;return void 0===n?t(e.since).year():t(e.since).year()+(n-e.offset)*r}function r2(){var e,t,n,r=this.localeData().eras();for(e=0,t=r.length;ea&&(t=a),ip.call(this,e,t,n,r,i))}function ip(e,t,n,r,i){var a=th(e,t,n,r,i),o=tf(a.year,0,a.dayOfYear);return this.year(o.getUTCFullYear()),this.month(o.getUTCMonth()),this.date(o.getUTCDate()),this}function ib(e){return null==e?Math.ceil((this.month()+1)/3):this.month((e-1)*3+this.month()%3)}B("N",0,0,"eraAbbr"),B("NN",0,0,"eraAbbr"),B("NNN",0,0,"eraAbbr"),B("NNNN",0,0,"eraName"),B("NNNNN",0,0,"eraNarrow"),B("y",["y",1],"yo","eraYear"),B("y",["yy",2],0,"eraYear"),B("y",["yyy",3],0,"eraYear"),B("y",["yyyy",4],0,"eraYear"),ej("N",r7),ej("NN",r7),ej("NNN",r7),ej("NNNN",ie),ej("NNNNN",it),eH(["N","NN","NNN","NNNN","NNNNN"],function(e,t,n,r){var i=n._locale.erasParse(e,r,n._strict);i?b(n).era=i:b(n).invalidEra=e}),ej("y",eC),ej("yy",eC),ej("yyy",eC),ej("yyyy",eC),ej("yo",ir),eH(["y","yy","yyy","yyyy"],eG),eH(["yo"],function(e,t,n,r){var i;n._locale._eraYearOrdinalRegex&&(i=e.match(n._locale._eraYearOrdinalRegex)),n._locale.eraYearOrdinalParse?t[eG]=n._locale.eraYearOrdinalParse(e,i):t[eG]=parseInt(e,10)}),B(0,["gg",2],0,function(){return this.weekYear()%100}),B(0,["GG",2],0,function(){return this.isoWeekYear()%100}),ia("gggg","weekYear"),ia("ggggg","weekYear"),ia("GGGG","isoWeekYear"),ia("GGGGG","isoWeekYear"),en("weekYear","gg"),en("isoWeekYear","GG"),eo("weekYear",1),eo("isoWeekYear",1),ej("G",eI),ej("g",eI),ej("GG",ex,e_),ej("gg",ex,e_),ej("GGGG",eA,eS),ej("gggg",eA,eS),ej("GGGGG",eL,ek),ej("ggggg",eL,ek),e$(["gggg","ggggg","GGGG","GGGGG"],function(e,t,n,r){t[r.substr(0,2)]=el(e)}),e$(["gg","GG"],function(e,n,r,i){n[i]=t.parseTwoDigitYear(e)}),B("Q",0,"Qo","quarter"),en("quarter","Q"),eo("quarter",7),ej("Q",ew),eH("Q",function(e,t){t[eW]=(el(e)-1)*3}),B("D",["DD",2],"Do","date"),en("date","D"),eo("date",9),ej("D",ex),ej("DD",ex,e_),ej("Do",function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient}),eH(["D","DD"],eK),eH("Do",function(e,t){t[eK]=el(e.match(ex)[0])});var im=ef("Date",!0);function ig(e){var t=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==e?t:this.add(e-t,"d")}B("DDD",["DDDD",3],"DDDo","dayOfYear"),en("dayOfYear","DDD"),eo("dayOfYear",4),ej("DDD",eO),ej("DDDD",eE),eH(["DDD","DDDD"],function(e,t,n){n._dayOfYear=el(e)}),B("m",["mm",2],0,"minute"),en("minute","m"),eo("minute",14),ej("m",ex),ej("mm",ex,e_),eH(["m","mm"],eq);var iv=ef("Minutes",!1);B("s",["ss",2],0,"second"),en("second","s"),eo("second",15),ej("s",ex),ej("ss",ex,e_),eH(["s","ss"],eZ);var iy=ef("Seconds",!1);for(B("S",0,0,function(){return~~(this.millisecond()/100)}),B(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),B(0,["SSS",3],0,"millisecond"),B(0,["SSSS",4],0,function(){return 10*this.millisecond()}),B(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),B(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),B(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),B(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),B(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),en("millisecond","ms"),eo("millisecond",16),ej("S",eO,ew),ej("SS",eO,e_),ej("SSS",eO,eE),v="SSSS";v.length<=9;v+="S")ej(v,eC);function iw(e,t){t[eX]=el(("0."+e)*1e3)}for(v="S";v.length<=9;v+="S")eH(v,iw);function i_(){return this._isUTC?"UTC":""}function iE(){return this._isUTC?"Coordinated Universal Time":""}y=ef("Milliseconds",!1),B("z",0,0,"zoneAbbr"),B("zz",0,0,"zoneName");var iS=S.prototype;function ik(e){return nL(1e3*e)}function ix(){return nL.apply(null,arguments).parseZone()}function iT(e){return e}iS.add=ra,iS.calendar=rh,iS.clone=rp,iS.diff=r_,iS.endOf=rH,iS.format=rT,iS.from=rM,iS.fromNow=rO,iS.to=rA,iS.toNow=rL,iS.get=ep,iS.invalidAt=rX,iS.isAfter=rb,iS.isBefore=rm,iS.isBetween=rg,iS.isSame=rv,iS.isSameOrAfter=ry,iS.isSameOrBefore=rw,iS.isValid=rq,iS.lang=rI,iS.locale=rC,iS.localeData=rD,iS.max=nI,iS.min=nC,iS.parsingFlags=rZ,iS.set=eb,iS.startOf=rU,iS.subtract=ro,iS.toArray=rW,iS.toObject=rK,iS.toDate=rG,iS.toISOString=rk,iS.inspect=rx,"undefined"!=typeof Symbol&&null!=Symbol.for&&(iS[Symbol.for("nodejs.util.inspect.custom")]=function(){return"Moment<"+this.format()+">"}),iS.toJSON=rV,iS.toString=rS,iS.unix=rz,iS.valueOf=r$,iS.creationData=rJ,iS.eraName=r2,iS.eraNarrow=r3,iS.eraAbbr=r4,iS.eraYear=r5,iS.year=tu,iS.isLeapYear=tc,iS.weekYear=io,iS.isoWeekYear=is,iS.quarter=iS.quarters=ib,iS.month=tn,iS.daysInMonth=tr,iS.week=iS.weeks=tw,iS.isoWeek=iS.isoWeeks=t_,iS.weeksInYear=il,iS.weeksInWeekYear=id,iS.isoWeeksInYear=iu,iS.isoWeeksInISOWeekYear=ic,iS.date=im,iS.day=iS.days=tR,iS.weekday=tj,iS.isoWeekday=tF,iS.dayOfYear=ig,iS.hour=iS.hours=tq,iS.minute=iS.minutes=iv,iS.second=iS.seconds=iy,iS.millisecond=iS.milliseconds=y,iS.utcOffset=nZ,iS.utc=nJ,iS.local=nQ,iS.parseZone=n1,iS.hasAlignedHourOffset=n0,iS.isDST=n2,iS.isLocal=n4,iS.isUtcOffset=n5,iS.isUtc=n6,iS.isUTC=n6,iS.zoneAbbr=i_,iS.zoneName=iE,iS.dates=T("dates accessor is deprecated. Use date instead.",im),iS.months=T("months accessor is deprecated. Use month instead",tn),iS.years=T("years accessor is deprecated. Use year instead",tu),iS.zone=T("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",nX),iS.isDSTShifted=T("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",n3);var iM=I.prototype;function iO(e,t,n,r){var i=t7(),a=h().set(r,t);return i[n](a,e)}function iA(e,t,n){if(c(e)&&(t=e,e=void 0),e=e||"",null!=t)return iO(e,t,n,"month");var r,i=[];for(r=0;r<12;r++)i[r]=iO(e,r,n,"month");return i}function iL(e,t,n,r){"boolean"==typeof e?(c(t)&&(n=t,t=void 0),t=t||""):(n=t=e,e=!1,c(t)&&(n=t,t=void 0),t=t||"");var i,a=t7(),o=e?a._week.dow:0,s=[];if(null!=n)return iO(t,(n+o)%7,r,"day");for(i=0;i<7;i++)s[i]=iO(t,(i+o)%7,r,"day");return s}function iC(e,t){return iA(e,t,"months")}function iI(e,t){return iA(e,t,"monthsShort")}function iD(e,t,n){return iL(e,t,n,"weekdays")}function iN(e,t,n){return iL(e,t,n,"weekdaysShort")}function iP(e,t,n){return iL(e,t,n,"weekdaysMin")}iM.calendar=N,iM.longDateFormat=W,iM.invalidDate=V,iM.ordinal=X,iM.preparse=iT,iM.postformat=iT,iM.relativeTime=Q,iM.pastFuture=ee,iM.set=L,iM.eras=rQ,iM.erasParse=r1,iM.erasConvertYear=r0,iM.erasAbbrRegex=r9,iM.erasNameRegex=r6,iM.erasNarrowRegex=r8,iM.months=e9,iM.monthsShort=e8,iM.monthsParse=te,iM.monthsRegex=ta,iM.monthsShortRegex=ti,iM.week=tm,iM.firstDayOfYear=ty,iM.firstDayOfWeek=tv,iM.weekdays=tC,iM.weekdaysMin=tD,iM.weekdaysShort=tI,iM.weekdaysParse=tP,iM.weekdaysRegex=tY,iM.weekdaysShortRegex=tB,iM.weekdaysMinRegex=tU,iM.isPM=tK,iM.meridiem=tZ,t6("en",{eras:[{since:"0001-01-01",until:Infinity,offset:1,name:"Anno Domini",narrow:"AD",abbr:"AD"},{since:"0000-12-31",until:-1/0,offset:1,name:"Before Christ",narrow:"BC",abbr:"BC"},],dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10,n=1===el(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+n}}),t.lang=T("moment.lang is deprecated. Use moment.locale instead.",t6),t.langData=T("moment.langData is deprecated. Use moment.localeData instead.",t7);var iR=Math.abs;function ij(){var e=this._data;return this._milliseconds=iR(this._milliseconds),this._days=iR(this._days),this._months=iR(this._months),e.milliseconds=iR(e.milliseconds),e.seconds=iR(e.seconds),e.minutes=iR(e.minutes),e.hours=iR(e.hours),e.months=iR(e.months),e.years=iR(e.years),this}function iF(e,t,n,r){var i=n7(t,n);return e._milliseconds+=r*i._milliseconds,e._days+=r*i._days,e._months+=r*i._months,e._bubble()}function iY(e,t){return iF(this,e,t,1)}function iB(e,t){return iF(this,e,t,-1)}function iU(e){return e<0?Math.floor(e):Math.ceil(e)}function iH(){var e,t,n,r,i,a=this._milliseconds,o=this._days,s=this._months,u=this._data;return a>=0&&o>=0&&s>=0||a<=0&&o<=0&&s<=0||(a+=864e5*iU(iz(s)+o),o=0,s=0),u.milliseconds=a%1e3,e=ec(a/1e3),u.seconds=e%60,t=ec(e/60),u.minutes=t%60,n=ec(t/60),u.hours=n%24,o+=ec(n/24),s+=i=ec(i$(o)),o-=iU(iz(i)),r=ec(s/12),s%=12,u.days=o,u.months=s,u.years=r,this}function i$(e){return 4800*e/146097}function iz(e){return 146097*e/4800}function iG(e){if(!this.isValid())return NaN;var t,n,r=this._milliseconds;if("month"===(e=er(e))||"quarter"===e||"year"===e)switch(t=this._days+r/864e5,n=this._months+i$(t),e){case"month":return n;case"quarter":return n/3;case"year":return n/12}else switch(t=this._days+Math.round(iz(this._months)),e){case"week":return t/7+r/6048e5;case"day":return t+r/864e5;case"hour":return 24*t+r/36e5;case"minute":return 1440*t+r/6e4;case"second":return 86400*t+r/1e3;case"millisecond":return Math.floor(864e5*t)+r;default:throw Error("Unknown unit "+e)}}function iW(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*el(this._months/12):NaN}function iK(e){return function(){return this.as(e)}}var iV=iK("ms"),iq=iK("s"),iZ=iK("m"),iX=iK("h"),iJ=iK("d"),iQ=iK("w"),i1=iK("M"),i0=iK("Q"),i2=iK("y");function i3(){return n7(this)}function i4(e){return e=er(e),this.isValid()?this[e+"s"]():NaN}function i5(e){return function(){return this.isValid()?this._data[e]:NaN}}var i6=i5("milliseconds"),i9=i5("seconds"),i8=i5("minutes"),i7=i5("hours"),ae=i5("days"),at=i5("months"),an=i5("years");function ar(){return ec(this.days()/7)}var ai=Math.round,aa={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};function ao(e,t,n,r,i){return i.relativeTime(t||1,!!n,e,r)}function as(e,t,n,r){var i=n7(e).abs(),a=ai(i.as("s")),o=ai(i.as("m")),s=ai(i.as("h")),u=ai(i.as("d")),c=ai(i.as("M")),l=ai(i.as("w")),f=ai(i.as("y")),d=a<=n.ss&&["s",a]||a0,d[4]=r,ao.apply(null,d)}function au(e){return void 0===e?ai:"function"==typeof e&&(ai=e,!0)}function ac(e,t){return void 0!==aa[e]&&(void 0===t?aa[e]:(aa[e]=t,"s"===e&&(aa.ss=t-1),!0))}function al(e,t){if(!this.isValid())return this.localeData().invalidDate();var n,r,i=!1,a=aa;return"object"==typeof e&&(t=e,e=!1),"boolean"==typeof e&&(i=e),"object"==typeof t&&(a=Object.assign({},aa,t),null!=t.s&&null==t.ss&&(a.ss=t.s-1)),r=as(this,!i,a,n=this.localeData()),i&&(r=n.pastFuture(+this,r)),n.postformat(r)}var af=Math.abs;function ad(e){return(e>0)-(e<0)||+e}function ah(){if(!this.isValid())return this.localeData().invalidDate();var e,t,n,r,i,a,o,s,u=af(this._milliseconds)/1e3,c=af(this._days),l=af(this._months),f=this.asSeconds();return f?(e=ec(u/60),t=ec(e/60),u%=60,e%=60,n=ec(l/12),l%=12,r=u?u.toFixed(3).replace(/\.?0+$/,""):"",i=f<0?"-":"",a=ad(this._months)!==ad(f)?"-":"",o=ad(this._days)!==ad(f)?"-":"",s=ad(this._milliseconds)!==ad(f)?"-":"",i+"P"+(n?a+n+"Y":"")+(l?a+l+"M":"")+(c?o+c+"D":"")+(t||e||u?"T":"")+(t?s+t+"H":"")+(e?s+e+"M":"")+(u?s+r+"S":"")):"P0D"}var ap=nU.prototype;return ap.isValid=nY,ap.abs=ij,ap.add=iY,ap.subtract=iB,ap.as=iG,ap.asMilliseconds=iV,ap.asSeconds=iq,ap.asMinutes=iZ,ap.asHours=iX,ap.asDays=iJ,ap.asWeeks=iQ,ap.asMonths=i1,ap.asQuarters=i0,ap.asYears=i2,ap.valueOf=iW,ap._bubble=iH,ap.clone=i3,ap.get=i4,ap.milliseconds=i6,ap.seconds=i9,ap.minutes=i8,ap.hours=i7,ap.days=ae,ap.weeks=ar,ap.months=at,ap.years=an,ap.humanize=al,ap.toISOString=ah,ap.toString=ah,ap.toJSON=ah,ap.locale=rC,ap.localeData=rD,ap.toIsoString=T("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ah),ap.lang=rI,B("X",0,0,"unix"),B("x",0,0,"valueOf"),ej("x",eI),ej("X",eP),eH("X",function(e,t,n){n._d=new Date(1e3*parseFloat(e))}),eH("x",function(e,t,n){n._d=new Date(el(e))}),//! moment.js +t.version="2.29.1",r(nL),t.fn=iS,t.min=nN,t.max=nP,t.now=nR,t.utc=h,t.unix=ik,t.months=iC,t.isDate=l,t.locale=t6,t.invalid=g,t.duration=n7,t.isMoment=k,t.weekdays=iD,t.parseZone=ix,t.localeData=t7,t.isDuration=nH,t.monthsShort=iI,t.weekdaysMin=iP,t.defineLocale=t9,t.updateLocale=t8,t.locales=ne,t.weekdaysShort=iN,t.normalizeUnits=er,t.relativeTimeRounding=au,t.relativeTimeThreshold=ac,t.calendarFormat=rd,t.prototype=iS,t.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},t},e.exports=i()},46417(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n,r=!!("undefined"!=typeof window&&window.document&&window.document.createElement);function i(e){n=e}function a(){if(n)return n;if(!r||!window.document.body)return"indeterminate";var e=window.document.createElement("div");return e.appendChild(document.createTextNode("ABCD")),e.dir="rtl",e.style.fontSize="14px",e.style.width="4px",e.style.height="1px",e.style.position="absolute",e.style.top="-1000px",e.style.overflow="scroll",document.body.appendChild(e),n="reverse",e.scrollLeft>0?n="default":(e.scrollLeft=1,0===e.scrollLeft&&(n="negative")),document.body.removeChild(e),n}function o(e,t){var n=e.scrollLeft;if("rtl"!==t)return n;var r=a();if("indeterminate"===r)return Number.NaN;switch(r){case"negative":return e.scrollWidth-e.clientWidth+n;case"reverse":return e.scrollWidth-e.clientWidth-n}return n}function s(e,t,n){if("rtl"!==n){e.scrollLeft=t;return}var r=a();if("indeterminate"!==r)switch(r){case"negative":e.scrollLeft=e.clientWidth-e.scrollWidth+t;break;case"reverse":e.scrollLeft=e.scrollWidth-e.clientWidth-t;break;default:e.scrollLeft=t}}t._setScrollType=i,t.detectScrollType=a,t.getNormalizedScrollLeft=o,t.setNormalizedScrollLeft=s},27418(e){"use strict";/* +object-assign +(c) Sindre Sorhus +@license MIT +*/ var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;function i(e){if(null==e)throw TypeError("Object.assign cannot be called with null or undefined");return Object(e)}function a(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;var r=Object.getOwnPropertyNames(t).map(function(e){return t[e]});if("0123456789"!==r.join(""))return!1;var i={};if("abcdefghijklmnopqrst".split("").forEach(function(e){i[e]=e}),"abcdefghijklmnopqrst"!==Object.keys(Object.assign({},i)).join(""))return!1;return!0}catch(a){return!1}}e.exports=a()?Object.assign:function(e,a){for(var o,s,u=i(e),c=1;c65535&&(Y-=65536,G+=l(Y>>>10|55296),Y=56320|1023&Y),Y=G+l(Y))):q!==x&&$(D,Q)),Y?(ew(),X=ev(),ed=ee-1,ep+=ee-V+1,eg.push(Y),J=ev(),J.offset++,ei&&ei.call(es,Y,{start:X,end:J},e.slice(V-1,ee)),X=J):(em+=d=e.slice(V-1,ee),ep+=d.length,ed=ee-1)}else 10===F&&(eb++,eh++,ep=0),F==F?(em+=l(F),ep++):ew();return eg.join("");function ev(){return{line:eb,column:ep,offset:ed+(ec.offset||0)}}function ey(e,t){var n=ev();n.column+=t,n.offset+=t,ea.call(eu,j[e],n,e)}function ew(){em&&(eg.push(em),er&&er.call(eo,em,{start:X,end:ev()}),em="")}}function B(e){return e>=55296&&e<=57343||e>1114111}function U(e){return e>=1&&e<=8||11===e||e>=13&&e<=31||e>=127&&e<=159||e>=64976&&e<=65007||(65535&e)==65535||(65535&e)==65534}j[L]="Named character references must be terminated by a semicolon",j[C]="Numeric character references must be terminated by a semicolon",j[I]="Named character references cannot be empty",j[D]="Numeric character references cannot be empty",j[N]="Named character references must be known",j[P]="Numeric character references cannot be disallowed",j[R]="Numeric character references cannot be outside the permissible Unicode range"},14779(e){e.exports=b,e.exports.match=a,e.exports.regexpToFunction=o,e.exports.parse=r,e.exports.compile=i,e.exports.tokensToFunction=s,e.exports.tokensToRegExp=p;var t="/",n=RegExp("(\\\\.)|(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?","g");function r(e,r){for(var i,a=[],o=0,s=0,l="",f=r&&r.delimiter||t,d=r&&r.whitelist||void 0,h=!1;null!==(i=n.exec(e));){var p=i[0],b=i[1],m=i.index;if(l+=e.slice(s,m),s=m+p.length,b){l+=b[1],h=!0;continue}var g="",v=i[2],y=i[3],w=i[4],_=i[5];if(!h&&l.length){var E=l.length-1,S=l[E];(!d||d.indexOf(S)>-1)&&(g=S,l=l.slice(0,E))}l&&(a.push(l),l="",h=!1);var k="+"===_||"*"===_,x="?"===_||"*"===_,T=y||w,M=g||f;a.push({name:v||o++,prefix:g,delimiter:M,optional:x,repeat:k,pattern:T?c(T):"[^"+u(M===f?M:M+f)+"]+?"})}return(l||seM});/**! + * @fileOverview Kickass library to create and place poppers near their reference elements. + * @version 1.16.0 + * @license + * Copyright (c) 2016 Federico Zivolo and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ var r="undefined"!=typeof window&&"undefined"!=typeof document&&"undefined"!=typeof navigator,i=function(){for(var e=["Edge","Trident","Firefox"],t=0;t=0)return 1;return 0}();function a(e){var t=!1;return function(){!t&&(t=!0,window.Promise.resolve().then(function(){t=!1,e()}))}}function o(e){var t=!1;return function(){t||(t=!0,setTimeout(function(){t=!1,e()},i))}}var s=r&&window.Promise?a:o;function u(e){var t={};return e&&"[object Function]"===t.toString.call(e)}function c(e,t){if(1!==e.nodeType)return[];var n=e.ownerDocument.defaultView.getComputedStyle(e,null);return t?n[t]:n}function l(e){return"HTML"===e.nodeName?e:e.parentNode||e.host}function f(e){if(!e)return document.body;switch(e.nodeName){case"HTML":case"BODY":return e.ownerDocument.body;case"#document":return e.body}var t=c(e),n=t.overflow,r=t.overflowX,i=t.overflowY;return/(auto|scroll|overlay)/.test(n+i+r)?e:f(l(e))}function d(e){return e&&e.referenceNode?e.referenceNode:e}var h=r&&!!(window.MSInputMethodContext&&document.documentMode),p=r&&/MSIE 10/.test(navigator.userAgent);function b(e){return 11===e?h:10===e?p:h||p}function m(e){if(!e)return document.documentElement;for(var t=b(10)?document.body:null,n=e.offsetParent||null;n===t&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var r=n&&n.nodeName;return r&&"BODY"!==r&&"HTML"!==r?-1!==["TH","TD","TABLE"].indexOf(n.nodeName)&&"static"===c(n,"position")?m(n):n:e?e.ownerDocument.documentElement:document.documentElement}function g(e){var t=e.nodeName;return"BODY"!==t&&("HTML"===t||m(e.firstElementChild)===e)}function v(e){return null!==e.parentNode?v(e.parentNode):e}function y(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var n=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,r=n?e:t,i=n?t:e,a=document.createRange();a.setStart(r,0),a.setEnd(i,0);var o=a.commonAncestorContainer;if(e!==o&&t!==o||r.contains(i))return g(o)?o:m(o);var s=v(e);return s.host?y(s.host,t):y(e,v(t).host)}function w(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"top",n="top"===t?"scrollTop":"scrollLeft",r=e.nodeName;if("BODY"===r||"HTML"===r){var i=e.ownerDocument.documentElement;return(e.ownerDocument.scrollingElement||i)[n]}return e[n]}function _(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=w(t,"top"),i=w(t,"left"),a=n?-1:1;return e.top+=r*a,e.bottom+=r*a,e.left+=i*a,e.right+=i*a,e}function E(e,t){var n="x"===t?"Left":"Top",r="Left"===n?"Right":"Bottom";return parseFloat(e["border"+n+"Width"],10)+parseFloat(e["border"+r+"Width"],10)}function S(e,t,n,r){return Math.max(t["offset"+e],t["scroll"+e],n["client"+e],n["offset"+e],n["scroll"+e],b(10)?parseInt(n["offset"+e])+parseInt(r["margin"+("Height"===e?"Top":"Left")])+parseInt(r["margin"+("Height"===e?"Bottom":"Right")]):0)}function k(e){var t=e.body,n=e.documentElement,r=b(10)&&getComputedStyle(n);return{height:S("Height",t,n,r),width:S("Width",t,n,r)}}var x=function(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")},T=function(){function e(e,t){for(var n=0;n2&&void 0!==arguments[2]&&arguments[2],r=b(10),i="HTML"===t.nodeName,a=L(e),o=L(t),s=f(e),u=c(t),l=parseFloat(u.borderTopWidth,10),d=parseFloat(u.borderLeftWidth,10);n&&i&&(o.top=Math.max(o.top,0),o.left=Math.max(o.left,0));var h=A({top:a.top-o.top-l,left:a.left-o.left-d,width:a.width,height:a.height});if(h.marginTop=0,h.marginLeft=0,!r&&i){var p=parseFloat(u.marginTop,10),m=parseFloat(u.marginLeft,10);h.top-=l-p,h.bottom-=l-p,h.left-=d-m,h.right-=d-m,h.marginTop=p,h.marginLeft=m}return(r&&!n?t.contains(s):t===s&&"BODY"!==s.nodeName)&&(h=_(h,t)),h}function I(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=e.ownerDocument.documentElement,r=C(e,n),i=Math.max(n.clientWidth,window.innerWidth||0),a=Math.max(n.clientHeight,window.innerHeight||0),o=t?0:w(n),s=t?0:w(n,"left");return A({top:o-r.top+r.marginTop,left:s-r.left+r.marginLeft,width:i,height:a})}function D(e){var t=e.nodeName;if("BODY"===t||"HTML"===t)return!1;if("fixed"===c(e,"position"))return!0;var n=l(e);return!!n&&D(n)}function N(e){if(!e||!e.parentElement||b())return document.documentElement;for(var t=e.parentElement;t&&"none"===c(t,"transform");)t=t.parentElement;return t||document.documentElement}function P(e,t,n,r){var i=arguments.length>4&&void 0!==arguments[4]&&arguments[4],a={top:0,left:0},o=i?N(e):y(e,d(t));if("viewport"===r)a=I(o,i);else{var s=void 0;"scrollParent"===r?"BODY"===(s=f(l(t))).nodeName&&(s=e.ownerDocument.documentElement):s="window"===r?e.ownerDocument.documentElement:r;var u=C(s,o,i);if("HTML"!==s.nodeName||D(o))a=u;else{var c=k(e.ownerDocument),h=c.height,p=c.width;a.top+=u.top-u.marginTop,a.bottom=h+u.top,a.left+=u.left-u.marginLeft,a.right=p+u.left}}var b="number"==typeof(n=n||0);return a.left+=b?n:n.left||0,a.top+=b?n:n.top||0,a.right-=b?n:n.right||0,a.bottom-=b?n:n.bottom||0,a}function R(e){var t;return e.width*e.height}function j(e,t,n,r,i){var a=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0;if(-1===e.indexOf("auto"))return e;var o=P(n,r,a,i),s={top:{width:o.width,height:t.top-o.top},right:{width:o.right-t.right,height:o.height},bottom:{width:o.width,height:o.bottom-t.bottom},left:{width:t.left-o.left,height:o.height}},u=Object.keys(s).map(function(e){return O({key:e},s[e],{area:R(s[e])})}).sort(function(e,t){return t.area-e.area}),c=u.filter(function(e){var t=e.width,r=e.height;return t>=n.clientWidth&&r>=n.clientHeight}),l=c.length>0?c[0].key:u[0].key,f=e.split("-")[1];return l+(f?"-"+f:"")}function F(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,i=r?N(t):y(t,d(n));return C(n,i,r)}function Y(e){var t=e.ownerDocument.defaultView.getComputedStyle(e),n=parseFloat(t.marginTop||0)+parseFloat(t.marginBottom||0),r=parseFloat(t.marginLeft||0)+parseFloat(t.marginRight||0);return{width:e.offsetWidth+r,height:e.offsetHeight+n}}function B(e){var t={left:"right",right:"left",bottom:"top",top:"bottom"};return e.replace(/left|right|bottom|top/g,function(e){return t[e]})}function U(e,t,n){n=n.split("-")[0];var r=Y(e),i={width:r.width,height:r.height},a=-1!==["right","left"].indexOf(n),o=a?"top":"left",s=a?"left":"top",u=a?"height":"width",c=a?"width":"height";return i[o]=t[o]+t[u]/2-r[u]/2,n===s?i[s]=t[s]-r[c]:i[s]=t[B(s)],i}function H(e,t){return Array.prototype.find?e.find(t):e.filter(t)[0]}function $(e,t,n){if(Array.prototype.findIndex)return e.findIndex(function(e){return e[t]===n});var r=H(e,function(e){return e[t]===n});return e.indexOf(r)}function z(e,t,n){return(void 0===n?e:e.slice(0,$(e,"name",n))).forEach(function(e){e.function&&console.warn("`modifier.function` is deprecated, use `modifier.fn`!");var n=e.function||e.fn;e.enabled&&u(n)&&(t.offsets.popper=A(t.offsets.popper),t.offsets.reference=A(t.offsets.reference),t=n(t,e))}),t}function G(){if(!this.state.isDestroyed){var e={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};e.offsets.reference=F(this.state,this.popper,this.reference,this.options.positionFixed),e.placement=j(this.options.placement,e.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),e.originalPlacement=e.placement,e.positionFixed=this.options.positionFixed,e.offsets.popper=U(this.popper,e.offsets.reference,e.placement),e.offsets.popper.position=this.options.positionFixed?"fixed":"absolute",e=z(this.modifiers,e),this.state.isCreated?this.options.onUpdate(e):(this.state.isCreated=!0,this.options.onCreate(e))}}function W(e,t){return e.some(function(e){var n=e.name;return e.enabled&&n===t})}function K(e){for(var t=[!1,"ms","Webkit","Moz","O"],n=e.charAt(0).toUpperCase()+e.slice(1),r=0;ro[p]&&(e.offsets.popper[d]+=s[d]+b-o[p]),e.offsets.popper=A(e.offsets.popper);var m=s[d]+s[l]/2-b/2,g=c(e.instance.popper),v=parseFloat(g["margin"+f],10),y=parseFloat(g["border"+f+"Width"],10),w=m-e.offsets.popper[d]-v-y;return w=Math.max(Math.min(o[l]-b,w),0),e.arrowElement=r,e.offsets.arrow=(M(n={},d,Math.round(w)),M(n,h,""),n),e}function ef(e){return"end"===e?"start":"start"===e?"end":e}var ed=["auto-start","auto","auto-end","top-start","top","top-end","right-start","right","right-end","bottom-end","bottom","bottom-start","left-end","left","left-start"],eh=ed.slice(3);function ep(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=eh.indexOf(e),r=eh.slice(n+1).concat(eh.slice(0,n));return t?r.reverse():r}var eb={FLIP:"flip",CLOCKWISE:"clockwise",COUNTERCLOCKWISE:"counterclockwise"};function em(e,t){if(W(e.instance.modifiers,"inner")||e.flipped&&e.placement===e.originalPlacement)return e;var n=P(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),r=e.placement.split("-")[0],i=B(r),a=e.placement.split("-")[1]||"",o=[];switch(t.behavior){case eb.FLIP:o=[r,i];break;case eb.CLOCKWISE:o=ep(r);break;case eb.COUNTERCLOCKWISE:o=ep(r,!0);break;default:o=t.behavior}return o.forEach(function(s,u){if(r!==s||o.length===u+1)return e;i=B(r=e.placement.split("-")[0]);var c=e.offsets.popper,l=e.offsets.reference,f=Math.floor,d="left"===r&&f(c.right)>f(l.left)||"right"===r&&f(c.left)f(l.top)||"bottom"===r&&f(c.top)f(n.right),b=f(c.top)f(n.bottom),g="left"===r&&h||"right"===r&&p||"top"===r&&b||"bottom"===r&&m,v=-1!==["top","bottom"].indexOf(r),y=!!t.flipVariations&&(v&&"start"===a&&h||v&&"end"===a&&p||!v&&"start"===a&&b||!v&&"end"===a&&m),w=!!t.flipVariationsByContent&&(v&&"start"===a&&p||v&&"end"===a&&h||!v&&"start"===a&&m||!v&&"end"===a&&b),_=y||w;(d||g||_)&&(e.flipped=!0,(d||g)&&(r=o[u+1]),_&&(a=ef(a)),e.placement=r+(a?"-"+a:""),e.offsets.popper=O({},e.offsets.popper,U(e.instance.popper,e.offsets.reference,e.placement)),e=z(e.instance.modifiers,e,"flip"))}),e}function eg(e){var t=e.offsets,n=t.popper,r=t.reference,i=e.placement.split("-")[0],a=Math.floor,o=-1!==["top","bottom"].indexOf(i),s=o?"right":"bottom",u=o?"left":"top",c=o?"width":"height";return n[s]a(r[s])&&(e.offsets.popper[u]=a(r[s])),e}function ev(e,t,n,r){var i=e.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),a=+i[1],o=i[2];if(!a)return e;if(0===o.indexOf("%")){var s=void 0;return A(s="%p"===o?n:r)[t]/100*a}if("vh"!==o&&"vw"!==o)return a;var u=void 0;return(u="vh"===o?Math.max(document.documentElement.clientHeight,window.innerHeight||0):Math.max(document.documentElement.clientWidth,window.innerWidth||0))/100*a}function ey(e,t,n,r){var i=[0,0],a=-1!==["right","left"].indexOf(r),o=e.split(/(\+|\-)/).map(function(e){return e.trim()}),s=o.indexOf(H(o,function(e){return -1!==e.search(/,|\s/)}));o[s]&&-1===o[s].indexOf(",")&&console.warn("Offsets separated by white space(s) are deprecated, use a comma (,) instead.");var u=/\s*,\s*|\s+/,c=-1!==s?[o.slice(0,s).concat([o[s].split(u)[0]]),[o[s].split(u)[1]].concat(o.slice(s+1))]:[o];return(c=c.map(function(e,r){var i=(1===r?!a:a)?"height":"width",o=!1;return e.reduce(function(e,t){return""===e[e.length-1]&&-1!==["+","-"].indexOf(t)?(e[e.length-1]=t,o=!0,e):o?(e[e.length-1]+=t,o=!1,e):e.concat(t)},[]).map(function(e){return ev(e,i,t,n)})})).forEach(function(e,t){e.forEach(function(n,r){et(n)&&(i[t]+=n*("-"===e[r-1]?-1:1))})}),i}function ew(e,t){var n=t.offset,r=e.placement,i=e.offsets,a=i.popper,o=i.reference,s=r.split("-")[0],u=void 0;return u=et(+n)?[+n,0]:ey(n,a,o,s),"left"===s?(a.top+=u[0],a.left-=u[1]):"right"===s?(a.top+=u[0],a.left+=u[1]):"top"===s?(a.left+=u[0],a.top-=u[1]):"bottom"===s&&(a.left+=u[0],a.top+=u[1]),e.popper=a,e}function e_(e,t){var n=t.boundariesElement||m(e.instance.popper);e.instance.reference===n&&(n=m(n));var r=K("transform"),i=e.instance.popper.style,a=i.top,o=i.left,s=i[r];i.top="",i.left="",i[r]="";var u=P(e.instance.popper,e.instance.reference,t.padding,n,e.positionFixed);i.top=a,i.left=o,i[r]=s,t.boundaries=u;var c=t.priority,l=e.offsets.popper,f={primary:function(e){var n=l[e];return l[e]u[e]&&!t.escapeWithReference&&(r=Math.min(l[n],u[e]-("right"===e?l.width:l.height))),M({},n,r)}};return c.forEach(function(e){l=O({},l,f[-1!==["left","top"].indexOf(e)?"primary":"secondary"](e))}),e.offsets.popper=l,e}function eE(e){var t=e.placement,n=t.split("-")[0],r=t.split("-")[1];if(r){var i=e.offsets,a=i.reference,o=i.popper,s=-1!==["bottom","top"].indexOf(n),u=s?"left":"top",c=s?"width":"height",l={start:M({},u,a[u]),end:M({},u,a[u]+a[c]-o[c])};e.offsets.popper=O({},o,l[r])}return e}function eS(e){if(!ec(e.instance.modifiers,"hide","preventOverflow"))return e;var t=e.offsets.reference,n=H(e.instance.modifiers,function(e){return"preventOverflow"===e.name}).boundaries;if(t.bottomn.right||t.top>n.bottom||t.right2&&void 0!==arguments[2]?arguments[2]:{};x(this,e),this.scheduleUpdate=function(){return requestAnimationFrame(r.update)},this.update=s(this.update.bind(this)),this.options=O({},e.Defaults,i),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=t&&t.jquery?t[0]:t,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},Object.keys(O({},e.Defaults.modifiers,i.modifiers)).forEach(function(t){r.options.modifiers[t]=O({},e.Defaults.modifiers[t]||{},i.modifiers?i.modifiers[t]:{})}),this.modifiers=Object.keys(this.options.modifiers).map(function(e){return O({name:e},r.options.modifiers[e])}).sort(function(e,t){return e.order-t.order}),this.modifiers.forEach(function(e){e.enabled&&u(e.onLoad)&&e.onLoad(r.reference,r.popper,r.options,e,r.state)}),this.update();var a=this.options.eventsEnabled;a&&this.enableEventListeners(),this.state.eventsEnabled=a}return T(e,[{key:"update",value:function(){return G.call(this)}},{key:"destroy",value:function(){return V.call(this)}},{key:"enableEventListeners",value:function(){return J.call(this)}},{key:"disableEventListeners",value:function(){return ee.call(this)}}]),e}();eT.Utils=("undefined"!=typeof window?window:n.g).PopperUtils,eT.placements=ed,eT.Defaults=ex;let eM=eT},92703(e,t,n){"use strict";var r=n(50414);function i(){}function a(){}a.resetWarningCache=i,e.exports=function(){function e(e,t,n,i,a,o){if(o!==r){var s=Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name="Invariant Violation",s}}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:a,resetWarningCache:i};return n.PropTypes=n,n}},45697(e,t,n){e.exports=n(92703)()},50414(e){"use strict";var t="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED";e.exports=t},55760(e){"use strict";function t(e){this._maxSize=e,this.clear()}t.prototype.clear=function(){this._size=0,this._values=Object.create(null)},t.prototype.get=function(e){return this._values[e]},t.prototype.set=function(e,t){return this._size>=this._maxSize&&this.clear(),!(e in this._values)&&this._size++,this._values[e]=t};var n=/[^.^\]^[]+|(?=\[\]|\.\.)/g,r=/^\d+$/,i=/^\d/,a=/[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/g,o=/^\s*(['"]?)(.*?)(\1)\s*$/,s=512,u=new t(s),c=new t(s),l=new t(s);function f(e){return u.get(e)||u.set(e,d(e).map(function(e){return e.replace(o,"$2")}))}function d(e){return e.match(n)}function h(e,t,n){var r,i,a,o,s=e.length;for(i=0;i4&&n.slice(0,4)===o&&s.test(t)&&("-"===t.charAt(4)?u=f(t):t=d(t),c=i),new c(u,t))}function f(e){var t=e.slice(5).replace(u,p);return o+t.charAt(0).toUpperCase()+t.slice(1)}function d(e){var t=e.slice(4);return u.test(t)?e:("-"!==(t=t.replace(c,h)).charAt(0)&&(t="-"+t),o+t)}function h(e){return"-"+e.toLowerCase()}function p(e){return e.charAt(1).toUpperCase()}},97247(e,t,n){"use strict";var r=n(19940),i=n(8289),a=n(5812),o=n(94397),s=n(67716),u=n(61805);e.exports=r([a,i,o,s,u])},67716(e,t,n){"use strict";var r=n(17e3),i=n(17596),a=r.booleanish,o=r.number,s=r.spaceSeparated;function u(e,t){return"role"===t?t:"aria-"+t.slice(4).toLowerCase()}e.exports=i({transform:u,properties:{ariaActiveDescendant:null,ariaAtomic:a,ariaAutoComplete:null,ariaBusy:a,ariaChecked:a,ariaColCount:o,ariaColIndex:o,ariaColSpan:o,ariaControls:s,ariaCurrent:null,ariaDescribedBy:s,ariaDetails:null,ariaDisabled:a,ariaDropEffect:s,ariaErrorMessage:null,ariaExpanded:a,ariaFlowTo:s,ariaGrabbed:a,ariaHasPopup:null,ariaHidden:a,ariaInvalid:null,ariaKeyShortcuts:null,ariaLabel:null,ariaLabelledBy:s,ariaLevel:o,ariaLive:null,ariaModal:a,ariaMultiLine:a,ariaMultiSelectable:a,ariaOrientation:null,ariaOwns:s,ariaPlaceholder:null,ariaPosInSet:o,ariaPressed:a,ariaReadOnly:a,ariaRelevant:null,ariaRequired:a,ariaRoleDescription:s,ariaRowCount:o,ariaRowIndex:o,ariaRowSpan:o,ariaSelected:a,ariaSetSize:o,ariaSort:null,ariaValueMax:o,ariaValueMin:o,ariaValueNow:o,ariaValueText:null,role:null}})},61805(e,t,n){"use strict";var r=n(17e3),i=n(17596),a=n(10855),o=r.boolean,s=r.overloadedBoolean,u=r.booleanish,c=r.number,l=r.spaceSeparated,f=r.commaSeparated;e.exports=i({space:"html",attributes:{acceptcharset:"accept-charset",classname:"class",htmlfor:"for",httpequiv:"http-equiv"},transform:a,mustUseProperty:["checked","multiple","muted","selected"],properties:{abbr:null,accept:f,acceptCharset:l,accessKey:l,action:null,allow:null,allowFullScreen:o,allowPaymentRequest:o,allowUserMedia:o,alt:null,as:null,async:o,autoCapitalize:null,autoComplete:l,autoFocus:o,autoPlay:o,capture:o,charSet:null,checked:o,cite:null,className:l,cols:c,colSpan:null,content:null,contentEditable:u,controls:o,controlsList:l,coords:c|f,crossOrigin:null,data:null,dateTime:null,decoding:null,default:o,defer:o,dir:null,dirName:null,disabled:o,download:s,draggable:u,encType:null,enterKeyHint:null,form:null,formAction:null,formEncType:null,formMethod:null,formNoValidate:o,formTarget:null,headers:l,height:c,hidden:o,high:c,href:null,hrefLang:null,htmlFor:l,httpEquiv:l,id:null,imageSizes:null,imageSrcSet:f,inputMode:null,integrity:null,is:null,isMap:o,itemId:null,itemProp:l,itemRef:l,itemScope:o,itemType:l,kind:null,label:null,lang:null,language:null,list:null,loading:null,loop:o,low:c,manifest:null,max:null,maxLength:c,media:null,method:null,min:null,minLength:c,multiple:o,muted:o,name:null,nonce:null,noModule:o,noValidate:o,onAbort:null,onAfterPrint:null,onAuxClick:null,onBeforePrint:null,onBeforeUnload:null,onBlur:null,onCancel:null,onCanPlay:null,onCanPlayThrough:null,onChange:null,onClick:null,onClose:null,onContextMenu:null,onCopy:null,onCueChange:null,onCut:null,onDblClick:null,onDrag:null,onDragEnd:null,onDragEnter:null,onDragExit:null,onDragLeave:null,onDragOver:null,onDragStart:null,onDrop:null,onDurationChange:null,onEmptied:null,onEnded:null,onError:null,onFocus:null,onFormData:null,onHashChange:null,onInput:null,onInvalid:null,onKeyDown:null,onKeyPress:null,onKeyUp:null,onLanguageChange:null,onLoad:null,onLoadedData:null,onLoadedMetadata:null,onLoadEnd:null,onLoadStart:null,onMessage:null,onMessageError:null,onMouseDown:null,onMouseEnter:null,onMouseLeave:null,onMouseMove:null,onMouseOut:null,onMouseOver:null,onMouseUp:null,onOffline:null,onOnline:null,onPageHide:null,onPageShow:null,onPaste:null,onPause:null,onPlay:null,onPlaying:null,onPopState:null,onProgress:null,onRateChange:null,onRejectionHandled:null,onReset:null,onResize:null,onScroll:null,onSecurityPolicyViolation:null,onSeeked:null,onSeeking:null,onSelect:null,onSlotChange:null,onStalled:null,onStorage:null,onSubmit:null,onSuspend:null,onTimeUpdate:null,onToggle:null,onUnhandledRejection:null,onUnload:null,onVolumeChange:null,onWaiting:null,onWheel:null,open:o,optimum:c,pattern:null,ping:l,placeholder:null,playsInline:o,poster:null,preload:null,readOnly:o,referrerPolicy:null,rel:l,required:o,reversed:o,rows:c,rowSpan:c,sandbox:l,scope:null,scoped:o,seamless:o,selected:o,shape:null,size:c,sizes:null,slot:null,span:c,spellCheck:u,src:null,srcDoc:null,srcLang:null,srcSet:f,start:c,step:null,style:null,tabIndex:c,target:null,title:null,translate:null,type:null,typeMustMatch:o,useMap:null,value:u,width:c,wrap:null,align:null,aLink:null,archive:l,axis:null,background:null,bgColor:null,border:c,borderColor:null,bottomMargin:c,cellPadding:null,cellSpacing:null,char:null,charOff:null,classId:null,clear:null,code:null,codeBase:null,codeType:null,color:null,compact:o,declare:o,event:null,face:null,frame:null,frameBorder:null,hSpace:c,leftMargin:c,link:null,longDesc:null,lowSrc:null,marginHeight:c,marginWidth:c,noResize:o,noHref:o,noShade:o,noWrap:o,object:null,profile:null,prompt:null,rev:null,rightMargin:c,rules:null,scheme:null,scrolling:u,standby:null,summary:null,text:null,topMargin:c,valueType:null,version:null,vAlign:null,vLink:null,vSpace:c,allowTransparency:null,autoCorrect:null,autoSave:null,disablePictureInPicture:o,disableRemotePlayback:o,prefix:null,property:null,results:c,security:null,unselectable:null}})},10855(e,t,n){"use strict";var r=n(28740);function i(e,t){return r(e,t.toLowerCase())}e.exports=i},28740(e){"use strict";function t(e,t){return t in e?e[t]:t}e.exports=t},17596(e,t,n){"use strict";var r=n(66632),i=n(99607),a=n(81674);function o(e){var t,n,o=e.space,s=e.mustUseProperty||[],u=e.attributes||{},c=e.properties,l=e.transform,f={},d={};for(t in c)n=new a(t,l(u,t),c[t],o),-1!==s.indexOf(t)&&(n.mustUseProperty=!0),f[t]=n,d[r(t)]=t,d[r(n.attribute)]=t;return new i(f,d,o)}e.exports=o},81674(e,t,n){"use strict";var r=n(57643),i=n(17e3);e.exports=s,s.prototype=new r,s.prototype.defined=!0;var a=["boolean","booleanish","overloadedBoolean","number","commaSeparated","spaceSeparated","commaOrSpaceSeparated"],o=a.length;function s(e,t,n,s){var c,l=-1;for(u(this,"space",s),r.call(this,e,t);++l=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function l(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}function f(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function d(e,t){for(var n=0;n1&&void 0!==arguments[1]&&arguments[1];return n._tick((0,d.updateNodeHighlightedValue)(n.state.nodes,n.state.links,n.state.config,e,t))}),O(S(n),"_tick",function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0;return t?n.setState(e,t):n.setState(e)}),O(S(n),"_zoomConfig",function(){var e=(0,o.select)("#".concat(n.state.id,"-").concat(u.default.GRAPH_WRAPPER_ID)),t=(0,s.zoom)().scaleExtent([n.state.config.minZoom,n.state.config.maxZoom]);n.state.config.freezeAllDragEvents||t.on("zoom",n._zoomed),null!==n.state.config.initialZoom&&t.scaleTo(e,n.state.config.initialZoom),e.call(t).on("dblclick.zoom",null)}),O(S(n),"_zoomed",function(){var e=o.event.transform;(0,o.selectAll)("#".concat(n.state.id,"-").concat(u.default.GRAPH_CONTAINER_ID)).attr("transform",e),n.state.config.panAndZoom&&n.setState({transform:e.k}),n.debouncedOnZoomChange&&n.state.previousZoom!==e.k&&(n.debouncedOnZoomChange(n.state.previousZoom,e.k),n.setState({previousZoom:e.k}))}),O(S(n),"onClickGraph",function(e){n.state.enableFocusAnimation&&n.setState({enableFocusAnimation:!1});var t,r,i,a=e.target&&e.target.tagName,o=null==e?void 0:null===(t=e.target)||void 0===t?void 0:null===(r=t.attributes)||void 0===r?void 0:null===(i=r.name)||void 0===i?void 0:i.value,s="svg-container-".concat(n.state.id);"SVG"===a.toUpperCase()&&o===s&&n.props.onClickGraph&&n.props.onClickGraph(e)}),O(S(n),"onClickNode",function(e){var t=n.state.nodes[e];if(n.state.config.collapsible){var r=(0,f.getTargetLeafConnections)(e,n.state.links,n.state.config),i=(0,f.toggleLinksMatrixConnections)(n.state.links,r,n.state.config),a=(0,f.toggleLinksConnections)(n.state.d3Links,i),o=null==r?void 0:r["0"],s=!1;o&&(s=1===i[o.source][o.target]),n._tick({links:i,d3Links:a},function(){n.props.onClickNode&&n.props.onClickNode(e,t),s&&n._graphNodeDragConfig()})}else n.nodeClickTimer?(n.props.onDoubleClickNode&&n.props.onDoubleClickNode(e,t),n.nodeClickTimer=clearTimeout(n.nodeClickTimer)):n.nodeClickTimer=setTimeout(function(){n.props.onClickNode&&n.props.onClickNode(e,t),n.nodeClickTimer=null},u.default.TTL_DOUBLE_CLICK_IN_MS)}),O(S(n),"onRightClickNode",function(e,t){var r=n.state.nodes[t];n.props.onRightClickNode&&n.props.onRightClickNode(e,t,r)}),O(S(n),"onMouseOverNode",function(e){if(!n.isDraggingNode){var t=n.state.nodes[e];n.props.onMouseOverNode&&n.props.onMouseOverNode(e,t),n.state.config.nodeHighlightBehavior&&n._setNodeHighlightedValue(e,!0)}}),O(S(n),"onMouseOutNode",function(e){if(!n.isDraggingNode){var t=n.state.nodes[e];n.props.onMouseOutNode&&n.props.onMouseOutNode(e,t),n.state.config.nodeHighlightBehavior&&n._setNodeHighlightedValue(e,!1)}}),O(S(n),"onMouseOverLink",function(e,t){if(n.props.onMouseOverLink&&n.props.onMouseOverLink(e,t),n.state.config.linkHighlightBehavior){var r={source:e,target:t};n._tick({highlightedLink:r})}}),O(S(n),"onMouseOutLink",function(e,t){if(n.props.onMouseOutLink&&n.props.onMouseOutLink(e,t),n.state.config.linkHighlightBehavior){var r=void 0;n._tick({highlightedLink:r})}}),O(S(n),"onNodePositionChange",function(e){if(n.props.onNodePositionChange){var t=e.id,r=e.x,i=e.y;n.props.onNodePositionChange(t,r,i)}}),O(S(n),"pauseSimulation",function(){return n.state.simulation.stop()}),O(S(n),"resetNodesPositions",function(){if(!n.state.config.staticGraph){var e=(0,d.initializeNodes)(n.props.data.nodes);for(var t in n.state.nodes){var r=n.state.nodes[t];if(r.fx&&r.fy&&(Reflect.deleteProperty(r,"fx"),Reflect.deleteProperty(r,"fy")),t in e){var i=e[t];r.x=i.x,r.y=i.y}}n.state.simulation.alphaTarget(n.state.config.d3.alphaTarget).restart(),n._tick()}}),O(S(n),"restartSimulation",function(){return!n.state.config.staticGraph&&n.state.simulation.restart()}),n.props.id||(0,p.throwErr)(n.constructor.name,l.default.GRAPH_NO_ID_PROP),n.focusAnimationTimeout=null,n.nodeClickTimer=null,n.isDraggingNode=!1,n.state=(0,d.initializeGraphState)(n.props,n.state),n.debouncedOnZoomChange=n.props.onZoomChange?(0,p.debounce)(n.props.onZoomChange,100):null,n}return T(t,e),x(t,[{key:"_graphLinkForceConfig",value:function(){var e=(0,a.forceLink)(this.state.d3Links).id(function(e){return e.id}).distance(this.state.config.d3.linkLength).strength(this.state.config.d3.linkStrength);this.state.simulation.force(u.default.LINK_CLASS_NAME,e)}},{key:"_graphNodeDragConfig",value:function(){var e=(0,i.drag)().on("start",this._onDragStart).on("drag",this._onDragMove).on("end",this._onDragEnd);(0,o.select)("#".concat(this.state.id,"-").concat(u.default.GRAPH_WRAPPER_ID)).selectAll(".node").call(e)}},{key:"_graphBindD3ToReactComponent",value:function(){this.state.config.d3.disableLinkForce||(this.state.simulation.nodes(this.state.d3Nodes).on("tick",this._tick),this._graphLinkForceConfig()),this.state.config.freezeAllDragEvents||this._graphNodeDragConfig()}}]),x(t,[{key:"UNSAFE_componentWillReceiveProps",value:function(e){var t=(0,d.checkForGraphElementsChanges)(e,this.state),n=t.graphElementsUpdated,r=t.newGraphElements,i=n?(0,d.initializeGraphState)(e,this.state):this.state,a=e.config||{},o=(0,d.checkForGraphConfigChanges)(e,this.state),s=o.configUpdated,l=o.d3ConfigUpdated,f=s?(0,p.merge)(c.default,a):this.state.config;r&&this.pauseSimulation();var h=a.panAndZoom!==this.state.config.panAndZoom?1:this.state.transform,b=e.data.focusedNodeId,m=this.state.d3Nodes.find(function(e){return"".concat(e.id)==="".concat(b)}),g="".concat(this.state.id,"-").concat(u.default.GRAPH_WRAPPER_ID),v=(0,d.getCenterAndZoomTransformation)(m,this.state.config,g)||this.state.focusTransformation,w=this.props.data.focusedNodeId!==e.data.focusedNodeId;e.onZoomChange&&(this.debouncedOnZoomChange=(0,p.debounce)(e.onZoomChange,100)),this.setState(y({},i,{config:f,configUpdated:s,d3ConfigUpdated:l,newGraphElements:r,transform:h,focusedNodeId:b,enableFocusAnimation:w,focusTransformation:v}))}},{key:"componentDidUpdate",value:function(){(this.state.config.staticGraph||this.state.config.staticGraphWithDragAndDrop)&&this.pauseSimulation(),!this.state.config.staticGraph&&(this.state.newGraphElements||this.state.d3ConfigUpdated)?(this._graphBindD3ToReactComponent(),this.state.config.staticGraphWithDragAndDrop||this.restartSimulation(),this.setState({newGraphElements:!1,d3ConfigUpdated:!1})):this.state.configUpdated&&this._graphNodeDragConfig(),this.state.configUpdated&&(this._zoomConfig(),this.setState({configUpdated:!1}))}},{key:"componentDidMount",value:function(){this.state.config.staticGraph||this._graphBindD3ToReactComponent(),this._zoomConfig()}},{key:"componentWillUnmount",value:function(){this.pauseSimulation(),this.nodeClickTimer&&(clearTimeout(this.nodeClickTimer),this.nodeClickTimer=null),this.focusAnimationTimeout&&(clearTimeout(this.focusAnimationTimeout),this.focusAnimationTimeout=null)}},{key:"render",value:function(){var e=(0,h.renderGraph)(this.state.nodes,{onClickNode:this.onClickNode,onDoubleClickNode:this.onDoubleClickNode,onRightClickNode:this.onRightClickNode,onMouseOverNode:this.onMouseOverNode,onMouseOut:this.onMouseOutNode},this.state.d3Links,this.state.links,{onClickLink:this.props.onClickLink,onRightClickLink:this.props.onRightClickLink,onMouseOverLink:this.onMouseOverLink,onMouseOutLink:this.onMouseOutLink},this.state.config,this.state.highlightedNode,this.state.highlightedLink,this.state.transform),t=e.nodes,n=e.links,i=e.defs,a={height:this.state.config.height,width:this.state.config.width},o=this._generateFocusAnimationProps();return r.default.createElement("div",{id:"".concat(this.state.id,"-").concat(u.default.GRAPH_WRAPPER_ID)},r.default.createElement("svg",{name:"svg-container-".concat(this.state.id),style:a,onClick:this.onClickGraph},i,r.default.createElement("g",g({id:"".concat(this.state.id,"-").concat(u.default.GRAPH_CONTAINER_ID)},o),n,t)))}}]),t}(r.default.Component);t.default=A},37973(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.computeNodeDegree=l,t.getTargetLeafConnections=f,t.isNodeVisible=d,t.toggleLinksConnections=h,t.toggleLinksMatrixConnections=p;var r=n(52694);function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function a(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};return Object.keys(t).reduce(function(n,r){return t[r]?Object.keys(t[r]).reduce(function(n,i){return e===r&&(n.outDegree+=t[e][i]),e===i&&(n.inDegree+=t[r][e]),n},n):n},{inDegree:0,outDegree:0})}function f(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=n.directed;return(t[e]?Object.keys(t[e]):[]).reduce(function(n,i){return c(i,t,r)&&n.push({source:e,target:i}),n},[])}function d(e,t,n){if(!t[e])return!1;if(t[e]._orphan)return!0;var r=l(e,n),i=r.inDegree,a=r.outDegree;return i>0||a>0}function h(e,t){return e.map(function(e){var n=e.source,i=e.target,o=(0,r.getId)(n),s=(0,r.getId)(i);return a({},e,{isHidden:!(t&&t[o]&&t[o][s])})})}function p(e,t,n){var r=n.directed;return t.reduce(function(e,t){e[t.source]||(e[t.source]={}),e[t.source][t.target]||(e[t.source][t.target]=0);var n=0===e[t.source][t.target]?1:0;return e[t.source][t.target]=n,r||(e[t.target][t.source]=n),e},a({},e))}n(69901)},99182(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.buildLinkProps=h,t.buildNodeProps=p;var r=s(n(53880)),i=n(37109),a=n(80362),o=n(52694);function s(e){return e&&e.__esModule?e:{default:e}}function u(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function c(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:{},i=arguments.length>3?arguments[3]:void 0,a=arguments.length>4?arguments[4]:void 0,o=arguments.length>5?arguments[5]:void 0,s=e.highlighted||e.id===(a&&a.source)||e.id===(a&&a.target),u=d(e,i,a,t),l=e.color||t.node.color;s&&t.node.highlightColor!==r.default.KEYWORDS.SAME&&(l=t.node.highlightColor);var h=e.strokeColor||t.node.strokeColor;s&&t.node.highlightStrokeColor!==r.default.KEYWORDS.SAME&&(h=t.node.highlightStrokeColor);var p=e[t.node.labelProperty]||e.id;"function"==typeof t.node.labelProperty&&(p=t.node.labelProperty(e));var b=e.labelPosition||t.node.labelPosition,m=e.strokeWidth||t.node.strokeWidth;s&&t.node.highlightStrokeWidth!==r.default.KEYWORDS.SAME&&(m=t.node.highlightStrokeWidth);var g=1/o,v=e.size||t.node.size,y="object"!==f(v),w=0;y?w=v:"top"===b||"bottom"===b?w=v.height:("right"===b||"left"===b)&&(w=v.width);var _=e.fontSize||t.node.fontSize,E=e.highlightFontSize||t.node.highlightFontSize,S=s?E:_,k=S*g+w/100+1.5,x=e.svg||t.node.svg,T=e.fontColor||t.node.fontColor,M=t.node.renderLabel;return void 0!==e.renderLabel&&"boolean"==typeof e.renderLabel&&(M=e.renderLabel),c({},e,{className:r.default.NODE_CLASS_NAME,cursor:t.node.mouseCursor,cx:(null==e?void 0:e.x)||"0",cy:(null==e?void 0:e.y)||"0",dx:k,fill:l,fontColor:T,fontSize:S*g,fontWeight:s?t.node.highlightFontWeight:t.node.fontWeight,id:e.id,label:p,labelPosition:b,opacity:u,overrideGlobalViewGenerator:!e.viewGenerator&&e.svg,renderLabel:M,size:y?v*g:{height:v.height*g,width:v.width*g},stroke:h,strokeWidth:m*g,svg:x,type:e.symbolType||t.node.symbolType,viewGenerator:e.viewGenerator||t.node.viewGenerator,onClickNode:n.onClickNode,onMouseOut:n.onMouseOut,onMouseOverNode:n.onMouseOverNode,onRightClickNode:n.onRightClickNode})}},98510(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={automaticRearrangeAfterDropNode:!1,collapsible:!1,directed:!1,focusAnimationDuration:.75,focusZoom:1,freezeAllDragEvents:!1,height:400,highlightDegree:1,highlightOpacity:1,linkHighlightBehavior:!1,maxZoom:8,minZoom:.1,initialZoom:null,nodeHighlightBehavior:!1,panAndZoom:!1,staticGraph:!1,staticGraphWithDragAndDrop:!1,width:800,d3:{alphaTarget:.05,gravity:-100,linkLength:100,linkStrength:1,disableLinkForce:!1},node:{color:"#d3d3d3",fontColor:"black",fontSize:8,fontWeight:"normal",highlightColor:"SAME",highlightFontSize:8,highlightFontWeight:"normal",highlightStrokeColor:"SAME",highlightStrokeWidth:"SAME",labelProperty:"id",labelPosition:null,mouseCursor:"pointer",opacity:1,renderLabel:!0,size:200,strokeColor:"none",strokeWidth:1.5,svg:"",symbolType:"circle",viewGenerator:null},link:{color:"#d3d3d3",fontColor:"black",fontSize:8,fontWeight:"normal",highlightColor:"SAME",highlightFontSize:8,highlightFontWeight:"normal",labelProperty:"label",mouseCursor:"pointer",opacity:1,renderLabel:!1,semanticStrokeWidth:!1,strokeWidth:1.5,markerHeight:6,markerWidth:6,type:"STRAIGHT",strokeDasharray:0,strokeDashoffset:0,strokeLinecap:"butt"}};t.default=n},53880(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=i(n(11041));function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function o(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:[],r=arguments.length>3?arguments[3]:void 0,i=arguments.length>4&&void 0!==arguments[4]?arguments[4]:{},a=n.find(function(t){return t.source.id===e.source&&t.target.id===e.target}),o=a&&(0,c.pick)(a,m),s=(0,c.antiPick)(e,["source","target"]);if(o){var u=i.config&&Object.prototype.hasOwnProperty.call(i.config,"directed")&&r.directed!==i.config.directed,l=h({index:t},o,{},s);return u?h({},l,{isHidden:!1}):r.collapsible?l:h({},l,{isHidden:!1})}var f=!1,d={id:e.source,highlighted:f},p={id:e.target,highlighted:f};return h({index:t,source:d,target:p},s)}function _(e,t){return Object.keys(e).reduce(function(n,r){var i=(0,l.computeNodeDegree)(r,t),a=i.inDegree,o=i.outDegree,s=e[r],u=0===a&&0===o?h({},s,{_orphan:!0}):s;return n[r]=u,n},{})}function E(e){e.nodes&&e.nodes.length||((0,c.logWarning)("Graph",u.default.INSUFFICIENT_DATA),e.nodes=[]),e.links||((0,c.logWarning)("Graph",u.default.INSUFFICIENT_LINKS),e.links=[]);for(var t=e.links.length,n=function(t){var n=e.links[t];e.nodes.find(function(e){return e.id===n.source})||(0,c.throwErr)("Graph","".concat(u.default.INVALID_LINKS,' - "').concat(n.source,'" is not a valid source node id')),e.nodes.find(function(e){return e.id===n.target})||(0,c.throwErr)("Graph","".concat(u.default.INVALID_LINKS,' - "').concat(n.target,'" is not a valid target node id')),n&&void 0!==n.value&&"number"!=typeof n.value&&(0,c.throwErr)("Graph","".concat(u.default.INVALID_LINK_VALUE,' - found in link with source "').concat(n.source,'" and target "').concat(n.target,'"'))},r=0;rx?o.focusZoom=x:T4&&void 0!==arguments[4]&&arguments[4],a=i?r:"",o=h({},e[r],{highlighted:i}),s=h({},e,p({},r,o));return t[r]&&0!==n.highlightDegree&&(s=Object.keys(t[r]).reduce(function(e,t){var n=h({},s[t],{highlighted:i});return e[t]=n,e},s)),{nodes:s,highlightedNode:a}}function I(e){var t=Math.sqrt(Math.pow(e.x,2)+Math.pow(e.y,2));return 0===t?e:{x:e.x/t,y:e.y/t}}var D=new Set([o.default.SYMBOLS.CIRCLE]);function N(e,t,n,r){var i=e.sourceId,a=e.targetId,s=e.sourceCoords,u=void 0===s?{}:s,c=e.targetCoords,l=void 0===c?{}:c,f=null==t?void 0:t[i],d=null==t?void 0:t[a];if(!f||!d||(null===(_=n.node)||void 0===_?void 0:_.viewGenerator)||(null==f?void 0:f.viewGenerator)||(null==d?void 0:d.viewGenerator))return{sourceCoords:u,targetCoords:l};var h=f.symbolType||(null===(E=n.node)||void 0===E?void 0:E.symbolType),p=d.symbolType||(null===(S=n.node)||void 0===S?void 0:S.symbolType);if(!D.has(h)&&!D.has(p))return{sourceCoords:u,targetCoords:l};var b=u.x,m=u.y,g=l.x,v=l.y,y=I({x:g-b,y:v-m});if(h===o.default.SYMBOLS.CIRCLE){var w=(null==f?void 0:f.size)||n.node.size;b+=(w=.95*Math.sqrt(w/Math.PI))*y.x,m+=w*y.y}if(p===o.default.SYMBOLS.CIRCLE){var _,E,S,k,x,T=r*Math.min((null===(k=n.link)||void 0===k?void 0:k.markerWidth)||0,(null===(x=n.link)||void 0===x?void 0:x.markerHeight)||0),M=(null==d?void 0:d.size)||n.node.size;g-=((M=.95*Math.sqrt(M/Math.PI))+(n.directed?T:0))*y.x,v-=(M+(n.directed?T:0))*y.y}return{sourceCoords:{x:b,y:m},targetCoords:{x:g,y:v}}}},75791(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.renderGraph=E;var r=h(n(67294)),i=h(n(53880)),a=n(7619),o=h(n(33938)),s=h(n(61740)),u=h(n(28017)),c=n(99182),l=n(52694),f=n(37973),d=n(80362);function h(e){return e&&e.__esModule?e:{default:e}}function p(){return(p=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:i.LINE_TYPES.STRAIGHT,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[],o=e.x,s=e.y,u=p(i.LINE_TYPES[n]||i.LINE_TYPES.STRAIGHT),c=[].concat(a(r),[t]),l=c.map(function(t,n){var r,i=t.x,a=t.y,o=n>0?c[n-1]:e,s=u(o.x,o.y,i,a);return" A".concat(s,",").concat(s," 0 0,1 ").concat(i,",").concat(a)}).join("");return"M".concat(o,",").concat(s).concat(l)}},28017(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=i(n(67294));function i(e){return e&&e.__esModule?e:{default:e}}function a(e){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function o(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function s(e,t){for(var n=0;n=t&&e0&&void 0!==arguments[0]?arguments[0]:i.default.DEFAULT_NODE_SIZE,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:i.default.SYMBOLS.CIRCLE;return(0,r.symbol)().size(function(){return e}).type(function(){return o(t)})()}function u(e,t){switch(t){case"right":return{dx:e?"".concat(e):i.default.NODE_LABEL_DX,dy:"0",dominantBaseline:"middle",textAnchor:"start"};case"left":return{dx:e?"".concat(-e):"-".concat(i.default.NODE_LABEL_DX),dy:"0",dominantBaseline:"middle",textAnchor:"end"};case"top":return{dx:"0",dy:e?"".concat(-e):"-".concat(i.default.NODE_LABEL_DX),dominantBaseline:"baseline",textAnchor:"middle"};case"bottom":return{dx:"0",dy:e?"".concat(e):i.default.NODE_LABEL_DX,dominantBaseline:"hanging",textAnchor:"middle"};case"center":return{dx:"0",dy:"0",dominantBaseline:"middle",textAnchor:"middle"};default:return{dx:e?"".concat(e):i.default.NODE_LABEL_DX,dy:i.default.NODE_LABEL_DY}}}var c={buildSvgSymbol:s,getLabelPlacementProps:u};t.default=c},11041(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={SYMBOLS:{CIRCLE:"circle",CROSS:"cross",DIAMOND:"diamond",SQUARE:"square",STAR:"star",TRIANGLE:"triangle",WYE:"wye"}};t.default=n},34214(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n={GRAPH_NO_ID_PROP:"id prop not defined! id property is mandatory and it should be unique.",INSUFFICIENT_LINKS:"you are passing invalid data to react-d3-graph. You must include a links array, even if empty, in the data object you're passing down to the component.",INVALID_LINKS:"you provided a invalid links data structure. Links source and target attributes must point to an existent node",INSUFFICIENT_DATA:"you have not provided enough data for react-d3-graph to render something. You need to provide at least one node",INVALID_LINK_VALUE:"links 'value' attribute must be of type number"};t.default=n},94164(e,t,n){"use strict";r={value:!0},Object.defineProperty(t,"kJ",{enumerable:!0,get:function(){return i.default}}),r={enumerable:!0,get:function(){return a.default}},r={enumerable:!0,get:function(){return o.default}};var r,i=s(n(82623)),a=s(n(61740)),o=s(n(33938));function s(e){return e&&e.__esModule?e:{default:e}}},69901(e,t){"use strict";function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.isDeepEqual=a,t.isEmptyObject=o,t.deepClone=s,t.merge=u,t.pick=c,t.antiPick=l,t.debounce=f,t.throwErr=h,t.logError=p,t.logWarning=b;var r=20;function i(e,t){return!!e&&Object.prototype.hasOwnProperty.call(e,t)&&"object"===n(e[t])&&null!==e[t]&&!o(e[t])}function a(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,s=[];if(0===n&&e===t)return!0;if(o(e)&&!o(t)||!o(e)&&o(t))return!1;var u=Object.keys(e),c=Object.keys(t);if(u.length!==c.length)return!1;for(var l=0,f=u;l1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,a=Object.keys(e),o=0,u=a;o0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,a={};if(0===Object.keys(e||{}).length)return t&&!o(t)?t:{};for(var s=0,c=Object.keys(e);s1&&void 0!==arguments[1]?arguments[1]:[];return t.reduce(function(t,n){return Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]),t},{})}function l(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=Object.keys(e).filter(function(e){return!t.includes(e)});return c(e,n)}function f(e,t){var n;return function(){for(var r=arguments.length,i=Array(r),a=0;a0&&void 0!==arguments[0]?arguments[0]:"N/A",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"N/A";return"react-d3-graph :: ".concat(e," :: ").concat(t)}function h(e,t){throw Error(d(e,t))}function p(e,t){console.error(d(e,t))}function b(e,t){var n="react-d3-graph :: ".concat(e," :: ").concat(t);console.warn(n)}},64448(e,t,n){"use strict";/** @license React v16.12.0 + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ var r,i,a,o,s,u=n(67294),c=n(27418),l=n(63840);function f(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;nt}return!1}function eM(e,t,n,r,i,a){this.acceptsBooleans=2===t||3===t||4===t,this.attributeName=r,this.attributeNamespace=i,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=a}var eO={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){eO[e]=new eM(e,0,!1,e,null,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];eO[t]=new eM(t,1,!1,e[1],null,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){eO[e]=new eM(e,2,!1,e.toLowerCase(),null,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){eO[e]=new eM(e,2,!1,e,null,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){eO[e]=new eM(e,3,!1,e.toLowerCase(),null,!1)}),["checked","multiple","muted","selected"].forEach(function(e){eO[e]=new eM(e,3,!0,e,null,!1)}),["capture","download"].forEach(function(e){eO[e]=new eM(e,4,!1,e,null,!1)}),["cols","rows","size","span"].forEach(function(e){eO[e]=new eM(e,6,!1,e,null,!1)}),["rowSpan","start"].forEach(function(e){eO[e]=new eM(e,5,!1,e.toLowerCase(),null,!1)});var eA=/[\-:]([a-z])/g;function eL(e){return e[1].toUpperCase()}function eC(e){switch(typeof e){case"boolean":case"number":case"object":case"string":case"undefined":return e;default:return""}}function eI(e,t,n,r){var i=eO.hasOwnProperty(t)?eO[t]:null;(null!==i?0===i.type:!r&&2=t.length))throw Error(f(93));t=t[0]}n=t}null==n&&(n="")}e._wrapperState={initialValue:eC(n)}}function eV(e,t){var n=eC(t.value),r=eC(t.defaultValue);null!=n&&((n=""+n)!==e.value&&(e.value=n),null==t.defaultValue&&e.defaultValue!==n&&(e.defaultValue=n)),null!=r&&(e.defaultValue=""+r)}function eq(e){var t=e.textContent;t===e._wrapperState.initialValue&&""!==t&&null!==t&&(e.value=t)}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(eA,eL);eO[t]=new eM(t,1,!1,e,null,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(eA,eL);eO[t]=new eM(t,1,!1,e,"http://www.w3.org/1999/xlink",!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(eA,eL);eO[t]=new eM(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1)}),["tabIndex","crossOrigin"].forEach(function(e){eO[e]=new eM(e,1,!1,e.toLowerCase(),null,!1)}),eO.xlinkHref=new eM("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0),["src","href","action","formAction"].forEach(function(e){eO[e]=new eM(e,1,!1,e.toLowerCase(),null,!0)});var eZ={html:"http://www.w3.org/1999/xhtml",mathml:"http://www.w3.org/1998/Math/MathML",svg:"http://www.w3.org/2000/svg"};function eX(e){switch(e){case"svg":return"http://www.w3.org/2000/svg";case"math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function eJ(e,t){return null==e||"http://www.w3.org/1999/xhtml"===e?eX(t):"http://www.w3.org/2000/svg"===e&&"foreignObject"===t?"http://www.w3.org/1999/xhtml":e}var eQ,e1,e0=(eQ=function(e,t){if(e.namespaceURI!==eZ.svg||"innerHTML"in e)e.innerHTML=t;else{for((e1=e1||document.createElement("div")).innerHTML=""+t.valueOf().toString()+"",t=e1.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}},"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(e,t,n,r){MSApp.execUnsafeLocalFunction(function(){return eQ(e,t,n,r)})}:eQ);function e2(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType){n.nodeValue=t;return}}e.textContent=t}function e3(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var e4={animationend:e3("Animation","AnimationEnd"),animationiteration:e3("Animation","AnimationIteration"),animationstart:e3("Animation","AnimationStart"),transitionend:e3("Transition","TransitionEnd")},e5={},e6={};function e9(e){if(e5[e])return e5[e];if(!e4[e])return e;var t,n=e4[e];for(t in n)if(n.hasOwnProperty(t)&&t in e6)return e5[e]=n[t];return e}eo&&(e6=document.createElement("div").style,"AnimationEvent"in window||(delete e4.animationend.animation,delete e4.animationiteration.animation,delete e4.animationstart.animation),"TransitionEvent"in window||delete e4.transitionend.transition);var e8=e9("animationend"),e7=e9("animationiteration"),te=e9("animationstart"),tt=e9("transitionend"),tn="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting".split(" ");function tr(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do 0!=(1026&(t=e).effectTag)&&(n=t.return),e=t.return;while(e)}return 3===t.tag?n:null}function ti(e){if(13===e.tag){var t=e.memoizedState;if(null===t&&null!==(e=e.alternate)&&(t=e.memoizedState),null!==t)return t.dehydrated}return null}function ta(e){if(tr(e)!==e)throw Error(f(188))}function to(e){var t=e.alternate;if(!t){if(null===(t=tr(e)))throw Error(f(188));return t!==e?null:e}for(var n=e,r=t;;){var i=n.return;if(null===i)break;var a=i.alternate;if(null===a){if(null!==(r=i.return)){n=r;continue}break}if(i.child===a.child){for(a=i.child;a;){if(a===n)return ta(i),e;if(a===r)return ta(i),t;a=a.sibling}throw Error(f(188))}if(n.return!==r.return)n=i,r=a;else{for(var o=!1,s=i.child;s;){if(s===n){o=!0,n=i,r=a;break}if(s===r){o=!0,r=i,n=a;break}s=s.sibling}if(!o){for(s=a.child;s;){if(s===n){o=!0,n=a,r=i;break}if(s===r){o=!0,r=a,n=i;break}s=s.sibling}if(!o)throw Error(f(189))}}if(n.alternate!==r)throw Error(f(190))}if(3!==n.tag)throw Error(f(188));return n.stateNode.current===n?e:t}function ts(e){if(!(e=to(e)))return null;for(var t=e;;){if(5===t.tag||6===t.tag)return t;if(t.child)t.child.return=t,t=t.child;else{if(t===e)break;for(;!t.sibling;){if(!t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}}return null}var tu,tc,tl,tf=!1,td=[],th=null,tp=null,tb=null,tm=new Map,tg=new Map,tv=[],ty="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput close cancel copy cut paste click change contextmenu reset submit".split(" "),tw="focus blur dragenter dragleave mouseover mouseout pointerover pointerout gotpointercapture lostpointercapture".split(" ");function t_(e){var t=nA(e);ty.forEach(function(n){nL(n,e,t)}),tw.forEach(function(n){nL(n,e,t)})}function tE(e,t,n,r){return{blockedOn:e,topLevelType:t,eventSystemFlags:32|n,nativeEvent:r}}function tS(e,t){switch(e){case"focus":case"blur":th=null;break;case"dragenter":case"dragleave":tp=null;break;case"mouseover":case"mouseout":tb=null;break;case"pointerover":case"pointerout":tm.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":tg.delete(t.pointerId)}}function tk(e,t,n,r,i){return null===e||e.nativeEvent!==i?(e=tE(t,n,r,i),null!==t&&null!==(t=n7(t))&&tc(t),e):(e.eventSystemFlags|=r,e)}function tx(e,t,n,r){switch(t){case"focus":return th=tk(th,e,t,n,r),!0;case"dragenter":return tp=tk(tp,e,t,n,r),!0;case"mouseover":return tb=tk(tb,e,t,n,r),!0;case"pointerover":var i=r.pointerId;return tm.set(i,tk(tm.get(i)||null,e,t,n,r)),!0;case"gotpointercapture":return i=r.pointerId,tg.set(i,tk(tg.get(i)||null,e,t,n,r)),!0}return!1}function tT(e){var t=n8(e.target);if(null!==t){var n=tr(t);if(null!==n){if(13===(t=n.tag)){if(null!==(t=ti(n))){e.blockedOn=t,l.unstable_runWithPriority(e.priority,function(){tl(n)});return}}else if(3===t&&n.stateNode.hydrate){e.blockedOn=3===n.tag?n.stateNode.containerInfo:null;return}}}e.blockedOn=null}function tM(e){if(null!==e.blockedOn)return!1;var t=nT(e.topLevelType,e.eventSystemFlags,e.nativeEvent);if(null!==t){var n=n7(t);return null!==n&&tc(n),e.blockedOn=t,!1}return!0}function tO(e,t,n){tM(e)&&n.delete(t)}function tA(){for(tf=!1;0this.eventPool.length&&this.eventPool.push(e)}function tz(e){e.eventPool=[],e.getPooled=tH,e.release=t$}c(tU.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=tY)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=tY)},persist:function(){this.isPersistent=tY},isPersistent:tB,destructor:function(){var e,t=this.constructor.Interface;for(e in t)this[e]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null,this.isPropagationStopped=this.isDefaultPrevented=tB,this._dispatchInstances=this._dispatchListeners=null}}),tU.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null},tU.extend=function(e){function t(){}function n(){return r.apply(this,arguments)}var r=this;t.prototype=r.prototype;var i=new t;return c(i,n.prototype),n.prototype=i,n.prototype.constructor=n,n.Interface=c({},r.Interface,e),n.extend=r.extend,tz(n),n},tz(tU);var tG=tU.extend({animationName:null,elapsedTime:null,pseudoElement:null}),tW=tU.extend({clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}}),tK=tU.extend({view:null,detail:null}),tV=tK.extend({relatedTarget:null});function tq(e){var t=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&13===t&&(e=13):e=t,10===e&&(e=13),32<=e||13===e?e:0}var tZ={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},tX={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},tJ={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function tQ(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):!!(e=tJ[e])&&!!t[e]}function t1(){return tQ}for(var t0=tK.extend({key:function(e){if(e.key){var t=tZ[e.key]||e.key;if("Unidentified"!==t)return t}return"keypress"===e.type?13===(e=tq(e))?"Enter":String.fromCharCode(e):"keydown"===e.type||"keyup"===e.type?tX[e.keyCode]||"Unidentified":""},location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:t1,charCode:function(e){return"keypress"===e.type?tq(e):0},keyCode:function(e){return"keydown"===e.type||"keyup"===e.type?e.keyCode:0},which:function(e){return"keypress"===e.type?tq(e):"keydown"===e.type||"keyup"===e.type?e.keyCode:0}}),t2=0,t3=0,t4=!1,t5=!1,t6=tK.extend({screenX:null,screenY:null,clientX:null,clientY:null,pageX:null,pageY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:t1,button:null,buttons:null,relatedTarget:function(e){return e.relatedTarget||(e.fromElement===e.srcElement?e.toElement:e.fromElement)},movementX:function(e){if(("movementX"in e))return e.movementX;var t=t2;return t2=e.screenX,t4?"mousemove"===e.type?e.screenX-t:0:(t4=!0,0)},movementY:function(e){if(("movementY"in e))return e.movementY;var t=t3;return t3=e.screenY,t5?"mousemove"===e.type?e.screenY-t:0:(t5=!0,0)}}),t9=t6.extend({pointerId:null,width:null,height:null,pressure:null,tangentialPressure:null,tiltX:null,tiltY:null,twist:null,pointerType:null,isPrimary:null}),t8=t6.extend({dataTransfer:null}),t7=tK.extend({touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:t1}),ne=tU.extend({propertyName:null,elapsedTime:null,pseudoElement:null}),nt=t6.extend({deltaX:function(e){return("deltaX"in e)?e.deltaX:("wheelDeltaX"in e)?-e.wheelDeltaX:0},deltaY:function(e){return("deltaY"in e)?e.deltaY:("wheelDeltaY"in e)?-e.wheelDeltaY:("wheelDelta"in e)?-e.wheelDelta:0},deltaZ:null,deltaMode:null}),nn=[["blur","blur",0],["cancel","cancel",0],["click","click",0],["close","close",0],["contextmenu","contextMenu",0],["copy","copy",0],["cut","cut",0],["auxclick","auxClick",0],["dblclick","doubleClick",0],["dragend","dragEnd",0],["dragstart","dragStart",0],["drop","drop",0],["focus","focus",0],["input","input",0],["invalid","invalid",0],["keydown","keyDown",0],["keypress","keyPress",0],["keyup","keyUp",0],["mousedown","mouseDown",0],["mouseup","mouseUp",0],["paste","paste",0],["pause","pause",0],["play","play",0],["pointercancel","pointerCancel",0],["pointerdown","pointerDown",0],["pointerup","pointerUp",0],["ratechange","rateChange",0],["reset","reset",0],["seeked","seeked",0],["submit","submit",0],["touchcancel","touchCancel",0],["touchend","touchEnd",0],["touchstart","touchStart",0],["volumechange","volumeChange",0],["drag","drag",1],["dragenter","dragEnter",1],["dragexit","dragExit",1],["dragleave","dragLeave",1],["dragover","dragOver",1],["mousemove","mouseMove",1],["mouseout","mouseOut",1],["mouseover","mouseOver",1],["pointermove","pointerMove",1],["pointerout","pointerOut",1],["pointerover","pointerOver",1],["scroll","scroll",1],["toggle","toggle",1],["touchmove","touchMove",1],["wheel","wheel",1],["abort","abort",2],[e8,"animationEnd",2],[e7,"animationIteration",2],[te,"animationStart",2],["canplay","canPlay",2],["canplaythrough","canPlayThrough",2],["durationchange","durationChange",2],["emptied","emptied",2],["encrypted","encrypted",2],["ended","ended",2],["error","error",2],["gotpointercapture","gotPointerCapture",2],["load","load",2],["loadeddata","loadedData",2],["loadedmetadata","loadedMetadata",2],["loadstart","loadStart",2],["lostpointercapture","lostPointerCapture",2],["playing","playing",2],["progress","progress",2],["seeking","seeking",2],["stalled","stalled",2],["suspend","suspend",2],["timeupdate","timeUpdate",2],[tt,"transitionEnd",2],["waiting","waiting",2]],nr={},ni={},na=0;na=t)return{node:r,offset:t-e};e=n}a:{for(;r;){if(r.nextSibling){r=r.nextSibling;break a}r=r.parentNode}r=void 0}r=nU(r)}}function n$(e,t){return!!e&&!!t&&(e===t||(!e||3!==e.nodeType)&&(t&&3===t.nodeType?n$(e,t.parentNode):"contains"in e?e.contains(t):!!e.compareDocumentPosition&&!!(16&e.compareDocumentPosition(t))))}function nz(){for(var e=window,t=nB();t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(r){n=!1}if(n)e=t.contentWindow;else break;t=nB(e.document)}return t}function nG(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var nW="$",nK="/$",nV="$?",nq="$!",nZ=null,nX=null;function nJ(e,t){switch(e){case"button":case"input":case"select":case"textarea":return!!t.autoFocus}return!1}function nQ(e,t){return"textarea"===e||"option"===e||"noscript"===e||"string"==typeof t.children||"number"==typeof t.children||"object"==typeof t.dangerouslySetInnerHTML&&null!==t.dangerouslySetInnerHTML&&null!=t.dangerouslySetInnerHTML.__html}var n1="function"==typeof setTimeout?setTimeout:void 0,n0="function"==typeof clearTimeout?clearTimeout:void 0;function n2(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break}return e}function n3(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if(n===nW||n===nq||n===nV){if(0===t)return e;t--}else n===nK&&t++}e=e.previousSibling}return null}var n4=Math.random().toString(36).slice(2),n5="__reactInternalInstance$"+n4,n6="__reactEventHandlers$"+n4,n9="__reactContainere$"+n4;function n8(e){var t=e[n5];if(t)return t;for(var n=e.parentNode;n;){if(t=n[n9]||n[n5]){if(n=t.alternate,null!==t.child||null!==n&&null!==n.child)for(e=n3(e);null!==e;){if(n=e[n5])return n;e=n3(e)}return t}n=(e=n).parentNode}return null}function n7(e){return(e=e[n5]||e[n9])&&(5===e.tag||6===e.tag||13===e.tag||3===e.tag)?e:null}function re(e){if(5===e.tag||6===e.tag)return e.stateNode;throw Error(f(33))}function rt(e){return e[n6]||null}var rn=null,rr=null,ri=null;function ra(){if(ri)return ri;var e,t,n=rr,r=n.length,i="value"in rn?rn.value:rn.textContent,a=i.length;for(e=0;e=rl),rh=" ",rp={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},rb=!1;function rm(e,t){switch(e){case"keyup":return -1!==ru.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"blur":return!0;default:return!1}}function rg(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var rv=!1;function ry(e,t){switch(e){case"compositionend":return rg(t);case"keypress":if(32!==t.which)return null;return rb=!0,rh;case"textInput":return(e=t.data)===rh&&rb?null:e;default:return null}}function rw(e,t){if(rv)return"compositionend"===e||!rc&&rm(e,t)?(e=ra(),ri=rr=rn=null,rv=!1,e):null;switch(e){case"paste":default:return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=document.documentMode,rK={select:{phasedRegistrationNames:{bubbled:"onSelect",captured:"onSelectCapture"},dependencies:"blur contextmenu dragend focus keydown keyup mousedown mouseup selectionchange".split(" ")}},rV=null,rq=null,rZ=null,rX=!1;function rJ(e,t){var n=t.window===t?t.document:9===t.nodeType?t:t.ownerDocument;return rX||null==rV||rV!==nB(n)?null:(n="selectionStart"in(n=rV)&&nG(n)?{start:n.selectionStart,end:n.selectionEnd}:{anchorNode:(n=(n.ownerDocument&&n.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:n.anchorOffset,focusNode:n.focusNode,focusOffset:n.focusOffset},rZ&&rG(rZ,n)?null:(rZ=n,(e=tU.getPooled(rK.select,rq,e,t)).type="select",e.target=rV,tF(e),e))}var rQ={eventTypes:rK,extractEvents:function(e,t,n,r){var i,a=r.window===r?r.document:9===r.nodeType?r:r.ownerDocument;if(!(i=!a)){a:{a=nA(a),i=y.onSelect;for(var o=0;or2||(e.current=r0[r2],r0[r2]=null,r2--)}function r4(e,t){r0[++r2]=e.current,e.current=t}var r5={},r6={current:r5},r9={current:!1},r8=r5;function r7(e,t){var n=e.type.contextTypes;if(!n)return r5;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var i,a={};for(i in n)a[i]=t[i];return r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=a),a}function ie(e){return null!=(e=e.childContextTypes)}function it(e){r3(r9,e),r3(r6,e)}function ir(e){r3(r9,e),r3(r6,e)}function ii(e,t,n){if(r6.current!==r5)throw Error(f(168));r4(r6,t,e),r4(r9,n,e)}function ia(e,t,n){var r=e.stateNode;if(e=t.childContextTypes,"function"!=typeof r.getChildContext)return n;for(var i in r=r.getChildContext())if(!(i in e))throw Error(f(108,ei(t)||"Unknown",i));return c({},n,{},r)}function io(e){var t=e.stateNode;return t=t&&t.__reactInternalMemoizedMergedChildContext||r5,r8=r6.current,r4(r6,t,e),r4(r9,r9.current,e),!0}function is(e,t,n){var r=e.stateNode;if(!r)throw Error(f(169));n?(t=ia(e,t,r8),r.__reactInternalMemoizedMergedChildContext=t,r3(r9,e),r3(r6,e),r4(r6,t,e)):r3(r9,e),r4(r9,n,e)}var iu=l.unstable_runWithPriority,ic=l.unstable_scheduleCallback,il=l.unstable_cancelCallback,id=l.unstable_shouldYield,ih=l.unstable_requestPaint,ip=l.unstable_now,ib=l.unstable_getCurrentPriorityLevel,im=l.unstable_ImmediatePriority,ig=l.unstable_UserBlockingPriority,iv=l.unstable_NormalPriority,iy=l.unstable_LowPriority,iw=l.unstable_IdlePriority,i_={},iE=void 0!==ih?ih:function(){},iS=null,ik=null,ix=!1,iT=ip(),iM=1e4>iT?ip:function(){return ip()-iT};function iO(){switch(ib()){case im:return 99;case ig:return 98;case iv:return 97;case iy:return 96;case iw:return 95;default:throw Error(f(332))}}function iA(e){switch(e){case 99:return im;case 98:return ig;case 97:return iv;case 96:return iy;case 95:return iw;default:throw Error(f(332))}}function iL(e,t){return e=iA(e),iu(e,t)}function iC(e,t,n){return e=iA(e),ic(e,t,n)}function iI(e){return null===iS?(iS=[e],ik=ic(im,iN)):iS.push(e),i_}function iD(){if(null!==ik){var e=ik;ik=null,il(e)}iN()}function iN(){if(!ix&&null!==iS){ix=!0;var e=0;try{var t=iS;iL(99,function(){for(;e=t&&(oo=!0),e.firstContext=null)}function iK(e,t){if(iU!==e&&!1!==t&&0!==t){if(("number"!=typeof t||1073741823===t)&&(iU=e,t=1073741823),t={context:e,observedBits:t,next:null},null===iB){if(null===iY)throw Error(f(308));iB=t,iY.dependencies={expirationTime:0,firstContext:t,responders:null}}else iB=iB.next=t}return e._currentValue}var iV=!1;function iq(e){return{baseState:e,firstUpdate:null,lastUpdate:null,firstCapturedUpdate:null,lastCapturedUpdate:null,firstEffect:null,lastEffect:null,firstCapturedEffect:null,lastCapturedEffect:null}}function iZ(e){return{baseState:e.baseState,firstUpdate:e.firstUpdate,lastUpdate:e.lastUpdate,firstCapturedUpdate:null,lastCapturedUpdate:null,firstEffect:null,lastEffect:null,firstCapturedEffect:null,lastCapturedEffect:null}}function iX(e,t){return{expirationTime:e,suspenseConfig:t,tag:0,payload:null,callback:null,next:null,nextEffect:null}}function iJ(e,t){null===e.lastUpdate?e.firstUpdate=e.lastUpdate=t:(e.lastUpdate.next=t,e.lastUpdate=t)}function iQ(e,t){var n=e.alternate;if(null===n){var r=e.updateQueue,i=null;null===r&&(r=e.updateQueue=iq(e.memoizedState))}else r=e.updateQueue,i=n.updateQueue,null===r?null===i?(r=e.updateQueue=iq(e.memoizedState),i=n.updateQueue=iq(n.memoizedState)):r=e.updateQueue=iZ(i):null===i&&(i=n.updateQueue=iZ(r));null===i||r===i?iJ(r,t):null===r.lastUpdate||null===i.lastUpdate?(iJ(r,t),iJ(i,t)):(iJ(r,t),i.lastUpdate=t)}function i1(e,t){var n=e.updateQueue;null===(n=null===n?e.updateQueue=iq(e.memoizedState):i0(e,n)).lastCapturedUpdate?n.firstCapturedUpdate=n.lastCapturedUpdate=t:(n.lastCapturedUpdate.next=t,n.lastCapturedUpdate=t)}function i0(e,t){var n=e.alternate;return null!==n&&t===n.updateQueue&&(t=e.updateQueue=iZ(t)),t}function i2(e,t,n,r,i,a){switch(n.tag){case 1:return"function"==typeof(e=n.payload)?e.call(a,r,i):e;case 3:e.effectTag=-4097&e.effectTag|64;case 0:if(null==(i="function"==typeof(e=n.payload)?e.call(a,r,i):e))break;return c({},r,i);case 2:iV=!0}return r}function i3(e,t,n,r,i){iV=!1,t=i0(e,t);for(var a=t.baseState,o=null,s=0,u=t.firstUpdate,c=a;null!==u;){var l=u.expirationTime;lb?(m=f,f=null):m=f.sibling;var g=h(i,f,s[b],u);if(null===g){null===f&&(f=m);break}e&&f&&null===g.alternate&&t(i,f),o=a(g,o,b),null===l?c=g:l.sibling=g,l=g,f=m}if(b===s.length)return n(i,f),c;if(null===f){for(;bm?(g=b,b=null):g=b.sibling;var y=h(i,b,v.value,u);if(null===y){null===b&&(b=g);break}e&&b&&null===y.alternate&&t(i,b),o=a(y,o,m),null===l?c=y:l.sibling=y,l=y,b=g}if(v.done)return n(i,b),c;if(null===b){for(;!v.done;m++,v=s.next())null!==(v=d(i,v.value,u))&&(o=a(v,o,m),null===l?c=v:l.sibling=v,l=v);return c}for(b=r(i,b);!v.done;m++,v=s.next())null!==(v=p(b,i,m,v.value,u))&&(e&&null!==v.alternate&&b.delete(null===v.key?m:v.key),o=a(v,o,m),null===l?c=v:l.sibling=v,l=v);return e&&b.forEach(function(e){return t(i,e)}),c}return function(e,r,a,s){var u="object"==typeof a&&null!==a&&a.type===z&&null===a.key;u&&(a=a.props.children);var c="object"==typeof a&&null!==a;if(c)switch(a.$$typeof){case H:a:{for(c=a.key,u=r;null!==u;){if(u.key===c){if(7===u.tag?a.type===z:u.elementType===a.type){n(e,u.sibling),(r=i(u,a.type===z?a.props.children:a.props,s)).ref=aa(e,u,a),r.return=e,e=r;break a}n(e,u);break}t(e,u),u=u.sibling}a.type===z?((r=s1(a.props.children,e.mode,s,a.key)).return=e,e=r):((s=sQ(a.type,a.key,a.props,null,e.mode,s)).ref=aa(e,r,a),s.return=e,e=s)}return o(e);case $:a:{for(u=a.key;null!==r;){if(r.key===u){if(4===r.tag&&r.stateNode.containerInfo===a.containerInfo&&r.stateNode.implementation===a.implementation){n(e,r.sibling),(r=i(r,a.children||[],s)).return=e,e=r;break a}n(e,r);break}t(e,r),r=r.sibling}(r=s2(a,e.mode,s)).return=e,e=r}return o(e)}if("string"==typeof a||"number"==typeof a)return a=""+a,null!==r&&6===r.tag?(n(e,r.sibling),(r=i(r,a,s)).return=e,e=r):(n(e,r),(r=s0(a,e.mode,s)).return=e,e=r),o(e);if(ai(a))return b(e,r,a,s);if(en(a))return m(e,r,a,s);if(c&&ao(e,a),void 0===a&&!u)switch(e.tag){case 1:case 0:throw Error(f(152,(e=e.type).displayName||e.name||"Component"))}return n(e,r)}}var au=as(!0),ac=as(!1),al={},af={current:al},ad={current:al},ah={current:al};function ap(e){if(e===al)throw Error(f(174));return e}function ab(e,t){r4(ah,t,e),r4(ad,e,e),r4(af,al,e);var n=t.nodeType;switch(n){case 9:case 11:t=(t=t.documentElement)?t.namespaceURI:eJ(null,"");break;default:t=eJ(t=(n=8===n?t.parentNode:t).namespaceURI||null,n=n.tagName)}r3(af,e),r4(af,t,e)}function am(e){r3(af,e),r3(ad,e),r3(ah,e)}function ag(e){ap(ah.current);var t=ap(af.current),n=eJ(t,e.type);t!==n&&(r4(ad,e,e),r4(af,n,e))}function av(e){ad.current===e&&(r3(af,e),r3(ad,e))}var ay={current:0};function aw(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||n.data===nV||n.data===nq))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(0!=(64&t.effectTag))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function a_(e,t){return{responder:e,props:t}}var aE=Y.ReactCurrentDispatcher,aS=Y.ReactCurrentBatchConfig,ak=0,ax=null,aT=null,aM=null,aO=null,aA=null,aL=null,aC=0,aI=null,aD=0,aN=!1,aP=null,aR=0;function aj(){throw Error(f(321))}function aF(e,t){if(null===t)return!1;for(var n=0;naC&&sL(aC=l)):(sA(l,u.suspenseConfig),a=u.eagerReducer===e?u.eagerState:e(a,u.action)),o=u,u=u.next}while(null!==u&&u!==r)c||(s=o,i=a),r$(a,t.memoizedState)||(oo=!0),t.memoizedState=a,t.baseUpdate=s,t.baseState=i,n.lastRenderedState=a}return[t.memoizedState,n.dispatch]}function aG(e){var t=aU();return"function"==typeof e&&(e=e()),t.memoizedState=t.baseState=e,e=(e=t.queue={last:null,dispatch:null,lastRenderedReducer:a$,lastRenderedState:e}).dispatch=a2.bind(null,ax,e),[t.memoizedState,e]}function aW(e){return az(a$,e)}function aK(e,t,n,r){return e={tag:e,create:t,destroy:n,deps:r,next:null},null===aI?(aI={lastEffect:null}).lastEffect=e.next=e:null===(t=aI.lastEffect)?aI.lastEffect=e.next=e:(n=t.next,t.next=e,e.next=n,aI.lastEffect=e),e}function aV(e,t,n,r){var i=aU();aD|=e,i.memoizedState=aK(t,n,void 0,void 0===r?null:r)}function aq(e,t,n,r){var i=aH();r=void 0===r?null:r;var a=void 0;if(null!==aT){var o=aT.memoizedState;if(a=o.destroy,null!==r&&aF(r,o.deps)){aK(0,n,a,r);return}}aD|=e,i.memoizedState=aK(t,n,a,r)}function aZ(e,t){return aV(516,192,e,t)}function aX(e,t){return aq(516,192,e,t)}function aJ(e,t){return"function"==typeof t?(t(e=e()),function(){t(null)}):null!=t?(e=e(),t.current=e,function(){t.current=null}):void 0}function aQ(){}function a1(e,t){return aU().memoizedState=[e,void 0===t?null:t],e}function a0(e,t){var n=aH();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&aF(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function a2(e,t,n){if(!(25>aR))throw Error(f(301));var r=e.alternate;if(e===ax||null!==r&&r===ax){if(aN=!0,e={expirationTime:ak,suspenseConfig:null,action:n,eagerReducer:null,eagerState:null,next:null},null===aP&&(aP=new Map),void 0===(n=aP.get(t)))aP.set(t,e);else{for(t=n;null!==t.next;)t=t.next;t.next=e}}else{var i=sb(),a=i6.suspense;a={expirationTime:i=sm(i,e,a),suspenseConfig:a,action:n,eagerReducer:null,eagerState:null,next:null};var o=t.last;if(null===o)a.next=a;else{var s=o.next;null!==s&&(a.next=s),o.next=a}if(t.last=a,0===e.expirationTime&&(null===r||0===r.expirationTime)&&null!==(r=t.lastRenderedReducer))try{var u=t.lastRenderedState,c=r(u,n);if(a.eagerReducer=r,a.eagerState=c,r$(c,u))return}catch(l){}finally{}sg(e,i)}}var a3={readContext:iK,useCallback:aj,useContext:aj,useEffect:aj,useImperativeHandle:aj,useLayoutEffect:aj,useMemo:aj,useReducer:aj,useRef:aj,useState:aj,useDebugValue:aj,useResponder:aj,useDeferredValue:aj,useTransition:aj},a4={readContext:iK,useCallback:a1,useContext:iK,useEffect:aZ,useImperativeHandle:function(e,t,n){return n=null!=n?n.concat([e]):null,aV(4,36,aJ.bind(null,t,e),n)},useLayoutEffect:function(e,t){return aV(4,36,e,t)},useMemo:function(e,t){var n=aU();return t=void 0===t?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=aU();return t=void 0!==n?n(t):t,r.memoizedState=r.baseState=t,e=(e=r.queue={last:null,dispatch:null,lastRenderedReducer:e,lastRenderedState:t}).dispatch=a2.bind(null,ax,e),[r.memoizedState,e]},useRef:function(e){var t=aU();return e={current:e},t.memoizedState=e},useState:aG,useDebugValue:aQ,useResponder:a_,useDeferredValue:function(e,t){var n=aG(e),r=n[0],i=n[1];return aZ(function(){l.unstable_next(function(){var n=aS.suspense;aS.suspense=void 0===t?null:t;try{i(e)}finally{aS.suspense=n}})},[e,t]),r},useTransition:function(e){var t=aG(!1),n=t[0],r=t[1];return[a1(function(t){r(!0),l.unstable_next(function(){var n=aS.suspense;aS.suspense=void 0===e?null:e;try{r(!1),t()}finally{aS.suspense=n}})},[e,n]),n]}},a5={readContext:iK,useCallback:a0,useContext:iK,useEffect:aX,useImperativeHandle:function(e,t,n){return n=null!=n?n.concat([e]):null,aq(4,36,aJ.bind(null,t,e),n)},useLayoutEffect:function(e,t){return aq(4,36,e,t)},useMemo:function(e,t){var n=aH();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&aF(t,r[1])?r[0]:(e=e(),n.memoizedState=[e,t],e)},useReducer:az,useRef:function(){return aH().memoizedState},useState:aW,useDebugValue:aQ,useResponder:a_,useDeferredValue:function(e,t){var n=aW(e),r=n[0],i=n[1];return aX(function(){l.unstable_next(function(){var n=aS.suspense;aS.suspense=void 0===t?null:t;try{i(e)}finally{aS.suspense=n}})},[e,t]),r},useTransition:function(e){var t=aW(!1),n=t[0],r=t[1];return[a0(function(t){r(!0),l.unstable_next(function(){var n=aS.suspense;aS.suspense=void 0===e?null:e;try{r(!1),t()}finally{aS.suspense=n}})},[e,n]),n]}},a6=null,a9=null,a8=!1;function a7(e,t){var n=sq(5,null,null,0);n.elementType="DELETED",n.type="DELETED",n.stateNode=t,n.return=e,n.effectTag=8,null!==e.lastEffect?(e.lastEffect.nextEffect=n,e.lastEffect=n):e.firstEffect=e.lastEffect=n}function oe(e,t){switch(e.tag){case 5:var n=e.type;return null!==(t=1!==t.nodeType||n.toLowerCase()!==t.nodeName.toLowerCase()?null:t)&&(e.stateNode=t,!0);case 6:return null!==(t=""===e.pendingProps||3!==t.nodeType?null:t)&&(e.stateNode=t,!0);default:return!1}}function ot(e){if(a8){var t=a9;if(t){var n=t;if(!oe(e,t)){if(!(t=n2(n.nextSibling))||!oe(e,t)){e.effectTag=-1025&e.effectTag|2,a8=!1,a6=e;return}a7(a6,n)}a6=e,a9=n2(t.firstChild)}else e.effectTag=-1025&e.effectTag|2,a8=!1,a6=e}}function on(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag&&13!==e.tag;)e=e.return;a6=e}function or(e){if(e!==a6)return!1;if(!a8)return on(e),a8=!0,!1;var t=e.type;if(5!==e.tag||"head"!==t&&"body"!==t&&!nQ(t,e.memoizedProps))for(t=a9;t;)a7(e,t),t=n2(t.nextSibling);if(on(e),13===e.tag){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(f(317));a:{for(t=0,e=e.nextSibling;e;){if(8===e.nodeType){var n=e.data;if(n===nK){if(0===t){a9=n2(e.nextSibling);break a}t--}else n!==nW&&n!==nq&&n!==nV||t++}e=e.nextSibling}a9=null}}else a9=a6?n2(e.stateNode.nextSibling):null;return!0}function oi(){a9=a6=null,a8=!1}var oa=Y.ReactCurrentOwner,oo=!1;function os(e,t,n,r){t.child=null===e?ac(t,null,n,r):au(t,e.child,n,r)}function ou(e,t,n,r,i){n=n.render;var a=t.ref;return(iW(t,i),r=aY(e,t,n,r,a,i),null===e||oo)?(t.effectTag|=1,os(e,t,r,i),t.child):(t.updateQueue=e.updateQueue,t.effectTag&=-517,e.expirationTime<=i&&(e.expirationTime=0),o_(e,t,i))}function oc(e,t,n,r,i,a){if(null===e){var o=n.type;return"function"!=typeof o||sZ(o)||void 0!==o.defaultProps||null!==n.compare||void 0!==n.defaultProps?((e=sQ(n.type,null,r,null,t.mode,a)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=o,ol(e,t,o,r,i,a))}return(o=e.child,it)&&sf.set(e,t))}}function sv(e,t){e.expirationTime(e=e.nextKnownPendingLevel)?t:e:t}function sw(e){if(0!==e.lastExpiredTime)e.callbackExpirationTime=1073741823,e.callbackPriority=99,e.callbackNode=iI(sE.bind(null,e));else{var t=sy(e),n=e.callbackNode;if(0===t)null!==n&&(e.callbackNode=null,e.callbackExpirationTime=0,e.callbackPriority=90);else{var r=sb();if(r=1073741823===t?99:1===t||2===t?95:0>=(r=10*(1073741821-t)-10*(1073741821-r))?99:250>=r?98:5250>=r?97:95,null!==n){var i=e.callbackPriority;if(e.callbackExpirationTime===t&&i>=r)return;n!==i_&&il(n)}e.callbackExpirationTime=t,e.callbackPriority=r,t=1073741823===t?iI(sE.bind(null,e)):iC(r,s_.bind(null,e),{timeout:10*(1073741821-t)-iM()}),e.callbackNode=t}}}function s_(e,t){if(sp=0,t)return t=sb(),s9(e,t),sw(e),null;var n=sy(e);if(0!==n){if(t=e.callbackNode,(o0&(oK|oV))!==oG)throw Error(f(327));if(sY(),e===o2&&n===o4||sT(e,n),null!==o3){var r=o0;o0|=oK;for(var i=sO(e);;)try{sI();break}catch(a){sM(e,a)}if(iH(),o0=r,o$.current=i,o5===oZ)throw t=o6,sT(e,n),s5(e,n),sw(e),t;if(null===o3)switch(i=e.finishedWork=e.current.alternate,e.finishedExpirationTime=n,o2=null,r=o5){case oq:case oZ:throw Error(f(345));case oX:s9(e,2=n){e.lastPingedTime=n,sT(e,n);break}}if(0!==(o=sy(e))&&o!==n)break;if(0!==r&&r!==n){e.lastPingedTime=r;break}e.timeoutHandle=n1(sR.bind(null,e),i);break}sR(e);break;case oQ:if(s5(e,n),n===(r=e.lastSuspendedTime)&&(e.nextKnownPendingLevel=sP(i)),st&&(0===(i=e.lastPingedTime)||i>=n)){e.lastPingedTime=n,sT(e,n);break}if(0!==(i=sy(e))&&i!==n)break;if(0!==r&&r!==n){e.lastPingedTime=r;break}if(1073741823!==o8?r=10*(1073741821-o8)-iM():1073741823===o9?r=0:(r=10*(1073741821-o9)-5e3,n=10*(1073741821-n)-(i=iM()),0>(r=i-r)&&(r=0),n<(r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*oH(r/1960))-r)&&(r=n)),10=(r=0|s.busyMinDurationMs)?r=0:(i=0|s.busyDelayMs,r=(o=iM()-(10*(1073741821-o)-(0|s.timeoutMs||5e3)))<=i?0:i+r-o),10 component higher in the tree to provide a loading indicator or placeholder to display."+ea(i))}o5!==o1&&(o5=oX),a=ox(a,i),c=r;do{switch(c.tag){case 3:s=a,c.effectTag|=4096,c.expirationTime=t;var g=oB(c,s,t);i1(c,g);break a;case 1:s=a;var v=c.type,y=c.stateNode;if(0==(64&c.effectTag)&&("function"==typeof v.getDerivedStateFromError||null!==y&&"function"==typeof y.componentDidCatch&&(null===ss||!ss.has(y)))){c.effectTag|=4096,c.expirationTime=t;var w=oU(c,s,t);i1(c,w);break a}}c=c.return}while(null!==c)}o3=sN(o3)}catch(_){t=_;continue}break}}function sO(){var e=o$.current;return o$.current=a3,null===e?a3:e}function sA(e,t){ese&&(se=e)}function sC(){for(;null!==o3;)o3=sD(o3)}function sI(){for(;null!==o3&&!id();)o3=sD(o3)}function sD(e){var t=s(e.alternate,e,o4);return e.memoizedProps=e.pendingProps,null===t&&(t=sN(e)),oz.current=null,t}function sN(e){o3=e;do{var t=o3.alternate;if(e=o3.return,0==(2048&o3.effectTag)){a:{var n=t;t=o3;var s=o4,u=t.pendingProps;switch(t.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:case 20:case 21:break;case 1:case 17:ie(t.type)&&it(t);break;case 3:am(t),ir(t),(u=t.stateNode).pendingContext&&(u.context=u.pendingContext,u.pendingContext=null),(null===n||null===n.child)&&or(t)&&oE(t),i(t);break;case 5:av(t),s=ap(ah.current);var l=t.type;if(null!==n&&null!=t.stateNode)a(n,t,l,u,s),n.ref!==t.ref&&(t.effectTag|=128);else if(u){var d=ap(af.current);if(or(t)){var h=(u=t).stateNode;n=u.type;var p=u.memoizedProps,b=s;switch(h[n5]=u,h[n6]=p,l=void 0,s=h,n){case"iframe":case"object":case"embed":nw("load",s);break;case"video":case"audio":for(h=0;h",h=p.removeChild(p.firstChild)):"string"==typeof p.is?h=h.createElement(b,{is:p.is}):(h=h.createElement(b),"select"===b&&(b=h,p.multiple?b.multiple=!0:p.size&&(b.size=p.size))):h=h.createElementNS(d,b),(p=h)[n5]=n,p[n6]=u,r(p,t,!1,!1),t.stateNode=p,b=l;var m=s,g=nj(b,n=u);switch(b){case"iframe":case"object":case"embed":nw("load",p),s=n;break;case"video":case"audio":for(s=0;su.tailExpiration&&1l&&(l=n),p>l&&(l=p),s=s.sibling;u.childExpirationTime=l}if(null!==t)return t;null!==e&&0==(2048&e.effectTag)&&(null===e.firstEffect&&(e.firstEffect=o3.firstEffect),null!==o3.lastEffect&&(null!==e.lastEffect&&(e.lastEffect.nextEffect=o3.firstEffect),e.lastEffect=o3.lastEffect),1(e=e.childExpirationTime)?t:e}function sR(e){var t=iO();return iL(99,sj.bind(null,e,t)),null}function sj(e,t){do sY();while(null!==sc)if((o0&(oK|oV))!==oG)throw Error(f(327));var n=e.finishedWork,r=e.finishedExpirationTime;if(null===n)return null;if(e.finishedWork=null,e.finishedExpirationTime=0,n===e.current)throw Error(f(177));e.callbackNode=null,e.callbackExpirationTime=0,e.callbackPriority=90,e.nextKnownPendingLevel=0;var i=sP(n);if(e.firstPendingTime=i,r<=e.lastSuspendedTime?e.firstSuspendedTime=e.lastSuspendedTime=e.nextKnownPendingLevel=0:r<=e.firstSuspendedTime&&(e.firstSuspendedTime=r-1),r<=e.lastPingedTime&&(e.lastPingedTime=0),r<=e.lastExpiredTime&&(e.lastExpiredTime=0),e===o2&&(o3=o2=null,o4=0),1s&&(l=s,s=o,o=l),l=nH(E,o),d=nH(E,s),l&&d&&(1!==k.rangeCount||k.anchorNode!==l.node||k.anchorOffset!==l.offset||k.focusNode!==d.node||k.focusOffset!==d.offset)&&((S=S.createRange()).setStart(l.node,l.offset),k.removeAllRanges(),o>s?(k.addRange(S),k.extend(d.node,d.offset)):(S.setEnd(d.node,d.offset),k.addRange(S))))),S=[],k=E;k=k.parentNode;)1===k.nodeType&&S.push({element:k,left:k.scrollLeft,top:k.scrollTop});for("function"==typeof E.focus&&E.focus(),E=0;E=n)return og(e,t,n);return r4(ay,1&ay.current,t),null!==(t=o_(e,t,n))?t.sibling:null}r4(ay,1&ay.current,t);break;case 19:if(r=t.childExpirationTime>=n,0!=(64&e.effectTag)){if(r)return ow(e,t,n);t.effectTag|=64}if(null!==(i=t.memoizedState)&&(i.rendering=null,i.tail=null),r4(ay,ay.current,t),!r)return null}return o_(e,t,n)}oo=!1}}else oo=!1;switch(t.expirationTime=0,t.tag){case 2:if(r=t.type,null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),e=t.pendingProps,i=r7(t,r6.current),iW(t,n),i=aY(null,t,r,e,i,n),t.effectTag|=1,"object"==typeof i&&null!==i&&"function"==typeof i.render&&void 0===i.$$typeof){if(t.tag=1,aB(),ie(r)){var a=!0;io(t)}else a=!1;t.memoizedState=null!==i.state&&void 0!==i.state?i.state:null;var o=r.getDerivedStateFromProps;"function"==typeof o&&i8(t,r,o,e),i.updater=i7,t.stateNode=i,i._reactInternalFiber=t,ar(t,r,e,n),t=op(null,t,r,!0,a,n)}else t.tag=0,os(null,t,i,n),t=t.child;return t;case 16:if(i=t.elementType,null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),e=t.pendingProps,er(i),1!==i._status)throw i._result;switch(i=i._result,t.type=i,a=t.tag=sX(i),e=ij(i,e),a){case 0:t=od(null,t,i,e,n);break;case 1:t=oh(null,t,i,e,n);break;case 11:t=ou(null,t,i,e,n);break;case 14:t=oc(null,t,i,ij(i.type,e),r,n);break;default:throw Error(f(306,i,""))}return t;case 0:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:ij(r,i),od(e,t,r,i,n);case 1:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:ij(r,i),oh(e,t,r,i,n);case 3:if(ob(t),null===(r=t.updateQueue))throw Error(f(282));if(i=null!==(i=t.memoizedState)?i.element:null,i3(t,r,t.pendingProps,null,n),(r=t.memoizedState.element)===i)oi(),t=o_(e,t,n);else{if((i=t.stateNode.hydrate)&&(a9=n2(t.stateNode.containerInfo.firstChild),a6=t,i=a8=!0),i)for(n=ac(t,null,r,n),t.child=n;n;)n.effectTag=-3&n.effectTag|1024,n=n.sibling;else os(e,t,r,n),oi();t=t.child}return t;case 5:return ag(t),null===e&&ot(t),r=t.type,i=t.pendingProps,a=null!==e?e.memoizedProps:null,o=i.children,nQ(r,i)?o=null:null!==a&&nQ(r,a)&&(t.effectTag|=16),of(e,t),4&t.mode&&1!==n&&i.hidden?(t.expirationTime=t.childExpirationTime=1,t=null):(os(e,t,o,n),t=t.child),t;case 6:return null===e&&ot(t),null;case 13:return og(e,t,n);case 4:return ab(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=au(t,null,r,n):os(e,t,r,n),t.child;case 11:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:ij(r,i),ou(e,t,r,i,n);case 7:return os(e,t,t.pendingProps,n),t.child;case 8:case 12:return os(e,t,t.pendingProps.children,n),t.child;case 10:a:{if(r=t.type._context,i=t.pendingProps,o=t.memoizedProps,i$(t,a=i.value),null!==o){var s=o.value;if(0==(a=r$(s,a)?0:("function"==typeof r._calculateChangedBits?r._calculateChangedBits(s,a):1073741823)|0)){if(o.children===i.children&&!r9.current){t=o_(e,t,n);break a}}else for(null!==(s=t.child)&&(s.return=t);null!==s;){var u=s.dependencies;if(null!==u){o=s.child;for(var c=u.firstContext;null!==c;){if(c.context===r&&0!=(c.observedBits&a)){1===s.tag&&((c=iX(n,null)).tag=2,iQ(s,c)),s.expirationTime=t&&e<=t}function s5(e,t){var n=e.firstSuspendedTime,r=e.lastSuspendedTime;nt||0===n)&&(e.lastSuspendedTime=t),t<=e.lastPingedTime&&(e.lastPingedTime=0),t<=e.lastExpiredTime&&(e.lastExpiredTime=0)}function s6(e,t){t>e.firstPendingTime&&(e.firstPendingTime=t);var n=e.firstSuspendedTime;0!==n&&(t>=n?e.firstSuspendedTime=e.lastSuspendedTime=e.nextKnownPendingLevel=0:t>=e.lastSuspendedTime&&(e.lastSuspendedTime=t+1),t>e.nextKnownPendingLevel&&(e.nextKnownPendingLevel=t))}function s9(e,t){var n=e.lastExpiredTime;(0===n||n>t)&&(e.lastExpiredTime=t)}function s8(e,t,n,r){var i=t.current,a=sb(),o=i6.suspense;a=sm(a,i,o);a:if(n){n=n._reactInternalFiber;b:{if(tr(n)!==n||1!==n.tag)throw Error(f(170));var s=n;do{switch(s.tag){case 3:s=s.stateNode.context;break b;case 1:if(ie(s.type)){s=s.stateNode.__reactInternalMemoizedMergedChildContext;break b}}s=s.return}while(null!==s)throw Error(f(171))}if(1===n.tag){var u=n.type;if(ie(u)){n=ia(n,u,s);break a}}n=s}else n=r5;return null===t.context?t.context=n:t.pendingContext=n,(t=iX(a,o)).payload={element:e},null!==(r=void 0===r?null:r)&&(t.callback=r),iQ(i,t),sg(i,a),a}function s7(e){return(e=e.current).child?(e.child.tag,e.child.stateNode):null}function ue(e,t){null!==(e=e.memoizedState)&&null!==e.dehydrated&&e.retryTime1&&void 0!==arguments[1]?arguments[1]:this.props,n=t.target;if(n){var r=n;"string"==typeof n&&(r=window[n]),_(t,e.bind(null,r))}}},{key:"render",value:function(){return this.props.children||null}}]),t}(h.PureComponent);S.propTypes={},t.withOptions=E,t.default=S},69590(e){"use strict";var t=Array.isArray,n=Object.keys,r=Object.prototype.hasOwnProperty,i="undefined"!=typeof Element;function a(e,o){if(e===o)return!0;if(e&&o&&"object"==typeof e&&"object"==typeof o){var s,u,c,l=t(e),f=t(o);if(l&&f){if((u=e.length)!=o.length)return!1;for(s=u;0!=s--;)if(!a(e[s],o[s]))return!1;return!0}if(l!=f)return!1;var d=e instanceof Date,h=o instanceof Date;if(d!=h)return!1;if(d&&h)return e.getTime()==o.getTime();var p=e instanceof RegExp,b=o instanceof RegExp;if(p!=b)return!1;if(p&&b)return e.toString()==o.toString();var m=n(e);if((u=m.length)!==n(o).length)return!1;for(s=u;0!=s--;)if(!r.call(o,m[s]))return!1;if(i&&e instanceof Element&&o instanceof Element)return e===o;for(s=u;0!=s--;)if(("_owner"!==(c=m[s])||!e.$$typeof)&&!a(e[c],o[c]))return!1;return!0}return e!=e&&o!=o}e.exports=function(e,t){try{return a(e,t)}catch(n){if(n.message&&n.message.match(/stack|recursion/i)||-2146828260===n.number)return console.warn("Warning: react-fast-compare does not handle circular references.",n.name,n.message),!1;throw n}}},57209(e,t,n){"use strict";function r(e){return e&&"object"==typeof e&&"default"in e?e.default:e}i={value:!0};var i,a=r(n(67294));function o(e){return o.warnAboutHMRDisabled&&(o.warnAboutHMRDisabled=!0,console.error("React-Hot-Loader: misconfiguration detected, using production version in non-production environment."),console.error("React-Hot-Loader: Hot Module Replacement is not enabled.")),a.Children.only(e.children)}o.warnAboutHMRDisabled=!1;var s=function e(){return e.shouldWrapWithAppContainer?function(e){return function(t){return a.createElement(o,null,a.createElement(e,t))}}:function(e){return e}};s.shouldWrapWithAppContainer=!1;var u=function(e,t){return e===t},c=function(){},l=function(e){return e},f=function(){};t.zj=o,t.wU=s,i=u,i=c,i=l,i=f},69921(e,t){"use strict";/** @license React v16.13.1 + * react-is.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ var n="function"==typeof Symbol&&Symbol.for,r=n?Symbol.for("react.element"):60103,i=n?Symbol.for("react.portal"):60106,a=n?Symbol.for("react.fragment"):60107,o=n?Symbol.for("react.strict_mode"):60108,s=n?Symbol.for("react.profiler"):60114,u=n?Symbol.for("react.provider"):60109,c=n?Symbol.for("react.context"):60110,l=n?Symbol.for("react.async_mode"):60111,f=n?Symbol.for("react.concurrent_mode"):60111,d=n?Symbol.for("react.forward_ref"):60112,h=n?Symbol.for("react.suspense"):60113,p=n?Symbol.for("react.suspense_list"):60120,b=n?Symbol.for("react.memo"):60115,m=n?Symbol.for("react.lazy"):60116,g=n?Symbol.for("react.block"):60121,v=n?Symbol.for("react.fundamental"):60117,y=n?Symbol.for("react.responder"):60118,w=n?Symbol.for("react.scope"):60119;function _(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case r:switch(e=e.type){case l:case f:case a:case s:case o:case h:return e;default:switch(e=e&&e.$$typeof){case c:case d:case m:case b:case u:return e;default:return t}}case i:return t}}}function E(e){return _(e)===f}t.AsyncMode=l,t.ConcurrentMode=f,t.ContextConsumer=c,t.ContextProvider=u,t.Element=r,t.ForwardRef=d,t.Fragment=a,t.Lazy=m,t.Memo=b,t.Portal=i,t.Profiler=s,t.StrictMode=o,t.Suspense=h,t.isAsyncMode=function(e){return E(e)||_(e)===l},t.isConcurrentMode=E,t.isContextConsumer=function(e){return _(e)===c},t.isContextProvider=function(e){return _(e)===u},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===r},t.isForwardRef=function(e){return _(e)===d},t.isFragment=function(e){return _(e)===a},t.isLazy=function(e){return _(e)===m},t.isMemo=function(e){return _(e)===b},t.isPortal=function(e){return _(e)===i},t.isProfiler=function(e){return _(e)===s},t.isStrictMode=function(e){return _(e)===o},t.isSuspense=function(e){return _(e)===h},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===a||e===f||e===s||e===o||e===h||e===p||"object"==typeof e&&null!==e&&(e.$$typeof===m||e.$$typeof===b||e.$$typeof===u||e.$$typeof===c||e.$$typeof===d||e.$$typeof===v||e.$$typeof===y||e.$$typeof===w||e.$$typeof===g)},t.typeOf=_},59864(e,t,n){"use strict";e.exports=n(69921)},46871(e,t,n){"use strict";function r(){var e=this.constructor.getDerivedStateFromProps(this.props,this.state);null!=e&&this.setState(e)}function i(e){function t(t){var n=this.constructor.getDerivedStateFromProps(e,t);return null!=n?n:null}this.setState(t.bind(this))}function a(e,t){try{var n=this.props,r=this.state;this.props=e,this.state=t,this.__reactInternalSnapshotFlag=!0,this.__reactInternalSnapshot=this.getSnapshotBeforeUpdate(n,r)}finally{this.props=n,this.state=r}}function o(e){var t,n=e.prototype;if(!n||!n.isReactComponent)throw Error("Can only polyfill class components");if("function"!=typeof e.getDerivedStateFromProps&&"function"!=typeof n.getSnapshotBeforeUpdate)return e;var o=null,s=null,u=null;if("function"==typeof n.componentWillMount?o="componentWillMount":"function"==typeof n.UNSAFE_componentWillMount&&(o="UNSAFE_componentWillMount"),"function"==typeof n.componentWillReceiveProps?s="componentWillReceiveProps":"function"==typeof n.UNSAFE_componentWillReceiveProps&&(s="UNSAFE_componentWillReceiveProps"),"function"==typeof n.componentWillUpdate?u="componentWillUpdate":"function"==typeof n.UNSAFE_componentWillUpdate&&(u="UNSAFE_componentWillUpdate"),null!==o||null!==s||null!==u){throw Error("Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n"+(e.displayName||e.name)+" uses "+("function"==typeof e.getDerivedStateFromProps?"getDerivedStateFromProps()":"getSnapshotBeforeUpdate()")+" but also contains the following legacy lifecycles:"+(null!==o?"\n "+o:"")+(null!==s?"\n "+s:"")+(null!==u?"\n "+u:"")+"\n\nThe above lifecycles should be removed. Learn more about this warning here:\nhttps://fb.me/react-async-component-lifecycle-hooks")}if("function"==typeof e.getDerivedStateFromProps&&(n.componentWillMount=r,n.componentWillReceiveProps=i),"function"==typeof n.getSnapshotBeforeUpdate){if("function"!=typeof n.componentDidUpdate)throw Error("Cannot polyfill getSnapshotBeforeUpdate() for components that do not define componentDidUpdate() on the prototype");n.componentWillUpdate=a;var c=n.componentDidUpdate;n.componentDidUpdate=function(e,t,n){var r=this.__reactInternalSnapshotFlag?this.__reactInternalSnapshot:n;c.call(this,e,t,r)}}return e}n.r(t),n.d(t,{polyfill:()=>o}),r.__suppressDeprecationWarning=!0,i.__suppressDeprecationWarning=!0,a.__suppressDeprecationWarning=!0},55977(e,t,n){"use strict";n.d(t,{zt:()=>h,$j:()=>J,wU:()=>A,I0:()=>er,v9:()=>es});var r=n(67294);n(45697);var i=r.createContext(null);function a(e){e()}var o=a,s=function(e){return o=e},u=function(){return o},c={notify:function(){}};function l(){var e=u(),t=null,n=null;return{clear:function(){t=null,n=null},notify:function(){e(function(){for(var e=t;e;)e.callback(),e=e.next})},get:function(){for(var e=[],n=t;n;)e.push(n),n=n.next;return e},subscribe:function(e){var r=!0,i=n={callback:e,next:null,prev:n};return i.prev?i.prev.next=i:t=i,function(){r&&null!==t&&(r=!1,i.next?i.next.prev=i.prev:n=i.prev,i.prev?i.prev.next=i.next:t=i.next)}}}}var f=function(){function e(e,t){this.store=e,this.parentSub=t,this.unsubscribe=null,this.listeners=c,this.handleChangeWrapper=this.handleChangeWrapper.bind(this)}var t=e.prototype;return t.addNestedSub=function(e){return this.trySubscribe(),this.listeners.subscribe(e)},t.notifyNestedSubs=function(){this.listeners.notify()},t.handleChangeWrapper=function(){this.onStateChange&&this.onStateChange()},t.isSubscribed=function(){return Boolean(this.unsubscribe)},t.trySubscribe=function(){this.unsubscribe||(this.unsubscribe=this.parentSub?this.parentSub.addNestedSub(this.handleChangeWrapper):this.store.subscribe(this.handleChangeWrapper),this.listeners=l())},t.tryUnsubscribe=function(){this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null,this.listeners.clear(),this.listeners=c)},e}();function d(e){var t=e.store,n=e.context,a=e.children,o=(0,r.useMemo)(function(){var e=new f(t);return e.onStateChange=e.notifyNestedSubs,{store:t,subscription:e}},[t]),s=(0,r.useMemo)(function(){return t.getState()},[t]);(0,r.useEffect)(function(){var e=o.subscription;return e.trySubscribe(),s!==t.getState()&&e.notifyNestedSubs(),function(){e.tryUnsubscribe(),e.onStateChange=null}},[o,s]);var u=n||i;return r.createElement(u.Provider,{value:o},a)}let h=d;var p=n(87462);function b(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}var m=n(8679),g=n.n(m),v=n(59864),y="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?r.useLayoutEffect:r.useEffect,w=[],_=[null,null];function E(e,t){var n=e[1];return[t.payload,n+1]}function S(e,t,n){y(function(){return e.apply(void 0,t)},n)}function k(e,t,n,r,i,a,o){e.current=r,t.current=i,n.current=!1,a.current&&(a.current=null,o())}function x(e,t,n,r,i,a,o,s,u,c){if(e){var l,f=!1,d=null,h=function(){if(!f){var e,n,l=t.getState();try{e=r(l,i.current)}catch(h){n=h,d=h}n||(d=null),e===a.current?o.current||u():(a.current=e,s.current=e,o.current=!0,c({type:"STORE_UPDATED",payload:{error:n}}))}};return n.onStateChange=h,n.trySubscribe(),h(),function(){if(f=!0,n.tryUnsubscribe(),n.onStateChange=null,d)throw d}}}var T=function(){return[null,0]};function M(e,t){void 0===t&&(t={});var n=t,a=n.getDisplayName,o=void 0===a?function(e){return"ConnectAdvanced("+e+")"}:a,s=n.methodName,u=void 0===s?"connectAdvanced":s,c=n.renderCountProp,l=void 0===c?void 0:c,d=n.shouldHandleStateChanges,h=void 0===d||d,m=n.storeKey,y=void 0===m?"store":m,M=(n.withRef,n.forwardRef),O=void 0!==M&&M,A=n.context,L=void 0===A?i:A,C=b(n,["getDisplayName","methodName","renderCountProp","shouldHandleStateChanges","storeKey","withRef","forwardRef","context"]),I=L;return function(t){var n=t.displayName||t.name||"Component",i=o(n),a=(0,p.Z)({},C,{getDisplayName:o,methodName:u,renderCountProp:l,shouldHandleStateChanges:h,storeKey:y,displayName:i,wrappedComponentName:n,WrappedComponent:t}),s=C.pure;function c(t){return e(t.dispatch,a)}var d=s?r.useMemo:function(e){return e()};function m(e){var n=(0,r.useMemo)(function(){var t=e.reactReduxForwardedRef,n=b(e,["reactReduxForwardedRef"]);return[e.context,t,n]},[e]),i=n[0],a=n[1],o=n[2],s=(0,r.useMemo)(function(){return i&&i.Consumer&&(0,v.isContextConsumer)(r.createElement(i.Consumer,null))?i:I},[i,I]),u=(0,r.useContext)(s),l=Boolean(e.store)&&Boolean(e.store.getState)&&Boolean(e.store.dispatch);Boolean(u)&&u.store;var m=l?e.store:u.store,g=(0,r.useMemo)(function(){return c(m)},[m]),y=(0,r.useMemo)(function(){if(!h)return _;var e=new f(m,l?null:u.subscription),t=e.notifyNestedSubs.bind(e);return[e,t]},[m,l,u]),M=y[0],O=y[1],A=(0,r.useMemo)(function(){return l?u:(0,p.Z)({},u,{subscription:M})},[l,u,M]),L=(0,r.useReducer)(E,w,T),C=L[0][0],D=L[1];if(C&&C.error)throw C.error;var N=(0,r.useRef)(),P=(0,r.useRef)(o),R=(0,r.useRef)(),j=(0,r.useRef)(!1),F=d(function(){return R.current&&o===P.current?R.current:g(m.getState(),o)},[m,C,o]);S(k,[P,N,j,o,F,R,O]),S(x,[h,m,M,g,P,N,j,R,O,D],[m,M,g]);var Y=(0,r.useMemo)(function(){return r.createElement(t,(0,p.Z)({},F,{ref:a}))},[a,t,F]);return(0,r.useMemo)(function(){return h?r.createElement(s.Provider,{value:A},Y):Y},[s,Y,A])}var M=s?r.memo(m):m;if(M.WrappedComponent=t,M.displayName=i,O){var A=r.forwardRef(function(e,t){return r.createElement(M,(0,p.Z)({},e,{reactReduxForwardedRef:t}))});return A.displayName=i,A.WrappedComponent=t,g()(A,t)}return g()(M,t)}}function O(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function A(e,t){if(O(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(var i=0;i=0;r--){var i=t[r](e);if(i)return i}return function(t,r){throw Error("Invalid value of type "+typeof e+" for "+n+" argument when connecting component "+r.wrappedComponentName+".")}}function Z(e,t){return e===t}function X(e){var t=void 0===e?{}:e,n=t.connectHOC,r=void 0===n?M:n,i=t.mapStateToPropsFactories,a=void 0===i?B:i,o=t.mapDispatchToPropsFactories,s=void 0===o?j:o,u=t.mergePropsFactories,c=void 0===u?G:u,l=t.selectorFactory,f=void 0===l?V:l;return function(e,t,n,i){void 0===i&&(i={});var o=i,u=o.pure,l=void 0===u||u,d=o.areStatesEqual,h=void 0===d?Z:d,m=o.areOwnPropsEqual,g=void 0===m?A:m,v=o.areStatePropsEqual,y=void 0===v?A:v,w=o.areMergedPropsEqual,_=void 0===w?A:w,E=b(o,["pure","areStatesEqual","areOwnPropsEqual","areStatePropsEqual","areMergedPropsEqual"]),S=q(e,a,"mapStateToProps"),k=q(t,s,"mapDispatchToProps"),x=q(n,c,"mergeProps");return r(f,(0,p.Z)({methodName:"connect",getDisplayName:function(e){return"Connect("+e+")"},shouldHandleStateChanges:Boolean(e),initMapStateToProps:S,initMapDispatchToProps:k,initMergeProps:x,pure:l,areStatesEqual:h,areOwnPropsEqual:g,areStatePropsEqual:y,areMergedPropsEqual:_},E))}}let J=X();function Q(){var e;return(0,r.useContext)(i)}function ee(e){void 0===e&&(e=i);var t=e===i?Q:function(){return(0,r.useContext)(e)};return function(){return t().store}}var et=ee();function en(e){void 0===e&&(e=i);var t=e===i?et:ee(e);return function(){return t().dispatch}}var er=en(),ei=function(e,t){return e===t};function ea(e,t,n,i){var a,o=(0,r.useReducer)(function(e){return e+1},0)[1],s=(0,r.useMemo)(function(){return new f(n,i)},[n,i]),u=(0,r.useRef)(),c=(0,r.useRef)(),l=(0,r.useRef)(),d=(0,r.useRef)(),h=n.getState();try{a=e!==c.current||h!==l.current||u.current?e(h):d.current}catch(p){throw u.current&&(p.message+="\nThe error may be correlated with this previous error:\n"+u.current.stack+"\n\n"),p}return y(function(){c.current=e,l.current=h,d.current=a,u.current=void 0}),y(function(){function e(){try{var e=c.current(n.getState());if(t(e,d.current))return;d.current=e}catch(r){u.current=r}o()}return s.onStateChange=e,s.trySubscribe(),e(),function(){return s.tryUnsubscribe()}},[n,s]),a}function eo(e){void 0===e&&(e=i);var t=e===i?Q:function(){return(0,r.useContext)(e)};return function(e,n){void 0===n&&(n=ei);var i,a=t(),o=ea(e,n,a.store,a.subscription);return(0,r.useDebugValue)(o),o}}var es=eo();s(n(73935).unstable_batchedUpdates)},76(e,t,n){"use strict";n.d(t,{VK:()=>f,rU:()=>v});var r=n(47886);function i(e,t){return(i=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function a(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,i(e,t)}var o=n(67294),s=n(90071);function u(){return(u=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}n(45697);var l=n(2177),f=function(e){function t(){for(var t,n=arguments.length,r=Array(n),i=0;iN,AW:()=>U,F0:()=>M,rs:()=>$,s6:()=>T,LX:()=>Y,k6:()=>G,TH:()=>W,UO:()=>K,$B:()=>V});var a=n(67294),o=n(45697),s=n.n(o),u=n(90071);function c(e,t){return(c=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function l(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,c(e,t)}var f=1073741823,d="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==n.g?n.g:{};function h(){var e="__global_unique_id__";return d[e]=(d[e]||0)+1}function p(e,t){return e===t?0!==e||1/e==1/t:e!=e&&t!=t}function b(e){var t=[];return{on:function(e){t.push(e)},off:function(e){t=t.filter(function(t){return t!==e})},get:function(){return e},set:function(n,r){e=n,t.forEach(function(t){return t(e,r)})}}}function m(e){return Array.isArray(e)?e[0]:e}function g(e,t){var n,r,i="__create-react-context-"+h()+"__",o=function(e){function n(){var t;return t=e.apply(this,arguments)||this,t.emitter=b(t.props.value),t}l(n,e);var r=n.prototype;return r.getChildContext=function(){var e;return(e={})[i]=this.emitter,e},r.componentWillReceiveProps=function(e){if(this.props.value!==e.value){var n,r=this.props.value,i=e.value;p(r,i)?n=0:(n="function"==typeof t?t(r,i):f,0!=(n|=0)&&this.emitter.set(e.value,n))}},r.render=function(){return this.props.children},n}(a.Component);o.childContextTypes=((n={})[i]=s().object.isRequired,n);var u=function(t){function n(){var e;return e=t.apply(this,arguments)||this,e.state={value:e.getValue()},e.onUpdate=function(t,n){((0|e.observedBits)&n)!=0&&e.setState({value:e.getValue()})},e}l(n,t);var r=n.prototype;return r.componentWillReceiveProps=function(e){var t=e.observedBits;this.observedBits=null==t?f:t},r.componentDidMount=function(){this.context[i]&&this.context[i].on(this.onUpdate);var e=this.props.observedBits;this.observedBits=null==e?f:e},r.componentWillUnmount=function(){this.context[i]&&this.context[i].off(this.onUpdate)},r.getValue=function(){return this.context[i]?this.context[i].get():e},r.render=function(){return m(this.props.children)(this.state.value)},n}(a.Component);return u.contextTypes=((r={})[i]=s().object,r),{Provider:o,Consumer:u}}var v=a.createContext||g;let y=v;var w=n(2177);function _(){return(_=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}function l(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.__proto__=t}n(54726);var f="unmounted";t.UNMOUNTED=f;var d="exited";t.EXITED=d;var h="entering";t.ENTERING=h;var p="entered";t.ENTERED=p;var b="exiting";t.EXITING=b;var m=function(e){function t(t,n){r=e.call(this,t,n)||this;var r,i,a=n.transitionGroup,o=a&&!a.isMounting?t.enter:t.appear;return r.appearStatus=null,t.in?o?(i=d,r.appearStatus=h):i=p:i=t.unmountOnExit||t.mountOnEnter?f:d,r.state={status:i},r.nextCallback=null,r}l(t,e);var n=t.prototype;return n.getChildContext=function(){return{transitionGroup:null}},t.getDerivedStateFromProps=function(e,t){return e.in&&t.status===f?{status:d}:null},n.componentDidMount=function(){this.updateStatus(!0,this.appearStatus)},n.componentDidUpdate=function(e){var t=null;if(e!==this.props){var n=this.state.status;this.props.in?n!==h&&n!==p&&(t=h):(n===h||n===p)&&(t=b)}this.updateStatus(!1,t)},n.componentWillUnmount=function(){this.cancelNextCallback()},n.getTimeouts=function(){var e,t,n,r=this.props.timeout;return e=t=n=r,null!=r&&"number"!=typeof r&&(e=r.exit,t=r.enter,n=void 0!==r.appear?r.appear:t),{exit:e,enter:t,appear:n}},n.updateStatus=function(e,t){if(void 0===e&&(e=!1),null!==t){this.cancelNextCallback();var n=a.default.findDOMNode(this);t===h?this.performEnter(n,e):this.performExit(n)}else this.props.unmountOnExit&&this.state.status===d&&this.setState({status:f})},n.performEnter=function(e,t){var n=this,r=this.props.enter,i=this.context.transitionGroup?this.context.transitionGroup.isMounting:t,a=this.getTimeouts(),o=i?a.appear:a.enter;if(!t&&!r){this.safeSetState({status:p},function(){n.props.onEntered(e)});return}this.props.onEnter(e,i),this.safeSetState({status:h},function(){n.props.onEntering(e,i),n.onTransitionEnd(e,o,function(){n.safeSetState({status:p},function(){n.props.onEntered(e,i)})})})},n.performExit=function(e){var t=this,n=this.props.exit,r=this.getTimeouts();if(!n){this.safeSetState({status:d},function(){t.props.onExited(e)});return}this.props.onExit(e),this.safeSetState({status:b},function(){t.props.onExiting(e),t.onTransitionEnd(e,r.exit,function(){t.safeSetState({status:d},function(){t.props.onExited(e)})})})},n.cancelNextCallback=function(){null!==this.nextCallback&&(this.nextCallback.cancel(),this.nextCallback=null)},n.safeSetState=function(e,t){t=this.setNextCallback(t),this.setState(e,t)},n.setNextCallback=function(e){var t=this,n=!0;return this.nextCallback=function(r){n&&(n=!1,t.nextCallback=null,e(r))},this.nextCallback.cancel=function(){n=!1},this.nextCallback},n.onTransitionEnd=function(e,t,n){this.setNextCallback(n);var r=null==t&&!this.props.addEndListener;if(!e||r){setTimeout(this.nextCallback,0);return}this.props.addEndListener&&this.props.addEndListener(e,this.nextCallback),null!=t&&setTimeout(this.nextCallback,t)},n.render=function(){var e=this.state.status;if(e===f)return null;var t=this.props,n=t.children,r=c(t,["children"]);if(delete r.in,delete r.mountOnEnter,delete r.unmountOnExit,delete r.appear,delete r.enter,delete r.exit,delete r.timeout,delete r.addEndListener,delete r.onEnter,delete r.onEntering,delete r.onEntered,delete r.onExit,delete r.onExiting,delete r.onExited,"function"==typeof n)return n(e,r);var a=i.default.Children.only(n);return i.default.cloneElement(a,r)},t}(i.default.Component);function g(){}m.contextTypes={transitionGroup:r.object},m.childContextTypes={transitionGroup:function(){}},m.propTypes={},m.defaultProps={in:!1,mountOnEnter:!1,unmountOnExit:!1,appear:!1,enter:!0,exit:!0,onEnter:g,onEntering:g,onEntered:g,onExit:g,onExiting:g,onExited:g},m.UNMOUNTED=0,m.EXITED=1,m.ENTERING=2,m.ENTERED=3,m.EXITING=4;var v=(0,o.polyfill)(m);t.default=v},92381(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var r=s(n(45697)),i=s(n(67294)),a=n(46871),o=n(40537);function s(e){return e&&e.__esModule?e:{default:e}}function u(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}function c(){return(c=Object.assign||function(e){for(var t=1;tI.length&&I.push(e)}function P(e,t,n,r){var i=typeof e;("undefined"===i||"boolean"===i)&&(e=null);var s=!1;if(null===e)s=!0;else switch(i){case"string":case"number":s=!0;break;case"object":switch(e.$$typeof){case a:case o:s=!0}}if(s)return n(r,e,""===t?"."+j(e,0):t),1;if(s=0,t=""===t?".":t+":",Array.isArray(e))for(var u=0;u2)?"one of ".concat(t," ").concat(e.slice(0,n-1).join(", "),", or ")+e[n-1]:2===n?"one of ".concat(t," ").concat(e[0]," or ").concat(e[1]):"of ".concat(t," ").concat(e[0])}function a(e,t,n){return e.substr(!n||n<0?0:+n,t.length)===t}function o(e,t,n){return(void 0===n||n>e.length)&&(n=e.length),e.substring(n-t.length,n)===t}function s(e,t,n){return"number"!=typeof n&&(n=0),!(n+t.length>e.length)&&-1!==e.indexOf(t,n)}r("ERR_INVALID_OPT_VALUE",function(e,t){return'The value "'+t+'" is invalid for option "'+e+'"'},TypeError),r("ERR_INVALID_ARG_TYPE",function(e,t,n){if("string"==typeof t&&a(t,"not ")?(r="must not be",t=t.replace(/^not /,"")):r="must be",o(e," argument"))u="The ".concat(e," ").concat(r," ").concat(i(t,"type"));else{var r,u,c=s(e,".")?"property":"argument";u='The "'.concat(e,'" ').concat(c," ").concat(r," ").concat(i(t,"type"))}return u+". Received type ".concat(typeof n)},TypeError),r("ERR_STREAM_PUSH_AFTER_EOF","stream.push() after EOF"),r("ERR_METHOD_NOT_IMPLEMENTED",function(e){return"The "+e+" method is not implemented"}),r("ERR_STREAM_PREMATURE_CLOSE","Premature close"),r("ERR_STREAM_DESTROYED",function(e){return"Cannot call "+e+" after a stream was destroyed"}),r("ERR_MULTIPLE_CALLBACK","Callback called multiple times"),r("ERR_STREAM_CANNOT_PIPE","Cannot pipe, not readable"),r("ERR_STREAM_WRITE_AFTER_END","write after end"),r("ERR_STREAM_NULL_VALUES","May not write null values to stream",TypeError),r("ERR_UNKNOWN_ENCODING",function(e){return"Unknown encoding: "+e},TypeError),r("ERR_STREAM_UNSHIFT_AFTER_END_EVENT","stream.unshift() after end event"),e.exports.q=n},56753(e,t,n){"use strict";var r=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};e.exports=c;var i=n(79481),a=n(64229);n(35717)(c,i);for(var o=r(a.prototype),s=0;s0){if("string"==typeof t||u.objectMode||Object.getPrototypeOf(t)===a.prototype||(t=s(t)),r)u.endEmitted?S(e,new E):A(e,u,t,!0);else if(u.ended)S(e,new w);else{if(u.destroyed)return!1;u.reading=!1,u.decoder&&!n?(t=u.decoder.write(t),u.objectMode||0!==t.length?A(e,u,t,!1):j(e,u)):A(e,u,t,!1)}}else r||(u.reading=!1,j(e,u));return!u.ended&&(u.length=C?e=C:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}function D(e,t){return e<=0||0===t.length&&t.ended?0:t.objectMode?1:e!=e?t.flowing&&t.length?t.buffer.head.data.length:t.length:(e>t.highWaterMark&&(t.highWaterMark=I(e)),e<=t.length)?e:t.ended?t.length:(t.needReadable=!0,0)}function N(e,t){if(f("onEofChunk"),!t.ended){if(t.decoder){var n=t.decoder.end();n&&n.length&&(t.buffer.push(n),t.length+=t.objectMode?1:n.length)}t.ended=!0,t.sync?P(e):(t.needReadable=!1,t.emittedReadable||(t.emittedReadable=!0,R(e)))}}function P(e){var t=e._readableState;f("emitReadable",t.needReadable,t.emittedReadable),t.needReadable=!1,t.emittedReadable||(f("emitReadable",t.flowing),t.emittedReadable=!0,process.nextTick(R,e))}function R(e){var t=e._readableState;f("emitReadable_",t.destroyed,t.length,t.ended),!t.destroyed&&(t.length||t.ended)&&(e.emit("readable"),t.emittedReadable=!1),t.needReadable=!t.flowing&&!t.ended&&t.length<=t.highWaterMark,z(e)}function j(e,t){t.readingMore||(t.readingMore=!0,process.nextTick(F,e,t))}function F(e,t){for(;!t.reading&&!t.ended&&(t.length0,t.resumeScheduled&&!t.paused?t.flowing=!0:e.listenerCount("data")>0&&e.resume()}function U(e){f("readable nexttick read 0"),e.read(0)}function H(e,t){t.resumeScheduled||(t.resumeScheduled=!0,process.nextTick($,e,t))}function $(e,t){f("resume",t.reading),t.reading||e.read(0),t.resumeScheduled=!1,e.emit("resume"),z(e),t.flowing&&!t.reading&&e.read(0)}function z(e){var t=e._readableState;for(f("flow",t.flowing);t.flowing&&null!==e.read(););}function G(e,t){var n;return 0===t.length?null:(t.objectMode?n=t.buffer.shift():!e||e>=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.first():t.buffer.concat(t.length),t.buffer.clear()):n=t.buffer.consume(e,t.decoder),n)}function W(e){var t=e._readableState;f("endReadable",t.endEmitted),t.endEmitted||(t.ended=!0,process.nextTick(K,t,e))}function K(e,t){if(f("endReadableNT",e.endEmitted,e.length),!e.endEmitted&&0===e.length&&(e.endEmitted=!0,t.readable=!1,t.emit("end"),e.autoDestroy)){var n=t._writableState;(!n||n.autoDestroy&&n.finished)&&t.destroy()}}function V(e,t){for(var n=0,r=e.length;n=n.highWaterMark:n.length>0)||n.ended))return f("read: emitReadable",n.length,n.ended),0===n.length&&n.ended?W(this):P(this),null;if(0===(e=D(e,n))&&n.ended)return 0===n.length&&W(this),null;var i=n.needReadable;return f("need readable",i),(0===n.length||n.length-e0?G(e,n):null)?(n.needReadable=n.length<=n.highWaterMark,e=0):(n.length-=e,n.awaitDrain=0),0===n.length&&(n.ended||(n.needReadable=!0),r!==e&&n.ended&&W(this)),null!==t&&this.emit("data",t),t},M.prototype._read=function(e){S(this,new _("_read()"))},M.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,f("pipe count=%d opts=%j",i.pipesCount,t);var a=t&&!1===t.end||e===process.stdout||e===process.stderr?m:s;function o(e,t){f("onunpipe"),e===n&&t&&!1===t.hasUnpiped&&(t.hasUnpiped=!0,l())}function s(){f("onend"),e.end()}i.endEmitted?process.nextTick(a):n.once("end",a),e.on("unpipe",o);var u=Y(n);e.on("drain",u);var c=!1;function l(){f("cleanup"),e.removeListener("close",p),e.removeListener("finish",b),e.removeListener("drain",u),e.removeListener("error",h),e.removeListener("unpipe",o),n.removeListener("end",s),n.removeListener("end",m),n.removeListener("data",d),c=!0,i.awaitDrain&&(!e._writableState||e._writableState.needDrain)&&u()}function d(t){f("ondata");var r=e.write(t);f("dest.write",r),!1===r&&((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==V(i.pipes,e))&&!c&&(f("false write response, pause",i.awaitDrain),i.awaitDrain++),n.pause())}function h(t){f("onerror",t),m(),e.removeListener("error",h),0===r(e,"error")&&S(e,t)}function p(){e.removeListener("finish",b),m()}function b(){f("onfinish"),e.removeListener("close",p),m()}function m(){f("unpipe"),n.unpipe(e)}return n.on("data",d),x(e,"error",h),e.once("close",p),e.once("finish",b),e.emit("pipe",n),i.flowing||(f("pipe resume"),n.resume()),e},M.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes||(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n)),this;if(!e){var r=t.pipes,i=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var a=0;a0,!1!==r.flowing&&this.resume()):"readable"!==e||r.endEmitted||r.readableListening||(r.readableListening=r.needReadable=!0,r.flowing=!1,r.emittedReadable=!1,f("on readable",r.length,r.reading),r.length?P(this):r.reading||process.nextTick(U,this)),n},M.prototype.addListener=M.prototype.on,M.prototype.removeListener=function(e,t){var n=i.prototype.removeListener.call(this,e,t);return"readable"===e&&process.nextTick(B,this),n},M.prototype.removeAllListeners=function(e){var t=i.prototype.removeAllListeners.apply(this,arguments);return("readable"===e||void 0===e)&&process.nextTick(B,this),t},M.prototype.resume=function(){var e=this._readableState;return e.flowing||(f("resume"),e.flowing=!e.readableListening,H(this,e)),e.paused=!1,this},M.prototype.pause=function(){return f("call pause flowing=%j",this._readableState.flowing),!1!==this._readableState.flowing&&(f("pause"),this._readableState.flowing=!1,this.emit("pause")),this._readableState.paused=!0,this},M.prototype.wrap=function(e){var t=this,n=this._readableState,r=!1;for(var i in e.on("end",function(){if(f("wrapped end"),n.decoder&&!n.ended){var e=n.decoder.end();e&&e.length&&t.push(e)}t.push(null)}),e.on("data",function(i){if(f("wrapped data"),n.decoder&&(i=n.decoder.write(i)),!n.objectMode||null!=i)(n.objectMode||i&&i.length)&&(t.push(i)||(r=!0,e.pause()))}),e)void 0===this[i]&&"function"==typeof e[i]&&(this[i]=function(t){return function(){return e[t].apply(e,arguments)}}(i));for(var a=0;a-1))throw new E(e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(T.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}}),Object.defineProperty(T.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),T.prototype._write=function(e,t,n){n(new m("_write()"))},T.prototype._writev=null,T.prototype.end=function(e,t,n){var r=this._writableState;return"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!=e&&this.write(e,t),r.corked&&(r.corked=1,this.uncork()),r.ending||H(this,r,n),this},Object.defineProperty(T.prototype,"writableLength",{enumerable:!1,get:function(){return this._writableState.length}}),Object.defineProperty(T.prototype,"destroyed",{enumerable:!1,get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),T.prototype.destroy=d.destroy,T.prototype._undestroy=d.undestroy,T.prototype._destroy=function(e,t){t(e)}},45850(e,t,n){"use strict";function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var i,a=n(8610),o=Symbol("lastResolve"),s=Symbol("lastReject"),u=Symbol("error"),c=Symbol("ended"),l=Symbol("lastPromise"),f=Symbol("handlePromise"),d=Symbol("stream");function h(e,t){return{value:e,done:t}}function p(e){var t=e[o];if(null!==t){var n=e[d].read();null!==n&&(e[l]=null,e[o]=null,e[s]=null,t(h(n,!1)))}}function b(e){process.nextTick(p,e)}function m(e,t){return function(n,r){e.then(function(){if(t[c]){n(h(void 0,!0));return}t[f](n,r)},r)}}var g=Object.getPrototypeOf(function(){}),v=Object.setPrototypeOf((i={get stream(){return this[d]},next:function(){var e,t=this,n=this[u];if(null!==n)return Promise.reject(n);if(this[c])return Promise.resolve(h(void 0,!0));if(this[d].destroyed)return new Promise(function(e,n){process.nextTick(function(){t[u]?n(t[u]):e(h(void 0,!0))})});var r=this[l];if(r)e=new Promise(m(r,this));else{var i=this[d].read();if(null!==i)return Promise.resolve(h(i,!1));e=new Promise(this[f])}return this[l]=e,e}},r(i,Symbol.asyncIterator,function(){return this}),r(i,"return",function(){var e=this;return new Promise(function(t,n){e[d].destroy(null,function(e){if(e){n(e);return}t(h(void 0,!0))})})}),i),g),y=function(e){var t,n=Object.create(v,(r(t={},d,{value:e,writable:!0}),r(t,o,{value:null,writable:!0}),r(t,s,{value:null,writable:!0}),r(t,u,{value:null,writable:!0}),r(t,c,{value:e._readableState.endEmitted,writable:!0}),r(t,f,{value:function(e,t){var r=n[d].read();r?(n[l]=null,n[o]=null,n[s]=null,e(h(r,!1))):(n[o]=e,n[s]=t)},writable:!0}),t));return n[l]=null,a(e,function(e){if(e&&"ERR_STREAM_PREMATURE_CLOSE"!==e.code){var t=n[s];null!==t&&(n[l]=null,n[o]=null,n[s]=null,t(e)),n[u]=e;return}var r=n[o];null!==r&&(n[l]=null,n[o]=null,n[s]=null,r(h(void 0,!0))),n[c]=!0}),e.on("readable",b.bind(null,n)),n};e.exports=y},77086(e,t,n){"use strict";function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function i(e){for(var t=1;t0?this.tail.next=t:this.head=t,this.tail=t,++this.length}},{key:"unshift",value:function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length}},{key:"shift",value:function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}}},{key:"clear",value:function(){this.head=this.tail=null,this.length=0}},{key:"join",value:function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n}},{key:"concat",value:function(e){if(0===this.length)return c.alloc(0);for(var t=c.allocUnsafe(e>>>0),n=this.head,r=0;n;)d(n.data,t,r),r+=n.data.length,n=n.next;return t}},{key:"consume",value:function(e,t){var n;return ei.length?i.length:e;if(a===i.length?r+=i:r+=i.slice(0,e),0==(e-=a)){a===i.length?(++n,t.next?this.head=t.next:this.head=this.tail=null):(this.head=t,t.data=i.slice(a));break}++n}return this.length-=n,r}},{key:"_getBuffer",value:function(e){var t=c.allocUnsafe(e),n=this.head,r=1;for(n.data.copy(t),e-=n.data.length;n=n.next;){var i=n.data,a=e>i.length?i.length:e;if(i.copy(t,t.length-e,0,a),0==(e-=a)){a===i.length?(++r,n.next?this.head=n.next:this.head=this.tail=null):(this.head=n,n.data=i.slice(a));break}++r}return this.length-=r,t}},{key:f,value:function(e,t){return l(this,i({},t,{depth:0,customInspect:!1}))}}]),e}()},61195(e){"use strict";function t(e,t){var i=this,o=this._readableState&&this._readableState.destroyed,s=this._writableState&&this._writableState.destroyed;return o||s?(t?t(e):e&&(this._writableState?this._writableState.errorEmitted||(this._writableState.errorEmitted=!0,process.nextTick(a,this,e)):process.nextTick(a,this,e)),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?i._writableState?i._writableState.errorEmitted?process.nextTick(r,i):(i._writableState.errorEmitted=!0,process.nextTick(n,i,e)):process.nextTick(n,i,e):t?(process.nextTick(r,i),t(e)):process.nextTick(r,i)}),this)}function n(e,t){a(e,t),r(e)}function r(e){(!e._writableState||e._writableState.emitClose)&&(!e._readableState||e._readableState.emitClose)&&e.emit("close")}function i(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}function a(e,t){e.emit("error",t)}function o(e,t){var n=e._readableState,r=e._writableState;n&&n.autoDestroy||r&&r.autoDestroy?e.destroy(t):e.emit("error",t)}e.exports={destroy:t,undestroy:i,errorOrDestroy:o}},8610(e,t,n){"use strict";var r=n(94281).q.ERR_STREAM_PREMATURE_CLOSE;function i(e){var t=!1;return function(){if(!t){t=!0;for(var n=arguments.length,r=Array(n),i=0;i0,function(t){e||(e=t),t&&a.forEach(f),o||(a.forEach(f),i(e))})});return n.reduce(d)}e.exports=p},82457(e,t,n){"use strict";var r=n(94281).q.ERR_INVALID_OPT_VALUE;function i(e,t,n){return null!=e.highWaterMark?e.highWaterMark:t?e[n]:null}function a(e,t,n,a){var o=i(t,a,n);if(null!=o){if(!(isFinite(o)&&Math.floor(o)===o)||o<0){var s=a?n:"highWaterMark";throw new r(s,o)}return Math.floor(o)}return e.objectMode?16:16384}e.exports={getHighWaterMark:a}},22503(e,t,n){e.exports=n(17187).EventEmitter},61566(e,t){"use strict";t.__esModule=!0,t.default=void 0;var n=function(e){return"string"==typeof e?e:e?e.displayName||e.name||"Component":void 0};t.default=n},60375(e){"use strict";var t=Object.prototype.hasOwnProperty;function n(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function r(e,r){if(n(e,r))return!0;if("object"!=typeof e||null===e||"object"!=typeof r||null===r)return!1;var i=Object.keys(e),a=Object.keys(r);if(i.length!==a.length)return!1;for(var o=0;og,DE:()=>b,UY:()=>h,qC:()=>m,MT:()=>f});var s="function"==typeof Symbol&&Symbol.observable||"@@observable",u=function(){return Math.random().toString(36).substring(7).split("").join(".")},c={INIT:"@@redux/INIT"+u(),REPLACE:"@@redux/REPLACE"+u(),PROBE_UNKNOWN_ACTION:function(){return"@@redux/PROBE_UNKNOWN_ACTION"+u()}};function l(e){if("object"!=typeof e||null===e)return!1;for(var t=e;null!==Object.getPrototypeOf(t);)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t}function f(e,t,n){if("function"==typeof t&&"function"==typeof n||"function"==typeof n&&"function"==typeof arguments[3])throw Error(o(0));if("function"==typeof t&&void 0===n&&(n=t,t=void 0),void 0!==n){if("function"!=typeof n)throw Error(o(1));return n(f)(e,t)}if("function"!=typeof e)throw Error(o(2));var r,i=e,a=t,u=[],d=u,h=!1;function p(){d===u&&(d=u.slice())}function b(){if(h)throw Error(o(3));return a}function m(e){if("function"!=typeof e)throw Error(o(4));if(h)throw Error(o(5));var t=!0;return p(),d.push(e),function(){if(t){if(h)throw Error(o(6));t=!1,p();var n=d.indexOf(e);d.splice(n,1),u=null}}}function g(e){if(!l(e))throw Error(o(7));if(void 0===e.type)throw Error(o(8));if(h)throw Error(o(9));try{h=!0,a=i(a,e)}finally{h=!1}for(var t=u=d,n=0;n]?|>=?|\?=|[-+\/=])(?=\s)/,lookbehind:!0},"string-operator":{pattern:/(\s)&&?(?=\s)/,lookbehind:!0,alias:"keyword"},"token-operator":[{pattern:/(\w)(?:->?|=>|[~|{}])(?=\w)/,lookbehind:!0,alias:"punctuation"},{pattern:/[|{}]/,alias:"punctuation"}],punctuation:/[,.:()]/}}e.exports=t,t.displayName="abap",t.aliases=[]},68313(e){"use strict";function t(e){var t,n;n="(?:ALPHA|BIT|CHAR|CR|CRLF|CTL|DIGIT|DQUOTE|HEXDIG|HTAB|LF|LWSP|OCTET|SP|VCHAR|WSP)",(t=e).languages.abnf={comment:/;.*/,string:{pattern:/(?:%[is])?"[^"\n\r]*"/,greedy:!0,inside:{punctuation:/^%[is]/}},range:{pattern:/%(?:b[01]+-[01]+|d\d+-\d+|x[A-F\d]+-[A-F\d]+)/i,alias:"number"},terminal:{pattern:/%(?:b[01]+(?:\.[01]+)*|d\d+(?:\.\d+)*|x[A-F\d]+(?:\.[A-F\d]+)*)/i,alias:"number"},repetition:{pattern:/(^|[^\w-])(?:\d*\*\d*|\d+)/,lookbehind:!0,alias:"operator"},definition:{pattern:/(^[ \t]*)(?:[a-z][\w-]*|<[^<>\r\n]*>)(?=\s*=)/m,lookbehind:!0,alias:"keyword",inside:{punctuation:/<|>/}},"core-rule":{pattern:RegExp("(?:(^|[^<\\w-])"+n+"|<"+n+">)(?![\\w-])","i"),lookbehind:!0,alias:["rule","constant"],inside:{punctuation:/<|>/}},rule:{pattern:/(^|[^<\w-])[a-z][\w-]*|<[^<>\r\n]*>/i,lookbehind:!0,inside:{punctuation:/<|>/}},operator:/=\/?|\//,punctuation:/[()\[\]]/}}e.exports=t,t.displayName="abnf",t.aliases=[]},5199(e){"use strict";function t(e){e.languages.actionscript=e.languages.extend("javascript",{keyword:/\b(?:as|break|case|catch|class|const|default|delete|do|else|extends|finally|for|function|if|implements|import|in|instanceof|interface|internal|is|native|new|null|package|private|protected|public|return|super|switch|this|throw|try|typeof|use|var|void|while|with|dynamic|each|final|get|include|namespace|override|set|static)\b/,operator:/\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<>?>?|[!=]=?)=?|[~?@]/}),e.languages.actionscript["class-name"].alias="function",e.languages.markup&&e.languages.insertBefore("actionscript","string",{xml:{pattern:/(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/,lookbehind:!0,inside:e.languages.markup}})}e.exports=t,t.displayName="actionscript",t.aliases=[]},89693(e){"use strict";function t(e){e.languages.ada={comment:/--.*/,string:/"(?:""|[^"\r\f\n])*"/i,number:[{pattern:/\b\d(?:_?\d)*#[\dA-F](?:_?[\dA-F])*(?:\.[\dA-F](?:_?[\dA-F])*)?#(?:E[+-]?\d(?:_?\d)*)?/i},{pattern:/\b\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:E[+-]?\d(?:_?\d)*)?\b/i}],"attr-name":/\b'\w+/i,keyword:/\b(?:abort|abs|abstract|accept|access|aliased|all|and|array|at|begin|body|case|constant|declare|delay|delta|digits|do|else|new|return|elsif|end|entry|exception|exit|for|function|generic|goto|if|in|interface|is|limited|loop|mod|not|null|of|others|out|overriding|package|pragma|private|procedure|protected|raise|range|record|rem|renames|requeue|reverse|select|separate|some|subtype|synchronized|tagged|task|terminate|then|type|until|use|when|while|with|xor)\b/i,boolean:/\b(?:true|false)\b/i,operator:/<[=>]?|>=?|=>?|:=|\/=?|\*\*?|[&+-]/,punctuation:/\.\.?|[,;():]/,char:/'.'/,variable:/\b[a-z](?:\w)*\b/i}}e.exports=t,t.displayName="ada",t.aliases=[]},24001(e){"use strict";function t(e){var t;(t=e).languages.agda={comment:/\{-[\s\S]*?(?:-\}|$)|--.*/,string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},punctuation:/[(){}⦃⦄.;@]/,"class-name":{pattern:/((?:data|record) +)\S+/,lookbehind:!0},function:{pattern:/(^[ \t]*)(?!\s)[^:\r\n]+(?=:)/m,lookbehind:!0},operator:{pattern:/(^\s*|\s)(?:[=|:∀→λ\\?_]|->)(?=\s)/,lookbehind:!0},keyword:/\b(?:Set|abstract|constructor|data|eta-equality|field|forall|hiding|import|in|inductive|infix|infixl|infixr|instance|let|macro|module|mutual|no-eta-equality|open|overlap|pattern|postulate|primitive|private|public|quote|quoteContext|quoteGoal|quoteTerm|record|renaming|rewrite|syntax|tactic|unquote|unquoteDecl|unquoteDef|using|variable|where|with)\b/}}e.exports=t,t.displayName="agda",t.aliases=[]},18018(e){"use strict";function t(e){e.languages.al={comment:/\/\/.*|\/\*[\s\S]*?\*\//,string:{pattern:/'(?:''|[^'\r\n])*'(?!')|"(?:""|[^"\r\n])*"(?!")/,greedy:!0},function:{pattern:/(\b(?:event|procedure|trigger)\s+|(?:^|[^.])\.\s*)[a-z_]\w*(?=\s*\()/i,lookbehind:!0},keyword:[/\b(?:array|asserterror|begin|break|case|do|downto|else|end|event|exit|for|foreach|function|if|implements|in|indataset|interface|internal|local|of|procedure|program|protected|repeat|runonclient|securityfiltering|suppressdispose|temporary|then|to|trigger|until|var|while|with|withevents)\b/i,/\b(?:action|actions|addafter|addbefore|addfirst|addlast|area|assembly|chartpart|codeunit|column|controladdin|cuegroup|customizes|dataitem|dataset|dotnet|elements|enum|enumextension|extends|field|fieldattribute|fieldelement|fieldgroup|fieldgroups|fields|filter|fixed|grid|group|key|keys|label|labels|layout|modify|moveafter|movebefore|movefirst|movelast|page|pagecustomization|pageextension|part|profile|query|repeater|report|requestpage|schema|separator|systempart|table|tableelement|tableextension|textattribute|textelement|type|usercontrol|value|xmlport)\b/i],number:/\b(?:0x[\da-f]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?)(?:F|U(?:LL?)?|LL?)?\b/i,boolean:/\b(?:false|true)\b/i,variable:/\b(?:Curr(?:FieldNo|Page|Report)|RequestOptionsPage|x?Rec)\b/,"class-name":/\b(?:automation|biginteger|bigtext|blob|boolean|byte|char|clienttype|code|completiontriggererrorlevel|connectiontype|database|dataclassification|datascope|date|dateformula|datetime|decimal|defaultlayout|dialog|dictionary|dotnetassembly|dotnettypedeclaration|duration|errorinfo|errortype|executioncontext|executionmode|fieldclass|fieldref|fieldtype|file|filterpagebuilder|guid|httpclient|httpcontent|httpheaders|httprequestmessage|httpresponsemessage|instream|integer|joker|jsonarray|jsonobject|jsontoken|jsonvalue|keyref|list|moduledependencyinfo|moduleinfo|none|notification|notificationscope|objecttype|option|outstream|pageresult|record|recordid|recordref|reportformat|securityfilter|sessionsettings|tableconnectiontype|tablefilter|testaction|testfield|testfilterfield|testpage|testpermissions|testrequestpage|text|textbuilder|textconst|textencoding|time|transactionmodel|transactiontype|variant|verbosity|version|view|views|webserviceactioncontext|webserviceactionresultcode|xmlattribute|xmlattributecollection|xmlcdata|xmlcomment|xmldeclaration|xmldocument|xmldocumenttype|xmlelement|xmlnamespacemanager|xmlnametable|xmlnode|xmlnodelist|xmlprocessinginstruction|xmlreadoptions|xmltext|xmlwriteoptions)\b/i,operator:/\.\.|:[=:]|[-+*/]=?|<>|[<>]=?|=|\b(?:and|div|mod|not|or|xor)\b/i,punctuation:/[()\[\]{}:.;,]/}}e.exports=t,t.displayName="al",t.aliases=[]},36363(e){"use strict";function t(e){e.languages.antlr4={comment:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,string:{pattern:/'(?:\\.|[^\\'\r\n])*'/,greedy:!0},"character-class":{pattern:/\[(?:\\.|[^\\\]\r\n])*\]/,greedy:!0,alias:"regex",inside:{range:{pattern:/([^[]|(?:^|[^\\])(?:\\\\)*\\\[)-(?!\])/,lookbehind:!0,alias:"punctuation"},escape:/\\(?:u(?:[a-fA-F\d]{4}|\{[a-fA-F\d]+\})|[pP]\{[=\w-]+\}|[^\r\nupP])/,punctuation:/[\[\]]/}},action:{pattern:/\{(?:[^{}]|\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})*\}/,greedy:!0,inside:{content:{pattern:/(\{)[\s\S]+(?=\})/,lookbehind:!0},punctuation:/[{}]/}},command:{pattern:/(->\s*(?!\s))(?:\s*(?:,\s*)?\b[a-z]\w*(?:\s*\([^()\r\n]*\))?)+(?=\s*;)/i,lookbehind:!0,inside:{function:/\b\w+(?=\s*(?:[,(]|$))/,punctuation:/[,()]/}},annotation:{pattern:/@\w+(?:::\w+)*/,alias:"keyword"},label:{pattern:/#[ \t]*\w+/,alias:"punctuation"},keyword:/\b(?:catch|channels|finally|fragment|grammar|import|lexer|locals|mode|options|parser|returns|throws|tokens)\b/,definition:[{pattern:/\b[a-z]\w*(?=\s*:)/,alias:["rule","class-name"]},{pattern:/\b[A-Z]\w*(?=\s*:)/,alias:["token","constant"]}],constant:/\b[A-Z][A-Z_]*\b/,operator:/\.\.|->|[|~]|[*+?]\??/,punctuation:/[;:()=]/},e.languages.g4=e.languages.antlr4}e.exports=t,t.displayName="antlr4",t.aliases=["g4"]},35281(e){"use strict";function t(e){e.languages.apacheconf={comment:/#.*/,"directive-inline":{pattern:/(^[\t ]*)\b(?:AcceptFilter|AcceptPathInfo|AccessFileName|Action|Add(?:Alt|AltByEncoding|AltByType|Charset|DefaultCharset|Description|Encoding|Handler|Icon|IconByEncoding|IconByType|InputFilter|Language|ModuleInfo|OutputFilter|OutputFilterByType|Type)|Alias|AliasMatch|Allow(?:CONNECT|EncodedSlashes|Methods|Override|OverrideList)?|Anonymous(?:_LogEmail|_MustGiveEmail|_NoUserID|_VerifyEmail)?|AsyncRequestWorkerFactor|Auth(?:BasicAuthoritative|BasicFake|BasicProvider|BasicUseDigestAlgorithm|DBDUserPWQuery|DBDUserRealmQuery|DBMGroupFile|DBMType|DBMUserFile|Digest(?:Algorithm|Domain|NonceLifetime|Provider|Qop|ShmemSize)|Form(?:Authoritative|Body|DisableNoStore|FakeBasicAuth|Location|LoginRequiredLocation|LoginSuccessLocation|LogoutLocation|Method|Mimetype|Password|Provider|SitePassphrase|Size|Username)|GroupFile|LDAP(?:AuthorizePrefix|BindAuthoritative|BindDN|BindPassword|CharsetConfig|CompareAsUser|CompareDNOnServer|DereferenceAliases|GroupAttribute|GroupAttributeIsDN|InitialBindAsUser|InitialBindPattern|MaxSubGroupDepth|RemoteUserAttribute|RemoteUserIsDN|SearchAsUser|SubGroupAttribute|SubGroupClass|Url)|Merging|Name|Type|UserFile|nCache(?:Context|Enable|ProvideFor|SOCache|Timeout)|nzFcgiCheckAuthnProvider|nzFcgiDefineProvider|zDBDLoginToReferer|zDBDQuery|zDBDRedirectQuery|zDBMType|zSendForbiddenOnFailure)|BalancerGrowth|BalancerInherit|BalancerMember|BalancerPersist|BrowserMatch|BrowserMatchNoCase|BufferSize|BufferedLogs|CGIDScriptTimeout|CGIMapExtension|Cache(?:DefaultExpire|DetailHeader|DirLength|DirLevels|Disable|Enable|File|Header|IgnoreCacheControl|IgnoreHeaders|IgnoreNoLastMod|IgnoreQueryString|IgnoreURLSessionIdentifiers|KeyBaseURL|LastModifiedFactor|Lock|LockMaxAge|LockPath|MaxExpire|MaxFileSize|MinExpire|MinFileSize|NegotiatedDocs|QuickHandler|ReadSize|ReadTime|Root|Socache(?:MaxSize|MaxTime|MinTime|ReadSize|ReadTime)?|StaleOnError|StoreExpired|StoreNoStore|StorePrivate)|CharsetDefault|CharsetOptions|CharsetSourceEnc|CheckCaseOnly|CheckSpelling|ChrootDir|ContentDigest|CookieDomain|CookieExpires|CookieName|CookieStyle|CookieTracking|CoreDumpDirectory|CustomLog|DBDExptime|DBDInitSQL|DBDKeep|DBDMax|DBDMin|DBDParams|DBDPersist|DBDPrepareSQL|DBDriver|DTracePrivileges|Dav|DavDepthInfinity|DavGenericLockDB|DavLockDB|DavMinTimeout|DefaultIcon|DefaultLanguage|DefaultRuntimeDir|DefaultType|Define|Deflate(?:BufferSize|CompressionLevel|FilterNote|InflateLimitRequestBody|InflateRatio(?:Burst|Limit)|MemLevel|WindowSize)|Deny|DirectoryCheckHandler|DirectoryIndex|DirectoryIndexRedirect|DirectorySlash|DocumentRoot|DumpIOInput|DumpIOOutput|EnableExceptionHook|EnableMMAP|EnableSendfile|Error|ErrorDocument|ErrorLog|ErrorLogFormat|Example|ExpiresActive|ExpiresByType|ExpiresDefault|ExtFilterDefine|ExtFilterOptions|ExtendedStatus|FallbackResource|FileETag|FilterChain|FilterDeclare|FilterProtocol|FilterProvider|FilterTrace|ForceLanguagePriority|ForceType|ForensicLog|GprofDir|GracefulShutdownTimeout|Group|Header|HeaderName|Heartbeat(?:Address|Listen|MaxServers|Storage)|HostnameLookups|ISAPI(?:AppendLogToErrors|AppendLogToQuery|CacheFile|FakeAsync|LogNotSupported|ReadAheadBuffer)|IdentityCheck|IdentityCheckTimeout|ImapBase|ImapDefault|ImapMenu|Include|IncludeOptional|Index(?:HeadInsert|Ignore|IgnoreReset|Options|OrderDefault|StyleSheet)|InputSed|KeepAlive|KeepAliveTimeout|KeptBodySize|LDAP(?:CacheEntries|CacheTTL|ConnectionPoolTTL|ConnectionTimeout|LibraryDebug|OpCacheEntries|OpCacheTTL|ReferralHopLimit|Referrals|Retries|RetryDelay|SharedCacheFile|SharedCacheSize|Timeout|TrustedClientCert|TrustedGlobalCert|TrustedMode|VerifyServerCert)|LanguagePriority|Limit(?:InternalRecursion|Request(?:Body|FieldSize|Fields|Line)|XMLRequestBody)|Listen|ListenBackLog|LoadFile|LoadModule|LogFormat|LogLevel|LogMessage|LuaAuthzProvider|LuaCodeCache|Lua(?:Hook(?:AccessChecker|AuthChecker|CheckUserID|Fixups|InsertFilter|Log|MapToStorage|TranslateName|TypeChecker)|Inherit|InputFilter|MapHandler|OutputFilter|PackageCPath|PackagePath|QuickHandler|Root|Scope)|MMapFile|Max(?:ConnectionsPerChild|KeepAliveRequests|MemFree|RangeOverlaps|RangeReversals|Ranges|RequestWorkers|SpareServers|SpareThreads|Threads)|MergeTrailers|MetaDir|MetaFiles|MetaSuffix|MimeMagicFile|MinSpareServers|MinSpareThreads|ModMimeUsePathInfo|ModemStandard|MultiviewsMatch|Mutex|NWSSLTrustedCerts|NWSSLUpgradeable|NameVirtualHost|NoProxy|Options|Order|OutputSed|PassEnv|PidFile|PrivilegesMode|Protocol|ProtocolEcho|Proxy(?:AddHeaders|BadHeader|Block|Domain|ErrorOverride|ExpressDBMFile|ExpressDBMType|ExpressEnable|FtpDirCharset|FtpEscapeWildcards|FtpListOnWildcard|HTML(?:BufSize|CharsetOut|DocType|Enable|Events|Extended|Fixups|Interp|Links|Meta|StripComments|URLMap)|IOBufferSize|MaxForwards|Pass(?:Inherit|InterpolateEnv|Match|Reverse|ReverseCookieDomain|ReverseCookiePath)?|PreserveHost|ReceiveBufferSize|Remote|RemoteMatch|Requests|SCGIInternalRedirect|SCGISendfile|Set|SourceAddress|Status|Timeout|Via)|RLimitCPU|RLimitMEM|RLimitNPROC|ReadmeName|ReceiveBufferSize|Redirect|RedirectMatch|RedirectPermanent|RedirectTemp|ReflectorHeader|RemoteIP(?:Header|InternalProxy|InternalProxyList|ProxiesHeader|TrustedProxy|TrustedProxyList)|RemoveCharset|RemoveEncoding|RemoveHandler|RemoveInputFilter|RemoveLanguage|RemoveOutputFilter|RemoveType|RequestHeader|RequestReadTimeout|Require|Rewrite(?:Base|Cond|Engine|Map|Options|Rule)|SSIETag|SSIEndTag|SSIErrorMsg|SSILastModified|SSILegacyExprParser|SSIStartTag|SSITimeFormat|SSIUndefinedEcho|SSL(?:CACertificateFile|CACertificatePath|CADNRequestFile|CADNRequestPath|CARevocationCheck|CARevocationFile|CARevocationPath|CertificateChainFile|CertificateFile|CertificateKeyFile|CipherSuite|Compression|CryptoDevice|Engine|FIPS|HonorCipherOrder|InsecureRenegotiation|OCSP(?:DefaultResponder|Enable|OverrideResponder|ResponderTimeout|ResponseMaxAge|ResponseTimeSkew|UseRequestNonce)|OpenSSLConfCmd|Options|PassPhraseDialog|Protocol|Proxy(?:CACertificateFile|CACertificatePath|CARevocation(?:Check|File|Path)|CheckPeer(?:CN|Expire|Name)|CipherSuite|Engine|MachineCertificate(?:ChainFile|File|Path)|Protocol|Verify|VerifyDepth)|RandomSeed|RenegBufferSize|Require|RequireSSL|SRPUnknownUserSeed|SRPVerifierFile|Session(?:Cache|CacheTimeout|TicketKeyFile|Tickets)|Stapling(?:Cache|ErrorCacheTimeout|FakeTryLater|ForceURL|ResponderTimeout|ResponseMaxAge|ResponseTimeSkew|ReturnResponderErrors|StandardCacheTimeout)|StrictSNIVHostCheck|UseStapling|UserName|VerifyClient|VerifyDepth)|Satisfy|ScoreBoardFile|Script(?:Alias|AliasMatch|InterpreterSource|Log|LogBuffer|LogLength|Sock)?|SecureListen|SeeRequestTail|SendBufferSize|Server(?:Admin|Alias|Limit|Name|Path|Root|Signature|Tokens)|Session(?:Cookie(?:Name|Name2|Remove)|Crypto(?:Cipher|Driver|Passphrase|PassphraseFile)|DBD(?:CookieName|CookieName2|CookieRemove|DeleteLabel|InsertLabel|PerUser|SelectLabel|UpdateLabel)|Env|Exclude|Header|Include|MaxAge)?|SetEnv|SetEnvIf|SetEnvIfExpr|SetEnvIfNoCase|SetHandler|SetInputFilter|SetOutputFilter|StartServers|StartThreads|Substitute|Suexec|SuexecUserGroup|ThreadLimit|ThreadStackSize|ThreadsPerChild|TimeOut|TraceEnable|TransferLog|TypesConfig|UnDefine|UndefMacro|UnsetEnv|Use|UseCanonicalName|UseCanonicalPhysicalPort|User|UserDir|VHostCGIMode|VHostCGIPrivs|VHostGroup|VHostPrivs|VHostSecure|VHostUser|Virtual(?:DocumentRoot|ScriptAlias)(?:IP)?|WatchdogInterval|XBitHack|xml2EncAlias|xml2EncDefault|xml2StartParse)\b/im,lookbehind:!0,alias:"property"},"directive-block":{pattern:/<\/?\b(?:Auth[nz]ProviderAlias|Directory|DirectoryMatch|Else|ElseIf|Files|FilesMatch|If|IfDefine|IfModule|IfVersion|Limit|LimitExcept|Location|LocationMatch|Macro|Proxy|Require(?:All|Any|None)|VirtualHost)\b.*>/i,inside:{"directive-block":{pattern:/^<\/?\w+/,inside:{punctuation:/^<\/?/},alias:"tag"},"directive-block-parameter":{pattern:/.*[^>]/,inside:{punctuation:/:/,string:{pattern:/("|').*\1/,inside:{variable:/[$%]\{?(?:\w\.?[-+:]?)+\}?/}}},alias:"attr-value"},punctuation:/>/},alias:"tag"},"directive-flags":{pattern:/\[(?:[\w=],?)+\]/,alias:"keyword"},string:{pattern:/("|').*\1/,inside:{variable:/[$%]\{?(?:\w\.?[-+:]?)+\}?/}},variable:/[$%]\{?(?:\w\.?[-+:]?)+\}?/,regex:/\^?.*\$|\^.*\$?/}}e.exports=t,t.displayName="apacheconf",t.aliases=[]},10433(e,t,n){"use strict";var r=n(11114);function i(e){e.register(r),function(e){var t=/\b(?:abstract|activate|and|any|array|as|asc|autonomous|begin|bigdecimal|blob|boolean|break|bulk|by|byte|case|cast|catch|char|class|collect|commit|const|continue|currency|date|datetime|decimal|default|delete|desc|do|double|else|end|enum|exception|exit|export|extends|final|finally|float|for|from|global|goto|group|having|hint|if|implements|import|in|inner|insert|instanceof|int|integer|interface|into|join|like|limit|list|long|loop|map|merge|new|not|null|nulls|number|object|of|on|or|outer|override|package|parallel|pragma|private|protected|public|retrieve|return|rollback|select|set|short|sObject|sort|static|string|super|switch|synchronized|system|testmethod|then|this|throw|time|transaction|transient|trigger|try|undelete|update|upsert|using|virtual|void|webservice|when|where|while|get(?=\s*[{};])|(?:after|before)(?=\s+[a-z])|(?:inherited|with|without)\s+sharing)\b/i,n=/\b(?:(?=[a-z_]\w*\s*[<\[])|(?!))[A-Z_]\w*(?:\s*\.\s*[A-Z_]\w*)*\b(?:\s*(?:\[\s*\]|<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>))*/.source.replace(//g,function(){return t.source});function r(e){return RegExp(e.replace(//g,function(){return n}),"i")}var i={keyword:t,punctuation:/[()\[\]{};,:.<>]/};e.languages.apex={comment:e.languages.clike.comment,string:e.languages.clike.string,sql:{pattern:/((?:[=,({:]|\breturn)\s*)\[[^\[\]]*\]/i,lookbehind:!0,greedy:!0,alias:"language-sql",inside:e.languages.sql},annotation:{pattern:/@\w+\b/,alias:"punctuation"},"class-name":[{pattern:r(/(\b(?:class|enum|extends|implements|instanceof|interface|new|trigger\s+\w+\s+on)\s+)/.source),lookbehind:!0,inside:i},{pattern:r(/(\(\s*)(?=\s*\)\s*[\w(])/.source),lookbehind:!0,inside:i},{pattern:r(/(?=\s*\w+\s*[;=,(){:])/.source),inside:i}],trigger:{pattern:/(\btrigger\s+)\w+\b/i,lookbehind:!0,alias:"class-name"},keyword:t,function:/\b[a-z_]\w*(?=\s*\()/i,boolean:/\b(?:false|true)\b/i,number:/(?:\B\.\d+|\b\d+(?:\.\d+|L)?)\b/i,operator:/[!=](?:==?)?|\?\.?|&&|\|\||--|\+\+|[-+*/^&|]=?|:|<{1,3}=?/,punctuation:/[()\[\]{};,.]/}}(e)}e.exports=i,i.displayName="apex",i.aliases=[]},84039(e){"use strict";function t(e){e.languages.apl={comment:/(?:⍝|#[! ]).*$/m,string:{pattern:/'(?:[^'\r\n]|'')*'/,greedy:!0},number:/¯?(?:\d*\.?\b\d+(?:e[+¯]?\d+)?|¯|∞)(?:j¯?(?:(?:\d+(?:\.\d+)?|\.\d+)(?:e[+¯]?\d+)?|¯|∞))?/i,statement:/:[A-Z][a-z][A-Za-z]*\b/,"system-function":{pattern:/⎕[A-Z]+/i,alias:"function"},constant:/[⍬⌾#⎕⍞]/,function:/[-+×÷⌈⌊∣|⍳⍸?*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⊆⊇⌷⍋⍒⊤⊥⍕⍎⊣⊢⍁⍂≈⍯↗¤→]/,"monadic-operator":{pattern:/[\\\/⌿⍀¨⍨⌶&∥]/,alias:"operator"},"dyadic-operator":{pattern:/[.⍣⍠⍤∘⌸@⌺⍥]/,alias:"operator"},assignment:{pattern:/←/,alias:"keyword"},punctuation:/[\[;\]()◇⋄]/,dfn:{pattern:/[{}⍺⍵⍶⍹∇⍫:]/,alias:"builtin"}}}e.exports=t,t.displayName="apl",t.aliases=[]},71336(e){"use strict";function t(e){e.languages.applescript={comment:[/\(\*(?:\(\*(?:[^*]|\*(?!\)))*\*\)|(?!\(\*)[\s\S])*?\*\)/,/--.+/,/#.+/],string:/"(?:\\.|[^"\\\r\n])*"/,number:/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e-?\d+)?\b/i,operator:[/[&=≠≤≥*+\-\/÷^]|[<>]=?/,/\b(?:(?:start|begin|end)s? with|(?:(?:does not|doesn't) contain|contains?)|(?:is|isn't|is not) (?:in|contained by)|(?:(?:is|isn't|is not) )?(?:greater|less) than(?: or equal)?(?: to)?|(?:(?:does not|doesn't) come|comes) (?:before|after)|(?:is|isn't|is not) equal(?: to)?|(?:(?:does not|doesn't) equal|equals|equal to|isn't|is not)|(?:a )?(?:ref(?: to)?|reference to)|(?:and|or|div|mod|as|not))\b/],keyword:/\b(?:about|above|after|against|apart from|around|aside from|at|back|before|beginning|behind|below|beneath|beside|between|but|by|considering|continue|copy|does|eighth|else|end|equal|error|every|exit|false|fifth|first|for|fourth|from|front|get|given|global|if|ignoring|in|instead of|into|is|it|its|last|local|me|middle|my|ninth|of|on|onto|out of|over|prop|property|put|repeat|return|returning|second|set|seventh|since|sixth|some|tell|tenth|that|the|then|third|through|thru|timeout|times|to|transaction|true|try|until|where|while|whose|with|without)\b/,class:{pattern:/\b(?:alias|application|boolean|class|constant|date|file|integer|list|number|POSIX file|real|record|reference|RGB color|script|text|centimetres|centimeters|feet|inches|kilometres|kilometers|metres|meters|miles|yards|square feet|square kilometres|square kilometers|square metres|square meters|square miles|square yards|cubic centimetres|cubic centimeters|cubic feet|cubic inches|cubic metres|cubic meters|cubic yards|gallons|litres|liters|quarts|grams|kilograms|ounces|pounds|degrees Celsius|degrees Fahrenheit|degrees Kelvin)\b/,alias:"builtin"},punctuation:/[{}():,¬«»《》]/}}e.exports=t,t.displayName="applescript",t.aliases=[]},4481(e){"use strict";function t(e){e.languages.aql={comment:/\/\/.*|\/\*[\s\S]*?\*\//,property:{pattern:/([{,]\s*)(?:(?!\d)\w+|(["'´`])(?:(?!\2)[^\\\r\n]|\\.)*\2)(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(["'´`])(?:(?!\1)[^\\\r\n]|\\.)*\1/,greedy:!0},variable:/@@?\w+/,keyword:[{pattern:/(\bWITH\s+)COUNT(?=\s+INTO\b)/i,lookbehind:!0},/\b(?:AGGREGATE|ALL|AND|ANY|ASC|COLLECT|DESC|DISTINCT|FILTER|FOR|GRAPH|IN|INBOUND|INSERT|INTO|K_PATHS|K_SHORTEST_PATHS|LET|LIKE|LIMIT|NONE|NOT|NULL|OR|OUTBOUND|REMOVE|REPLACE|RETURN|SHORTEST_PATH|SORT|UPDATE|UPSERT|WINDOW|WITH)\b/i,{pattern:/(^|[^\w.[])(?:KEEP|PRUNE|SEARCH|TO)\b/i,lookbehind:!0},{pattern:/(^|[^\w.[])(?:CURRENT|NEW|OLD)\b/,lookbehind:!0},{pattern:/\bOPTIONS(?=\s*\{)/i}],function:/\b(?!\d)\w+(?=\s*\()/,boolean:/\b(?:true|false)\b/i,range:{pattern:/\.\./,alias:"operator"},number:[/\b0b[01]+/i,/\b0x[0-9a-f]+/i,/(?:\B\.\d+|\b(?:0|[1-9]\d*)(?:\.\d+)?)(?:e[+-]?\d+)?/i],operator:/\*{2,}|[=!]~|[!=<>]=?|&&|\|\||[-+*/%]/,punctuation:/::|[?.:,;()[\]{}]/}}e.exports=t,t.displayName="aql",t.aliases=[]},2159(e,t,n){"use strict";var r=n(80096);function i(e){e.register(r),e.languages.arduino=e.languages.extend("cpp",{constant:/\b(?:DIGITAL_MESSAGE|FIRMATA_STRING|ANALOG_MESSAGE|REPORT_DIGITAL|REPORT_ANALOG|INPUT_PULLUP|SET_PIN_MODE|INTERNAL2V56|SYSTEM_RESET|LED_BUILTIN|INTERNAL1V1|SYSEX_START|INTERNAL|EXTERNAL|DEFAULT|OUTPUT|INPUT|HIGH|LOW)\b/,keyword:/\b(?:setup|if|else|while|do|for|return|in|instanceof|default|function|loop|goto|switch|case|new|try|throw|catch|finally|null|break|continue|boolean|bool|void|byte|word|string|String|array|int|long|integer|double)\b/,builtin:/\b(?:KeyboardController|MouseController|SoftwareSerial|EthernetServer|EthernetClient|LiquidCrystal|LiquidCrystal_I2C|RobotControl|GSMVoiceCall|EthernetUDP|EsploraTFT|HttpClient|RobotMotor|WiFiClient|GSMScanner|FileSystem|Scheduler|GSMServer|YunClient|YunServer|IPAddress|GSMClient|GSMModem|Keyboard|Ethernet|Console|GSMBand|Esplora|Stepper|Process|WiFiUDP|GSM_SMS|Mailbox|USBHost|Firmata|PImage|Client|Server|GSMPIN|FileIO|Bridge|Serial|EEPROM|Stream|Mouse|Audio|Servo|File|Task|GPRS|WiFi|Wire|TFT|GSM|SPI|SD|runShellCommandAsynchronously|analogWriteResolution|retrieveCallingNumber|printFirmwareVersion|analogReadResolution|sendDigitalPortPair|noListenOnLocalhost|readJoystickButton|setFirmwareVersion|readJoystickSwitch|scrollDisplayRight|getVoiceCallStatus|scrollDisplayLeft|writeMicroseconds|delayMicroseconds|beginTransmission|getSignalStrength|runAsynchronously|getAsynchronously|listenOnLocalhost|getCurrentCarrier|readAccelerometer|messageAvailable|sendDigitalPorts|lineFollowConfig|countryNameWrite|runShellCommand|readStringUntil|rewindDirectory|readTemperature|setClockDivider|readLightSensor|endTransmission|analogReference|detachInterrupt|countryNameRead|attachInterrupt|encryptionType|readBytesUntil|robotNameWrite|readMicrophone|robotNameRead|cityNameWrite|userNameWrite|readJoystickY|readJoystickX|mouseReleased|openNextFile|scanNetworks|noInterrupts|digitalWrite|beginSpeaker|mousePressed|isActionDone|mouseDragged|displayLogos|noAutoscroll|addParameter|remoteNumber|getModifiers|keyboardRead|userNameRead|waitContinue|processInput|parseCommand|printVersion|readNetworks|writeMessage|blinkVersion|cityNameRead|readMessage|setDataMode|parsePacket|isListening|setBitOrder|beginPacket|isDirectory|motorsWrite|drawCompass|digitalRead|clearScreen|serialEvent|rightToLeft|setTextSize|leftToRight|requestFrom|keyReleased|compassRead|analogWrite|interrupts|WiFiServer|disconnect|playMelody|parseFloat|autoscroll|getPINUsed|setPINUsed|setTimeout|sendAnalog|readSlider|analogRead|beginWrite|createChar|motorsStop|keyPressed|tempoWrite|readButton|subnetMask|debugPrint|macAddress|writeGreen|randomSeed|attachGPRS|readString|sendString|remotePort|releaseAll|mouseMoved|background|getXChange|getYChange|answerCall|getResult|voiceCall|endPacket|constrain|getSocket|writeJSON|getButton|available|connected|findUntil|readBytes|exitValue|readGreen|writeBlue|startLoop|isPressed|sendSysex|pauseMode|gatewayIP|setCursor|getOemKey|tuneWrite|noDisplay|loadImage|switchPIN|onRequest|onReceive|changePIN|playFile|noBuffer|parseInt|overflow|checkPIN|knobRead|beginTFT|bitClear|updateIR|bitWrite|position|writeRGB|highByte|writeRed|setSpeed|readBlue|noStroke|remoteIP|transfer|shutdown|hangCall|beginSMS|endWrite|attached|maintain|noCursor|checkReg|checkPUK|shiftOut|isValid|shiftIn|pulseIn|connect|println|localIP|pinMode|getIMEI|display|noBlink|process|getBand|running|beginSD|drawBMP|lowByte|setBand|release|bitRead|prepare|pointTo|readRed|setMode|noFill|remove|listen|stroke|detach|attach|noTone|exists|buffer|height|bitSet|circle|config|cursor|random|IRread|setDNS|endSMS|getKey|micros|millis|begin|print|write|ready|flush|width|isPIN|blink|clear|press|mkdir|rmdir|close|point|yield|image|BSSID|click|delay|read|text|move|peek|beep|rect|line|open|seek|fill|size|turn|stop|home|find|step|tone|sqrt|RSSI|SSID|end|bit|tan|cos|sin|pow|map|abs|max|min|get|run|put)\b/})}e.exports=i,i.displayName="arduino",i.aliases=[]},60274(e){"use strict";function t(e){e.languages.arff={comment:/%.*/,string:{pattern:/(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},keyword:/@(?:attribute|data|end|relation)\b/i,number:/\b\d+(?:\.\d+)?\b/,punctuation:/[{},]/}}e.exports=t,t.displayName="arff",t.aliases=[]},18738(e){"use strict";function t(e){!function(e){var t={pattern:/(^[ \t]*)\[(?!\[)(?:(["'$`])(?:(?!\2)[^\\]|\\.)*\2|\[(?:[^\[\]\\]|\\.)*\]|[^\[\]\\"'$`]|\\.)*\]/m,lookbehind:!0,inside:{quoted:{pattern:/([$`])(?:(?!\1)[^\\]|\\.)*\1/,inside:{punctuation:/^[$`]|[$`]$/}},interpreted:{pattern:/'(?:[^'\\]|\\.)*'/,inside:{punctuation:/^'|'$/}},string:/"(?:[^"\\]|\\.)*"/,variable:/\w+(?==)/,punctuation:/^\[|\]$|,/,operator:/=/,"attr-value":/(?!^\s+$).+/}},n=e.languages.asciidoc={"comment-block":{pattern:/^(\/{4,})(?:\r?\n|\r)(?:[\s\S]*(?:\r?\n|\r))??\1/m,alias:"comment"},table:{pattern:/^\|={3,}(?:(?:\r?\n|\r(?!\n)).*)*?(?:\r?\n|\r)\|={3,}$/m,inside:{specifiers:{pattern:/(?!\|)(?:(?:(?:\d+(?:\.\d+)?|\.\d+)[+*])?(?:[<^>](?:\.[<^>])?|\.[<^>])?[a-z]*)(?=\|)/,alias:"attr-value"},punctuation:{pattern:/(^|[^\\])[|!]=*/,lookbehind:!0}}},"passthrough-block":{pattern:/^(\+{4,})(?:\r?\n|\r)(?:[\s\S]*(?:\r?\n|\r))??\1$/m,inside:{punctuation:/^\++|\++$/}},"literal-block":{pattern:/^(-{4,}|\.{4,})(?:\r?\n|\r)(?:[\s\S]*(?:\r?\n|\r))??\1$/m,inside:{punctuation:/^(?:-+|\.+)|(?:-+|\.+)$/}},"other-block":{pattern:/^(--|\*{4,}|_{4,}|={4,})(?:\r?\n|\r)(?:[\s\S]*(?:\r?\n|\r))??\1$/m,inside:{punctuation:/^(?:-+|\*+|_+|=+)|(?:-+|\*+|_+|=+)$/}},"list-punctuation":{pattern:/(^[ \t]*)(?:-|\*{1,5}|\.{1,5}|(?:[a-z]|\d+)\.|[xvi]+\))(?= )/im,lookbehind:!0,alias:"punctuation"},"list-label":{pattern:/(^[ \t]*)[a-z\d].+(?::{2,4}|;;)(?=\s)/im,lookbehind:!0,alias:"symbol"},"indented-block":{pattern:/((\r?\n|\r)\2)([ \t]+)\S.*(?:(?:\r?\n|\r)\3.+)*(?=\2{2}|$)/,lookbehind:!0},comment:/^\/\/.*/m,title:{pattern:/^.+(?:\r?\n|\r)(?:={3,}|-{3,}|~{3,}|\^{3,}|\+{3,})$|^={1,5} .+|^\.(?![\s.]).*/m,alias:"important",inside:{punctuation:/^(?:\.|=+)|(?:=+|-+|~+|\^+|\++)$/}},"attribute-entry":{pattern:/^:[^:\r\n]+:(?: .*?(?: \+(?:\r?\n|\r).*?)*)?$/m,alias:"tag"},attributes:t,hr:{pattern:/^'{3,}$/m,alias:"punctuation"},"page-break":{pattern:/^<{3,}$/m,alias:"punctuation"},admonition:{pattern:/^(?:TIP|NOTE|IMPORTANT|WARNING|CAUTION):/m,alias:"keyword"},callout:[{pattern:/(^[ \t]*)/m,lookbehind:!0,alias:"symbol"},{pattern:/<\d+>/,alias:"symbol"}],macro:{pattern:/\b[a-z\d][a-z\d-]*::?(?:[^\s\[\]]*\[(?:[^\]\\"']|(["'])(?:(?!\1)[^\\]|\\.)*\1|\\.)*\])/,inside:{function:/^[a-z\d-]+(?=:)/,punctuation:/^::?/,attributes:{pattern:/(?:\[(?:[^\]\\"']|(["'])(?:(?!\1)[^\\]|\\.)*\1|\\.)*\])/,inside:t.inside}}},inline:{pattern:/(^|[^\\])(?:(?:\B\[(?:[^\]\\"']|(["'])(?:(?!\2)[^\\]|\\.)*\2|\\.)*\])?(?:\b_(?!\s)(?: _|[^_\\\r\n]|\\.)+(?:(?:\r?\n|\r)(?: _|[^_\\\r\n]|\\.)+)*_\b|\B``(?!\s).+?(?:(?:\r?\n|\r).+?)*''\B|\B`(?!\s)(?:[^`'\s]|\s+\S)+['`]\B|\B(['*+#])(?!\s)(?: \3|(?!\3)[^\\\r\n]|\\.)+(?:(?:\r?\n|\r)(?: \3|(?!\3)[^\\\r\n]|\\.)+)*\3\B)|(?:\[(?:[^\]\\"']|(["'])(?:(?!\4)[^\\]|\\.)*\4|\\.)*\])?(?:(__|\*\*|\+\+\+?|##|\$\$|[~^]).+?(?:(?:\r?\n|\r).+?)*\5|\{[^}\r\n]+\}|\[\[\[?.+?(?:(?:\r?\n|\r).+?)*\]?\]\]|<<.+?(?:(?:\r?\n|\r).+?)*>>|\(\(\(?.+?(?:(?:\r?\n|\r).+?)*\)?\)\)))/m,lookbehind:!0,inside:{attributes:t,url:{pattern:/^(?:\[\[\[?.+?\]?\]\]|<<.+?>>)$/,inside:{punctuation:/^(?:\[\[\[?|<<)|(?:\]\]\]?|>>)$/}},"attribute-ref":{pattern:/^\{.+\}$/,inside:{variable:{pattern:/(^\{)[a-z\d,+_-]+/,lookbehind:!0},operator:/^[=?!#%@$]|!(?=[:}])/,punctuation:/^\{|\}$|::?/}},italic:{pattern:/^(['_])[\s\S]+\1$/,inside:{punctuation:/^(?:''?|__?)|(?:''?|__?)$/}},bold:{pattern:/^\*[\s\S]+\*$/,inside:{punctuation:/^\*\*?|\*\*?$/}},punctuation:/^(?:``?|\+{1,3}|##?|\$\$|[~^]|\(\(\(?)|(?:''?|\+{1,3}|##?|\$\$|[~^`]|\)?\)\))$/}},replacement:{pattern:/\((?:C|TM|R)\)/,alias:"builtin"},entity:/&#?[\da-z]{1,8};/i,"line-continuation":{pattern:/(^| )\+$/m,lookbehind:!0,alias:"punctuation"}};function r(e){e=e.split(" ");for(var t={},r=0,i=e.length;r/i,alias:"tag",inside:{"page-directive":{pattern:/<%\s*@\s*(?:Assembly|Control|Implements|Import|Master(?:Type)?|OutputCache|Page|PreviousPageType|Reference|Register)?|%>/i,alias:"tag"},rest:e.languages.markup.tag.inside}},directive:{pattern:/<%.*%>/i,alias:"tag",inside:{directive:{pattern:/<%\s*?[$=%#:]{0,2}|%>/i,alias:"tag"},rest:e.languages.csharp}}}),e.languages.aspnet.tag.pattern=/<(?!%)\/?[^\s>\/]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,e.languages.insertBefore("inside","punctuation",{directive:e.languages.aspnet.directive},e.languages.aspnet.tag.inside["attr-value"]),e.languages.insertBefore("aspnet","comment",{"asp-comment":{pattern:/<%--[\s\S]*?--%>/,alias:["asp","comment"]}}),e.languages.insertBefore("aspnet",e.languages.javascript?"script":"tag",{"asp-script":{pattern:/(]*>)[\s\S]*?(?=<\/script>)/i,lookbehind:!0,alias:["asp","script"],inside:e.languages.csharp||{}}})}e.exports=i,i.displayName="aspnet",i.aliases=[]},6681(e){"use strict";function t(e){e.languages.autohotkey={comment:[{pattern:/(^|\s);.*/,lookbehind:!0},{pattern:/(^[\t ]*)\/\*(?:[\r\n](?![ \t]*\*\/)|[^\r\n])*(?:[\r\n][ \t]*\*\/)?/m,lookbehind:!0,greedy:!0}],tag:{pattern:/^([ \t]*)[^\s,`":]+(?=:[ \t]*$)/m,lookbehind:!0},string:/"(?:[^"\n\r]|"")*"/m,variable:/%\w+%/,number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/\?|\/\/?=?|:=|\|[=|]?|&[=&]?|\+[=+]?|-[=-]?|\*[=*]?|<(?:<=?|>|=)?|>>?=?|[.^!=~]=?|\b(?:AND|NOT|OR)\b/,boolean:/\b(?:true|false)\b/,selector:/\b(?:AutoTrim|BlockInput|Break|Click|ClipWait|Continue|Control|ControlClick|ControlFocus|ControlGet|ControlGetFocus|ControlGetPos|ControlGetText|ControlMove|ControlSend|ControlSendRaw|ControlSetText|CoordMode|Critical|DetectHiddenText|DetectHiddenWindows|Drive|DriveGet|DriveSpaceFree|EnvAdd|EnvDiv|EnvGet|EnvMult|EnvSet|EnvSub|EnvUpdate|Exit|ExitApp|FileAppend|FileCopy|FileCopyDir|FileCreateDir|FileCreateShortcut|FileDelete|FileEncoding|FileGetAttrib|FileGetShortcut|FileGetSize|FileGetTime|FileGetVersion|FileInstall|FileMove|FileMoveDir|FileRead|FileReadLine|FileRecycle|FileRecycleEmpty|FileRemoveDir|FileSelectFile|FileSelectFolder|FileSetAttrib|FileSetTime|FormatTime|GetKeyState|Gosub|Goto|GroupActivate|GroupAdd|GroupClose|GroupDeactivate|Gui|GuiControl|GuiControlGet|Hotkey|ImageSearch|IniDelete|IniRead|IniWrite|Input|InputBox|KeyWait|ListHotkeys|ListLines|ListVars|Loop|Menu|MouseClick|MouseClickDrag|MouseGetPos|MouseMove|MsgBox|OnExit|OutputDebug|Pause|PixelGetColor|PixelSearch|PostMessage|Process|Progress|Random|RegDelete|RegRead|RegWrite|Reload|Repeat|Return|Run|RunAs|RunWait|Send|SendEvent|SendInput|SendMessage|SendMode|SendPlay|SendRaw|SetBatchLines|SetCapslockState|SetControlDelay|SetDefaultMouseSpeed|SetEnv|SetFormat|SetKeyDelay|SetMouseDelay|SetNumlockState|SetRegView|SetScrollLockState|SetStoreCapslockMode|SetTimer|SetTitleMatchMode|SetWinDelay|SetWorkingDir|Shutdown|Sleep|Sort|SoundBeep|SoundGet|SoundGetWaveVolume|SoundPlay|SoundSet|SoundSetWaveVolume|SplashImage|SplashTextOff|SplashTextOn|SplitPath|StatusBarGetText|StatusBarWait|StringCaseSense|StringGetPos|StringLeft|StringLen|StringLower|StringMid|StringReplace|StringRight|StringSplit|StringTrimLeft|StringTrimRight|StringUpper|Suspend|SysGet|Thread|ToolTip|Transform|TrayTip|URLDownloadToFile|WinActivate|WinActivateBottom|WinClose|WinGet|WinGetActiveStats|WinGetActiveTitle|WinGetClass|WinGetPos|WinGetText|WinGetTitle|WinHide|WinKill|WinMaximize|WinMenuSelectItem|WinMinimize|WinMinimizeAll|WinMinimizeAllUndo|WinMove|WinRestore|WinSet|WinSetTitle|WinShow|WinWait|WinWaitActive|WinWaitClose|WinWaitNotActive)\b/i,constant:/\b(?:a_ahkpath|a_ahkversion|a_appdata|a_appdatacommon|a_autotrim|a_batchlines|a_caretx|a_carety|a_computername|a_controldelay|a_cursor|a_dd|a_ddd|a_dddd|a_defaultmousespeed|a_desktop|a_desktopcommon|a_detecthiddentext|a_detecthiddenwindows|a_endchar|a_eventinfo|a_exitreason|a_fileencoding|a_formatfloat|a_formatinteger|a_gui|a_guievent|a_guicontrol|a_guicontrolevent|a_guiheight|a_guiwidth|a_guix|a_guiy|a_hour|a_iconfile|a_iconhidden|a_iconnumber|a_icontip|a_index|a_ipaddress1|a_ipaddress2|a_ipaddress3|a_ipaddress4|a_is64bitos|a_isadmin|a_iscompiled|a_iscritical|a_ispaused|a_issuspended|a_isunicode|a_keydelay|a_language|a_lasterror|a_linefile|a_linenumber|a_loopfield|a_loopfileattrib|a_loopfiledir|a_loopfileext|a_loopfilefullpath|a_loopfilelongpath|a_loopfilename|a_loopfileshortname|a_loopfileshortpath|a_loopfilesize|a_loopfilesizekb|a_loopfilesizemb|a_loopfiletimeaccessed|a_loopfiletimecreated|a_loopfiletimemodified|a_loopreadline|a_loopregkey|a_loopregname|a_loopregsubkey|a_loopregtimemodified|a_loopregtype|a_mday|a_min|a_mm|a_mmm|a_mmmm|a_mon|a_mousedelay|a_msec|a_mydocuments|a_now|a_nowutc|a_numbatchlines|a_ostype|a_osversion|a_priorhotkey|a_priorkey|programfiles|a_programfiles|a_programs|a_programscommon|a_ptrsize|a_regview|a_screendpi|a_screenheight|a_screenwidth|a_scriptdir|a_scriptfullpath|a_scripthwnd|a_scriptname|a_sec|a_space|a_startmenu|a_startmenucommon|a_startup|a_startupcommon|a_stringcasesense|a_tab|a_temp|a_thisfunc|a_thishotkey|a_thislabel|a_thismenu|a_thismenuitem|a_thismenuitempos|a_tickcount|a_timeidle|a_timeidlephysical|a_timesincepriorhotkey|a_timesincethishotkey|a_titlematchmode|a_titlematchmodespeed|a_username|a_wday|a_windelay|a_windir|a_workingdir|a_yday|a_year|a_yweek|a_yyyy|clipboard|clipboardall|comspec|errorlevel)\b/i,builtin:/\b(?:abs|acos|asc|asin|atan|ceil|chr|class|comobjactive|comobjarray|comobjconnect|comobjcreate|comobjerror|comobjflags|comobjget|comobjquery|comobjtype|comobjvalue|cos|dllcall|exp|fileexist|Fileopen|floor|format|il_add|il_create|il_destroy|instr|substr|isfunc|islabel|IsObject|ln|log|lv_add|lv_delete|lv_deletecol|lv_getcount|lv_getnext|lv_gettext|lv_insert|lv_insertcol|lv_modify|lv_modifycol|lv_setimagelist|ltrim|rtrim|mod|onmessage|numget|numput|registercallback|regexmatch|regexreplace|round|sin|tan|sqrt|strlen|strreplace|sb_seticon|sb_setparts|sb_settext|strsplit|tv_add|tv_delete|tv_getchild|tv_getcount|tv_getnext|tv_get|tv_getparent|tv_getprev|tv_getselection|tv_gettext|tv_modify|varsetcapacity|winactive|winexist|__New|__Call|__Get|__Set)\b/i,symbol:/\b(?:alt|altdown|altup|appskey|backspace|browser_back|browser_favorites|browser_forward|browser_home|browser_refresh|browser_search|browser_stop|bs|capslock|ctrl|ctrlbreak|ctrldown|ctrlup|del|delete|down|end|enter|esc|escape|f1|f10|f11|f12|f13|f14|f15|f16|f17|f18|f19|f2|f20|f21|f22|f23|f24|f3|f4|f5|f6|f7|f8|f9|home|ins|insert|joy1|joy10|joy11|joy12|joy13|joy14|joy15|joy16|joy17|joy18|joy19|joy2|joy20|joy21|joy22|joy23|joy24|joy25|joy26|joy27|joy28|joy29|joy3|joy30|joy31|joy32|joy4|joy5|joy6|joy7|joy8|joy9|joyaxes|joybuttons|joyinfo|joyname|joypov|joyr|joyu|joyv|joyx|joyy|joyz|lalt|launch_app1|launch_app2|launch_mail|launch_media|lbutton|lcontrol|lctrl|left|lshift|lwin|lwindown|lwinup|mbutton|media_next|media_play_pause|media_prev|media_stop|numlock|numpad0|numpad1|numpad2|numpad3|numpad4|numpad5|numpad6|numpad7|numpad8|numpad9|numpadadd|numpadclear|numpaddel|numpaddiv|numpaddot|numpaddown|numpadend|numpadenter|numpadhome|numpadins|numpadleft|numpadmult|numpadpgdn|numpadpgup|numpadright|numpadsub|numpadup|pgdn|pgup|printscreen|ralt|rbutton|rcontrol|rctrl|right|rshift|rwin|rwindown|rwinup|scrolllock|shift|shiftdown|shiftup|space|tab|up|volume_down|volume_mute|volume_up|wheeldown|wheelleft|wheelright|wheelup|xbutton1|xbutton2)\b/i,important:/#\b(?:AllowSameLineComments|ClipboardTimeout|CommentFlag|DerefChar|ErrorStdOut|EscapeChar|HotkeyInterval|HotkeyModifierTimeout|Hotstring|If|IfTimeout|IfWinActive|IfWinExist|IfWinNotActive|IfWinNotExist|Include|IncludeAgain|InputLevel|InstallKeybdHook|InstallMouseHook|KeyHistory|MaxHotkeysPerInterval|MaxMem|MaxThreads|MaxThreadsBuffer|MaxThreadsPerHotkey|MenuMaskKey|NoEnv|NoTrayIcon|Persistent|SingleInstance|UseHook|Warn|WinActivateForce)\b/i,keyword:/\b(?:Abort|AboveNormal|Add|ahk_class|ahk_exe|ahk_group|ahk_id|ahk_pid|All|Alnum|Alpha|AltSubmit|AltTab|AltTabAndMenu|AltTabMenu|AltTabMenuDismiss|AlwaysOnTop|AutoSize|Background|BackgroundTrans|BelowNormal|between|BitAnd|BitNot|BitOr|BitShiftLeft|BitShiftRight|BitXOr|Bold|Border|Button|ByRef|Checkbox|Checked|CheckedGray|Choose|ChooseString|Close|Color|ComboBox|Contains|ControlList|Count|Date|DateTime|Days|DDL|Default|DeleteAll|Delimiter|Deref|Destroy|Digit|Disable|Disabled|DropDownList|Edit|Eject|Else|Enable|Enabled|Error|Exist|Expand|ExStyle|FileSystem|First|Flash|Float|FloatFast|Focus|Font|for|global|Grid|Group|GroupBox|GuiClose|GuiContextMenu|GuiDropFiles|GuiEscape|GuiSize|Hdr|Hidden|Hide|High|HKCC|HKCR|HKCU|HKEY_CLASSES_ROOT|HKEY_CURRENT_CONFIG|HKEY_CURRENT_USER|HKEY_LOCAL_MACHINE|HKEY_USERS|HKLM|HKU|Hours|HScroll|Icon|IconSmall|ID|IDLast|If|IfEqual|IfExist|IfGreater|IfGreaterOrEqual|IfInString|IfLess|IfLessOrEqual|IfMsgBox|IfNotEqual|IfNotExist|IfNotInString|IfWinActive|IfWinExist|IfWinNotActive|IfWinNotExist|Ignore|ImageList|in|Integer|IntegerFast|Interrupt|is|italic|Join|Label|LastFound|LastFoundExist|Limit|Lines|List|ListBox|ListView|local|Lock|Logoff|Low|Lower|Lowercase|MainWindow|Margin|Maximize|MaximizeBox|MaxSize|Minimize|MinimizeBox|MinMax|MinSize|Minutes|MonthCal|Mouse|Move|Multi|NA|No|NoActivate|NoDefault|NoHide|NoIcon|NoMainWindow|norm|Normal|NoSort|NoSortHdr|NoStandard|Not|NoTab|NoTimers|Number|Off|Ok|On|OwnDialogs|Owner|Parse|Password|Picture|Pixel|Pos|Pow|Priority|ProcessName|Radio|Range|Read|ReadOnly|Realtime|Redraw|REG_BINARY|REG_DWORD|REG_EXPAND_SZ|REG_MULTI_SZ|REG_SZ|Region|Relative|Rename|Report|Resize|Restore|Retry|RGB|Screen|Seconds|Section|Serial|SetLabel|ShiftAltTab|Show|Single|Slider|SortDesc|Standard|static|Status|StatusBar|StatusCD|strike|Style|Submit|SysMenu|Tab2|TabStop|Text|Theme|Tile|ToggleCheck|ToggleEnable|ToolWindow|Top|Topmost|TransColor|Transparent|Tray|TreeView|TryAgain|Throw|Try|Catch|Finally|Type|UnCheck|underline|Unicode|Unlock|Until|UpDown|Upper|Uppercase|UseErrorLevel|Vis|VisFirst|Visible|VScroll|Wait|WaitClose|WantCtrlA|WantF2|WantReturn|While|Wrap|Xdigit|xm|xp|xs|Yes|ym|yp|ys)\b/i,function:/[^(); \t,\n+*\-=?>:\\\/<&%\[\]]+(?=\()/m,punctuation:/[{}[\]():,]/}}e.exports=t,t.displayName="autohotkey",t.aliases=[]},53358(e){"use strict";function t(e){e.languages.autoit={comment:[/;.*/,{pattern:/(^[\t ]*)#(?:comments-start|cs)[\s\S]*?^[ \t]*#(?:comments-end|ce)/m,lookbehind:!0}],url:{pattern:/(^[\t ]*#include\s+)(?:<[^\r\n>]+>|"[^\r\n"]+")/m,lookbehind:!0},string:{pattern:/(["'])(?:\1\1|(?!\1)[^\r\n])*\1/,greedy:!0,inside:{variable:/([%$@])\w+\1/}},directive:{pattern:/(^[\t ]*)#\w+/m,lookbehind:!0,alias:"keyword"},function:/\b\w+(?=\()/,variable:/[$@]\w+/,keyword:/\b(?:Case|Const|Continue(?:Case|Loop)|Default|Dim|Do|Else(?:If)?|End(?:Func|If|Select|Switch|With)|Enum|Exit(?:Loop)?|For|Func|Global|If|In|Local|Next|Null|ReDim|Select|Static|Step|Switch|Then|To|Until|Volatile|WEnd|While|With)\b/i,number:/\b(?:0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)\b/i,boolean:/\b(?:True|False)\b/i,operator:/<[=>]?|[-+*\/=&>]=?|[?^]|\b(?:And|Or|Not)\b/i,punctuation:/[\[\]().,:]/}}e.exports=t,t.displayName="autoit",t.aliases=[]},6979(e){"use strict";function t(e){!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},r={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:r},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:r},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:r.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:r.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|aptitude|apt-cache|apt-get|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:if|then|else|elif|fi|for|while|in|case|esac|function|select|do|done|until)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|break|cd|continue|eval|exec|exit|export|getopts|hash|pwd|readonly|return|shift|test|times|trap|umask|unset|alias|bind|builtin|caller|command|declare|echo|enable|help|let|local|logout|mapfile|printf|read|readarray|source|type|typeset|ulimit|unalias|set|shopt)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:true|false)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var i=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],a=r.variable[1].inside,o=0;o?^\w +\-.])*"/i,greedy:!0},number:/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:E[+-]?\d+)?/i,keyword:/\b(?:AS|BEEP|BLOAD|BSAVE|CALL(?: ABSOLUTE)?|CASE|CHAIN|CHDIR|CLEAR|CLOSE|CLS|COM|COMMON|CONST|DATA|DECLARE|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DIM|DO|DOUBLE|ELSE|ELSEIF|END|ENVIRON|ERASE|ERROR|EXIT|FIELD|FILES|FOR|FUNCTION|GET|GOSUB|GOTO|IF|INPUT|INTEGER|IOCTL|KEY|KILL|LINE INPUT|LOCATE|LOCK|LONG|LOOP|LSET|MKDIR|NAME|NEXT|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPEN|OPTION BASE|OUT|POKE|PUT|READ|REDIM|REM|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SHARED|SINGLE|SELECT CASE|SHELL|SLEEP|STATIC|STEP|STOP|STRING|SUB|SWAP|SYSTEM|THEN|TIMER|TO|TROFF|TRON|TYPE|UNLOCK|UNTIL|USING|VIEW PRINT|WAIT|WEND|WHILE|WRITE)(?:\$|\b)/i,function:/\b(?:ABS|ACCESS|ACOS|ANGLE|AREA|ARITHMETIC|ARRAY|ASIN|ASK|AT|ATN|BASE|BEGIN|BREAK|CAUSE|CEIL|CHR|CLIP|COLLATE|COLOR|CON|COS|COSH|COT|CSC|DATE|DATUM|DEBUG|DECIMAL|DEF|DEG|DEGREES|DELETE|DET|DEVICE|DISPLAY|DOT|ELAPSED|EPS|ERASABLE|EXLINE|EXP|EXTERNAL|EXTYPE|FILETYPE|FIXED|FP|GO|GRAPH|HANDLER|IDN|IMAGE|IN|INT|INTERNAL|IP|IS|KEYED|LBOUND|LCASE|LEFT|LEN|LENGTH|LET|LINE|LINES|LOG|LOG10|LOG2|LTRIM|MARGIN|MAT|MAX|MAXNUM|MID|MIN|MISSING|MOD|NATIVE|NUL|NUMERIC|OF|OPTION|ORD|ORGANIZATION|OUTIN|OUTPUT|PI|POINT|POINTER|POINTS|POS|PRINT|PROGRAM|PROMPT|RAD|RADIANS|RANDOMIZE|RECORD|RECSIZE|RECTYPE|RELATIVE|REMAINDER|REPEAT|REST|RETRY|REWRITE|RIGHT|RND|ROUND|RTRIM|SAME|SEC|SELECT|SEQUENTIAL|SET|SETTER|SGN|SIN|SINH|SIZE|SKIP|SQR|STANDARD|STATUS|STR|STREAM|STYLE|TAB|TAN|TANH|TEMPLATE|TEXT|THERE|TIME|TIMEOUT|TRACE|TRANSFORM|TRUNCATE|UBOUND|UCASE|USE|VAL|VARIABLE|VIEWPORT|WHEN|WINDOW|WITH|ZER|ZONEWIDTH)(?:\$|\b)/i,operator:/<[=>]?|>=?|[+\-*\/^=&]|\b(?:AND|EQV|IMP|NOT|OR|XOR)\b/i,punctuation:/[,;:()]/}}e.exports=t,t.displayName="basic",t.aliases=[]},94781(e){"use strict";function t(e){var t,n,r,i,a;n=/%%?[~:\w]+%?|!\S+!/,r={pattern:/\/[a-z?]+(?=[ :]|$):?|-[a-z]\b|--[a-z-]+\b/im,alias:"attr-name",inside:{punctuation:/:/}},i=/"(?:[\\"]"|[^"])*"(?!")/,a=/(?:\b|-)\d+\b/,(t=e).languages.batch={comment:[/^::.*/m,{pattern:/((?:^|[&(])[ \t]*)rem\b(?:[^^&)\r\n]|\^(?:\r\n|[\s\S]))*/im,lookbehind:!0}],label:{pattern:/^:.*/m,alias:"property"},command:[{pattern:/((?:^|[&(])[ \t]*)for(?: \/[a-z?](?:[ :](?:"[^"]*"|[^\s"/]\S*))?)* \S+ in \([^)]+\) do/im,lookbehind:!0,inside:{keyword:/^for\b|\b(?:in|do)\b/i,string:i,parameter:r,variable:n,number:a,punctuation:/[()',]/}},{pattern:/((?:^|[&(])[ \t]*)if(?: \/[a-z?](?:[ :](?:"[^"]*"|[^\s"/]\S*))?)* (?:not )?(?:cmdextversion \d+|defined \w+|errorlevel \d+|exist \S+|(?:"[^"]*"|(?!")(?:(?!==)\S)+)?(?:==| (?:equ|neq|lss|leq|gtr|geq) )(?:"[^"]*"|[^\s"]\S*))/im,lookbehind:!0,inside:{keyword:/^if\b|\b(?:not|cmdextversion|defined|errorlevel|exist)\b/i,string:i,parameter:r,variable:n,number:a,operator:/\^|==|\b(?:equ|neq|lss|leq|gtr|geq)\b/i}},{pattern:/((?:^|[&()])[ \t]*)else\b/im,lookbehind:!0,inside:{keyword:/^else\b/i}},{pattern:/((?:^|[&(])[ \t]*)set(?: \/[a-z](?:[ :](?:"[^"]*"|[^\s"/]\S*))?)* (?:[^^&)\r\n]|\^(?:\r\n|[\s\S]))*/im,lookbehind:!0,inside:{keyword:/^set\b/i,string:i,parameter:r,variable:[n,/\w+(?=(?:[*\/%+\-&^|]|<<|>>)?=)/],number:a,operator:/[*\/%+\-&^|]=?|<<=?|>>=?|[!~_=]/,punctuation:/[()',]/}},{pattern:/((?:^|[&(])[ \t]*@?)\w+\b(?:"(?:[\\"]"|[^"])*"(?!")|[^"^&)\r\n]|\^(?:\r\n|[\s\S]))*/im,lookbehind:!0,inside:{keyword:/^\w+\b/i,string:i,parameter:r,label:{pattern:/(^\s*):\S+/m,lookbehind:!0,alias:"property"},variable:n,number:a,operator:/\^/}}],operator:/[&@]/,punctuation:/[()']/}}e.exports=t,t.displayName="batch",t.aliases=[]},62260(e){"use strict";function t(e){e.languages.bbcode={tag:{pattern:/\[\/?[^\s=\]]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'"\]=]+))?(?:\s+[^\s=\]]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'"\]=]+))*\s*\]/,inside:{tag:{pattern:/^\[\/?[^\s=\]]+/,inside:{punctuation:/^\[\/?/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'"\]=]+)/i,inside:{punctuation:[/^=/,{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\]/,"attr-name":/[^\s=\]]+/}}},e.languages.shortcode=e.languages.bbcode}e.exports=t,t.displayName="bbcode",t.aliases=["shortcode"]},59258(e){"use strict";function t(e){e.languages.birb=e.languages.extend("clike",{string:{pattern:/r?("|')(?:\\.|(?!\1)[^\\])*\1/,greedy:!0},"class-name":[/\b[A-Z](?:[\d_]*[a-zA-Z]\w*)?\b/,/\b[A-Z]\w*(?=\s+\w+\s*[;,=()])/],keyword:/\b(?:assert|break|case|class|const|default|else|enum|final|follows|for|grab|if|nest|next|new|noSeeb|return|static|switch|throw|var|void|while)\b/,operator:/\+\+|--|&&|\|\||<<=?|>>=?|~(?:\/=?)?|[+\-*\/%&^|=!<>]=?|\?|:/,variable:/\b[a-z_]\w*\b/}),e.languages.insertBefore("birb","function",{metadata:{pattern:/<\w+>/,greedy:!0,alias:"symbol"}})}e.exports=t,t.displayName="birb",t.aliases=[]},62890(e,t,n){"use strict";var r=n(65806);function i(e){e.register(r),e.languages.bison=e.languages.extend("c",{}),e.languages.insertBefore("bison","comment",{bison:{pattern:/^(?:[^%]|%(?!%))*%%[\s\S]*?%%/,inside:{c:{pattern:/%\{[\s\S]*?%\}|\{(?:\{[^}]*\}|[^{}])*\}/,inside:{delimiter:{pattern:/^%?\{|%?\}$/,alias:"punctuation"},"bison-variable":{pattern:/[$@](?:<[^\s>]+>)?[\w$]+/,alias:"variable",inside:{punctuation:/<|>/}},rest:e.languages.c}},comment:e.languages.c.comment,string:e.languages.c.string,property:/\S+(?=:)/,keyword:/%\w+/,number:{pattern:/(^|[^@])\b(?:0x[\da-f]+|\d+)/i,lookbehind:!0},punctuation:/%[%?]|[|:;\[\]<>]/}}})}e.exports=i,i.displayName="bison",i.aliases=[]},15958(e){"use strict";function t(e){e.languages.bnf={string:{pattern:/"[^\r\n"]*"|'[^\r\n']*'/},definition:{pattern:/<[^<>\r\n\t]+>(?=\s*::=)/,alias:["rule","keyword"],inside:{punctuation:/^<|>$/}},rule:{pattern:/<[^<>\r\n\t]+>/,inside:{punctuation:/^<|>$/}},operator:/::=|[|()[\]{}*+?]|\.{3}/},e.languages.rbnf=e.languages.bnf}e.exports=t,t.displayName="bnf",t.aliases=["rbnf"]},61321(e){"use strict";function t(e){e.languages.brainfuck={pointer:{pattern:/<|>/,alias:"keyword"},increment:{pattern:/\+/,alias:"inserted"},decrement:{pattern:/-/,alias:"deleted"},branching:{pattern:/\[|\]/,alias:"important"},operator:/[.,]/,comment:/\S+/}}e.exports=t,t.displayName="brainfuck",t.aliases=[]},77856(e){"use strict";function t(e){e.languages.brightscript={comment:/(?:\brem|').*/i,"directive-statement":{pattern:/(^[\t ]*)#(?:const|else(?:[\t ]+if)?|end[\t ]+if|error|if).*/im,lookbehind:!0,alias:"property",inside:{"error-message":{pattern:/(^#error).+/,lookbehind:!0},directive:{pattern:/^#(?:const|else(?:[\t ]+if)?|end[\t ]+if|error|if)/,alias:"keyword"},expression:{pattern:/[\s\S]+/,inside:null}}},property:{pattern:/([\r\n{,][\t ]*)(?:(?!\d)\w+|"(?:[^"\r\n]|"")*"(?!"))(?=[ \t]*:)/,lookbehind:!0,greedy:!0},string:{pattern:/"(?:[^"\r\n]|"")*"(?!")/,greedy:!0},"class-name":{pattern:/(\bAs[\t ]+)\w+/i,lookbehind:!0},keyword:/\b(?:As|Dim|Each|Else|Elseif|End|Exit|For|Function|Goto|If|In|Print|Return|Step|Stop|Sub|Then|To|While)\b/i,boolean:/\b(?:true|false)\b/i,function:/\b(?!\d)\w+(?=[\t ]*\()/i,number:/(?:\b\d+(?:\.\d+)?(?:[ed][+-]\d+)?|&h[a-f\d]+)\b[%&!#]?/i,operator:/--|\+\+|>>=?|<<=?|<>|[-+*/\\<>]=?|[:^=?]|\b(?:and|mod|not|or)\b/i,punctuation:/[.,;()[\]{}]/,constant:/\b(?:LINE_NUM)\b/i},e.languages.brightscript["directive-statement"].inside.expression.inside=e.languages.brightscript}e.exports=t,t.displayName="brightscript",t.aliases=[]},90741(e){"use strict";function t(e){e.languages.bro={comment:{pattern:/(^|[^\\$])#.*/,lookbehind:!0,inside:{italic:/\b(?:TODO|FIXME|XXX)\b/}},string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},boolean:/\b[TF]\b/,function:{pattern:/(?:function|hook|event) \w+(?:::\w+)?/,inside:{keyword:/^(?:function|hook|event)/}},variable:{pattern:/(?:global|local) \w+/i,inside:{keyword:/(?:global|local)/}},builtin:/(?:@(?:load(?:-(?:sigs|plugin))?|unload|prefixes|ifn?def|else|(?:end)?if|DIR|FILENAME))|(?:&?(?:redef|priority|log|optional|default|add_func|delete_func|expire_func|read_expire|write_expire|create_expire|synchronized|persistent|rotate_interval|rotate_size|encrypt|raw_output|mergeable|group|error_handler|type_column))/,constant:{pattern:/const \w+/i,inside:{keyword:/const/}},keyword:/\b(?:break|next|continue|alarm|using|of|add|delete|export|print|return|schedule|when|timeout|addr|any|bool|count|double|enum|file|int|interval|pattern|opaque|port|record|set|string|subnet|table|time|vector|for|if|else|in|module|function)\b/,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&|\|\|?|\?|\*|\/|~|\^|%/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,punctuation:/[{}[\];(),.:]/}}e.exports=t,t.displayName="bro",t.aliases=[]},83410(e){"use strict";function t(e){e.languages.bsl={comment:/\/\/.*/,string:[{pattern:/"(?:[^"]|"")*"(?!")/,greedy:!0},{pattern:/'(?:[^'\r\n\\]|\\.)*'/}],keyword:[{pattern:/(^|[^\w\u0400-\u0484\u0487-\u052f\u1d2b\u1d78\u2de0-\u2dff\ua640-\ua69f\ufe2e\ufe2f])(?:пока|для|новый|прервать|попытка|исключение|вызватьисключение|иначе|конецпопытки|неопределено|функция|перем|возврат|конецфункции|если|иначеесли|процедура|конецпроцедуры|тогда|знач|экспорт|конецесли|из|каждого|истина|ложь|по|цикл|конеццикла|выполнить)(?![\w\u0400-\u0484\u0487-\u052f\u1d2b\u1d78\u2de0-\u2dff\ua640-\ua69f\ufe2e\ufe2f])/i,lookbehind:!0},{pattern:/\b(?:while|for|new|break|try|except|raise|else|endtry|undefined|function|var|return|endfunction|null|if|elseif|procedure|endprocedure|then|val|export|endif|in|each|true|false|to|do|enddo|execute)\b/i}],number:{pattern:/(^(?=\d)|[^\w\u0400-\u0484\u0487-\u052f\u1d2b\u1d78\u2de0-\u2dff\ua640-\ua69f\ufe2e\ufe2f])(?:\d+(?:\.\d*)?|\.\d+)(?:E[+-]?\d+)?/i,lookbehind:!0},operator:[/[<>+\-*/]=?|[%=]/,{pattern:/(^|[^\w\u0400-\u0484\u0487-\u052f\u1d2b\u1d78\u2de0-\u2dff\ua640-\ua69f\ufe2e\ufe2f])(?:и|или|не)(?![\w\u0400-\u0484\u0487-\u052f\u1d2b\u1d78\u2de0-\u2dff\ua640-\ua69f\ufe2e\ufe2f])/i,lookbehind:!0},{pattern:/\b(?:and|or|not)\b/i}],punctuation:/\(\.|\.\)|[()\[\]:;,.]/,directive:[{pattern:/^(\s*)&.*/m,lookbehind:!0,alias:"important"},{pattern:/^\s*#.*/gm,alias:"important"}]},e.languages.oscript=e.languages.bsl}e.exports=t,t.displayName="bsl",t.aliases=[]},65806(e){"use strict";function t(e){e.languages.c=e.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:__attribute__|_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),e.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},e.languages.c.string],comment:e.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:e.languages.c}}},constant:/\b(?:__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|stdin|stdout|stderr)\b/}),delete e.languages.c.boolean}e.exports=t,t.displayName="c",t.aliases=[]},33039(e){"use strict";function t(e){e.languages.cfscript=e.languages.extend("clike",{comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,inside:{annotation:{pattern:/(?:^|[^.])@[\w\.]+/,alias:"punctuation"}}},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],keyword:/\b(?:abstract|break|catch|component|continue|default|do|else|extends|final|finally|for|function|if|in|include|package|private|property|public|remote|required|rethrow|return|static|switch|throw|try|var|while|xml)\b(?!\s*=)/,operator:[/\+\+|--|&&|\|\||::|=>|[!=]==|<=?|>=?|[-+*/%&|^!=<>]=?|\?(?:\.|:)?|[?:]/,/\b(?:and|contains|eq|equal|eqv|gt|gte|imp|is|lt|lte|mod|not|or|xor)\b/],scope:{pattern:/\b(?:application|arguments|cgi|client|cookie|local|session|super|this|variables)\b/,alias:"global"},type:{pattern:/\b(?:any|array|binary|boolean|date|guid|numeric|query|string|struct|uuid|void|xml)\b/,alias:"builtin"}}),e.languages.insertBefore("cfscript","keyword",{"function-variable":{pattern:/[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"}}),delete e.languages.cfscript["class-name"],e.languages.cfc=e.languages.cfscript}e.exports=t,t.displayName="cfscript",t.aliases=[]},85082(e,t,n){"use strict";var r=n(80096);function i(e){e.register(r),e.languages.chaiscript=e.languages.extend("clike",{string:{pattern:/(^|[^\\])'(?:[^'\\]|\\[\s\S])*'/,lookbehind:!0,greedy:!0},"class-name":[{pattern:/(\bclass\s+)\w+/,lookbehind:!0},{pattern:/(\b(?:attr|def)\s+)\w+(?=\s*::)/,lookbehind:!0}],keyword:/\b(?:attr|auto|break|case|catch|class|continue|def|default|else|finally|for|fun|global|if|return|switch|this|try|var|while)\b/,number:[e.languages.cpp.number,/\b(?:Infinity|NaN)\b/],operator:/>>=?|<<=?|\|\||&&|:[:=]?|--|\+\+|[=!<>+\-*/%|&^]=?|[?~]|`[^`\r\n]{1,4}`/}),e.languages.insertBefore("chaiscript","operator",{"parameter-type":{pattern:/([,(]\s*)\w+(?=\s+\w)/,lookbehind:!0,alias:"class-name"}}),e.languages.insertBefore("chaiscript","string",{"string-interpolation":{pattern:/(^|[^\\])"(?:[^"$\\]|\\[\s\S]|\$(?!\{)|\$\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})*"/,lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}/,lookbehind:!0,inside:{"interpolation-expression":{pattern:/(^\$\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:e.languages.chaiscript},"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"}}},string:/[\s\S]+/}}})}e.exports=i,i.displayName="chaiscript",i.aliases=[]},79415(e){"use strict";function t(e){e.languages.cil={comment:/\/\/.*/,string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},directive:{pattern:/(^|\W)\.[a-z]+(?=\s)/,lookbehind:!0,alias:"class-name"},variable:/\[[\w\.]+\]/,keyword:/\b(?:abstract|ansi|assembly|auto|autochar|beforefieldinit|bool|bstr|byvalstr|catch|char|cil|class|currency|date|decimal|default|enum|error|explicit|extends|extern|famandassem|family|famorassem|final(?:ly)?|float32|float64|hidebysig|iant|idispatch|implements|import|initonly|instance|u?int(?:8|16|32|64)?|interface|iunknown|literal|lpstr|lpstruct|lptstr|lpwstr|managed|method|native(?:Type)?|nested|newslot|object(?:ref)?|pinvokeimpl|private|privatescope|public|reqsecobj|rtspecialname|runtime|sealed|sequential|serializable|specialname|static|string|struct|syschar|tbstr|unicode|unmanagedexp|unsigned|value(?:type)?|variant|virtual|void)\b/,function:/\b(?:(?:constrained|unaligned|volatile|readonly|tail|no)\.)?(?:conv\.(?:[iu][1248]?|ovf\.[iu][1248]?(?:\.un)?|r\.un|r4|r8)|ldc\.(?:i4(?:\.[0-9]+|\.[mM]1|\.s)?|i8|r4|r8)|ldelem(?:\.[iu][1248]?|\.r[48]|\.ref|a)?|ldind\.(?:[iu][1248]?|r[48]|ref)|stelem\.?(?:i[1248]?|r[48]|ref)?|stind\.(?:i[1248]?|r[48]|ref)?|end(?:fault|filter|finally)|ldarg(?:\.[0-3s]|a(?:\.s)?)?|ldloc(?:\.[0-9]+|\.s)?|sub(?:\.ovf(?:\.un)?)?|mul(?:\.ovf(?:\.un)?)?|add(?:\.ovf(?:\.un)?)?|stloc(?:\.[0-3s])?|refany(?:type|val)|blt(?:\.un)?(?:\.s)?|ble(?:\.un)?(?:\.s)?|bgt(?:\.un)?(?:\.s)?|bge(?:\.un)?(?:\.s)?|unbox(?:\.any)?|init(?:blk|obj)|call(?:i|virt)?|brfalse(?:\.s)?|bne\.un(?:\.s)?|ldloca(?:\.s)?|brzero(?:\.s)?|brtrue(?:\.s)?|brnull(?:\.s)?|brinst(?:\.s)?|starg(?:\.s)?|leave(?:\.s)?|shr(?:\.un)?|rem(?:\.un)?|div(?:\.un)?|clt(?:\.un)?|alignment|ldvirtftn|castclass|beq(?:\.s)?|mkrefany|localloc|ckfinite|rethrow|ldtoken|ldsflda|cgt\.un|arglist|switch|stsfld|sizeof|newobj|newarr|ldsfld|ldnull|ldflda|isinst|throw|stobj|stfld|ldstr|ldobj|ldlen|ldftn|ldfld|cpobj|cpblk|break|br\.s|xor|shl|ret|pop|not|nop|neg|jmp|dup|cgt|ceq|box|and|or|br)\b/,boolean:/\b(?:true|false)\b/,number:/\b-?(?:0x[0-9a-f]+|[0-9]+)(?:\.[0-9a-f]+)?\b/i,punctuation:/[{}[\];(),:=]|IL_[0-9A-Za-z]+/}}e.exports=t,t.displayName="cil",t.aliases=[]},29726(e){"use strict";function t(e){e.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}}e.exports=t,t.displayName="clike",t.aliases=[]},62849(e){"use strict";function t(e){e.languages.clojure={comment:/;.*/,string:{pattern:/"(?:[^"\\]|\\.)*"/,greedy:!0},operator:/(?:::|[:|'])\b[a-z][\w*+!?-]*\b/i,keyword:{pattern:/([^\w+*'?-])(?:def|if|do|let|\.\.|quote|var|->>|->|fn|loop|recur|throw|try|monitor-enter|\.|new|set!|def-|defn|defn-|defmacro|defmulti|defmethod|defstruct|defonce|declare|definline|definterface|defprotocol|==|defrecord|>=|deftype|<=|defproject|ns|\*|\+|-|\/|<|=|>|accessor|agent|agent-errors|aget|alength|all-ns|alter|and|append-child|apply|array-map|aset|aset-boolean|aset-byte|aset-char|aset-double|aset-float|aset-int|aset-long|aset-short|assert|assoc|await|await-for|bean|binding|bit-and|bit-not|bit-or|bit-shift-left|bit-shift-right|bit-xor|boolean|branch\?|butlast|byte|cast|char|children|class|clear-agent-errors|comment|commute|comp|comparator|complement|concat|conj|cons|constantly|cond|if-not|construct-proxy|contains\?|count|create-ns|create-struct|cycle|dec|deref|difference|disj|dissoc|distinct|doall|doc|dorun|doseq|dosync|dotimes|doto|double|down|drop|drop-while|edit|end\?|ensure|eval|every\?|false\?|ffirst|file-seq|filter|find|find-doc|find-ns|find-var|first|float|flush|for|fnseq|frest|gensym|get-proxy-class|get|hash-map|hash-set|identical\?|identity|if-let|import|in-ns|inc|index|insert-child|insert-left|insert-right|inspect-table|inspect-tree|instance\?|int|interleave|intersection|into|into-array|iterate|join|key|keys|keyword|keyword\?|last|lazy-cat|lazy-cons|left|lefts|line-seq|list\*|list|load|load-file|locking|long|macroexpand|macroexpand-1|make-array|make-node|map|map-invert|map\?|mapcat|max|max-key|memfn|merge|merge-with|meta|min|min-key|name|namespace|neg\?|newline|next|nil\?|node|not|not-any\?|not-every\?|not=|ns-imports|ns-interns|ns-map|ns-name|ns-publics|ns-refers|ns-resolve|ns-unmap|nth|nthrest|or|parse|partial|path|peek|pop|pos\?|pr|pr-str|print|print-str|println|println-str|prn|prn-str|project|proxy|proxy-mappings|quot|rand|rand-int|range|re-find|re-groups|re-matcher|re-matches|re-pattern|re-seq|read|read-line|reduce|ref|ref-set|refer|rem|remove|remove-method|remove-ns|rename|rename-keys|repeat|replace|replicate|resolve|rest|resultset-seq|reverse|rfirst|right|rights|root|rrest|rseq|second|select|select-keys|send|send-off|seq|seq-zip|seq\?|set|short|slurp|some|sort|sort-by|sorted-map|sorted-map-by|sorted-set|special-symbol\?|split-at|split-with|str|string\?|struct|struct-map|subs|subvec|symbol|symbol\?|sync|take|take-nth|take-while|test|time|to-array|to-array-2d|tree-seq|true\?|union|up|update-proxy|val|vals|var-get|var-set|var\?|vector|vector-zip|vector\?|when|when-first|when-let|when-not|with-local-vars|with-meta|with-open|with-out-str|xml-seq|xml-zip|zero\?|zipmap|zipper)(?=[^\w+*'?-])/,lookbehind:!0},boolean:/\b(?:true|false|nil)\b/,number:/\b[\da-f]+\b/i,punctuation:/[{}\[\](),]/}}e.exports=t,t.displayName="clojure",t.aliases=[]},55773(e){"use strict";function t(e){e.languages.cmake={comment:/#.*/,string:{pattern:/"(?:[^\\"]|\\.)*"/,greedy:!0,inside:{interpolation:{pattern:/\$\{(?:[^{}$]|\$\{[^{}$]*\})*\}/,inside:{punctuation:/\$\{|\}/,variable:/\w+/}}}},variable:/\b(?:CMAKE_\w+|\w+_(?:VERSION(?:_MAJOR|_MINOR|_PATCH|_TWEAK)?|(?:BINARY|SOURCE)_DIR|DESCRIPTION|HOMEPAGE_URL|ROOT)|(?:ANDROID|APPLE|BORLAND|BUILD_SHARED_LIBS|CACHE|CPACK_(?:ABSOLUTE_DESTINATION_FILES|COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY|ERROR_ON_ABSOLUTE_INSTALL_DESTINATION|INCLUDE_TOPLEVEL_DIRECTORY|INSTALL_DEFAULT_DIRECTORY_PERMISSIONS|INSTALL_SCRIPT|PACKAGING_INSTALL_PREFIX|SET_DESTDIR|WARN_ON_ABSOLUTE_INSTALL_DESTINATION)|CTEST_(?:BINARY_DIRECTORY|BUILD_COMMAND|BUILD_NAME|BZR_COMMAND|BZR_UPDATE_OPTIONS|CHANGE_ID|CHECKOUT_COMMAND|CONFIGURATION_TYPE|CONFIGURE_COMMAND|COVERAGE_COMMAND|COVERAGE_EXTRA_FLAGS|CURL_OPTIONS|CUSTOM_(?:COVERAGE_EXCLUDE|ERROR_EXCEPTION|ERROR_MATCH|ERROR_POST_CONTEXT|ERROR_PRE_CONTEXT|MAXIMUM_FAILED_TEST_OUTPUT_SIZE|MAXIMUM_NUMBER_OF_(?:ERRORS|WARNINGS)|MAXIMUM_PASSED_TEST_OUTPUT_SIZE|MEMCHECK_IGNORE|POST_MEMCHECK|POST_TEST|PRE_MEMCHECK|PRE_TEST|TESTS_IGNORE|WARNING_EXCEPTION|WARNING_MATCH)|CVS_CHECKOUT|CVS_COMMAND|CVS_UPDATE_OPTIONS|DROP_LOCATION|DROP_METHOD|DROP_SITE|DROP_SITE_CDASH|DROP_SITE_PASSWORD|DROP_SITE_USER|EXTRA_COVERAGE_GLOB|GIT_COMMAND|GIT_INIT_SUBMODULES|GIT_UPDATE_CUSTOM|GIT_UPDATE_OPTIONS|HG_COMMAND|HG_UPDATE_OPTIONS|LABELS_FOR_SUBPROJECTS|MEMORYCHECK_(?:COMMAND|COMMAND_OPTIONS|SANITIZER_OPTIONS|SUPPRESSIONS_FILE|TYPE)|NIGHTLY_START_TIME|P4_CLIENT|P4_COMMAND|P4_OPTIONS|P4_UPDATE_OPTIONS|RUN_CURRENT_SCRIPT|SCP_COMMAND|SITE|SOURCE_DIRECTORY|SUBMIT_URL|SVN_COMMAND|SVN_OPTIONS|SVN_UPDATE_OPTIONS|TEST_LOAD|TEST_TIMEOUT|TRIGGER_SITE|UPDATE_COMMAND|UPDATE_OPTIONS|UPDATE_VERSION_ONLY|USE_LAUNCHERS)|CYGWIN|ENV|EXECUTABLE_OUTPUT_PATH|GHS-MULTI|IOS|LIBRARY_OUTPUT_PATH|MINGW|MSVC(?:10|11|12|14|60|70|71|80|90|_IDE|_TOOLSET_VERSION|_VERSION)?|MSYS|PROJECT_(?:BINARY_DIR|DESCRIPTION|HOMEPAGE_URL|NAME|SOURCE_DIR|VERSION|VERSION_(?:MAJOR|MINOR|PATCH|TWEAK))|UNIX|WIN32|WINCE|WINDOWS_PHONE|WINDOWS_STORE|XCODE|XCODE_VERSION))\b/,property:/\b(?:cxx_\w+|(?:ARCHIVE_OUTPUT_(?:DIRECTORY|NAME)|COMPILE_DEFINITIONS|COMPILE_PDB_NAME|COMPILE_PDB_OUTPUT_DIRECTORY|EXCLUDE_FROM_DEFAULT_BUILD|IMPORTED_(?:IMPLIB|LIBNAME|LINK_DEPENDENT_LIBRARIES|LINK_INTERFACE_LANGUAGES|LINK_INTERFACE_LIBRARIES|LINK_INTERFACE_MULTIPLICITY|LOCATION|NO_SONAME|OBJECTS|SONAME)|INTERPROCEDURAL_OPTIMIZATION|LIBRARY_OUTPUT_DIRECTORY|LIBRARY_OUTPUT_NAME|LINK_FLAGS|LINK_INTERFACE_LIBRARIES|LINK_INTERFACE_MULTIPLICITY|LOCATION|MAP_IMPORTED_CONFIG|OSX_ARCHITECTURES|OUTPUT_NAME|PDB_NAME|PDB_OUTPUT_DIRECTORY|RUNTIME_OUTPUT_DIRECTORY|RUNTIME_OUTPUT_NAME|STATIC_LIBRARY_FLAGS|VS_CSHARP|VS_DOTNET_REFERENCEPROP|VS_DOTNET_REFERENCE|VS_GLOBAL_SECTION_POST|VS_GLOBAL_SECTION_PRE|VS_GLOBAL|XCODE_ATTRIBUTE)_\w+|\w+_(?:CLANG_TIDY|COMPILER_LAUNCHER|CPPCHECK|CPPLINT|INCLUDE_WHAT_YOU_USE|OUTPUT_NAME|POSTFIX|VISIBILITY_PRESET)|ABSTRACT|ADDITIONAL_MAKE_CLEAN_FILES|ADVANCED|ALIASED_TARGET|ALLOW_DUPLICATE_CUSTOM_TARGETS|ANDROID_(?:ANT_ADDITIONAL_OPTIONS|API|API_MIN|ARCH|ASSETS_DIRECTORIES|GUI|JAR_DEPENDENCIES|NATIVE_LIB_DEPENDENCIES|NATIVE_LIB_DIRECTORIES|PROCESS_MAX|PROGUARD|PROGUARD_CONFIG_PATH|SECURE_PROPS_PATH|SKIP_ANT_STEP|STL_TYPE)|ARCHIVE_OUTPUT_DIRECTORY|ATTACHED_FILES|ATTACHED_FILES_ON_FAIL|AUTOGEN_(?:BUILD_DIR|ORIGIN_DEPENDS|PARALLEL|SOURCE_GROUP|TARGETS_FOLDER|TARGET_DEPENDS)|AUTOMOC|AUTOMOC_(?:COMPILER_PREDEFINES|DEPEND_FILTERS|EXECUTABLE|MACRO_NAMES|MOC_OPTIONS|SOURCE_GROUP|TARGETS_FOLDER)|AUTORCC|AUTORCC_EXECUTABLE|AUTORCC_OPTIONS|AUTORCC_SOURCE_GROUP|AUTOUIC|AUTOUIC_EXECUTABLE|AUTOUIC_OPTIONS|AUTOUIC_SEARCH_PATHS|BINARY_DIR|BUILDSYSTEM_TARGETS|BUILD_RPATH|BUILD_RPATH_USE_ORIGIN|BUILD_WITH_INSTALL_NAME_DIR|BUILD_WITH_INSTALL_RPATH|BUNDLE|BUNDLE_EXTENSION|CACHE_VARIABLES|CLEAN_NO_CUSTOM|COMMON_LANGUAGE_RUNTIME|COMPATIBLE_INTERFACE_(?:BOOL|NUMBER_MAX|NUMBER_MIN|STRING)|COMPILE_(?:DEFINITIONS|FEATURES|FLAGS|OPTIONS|PDB_NAME|PDB_OUTPUT_DIRECTORY)|COST|CPACK_DESKTOP_SHORTCUTS|CPACK_NEVER_OVERWRITE|CPACK_PERMANENT|CPACK_STARTUP_SHORTCUTS|CPACK_START_MENU_SHORTCUTS|CPACK_WIX_ACL|CROSSCOMPILING_EMULATOR|CUDA_EXTENSIONS|CUDA_PTX_COMPILATION|CUDA_RESOLVE_DEVICE_SYMBOLS|CUDA_SEPARABLE_COMPILATION|CUDA_STANDARD|CUDA_STANDARD_REQUIRED|CXX_EXTENSIONS|CXX_STANDARD|CXX_STANDARD_REQUIRED|C_EXTENSIONS|C_STANDARD|C_STANDARD_REQUIRED|DEBUG_CONFIGURATIONS|DEFINE_SYMBOL|DEFINITIONS|DEPENDS|DEPLOYMENT_ADDITIONAL_FILES|DEPLOYMENT_REMOTE_DIRECTORY|DISABLED|DISABLED_FEATURES|ECLIPSE_EXTRA_CPROJECT_CONTENTS|ECLIPSE_EXTRA_NATURES|ENABLED_FEATURES|ENABLED_LANGUAGES|ENABLE_EXPORTS|ENVIRONMENT|EXCLUDE_FROM_ALL|EXCLUDE_FROM_DEFAULT_BUILD|EXPORT_NAME|EXPORT_PROPERTIES|EXTERNAL_OBJECT|EchoString|FAIL_REGULAR_EXPRESSION|FIND_LIBRARY_USE_LIB32_PATHS|FIND_LIBRARY_USE_LIB64_PATHS|FIND_LIBRARY_USE_LIBX32_PATHS|FIND_LIBRARY_USE_OPENBSD_VERSIONING|FIXTURES_CLEANUP|FIXTURES_REQUIRED|FIXTURES_SETUP|FOLDER|FRAMEWORK|Fortran_FORMAT|Fortran_MODULE_DIRECTORY|GENERATED|GENERATOR_FILE_NAME|GENERATOR_IS_MULTI_CONFIG|GHS_INTEGRITY_APP|GHS_NO_SOURCE_GROUP_FILE|GLOBAL_DEPENDS_DEBUG_MODE|GLOBAL_DEPENDS_NO_CYCLES|GNUtoMS|HAS_CXX|HEADER_FILE_ONLY|HELPSTRING|IMPLICIT_DEPENDS_INCLUDE_TRANSFORM|IMPORTED|IMPORTED_(?:COMMON_LANGUAGE_RUNTIME|CONFIGURATIONS|GLOBAL|IMPLIB|LIBNAME|LINK_DEPENDENT_LIBRARIES|LINK_INTERFACE_(?:LANGUAGES|LIBRARIES|MULTIPLICITY)|LOCATION|NO_SONAME|OBJECTS|SONAME)|IMPORT_PREFIX|IMPORT_SUFFIX|INCLUDE_DIRECTORIES|INCLUDE_REGULAR_EXPRESSION|INSTALL_NAME_DIR|INSTALL_RPATH|INSTALL_RPATH_USE_LINK_PATH|INTERFACE_(?:AUTOUIC_OPTIONS|COMPILE_DEFINITIONS|COMPILE_FEATURES|COMPILE_OPTIONS|INCLUDE_DIRECTORIES|LINK_DEPENDS|LINK_DIRECTORIES|LINK_LIBRARIES|LINK_OPTIONS|POSITION_INDEPENDENT_CODE|SOURCES|SYSTEM_INCLUDE_DIRECTORIES)|INTERPROCEDURAL_OPTIMIZATION|IN_TRY_COMPILE|IOS_INSTALL_COMBINED|JOB_POOLS|JOB_POOL_COMPILE|JOB_POOL_LINK|KEEP_EXTENSION|LABELS|LANGUAGE|LIBRARY_OUTPUT_DIRECTORY|LINKER_LANGUAGE|LINK_(?:DEPENDS|DEPENDS_NO_SHARED|DIRECTORIES|FLAGS|INTERFACE_LIBRARIES|INTERFACE_MULTIPLICITY|LIBRARIES|OPTIONS|SEARCH_END_STATIC|SEARCH_START_STATIC|WHAT_YOU_USE)|LISTFILE_STACK|LOCATION|MACOSX_BUNDLE|MACOSX_BUNDLE_INFO_PLIST|MACOSX_FRAMEWORK_INFO_PLIST|MACOSX_PACKAGE_LOCATION|MACOSX_RPATH|MACROS|MANUALLY_ADDED_DEPENDENCIES|MEASUREMENT|MODIFIED|NAME|NO_SONAME|NO_SYSTEM_FROM_IMPORTED|OBJECT_DEPENDS|OBJECT_OUTPUTS|OSX_ARCHITECTURES|OUTPUT_NAME|PACKAGES_FOUND|PACKAGES_NOT_FOUND|PARENT_DIRECTORY|PASS_REGULAR_EXPRESSION|PDB_NAME|PDB_OUTPUT_DIRECTORY|POSITION_INDEPENDENT_CODE|POST_INSTALL_SCRIPT|PREDEFINED_TARGETS_FOLDER|PREFIX|PRE_INSTALL_SCRIPT|PRIVATE_HEADER|PROCESSORS|PROCESSOR_AFFINITY|PROJECT_LABEL|PUBLIC_HEADER|REPORT_UNDEFINED_PROPERTIES|REQUIRED_FILES|RESOURCE|RESOURCE_LOCK|RULE_LAUNCH_COMPILE|RULE_LAUNCH_CUSTOM|RULE_LAUNCH_LINK|RULE_MESSAGES|RUNTIME_OUTPUT_DIRECTORY|RUN_SERIAL|SKIP_AUTOGEN|SKIP_AUTOMOC|SKIP_AUTORCC|SKIP_AUTOUIC|SKIP_BUILD_RPATH|SKIP_RETURN_CODE|SOURCES|SOURCE_DIR|SOVERSION|STATIC_LIBRARY_FLAGS|STATIC_LIBRARY_OPTIONS|STRINGS|SUBDIRECTORIES|SUFFIX|SYMBOLIC|TARGET_ARCHIVES_MAY_BE_SHARED_LIBS|TARGET_MESSAGES|TARGET_SUPPORTS_SHARED_LIBS|TESTS|TEST_INCLUDE_FILE|TEST_INCLUDE_FILES|TIMEOUT|TIMEOUT_AFTER_MATCH|TYPE|USE_FOLDERS|VALUE|VARIABLES|VERSION|VISIBILITY_INLINES_HIDDEN|VS_(?:CONFIGURATION_TYPE|COPY_TO_OUT_DIR|DEBUGGER_(?:COMMAND|COMMAND_ARGUMENTS|ENVIRONMENT|WORKING_DIRECTORY)|DEPLOYMENT_CONTENT|DEPLOYMENT_LOCATION|DOTNET_REFERENCES|DOTNET_REFERENCES_COPY_LOCAL|GLOBAL_KEYWORD|GLOBAL_PROJECT_TYPES|GLOBAL_ROOTNAMESPACE|INCLUDE_IN_VSIX|IOT_STARTUP_TASK|KEYWORD|RESOURCE_GENERATOR|SCC_AUXPATH|SCC_LOCALPATH|SCC_PROJECTNAME|SCC_PROVIDER|SDK_REFERENCES|SHADER_(?:DISABLE_OPTIMIZATIONS|ENABLE_DEBUG|ENTRYPOINT|FLAGS|MODEL|OBJECT_FILE_NAME|OUTPUT_HEADER_FILE|TYPE|VARIABLE_NAME)|STARTUP_PROJECT|TOOL_OVERRIDE|USER_PROPS|WINRT_COMPONENT|WINRT_EXTENSIONS|WINRT_REFERENCES|XAML_TYPE)|WILL_FAIL|WIN32_EXECUTABLE|WINDOWS_EXPORT_ALL_SYMBOLS|WORKING_DIRECTORY|WRAP_EXCLUDE|XCODE_(?:EMIT_EFFECTIVE_PLATFORM_NAME|EXPLICIT_FILE_TYPE|FILE_ATTRIBUTES|LAST_KNOWN_FILE_TYPE|PRODUCT_TYPE|SCHEME_(?:ADDRESS_SANITIZER|ADDRESS_SANITIZER_USE_AFTER_RETURN|ARGUMENTS|DISABLE_MAIN_THREAD_CHECKER|DYNAMIC_LIBRARY_LOADS|DYNAMIC_LINKER_API_USAGE|ENVIRONMENT|EXECUTABLE|GUARD_MALLOC|MAIN_THREAD_CHECKER_STOP|MALLOC_GUARD_EDGES|MALLOC_SCRIBBLE|MALLOC_STACK|THREAD_SANITIZER(?:_STOP)?|UNDEFINED_BEHAVIOUR_SANITIZER(?:_STOP)?|ZOMBIE_OBJECTS))|XCTEST)\b/,keyword:/\b(?:add_compile_definitions|add_compile_options|add_custom_command|add_custom_target|add_definitions|add_dependencies|add_executable|add_library|add_link_options|add_subdirectory|add_test|aux_source_directory|break|build_command|build_name|cmake_host_system_information|cmake_minimum_required|cmake_parse_arguments|cmake_policy|configure_file|continue|create_test_sourcelist|ctest_build|ctest_configure|ctest_coverage|ctest_empty_binary_directory|ctest_memcheck|ctest_read_custom_files|ctest_run_script|ctest_sleep|ctest_start|ctest_submit|ctest_test|ctest_update|ctest_upload|define_property|else|elseif|enable_language|enable_testing|endforeach|endfunction|endif|endmacro|endwhile|exec_program|execute_process|export|export_library_dependencies|file|find_file|find_library|find_package|find_path|find_program|fltk_wrap_ui|foreach|function|get_cmake_property|get_directory_property|get_filename_component|get_property|get_source_file_property|get_target_property|get_test_property|if|include|include_directories|include_external_msproject|include_guard|include_regular_expression|install|install_files|install_programs|install_targets|link_directories|link_libraries|list|load_cache|load_command|macro|make_directory|mark_as_advanced|math|message|option|output_required_files|project|qt_wrap_cpp|qt_wrap_ui|remove|remove_definitions|return|separate_arguments|set|set_directory_properties|set_property|set_source_files_properties|set_target_properties|set_tests_properties|site_name|source_group|string|subdir_depends|subdirs|target_compile_definitions|target_compile_features|target_compile_options|target_include_directories|target_link_directories|target_link_libraries|target_link_options|target_sources|try_compile|try_run|unset|use_mangled_mesa|utility_source|variable_requires|variable_watch|while|write_file)(?=\s*\()\b/,boolean:/\b(?:ON|OFF|TRUE|FALSE)\b/,namespace:/\b(?:PROPERTIES|SHARED|PRIVATE|STATIC|PUBLIC|INTERFACE|TARGET_OBJECTS)\b/,operator:/\b(?:NOT|AND|OR|MATCHES|LESS|GREATER|EQUAL|STRLESS|STRGREATER|STREQUAL|VERSION_LESS|VERSION_EQUAL|VERSION_GREATER|DEFINED)\b/,inserted:{pattern:/\b\w+::\w+\b/,alias:"class-name"},number:/\b\d+(?:\.\d+)*\b/,function:/\b[a-z_]\w*(?=\s*\()\b/i,punctuation:/[()>}]|\$[<{]/}}e.exports=t,t.displayName="cmake",t.aliases=[]},32762(e){"use strict";function t(e){e.languages.cobol={comment:{pattern:/\*>.*|(^[ \t]*)\*.*/m,lookbehind:!0,greedy:!0},string:{pattern:/[xzgn]?(?:"(?:[^\r\n"]|"")*"(?!")|'(?:[^\r\n']|'')*'(?!'))/i,greedy:!0},level:{pattern:/(^[ \t]*)\d+\b/m,lookbehind:!0,greedy:!0,alias:"number"},"class-name":{pattern:/(\bpic(?:ture)?\s+)(?:(?:[-\w$/,:*+<>]|\.(?!\s|$))(?:\(\d+\))?)+/i,lookbehind:!0,inside:{number:{pattern:/(\()\d+/,lookbehind:!0},punctuation:/[()]/}},keyword:{pattern:/(^|[^\w-])(?:ABORT|ACCEPT|ACCESS|ADD|ADDRESS|ADVANCING|AFTER|ALIGNED|ALL|ALPHABET|ALPHABETIC|ALPHABETIC-LOWER|ALPHABETIC-UPPER|ALPHANUMERIC|ALPHANUMERIC-EDITED|ALSO|ALTER|ALTERNATE|ANY|ARE|AREA|AREAS|AS|ASCENDING|ASCII|ASSIGN|ASSOCIATED-DATA|ASSOCIATED-DATA-LENGTH|AT|ATTRIBUTE|AUTHOR|AUTO|AUTO-SKIP|BACKGROUND-COLOR|BACKGROUND-COLOUR|BASIS|BEEP|BEFORE|BEGINNING|BELL|BINARY|BIT|BLANK|BLINK|BLOCK|BOUNDS|BOTTOM|BY|BYFUNCTION|BYTITLE|CALL|CANCEL|CAPABLE|CCSVERSION|CD|CF|CH|CHAINING|CHANGED|CHANNEL|CHARACTER|CHARACTERS|CLASS|CLASS-ID|CLOCK-UNITS|CLOSE|CLOSE-DISPOSITION|COBOL|CODE|CODE-SET|COLLATING|COL|COLUMN|COM-REG|COMMA|COMMITMENT|COMMON|COMMUNICATION|COMP|COMP-1|COMP-2|COMP-3|COMP-4|COMP-5|COMPUTATIONAL|COMPUTATIONAL-1|COMPUTATIONAL-2|COMPUTATIONAL-3|COMPUTATIONAL-4|COMPUTATIONAL-5|COMPUTE|CONFIGURATION|CONTAINS|CONTENT|CONTINUE|CONTROL|CONTROL-POINT|CONTROLS|CONVENTION|CONVERTING|COPY|CORR|CORRESPONDING|COUNT|CRUNCH|CURRENCY|CURSOR|DATA|DATA-BASE|DATE|DATE-COMPILED|DATE-WRITTEN|DAY|DAY-OF-WEEK|DBCS|DE|DEBUG-CONTENTS|DEBUG-ITEM|DEBUG-LINE|DEBUG-NAME|DEBUG-SUB-1|DEBUG-SUB-2|DEBUG-SUB-3|DEBUGGING|DECIMAL-POINT|DECLARATIVES|DEFAULT|DEFAULT-DISPLAY|DEFINITION|DELETE|DELIMITED|DELIMITER|DEPENDING|DESCENDING|DESTINATION|DETAIL|DFHRESP|DFHVALUE|DISABLE|DISK|DISPLAY|DISPLAY-1|DIVIDE|DIVISION|DONTCARE|DOUBLE|DOWN|DUPLICATES|DYNAMIC|EBCDIC|EGCS|EGI|ELSE|EMI|EMPTY-CHECK|ENABLE|END|END-ACCEPT|END-ADD|END-CALL|END-COMPUTE|END-DELETE|END-DIVIDE|END-EVALUATE|END-IF|END-MULTIPLY|END-OF-PAGE|END-PERFORM|END-READ|END-RECEIVE|END-RETURN|END-REWRITE|END-SEARCH|END-START|END-STRING|END-SUBTRACT|END-UNSTRING|END-WRITE|ENDING|ENTER|ENTRY|ENTRY-PROCEDURE|ENVIRONMENT|EOP|ERASE|ERROR|EOL|EOS|ESCAPE|ESI|EVALUATE|EVENT|EVERY|EXCEPTION|EXCLUSIVE|EXHIBIT|EXIT|EXPORT|EXTEND|EXTENDED|EXTERNAL|FD|FILE|FILE-CONTROL|FILLER|FINAL|FIRST|FOOTING|FOR|FOREGROUND-COLOR|FOREGROUND-COLOUR|FROM|FULL|FUNCTION|FUNCTIONNAME|FUNCTION-POINTER|GENERATE|GOBACK|GIVING|GLOBAL|GO|GRID|GROUP|HEADING|HIGHLIGHT|HIGH-VALUE|HIGH-VALUES|I-O|I-O-CONTROL|ID|IDENTIFICATION|IF|IMPLICIT|IMPORT|IN|INDEX|INDEXED|INDICATE|INITIAL|INITIALIZE|INITIATE|INPUT|INPUT-OUTPUT|INSPECT|INSTALLATION|INTEGER|INTO|INVALID|INVOKE|IS|JUST|JUSTIFIED|KANJI|KEPT|KEY|KEYBOARD|LABEL|LANGUAGE|LAST|LB|LD|LEADING|LEFT|LEFTLINE|LENGTH|LENGTH-CHECK|LIBACCESS|LIBPARAMETER|LIBRARY|LIMIT|LIMITS|LINAGE|LINAGE-COUNTER|LINE|LINES|LINE-COUNTER|LINKAGE|LIST|LOCAL|LOCAL-STORAGE|LOCK|LONG-DATE|LONG-TIME|LOWER|LOWLIGHT|LOW-VALUE|LOW-VALUES|MEMORY|MERGE|MESSAGE|MMDDYYYY|MODE|MODULES|MORE-LABELS|MOVE|MULTIPLE|MULTIPLY|NAMED|NATIONAL|NATIONAL-EDITED|NATIVE|NEGATIVE|NETWORK|NEXT|NO|NO-ECHO|NULL|NULLS|NUMBER|NUMERIC|NUMERIC-DATE|NUMERIC-EDITED|NUMERIC-TIME|OBJECT-COMPUTER|OCCURS|ODT|OF|OFF|OMITTED|ON|OPEN|OPTIONAL|ORDER|ORDERLY|ORGANIZATION|OTHER|OUTPUT|OVERFLOW|OVERLINE|OWN|PACKED-DECIMAL|PADDING|PAGE|PAGE-COUNTER|PASSWORD|PERFORM|PF|PH|PIC|PICTURE|PLUS|POINTER|POSITION|POSITIVE|PORT|PRINTER|PRINTING|PRIVATE|PROCEDURE|PROCEDURE-POINTER|PROCEDURES|PROCEED|PROCESS|PROGRAM|PROGRAM-ID|PROGRAM-LIBRARY|PROMPT|PURGE|QUEUE|QUOTE|QUOTES|RANDOM|READER|REMOTE|RD|REAL|READ|RECEIVE|RECEIVED|RECORD|RECORDING|RECORDS|RECURSIVE|REDEFINES|REEL|REF|REFERENCE|REFERENCES|RELATIVE|RELEASE|REMAINDER|REMARKS|REMOVAL|REMOVE|RENAMES|REPLACE|REPLACING|REPORT|REPORTING|REPORTS|REQUIRED|RERUN|RESERVE|REVERSE-VIDEO|RESET|RETURN|RETURN-CODE|RETURNING|REVERSED|REWIND|REWRITE|RF|RH|RIGHT|ROUNDED|RUN|SAME|SAVE|SCREEN|SD|SEARCH|SECTION|SECURE|SECURITY|SEGMENT|SEGMENT-LIMIT|SELECT|SEND|SENTENCE|SEPARATE|SEQUENCE|SEQUENTIAL|SET|SHARED|SHAREDBYALL|SHAREDBYRUNUNIT|SHARING|SHIFT-IN|SHIFT-OUT|SHORT-DATE|SIGN|SIZE|SORT|SORT-CONTROL|SORT-CORE-SIZE|SORT-FILE-SIZE|SORT-MERGE|SORT-MESSAGE|SORT-MODE-SIZE|SORT-RETURN|SOURCE|SOURCE-COMPUTER|SPACE|SPACES|SPECIAL-NAMES|STANDARD|STANDARD-1|STANDARD-2|START|STATUS|STOP|STRING|SUB-QUEUE-1|SUB-QUEUE-2|SUB-QUEUE-3|SUBTRACT|SUM|SUPPRESS|SYMBOL|SYMBOLIC|SYNC|SYNCHRONIZED|TABLE|TALLY|TALLYING|TASK|TAPE|TERMINAL|TERMINATE|TEST|TEXT|THEN|THREAD|THREAD-LOCAL|THROUGH|THRU|TIME|TIMER|TIMES|TITLE|TO|TODAYS-DATE|TODAYS-NAME|TOP|TRAILING|TRUNCATED|TYPE|TYPEDEF|UNDERLINE|UNIT|UNSTRING|UNTIL|UP|UPON|USAGE|USE|USING|VALUE|VALUES|VARYING|VIRTUAL|WAIT|WHEN|WHEN-COMPILED|WITH|WORDS|WORKING-STORAGE|WRITE|YEAR|YYYYMMDD|YYYYDDD|ZERO-FILL|ZEROS|ZEROES)(?![\w-])/i,lookbehind:!0},boolean:{pattern:/(^|[^\w-])(?:false|true)(?![\w-])/i,lookbehind:!0},number:{pattern:/(^|[^\w-])(?:[+-]?(?:(?:\d+(?:[.,]\d+)?|[.,]\d+)(?:e[+-]?\d+)?|zero))(?![\w-])/i,lookbehind:!0},operator:[/<>|[<>]=?|[=+*/&]/,{pattern:/(^|[^\w-])(?:-|and|equal|greater|less|not|or|than)(?![\w-])/i,lookbehind:!0}],punctuation:/[.:,()]/}}e.exports=t,t.displayName="cobol",t.aliases=[]},43576(e){"use strict";function t(e){var t,n,r;n=/#(?!\{).+/,r={pattern:/#\{[^}]+\}/,alias:"variable"},(t=e).languages.coffeescript=t.languages.extend("javascript",{comment:n,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:r}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),t.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:n,interpolation:r}}}),t.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:t.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:r}}]}),t.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete t.languages.coffeescript["template-string"],t.languages.coffee=t.languages.coffeescript}e.exports=t,t.displayName="coffeescript",t.aliases=["coffee"]},71794(e){"use strict";function t(e){e.languages.concurnas={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],langext:{pattern:/\b\w+\s*\|\|[\s\S]+?\|\|/,greedy:!0,alias:"string"},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/,lookbehind:!0},keyword:/\b(?:abstract|actor|also|annotation|assert|async|await|bool|boolean|break|byte|case|catch|changed|char|class|closed|constant|continue|def|default|del|double|elif|else|enum|every|extends|false|finally|float|for|from|global|gpudef|gpukernel|if|import|in|init|inject|int|lambda|local|long|loop|match|new|nodefault|null|of|onchange|open|out|override|package|parfor|parforsync|post|pre|private|protected|provide|provider|public|return|shared|short|single|size_t|sizeof|super|sync|this|throw|trait|trans|transient|true|try|typedef|unchecked|using|val|var|void|while|with)\b/,boolean:/\b(?:false|true)\b/,number:/\b0b[01][01_]*L?\b|\b0x(?:[\da-f_]*\.)?[\da-f_p+-]+\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfls]?/i,punctuation:/[{}[\];(),.:]/,operator:/<==|>==|=>|->|<-|<>|\^|&==|&<>|!|\?:?|\.\?|\+\+|--|[-+*/=<>]=?|\b(?:and|as|band|bor|bxor|comp|is|isnot|mod|or)\b=?/,annotation:{pattern:/@(?:\w+:)?(?:\w+|\[[^\]]+\])?/,alias:"builtin"}},e.languages.insertBefore("concurnas","langext",{string:{pattern:/[rs]?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:e.languages.concurnas},string:/[\s\S]+/}}}),e.languages.conc=e.languages.concurnas}e.exports=t,t.displayName="concurnas",t.aliases=["conc"]},1315(e){"use strict";function t(e){!function(e){for(var t=/\(\*(?:[^(*]|\((?!\*)|\*(?!\))|)*\*\)/.source,n=0;n<2;n++)t=t.replace(//g,function(){return t});t=t.replace(//g,"[]"),e.languages.coq={comment:RegExp(t),string:{pattern:/"(?:[^"]|"")*"(?!")/,greedy:!0},attribute:[{pattern:RegExp(/#\[(?:[^\]("]|"(?:[^"]|"")*"(?!")|\((?!\*)|)*\]/.source.replace(//g,function(){return t})),greedy:!0,alias:"attr-name",inside:{comment:RegExp(t),string:{pattern:/"(?:[^"]|"")*"(?!")/,greedy:!0},operator:/=/,punctuation:/^#\[|\]$|[,()]/}},{pattern:/\b(?:Cumulative|Global|Local|Monomorphic|NonCumulative|Polymorphic|Private|Program)\b/,alias:"attr-name"}],keyword:/\b(?:_|Abort|About|Add|Admit|Admitted|All|apply|Arguments|as|As|Assumptions|at|Axiom|Axioms|Back|BackTo|Backtrace|Bind|BinOp|BinOpSpec|BinRel|Blacklist|by|Canonical|Case|Cd|Check|Class|Classes|Close|Coercion|Coercions|cofix|CoFixpoint|CoInductive|Collection|Combined|Compute|Conjecture|Conjectures|Constant|Constants|Constraint|Constructors|Context|Corollary|Create|CstOp|Custom|Cut|Debug|Declare|Defined|Definition|Delimit|Dependencies|Dependent|Derive|Diffs|Drop|Elimination|else|end|End|Entry|Equality|Eval|Example|Existential|Existentials|Existing|exists|exists2|Export|Extern|Extraction|Fact|Fail|Field|File|Firstorder|fix|Fixpoint|Flags|Focus|for|forall|From|fun|Funclass|Function|Functional|GC|Generalizable|Goal|Grab|Grammar|Graph|Guarded|Haskell|Heap|Hide|Hint|HintDb|Hints|Hypotheses|Hypothesis|Identity|if|IF|Immediate|Implicit|Implicits|Import|in|Include|Induction|Inductive|Infix|Info|Initial|InjTyp|Inline|Inspect|Instance|Instances|Intro|Intros|Inversion|Inversion_clear|JSON|Language|Left|Lemma|let|Let|Lia|Libraries|Library|Load|LoadPath|Locate|Ltac|Ltac2|match|Match|measure|Method|Minimality|ML|Module|Modules|Morphism|move|Next|NoInline|Notation|Number|Obligation|Obligations|OCaml|Opaque|Open|Optimize|Parameter|Parameters|Parametric|Path|Paths|Prenex|Preterm|Primitive|Print|Profile|Projections|Proof|Prop|PropBinOp|Property|PropOp|Proposition|PropUOp|Pwd|Qed|Quit|Rec|Record|Recursive|Redirect|Reduction|Register|Relation|Remark|Remove|removed|Require|Reserved|Reset|Resolve|Restart|return|Rewrite|Right|Ring|Rings|Saturate|Save|Scheme|Scope|Scopes|Search|SearchHead|SearchPattern|SearchRewrite|Section|Separate|Set|Setoid|Show|Signatures|Solve|Solver|Sort|Sortclass|Sorted|Spec|SProp|Step|Strategies|Strategy|String|struct|Structure|SubClass|Subgraph|SuchThat|Tactic|Term|TestCompile|then|Theorem|Time|Timeout|To|Transparent|Type|Typeclasses|Types|Typing|Undelimit|Undo|Unfocus|Unfocused|Unfold|Universe|Universes|UnOp|UnOpSpec|Unshelve|using|Variable|Variables|Variant|Verbose|View|Visibility|wf|where|with|Zify)\b/,number:/\b(?:0x[a-f0-9][a-f0-9_]*(?:\.[a-f0-9_]+)?(?:p[+-]?\d[\d_]*)?|\d[\d_]*(?:\.[\d_]+)?(?:e[+-]?\d[\d_]*)?)\b/i,punct:{pattern:/@\{|\{\||\[=|:>/,alias:"punctuation"},operator:/\/\\|\\\/|\.{2,3}|:{1,2}=|\*\*|[-=]>|<(?:->?|[+:=>]|<:)|>(?:=|->)|\|[-|]?|[-!%&*+/<=>?@^~']/,punctuation:/\.\(|`\(|@\{|`\{|\{\||\[=|:>|[:.,;(){}\[\]]/}}(e)}e.exports=t,t.displayName="coq",t.aliases=[]},80096(e,t,n){"use strict";var r=n(65806);function i(e){var t,n,i;e.register(r),t=e,n=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char8_t|char16_t|char32_t|class|compl|concept|const|consteval|constexpr|constinit|const_cast|continue|co_await|co_return|co_yield|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,i=/\b(?!)\w+(?:\s*\.\s*\w+)*\b/.source.replace(//g,function(){return n.source}),t.languages.cpp=t.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!)\w+/.source.replace(//g,function(){return n.source})),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:n,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:true|false)\b/}),t.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:module|import)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/(?:\s*:\s*)?|:\s*/.source.replace(//g,function(){return i})+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),t.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b[a-z_]\w*\s*<(?:[^<>]|<(?:[^<>])*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:t.languages.cpp}}}}),t.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),t.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:t.languages.extend("cpp",{})}}),t.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},t.languages.cpp["base-clause"])}e.exports=i,i.displayName="cpp",i.aliases=[]},99176(e,t,n){"use strict";var r=n(56939);function i(e){var t;e.register(r),(t=e).languages.crystal=t.languages.extend("ruby",{keyword:[/\b(?:abstract|alias|as|asm|begin|break|case|class|def|do|else|elsif|end|ensure|enum|extend|for|fun|if|include|instance_sizeof|lib|macro|module|next|of|out|pointerof|private|protected|rescue|return|require|select|self|sizeof|struct|super|then|type|typeof|uninitialized|union|unless|until|when|while|with|yield|__DIR__|__END_LINE__|__FILE__|__LINE__)\b/,{pattern:/(\.\s*)(?:is_a|responds_to)\?/,lookbehind:!0}],number:/\b(?:0b[01_]*[01]|0o[0-7_]*[0-7]|0x[\da-fA-F_]*[\da-fA-F]|(?:\d(?:[\d_]*\d)?)(?:\.[\d_]*\d)?(?:[eE][+-]?[\d_]*\d)?)(?:_(?:[uif](?:8|16|32|64))?)?\b/}),t.languages.insertBefore("crystal","string",{attribute:{pattern:/@\[.+?\]/,alias:"attr-name",inside:{delimiter:{pattern:/^@\[|\]$/,alias:"tag"},rest:t.languages.crystal}},expansion:[{pattern:/\{\{.+?\}\}/,inside:{delimiter:{pattern:/^\{\{|\}\}$/,alias:"tag"},rest:t.languages.crystal}},{pattern:/\{%.+?%\}/,inside:{delimiter:{pattern:/^\{%|%\}$/,alias:"tag"},rest:t.languages.crystal}}]})}e.exports=i,i.displayName="crystal",i.aliases=[]},61958(e){"use strict";function t(e){!function(e){function t(e,t){return e.replace(/<<(\d+)>>/g,function(e,n){return"(?:"+t[+n]+")"})}function n(e,n,r){return RegExp(t(e,n),r||"")}function r(e,t){for(var n=0;n>/g,function(){return"(?:"+e+")"});return e.replace(/<>/g,"[^\\s\\S]")}var i={type:"bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",typeDeclaration:"class enum interface struct",contextual:"add alias and ascending async await by descending from get global group into join let nameof not notnull on or orderby partial remove select set unmanaged value when where",other:"abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield"};function a(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var o=a(i.typeDeclaration),s=RegExp(a(i.type+" "+i.typeDeclaration+" "+i.contextual+" "+i.other)),u=a(i.typeDeclaration+" "+i.contextual+" "+i.other),c=a(i.type+" "+i.typeDeclaration+" "+i.other),l=r(/<(?:[^<>;=+\-*/%&|^]|<>)*>/.source,2),f=r(/\((?:[^()]|<>)*\)/.source,2),d=/@?\b[A-Za-z_]\w*\b/.source,h=t(/<<0>>(?:\s*<<1>>)?/.source,[d,l]),p=t(/(?!<<0>>)<<1>>(?:\s*\.\s*<<1>>)*/.source,[u,h]),b=/\[\s*(?:,\s*)*\]/.source,m=t(/<<0>>(?:\s*(?:\?\s*)?<<1>>)*(?:\s*\?)?/.source,[p,b]),g=t(/[^,()<>[\];=+\-*/%&|^]|<<0>>|<<1>>|<<2>>/.source,[l,f,b]),v=t(/\(<<0>>+(?:,<<0>>+)+\)/.source,[g]),y=t(/(?:<<0>>|<<1>>)(?:\s*(?:\?\s*)?<<2>>)*(?:\s*\?)?/.source,[v,p,b]),w={keyword:s,punctuation:/[<>()?,.:[\]]/},_=/'(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'/.source,E=/"(?:\\.|[^\\"\r\n])*"/.source,S=/@"(?:""|\\[\s\S]|[^\\"])*"(?!")/.source;e.languages.csharp=e.languages.extend("clike",{string:[{pattern:n(/(^|[^$\\])<<0>>/.source,[S]),lookbehind:!0,greedy:!0},{pattern:n(/(^|[^@$\\])<<0>>/.source,[E]),lookbehind:!0,greedy:!0},{pattern:RegExp(_),greedy:!0,alias:"character"}],"class-name":[{pattern:n(/(\busing\s+static\s+)<<0>>(?=\s*;)/.source,[p]),lookbehind:!0,inside:w},{pattern:n(/(\busing\s+<<0>>\s*=\s*)<<1>>(?=\s*;)/.source,[d,y]),lookbehind:!0,inside:w},{pattern:n(/(\busing\s+)<<0>>(?=\s*=)/.source,[d]),lookbehind:!0},{pattern:n(/(\b<<0>>\s+)<<1>>/.source,[o,h]),lookbehind:!0,inside:w},{pattern:n(/(\bcatch\s*\(\s*)<<0>>/.source,[p]),lookbehind:!0,inside:w},{pattern:n(/(\bwhere\s+)<<0>>/.source,[d]),lookbehind:!0},{pattern:n(/(\b(?:is(?:\s+not)?|as)\s+)<<0>>/.source,[m]),lookbehind:!0,inside:w},{pattern:n(/\b<<0>>(?=\s+(?!<<1>>)<<2>>(?:\s*[=,;:{)\]]|\s+(?:in|when)\b))/.source,[y,c,d]),inside:w}],keyword:s,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:ul|lu|[dflmu])?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),e.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),e.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:n(/([(,]\s*)<<0>>(?=\s*:)/.source,[d]),lookbehind:!0,alias:"punctuation"}}),e.languages.insertBefore("csharp","class-name",{namespace:{pattern:n(/(\b(?:namespace|using)\s+)<<0>>(?:\s*\.\s*<<0>>)*(?=\s*[;{])/.source,[d]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:n(/(\b(?:default|typeof|sizeof)\s*\(\s*(?!\s))(?:[^()\s]|\s(?!\s)|<<0>>)*(?=\s*\))/.source,[f]),lookbehind:!0,alias:"class-name",inside:w},"return-type":{pattern:n(/<<0>>(?=\s+(?:<<1>>\s*(?:=>|[({]|\.\s*this\s*\[)|this\s*\[))/.source,[y,p]),inside:w,alias:"class-name"},"constructor-invocation":{pattern:n(/(\bnew\s+)<<0>>(?=\s*[[({])/.source,[y]),lookbehind:!0,inside:w,alias:"class-name"},"generic-method":{pattern:n(/<<0>>\s*<<1>>(?=\s*\()/.source,[d,l]),inside:{function:n(/^<<0>>/.source,[d]),generic:{pattern:RegExp(l),alias:"class-name",inside:w}}},"type-list":{pattern:n(/\b((?:<<0>>\s+<<1>>|where\s+<<2>>)\s*:\s*)(?:<<3>>|<<4>>)(?:\s*,\s*(?:<<3>>|<<4>>))*(?=\s*(?:where|[{;]|=>|$))/.source,[o,h,d,y,s.source]),lookbehind:!0,inside:{keyword:s,"class-name":{pattern:RegExp(y),greedy:!0,inside:w},punctuation:/,/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var k=E+"|"+_,x=t(/\/(?![*/])|\/\/[^\r\n]*[\r\n]|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>/.source,[k]),T=r(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[x]),2),M=/\b(?:assembly|event|field|method|module|param|property|return|type)\b/.source,O=t(/<<0>>(?:\s*\(<<1>>*\))?/.source,[p,T]);e.languages.insertBefore("csharp","class-name",{attribute:{pattern:n(/((?:^|[^\s\w>)?])\s*\[\s*)(?:<<0>>\s*:\s*)?<<1>>(?:\s*,\s*<<1>>)*(?=\s*\])/.source,[M,O]),lookbehind:!0,greedy:!0,inside:{target:{pattern:n(/^<<0>>(?=\s*:)/.source,[M]),alias:"keyword"},"attribute-arguments":{pattern:n(/\(<<0>>*\)/.source,[T]),inside:e.languages.csharp},"class-name":{pattern:RegExp(p),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var A=/:[^}\r\n]+/.source,L=r(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[x]),2),C=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[L,A]),I=r(t(/[^"'/()]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>|\(<>*\)/.source,[k]),2),D=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[I,A]);function N(t,r){return{interpolation:{pattern:n(/((?:^|[^{])(?:\{\{)*)<<0>>/.source,[t]),lookbehind:!0,inside:{"format-string":{pattern:n(/(^\{(?:(?![}:])<<0>>)*)<<1>>(?=\}$)/.source,[r,A]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:e.languages.csharp}}},string:/[\s\S]+/}}e.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:n(/(^|[^\\])(?:\$@|@\$)"(?:""|\\[\s\S]|\{\{|<<0>>|[^\\{"])*"/.source,[C]),lookbehind:!0,greedy:!0,inside:N(C,L)},{pattern:n(/(^|[^@\\])\$"(?:\\.|\{\{|<<0>>|[^\\"{])*"/.source,[D]),lookbehind:!0,greedy:!0,inside:N(D,I)}]})}(e),e.languages.dotnet=e.languages.cs=e.languages.csharp}e.exports=t,t.displayName="csharp",t.aliases=["dotnet","cs"]},65447(e){"use strict";function t(e){e.languages.csp={directive:{pattern:/(^|[^-\da-z])(?:base-uri|block-all-mixed-content|(?:child|connect|default|font|frame|img|manifest|media|object|prefetch|script|style|worker)-src|disown-opener|form-action|frame-(?:ancestors|options)|input-protection(?:-(?:clip|selectors))?|navigate-to|plugin-types|policy-uri|referrer|reflected-xss|report-(?:to|uri)|require-sri-for|sandbox|(?:script|style)-src-(?:attr|elem)|upgrade-insecure-requests)(?=[^-\da-z]|$)/i,lookbehind:!0,alias:"keyword"},safe:{pattern:/'(?:deny|none|report-sample|self|strict-dynamic|top-only|(?:nonce|sha(?:256|384|512))-[-+/\w=]+)'/i,alias:"selector"},unsafe:{pattern:/(?:'unsafe-(?:allow-redirects|dynamic|eval|hash-attributes|hashed-attributes|hashes|inline)'|\*)/i,alias:"function"}}}e.exports=t,t.displayName="csp",t.aliases=[]},4762(e){"use strict";function t(e){var t,n,r,i,a;r=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,(t=e).languages.css.selector={pattern:t.languages.css.selector.pattern,lookbehind:!0,inside:n={"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+/,class:/\.[-\w]+/,id:/#[-\w]+/,attribute:{pattern:RegExp("\\[(?:[^[\\]\"']|"+r.source+")*\\]"),greedy:!0,inside:{punctuation:/^\[|\]$/,"case-sensitivity":{pattern:/(\s)[si]$/i,lookbehind:!0,alias:"keyword"},namespace:{pattern:/^(\s*)(?:(?!\s)[-*\w\xA0-\uFFFF])*\|(?!=)/,lookbehind:!0,inside:{punctuation:/\|$/}},"attr-name":{pattern:/^(\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+/,lookbehind:!0},"attr-value":[r,{pattern:/(=\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+(?=\s*$)/,lookbehind:!0}],operator:/[|~*^$]?=/}},"n-th":[{pattern:/(\(\s*)[+-]?\d*[\dn](?:\s*[+-]\s*\d+)?(?=\s*\))/,lookbehind:!0,inside:{number:/[\dn]+/,operator:/[+-]/}},{pattern:/(\(\s*)(?:even|odd)(?=\s*\))/i,lookbehind:!0}],combinator:/>|\+|~|\|\|/,punctuation:/[(),]/}},t.languages.css.atrule.inside["selector-function-argument"].inside=n,t.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}}),i={pattern:/(\b\d+)(?:%|[a-z]+\b)/,lookbehind:!0},a={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0},t.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[/\b(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)\b/i,{pattern:/\b(?:rgb|hsl)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:rgb|hsl)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:i,number:a,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:i,number:a})}e.exports=t,t.displayName="cssExtras",t.aliases=[]},12049(e){"use strict";function t(e){var t,n,r;n=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/,(t=e).languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+n.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+n.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+n.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:n,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},t.languages.css.atrule.inside.rest=t.languages.css,(r=t.languages.markup)&&(r.tag.addInlined("style","css"),r.tag.addAttribute("style","css"))}e.exports=t,t.displayName="css",t.aliases=[]},78090(e){"use strict";function t(e){e.languages.csv={value:/[^\r\n,"]+|"(?:[^"]|"")*"(?!")/,punctuation:/,/}}e.exports=t,t.displayName="csv",t.aliases=[]},40315(e){"use strict";function t(e){e.languages.cypher={comment:/\/\/.*/,string:{pattern:/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/,greedy:!0},"class-name":{pattern:/(:\s*)(?:\w+|`(?:[^`\\\r\n])*`)(?=\s*[{):])/,lookbehind:!0,greedy:!0},relationship:{pattern:/(-\[\s*(?:\w+\s*|`(?:[^`\\\r\n])*`\s*)?:\s*|\|\s*:\s*)(?:\w+|`(?:[^`\\\r\n])*`)/,lookbehind:!0,greedy:!0,alias:"property"},identifier:{pattern:/`(?:[^`\\\r\n])*`/,greedy:!0,alias:"symbol"},variable:/\$\w+/,keyword:/\b(?:ADD|ALL|AND|AS|ASC|ASCENDING|ASSERT|BY|CALL|CASE|COMMIT|CONSTRAINT|CONTAINS|CREATE|CSV|DELETE|DESC|DESCENDING|DETACH|DISTINCT|DO|DROP|ELSE|END|ENDS|EXISTS|FOR|FOREACH|IN|INDEX|IS|JOIN|KEY|LIMIT|LOAD|MANDATORY|MATCH|MERGE|NODE|NOT|OF|ON|OPTIONAL|OR|ORDER(?=\s+BY)|PERIODIC|REMOVE|REQUIRE|RETURN|SCALAR|SCAN|SET|SKIP|START|STARTS|THEN|UNION|UNIQUE|UNWIND|USING|WHEN|WHERE|WITH|XOR|YIELD)\b/i,function:/\b\w+\b(?=\s*\()/,boolean:/\b(?:true|false|null)\b/i,number:/\b(?:0x[\da-fA-F]+|\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/,operator:/:|<--?|--?>?|<>|=~?|[<>]=?|[+*/%^|]|\.\.\.?/,punctuation:/[()[\]{},;.]/}}e.exports=t,t.displayName="cypher",t.aliases=[]},7902(e){"use strict";function t(e){e.languages.d=e.languages.extend("clike",{comment:[{pattern:/^\s*#!.+/,greedy:!0},{pattern:RegExp(/(^|[^\\])/.source+"(?:"+[/\/\+(?:\/\+(?:[^+]|\+(?!\/))*\+\/|(?!\/\+)[\s\S])*?\+\//.source,/\/\/.*/.source,/\/\*[\s\S]*?\*\//.source].join("|")+")"),lookbehind:!0,greedy:!0}],string:[{pattern:RegExp([/\b[rx]"(?:\\[\s\S]|[^\\"])*"[cwd]?/.source,/\bq"(?:\[[\s\S]*?\]|\([\s\S]*?\)|<[\s\S]*?>|\{[\s\S]*?\})"/.source,/\bq"((?!\d)\w+)$[\s\S]*?^\1"/.source,/\bq"(.)[\s\S]*?\2"/.source,/'(?:\\(?:\W|\w+)|[^\\])'/.source,/(["`])(?:\\[\s\S]|(?!\3)[^\\])*\3[cwd]?/.source].join("|"),"m"),greedy:!0},{pattern:/\bq\{(?:\{[^{}]*\}|[^{}])*\}/,greedy:!0,alias:"token-string"}],keyword:/\$|\b(?:abstract|alias|align|asm|assert|auto|body|bool|break|byte|case|cast|catch|cdouble|cent|cfloat|char|class|const|continue|creal|dchar|debug|default|delegate|delete|deprecated|do|double|else|enum|export|extern|false|final|finally|float|for|foreach|foreach_reverse|function|goto|idouble|if|ifloat|immutable|import|inout|int|interface|invariant|ireal|lazy|long|macro|mixin|module|new|nothrow|null|out|override|package|pragma|private|protected|public|pure|real|ref|return|scope|shared|short|static|struct|super|switch|synchronized|template|this|throw|true|try|typedef|typeid|typeof|ubyte|ucent|uint|ulong|union|unittest|ushort|version|void|volatile|wchar|while|with|__(?:(?:FILE|MODULE|LINE|FUNCTION|PRETTY_FUNCTION|DATE|EOF|TIME|TIMESTAMP|VENDOR|VERSION)__|gshared|traits|vector|parameters)|string|wstring|dstring|size_t|ptrdiff_t)\b/,number:[/\b0x\.?[a-f\d_]+(?:(?!\.\.)\.[a-f\d_]*)?(?:p[+-]?[a-f\d_]+)?[ulfi]{0,4}/i,{pattern:/((?:\.\.)?)(?:\b0b\.?|\b|\.)\d[\d_]*(?:(?!\.\.)\.[\d_]*)?(?:e[+-]?\d[\d_]*)?[ulfi]{0,4}/i,lookbehind:!0}],operator:/\|[|=]?|&[&=]?|\+[+=]?|-[-=]?|\.?\.\.|=[>=]?|!(?:i[ns]\b|<>?=?|>=?|=)?|\bi[ns]\b|(?:<[<>]?|>>?>?|\^\^|[*\/%^~])=?/}),e.languages.insertBefore("d","keyword",{property:/\B@\w*/}),e.languages.insertBefore("d","function",{register:{pattern:/\b(?:[ABCD][LHX]|E[ABCD]X|E?(?:BP|SP|DI|SI)|[ECSDGF]S|CR[0234]|DR[012367]|TR[3-7]|X?MM[0-7]|R[ABCD]X|[BS]PL|R[BS]P|[DS]IL|R[DS]I|R(?:[89]|1[0-5])[BWD]?|XMM(?:[89]|1[0-5])|YMM(?:1[0-5]|\d))\b|\bST(?:\([0-7]\)|\b)/,alias:"variable"}})}e.exports=t,t.displayName="d",t.aliases=[]},28651(e){"use strict";function t(e){var t,n,r,i;t=e,n=[/\b(?:async|sync|yield)\*/,/\b(?:abstract|assert|async|await|break|case|catch|class|const|continue|covariant|default|deferred|do|dynamic|else|enum|export|extension|external|extends|factory|final|finally|for|get|hide|if|implements|interface|import|in|library|mixin|new|null|on|operator|part|rethrow|return|set|show|static|super|switch|sync|this|throw|try|typedef|var|void|while|with|yield)\b/],i={pattern:RegExp((r=/(^|[^\w.])(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source)+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}}}},t.languages.dart=t.languages.extend("clike",{string:[{pattern:/r?("""|''')[\s\S]*?\1/,greedy:!0},{pattern:/r?(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0}],"class-name":[i,{pattern:RegExp(r+/[A-Z]\w*(?=\s+\w+\s*[;,=()])/.source),lookbehind:!0,inside:i.inside}],keyword:n,operator:/\bis!|\b(?:as|is)\b|\+\+|--|&&|\|\||<<=?|>>=?|~(?:\/=?)?|[+\-*\/%&^|=!<>]=?|\?/}),t.languages.insertBefore("dart","function",{metadata:{pattern:/@\w+/,alias:"symbol"}}),t.languages.insertBefore("dart","class-name",{generics:{pattern:/<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<[\w\s,.&?]*>)*>)*>)*>/,inside:{"class-name":i,keyword:n,punctuation:/[<>(),.:]/,operator:/[?&|]/}}})}e.exports=t,t.displayName="dart",t.aliases=[]},55579(e){"use strict";function t(e){var t;(t=e).languages.dataweave={url:/\b[A-Za-z]+:\/\/[\w/:.?=&-]+|\burn:[\w:.?=&-]+/,property:{pattern:/(?:\b\w+#)?(?:"(?:\\.|[^\\"\r\n])*"|\b\w+)(?=\s*[:@])/,greedy:!0},string:{pattern:/(["'`])(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0},"mime-type":/\b(?:text|audio|video|application|multipart|image)\/[\w+-]+/,date:{pattern:/\|[\w:+-]+\|/,greedy:!0},comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],regex:{pattern:/\/(?:[^\\\/\r\n]|\\[^\r\n])+\//,greedy:!0},function:/\b[A-Z_]\w*(?=\s*\()/i,number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\];(),.:@]/,operator:/<<|>>|->|[<>~=]=?|!=|--?-?|\+\+?|!|\?/,boolean:/\b(?:true|false)\b/,keyword:/\b(?:match|input|output|ns|type|update|null|if|else|using|unless|at|is|as|case|do|fun|var|not|and|or)\b/}}e.exports=t,t.displayName="dataweave",t.aliases=[]},93685(e){"use strict";function t(e){e.languages.dax={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/).*)/,lookbehind:!0},"data-field":{pattern:/'(?:[^']|'')*'(?!')(?:\[[ \w\xA0-\uFFFF]+\])?|\w+\[[ \w\xA0-\uFFFF]+\]/,alias:"symbol"},measure:{pattern:/\[[ \w\xA0-\uFFFF]+\]/,alias:"constant"},string:{pattern:/"(?:[^"]|"")*"(?!")/,greedy:!0},function:/\b(?:ABS|ACOS|ACOSH|ACOT|ACOTH|ADDCOLUMNS|ADDMISSINGITEMS|ALL|ALLCROSSFILTERED|ALLEXCEPT|ALLNOBLANKROW|ALLSELECTED|AND|APPROXIMATEDISTINCTCOUNT|ASIN|ASINH|ATAN|ATANH|AVERAGE|AVERAGEA|AVERAGEX|BETA\.DIST|BETA\.INV|BLANK|CALCULATE|CALCULATETABLE|CALENDAR|CALENDARAUTO|CEILING|CHISQ\.DIST|CHISQ\.DIST\.RT|CHISQ\.INV|CHISQ\.INV\.RT|CLOSINGBALANCEMONTH|CLOSINGBALANCEQUARTER|CLOSINGBALANCEYEAR|COALESCE|COMBIN|COMBINA|COMBINEVALUES|CONCATENATE|CONCATENATEX|CONFIDENCE\.NORM|CONFIDENCE\.T|CONTAINS|CONTAINSROW|CONTAINSSTRING|CONTAINSSTRINGEXACT|CONVERT|COS|COSH|COT|COTH|COUNT|COUNTA|COUNTAX|COUNTBLANK|COUNTROWS|COUNTX|CROSSFILTER|CROSSJOIN|CURRENCY|CURRENTGROUP|CUSTOMDATA|DATATABLE|DATE|DATEADD|DATEDIFF|DATESBETWEEN|DATESINPERIOD|DATESMTD|DATESQTD|DATESYTD|DATEVALUE|DAY|DEGREES|DETAILROWS|DISTINCT|DISTINCTCOUNT|DISTINCTCOUNTNOBLANK|DIVIDE|EARLIER|EARLIEST|EDATE|ENDOFMONTH|ENDOFQUARTER|ENDOFYEAR|EOMONTH|ERROR|EVEN|EXACT|EXCEPT|EXP|EXPON\.DIST|FACT|FALSE|FILTER|FILTERS|FIND|FIRSTDATE|FIRSTNONBLANK|FIRSTNONBLANKVALUE|FIXED|FLOOR|FORMAT|GCD|GENERATE|GENERATEALL|GENERATESERIES|GEOMEAN|GEOMEANX|GROUPBY|HASONEFILTER|HASONEVALUE|HOUR|IF|IF\.EAGER|IFERROR|IGNORE|INT|INTERSECT|ISBLANK|ISCROSSFILTERED|ISEMPTY|ISERROR|ISEVEN|ISFILTERED|ISINSCOPE|ISLOGICAL|ISNONTEXT|ISNUMBER|ISO\.CEILING|ISODD|ISONORAFTER|ISSELECTEDMEASURE|ISSUBTOTAL|ISTEXT|KEEPFILTERS|KEYWORDMATCH|LASTDATE|LASTNONBLANK|LASTNONBLANKVALUE|LCM|LEFT|LEN|LN|LOG|LOG10|LOOKUPVALUE|LOWER|MAX|MAXA|MAXX|MEDIAN|MEDIANX|MID|MIN|MINA|MINUTE|MINX|MOD|MONTH|MROUND|NATURALINNERJOIN|NATURALLEFTOUTERJOIN|NEXTDAY|NEXTMONTH|NEXTQUARTER|NEXTYEAR|NONVISUAL|NORM\.DIST|NORM\.INV|NORM\.S\.DIST|NORM\.S\.INV|NOT|NOW|ODD|OPENINGBALANCEMONTH|OPENINGBALANCEQUARTER|OPENINGBALANCEYEAR|OR|PARALLELPERIOD|PATH|PATHCONTAINS|PATHITEM|PATHITEMREVERSE|PATHLENGTH|PERCENTILE\.EXC|PERCENTILE\.INC|PERCENTILEX\.EXC|PERCENTILEX\.INC|PERMUT|PI|POISSON\.DIST|POWER|PREVIOUSDAY|PREVIOUSMONTH|PREVIOUSQUARTER|PREVIOUSYEAR|PRODUCT|PRODUCTX|QUARTER|QUOTIENT|RADIANS|RAND|RANDBETWEEN|RANK\.EQ|RANKX|RELATED|RELATEDTABLE|REMOVEFILTERS|REPLACE|REPT|RIGHT|ROLLUP|ROLLUPADDISSUBTOTAL|ROLLUPGROUP|ROLLUPISSUBTOTAL|ROUND|ROUNDDOWN|ROUNDUP|ROW|SAMEPERIODLASTYEAR|SAMPLE|SEARCH|SECOND|SELECTCOLUMNS|SELECTEDMEASURE|SELECTEDMEASUREFORMATSTRING|SELECTEDMEASURENAME|SELECTEDVALUE|SIGN|SIN|SINH|SQRT|SQRTPI|STARTOFMONTH|STARTOFQUARTER|STARTOFYEAR|STDEV\.P|STDEV\.S|STDEVX\.P|STDEVX\.S|SUBSTITUTE|SUBSTITUTEWITHINDEX|SUM|SUMMARIZE|SUMMARIZECOLUMNS|SUMX|SWITCH|T\.DIST|T\.DIST\.2T|T\.DIST\.RT|T\.INV|T\.INV\.2T|TAN|TANH|TIME|TIMEVALUE|TODAY|TOPN|TOPNPERLEVEL|TOPNSKIP|TOTALMTD|TOTALQTD|TOTALYTD|TREATAS|TRIM|TRUE|TRUNC|UNICHAR|UNICODE|UNION|UPPER|USERELATIONSHIP|USERNAME|USEROBJECTID|USERPRINCIPALNAME|UTCNOW|UTCTODAY|VALUE|VALUES|VAR\.P|VAR\.S|VARX\.P|VARX\.S|WEEKDAY|WEEKNUM|XIRR|XNPV|YEAR|YEARFRAC)(?=\s*\()/i,keyword:/\b(?:DEFINE|MEASURE|EVALUATE|ORDER\s+BY|RETURN|VAR|START\s+AT|ASC|DESC)\b/i,boolean:{pattern:/\b(?:TRUE|FALSE|NULL)\b/i,alias:"constant"},number:/\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/:=|[-+*\/=^]|&&?|\|\||<(?:=>?|<|>)?|>[>=]?|\b(?:IN|NOT)\b/i,punctuation:/[;\[\](){}`,.]/}}e.exports=t,t.displayName="dax",t.aliases=[]},13934(e){"use strict";function t(e){e.languages.dhall={comment:/--.*|\{-(?:[^-{]|-(?!\})|\{(?!-)|\{-(?:[^-{]|-(?!\})|\{(?!-))*-\})*-\}/,string:{pattern:/"(?:[^"\\]|\\.)*"|''(?:[^']|'(?!')|'''|''\$\{)*''(?!'|\$)/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^{}]*\}/,inside:{expression:{pattern:/(^\$\{)[\s\S]+(?=\}$)/,lookbehind:!0,alias:"language-dhall",inside:null},punctuation:/\$\{|\}/}}}},label:{pattern:/`[^`]*`/,greedy:!0},url:{pattern:/\bhttps?:\/\/[\w.:%!$&'*+;=@~-]+(?:\/[\w.:%!$&'*+;=@~-]*)*(?:\?[/?\w.:%!$&'*+;=@~-]*)?/,greedy:!0},env:{pattern:/\benv:(?:(?!\d)\w+|"(?:[^"\\=]|\\.)*")/,greedy:!0,inside:{function:/^env/,operator:/^:/,variable:/[\s\S]+/}},hash:{pattern:/\bsha256:[\da-fA-F]{64}\b/,inside:{function:/sha256/,operator:/:/,number:/[\da-fA-F]{64}/}},keyword:/\b(?:as|assert|else|forall|if|in|let|merge|missing|then|toMap|using|with)\b|\u2200/,builtin:/\b(?:Some|None)\b/,boolean:/\b(?:False|True)\b/,number:/\bNaN\b|-?\bInfinity\b|[+-]?\b(?:0x[\da-fA-F]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)\b/,operator:/\/\\|\/\/\\\\|&&|\|\||===|[!=]=|\/\/|->|\+\+|::|[+*#@=:?<>|\\\u2227\u2a53\u2261\u2afd\u03bb\u2192]/,punctuation:/\.\.|[{}\[\](),./]/,"class-name":/\b[A-Z]\w*\b/},e.languages.dhall.string.inside.interpolation.inside.expression.inside=e.languages.dhall}e.exports=t,t.displayName="dhall",t.aliases=[]},93336(e){"use strict";function t(e){var t,n;(t=e).languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d.*$/m]},Object.keys(n={"deleted-sign":"-","deleted-arrow":"<","inserted-sign":"+","inserted-arrow":">",unchanged:" ",diff:"!"}).forEach(function(e){var r=n[e],i=[];/^\w+$/.test(e)||i.push(/\w+/.exec(e)[0]),"diff"===e&&i.push("bold"),t.languages.diff[e]={pattern:RegExp("^(?:["+r+"].*(?:\r\n?|\n|(?![\\s\\S])))+","m"),alias:i,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(e)[0]}}}}),Object.defineProperty(t.languages.diff,"PREFIXES",{value:n})}e.exports=t,t.displayName="diff",t.aliases=[]},13294(e,t,n){"use strict";var r=n(93205);function i(e){var t,n,i;e.register(r),(t=e).languages.django={comment:/^\{#[\s\S]*?#\}$/,tag:{pattern:/(^\{%[+-]?\s*)\w+/,lookbehind:!0,alias:"keyword"},delimiter:{pattern:/^\{[{%][+-]?|[+-]?[}%]\}$/,alias:"punctuation"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},filter:{pattern:/(\|)\w+/,lookbehind:!0,alias:"function"},test:{pattern:/(\bis\s+(?:not\s+)?)(?!not\b)\w+/,lookbehind:!0,alias:"function"},function:/\b[a-z_]\w+(?=\s*\()/i,keyword:/\b(?:and|as|by|else|for|if|import|in|is|loop|not|or|recursive|with|without)\b/,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,number:/\b\d+(?:\.\d+)?\b/,boolean:/[Tt]rue|[Ff]alse|[Nn]one/,variable:/\b\w+?\b/,punctuation:/[{}[\](),.:;]/},n=/\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}|\{#[\s\S]*?#\}/g,i=t.languages["markup-templating"],t.hooks.add("before-tokenize",function(e){i.buildPlaceholders(e,"django",n)}),t.hooks.add("after-tokenize",function(e){i.tokenizePlaceholders(e,"django")}),t.languages.jinja2=t.languages.django,t.hooks.add("before-tokenize",function(e){i.buildPlaceholders(e,"jinja2",n)}),t.hooks.add("after-tokenize",function(e){i.tokenizePlaceholders(e,"jinja2")})}e.exports=i,i.displayName="django",i.aliases=["jinja2"]},38223(e){"use strict";function t(e){e.languages["dns-zone-file"]={comment:/;.*/,string:{pattern:/"(?:\\.|[^"\\\r\n])*"/,greedy:!0},variable:[{pattern:/(^\$ORIGIN[ \t]+)\S+/m,lookbehind:!0},{pattern:/(^|\s)@(?=\s|$)/,lookbehind:!0}],keyword:/^\$(?:ORIGIN|INCLUDE|TTL)(?=\s|$)/m,class:{pattern:/(^|\s)(?:IN|CH|CS|HS)(?=\s|$)/,lookbehind:!0,alias:"keyword"},type:{pattern:/(^|\s)(?:A|A6|AAAA|AFSDB|APL|ATMA|CAA|CDNSKEY|CDS|CERT|CNAME|DHCID|DLV|DNAME|DNSKEY|DS|EID|GID|GPOS|HINFO|HIP|IPSECKEY|ISDN|KEY|KX|LOC|MAILA|MAILB|MB|MD|MF|MG|MINFO|MR|MX|NAPTR|NB|NBSTAT|NIMLOC|NINFO|NS|NSAP|NSAP-PTR|NSEC|NSEC3|NSEC3PARAM|NULL|NXT|OPENPGPKEY|PTR|PX|RKEY|RP|RRSIG|RT|SIG|SINK|SMIMEA|SOA|SPF|SRV|SSHFP|TA|TKEY|TLSA|TSIG|TXT|UID|UINFO|UNSPEC|URI|WKS|X25)(?=\s|$)/,lookbehind:!0,alias:"keyword"},punctuation:/[()]/},e.languages["dns-zone"]=e.languages["dns-zone-file"]}e.exports=t,t.displayName="dnsZoneFile",t.aliases=[]},97266(e){"use strict";function t(e){!function(e){var t=/\\[\r\n](?:\s|\\[\r\n]|#.*(?!.))*(?![\s#]|\\[\r\n])/.source,n=/(?:[ \t]+(?![ \t])(?:)?|)/.source.replace(//g,function(){return t}),r=/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"|'(?:[^'\\\r\n]|\\(?:\r\n|[\s\S]))*'/.source,i=/--[\w-]+=(?:|(?!["'])(?:[^\s\\]|\\.)+)/.source.replace(//g,function(){return r}),a={pattern:RegExp(r),greedy:!0},o={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function s(e,t){return e=e.replace(//g,function(){return i}).replace(//g,function(){return n}),RegExp(e,t)}e.languages.docker={instruction:{pattern:/(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/im,lookbehind:!0,greedy:!0,inside:{options:{pattern:s(/(^(?:ONBUILD)?\w+)(?:)*/.source,"i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[a,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:s(/(^(?:ONBUILD)?HEALTHCHECK(?:)*)(?:CMD|NONE)\b/.source,"i"),lookbehind:!0,greedy:!0},{pattern:s(/(^(?:ONBUILD)?FROM(?:)*(?!--)[^ \t\\]+)AS/.source,"i"),lookbehind:!0,greedy:!0},{pattern:s(/(^ONBUILD)\w+/.source,"i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:o,string:a,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,operator:/\\$/m}},comment:o},e.languages.dockerfile=e.languages.docker}(e)}e.exports=t,t.displayName="docker",t.aliases=["dockerfile"]},80636(e){"use strict";function t(e){!function(e){var t="(?:"+[/[a-zA-Z_\x80-\uFFFF][\w\x80-\uFFFF]*/.source,/-?(?:\.\d+|\d+(?:\.\d*)?)/.source,/"[^"\\]*(?:\\[\s\S][^"\\]*)*"/.source,/<(?:[^<>]|(?!)*>/.source].join("|")+")",n={markup:{pattern:/(^<)[\s\S]+(?=>$)/,lookbehind:!0,alias:["language-markup","language-html","language-xml"],inside:e.languages.markup}};function r(e,n){return RegExp(e.replace(//g,function(){return t}),n)}e.languages.dot={comment:{pattern:/\/\/.*|\/\*[\s\S]*?\*\/|^#.*/m,greedy:!0},"graph-name":{pattern:r(/(\b(?:digraph|graph|subgraph)[ \t\r\n]+)/.source,"i"),lookbehind:!0,greedy:!0,alias:"class-name",inside:n},"attr-value":{pattern:r(/(=[ \t\r\n]*)/.source),lookbehind:!0,greedy:!0,inside:n},"attr-name":{pattern:r(/([\[;, \t\r\n])(?=[ \t\r\n]*=)/.source),lookbehind:!0,greedy:!0,inside:n},keyword:/\b(?:digraph|edge|graph|node|strict|subgraph)\b/i,"compass-point":{pattern:/(:[ \t\r\n]*)(?:[ns][ew]?|[ewc_])(?![\w\x80-\uFFFF])/,lookbehind:!0,alias:"builtin"},node:{pattern:r(/(^|[^-.\w\x80-\uFFFF\\])/.source),lookbehind:!0,greedy:!0,inside:n},operator:/[=:]|-[->]/,punctuation:/[\[\]{};,]/},e.languages.gv=e.languages.dot}(e)}e.exports=t,t.displayName="dot",t.aliases=["gv"]},36500(e){"use strict";function t(e){e.languages.ebnf={comment:/\(\*[\s\S]*?\*\)/,string:{pattern:/"[^"\r\n]*"|'[^'\r\n]*'/,greedy:!0},special:{pattern:/\?[^?\r\n]*\?/,greedy:!0,alias:"class-name"},definition:{pattern:/^([\t ]*)[a-z]\w*(?:[ \t]+[a-z]\w*)*(?=\s*=)/im,lookbehind:!0,alias:["rule","keyword"]},rule:/\b[a-z]\w*(?:[ \t]+[a-z]\w*)*\b/i,punctuation:/\([:/]|[:/]\)|[.,;()[\]{}]/,operator:/[-=|*/!]/}}e.exports=t,t.displayName="ebnf",t.aliases=[]},30296(e){"use strict";function t(e){e.languages.editorconfig={comment:/[;#].*/,section:{pattern:/(^[ \t]*)\[.+\]/m,lookbehind:!0,alias:"keyword",inside:{regex:/\\\\[\[\]{},!?.*]/,operator:/[!?]|\.\.|\*{1,2}/,punctuation:/[\[\]{},]/}},property:{pattern:/(^[ \t]*)[^\s=]+(?=[ \t]*=)/m,lookbehind:!0},value:{pattern:/=.*/,alias:"string",inside:{punctuation:/^=/}}}}e.exports=t,t.displayName="editorconfig",t.aliases=[]},50115(e){"use strict";function t(e){e.languages.eiffel={comment:/--.*/,string:[{pattern:/"([^[]*)\[[\s\S]*?\]\1"/,greedy:!0},{pattern:/"([^{]*)\{[\s\S]*?\}\1"/,greedy:!0},{pattern:/"(?:%(?:(?!\n)\s)*\n\s*%|%\S|[^%"\r\n])*"/,greedy:!0}],char:/'(?:%.|[^%'\r\n])+'/,keyword:/\b(?:across|agent|alias|all|and|attached|as|assign|attribute|check|class|convert|create|Current|debug|deferred|detachable|do|else|elseif|end|ensure|expanded|export|external|feature|from|frozen|if|implies|inherit|inspect|invariant|like|local|loop|not|note|obsolete|old|once|or|Precursor|redefine|rename|require|rescue|Result|retry|select|separate|some|then|undefine|until|variant|Void|when|xor)\b/i,boolean:/\b(?:True|False)\b/i,"class-name":{pattern:/\b[A-Z][\dA-Z_]*\b/,alias:"builtin"},number:[/\b0[xcb][\da-f](?:_*[\da-f])*\b/i,/(?:\b\d(?:_*\d)*)?\.(?:(?:\d(?:_*\d)*)?e[+-]?)?\d(?:_*\d)*\b|\b\d(?:_*\d)*\b\.?/i],punctuation:/:=|<<|>>|\(\||\|\)|->|\.(?=\w)|[{}[\];(),:?]/,operator:/\\\\|\|\.\.\||\.\.|\/[~\/=]?|[><]=?|[-+*^=~]/}}e.exports=t,t.displayName="eiffel",t.aliases=[]},20791(e,t,n){"use strict";var r=n(93205);function i(e){var t;e.register(r),(t=e).languages.ejs={delimiter:{pattern:/^<%[-_=]?|[-_]?%>$/,alias:"punctuation"},comment:/^#[\s\S]*/,"language-javascript":{pattern:/[\s\S]+/,inside:t.languages.javascript}},t.hooks.add("before-tokenize",function(e){var n=/<%(?!%)[\s\S]+?%>/g;t.languages["markup-templating"].buildPlaceholders(e,"ejs",n)}),t.hooks.add("after-tokenize",function(e){t.languages["markup-templating"].tokenizePlaceholders(e,"ejs")}),t.languages.eta=t.languages.ejs}e.exports=i,i.displayName="ejs",i.aliases=["eta"]},11974(e){"use strict";function t(e){e.languages.elixir={doc:{pattern:/@(?:doc|moduledoc)\s+(?:("""|''')[\s\S]*?\1|("|')(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2)/,inside:{attribute:/^@\w+/,string:/['"][\s\S]+/}},comment:{pattern:/#.*/m,greedy:!0},regex:{pattern:/~[rR](?:("""|''')(?:\\[\s\S]|(?!\1)[^\\])+\1|([\/|"'])(?:\\.|(?!\2)[^\\\r\n])+\2|\((?:\\.|[^\\)\r\n])+\)|\[(?:\\.|[^\\\]\r\n])+\]|\{(?:\\.|[^\\}\r\n])+\}|<(?:\\.|[^\\>\r\n])+>)[uismxfr]*/,greedy:!0},string:[{pattern:/~[cCsSwW](?:("""|''')(?:\\[\s\S]|(?!\1)[^\\])+\1|([\/|"'])(?:\\.|(?!\2)[^\\\r\n])+\2|\((?:\\.|[^\\)\r\n])+\)|\[(?:\\.|[^\\\]\r\n])+\]|\{(?:\\.|#\{[^}]+\}|#(?!\{)|[^#\\}\r\n])+\}|<(?:\\.|[^\\>\r\n])+>)[csa]?/,greedy:!0,inside:{}},{pattern:/("""|''')[\s\S]*?\1/,greedy:!0,inside:{}},{pattern:/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{}}],atom:{pattern:/(^|[^:]):\w+/,lookbehind:!0,alias:"symbol"},module:{pattern:/\b[A-Z]\w*\b/,alias:"class-name"},"attr-name":/\b\w+\??:(?!:)/,argument:{pattern:/(^|[^&])&\d+/,lookbehind:!0,alias:"variable"},attribute:{pattern:/@\w+/,alias:"variable"},function:/\b[_a-zA-Z]\w*[?!]?(?:(?=\s*(?:\.\s*)?\()|(?=\/\d))/,number:/\b(?:0[box][a-f\d_]+|\d[\d_]*)(?:\.[\d_]+)?(?:e[+-]?[\d_]+)?\b/i,keyword:/\b(?:after|alias|and|case|catch|cond|def(?:callback|delegate|exception|impl|macro|module|n|np|p|protocol|struct)?|do|else|end|fn|for|if|import|not|or|quote|raise|require|rescue|try|unless|unquote|use|when)\b/,boolean:/\b(?:true|false|nil)\b/,operator:[/\bin\b|&&?|\|[|>]?|\\\\|::|\.\.\.?|\+\+?|-[->]?|<[-=>]|>=|!==?|\B!|=(?:==?|[>~])?|[*\/^]/,{pattern:/([^<])<(?!<)/,lookbehind:!0},{pattern:/([^>])>(?!>)/,lookbehind:!0}],punctuation:/<<|>>|[.,%\[\]{}()]/},e.languages.elixir.string.forEach(function(t){t.inside={interpolation:{pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"},rest:e.languages.elixir}}}})}e.exports=t,t.displayName="elixir",t.aliases=[]},8645(e){"use strict";function t(e){e.languages.elm={comment:/--.*|\{-[\s\S]*?-\}/,char:{pattern:/'(?:[^\\'\r\n]|\\(?:[abfnrtv\\']|\d+|x[0-9a-fA-F]+))'/,greedy:!0},string:[{pattern:/"""[\s\S]*?"""/,greedy:!0},{pattern:/"(?:[^\\"\r\n]|\\.)*"/,greedy:!0}],"import-statement":{pattern:/(^[\t ]*)import\s+[A-Z]\w*(?:\.[A-Z]\w*)*(?:\s+as\s+(?:[A-Z]\w*)(?:\.[A-Z]\w*)*)?(?:\s+exposing\s+)?/m,lookbehind:!0,inside:{keyword:/\b(?:import|as|exposing)\b/}},keyword:/\b(?:alias|as|case|else|exposing|if|in|infixl|infixr|let|module|of|then|type)\b/,builtin:/\b(?:abs|acos|always|asin|atan|atan2|ceiling|clamp|compare|cos|curry|degrees|e|flip|floor|fromPolar|identity|isInfinite|isNaN|logBase|max|min|negate|never|not|pi|radians|rem|round|sin|sqrt|tan|toFloat|toPolar|toString|truncate|turns|uncurry|xor)\b/,number:/\b(?:\d+(?:\.\d+)?(?:e[+-]?\d+)?|0x[0-9a-f]+)\b/i,operator:/\s\.\s|[+\-/*=.$<>:&|^?%#@~!]{2,}|[+\-/*=$<>:&|^?%#@~!]/,hvariable:/\b(?:[A-Z]\w*\.)*[a-z]\w*\b/,constant:/\b(?:[A-Z]\w*\.)*[A-Z]\w*\b/,punctuation:/[{}[\]|(),.:]/}}e.exports=t,t.displayName="elm",t.aliases=[]},84790(e,t,n){"use strict";var r=n(56939),i=n(93205);function a(e){var t;e.register(r),e.register(i),(t=e).languages.erb=t.languages.extend("ruby",{}),t.languages.insertBefore("erb","comment",{delimiter:{pattern:/^<%=?|%>$/,alias:"punctuation"}}),t.hooks.add("before-tokenize",function(e){var n=/<%=?(?:[^\r\n]|[\r\n](?!=begin)|[\r\n]=begin\s(?:[^\r\n]|[\r\n](?!=end))*[\r\n]=end)+?%>/gm;t.languages["markup-templating"].buildPlaceholders(e,"erb",n)}),t.hooks.add("after-tokenize",function(e){t.languages["markup-templating"].tokenizePlaceholders(e,"erb")})}e.exports=a,a.displayName="erb",a.aliases=[]},4502(e){"use strict";function t(e){e.languages.erlang={comment:/%.+/,string:{pattern:/"(?:\\.|[^\\"\r\n])*"/,greedy:!0},"quoted-function":{pattern:/'(?:\\.|[^\\'\r\n])+'(?=\()/,alias:"function"},"quoted-atom":{pattern:/'(?:\\.|[^\\'\r\n])+'/,alias:"atom"},boolean:/\b(?:true|false)\b/,keyword:/\b(?:fun|when|case|of|end|if|receive|after|try|catch)\b/,number:[/\$\\?./,/\b\d+#[a-z0-9]+/i,/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i],function:/\b[a-z][\w@]*(?=\()/,variable:{pattern:/(^|[^@])(?:\b|\?)[A-Z_][\w@]*/,lookbehind:!0},operator:[/[=\/<>:]=|=[:\/]=|\+\+?|--?|[=*\/!]|\b(?:bnot|div|rem|band|bor|bxor|bsl|bsr|not|and|or|xor|orelse|andalso)\b/,{pattern:/(^|[^<])<(?!<)/,lookbehind:!0},{pattern:/(^|[^>])>(?!>)/,lookbehind:!0}],atom:/\b[a-z][\w@]*/,punctuation:/[()[\]{}:;,.#|]|<<|>>/}}e.exports=t,t.displayName="erlang",t.aliases=[]},66055(e,t,n){"use strict";var r=n(59803),i=n(93205);function a(e){var t;e.register(r),e.register(i),(t=e).languages.etlua={delimiter:{pattern:/^<%[-=]?|-?%>$/,alias:"punctuation"},"language-lua":{pattern:/[\s\S]+/,inside:t.languages.lua}},t.hooks.add("before-tokenize",function(e){var n=/<%[\s\S]+?%>/g;t.languages["markup-templating"].buildPlaceholders(e,"etlua",n)}),t.hooks.add("after-tokenize",function(e){t.languages["markup-templating"].tokenizePlaceholders(e,"etlua")})}e.exports=a,a.displayName="etlua",a.aliases=[]},68876(e){"use strict";function t(e){e.languages["excel-formula"]={comment:{pattern:/(\bN\(\s*)"(?:[^"]|"")*"(?=\s*\))/i,lookbehind:!0,greedy:!0},string:{pattern:/"(?:[^"]|"")*"(?!")/,greedy:!0},reference:{pattern:/(?:'[^']*'|(?:[^\s()[\]{}<>*?"';,$&]*\[[^^\s()[\]{}<>*?"']+\])?\w+)!/,greedy:!0,alias:"string",inside:{operator:/!$/,punctuation:/'/,sheet:{pattern:/[^[\]]+$/,alias:"function"},file:{pattern:/\[[^[\]]+\]$/,inside:{punctuation:/[[\]]/}},path:/[\s\S]+/}},"function-name":{pattern:/\b[A-Z]\w*(?=\()/i,alias:"keyword"},range:{pattern:/\$?\b(?:[A-Z]+\$?\d+:\$?[A-Z]+\$?\d+|[A-Z]+:\$?[A-Z]+|\d+:\$?\d+)\b/i,alias:"property",inside:{operator:/:/,cell:/\$?[A-Z]+\$?\d+/i,column:/\$?[A-Z]+/i,row:/\$?\d+/}},cell:{pattern:/\b[A-Z]+\d+\b|\$[A-Za-z]+\$?\d+\b|\b[A-Za-z]+\$\d+\b/,alias:"property"},number:/(?:\b\d+(?:\.\d+)?|\B\.\d+)(?:e[+-]?\d+)?\b/i,boolean:/\b(?:TRUE|FALSE)\b/i,operator:/[-+*/^%=&,]|<[=>]?|>=?/,punctuation:/[[\]();{}|]/},e.languages.xlsx=e.languages.xls=e.languages["excel-formula"]}e.exports=t,t.displayName="excelFormula",t.aliases=[]},95126(e){"use strict";function t(e){var t,n,r,i,a,o,s,u;t=e,i={comment:[{pattern:/(^|\s)(?:! .*|!$)/,lookbehind:!0,inside:n={function:/\b(?:TODOS?|FIX(?:MES?)?|NOTES?|BUGS?|XX+|HACKS?|WARN(?:ING)?|\?{2,}|!{2,})\b/}},{pattern:/(^|\s)\/\*\s[\s\S]*?\*\/(?=\s|$)/,lookbehind:!0,greedy:!0,inside:n},{pattern:/(^|\s)!\[(={0,6})\[\s[\s\S]*?\]\2\](?=\s|$)/,lookbehind:!0,greedy:!0,inside:n}],number:[{pattern:/(^|\s)[+-]?\d+(?=\s|$)/,lookbehind:!0},{pattern:/(^|\s)[+-]?0(?:b[01]+|o[0-7]+|d\d+|x[\dA-F]+)(?=\s|$)/i,lookbehind:!0},{pattern:/(^|\s)[+-]?\d+\/\d+\.?(?=\s|$)/,lookbehind:!0},{pattern:/(^|\s)\+?\d+\+\d+\/\d+(?=\s|$)/,lookbehind:!0},{pattern:/(^|\s)-\d+-\d+\/\d+(?=\s|$)/,lookbehind:!0},{pattern:/(^|\s)[+-]?(?:\d*\.\d+|\d+\.\d*|\d+)(?:e[+-]?\d+)?(?=\s|$)/i,lookbehind:!0},{pattern:/(^|\s)NAN:\s+[\da-fA-F]+(?=\s|$)/,lookbehind:!0},{pattern:/(^|\s)[+-]?0(?:b1\.[01]*|o1\.[0-7]*|d1\.\d*|x1\.[\dA-F]*)p\d+(?=\s|$)/i,lookbehind:!0}],regexp:{pattern:/(^|\s)R\/\s(?:\\\S|[^\\/])*\/(?:[idmsr]*|[idmsr]+-[idmsr]+)(?=\s|$)/,lookbehind:!0,alias:"number",inside:{variable:/\\\S/,keyword:/[+?*\[\]^$(){}.|]/,operator:{pattern:/(\/)[idmsr]+(?:-[idmsr]+)?/,lookbehind:!0}}},boolean:{pattern:/(^|\s)[tf](?=\s|$)/,lookbehind:!0},"custom-string":{pattern:/(^|\s)[A-Z0-9\-]+"\s(?:\\\S|[^"\\])*"/,lookbehind:!0,greedy:!0,alias:"string",inside:{number:/\\\S|%\w|\//}},"multiline-string":[{pattern:/(^|\s)STRING:\s+\S+(?:\n|\r\n).*(?:\n|\r\n)\s*;(?=\s|$)/,lookbehind:!0,greedy:!0,alias:"string",inside:{number:(r={number:/\\[^\s']|%\w/}).number,"semicolon-or-setlocal":{pattern:/([\r\n][ \t]*);(?=\s|$)/,lookbehind:!0,alias:"function"}}},{pattern:/(^|\s)HEREDOC:\s+\S+(?:\n|\r\n).*(?:\n|\r\n)\s*\S+(?=\s|$)/,lookbehind:!0,greedy:!0,alias:"string",inside:r},{pattern:/(^|\s)\[(={0,6})\[\s[\s\S]*?\]\2\](?=\s|$)/,lookbehind:!0,greedy:!0,alias:"string",inside:r}],"special-using":{pattern:/(^|\s)USING:(?:\s\S+)*(?=\s+;(?:\s|$))/,lookbehind:!0,alias:"function",inside:{string:{pattern:/(\s)[^:\s]+/,lookbehind:!0}}},"stack-effect-delimiter":[{pattern:/(^|\s)(?:call|execute|eval)?\((?=\s)/,lookbehind:!0,alias:"operator"},{pattern:/(\s)--(?=\s)/,lookbehind:!0,alias:"operator"},{pattern:/(\s)\)(?=\s|$)/,lookbehind:!0,alias:"operator"}],combinators:{pattern:null,lookbehind:!0,alias:"keyword"},"kernel-builtin":{pattern:null,lookbehind:!0,alias:"variable"},"sequences-builtin":{pattern:null,lookbehind:!0,alias:"variable"},"math-builtin":{pattern:null,lookbehind:!0,alias:"variable"},"constructor-word":{pattern:/(^|\s)<(?!=+>|-+>)\S+>(?=\s|$)/,lookbehind:!0,alias:"keyword"},"other-builtin-syntax":{pattern:null,lookbehind:!0,alias:"operator"},"conventionally-named-word":{pattern:/(^|\s)(?!")(?:(?:set|change|with|new)-\S+|\$\S+|>[^>\s]+|[^:>\s]+>|[^>\s]+>[^>\s]+|\+[^+\s]+\+|[^?\s]+\?|\?[^?\s]+|[^>\s]+>>|>>[^>\s]+|[^<\s]+<<|\([^()\s]+\)|[^!\s]+!|[^*\s]\S*\*|[^.\s]\S*\.)(?=\s|$)/,lookbehind:!0,alias:"keyword"},"colon-syntax":{pattern:/(^|\s)(?:[A-Z0-9\-]+#?)?:{1,2}\s+(?:;\S+|(?!;)\S+)(?=\s|$)/,lookbehind:!0,greedy:!0,alias:"function"},"semicolon-or-setlocal":{pattern:/(\s)(?:;|:>)(?=\s|$)/,lookbehind:!0,alias:"function"},"curly-brace-literal-delimiter":[{pattern:/(^|\s)[a-z]*\{(?=\s)/i,lookbehind:!0,alias:"operator"},{pattern:/(\s)\}(?=\s|$)/,lookbehind:!0,alias:"operator"}],"quotation-delimiter":[{pattern:/(^|\s)\[(?=\s)/,lookbehind:!0,alias:"operator"},{pattern:/(\s)\](?=\s|$)/,lookbehind:!0,alias:"operator"}],"normal-word":{pattern:/(^|\s)[^"\s]\S*(?=\s|$)/,lookbehind:!0},string:{pattern:/"(?:\\\S|[^"\\])*"/,greedy:!0,inside:r}},a=function(e){return(e+"").replace(/([.?*+\^$\[\]\\(){}|\-])/g,"\\$1")},o=function(e){return RegExp("(^|\\s)(?:"+e.map(a).join("|")+")(?=\\s|$)")},Object.keys(s={"kernel-builtin":["or","2nipd","4drop","tuck","wrapper","nip","wrapper?","callstack>array","die","dupd","callstack","callstack?","3dup","hashcode","pick","4nip","build",">boolean","nipd","clone","5nip","eq?","?","=","swapd","2over","clear","2dup","get-retainstack","not","tuple?","dup","3nipd","call","-rotd","object","drop","assert=","assert?","-rot","execute","boa","get-callstack","curried?","3drop","pickd","overd","over","roll","3nip","swap","and","2nip","rotd","throw","(clone)","hashcode*","spin","reach","4dup","equal?","get-datastack","assert","2drop","","boolean?","identity-hashcode","identity-tuple?","null","composed?","new","5drop","rot","-roll","xor","identity-tuple","boolean"],"other-builtin-syntax":["=======","recursive","flushable",">>","<<<<<<","M\\","B","PRIVATE>","\\","======","final","inline","delimiter","deprecated",">>>>>","<<<<<<<","parse-complex","malformed-complex","read-only",">>>>>>>","call-next-method","<<","foldable","$","$[","${"],"sequences-builtin":["member-eq?","mismatch","append","assert-sequence=","longer","repetition","clone-like","3sequence","assert-sequence?","last-index-from","reversed","index-from","cut*","pad-tail","join-as","remove-eq!","concat-as","but-last","snip","nths","nth","sequence","longest","slice?","","remove-nth","tail-slice","empty?","tail*","member?","virtual-sequence?","set-length","drop-prefix","iota","unclip","bounds-error?","unclip-last-slice","non-negative-integer-expected","non-negative-integer-expected?","midpoint@","longer?","?set-nth","?first","rest-slice","prepend-as","prepend","fourth","sift","subseq-start","new-sequence","?last","like","first4","1sequence","reverse","slice","virtual@","repetition?","set-last","index","4sequence","max-length","set-second","immutable-sequence","first2","first3","supremum","unclip-slice","suffix!","insert-nth","tail","3append","short","suffix","concat","flip","immutable?","reverse!","2sequence","sum","delete-all","indices","snip-slice","","check-slice","sequence?","head","append-as","halves","sequence=","collapse-slice","?second","slice-error?","product","bounds-check?","bounds-check","immutable","virtual-exemplar","harvest","remove","pad-head","last","set-fourth","cartesian-product","remove-eq","shorten","shorter","reversed?","shorter?","shortest","head-slice","pop*","tail-slice*","but-last-slice","iota?","append!","cut-slice","new-resizable","head-slice*","sequence-hashcode","pop","set-nth","?nth","second","join","immutable-sequence?","","3append-as","virtual-sequence","subseq?","remove-nth!","length","last-index","lengthen","assert-sequence","copy","move","third","first","tail?","set-first","prefix","bounds-error","","exchange","surround","cut","min-length","set-third","push-all","head?","subseq-start-from","delete-slice","rest","sum-lengths","head*","infimum","remove!","glue","slice-error","subseq","push","replace-slice","subseq-as","unclip-last"],"math-builtin":["number=","next-power-of-2","?1+","fp-special?","imaginary-part","float>bits","number?","fp-infinity?","bignum?","fp-snan?","denominator","gcd","*","+","fp-bitwise=","-","u>=","/",">=","bitand","power-of-2?","log2-expects-positive","neg?","<","log2",">","integer?","number","bits>double","2/","zero?","bits>float","float?","shift","ratio?","rect>","even?","ratio","fp-sign","bitnot",">fixnum","complex?","/i","integer>fixnum","/f","sgn",">bignum","next-float","u<","u>","mod","recip","rational",">float","2^","integer","fixnum?","neg","fixnum","sq","bignum",">rect","bit?","fp-qnan?","simple-gcd","complex","","real",">fraction","double>bits","bitor","rem","fp-nan-payload","real-part","log2-expects-positive?","prev-float","align","unordered?","float","fp-nan?","abs","bitxor","integer>fixnum-strict","u<=","odd?","<=","/mod",">integer","real?","rational?","numerator"]}).forEach(function(e){i[e].pattern=o(s[e])}),u=["2bi","while","2tri","bi*","4dip","both?","same?","tri@","curry","prepose","3bi","?if","tri*","2keep","3keep","curried","2keepd","when","2bi*","2tri*","4keep","bi@","keepdd","do","unless*","tri-curry","if*","loop","bi-curry*","when*","2bi@","2tri@","with","2with","either?","bi","until","3dip","3curry","tri-curry*","tri-curry@","bi-curry","keepd","compose","2dip","if","3tri","unless","tuple","keep","2curry","tri","most","while*","dip","composed","bi-curry@","find-last-from","trim-head-slice","map-as","each-from","none?","trim-tail","partition","if-empty","accumulate*","reject!","find-from","accumulate-as","collector-for-as","reject","map","map-sum","accumulate!","2each-from","follow","supremum-by","map!","unless-empty","collector","padding","reduce-index","replicate-as","infimum-by","trim-tail-slice","count","find-index","filter","accumulate*!","reject-as","map-integers","map-find","reduce","selector","interleave","2map","filter-as","binary-reduce","map-index-as","find","produce","filter!","replicate","cartesian-map","cartesian-each","find-index-from","map-find-last","3map-as","3map","find-last","selector-as","2map-as","2map-reduce","accumulate","each","each-index","accumulate*-as","when-empty","all?","collector-as","push-either","new-like","collector-for","2selector","push-if","2all?","map-reduce","3each","any?","trim-slice","2reduce","change-nth","produce-as","2each","trim","trim-head","cartesian-find","map-index","if-zero","each-integer","unless-zero","(find-integer)","when-zero","find-last-integer","(all-integers?)","times","(each-integer)","find-integer","all-integers?","unless-negative","if-positive","when-positive","when-negative","unless-positive","if-negative","case","2cleave","cond>quot","case>quot","3cleave","wrong-values","to-fixed-point","alist>quot","cond","cleave","call-effect","recursive-hashcode","spread","deep-spread>quot","2||","0||","n||","0&&","2&&","3||","1||","1&&","n&&","3&&","smart-unless*","keep-inputs","reduce-outputs","smart-when*","cleave>array","smart-with","smart-apply","smart-if","inputs/outputs","output>sequence-n","map-outputs","map-reduce-outputs","dropping","output>array","smart-map-reduce","smart-2map-reduce","output>array-n","nullary","inputsequence"],i.combinators.pattern=o(u),t.languages.factor=i}e.exports=t,t.displayName="factor",t.aliases=[]},74644(e){"use strict";function t(e){var t;(t=e).languages.false={comment:{pattern:/\{[^}]*\}/},string:{pattern:/"[^"]*"/,greedy:!0},"character-code":{pattern:/'(?:[^\r]|\r\n?)/,alias:"number"},"assembler-code":{pattern:/\d+`/,alias:"important"},number:/\d+/,operator:/[-!#$%&'*+,./:;=>?@\\^_`|~ßø]/,punctuation:/\[|\]/,variable:/[a-z]/,"non-standard":{pattern:/[()!=]=?|[-+*/%]|\b(?:in|is)\b/}),delete e.languages["firestore-security-rules"]["class-name"],e.languages.insertBefore("firestore-security-rules","keyword",{path:{pattern:/(^|[\s(),])(?:\/(?:[\w\xA0-\uFFFF]+|\{[\w\xA0-\uFFFF]+(?:=\*\*)?\}|\$\([\w\xA0-\uFFFF.]+\)))+/,lookbehind:!0,greedy:!0,inside:{variable:{pattern:/\{[\w\xA0-\uFFFF]+(?:=\*\*)?\}|\$\([\w\xA0-\uFFFF.]+\)/,inside:{operator:/=/,keyword:/\*\*/,punctuation:/[.$(){}]/}},punctuation:/\//}},method:{pattern:/(\ballow\s+)[a-z]+(?:\s*,\s*[a-z]+)*(?=\s*[:;])/,lookbehind:!0,alias:"builtin",inside:{punctuation:/,/}}})}e.exports=t,t.displayName="firestoreSecurityRules",t.aliases=[]},37225(e){"use strict";function t(e){var t;(t=e).languages.flow=t.languages.extend("javascript",{}),t.languages.insertBefore("flow","keyword",{type:[{pattern:/\b(?:[Nn]umber|[Ss]tring|[Bb]oolean|Function|any|mixed|null|void)\b/,alias:"tag"}]}),t.languages.flow["function-variable"].pattern=/(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=\s*(?:function\b|(?:\([^()]*\)(?:\s*:\s*\w+)?|(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/i,delete t.languages.flow.parameter,t.languages.insertBefore("flow","operator",{"flow-punctuation":{pattern:/\{\||\|\}/,alias:"punctuation"}}),Array.isArray(t.languages.flow.keyword)||(t.languages.flow.keyword=[t.languages.flow.keyword]),t.languages.flow.keyword.unshift({pattern:/(^|[^$]\b)(?:type|opaque|declare|Class)\b(?!\$)/,lookbehind:!0},{pattern:/(^|[^$]\B)\$(?:await|Diff|Exact|Keys|ObjMap|PropertyType|Shape|Record|Supertype|Subtype|Enum)\b(?!\$)/,lookbehind:!0})}e.exports=t,t.displayName="flow",t.aliases=[]},16725(e){"use strict";function t(e){e.languages.fortran={"quoted-number":{pattern:/[BOZ](['"])[A-F0-9]+\1/i,alias:"number"},string:{pattern:/(?:\b\w+_)?(['"])(?:\1\1|&(?:\r\n?|\n)(?:[ \t]*!.*(?:\r\n?|\n)|(?![ \t]*!))|(?!\1).)*(?:\1|&)/,inside:{comment:{pattern:/(&(?:\r\n?|\n)\s*)!.*/,lookbehind:!0}}},comment:{pattern:/!.*/,greedy:!0},boolean:/\.(?:TRUE|FALSE)\.(?:_\w+)?/i,number:/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[ED][+-]?\d+)?(?:_\w+)?/i,keyword:[/\b(?:INTEGER|REAL|DOUBLE ?PRECISION|COMPLEX|CHARACTER|LOGICAL)\b/i,/\b(?:END ?)?(?:BLOCK ?DATA|DO|FILE|FORALL|FUNCTION|IF|INTERFACE|MODULE(?! PROCEDURE)|PROGRAM|SELECT|SUBROUTINE|TYPE|WHERE)\b/i,/\b(?:ALLOCATABLE|ALLOCATE|BACKSPACE|CALL|CASE|CLOSE|COMMON|CONTAINS|CONTINUE|CYCLE|DATA|DEALLOCATE|DIMENSION|DO|END|EQUIVALENCE|EXIT|EXTERNAL|FORMAT|GO ?TO|IMPLICIT(?: NONE)?|INQUIRE|INTENT|INTRINSIC|MODULE PROCEDURE|NAMELIST|NULLIFY|OPEN|OPTIONAL|PARAMETER|POINTER|PRINT|PRIVATE|PUBLIC|READ|RETURN|REWIND|SAVE|SELECT|STOP|TARGET|WHILE|WRITE)\b/i,/\b(?:ASSIGNMENT|DEFAULT|ELEMENTAL|ELSE|ELSEWHERE|ELSEIF|ENTRY|IN|INCLUDE|INOUT|KIND|NULL|ONLY|OPERATOR|OUT|PURE|RECURSIVE|RESULT|SEQUENCE|STAT|THEN|USE)\b/i],operator:[/\*\*|\/\/|=>|[=\/]=|[<>]=?|::|[+\-*=%]|\.[A-Z]+\./i,{pattern:/(^|(?!\().)\/(?!\))/,lookbehind:!0}],punctuation:/\(\/|\/\)|[(),;:&]/}}e.exports=t,t.displayName="fortran",t.aliases=[]},95559(e){"use strict";function t(e){e.languages.fsharp=e.languages.extend("clike",{comment:[{pattern:/(^|[^\\])\(\*(?!\))[\s\S]*?\*\)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(?:"""[\s\S]*?"""|@"(?:""|[^"])*"|"(?:\\[\s\S]|[^\\"])*")B?|'(?:[^\\']|\\(?:.|\d{3}|x[a-fA-F\d]{2}|u[a-fA-F\d]{4}|U[a-fA-F\d]{8}))'B?/,greedy:!0},"class-name":{pattern:/(\b(?:exception|inherit|interface|new|of|type)\s+|\w\s*:\s*|\s:\??>\s*)[.\w]+\b(?:\s*(?:->|\*)\s*[.\w]+\b)*(?!\s*[:.])/,lookbehind:!0,inside:{operator:/->|\*/,punctuation:/\./}},keyword:/\b(?:let|return|use|yield)(?:!\B|\b)|\b(?:abstract|and|as|assert|base|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|global|if|in|inherit|inline|interface|internal|lazy|match|member|module|mutable|namespace|new|not|null|of|open|or|override|private|public|rec|select|static|struct|then|to|true|try|type|upcast|val|void|when|while|with|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|include|method|mixin|object|parallel|process|protected|pure|sealed|tailcall|trait|virtual|volatile)\b/,number:[/\b0x[\da-fA-F]+(?:un|lf|LF)?\b/,/\b0b[01]+(?:y|uy)?\b/,/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[fm]|e[+-]?\d+)?\b/i,/\b\d+(?:[IlLsy]|u[lsy]?|UL)?\b/],operator:/([<>~&^])\1\1|([*.:<>&])\2|<-|->|[!=:]=|?|\??(?:<=|>=|<>|[-+*/%=<>])\??|[!?^&]|~[+~-]|:>|:\?>?/}),e.languages.insertBefore("fsharp","keyword",{preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(^#)\b(?:else|endif|if|light|line|nowarn)\b/,lookbehind:!0,alias:"keyword"}}}}),e.languages.insertBefore("fsharp","punctuation",{"computation-expression":{pattern:/\b[_a-z]\w*(?=\s*\{)/i,alias:"keyword"}}),e.languages.insertBefore("fsharp","string",{annotation:{pattern:/\[<.+?>\]/,inside:{punctuation:/^\[<|>\]$/,"class-name":{pattern:/^\w+$|(^|;\s*)[A-Z]\w*(?=\()/,lookbehind:!0},"annotation-content":{pattern:/[\s\S]+/,inside:e.languages.fsharp}}}})}e.exports=t,t.displayName="fsharp",t.aliases=[]},82114(e,t,n){"use strict";var r=n(93205);function i(e){e.register(r),function(e){for(var t=/[^<()"']|\((?:)*\)|<(?!#--)|<#--(?:[^-]|-(?!->))*-->|"(?:[^\\"]|\\.)*"|'(?:[^\\']|\\.)*'/.source,n=0;n<2;n++)t=t.replace(//g,function(){return t});t=t.replace(//g,/[^\s\S]/.source);var r={comment:/<#--[\s\S]*?-->/,string:[{pattern:/\br("|')(?:(?!\1)[^\\]|\\.)*\1/,greedy:!0},{pattern:RegExp(/("|')(?:(?!\1|\$\{)[^\\]|\\.|\$\{(?:(?!\})(?:))*\})*\1/.source.replace(//g,function(){return t})),greedy:!0,inside:{interpolation:{pattern:RegExp(/((?:^|[^\\])(?:\\\\)*)\$\{(?:(?!\})(?:))*\}/.source.replace(//g,function(){return t})),lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:null}}}}],keyword:/\b(?:as)\b/,boolean:/\b(?:true|false)\b/,"builtin-function":{pattern:/((?:^|[^?])\?\s*)\w+/,lookbehind:!0,alias:"function"},function:/\b\w+(?=\s*\()/,number:/\b\d+(?:\.\d+)?\b/,operator:/\.\.[<*!]?|->|--|\+\+|&&|\|\||\?{1,2}|[-+*/%!=<>]=?|\b(?:gt|gte|lt|lte)\b/,punctuation:/[,;.:()[\]{}]/};r.string[1].inside.interpolation.inside.rest=r,e.languages.ftl={"ftl-comment":{pattern:/^<#--[\s\S]*/,alias:"comment"},"ftl-directive":{pattern:/^<[\s\S]+>$/,inside:{directive:{pattern:/(^<\/?)[#@][a-z]\w*/i,lookbehind:!0,alias:"keyword"},punctuation:/^<\/?|\/?>$/,content:{pattern:/\s*\S[\s\S]*/,alias:"ftl",inside:r}}},"ftl-interpolation":{pattern:/^\$\{[\s\S]*\}$/,inside:{punctuation:/^\$\{|\}$/,content:{pattern:/\s*\S[\s\S]*/,alias:"ftl",inside:r}}}},e.hooks.add("before-tokenize",function(n){var r=RegExp(/<#--[\s\S]*?-->|<\/?[#@][a-zA-Z](?:)*?>|\$\{(?:)*?\}/.source.replace(//g,function(){return t}),"gi");e.languages["markup-templating"].buildPlaceholders(n,"ftl",r)}),e.hooks.add("after-tokenize",function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"ftl")})}(e)}e.exports=i,i.displayName="ftl",i.aliases=[]},12208(e){"use strict";function t(e){e.languages.gcode={comment:/;.*|\B\(.*?\)\B/,string:{pattern:/"(?:""|[^"])*"/,greedy:!0},keyword:/\b[GM]\d+(?:\.\d+)?\b/,property:/\b[A-Z]/,checksum:{pattern:/\*\d+/,alias:"punctuation"},punctuation:/:/}}e.exports=t,t.displayName="gcode",t.aliases=[]},62728(e){"use strict";function t(e){e.languages.gdscript={comment:/#.*/,string:{pattern:/@?(?:("|')(?:(?!\1)[^\n\\]|\\[\s\S])*\1(?!"|')|"""(?:[^\\]|\\[\s\S])*?""")/,greedy:!0},"class-name":{pattern:/(^(?:class_name|class|extends)[ \t]+|^export\([ \t]*|\bas[ \t]+|(?:\b(?:const|var)[ \t]|[,(])[ \t]*\w+[ \t]*:[ \t]*|->[ \t]*)[a-zA-Z_]\w*/m,lookbehind:!0},keyword:/\b(?:and|as|assert|break|breakpoint|class|class_name|const|continue|elif|else|enum|export|extends|for|func|if|in|is|master|mastersync|match|not|null|onready|or|pass|preload|puppet|puppetsync|remote|remotesync|return|self|setget|signal|static|tool|var|while|yield)\b/,function:/\b[a-z_]\w*(?=[ \t]*\()/i,variable:/\$\w+/,number:[/\b0b[01_]+\b|\b0x[\da-fA-F_]+\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.[\d_]+)(?:e[+-]?[\d_]+)?\b/,/\b(?:INF|NAN|PI|TAU)\b/],constant:/\b[A-Z][A-Z_\d]*\b/,boolean:/\b(?:false|true)\b/,operator:/->|:=|&&|\|\||<<|>>|[-+*/%&|!<>=]=?|[~^]/,punctuation:/[.:,;()[\]{}]/}}e.exports=t,t.displayName="gdscript",t.aliases=[]},81549(e){"use strict";function t(e){e.languages.gedcom={"line-value":{pattern:/(^[\t ]*\d+ +(?:@\w[\w!"$%&'()*+,\-./:;<=>?[\\\]^`{|}~\x80-\xfe #]*@ +)?\w+ ).+/m,lookbehind:!0,inside:{pointer:{pattern:/^@\w[\w!"$%&'()*+,\-./:;<=>?[\\\]^`{|}~\x80-\xfe #]*@$/,alias:"variable"}}},tag:{pattern:/(^[\t ]*\d+ +(?:@\w[\w!"$%&'()*+,\-./:;<=>?[\\\]^`{|}~\x80-\xfe #]*@ +)?)\w+/m,lookbehind:!0,alias:"string"},level:{pattern:/(^[\t ]*)\d+/m,lookbehind:!0,alias:"number"},pointer:{pattern:/@\w[\w!"$%&'()*+,\-./:;<=>?[\\\]^`{|}~\x80-\xfe #]*@/,alias:"variable"}}}e.exports=t,t.displayName="gedcom",t.aliases=[]},6024(e){"use strict";function t(e){var t,n;n=/(?:\r?\n|\r)[ \t]*\|.+\|(?:(?!\|).)*/.source,(t=e).languages.gherkin={pystring:{pattern:/("""|''')[\s\S]+?\1/,alias:"string"},comment:{pattern:/(^[ \t]*)#.*/m,lookbehind:!0},tag:{pattern:/(^[ \t]*)@\S*/m,lookbehind:!0},feature:{pattern:/((?:^|\r?\n|\r)[ \t]*)(?:Ability|Ahoy matey!|Arwedd|Aspekt|Besigheid Behoefte|Business Need|Caracteristica|Característica|Egenskab|Egenskap|Eiginleiki|Feature|Fīča|Fitur|Fonctionnalité|Fonksyonalite|Funcionalidade|Funcionalitat|Functionalitate|Funcţionalitate|Funcționalitate|Functionaliteit|Fungsi|Funkcia|Funkcija|Funkcionalitāte|Funkcionalnost|Funkcja|Funksie|Funktionalität|Funktionalitéit|Funzionalità|Hwaet|Hwæt|Jellemző|Karakteristik|laH|Lastnost|Mak|Mogucnost|Mogućnost|Moznosti|Možnosti|OH HAI|Omadus|Ominaisuus|Osobina|Özellik|perbogh|poQbogh malja'|Potrzeba biznesowa|Požadavek|Požiadavka|Pretty much|Qap|Qu'meH 'ut|Savybė|Tính năng|Trajto|Vermoë|Vlastnosť|Właściwość|Značilnost|Δυνατότητα|Λειτουργία|Могућност|Мөмкинлек|Особина|Свойство|Үзенчәлеклелек|Функционал|Функционалност|Функция|Функціонал|תכונה|خاصية|خصوصیت|صلاحیت|کاروبار کی ضرورت|وِیژگی|रूप लेख|ਖਾਸੀਅਤ|ਨਕਸ਼ ਨੁਹਾਰ|ਮੁਹਾਂਦਰਾ|గుణము|ಹೆಚ್ಚಳ|ความต้องการทางธุรกิจ|ความสามารถ|โครงหลัก|기능|フィーチャ|功能|機能):(?:[^:\r\n]+(?:\r?\n|\r|$))*/,lookbehind:!0,inside:{important:{pattern:/(:)[^\r\n]+/,lookbehind:!0},keyword:/[^:\r\n]+:/}},scenario:{pattern:/(^[ \t]*)(?:Abstract Scenario|Abstrakt Scenario|Achtergrond|Aer|Ær|Agtergrond|All y'all|Antecedentes|Antecedents|Atburðarás|Atburðarásir|Awww, look mate|B4|Background|Baggrund|Bakgrund|Bakgrunn|Bakgrunnur|Beispiele|Beispiller|Bối cảnh|Cefndir|Cenario|Cenário|Cenario de Fundo|Cenário de Fundo|Cenarios|Cenários|Contesto|Context|Contexte|Contexto|Conto|Contoh|Contone|Dæmi|Dasar|Dead men tell no tales|Delineacao do Cenario|Delineação do Cenário|Dis is what went down|Dữ liệu|Dyagram senaryo|Dyagram Senaryo|Egzanp|Ejemplos|Eksempler|Ekzemploj|Enghreifftiau|Esbozo do escenario|Escenari|Escenario|Esempi|Esquema de l'escenari|Esquema del escenario|Esquema do Cenario|Esquema do Cenário|Examples|EXAMPLZ|Exempel|Exemple|Exemples|Exemplos|First off|Fono|Forgatókönyv|Forgatókönyv vázlat|Fundo|Geçmiş|ghantoH|Grundlage|Hannergrond|Háttér|Heave to|Istorik|Juhtumid|Keadaan|Khung kịch bản|Khung tình huống|Kịch bản|Koncept|Konsep skenario|Kontèks|Kontekst|Kontekstas|Konteksts|Kontext|Konturo de la scenaro|Latar Belakang|lut|lut chovnatlh|lutmey|Lýsing Atburðarásar|Lýsing Dæma|Menggariskan Senario|MISHUN|MISHUN SRSLY|mo'|Náčrt Scenára|Náčrt Scénáře|Náčrt Scenáru|Oris scenarija|Örnekler|Osnova|Osnova Scenára|Osnova scénáře|Osnutek|Ozadje|Paraugs|Pavyzdžiai|Példák|Piemēri|Plan du scénario|Plan du Scénario|Plan senaryo|Plan Senaryo|Plang vum Szenario|Pozadí|Pozadie|Pozadina|Príklady|Příklady|Primer|Primeri|Primjeri|Przykłady|Raamstsenaarium|Reckon it's like|Rerefons|Scenár|Scénář|Scenarie|Scenarij|Scenarijai|Scenarijaus šablonas|Scenariji|Scenārijs|Scenārijs pēc parauga|Scenarijus|Scenario|Scénario|Scenario Amlinellol|Scenario Outline|Scenario Template|Scenariomal|Scenariomall|Scenarios|Scenariu|Scenariusz|Scenaro|Schema dello scenario|Se ðe|Se the|Se þe|Senario|Senaryo|Senaryo deskripsyon|Senaryo Deskripsyon|Senaryo taslağı|Shiver me timbers|Situācija|Situai|Situasie|Situasie Uiteensetting|Skenario|Skenario konsep|Skica|Structura scenariu|Structură scenariu|Struktura scenarija|Stsenaarium|Swa|Swa hwaer swa|Swa hwær swa|Szablon scenariusza|Szenario|Szenariogrundriss|Tapaukset|Tapaus|Tapausaihio|Taust|Tausta|Template Keadaan|Template Senario|Template Situai|The thing of it is|Tình huống|Variantai|Voorbeelde|Voorbeelden|Wharrimean is|Yo-ho-ho|You'll wanna|Założenia|Παραδείγματα|Περιγραφή Σεναρίου|Σενάρια|Σενάριο|Υπόβαθρο|Кереш|Контекст|Концепт|Мисаллар|Мисоллар|Основа|Передумова|Позадина|Предистория|Предыстория|Приклади|Пример|Примери|Примеры|Рамка на сценарий|Скица|Структура сценарија|Структура сценария|Структура сценарію|Сценарий|Сценарий структураси|Сценарийның төзелеше|Сценарији|Сценарио|Сценарій|Тарих|Үрнәкләр|דוגמאות|רקע|תבנית תרחיש|תרחיש|الخلفية|الگوی سناریو|امثلة|پس منظر|زمینه|سناریو|سيناريو|سيناريو مخطط|مثالیں|منظر نامے کا خاکہ|منظرنامہ|نمونه ها|उदाहरण|परिदृश्य|परिदृश्य रूपरेखा|पृष्ठभूमि|ਉਦਾਹਰਨਾਂ|ਪਟਕਥਾ|ਪਟਕਥਾ ਢਾਂਚਾ|ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ|ਪਿਛੋਕੜ|ఉదాహరణలు|కథనం|నేపథ్యం|సన్నివేశం|ಉದಾಹರಣೆಗಳು|ಕಥಾಸಾರಾಂಶ|ವಿವರಣೆ|ಹಿನ್ನೆಲೆ|โครงสร้างของเหตุการณ์|ชุดของตัวอย่าง|ชุดของเหตุการณ์|แนวคิด|สรุปเหตุการณ์|เหตุการณ์|배경|시나리오|시나리오 개요|예|サンプル|シナリオ|シナリオアウトライン|シナリオテンプレ|シナリオテンプレート|テンプレ|例|例子|剧本|剧本大纲|劇本|劇本大綱|场景|场景大纲|場景|場景大綱|背景):[^:\r\n]*/m,lookbehind:!0,inside:{important:{pattern:/(:)[^\r\n]*/,lookbehind:!0},keyword:/[^:\r\n]+:/}},"table-body":{pattern:RegExp("("+n+")(?:"+n+")+"),lookbehind:!0,inside:{outline:{pattern:/<[^>]+>/,alias:"variable"},td:{pattern:/\s*[^\s|][^|]*/,alias:"string"},punctuation:/\|/}},"table-head":{pattern:RegExp(n),inside:{th:{pattern:/\s*[^\s|][^|]*/,alias:"variable"},punctuation:/\|/}},atrule:{pattern:/(^[ \t]+)(?:'ach|'a|'ej|7|a|A také|A taktiež|A tiež|A zároveň|Aber|Ac|Adott|Akkor|Ak|Aleshores|Ale|Ali|Allora|Alors|Als|Ama|Amennyiben|Amikor|Ampak|an|AN|Ananging|And y'all|And|Angenommen|Anrhegedig a|An|Apabila|Atès|Atesa|Atunci|Avast!|Aye|A|awer|Bagi|Banjur|Bet|Biết|Blimey!|Buh|But at the end of the day I reckon|But y'all|But|BUT|Cal|Când|Cando|Cand|Ce|Cuando|Če|Ða ðe|Ða|Dadas|Dada|Dados|Dado|DaH ghu' bejlu'|dann|Dann|Dano|Dan|Dar|Dat fiind|Data|Date fiind|Date|Dati fiind|Dati|Daţi fiind|Dați fiind|Dato|DEN|Den youse gotta|Dengan|De|Diberi|Diyelim ki|Donada|Donat|Donitaĵo|Do|Dun|Duota|Ðurh|Eeldades|Ef|Eğer ki|Entao|Então|Entón|Entonces|En|Epi|E|És|Etant donnée|Etant donné|Et|Étant données|Étant donnée|Étant donné|Etant données|Etant donnés|Étant donnés|Fakat|Gangway!|Gdy|Gegeben seien|Gegeben sei|Gegeven|Gegewe|ghu' noblu'|Gitt|Given y'all|Given|Givet|Givun|Ha|Cho|I CAN HAZ|In|Ir|It's just unbelievable|I|Ja|Jeśli|Jeżeli|Kadar|Kada|Kad|Kai|Kaj|Když|Keď|Kemudian|Ketika|Khi|Kiedy|Ko|Kuid|Kui|Kun|Lan|latlh|Le sa a|Let go and haul|Le|Lè sa a|Lè|Logo|Lorsqu'<|Lorsque|mä|Maar|Mais|Mając|Majd|Maka|Manawa|Mas|Ma|Menawa|Men|Mutta|Nalikaning|Nalika|Nanging|Når|När|Nato|Nhưng|Niin|Njuk|O zaman|Og|Och|Oletetaan|Onda|Ond|Oraz|Pak|Pero|Però|Podano|Pokiaľ|Pokud|Potem|Potom|Privzeto|Pryd|qaSDI'|Quando|Quand|Quan|Så|Sed|Se|Siis|Sipoze ke|Sipoze Ke|Sipoze|Si|Şi|Și|Soit|Stel|Tada|Tad|Takrat|Tak|Tapi|Ter|Tetapi|Tha the|Tha|Then y'all|Then|Thì|Thurh|Toda|Too right|ugeholl|Und|Un|Và|vaj|Vendar|Ve|wann|Wanneer|WEN|Wenn|When y'all|When|Wtedy|Wun|Y'know|Yeah nah|Yna|Youse know like when|Youse know when youse got|Y|Za predpokladu|Za předpokladu|Zadani|Zadano|Zadan|Zadate|Zadato|Zakładając|Zaradi|Zatati|Þa þe|Þa|Þá|Þegar|Þurh|Αλλά|Δεδομένου|Και|Όταν|Τότε|А також|Агар|Але|Али|Аммо|А|Әгәр|Әйтик|Әмма|Бирок|Ва|Вә|Дадено|Дано|Допустим|Если|Задате|Задати|Задато|И|І|К тому же|Када|Кад|Когато|Когда|Коли|Ләкин|Лекин|Нәтиҗәдә|Нехай|Но|Онда|Припустимо, що|Припустимо|Пусть|Также|Та|Тогда|Тоді|То|Унда|Һәм|Якщо|אבל|אזי|אז|בהינתן|וגם|כאשר|آنگاه|اذاً|اگر|اما|اور|با فرض|بالفرض|بفرض|پھر|تب|ثم|جب|عندما|فرض کیا|لكن|لیکن|متى|هنگامی|و|अगर|और|कदा|किन्तु|चूंकि|जब|तथा|तदा|तब|परन्तु|पर|यदि|ਅਤੇ|ਜਦੋਂ|ਜਿਵੇਂ ਕਿ|ਜੇਕਰ|ਤਦ|ਪਰ|అప్పుడు|ఈ పరిస్థితిలో|కాని|చెప్పబడినది|మరియు|ಆದರೆ|ನಂತರ|ನೀಡಿದ|ಮತ್ತು|ಸ್ಥಿತಿಯನ್ನು|กำหนดให้|ดังนั้น|แต่|เมื่อ|และ|그러면<|그리고<|단<|만약<|만일<|먼저<|조건<|하지만<|かつ<|しかし<|ただし<|ならば<|もし<|並且<|但し<|但是<|假如<|假定<|假設<|假设<|前提<|同时<|同時<|并且<|当<|當<|而且<|那么<|那麼<)(?=[ \t])/m,lookbehind:!0},string:{pattern:/"(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*'/,inside:{outline:{pattern:/<[^>]+>/,alias:"variable"}}},outline:{pattern:/<[^>]+>/,alias:"variable"}}}e.exports=t,t.displayName="gherkin",t.aliases=[]},13600(e){"use strict";function t(e){e.languages.git={comment:/^#.*/m,deleted:/^[-–].*/m,inserted:/^\+.*/m,string:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/m,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s--?\w+/m}},coord:/^@@.*@@$/m,"commit-sha1":/^commit \w{40}$/m}}e.exports=t,t.displayName="git",t.aliases=[]},3322(e,t,n){"use strict";var r=n(65806);function i(e){e.register(r),e.languages.glsl=e.languages.extend("c",{keyword:/\b(?:attribute|const|uniform|varying|buffer|shared|coherent|volatile|restrict|readonly|writeonly|atomic_uint|layout|centroid|flat|smooth|noperspective|patch|sample|break|continue|do|for|while|switch|case|default|if|else|subroutine|in|out|inout|float|double|int|void|bool|true|false|invariant|precise|discard|return|d?mat[234](?:x[234])?|[ibdu]?vec[234]|uint|lowp|mediump|highp|precision|[iu]?sampler[123]D|[iu]?samplerCube|sampler[12]DShadow|samplerCubeShadow|[iu]?sampler[12]DArray|sampler[12]DArrayShadow|[iu]?sampler2DRect|sampler2DRectShadow|[iu]?samplerBuffer|[iu]?sampler2DMS(?:Array)?|[iu]?samplerCubeArray|samplerCubeArrayShadow|[iu]?image[123]D|[iu]?image2DRect|[iu]?imageCube|[iu]?imageBuffer|[iu]?image[12]DArray|[iu]?imageCubeArray|[iu]?image2DMS(?:Array)?|struct|common|partition|active|asm|class|union|enum|typedef|template|this|resource|goto|inline|noinline|public|static|extern|external|interface|long|short|half|fixed|unsigned|superp|input|output|hvec[234]|fvec[234]|sampler3DRect|filter|sizeof|cast|namespace|using)\b/})}e.exports=i,i.displayName="glsl",i.aliases=[]},53877(e){"use strict";function t(e){e.languages.gamemakerlanguage=e.languages.gml=e.languages.extend("clike",{keyword:/\b(?:if|else|switch|case|default|break|for|repeat|while|do|until|continue|exit|return|globalvar|var|enum)\b/,number:/(?:\b0x[\da-f]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ulf]{0,4}/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not|with|at|xor)\b/,constant:/\b(?:self|other|all|noone|global|local|undefined|pointer_(?:invalid|null)|action_(?:stop|restart|continue|reverse)|pi|GM_build_date|GM_version|timezone_(?:local|utc)|gamespeed_(?:fps|microseconds)|ev_(?:create|destroy|step|alarm|keyboard|mouse|collision|other|draw|draw_(?:begin|end|pre|post)|keypress|keyrelease|trigger|(?:left|right|middle|no)_button|(?:left|right|middle)_press|(?:left|right|middle)_release|mouse_(?:enter|leave|wheel_up|wheel_down)|global_(?:left|right|middle)_button|global_(?:left|right|middle)_press|global_(?:left|right|middle)_release|joystick(?:1|2)_(?:left|right|up|down|button1|button2|button3|button4|button5|button6|button7|button8)|outside|boundary|game_start|game_end|room_start|room_end|no_more_lives|animation_end|end_of_path|no_more_health|user\d|step_(?:normal|begin|end)|gui|gui_begin|gui_end)|vk_(?:nokey|anykey|enter|return|shift|control|alt|escape|space|backspace|tab|pause|printscreen|left|right|up|down|home|end|delete|insert|pageup|pagedown|f\d|numpad\d|divide|multiply|subtract|add|decimal|lshift|lcontrol|lalt|rshift|rcontrol|ralt)|mb_(?:any|none|left|right|middle)|c_(?:aqua|black|blue|dkgray|fuchsia|gray|green|lime|ltgray|maroon|navy|olive|purple|red|silver|teal|white|yellow|orange)|fa_(?:left|center|right|top|middle|bottom|readonly|hidden|sysfile|volumeid|directory|archive)|pr_(?:pointlist|linelist|linestrip|trianglelist|trianglestrip|trianglefan)|bm_(?:complex|normal|add|max|subtract|zero|one|src_colour|inv_src_colour|src_color|inv_src_color|src_alpha|inv_src_alpha|dest_alpha|inv_dest_alpha|dest_colour|inv_dest_colour|dest_color|inv_dest_color|src_alpha_sat)|audio_(?:falloff_(?:none|inverse_distance|inverse_distance_clamped|linear_distance|linear_distance_clamped|exponent_distance|exponent_distance_clamped)|old_system|new_system|mono|stereo|3d)|cr_(?:default|none|arrow|cross|beam|size_nesw|size_ns|size_nwse|size_we|uparrow|hourglass|drag|appstart|handpoint|size_all)|asset_(?:object|unknown|sprite|sound|room|path|script|font|timeline|tiles|shader)|ds_type_(?:map|list|stack|queue|grid|priority)|ef_(?:explosion|ring|ellipse|firework|smoke|smokeup|star|spark|flare|cloud|rain|snow)|pt_shape_(?:pixel|disk|square|line|star|circle|ring|sphere|flare|spark|explosion|cloud|smoke|snow)|ps_(?:distr|shape)_(?:linear|gaussian|invgaussian|rectangle|ellipse|diamond|line)|ty_(?:real|string)|dll_(?:cdel|cdecl|stdcall)|matrix_(?:view|projection|world)|os_(?:win32|windows|macosx|ios|android|linux|unknown|winphone|win8native|psvita|ps4|xboxone|ps3|uwp)|browser_(?:not_a_browser|unknown|ie|firefox|chrome|safari|safari_mobile|opera|tizen|windows_store|ie_mobile)|device_ios_(?:unknown|iphone|iphone_retina|ipad|ipad_retina|iphone5|iphone6|iphone6plus)|device_(?:emulator|tablet)|display_(?:landscape|landscape_flipped|portrait|portrait_flipped)|of_challenge_(?:win|lose|tie)|leaderboard_type_(?:number|time_mins_secs)|cmpfunc_(?:never|less|equal|lessequal|greater|notequal|greaterequal|always)|cull_(?:noculling|clockwise|counterclockwise)|lighttype_(?:dir|point)|iap_(?:ev_storeload|ev_product|ev_purchase|ev_consume|ev_restore|storeload_ok|storeload_failed|status_uninitialised|status_unavailable|status_loading|status_available|status_processing|status_restoring|failed|unavailable|available|purchased|canceled|refunded)|fb_login_(?:default|fallback_to_webview|no_fallback_to_webview|forcing_webview|use_system_account|forcing_safari)|phy_joint_(?:anchor_1_x|anchor_1_y|anchor_2_x|anchor_2_y|reaction_force_x|reaction_force_y|reaction_torque|motor_speed|angle|motor_torque|max_motor_torque|translation|speed|motor_force|max_motor_force|length_1|length_2|damping_ratio|frequency|lower_angle_limit|upper_angle_limit|angle_limits|max_length|max_torque|max_force)|phy_debug_render_(?:aabb|collision_pairs|coms|core_shapes|joints|obb|shapes)|phy_particle_flag_(?:water|zombie|wall|spring|elastic|viscous|powder|tensile|colourmixing|colormixing)|phy_particle_group_flag_(?:solid|rigid)|phy_particle_data_flag_(?:typeflags|position|velocity|colour|color|category)|achievement_(?:our_info|friends_info|leaderboard_info|info|filter_(?:all_players|friends_only|favorites_only)|type_challenge|type_score_challenge|pic_loaded|show_(?:ui|profile|leaderboard|achievement|bank|friend_picker|purchase_prompt))|network_(?:socket_(?:tcp|udp|bluetooth)|type_(?:connect|disconnect|data|non_blocking_connect)|config_(?:connect_timeout|use_non_blocking_socket|enable_reliable_udp|disable_reliable_udp))|buffer_(?:fixed|grow|wrap|fast|vbuffer|network|u8|s8|u16|s16|u32|s32|u64|f16|f32|f64|bool|text|string|seek_start|seek_relative|seek_end|generalerror|outofspace|outofbounds|invalidtype)|gp_(?:face\d|shoulderl|shoulderr|shoulderlb|shoulderrb|select|start|stickl|stickr|padu|padd|padl|padr|axislh|axislv|axisrh|axisrv)|ov_(?:friends|community|players|settings|gamegroup|achievements)|lb_sort_(?:none|ascending|descending)|lb_disp_(?:none|numeric|time_sec|time_ms)|ugc_(?:result_success|filetype_(?:community|microtrans)|visibility_(?:public|friends_only|private)|query_RankedBy(?:Vote|PublicationDate|Trend|NumTimesReported|TotalVotesAsc|VotesUp|TextSearch)|query_(?:AcceptedForGameRankedByAcceptanceDate|FavoritedByFriendsRankedByPublicationDate|CreatedByFriendsRankedByPublicationDate|NotYetRated)|sortorder_CreationOrder(?:Desc|Asc)|sortorder_(?:TitleAsc|LastUpdatedDesc|SubscriptionDateDesc|VoteScoreDesc|ForModeration)|list_(?:Published|VotedOn|VotedUp|VotedDown|WillVoteLater|Favorited|Subscribed|UsedOrPlayed|Followed)|match_(?:Items|Items_Mtx|Items_ReadyToUse|Collections|Artwork|Videos|Screenshots|AllGuides|WebGuides|IntegratedGuides|UsableInGame|ControllerBindings))|vertex_usage_(?:position|colour|color|normal|texcoord|textcoord|blendweight|blendindices|psize|tangent|binormal|fog|depth|sample)|vertex_type_(?:float\d|colour|color|ubyte4)|layerelementtype_(?:undefined|background|instance|oldtilemap|sprite|tilemap|particlesystem|tile)|tile_(?:rotate|flip|mirror|index_mask)|input_type|se_(?:chorus|compressor|echo|equalizer|flanger|gargle|none|reverb)|text_type|(?:obj|scr|spr|rm)\w+)\b/,variable:/\b(?:x|y|(?:x|y)(?:previous|start)|(?:h|v)speed|direction|speed|friction|gravity|gravity_direction|path_(?:index|position|positionprevious|speed|scale|orientation|endaction)|object_index|id|solid|persistent|mask_index|instance_(?:count|id)|alarm|timeline_(?:index|position|speed|running|loop)|visible|sprite_(?:index|width|height|xoffset|yoffset)|image_(?:number|index|speed|depth|xscale|yscale|angle|alpha|blend)|bbox_(?:left|right|top|bottom)|layer|phy_(?:rotation|(?:position|linear_velocity|speed|com|collision|col_normal)_(?:x|y)|angular_(?:velocity|damping)|position_(?:x|y)previous|speed|linear_damping|bullet|fixed_rotation|active|mass|inertia|dynamic|kinematic|sleeping|collision_points)|working_directory|webgl_enabled|view_(?:(?:y|x|w|h)view|(?:y|x|w|h)port|(?:v|h)(?:speed|border)|visible|surface_id|object|enabled|current|angle)|undefined|transition_(?:steps|kind|color)|temp_directory|show_(?:score|lives|health)|secure_mode|score|room_(?:width|speed|persistent|last|height|first|caption)|room|pointer_(?:null|invalid)|os_(?:version|type|device|browser)|mouse_(?:y|x|lastbutton|button)|lives|keyboard_(?:string|lastkey|lastchar|key)|iap_data|health|gamemaker_(?:version|registered|pro)|game_(?:save|project|display)_(?:id|name)|fps_real|fps|event_(?:type|object|number|action)|error_(?:occurred|last)|display_aa|delta_time|debug_mode|cursor_sprite|current_(?:year|weekday|time|second|month|minute|hour|day)|caption_(?:score|lives|health)|browser_(?:width|height)|background_(?:yscale|y|xscale|x|width|vtiled|vspeed|visible|showcolour|showcolor|index|htiled|hspeed|height|foreground|colour|color|blend|alpha)|async_load|application_surface|argument(?:_relitive|_count|\d)|argument|global|local|self|other)\b/})}e.exports=t,t.displayName="gml",t.aliases=[]},51519(e){"use strict";function t(e){e.languages.go=e.languages.extend("clike",{string:{pattern:/(["'`])(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|iota|nil|true|false)\b/,number:/(?:\b0x[a-f\d]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[-+]?\d+)?)i?/i,operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:bool|byte|complex(?:64|128)|error|float(?:32|64)|rune|string|u?int(?:8|16|32|64)?|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(?:ln)?|real|recover)\b/}),delete e.languages.go["class-name"]}e.exports=t,t.displayName="go",t.aliases=[]},94055(e){"use strict";function t(e){e.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:e.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:true|false)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/[A-Z]\w*Input(?=!?.*$)/m,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*\{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},e.hooks.add("after-tokenize",function(e){if("graphql"===e.language){for(var t=e.tokens.filter(function(e){return"string"!=typeof e&&"comment"!==e.type&&"scalar"!==e.type}),n=0;n0)){var s=d(/^\{$/,/^\}$/);if(-1===s)continue;for(var u=n;u=0&&h(c,"variable-input")}}}}}function l(e){return t[n+e]}function f(e,t){t=t||0;for(var n=0;n]?|\+[+=]?|!=?|<(?:<=?|=>?)?|>(?:>>?=?|=)?|&[&=]?|\|[|=]?|\/=?|\^=?|%=?)/,lookbehind:!0},punctuation:/\.+|[{}[\];(),:$]/}),e.languages.insertBefore("groovy","string",{shebang:{pattern:/#!.+/,alias:"comment"}}),e.languages.insertBefore("groovy","punctuation",{"spock-block":/\b(?:setup|given|when|then|and|cleanup|expect|where):/}),e.languages.insertBefore("groovy","function",{annotation:{pattern:/(^|[^.])@\w+/,lookbehind:!0,alias:"punctuation"}}),e.hooks.add("wrap",function(t){if("groovy"===t.language&&"string"===t.type){var n=t.content.value[0];if("'"!=n){var r=/([^\\])(?:\$(?:\{.*?\}|[\w.]+))/;"$"===n&&(r=/([^\$])(?:\$(?:\{.*?\}|[\w.]+))/),t.content.value=t.content.value.replace(/</g,"<").replace(/&/g,"&"),t.content=e.highlight(t.content.value,{expression:{pattern:r,lookbehind:!0,inside:e.languages.groovy}}),t.classes.push("/"===n?"regex":"gstring")}}})}e.exports=t,t.displayName="groovy",t.aliases=[]},29536(e,t,n){"use strict";var r=n(56939);function i(e){e.register(r),function(e){e.languages.haml={"multiline-comment":{pattern:/((?:^|\r?\n|\r)([\t ]*))(?:\/|-#).*(?:(?:\r?\n|\r)\2[\t ].+)*/,lookbehind:!0,alias:"comment"},"multiline-code":[{pattern:/((?:^|\r?\n|\r)([\t ]*)(?:[~-]|[&!]?=)).*,[\t ]*(?:(?:\r?\n|\r)\2[\t ].*,[\t ]*)*(?:(?:\r?\n|\r)\2[\t ].+)/,lookbehind:!0,inside:e.languages.ruby},{pattern:/((?:^|\r?\n|\r)([\t ]*)(?:[~-]|[&!]?=)).*\|[\t ]*(?:(?:\r?\n|\r)\2[\t ].*\|[\t ]*)*/,lookbehind:!0,inside:e.languages.ruby}],filter:{pattern:/((?:^|\r?\n|\r)([\t ]*)):[\w-]+(?:(?:\r?\n|\r)(?:\2[\t ].+|\s*?(?=\r?\n|\r)))+/,lookbehind:!0,inside:{"filter-name":{pattern:/^:[\w-]+/,alias:"variable"}}},markup:{pattern:/((?:^|\r?\n|\r)[\t ]*)<.+/,lookbehind:!0,inside:e.languages.markup},doctype:{pattern:/((?:^|\r?\n|\r)[\t ]*)!!!(?: .+)?/,lookbehind:!0},tag:{pattern:/((?:^|\r?\n|\r)[\t ]*)[%.#][\w\-#.]*[\w\-](?:\([^)]+\)|\{(?:\{[^}]+\}|[^{}])+\}|\[[^\]]+\])*[\/<>]*/,lookbehind:!0,inside:{attributes:[{pattern:/(^|[^#])\{(?:\{[^}]+\}|[^{}])+\}/,lookbehind:!0,inside:e.languages.ruby},{pattern:/\([^)]+\)/,inside:{"attr-value":{pattern:/(=\s*)(?:"(?:\\.|[^\\"\r\n])*"|[^)\s]+)/,lookbehind:!0},"attr-name":/[\w:-]+(?=\s*!?=|\s*[,)])/,punctuation:/[=(),]/}},{pattern:/\[[^\]]+\]/,inside:e.languages.ruby}],punctuation:/[<>]/}},code:{pattern:/((?:^|\r?\n|\r)[\t ]*(?:[~-]|[&!]?=)).+/,lookbehind:!0,inside:e.languages.ruby},interpolation:{pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"},rest:e.languages.ruby}},punctuation:{pattern:/((?:^|\r?\n|\r)[\t ]*)[~=\-&!]+/,lookbehind:!0}};for(var t="((?:^|\\r?\\n|\\r)([\\t ]*)):{{filter_name}}(?:(?:\\r?\\n|\\r)(?:\\2[\\t ].+|\\s*?(?=\\r?\\n|\\r)))+",n=["css",{filter:"coffee",language:"coffeescript"},"erb","javascript","less","markdown","ruby","scss","textile"],r={},i=0,a=n.length;i@\[\\\]^`{|}~]/,variable:/[^!"#%&'()*+,\/;<=>@\[\\\]^`{|}~\s]+/},t.hooks.add("before-tokenize",function(e){var n=/\{\{\{[\s\S]+?\}\}\}|\{\{[\s\S]+?\}\}/g;t.languages["markup-templating"].buildPlaceholders(e,"handlebars",n)}),t.hooks.add("after-tokenize",function(e){t.languages["markup-templating"].tokenizePlaceholders(e,"handlebars")}),t.languages.hbs=t.languages.handlebars}e.exports=i,i.displayName="handlebars",i.aliases=["hbs"]},58090(e){"use strict";function t(e){e.languages.haskell={comment:{pattern:/(^|[^-!#$%*+=?&@|~.:<>^\\\/])(?:--(?:(?=.)[^-!#$%*+=?&@|~.:<>^\\\/].*|$)|\{-[\s\S]*?-\})/m,lookbehind:!0},char:{pattern:/'(?:[^\\']|\\(?:[abfnrtv\\"'&]|\^[A-Z@[\]^_]|NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|\d+|o[0-7]+|x[0-9a-fA-F]+))'/,alias:"string"},string:{pattern:/"(?:[^\\"]|\\(?:\S|\s+\\))*"/,greedy:!0},keyword:/\b(?:case|class|data|deriving|do|else|if|in|infixl|infixr|instance|let|module|newtype|of|primitive|then|type|where)\b/,"import-statement":{pattern:/(^[\t ]*)import\s+(?:qualified\s+)?(?:[A-Z][\w']*)(?:\.[A-Z][\w']*)*(?:\s+as\s+(?:[A-Z][\w']*)(?:\.[A-Z][\w']*)*)?(?:\s+hiding\b)?/m,lookbehind:!0,inside:{keyword:/\b(?:import|qualified|as|hiding)\b/}},builtin:/\b(?:abs|acos|acosh|all|and|any|appendFile|approxRational|asTypeOf|asin|asinh|atan|atan2|atanh|basicIORun|break|catch|ceiling|chr|compare|concat|concatMap|const|cos|cosh|curry|cycle|decodeFloat|denominator|digitToInt|div|divMod|drop|dropWhile|either|elem|encodeFloat|enumFrom|enumFromThen|enumFromThenTo|enumFromTo|error|even|exp|exponent|fail|filter|flip|floatDigits|floatRadix|floatRange|floor|fmap|foldl|foldl1|foldr|foldr1|fromDouble|fromEnum|fromInt|fromInteger|fromIntegral|fromRational|fst|gcd|getChar|getContents|getLine|group|head|id|inRange|index|init|intToDigit|interact|ioError|isAlpha|isAlphaNum|isAscii|isControl|isDenormalized|isDigit|isHexDigit|isIEEE|isInfinite|isLower|isNaN|isNegativeZero|isOctDigit|isPrint|isSpace|isUpper|iterate|last|lcm|length|lex|lexDigits|lexLitChar|lines|log|logBase|lookup|map|mapM|mapM_|max|maxBound|maximum|maybe|min|minBound|minimum|mod|negate|not|notElem|null|numerator|odd|or|ord|otherwise|pack|pi|pred|primExitWith|print|product|properFraction|putChar|putStr|putStrLn|quot|quotRem|range|rangeSize|read|readDec|readFile|readFloat|readHex|readIO|readInt|readList|readLitChar|readLn|readOct|readParen|readSigned|reads|readsPrec|realToFrac|recip|rem|repeat|replicate|return|reverse|round|scaleFloat|scanl|scanl1|scanr|scanr1|seq|sequence|sequence_|show|showChar|showInt|showList|showLitChar|showParen|showSigned|showString|shows|showsPrec|significand|signum|sin|sinh|snd|sort|span|splitAt|sqrt|subtract|succ|sum|tail|take|takeWhile|tan|tanh|threadToIOResult|toEnum|toInt|toInteger|toLower|toRational|toUpper|truncate|uncurry|undefined|unlines|until|unwords|unzip|unzip3|userError|words|writeFile|zip|zip3|zipWith|zipWith3)\b/,number:/\b(?:\d+(?:\.\d+)?(?:e[+-]?\d+)?|0o[0-7]+|0x[0-9a-f]+)\b/i,operator:/\s\.\s|[-!#$%*+=?&@|~:<>^\\\/]*\.[-!#$%*+=?&@|~.:<>^\\\/]+|[-!#$%*+=?&@|~.:<>^\\\/]+\.[-!#$%*+=?&@|~:<>^\\\/]*|[-!#$%*+=?&@|~:<>^\\\/]+|`(?:[A-Z][\w']*\.)*[_a-z][\w']*`/,hvariable:/\b(?:[A-Z][\w']*\.)*[_a-z][\w']*\b/,constant:/\b(?:[A-Z][\w']*\.)*[A-Z][\w']*\b/,punctuation:/[{}[\];(),.:]/},e.languages.hs=e.languages.haskell}e.exports=t,t.displayName="haskell",t.aliases=["hs"]},95121(e){"use strict";function t(e){e.languages.haxe=e.languages.extend("clike",{string:{pattern:/(["'])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0,inside:{interpolation:{pattern:/(^|[^\\])\$(?:\w+|\{[^}]+\})/,lookbehind:!0,inside:{interpolation:{pattern:/^\$\w*/,alias:"variable"}}}}},keyword:/\bthis\b|\b(?:abstract|as|break|case|cast|catch|class|continue|default|do|dynamic|else|enum|extends|extern|from|for|function|if|implements|import|in|inline|interface|macro|new|null|override|public|private|return|static|super|switch|throw|to|try|typedef|using|var|while)(?!\.)\b/,operator:/\.{3}|\+\+?|-[->]?|[=!]=?|&&?|\|\|?|<[<=]?|>[>=]?|[*\/%~^]/}),e.languages.insertBefore("haxe","class-name",{regex:{pattern:/~\/(?:[^\/\\\r\n]|\\.)+\/[igmsu]*/,greedy:!0}}),e.languages.insertBefore("haxe","keyword",{preprocessor:{pattern:/#\w+/,alias:"builtin"},metadata:{pattern:/@:?\w+/,alias:"symbol"},reification:{pattern:/\$(?:\w+|(?=\{))/,alias:"variable"}}),e.languages.haxe.string.inside.interpolation.inside.rest=e.languages.haxe,delete e.languages.haxe["class-name"]}e.exports=t,t.displayName="haxe",t.aliases=[]},59904(e){"use strict";function t(e){e.languages.hcl={comment:/(?:\/\/|#).*|\/\*[\s\S]*?(?:\*\/|$)/,heredoc:{pattern:/<<-?(\w+\b)[\s\S]*?^[ \t]*\1/m,greedy:!0,alias:"string"},keyword:[{pattern:/(?:resource|data)\s+(?:"(?:\\[\s\S]|[^\\"])*")(?=\s+"[\w-]+"\s+\{)/i,inside:{type:{pattern:/(resource|data|\s+)(?:"(?:\\[\s\S]|[^\\"])*")/i,lookbehind:!0,alias:"variable"}}},{pattern:/(?:provider|provisioner|variable|output|module|backend)\s+(?:[\w-]+|"(?:\\[\s\S]|[^\\"])*")\s+(?=\{)/i,inside:{type:{pattern:/(provider|provisioner|variable|output|module|backend)\s+(?:[\w-]+|"(?:\\[\s\S]|[^\\"])*")\s+/i,lookbehind:!0,alias:"variable"}}},/[\w-]+(?=\s+\{)/],property:[/[-\w\.]+(?=\s*=(?!=))/,/"(?:\\[\s\S]|[^\\"])+"(?=\s*[:=])/],string:{pattern:/"(?:[^\\$"]|\\[\s\S]|\$(?:(?=")|\$+(?!\$)|[^"${])|\$\{(?:[^{}"]|"(?:[^\\"]|\\[\s\S])*")*\})*"/,greedy:!0,inside:{interpolation:{pattern:/(^|[^$])\$\{(?:[^{}"]|"(?:[^\\"]|\\[\s\S])*")*\}/,lookbehind:!0,inside:{type:{pattern:/(\b(?:terraform|var|self|count|module|path|data|local)\b\.)[\w\*]+/i,lookbehind:!0,alias:"variable"},keyword:/\b(?:terraform|var|self|count|module|path|data|local)\b/i,function:/\w+(?=\()/,string:{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0},number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?(?:e[+-]?\d+)?/i,punctuation:/[!\$#%&'()*+,.\/;<=>@\[\\\]^`{|}~?:]/}}}},number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?(?:e[+-]?\d+)?/i,boolean:/\b(?:true|false)\b/i,punctuation:/[=\[\]{}]/}}e.exports=t,t.displayName="hcl",t.aliases=[]},9436(e,t,n){"use strict";var r=n(65806);function i(e){e.register(r),e.languages.hlsl=e.languages.extend("c",{"class-name":[e.languages.c["class-name"],/\b(?:AppendStructuredBuffer|BlendState|Buffer|ByteAddressBuffer|CompileShader|ComputeShader|ConsumeStructuredBuffer|DepthStencilState|DepthStencilView|DomainShader|GeometryShader|Hullshader|InputPatch|LineStream|OutputPatch|PixelShader|PointStream|RasterizerState|RenderTargetView|RWBuffer|RWByteAddressBuffer|RWStructuredBuffer|RWTexture(?:1D|1DArray|2D|2DArray|3D)|SamplerComparisonState|SamplerState|StructuredBuffer|Texture(?:1D|1DArray|2D|2DArray|2DMS|2DMSArray|3D|Cube|CubeArray)|TriangleStream|VertexShader)\b/],keyword:[/\b(?:asm|asm_fragment|auto|break|case|catch|cbuffer|centroid|char|class|column_major|compile|compile_fragment|const|const_cast|continue|default|delete|discard|do|dynamic_cast|else|enum|explicit|export|extern|for|friend|fxgroup|goto|groupshared|if|in|inline|inout|interface|line|lineadj|linear|long|matrix|mutable|namespace|new|nointerpolation|noperspective|operator|out|packoffset|pass|pixelfragment|point|precise|private|protected|public|register|reinterpret_cast|return|row_major|sample|sampler|shared|short|signed|sizeof|snorm|stateblock|stateblock_state|static|static_cast|string|struct|switch|tbuffer|technique|technique10|technique11|template|texture|this|throw|triangle|triangleadj|try|typedef|typename|uniform|union|unorm|unsigned|using|vector|vertexfragment|virtual|void|volatile|while)\b/,/\b(?:bool|double|dword|float|half|int|min(?:10float|12int|16(?:float|int|uint))|uint)(?:[1-4](?:x[1-4])?)?\b/],number:/(?:(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[eE][+-]?\d+)?|\b0x[\da-fA-F]+)[fFhHlLuU]?\b/,boolean:/\b(?:false|true)\b/})}e.exports=i,i.displayName="hlsl",i.aliases=[]},76942(e){"use strict";function t(e){e.languages.hpkp={directive:{pattern:/\b(?:(?:includeSubDomains|preload|strict)(?: |;)|pin-sha256="[a-zA-Z\d+=/]+"|(?:max-age|report-uri)=|report-to )/,alias:"keyword"},safe:{pattern:/\b\d{7,}\b/,alias:"selector"},unsafe:{pattern:/\b\d{1,6}\b/,alias:"function"}}}e.exports=t,t.displayName="hpkp",t.aliases=[]},60561(e){"use strict";function t(e){e.languages.hsts={directive:{pattern:/\b(?:max-age=|includeSubDomains|preload)/,alias:"keyword"},safe:{pattern:/\b\d{8,}\b/,alias:"selector"},unsafe:{pattern:/\b\d{1,7}\b/,alias:"function"}}}e.exports=t,t.displayName="hsts",t.aliases=[]},49660(e){"use strict";function t(e){!function(e){e.languages.http={"request-line":{pattern:/^(?:GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI|SEARCH)\s(?:https?:\/\/|\/)\S*\sHTTP\/[0-9.]+/m,inside:{method:{pattern:/^[A-Z]+\b/,alias:"property"},"request-target":{pattern:/^(\s)(?:https?:\/\/|\/)\S*(?=\s)/,lookbehind:!0,alias:"url",inside:e.languages.uri},"http-version":{pattern:/^(\s)HTTP\/[0-9.]+/,lookbehind:!0,alias:"property"}}},"response-status":{pattern:/^HTTP\/[0-9.]+ \d+ .+/m,inside:{"http-version":{pattern:/^HTTP\/[0-9.]+/,alias:"property"},"status-code":{pattern:/^(\s)\d+(?=\s)/,lookbehind:!0,alias:"number"},"reason-phrase":{pattern:/^(\s).+/,lookbehind:!0,alias:"string"}}},"header-name":{pattern:/^[\w-]+:(?=.)/m,alias:"keyword"}};var t,n=e.languages,r={"application/javascript":n.javascript,"application/json":n.json||n.javascript,"application/xml":n.xml,"text/xml":n.xml,"text/html":n.html,"text/css":n.css},i={"application/json":!0,"application/xml":!0};function a(e){var t="\\w+/(?:[\\w.-]+\\+)+"+e.replace(/^[a-z]+\//,"")+"(?![+\\w.-])";return"(?:"+e+"|"+t+")"}for(var o in r)if(r[o]){t=t||{};var s=i[o]?a(o):o;t[o.replace(/\//g,"-")]={pattern:RegExp("(content-type:\\s*"+s+"(?:(?:\\r\\n?|\\n).+)*)(?:\\r?\\n|\\r){2}[\\s\\S]*","i"),lookbehind:!0,inside:r[o]}}t&&e.languages.insertBefore("http","header-name",t)}(e)}e.exports=t,t.displayName="http",t.aliases=[]},30615(e){"use strict";function t(e){e.languages.ichigojam={comment:/(?:\B'|REM)(?:[^\n\r]*)/i,string:{pattern:/"(?:""|[!#$%&'()*,\/:;<=>?^\w +\-.])*"/i,greedy:!0},number:/\B#[0-9A-F]+|\B`[01]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:E[+-]?\d+)?/i,keyword:/\b(?:BEEP|BPS|CASE|CLEAR|CLK|CLO|CLP|CLS|CLT|CLV|CONT|COPY|ELSE|END|FILE|FILES|FOR|GOSUB|GSB|GOTO|IF|INPUT|KBD|LED|LET|LIST|LOAD|LOCATE|LRUN|NEW|NEXT|OUT|RIGHT|PLAY|POKE|PRINT|PWM|REM|RENUM|RESET|RETURN|RTN|RUN|SAVE|SCROLL|SLEEP|SRND|STEP|STOP|SUB|TEMPO|THEN|TO|UART|VIDEO|WAIT)(?:\$|\b)/i,function:/\b(?:ABS|ANA|ASC|BIN|BTN|DEC|END|FREE|HELP|HEX|I2CR|I2CW|IN|INKEY|LEN|LINE|PEEK|RND|SCR|SOUND|STR|TICK|USR|VER|VPEEK|ZER)(?:\$|\b)/i,label:/(?:\B@\S+)/i,operator:/<[=>]?|>=?|\|\||&&|[+\-*\/=|&^~!]|\b(?:AND|NOT|OR)\b/i,punctuation:/[\[,;:()\]]/}}e.exports=t,t.displayName="ichigojam",t.aliases=[]},93865(e){"use strict";function t(e){e.languages.icon={comment:/#.*/,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n_]|\\.|_(?!\1)(?:\r\n|[\s\S]))*\1/,greedy:!0},number:/\b(?:\d+r[a-z\d]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)\b|\.\d+\b/i,"builtin-keyword":{pattern:/&(?:allocated|ascii|clock|collections|cset|current|date|dateline|digits|dump|e|error(?:number|text|value)?|errout|fail|features|file|host|input|lcase|letters|level|line|main|null|output|phi|pi|pos|progname|random|regions|source|storage|subject|time|trace|ucase|version)\b/,alias:"variable"},directive:{pattern:/\$\w+/,alias:"builtin"},keyword:/\b(?:break|by|case|create|default|do|else|end|every|fail|global|if|initial|invocable|link|local|next|not|of|procedure|record|repeat|return|static|suspend|then|to|until|while)\b/,function:/\b(?!\d)\w+(?=\s*[({]|\s*!\s*\[)/,operator:/[+-]:(?!=)|(?:[\/?@^%&]|\+\+?|--?|==?=?|~==?=?|\*\*?|\|\|\|?|<(?:->?|>?=?)(?::=)?|:(?:=:?)?|[!.\\|~]/,punctuation:/[\[\](){},;]/}}e.exports=t,t.displayName="icon",t.aliases=[]},51078(e){"use strict";function t(e){!function(e){function t(e,n){return n<=0?/[]/.source:e.replace(//g,function(){return t(e,n-1)})}var n=/'[{}:=,](?:[^']|'')*'(?!')/,r={pattern:/''/,greedy:!0,alias:"operator"},i={pattern:n,greedy:!0,inside:{escape:r}},a=t(/\{(?:[^{}']|'(?![{},'])|''||)*\}/.source.replace(//g,function(){return n.source}),8),o={pattern:RegExp(a),inside:{message:{pattern:/^(\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:null},"message-delimiter":{pattern:/./,alias:"punctuation"}}};e.languages["icu-message-format"]={argument:{pattern:RegExp(a),greedy:!0,inside:{content:{pattern:/^(\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:{"argument-name":{pattern:/^(\s*)[^{}:=,\s]+/,lookbehind:!0},"choice-style":{pattern:/^(\s*,\s*choice\s*,\s*)\S(?:[\s\S]*\S)?/,lookbehind:!0,inside:{punctuation:/\|/,range:{pattern:/^(\s*)[+-]?(?:\d+(?:\.\d*)?|\u221e)\s*[<#\u2264]/,lookbehind:!0,inside:{operator:/[<#\u2264]/,number:/\S+/}},rest:null}},"plural-style":{pattern:/^(\s*,\s*(?:plural|selectordinal)\s*,\s*)\S(?:[\s\S]*\S)?/,lookbehind:!0,inside:{offset:/^offset:\s*\d+/,"nested-message":o,selector:{pattern:/=\d+|[^{}:=,\s]+/,inside:{keyword:/^(?:zero|one|two|few|many|other)$/}}}},"select-style":{pattern:/^(\s*,\s*select\s*,\s*)\S(?:[\s\S]*\S)?/,lookbehind:!0,inside:{"nested-message":o,selector:{pattern:/[^{}:=,\s]+/,inside:{keyword:/^other$/}}}},keyword:/\b(?:choice|plural|select|selectordinal)\b/,"arg-type":{pattern:/\b(?:number|date|time|spellout|ordinal|duration)\b/,alias:"keyword"},"arg-skeleton":{pattern:/(,\s*)::[^{}:=,\s]+/,lookbehind:!0},"arg-style":{pattern:/(,\s*)(?:short|medium|long|full|integer|currency|percent)(?=\s*$)/,lookbehind:!0},"arg-style-text":{pattern:RegExp(/(^\s*,\s*(?=\S))/.source+t(/(?:[^{}']|'[^']*'|\{(?:)?\})+/.source,8)+"$"),lookbehind:!0,alias:"string"},punctuation:/,/}},"argument-delimiter":{pattern:/./,alias:"operator"}}},escape:r,string:i},o.inside.message.inside=e.languages["icu-message-format"],e.languages["icu-message-format"].argument.inside.content.inside["choice-style"].inside.rest=e.languages["icu-message-format"]}(e)}e.exports=t,t.displayName="icuMessageFormat",t.aliases=[]},91178(e,t,n){"use strict";var r=n(58090);function i(e){e.register(r),e.languages.idris=e.languages.extend("haskell",{comment:{pattern:/(?:(?:--|\|\|\|).*$|\{-[\s\S]*?-\})/m},keyword:/\b(?:Type|case|class|codata|constructor|corecord|data|do|dsl|else|export|if|implementation|implicit|import|impossible|in|infix|infixl|infixr|instance|interface|let|module|mutual|namespace|of|parameters|partial|postulate|private|proof|public|quoteGoal|record|rewrite|syntax|then|total|using|where|with)\b/,"import-statement":{pattern:/(^\s*)import\s+(?:[A-Z][\w']*)(?:\.[A-Z][\w']*)*/m,lookbehind:!0},builtin:void 0}),e.languages.idr=e.languages.idris}e.exports=i,i.displayName="idris",i.aliases=["idr"]},40011(e){"use strict";function t(e){e.languages.iecst={comment:[{pattern:/(^|[^\\])(?:\/\*[\s\S]*?(?:\*\/|$)|\(\*[\s\S]*?(?:\*\)|$)|\{[\s\S]*?(?:\}|$))/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":/\b(?:END_)?(?:PROGRAM|CONFIGURATION|INTERFACE|FUNCTION_BLOCK|FUNCTION|ACTION|TRANSITION|TYPE|STRUCT|(?:INITIAL_)?STEP|NAMESPACE|LIBRARY|CHANNEL|FOLDER|RESOURCE|VAR_(?:GLOBAL|INPUT|PUTPUT|IN_OUT|ACCESS|TEMP|EXTERNAL|CONFIG)|VAR|METHOD|PROPERTY)\b/i,keyword:/\b(?:(?:END_)?(?:IF|WHILE|REPEAT|CASE|FOR)|ELSE|FROM|THEN|ELSIF|DO|TO|BY|PRIVATE|PUBLIC|PROTECTED|CONSTANT|RETURN|EXIT|CONTINUE|GOTO|JMP|AT|RETAIN|NON_RETAIN|TASK|WITH|UNTIL|USING|EXTENDS|IMPLEMENTS|GET|SET|__TRY|__CATCH|__FINALLY|__ENDTRY)\b/,variable:/\b(?:AT|BOOL|BYTE|(?:D|L)?WORD|U?(?:S|D|L)?INT|L?REAL|TIME(?:_OF_DAY)?|TOD|DT|DATE(?:_AND_TIME)?|STRING|ARRAY|ANY|POINTER)\b/,symbol:/%[IQM][XBWDL][\d.]*|%[IQ][\d.]*/,number:/\b(?:16#[\da-f]+|2#[01_]+|0x[\da-f]+)\b|\b(?:T|D|DT|TOD)#[\d_shmd:]*|\b[A-Z]*#[\d.,_]*|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,boolean:/\b(?:TRUE|FALSE|NULL)\b/,function:/\w+(?=\()/,operator:/(?:S?R?:?=>?|&&?|\*\*?|<=?|>=?|[-:^/+])|\b(?:OR|AND|MOD|NOT|XOR|LE|GE|EQ|NE|GT|LT)\b/,punctuation:/[();]/,type:{pattern:/#/,alias:"selector"}}}e.exports=t,t.displayName="iecst",t.aliases=[]},12017(e){"use strict";function t(e){var t;(t=e).languages.ignore={comment:/^#.*/m,entry:{pattern:/\S(?:.*(?:(?:\\ )|\S))?/,alias:"string",inside:{operator:/^!|\*\*?|\?/,regex:{pattern:/(^|[^\\])\[[^\[\]]*\]/,lookbehind:!0},punctuation:/\//}}},t.languages.gitignore=t.languages.ignore,t.languages.hgignore=t.languages.ignore,t.languages.npmignore=t.languages.ignore}e.exports=t,t.displayName="ignore",t.aliases=["gitignore","hgignore","npmignore"]},65175(e){"use strict";function t(e){e.languages.inform7={string:{pattern:/"[^"]*"/,inside:{substitution:{pattern:/\[[^\[\]]+\]/,inside:{delimiter:{pattern:/\[|\]/,alias:"punctuation"}}}}},comment:{pattern:/\[[^\[\]]+\]/,greedy:!0},title:{pattern:/^[ \t]*(?:volume|book|part(?! of)|chapter|section|table)\b.+/im,alias:"important"},number:{pattern:/(^|[^-])(?:\b\d+(?:\.\d+)?(?:\^\d+)?(?:(?!\d)\w+)?|\b(?:one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve))\b(?!-)/i,lookbehind:!0},verb:{pattern:/(^|[^-])\b(?:applying to|are|attacking|answering|asking|be(?:ing)?|burning|buying|called|carries|carry(?! out)|carrying|climbing|closing|conceal(?:s|ing)?|consulting|contain(?:s|ing)?|cutting|drinking|dropping|eating|enclos(?:es?|ing)|entering|examining|exiting|getting|giving|going|ha(?:ve|s|ving)|hold(?:s|ing)?|impl(?:y|ies)|incorporat(?:es?|ing)|inserting|is|jumping|kissing|listening|locking|looking|mean(?:s|ing)?|opening|provid(?:es?|ing)|pulling|pushing|putting|relat(?:es?|ing)|removing|searching|see(?:s|ing)?|setting|showing|singing|sleeping|smelling|squeezing|switching|support(?:s|ing)?|swearing|taking|tasting|telling|thinking|throwing|touching|turning|tying|unlock(?:s|ing)?|var(?:y|ies|ying)|waiting|waking|waving|wear(?:s|ing)?)\b(?!-)/i,lookbehind:!0,alias:"operator"},keyword:{pattern:/(^|[^-])\b(?:after|before|carry out|check|continue the action|definition(?= *:)|do nothing|else|end (?:if|unless|the story)|every turn|if|include|instead(?: of)?|let|move|no|now|otherwise|repeat|report|resume the story|rule for|running through|say(?:ing)?|stop the action|test|try(?:ing)?|understand|unless|use|when|while|yes)\b(?!-)/i,lookbehind:!0},property:{pattern:/(^|[^-])\b(?:adjacent(?! to)|carried|closed|concealed|contained|dark|described|edible|empty|enclosed|enterable|even|female|fixed in place|full|handled|held|improper-named|incorporated|inedible|invisible|lighted|lit|lock(?:able|ed)|male|marked for listing|mentioned|negative|neuter|non-(?:empty|full|recurring)|odd|opaque|open(?:able)?|plural-named|portable|positive|privately-named|proper-named|provided|publically-named|pushable between rooms|recurring|related|rubbing|scenery|seen|singular-named|supported|swinging|switch(?:able|ed(?: on| off)?)|touch(?:able|ed)|transparent|unconcealed|undescribed|unlit|unlocked|unmarked for listing|unmentioned|unopenable|untouchable|unvisited|variable|visible|visited|wearable|worn)\b(?!-)/i,lookbehind:!0,alias:"symbol"},position:{pattern:/(^|[^-])\b(?:above|adjacent to|back side of|below|between|down|east|everywhere|front side|here|in|inside(?: from)?|north(?:east|west)?|nowhere|on(?: top of)?|other side|outside(?: from)?|parts? of|regionally in|south(?:east|west)?|through|up|west|within)\b(?!-)/i,lookbehind:!0,alias:"keyword"},type:{pattern:/(^|[^-])\b(?:actions?|activit(?:y|ies)|actors?|animals?|backdrops?|containers?|devices?|directions?|doors?|holders?|kinds?|lists?|m[ae]n|nobody|nothing|nouns?|numbers?|objects?|people|persons?|player(?:'s holdall)?|regions?|relations?|rooms?|rule(?:book)?s?|scenes?|someone|something|supporters?|tables?|texts?|things?|time|vehicles?|wom[ae]n)\b(?!-)/i,lookbehind:!0,alias:"variable"},punctuation:/[.,:;(){}]/},e.languages.inform7.string.inside.substitution.inside.rest=e.languages.inform7,e.languages.inform7.string.inside.substitution.inside.rest.text={pattern:/\S(?:\s*\S)*/,alias:"comment"}}e.exports=t,t.displayName="inform7",t.aliases=[]},14970(e){"use strict";function t(e){e.languages.ini={comment:{pattern:/(^[ \f\t\v]*)[#;][^\n\r]*/m,lookbehind:!0},header:{pattern:/(^[ \f\t\v]*)\[[^\n\r\]]*\]?/m,lookbehind:!0,inside:{"section-name":{pattern:/(^\[[ \f\t\v]*)[^ \f\t\v\]]+(?:[ \f\t\v]+[^ \f\t\v\]]+)*/,lookbehind:!0,alias:"selector"},punctuation:/\[|\]/}},key:{pattern:/(^[ \f\t\v]*)[^ \f\n\r\t\v=]+(?:[ \f\t\v]+[^ \f\n\r\t\v=]+)*(?=[ \f\t\v]*=)/m,lookbehind:!0,alias:"attr-name"},value:{pattern:/(=[ \f\t\v]*)[^ \f\n\r\t\v]+(?:[ \f\t\v]+[^ \f\n\r\t\v]+)*/,lookbehind:!0,alias:"attr-value",inside:{"inner-value":{pattern:/^("|').+(?=\1$)/,lookbehind:!0}}},punctuation:/=/}}e.exports=t,t.displayName="ini",t.aliases=[]},30764(e){"use strict";function t(e){e.languages.io={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0},{pattern:/(^|[^\\])#.*/,lookbehind:!0}],"triple-quoted-string":{pattern:/"""(?:\\[\s\S]|(?!""")[^\\])*"""/,greedy:!0,alias:"string"},string:{pattern:/"(?:\\.|[^\\\r\n"])*"/,greedy:!0},keyword:/\b(?:activate|activeCoroCount|asString|block|break|catch|clone|collectGarbage|compileString|continue|do|doFile|doMessage|doString|else|elseif|exit|for|foreach|forward|getSlot|getEnvironmentVariable|hasSlot|if|ifFalse|ifNil|ifNilEval|ifTrue|isActive|isNil|isResumable|list|message|method|parent|pass|pause|perform|performWithArgList|print|println|proto|raise|raiseResumable|removeSlot|resend|resume|schedulerSleepSeconds|self|sender|setSchedulerSleepSeconds|setSlot|shallowCopy|slotNames|super|system|then|thisBlock|thisContext|call|try|type|uniqueId|updateSlot|wait|while|write|yield)\b/,builtin:/\b(?:Array|AudioDevice|AudioMixer|Block|Box|Buffer|CFunction|CGI|Color|Curses|DBM|DNSResolver|DOConnection|DOProxy|DOServer|Date|Directory|Duration|DynLib|Error|Exception|FFT|File|Fnmatch|Font|Future|GL|GLE|GLScissor|GLU|GLUCylinder|GLUQuadric|GLUSphere|GLUT|Host|Image|Importer|LinkList|List|Lobby|Locals|MD5|MP3Decoder|MP3Encoder|Map|Message|Movie|Notification|Number|Object|OpenGL|Point|Protos|Regex|SGML|SGMLElement|SGMLParser|SQLite|Server|Sequence|ShowMessage|SleepyCat|SleepyCatCursor|Socket|SocketManager|Sound|Soup|Store|String|Tree|UDPSender|UPDReceiver|URL|User|Warning|WeakLink|Random|BigNum)\b/,boolean:/\b(?:true|false|nil)\b/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e-?\d+)?/i,operator:/[=!*/%+\-^&|]=|>>?=?|<+*\-%$|,#][.:]?|[?^]\.?|[;\[]:?|[~}"i][.:]|[ACeEIjLor]\.|(?:[_\/\\qsux]|_?\d):)/,alias:"keyword"},number:/\b_?(?:(?!\d:)\d+(?:\.\d+)?(?:(?:[ejpx]|ad|ar)_?\d+(?:\.\d+)?)*(?:b_?[\da-z]+(?:\.[\da-z]+)?)?|_\b(?!\.))/,adverb:{pattern:/[~}]|[\/\\]\.?|[bfM]\.|t[.:]/,alias:"builtin"},operator:/[=a][.:]|_\./,conjunction:{pattern:/&(?:\.:?|:)?|[.:@][.:]?|[!D][.:]|[;dHT]\.|`:?|[\^LS]:|"/,alias:"variable"},punctuation:/[()]/}}e.exports=t,t.displayName="j",t.aliases=[]},15909(e){"use strict";function t(e){var t,n,r,i;t=e,n=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,i={pattern:RegExp((r=/(^|[^\w.])(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source)+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}},t.languages.java=t.languages.extend("clike",{"class-name":[i,{pattern:RegExp(r+/[A-Z]\w*(?=\s+\w+\s*[;,=()])/.source),lookbehind:!0,inside:i.inside}],keyword:n,function:[t.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0}}),t.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"}}),t.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":i,keyword:n,punctuation:/[<>(),.:]/,operator:/[?&|]/}},namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(//g,function(){return n.source})),lookbehind:!0,inside:{punctuation:/\./}}})}e.exports=t,t.displayName="java",t.aliases=[]},36553(e,t,n){"use strict";var r=n(15909),i=n(9858);function a(e){var t,n,a,o;e.register(r),e.register(i),t=e,n=/(^(?:[\t ]*(?:\*\s*)*))[^*\s].*$/m,a=/#\s*\w+(?:\s*\([^()]*\))?/.source,o=/(?:\b[a-zA-Z]\w+\s*\.\s*)*\b[A-Z]\w*(?:\s*)?|/.source.replace(//g,function(){return a}),t.languages.javadoc=t.languages.extend("javadoclike",{}),t.languages.insertBefore("javadoc","keyword",{reference:{pattern:RegExp(/(@(?:exception|throws|see|link|linkplain|value)\s+(?:\*\s*)?)/.source+"(?:"+o+")"),lookbehind:!0,inside:{function:{pattern:/(#\s*)\w+(?=\s*\()/,lookbehind:!0},field:{pattern:/(#\s*)\w+/,lookbehind:!0},namespace:{pattern:/\b(?:[a-z]\w*\s*\.\s*)+/,inside:{punctuation:/\./}},"class-name":/\b[A-Z]\w*/,keyword:t.languages.java.keyword,punctuation:/[#()[\],.]/}},"class-name":{pattern:/(@param\s+)<[A-Z]\w*>/,lookbehind:!0,inside:{punctuation:/[.<>]/}},"code-section":[{pattern:/(\{@code\s+(?!\s))(?:[^\s{}]|\s+(?![\s}])|\{(?:[^{}]|\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})*\})+(?=\s*\})/,lookbehind:!0,inside:{code:{pattern:n,lookbehind:!0,inside:t.languages.java,alias:"language-java"}}},{pattern:/(<(code|pre|tt)>(?!)\s*)\S(?:\S|\s+\S)*?(?=\s*<\/\2>)/,lookbehind:!0,inside:{line:{pattern:n,lookbehind:!0,inside:{tag:t.languages.markup.tag,entity:t.languages.markup.entity,code:{pattern:/.+/,inside:t.languages.java,alias:"language-java"}}}}}],tag:t.languages.markup.tag,entity:t.languages.markup.entity}),t.languages.javadoclike.addSupport("java",t.languages.javadoc)}e.exports=a,a.displayName="javadoc",a.aliases=[]},9858(e){"use strict";function t(e){!function(e){var t=e.languages.javadoclike={parameter:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*@(?:param|arg|arguments)\s+)\w+/m,lookbehind:!0},keyword:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*|\{)@[a-z][a-zA-Z-]+\b/m,lookbehind:!0},punctuation:/[{}]/};function n(t,n){var r="doc-comment",i=e.languages[t];if(i){var a=i[r];if(!a){var o={};o[r]={pattern:/(^|[^\\])\/\*\*[^/][\s\S]*?(?:\*\/|$)/,lookbehind:!0,alias:"comment"},a=(i=e.languages.insertBefore(t,"comment",o))[r]}if(a instanceof RegExp&&(a=i[r]={pattern:a}),Array.isArray(a))for(var s=0,u=a.length;s|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),e.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,e.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:e.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:e.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:e.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:e.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:e.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),e.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:e.languages.javascript}},string:/[\s\S]+/}}}),e.languages.markup&&(e.languages.markup.tag.addInlined("script","javascript"),e.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),e.languages.js=e.languages.javascript}e.exports=t,t.displayName="javascript",t.aliases=["js"]},11223(e){"use strict";function t(e){e.languages.javastacktrace={summary:{pattern:/^[\t ]*(?:(?:Caused by:|Suppressed:|Exception in thread "[^"]*")[\t ]+)?[\w$.]+(?::.*)?$/m,inside:{keyword:{pattern:/^(\s*)(?:(?:Caused by|Suppressed)(?=:)|Exception in thread)/m,lookbehind:!0},string:{pattern:/^(\s*)"[^"]*"/,lookbehind:!0},exceptions:{pattern:/^(:?\s*)[\w$.]+(?=:|$)/,lookbehind:!0,inside:{"class-name":/[\w$]+(?=$|:)/,namespace:/[a-z]\w*/,punctuation:/[.:]/}},message:{pattern:/(:\s*)\S.*/,lookbehind:!0,alias:"string"},punctuation:/:/}},"stack-frame":{pattern:/^[\t ]*at (?:[\w$./]|@[\w$.+-]*\/)+(?:)?\([^()]*\)/m,inside:{keyword:{pattern:/^(\s*)at(?= )/,lookbehind:!0},source:[{pattern:/(\()\w+\.\w+:\d+(?=\))/,lookbehind:!0,inside:{file:/^\w+\.\w+/,punctuation:/:/,"line-number":{pattern:/\d+/,alias:"number"}}},{pattern:/(\()[^()]*(?=\))/,lookbehind:!0,inside:{keyword:/^(?:Unknown Source|Native Method)$/}}],"class-name":/[\w$]+(?=\.(?:|[\w$]+)\()/,function:/(?:|[\w$]+)(?=\()/,"class-loader":{pattern:/(\s)[a-z]\w*(?:\.[a-z]\w*)*(?=\/[\w@$.]*\/)/,lookbehind:!0,alias:"namespace",inside:{punctuation:/\./}},module:{pattern:/([\s/])[a-z]\w*(?:\.[a-z]\w*)*(?:@[\w$.+-]*)?(?=\/)/,lookbehind:!0,inside:{version:{pattern:/(@)[\s\S]+/,lookbehind:!0,alias:"number"},punctuation:/[@.]/}},namespace:{pattern:/(?:[a-z]\w*\.)+/,inside:{punctuation:/\./}},punctuation:/[()/.]/}},more:{pattern:/^[\t ]*\.{3} \d+ [a-z]+(?: [a-z]+)*/m,inside:{punctuation:/\.{3}/,number:/\d+/,keyword:/\b[a-z]+(?: [a-z]+)*\b/}}}}e.exports=t,t.displayName="javastacktrace",t.aliases=[]},57957(e){"use strict";function t(e){e.languages.jexl={string:/(["'])(?:\\[\s\S]|(?!\1)[^\\])*\1/,transform:{pattern:/(\|\s*)[a-zA-Zа-яА-Я_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF$][\wа-яА-Я\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF$]*/,alias:"function",lookbehind:!0},function:/[a-zA-Zа-яА-Я_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF$][\wа-яА-Я\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF$]*\s*(?=\()/,number:/\b\d+(?:\.\d+)?\b|\B\.\d+\b/,operator:/[<>!]=?|-|\+|&&|==|\|\|?|\/\/?|[?:*^%]/,boolean:/\b(?:true|false)\b/,keyword:/\bin\b/,punctuation:/[{}[\](),.]/}}e.exports=t,t.displayName="jexl",t.aliases=[]},75807(e){"use strict";function t(e){e.languages.jolie=e.languages.extend("clike",{string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},keyword:/\b(?:include|define|is_defined|undef|main|init|outputPort|inputPort|Location|Protocol|Interfaces|RequestResponse|OneWay|type|interface|extender|throws|cset|csets|forward|Aggregates|Redirects|embedded|courier|execution|sequential|concurrent|single|scope|install|throw|comp|cH|default|global|linkIn|linkOut|synchronized|this|new|for|if|else|while|in|Jolie|Java|Javascript|nullProcess|spawn|constants|with|provide|until|exit|foreach|instanceof|over|service)\b/,number:/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?l?/i,operator:/-[-=>]?|\+[+=]?|<[<=]?|[>=*!]=?|&&|\|\||[:?\/%^]/,punctuation:/[,.]/,builtin:/\b(?:undefined|string|int|void|long|Byte|bool|double|float|char|any)\b/,symbol:/[|;@]/}),delete e.languages.jolie["class-name"],e.languages.insertBefore("jolie","keyword",{function:{pattern:/((?:\b(?:outputPort|inputPort|in|service|courier)\b|@)\s*)\w+/,lookbehind:!0},aggregates:{pattern:/(\bAggregates\s*:\s*)(?:\w+(?:\s+with\s+\w+)?\s*,\s*)*\w+(?:\s+with\s+\w+)?/,lookbehind:!0,inside:{"with-extension":{pattern:/\bwith\s+\w+/,inside:{keyword:/\bwith\b/}},function:{pattern:/\w+/},punctuation:{pattern:/,/}}},redirects:{pattern:/(\bRedirects\s*:\s*)(?:\w+\s*=>\s*\w+\s*,\s*)*(?:\w+\s*=>\s*\w+)/,lookbehind:!0,inside:{punctuation:{pattern:/,/},function:{pattern:/\w+/},symbol:{pattern:/=>/}}}})}e.exports=t,t.displayName="jolie",t.aliases=[]},77935(e){"use strict";function t(e){var t,n,r,i,a;t=e,n=/\\\((?:[^()]|\([^()]*\))*\)/.source,r=RegExp(/"(?:[^"\r\n\\]|\\[^\r\n(]|__)*"/.source.replace(/__/g,function(){return n})),i={interpolation:{pattern:RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+n),lookbehind:!0,inside:{content:{pattern:/^(\\\()[\s\S]+(?=\)$)/,lookbehind:!0,inside:null},punctuation:/^\\\(|\)$/}}},a=t.languages.jq={comment:/#.*/,property:{pattern:RegExp(r.source+/(?=\s*:(?!:))/.source),greedy:!0,inside:i},string:{pattern:r,greedy:!0,inside:i},function:{pattern:/(\bdef\s+)[a-z_]\w+/i,lookbehind:!0},variable:/\B\$\w+/,"property-literal":{pattern:/\b[a-z_]\w*(?=\s*:(?!:))/i,alias:"property"},keyword:/\b(?:as|break|catch|def|elif|else|end|foreach|if|import|include|label|module|modulemeta|null|reduce|then|try|while)\b/,boolean:/\b(?:true|false)\b/,number:/(?:\b\d+\.|\B\.)?\b\d+(?:[eE][+-]?\d+)?\b/,operator:[{pattern:/\|=?/,alias:"pipe"},/\.\.|[!=<>]?=|\?\/\/|\/\/=?|[-+*/%]=?|[<>?]|\b(?:and|or|not)\b/],"c-style-function":{pattern:/\b[a-z_]\w*(?=\s*\()/i,alias:"function"},punctuation:/::|[()\[\]{},:;]|\.(?=\s*[\[\w$])/,dot:{pattern:/\./,alias:"important"}},i.interpolation.inside.content.inside=a}e.exports=t,t.displayName="jq",t.aliases=[]},46155(e){"use strict";function t(e){!function(e){function t(e,t){return RegExp(e.replace(//g,function(){return/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/.source}),t)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:(?:Uint|Int)(?:8|16|32)|Uint8Clamped|Float(?:32|64))?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|(?:Weak)?(?:Set|Map)|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:t(/(\bimport\b\s*)(?:(?:\s*,\s*(?:\*\s*as\s+|\{[^{}]*\}))?|\*\s*as\s+|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|for|finally|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:t(/(\.\s*)#?/.source),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|location|navigator|performance|(?:local|session)Storage|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var n=["function","function-variable","method","method-variable","property-access"],r=0;r=h.length)return;var n=e[t];if("string"==typeof n||"string"==typeof n.content){var r=h[o],i="string"==typeof n?n:n.content,a=i.indexOf(r);if(-1!==a){++o;var s=i.substring(0,a),u=c(l[r]),f=i.substring(a+r.length),d=[];if(s&&d.push(s),d.push(u),f){var b=[f];p(b),d.push.apply(d,b)}"string"==typeof n?(e.splice.apply(e,[t,1].concat(d)),t+=d.length-1):n.content=d}}else{var m=n.content;Array.isArray(m)?p(m):p([m])}}}return o=0,p(d),new e.Token(r,d,"language-"+r,t)}e.languages.javascript["template-string"]=[o("css",/\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source),o("html",/\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source),o("svg",/\bsvg/.source),o("markdown",/\b(?:md|markdown)/.source),o("graphql",/\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source),o("sql",/\bsql/.source),t].filter(Boolean);var f={javascript:!0,js:!0,typescript:!0,ts:!0,jsx:!0,tsx:!0};function d(e){return"string"==typeof e?e:Array.isArray(e)?e.map(d).join(""):d(e.content)}e.hooks.add("after-tokenize",function(t){t.language in f&&n(t.tokens);function n(t){for(var r=0,i=t.length;r\s+)?)[A-Z]\w*(?:\.[A-Z]\w*)*/.source.replace(//g,function(){return a})),lookbehind:!0,inside:{punctuation:/\./}},{pattern:RegExp("(@[a-z]+\\s+)"+a),lookbehind:!0,inside:{string:n.string,number:n.number,boolean:n.boolean,keyword:t.languages.typescript.keyword,operator:/=>|\.\.\.|[&|?:*]/,punctuation:/[.,;=<>{}()[\]]/}}],example:{pattern:/(@example\s+(?!\s))(?:[^@\s]|\s+(?!\s))+?(?=\s*(?:\*\s*)?(?:@\w|\*\/))/,lookbehind:!0,inside:{code:{pattern:/^([\t ]*(?:\*\s*)?)\S.*$/m,lookbehind:!0,inside:n,alias:"language-javascript"}}}}),t.languages.javadoclike.addSupport("javascript",t.languages.jsdoc)}e.exports=a,a.displayName="jsdoc",a.aliases=[]},45950(e){"use strict";function t(e){e.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:true|false)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},e.languages.webmanifest=e.languages.json}e.exports=t,t.displayName="json",t.aliases=["webmanifest"]},50235(e,t,n){"use strict";var r=n(45950);function i(e){var t,n;e.register(r),n=/("|')(?:\\(?:\r\n?|\n|.)|(?!\1)[^\\\r\n])*\1/,(t=e).languages.json5=t.languages.extend("json",{property:[{pattern:RegExp(n.source+"(?=\\s*:)"),greedy:!0},{pattern:/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/,alias:"unquoted"}],string:{pattern:n,greedy:!0},number:/[+-]?\b(?:NaN|Infinity|0x[a-fA-F\d]+)\b|[+-]?(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[eE][+-]?\d+\b)?/})}e.exports=i,i.displayName="json5",i.aliases=[]},80963(e,t,n){"use strict";var r=n(45950);function i(e){e.register(r),e.languages.jsonp=e.languages.extend("json",{punctuation:/[{}[\]();,.]/}),e.languages.insertBefore("jsonp","punctuation",{function:/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*\()/})}e.exports=i,i.displayName="jsonp",i.aliases=[]},79358(e){"use strict";function t(e){e.languages.jsstacktrace={"error-message":{pattern:/^\S.*/m,alias:"string"},"stack-frame":{pattern:/(^[ \t]+)at[ \t].*/m,lookbehind:!0,inside:{"not-my-code":{pattern:/^at[ \t]+(?!\s)(?:node\.js||.*(?:node_modules|\(\)|\(|$|\(internal\/|\(node\.js)).*/m,alias:"comment"},filename:{pattern:/(\bat\s+(?!\s)|\()(?:[a-zA-Z]:)?[^():]+(?=:)/,lookbehind:!0,alias:"url"},function:{pattern:/(at\s+(?:new\s+)?)(?!\s)[_$a-zA-Z\xA0-\uFFFF<][.$\w\xA0-\uFFFF<>]*/,lookbehind:!0,inside:{punctuation:/\./}},punctuation:/[()]/,keyword:/\b(?:at|new)\b/,alias:{pattern:/\[(?:as\s+)?(?!\s)[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\]/,alias:"variable"},"line-number":{pattern:/:[0-9]+(?::[0-9]+)?\b/,alias:"number",inside:{punctuation:/:/}}}}}}e.exports=t,t.displayName="jsstacktrace",t.aliases=[]},96412(e){"use strict";function t(e){!function(e){var t=e.util.clone(e.languages.javascript),n=/(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source,r=/(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source,i=/(?:\{*\.{3}(?:[^{}]|)*\})/.source;function a(e,t){return RegExp(e=e.replace(//g,function(){return n}).replace(//g,function(){return r}).replace(//g,function(){return i}),t)}i=a(i).source,e.languages.jsx=e.languages.extend("markup",t),e.languages.jsx.tag.pattern=a(/<\/?(?:[\w.:-]+(?:+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|))?|))**\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/i,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/i,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:a(//.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:a(/=/.source),inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:e.languages.jsx},alias:"language-javascript"}},e.languages.jsx.tag);var o=function(e){return e?"string"==typeof e?e:"string"==typeof e.content?e.content:e.content.map(o).join(""):""},s=function(t){for(var n=[],r=0;r0&&n[n.length-1].tagName===o(i.content[0].content[1])&&n.pop():"/>"===i.content[i.content.length-1].content||n.push({tagName:o(i.content[0].content[1]),openedBraces:0}):n.length>0&&"punctuation"===i.type&&"{"===i.content?n[n.length-1].openedBraces++:n.length>0&&n[n.length-1].openedBraces>0&&"punctuation"===i.type&&"}"===i.content?n[n.length-1].openedBraces--:a=!0),(a||"string"==typeof i)&&n.length>0&&0===n[n.length-1].openedBraces){var u=o(i);r0&&("string"==typeof t[r-1]||"plain-text"===t[r-1].type)&&(u=o(t[r-1])+u,t.splice(r-1,1),r--),t[r]=new e.Token("plain-text",u,null,u)}i.content&&"string"!=typeof i.content&&s(i.content)}};e.hooks.add("after-tokenize",function(e){("jsx"===e.language||"tsx"===e.language)&&s(e.tokens)})}(e)}e.exports=t,t.displayName="jsx",t.aliases=[]},39259(e){"use strict";function t(e){e.languages.julia={comment:{pattern:/(^|[^\\])(?:#=(?:[^#=]|=(?!#)|#(?!=)|#=(?:[^#=]|=(?!#)|#(?!=))*=#)*=#|#.*)/,lookbehind:!0},regex:{pattern:/r"(?:\\.|[^"\\\r\n])*"[imsx]{0,4}/,greedy:!0},string:{pattern:/"""[\s\S]+?"""|(?:\b\w+)?"(?:\\.|[^"\\\r\n])*"|(^|[^\w'])'(?:\\[^\r\n][^'\r\n]*|[^\\\r\n])'|`(?:[^\\`\r\n]|\\.)*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:abstract|baremodule|begin|bitstype|break|catch|ccall|const|continue|do|else|elseif|end|export|finally|for|function|global|if|immutable|import|importall|in|let|local|macro|module|print|println|quote|return|struct|try|type|typealias|using|while)\b/,boolean:/\b(?:true|false)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[box])?(?:[\da-f]+(?:_[\da-f]+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[efp][+-]?\d+(?:_\d+)*)?j?/i,operator:/&&|\|\||[-+*^%÷⊻&$\\]=?|\/[\/=]?|!=?=?|\|[=>]?|<(?:<=?|[=:|])?|>(?:=|>>?=?)?|==?=?|[~≠≤≥'√∛]/,punctuation:/::?|[{}[\]();,.?]/,constant:/\b(?:(?:NaN|Inf)(?:16|32|64)?|im|pi)\b|[πℯ]/}}e.exports=t,t.displayName="julia",t.aliases=[]},35760(e){"use strict";function t(e){e.languages.keyman={comment:/\bc\s.*/i,function:/\[\s*(?:(?:CTRL|SHIFT|ALT|LCTRL|RCTRL|LALT|RALT|CAPS|NCAPS)\s+)*(?:[TKU]_[\w?]+|".+?"|'.+?')\s*\]/i,string:/("|').*?\1/,bold:[/&(?:baselayout|bitmap|capsononly|capsalwaysoff|shiftfreescaps|copyright|ethnologuecode|hotkey|includecodes|keyboardversion|kmw_embedcss|kmw_embedjs|kmw_helpfile|kmw_helptext|kmw_rtl|language|layer|layoutfile|message|mnemoniclayout|name|oldcharposmatching|platform|targets|version|visualkeyboard|windowslanguages)\b/i,/\b(?:bitmap|bitmaps|caps on only|caps always off|shift frees caps|copyright|hotkey|language|layout|message|name|version)\b/i],keyword:/\b(?:any|baselayout|beep|call|context|deadkey|dk|if|index|layer|notany|nul|outs|platform|return|reset|save|set|store|use)\b/i,atrule:/\b(?:ansi|begin|unicode|group|using keys|match|nomatch)\b/i,number:/\b(?:U\+[\dA-F]+|d\d+|x[\da-f]+|\d+)\b/i,operator:/[+>\\,()]/,tag:/\$(?:keyman|kmfl|weaver|keymanweb|keymanonly):/i}}e.exports=t,t.displayName="keyman",t.aliases=[]},19715(e){"use strict";function t(e){var t,n;(t=e).languages.kotlin=t.languages.extend("clike",{keyword:{pattern:/(^|[^.])\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\b/,lookbehind:!0},function:[{pattern:/(?:`[^\r\n`]+`|\b\w+)(?=\s*\()/,greedy:!0},{pattern:/(\.)(?:`[^\r\n`]+`|\w+)(?=\s*\{)/,lookbehind:!0,greedy:!0}],number:/\b(?:0[xX][\da-fA-F]+(?:_[\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?[fFL]?)\b/,operator:/\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/}),delete t.languages.kotlin["class-name"],t.languages.insertBefore("kotlin","string",{"raw-string":{pattern:/("""|''')[\s\S]*?\1/,alias:"string"}}),t.languages.insertBefore("kotlin","keyword",{annotation:{pattern:/\B@(?:\w+:)?(?:[A-Z]\w*|\[[^\]]+\])/,alias:"builtin"}}),t.languages.insertBefore("kotlin","function",{label:{pattern:/\b\w+@|@\w+\b/,alias:"symbol"}}),n=[{pattern:/\$\{[^}]+\}/,inside:{delimiter:{pattern:/^\$\{|\}$/,alias:"variable"},rest:t.languages.kotlin}},{pattern:/\$\w+/,alias:"variable"}],t.languages.kotlin.string.inside=t.languages.kotlin["raw-string"].inside={interpolation:n},t.languages.kt=t.languages.kotlin,t.languages.kts=t.languages.kotlin}e.exports=t,t.displayName="kotlin",t.aliases=["kt","kts"]},27614(e){"use strict";function t(e){!function(e){var t=/\s\x00-\x1f\x22-\x2f\x3a-\x3f\x5b-\x5e\x60\x7b-\x7e/.source;function n(e,n){return RegExp(e.replace(//g,t),n)}e.languages.kumir={comment:{pattern:/\|.*/},prolog:{pattern:/#.*/,greedy:!0},string:{pattern:/"[^\n\r"]*"|'[^\n\r']*'/,greedy:!0},boolean:{pattern:n(/(^|[])(?:да|нет)(?=[]|$)/.source),lookbehind:!0},"operator-word":{pattern:n(/(^|[])(?:и|или|не)(?=[]|$)/.source),lookbehind:!0,alias:"keyword"},"system-variable":{pattern:n(/(^|[])знач(?=[]|$)/.source),lookbehind:!0,alias:"keyword"},type:[{pattern:n(/(^|[])(?:вещ|лит|лог|сим|цел)(?:\x20*таб)?(?=[]|$)/.source),lookbehind:!0,alias:"builtin"},{pattern:n(/(^|[])(?:компл|сканкод|файл|цвет)(?=[]|$)/.source),lookbehind:!0,alias:"important"}],keyword:{pattern:n(/(^|[])(?:алг|арг(?:\x20*рез)?|ввод|ВКЛЮЧИТЬ|вс[её]|выбор|вывод|выход|дано|для|до|дс|если|иначе|исп|использовать|кон(?:(?:\x20+|_)исп)?|кц(?:(?:\x20+|_)при)?|надо|нач|нс|нц|от|пауза|пока|при|раза?|рез|стоп|таб|то|утв|шаг)(?=[]|$)/.source),lookbehind:!0},name:{pattern:n(/(^|[])[^\d][^]*(?:\x20+[^]+)*(?=[]|$)/.source),lookbehind:!0},number:{pattern:n(/(^|[])(?:\B\$[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)(?=[]|$)/.source,"i"),lookbehind:!0},punctuation:/:=|[(),:;\[\]]/,"operator-char":{pattern:/\*\*?|<[=>]?|>=?|[-+/=]/,alias:"operator"}},e.languages.kum=e.languages.kumir}(e)}e.exports=t,t.displayName="kumir",t.aliases=["kum"]},42876(e){"use strict";function t(e){var t,n,r;t=e,r={"equation-command":{pattern:n=/\\(?:[^a-z()[\]]|[a-z*]+)/i,alias:"regex"}},t.languages.latex={comment:/%.*/m,cdata:{pattern:/(\\begin\{((?:verbatim|lstlisting)\*?)\})[\s\S]*?(?=\\end\{\2\})/,lookbehind:!0},equation:[{pattern:/\$\$(?:\\[\s\S]|[^\\$])+\$\$|\$(?:\\[\s\S]|[^\\$])+\$|\\\([\s\S]*?\\\)|\\\[[\s\S]*?\\\]/,inside:r,alias:"string"},{pattern:/(\\begin\{((?:equation|math|eqnarray|align|multline|gather)\*?)\})[\s\S]*?(?=\\end\{\2\})/,lookbehind:!0,inside:r,alias:"string"}],keyword:{pattern:/(\\(?:begin|end|ref|cite|label|usepackage|documentclass)(?:\[[^\]]+\])?\{)[^}]+(?=\})/,lookbehind:!0},url:{pattern:/(\\url\{)[^}]+(?=\})/,lookbehind:!0},headline:{pattern:/(\\(?:part|chapter|section|subsection|frametitle|subsubsection|paragraph|subparagraph|subsubparagraph|subsubsubparagraph)\*?(?:\[[^\]]+\])?\{)[^}]+(?=\})/,lookbehind:!0,alias:"class-name"},function:{pattern:n,alias:"selector"},punctuation:/[[\]{}&]/},t.languages.tex=t.languages.latex,t.languages.context=t.languages.latex}e.exports=t,t.displayName="latex",t.aliases=["tex","context"]},2980(e,t,n){"use strict";var r=n(93205),i=n(88262);function a(e){var t,n;e.register(r),e.register(i),(t=e).languages.latte={comment:/^\{\*[\s\S]*/,ld:{pattern:/^\{(?:[=_]|\/?(?!\d|\w+\()\w+)?/,inside:{punctuation:/^\{\/?/,tag:{pattern:/.+/,alias:"important"}}},rd:{pattern:/\}$/,inside:{punctuation:/.+/}},php:{pattern:/\S(?:[\s\S]*\S)?/,alias:"language-php",inside:t.languages.php}},n=t.languages.extend("markup",{}),t.languages.insertBefore("inside","attr-value",{"n-attr":{pattern:/n:[\w-]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+))?/,inside:{"attr-name":{pattern:/^[^\s=]+/,alias:"important"},"attr-value":{pattern:/=[\s\S]+/,inside:{punctuation:[/^=/,{pattern:/^(\s*)["']|["']$/,lookbehind:!0}],php:{pattern:/\S(?:[\s\S]*\S)?/,inside:t.languages.php}}}}}},n.tag),t.hooks.add("before-tokenize",function(e){if("latte"===e.language){var r=/\{\*[\s\S]*?\*\}|\{[^'"\s{}*](?:[^"'/{}]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|\/\*(?:[^*]|\*(?!\/))*\*\/)*?\}/g;t.languages["markup-templating"].buildPlaceholders(e,"latte",r),e.grammar=n}}),t.hooks.add("after-tokenize",function(e){t.languages["markup-templating"].tokenizePlaceholders(e,"latte")})}e.exports=a,a.displayName="latte",a.aliases=[]},41701(e){"use strict";function t(e){e.languages.less=e.languages.extend("css",{comment:[/\/\*[\s\S]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-](?:\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};@\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/i,operator:/[+\-*\/]/}),e.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-].*?(?=[(;])/,lookbehind:!0,alias:"function"}})}e.exports=t,t.displayName="less",t.aliases=[]},42491(e,t,n){"use strict";var r=n(9997);function i(e){e.register(r),function(e){for(var t=/\((?:[^();"#\\]|\\[\s\S]|;.*(?!.)|"(?:[^"\\]|\\.)*"|#(?:\{(?:(?!#\})[\s\S])*#\}|[^{])|)*\)/.source,n=5,r=0;r/g,function(){return t});t=t.replace(//g,/[^\s\S]/.source);var i=e.languages.lilypond={comment:/%(?:(?!\{).*|\{[\s\S]*?%\})/,"embedded-scheme":{pattern:RegExp(/(^|[=\s])#(?:"(?:[^"\\]|\\.)*"|[^\s()"]*(?:[^\s()]|))/.source.replace(//g,function(){return t}),"m"),lookbehind:!0,greedy:!0,inside:{scheme:{pattern:/^(#)[\s\S]+$/,lookbehind:!0,alias:"language-scheme",inside:{"embedded-lilypond":{pattern:/#\{[\s\S]*?#\}/,greedy:!0,inside:{punctuation:/^#\{|#\}$/,lilypond:{pattern:/[\s\S]+/,alias:"language-lilypond",inside:null}}},rest:e.languages.scheme}},punctuation:/#/}},string:{pattern:/"(?:[^"\\]|\\.)*"/,greedy:!0},"class-name":{pattern:/(\\new\s+)[\w-]+/,lookbehind:!0},keyword:{pattern:/\\[a-z][-\w]*/i,inside:{punctuation:/^\\/}},operator:/[=|]|<<|>>/,punctuation:{pattern:/(^|[a-z\d])(?:'+|,+|[_^]?-[_^]?(?:[-+^!>._]|(?=\d))|[_^]\.?|[.!])|[{}()[\]<>^~]|\\[()[\]<>\\!]|--|__/,lookbehind:!0},number:/\b\d+(?:\/\d+)?\b/};i["embedded-scheme"].inside.scheme.inside["embedded-lilypond"].inside.lilypond.inside=i,e.languages.ly=i}(e)}e.exports=i,i.displayName="lilypond",i.aliases=[]},34927(e,t,n){"use strict";var r=n(93205);function i(e){e.register(r),e.languages.liquid={comment:{pattern:/(^\{%\s*comment\s*%\})[\s\S]+(?=\{%\s*endcomment\s*%\}$)/,lookbehind:!0},delimiter:{pattern:/^\{(?:\{\{|[%\{])-?|-?(?:\}\}|[%\}])\}$/,alias:"punctuation"},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},keyword:/\b(?:as|assign|break|continue|cycle|decrement|echo|else|elsif|(?:end)?(?:capture|case|comment|for|form|if|paginate|style|raw|tablerow|unless)|in|include|increment|limit|liquid|offset|range|render|reversed|section|when|with)\b/,function:[{pattern:/(\|\s*)\w+/,lookbehind:!0,alias:"filter"},{pattern:/(\.\s*)(?:first|last|size)/,lookbehind:!0}],boolean:/\b(?:true|false|nil)\b/,range:{pattern:/\.\./,alias:"operator"},number:/\b\d+(?:\.\d+)?\b/,operator:/[!=]=|<>|[<>]=?|[|?:=-]|\b(?:and|or|contains(?=\s))\b/,punctuation:/[.,\[\]()]/},e.hooks.add("before-tokenize",function(t){var n=/\{%\s*comment\s*%\}[\s\S]*?\{%\s*endcomment\s*%\}|\{(?:%[\s\S]*?%|\{\{[\s\S]*?\}\}|\{[\s\S]*?\})\}/g,r=!1;e.languages["markup-templating"].buildPlaceholders(t,"liquid",n,function(e){var t=/^\{%-?\s*(\w+)/.exec(e);if(t){var n=t[1];if("raw"===n&&!r)return r=!0,!0;if("endraw"===n)return r=!1,!0}return!r})}),e.hooks.add("after-tokenize",function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"liquid")})}e.exports=i,i.displayName="liquid",i.aliases=[]},3848(e){"use strict";function t(e){!function(e){function t(e){return RegExp("(\\()"+e+"(?=[\\s\\)])")}function n(e){return RegExp("([\\s([])"+e+"(?=[\\s)])")}var r="[-+*/_~!@$%^=<>{}\\w]+",i="&"+r,a="(\\()",o="(?=\\))",s="(?=\\s)",u={heading:{pattern:/;;;.*/,alias:["comment","title"]},comment:/;.*/,string:{pattern:/"(?:[^"\\]|\\.)*"/,greedy:!0,inside:{argument:/[-A-Z]+(?=[.,\s])/,symbol:RegExp("`"+r+"'")}},"quoted-symbol":{pattern:RegExp("#?'"+r),alias:["variable","symbol"]},"lisp-property":{pattern:RegExp(":"+r),alias:"property"},splice:{pattern:RegExp(",@?"+r),alias:["symbol","variable"]},keyword:[{pattern:RegExp(a+"(?:(?:lexical-)?let\\*?|(?:cl-)?letf|if|when|while|unless|cons|cl-loop|and|or|not|cond|setq|error|message|null|require|provide|use-package)"+s),lookbehind:!0},{pattern:RegExp(a+"(?:for|do|collect|return|finally|append|concat|in|by)"+s),lookbehind:!0}],declare:{pattern:t("declare"),lookbehind:!0,alias:"keyword"},interactive:{pattern:t("interactive"),lookbehind:!0,alias:"keyword"},boolean:{pattern:n("(?:t|nil)"),lookbehind:!0},number:{pattern:n("[-+]?\\d+(?:\\.\\d*)?"),lookbehind:!0},defvar:{pattern:RegExp(a+"def(?:var|const|custom|group)\\s+"+r),lookbehind:!0,inside:{keyword:/^def[a-z]+/,variable:RegExp(r)}},defun:{pattern:RegExp(a+"(?:cl-)?(?:defun\\*?|defmacro)\\s+"+r+"\\s+\\([\\s\\S]*?\\)"),lookbehind:!0,inside:{keyword:/^(?:cl-)?def\S+/,arguments:null,function:{pattern:RegExp("(^\\s)"+r),lookbehind:!0},punctuation:/[()]/}},lambda:{pattern:RegExp(a+"lambda\\s+\\(\\s*(?:&?"+r+"(?:\\s+&?"+r+")*\\s*)?\\)"),lookbehind:!0,inside:{keyword:/^lambda/,arguments:null,punctuation:/[()]/}},car:{pattern:RegExp(a+r),lookbehind:!0},punctuation:[/(?:['`,]?\(|[)\[\]])/,{pattern:/(\s)\.(?=\s)/,lookbehind:!0}]},c={"lisp-marker":RegExp(i),rest:{argument:{pattern:RegExp(r),alias:"variable"},varform:{pattern:RegExp(a+r+"\\s+\\S[\\s\\S]*"+o),lookbehind:!0,inside:{string:u.string,boolean:u.boolean,number:u.number,symbol:u.symbol,punctuation:/[()]/}}}},l="\\S+(?:\\s+\\S+)*",f={pattern:RegExp(a+"[\\s\\S]*"+o),lookbehind:!0,inside:{"rest-vars":{pattern:RegExp("&(?:rest|body)\\s+"+l),inside:c},"other-marker-vars":{pattern:RegExp("&(?:optional|aux)\\s+"+l),inside:c},keys:{pattern:RegExp("&key\\s+"+l+"(?:\\s+&allow-other-keys)?"),inside:c},argument:{pattern:RegExp(r),alias:"variable"},punctuation:/[()]/}};u.lambda.inside.arguments=f,u.defun.inside.arguments=e.util.clone(f),u.defun.inside.arguments.inside.sublist=f,e.languages.lisp=u,e.languages.elisp=u,e.languages.emacs=u,e.languages["emacs-lisp"]=u}(e)}e.exports=t,t.displayName="lisp",t.aliases=[]},41469(e){"use strict";function t(e){e.languages.livescript={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\])#.*/,lookbehind:!0}],"interpolated-string":{pattern:/(^|[^"])("""|")(?:\\[\s\S]|(?!\2)[^\\])*\2(?!")/,lookbehind:!0,greedy:!0,inside:{variable:{pattern:/(^|[^\\])#[a-z_](?:-?[a-z]|[\d_])*/m,lookbehind:!0},interpolation:{pattern:/(^|[^\\])#\{[^}]+\}/m,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^#\{|\}$/,alias:"variable"}}},string:/[\s\S]+/}},string:[{pattern:/('''|')(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0},{pattern:/<\[[\s\S]*?\]>/,greedy:!0},/\\[^\s,;\])}]+/],regex:[{pattern:/\/\/(?:\[[^\r\n\]]*\]|\\.|(?!\/\/)[^\\\[])+\/\/[gimyu]{0,5}/,greedy:!0,inside:{comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0}}},{pattern:/\/(?:\[[^\r\n\]]*\]|\\.|[^/\\\r\n\[])+\/[gimyu]{0,5}/,greedy:!0}],keyword:{pattern:/(^|(?!-).)\b(?:break|case|catch|class|const|continue|default|do|else|extends|fallthrough|finally|for(?: ever)?|function|if|implements|it|let|loop|new|null|otherwise|own|return|super|switch|that|then|this|throw|try|unless|until|var|void|when|while|yield)(?!-)\b/m,lookbehind:!0},"keyword-operator":{pattern:/(^|[^-])\b(?:(?:delete|require|typeof)!|(?:and|by|delete|export|from|import(?: all)?|in|instanceof|is(?:nt| not)?|not|of|or|til|to|typeof|with|xor)(?!-)\b)/m,lookbehind:!0,alias:"operator"},boolean:{pattern:/(^|[^-])\b(?:false|no|off|on|true|yes)(?!-)\b/m,lookbehind:!0},argument:{pattern:/(^|(?!\.&\.)[^&])&(?!&)\d*/m,lookbehind:!0,alias:"variable"},number:/\b(?:\d+~[\da-z]+|\d[\d_]*(?:\.\d[\d_]*)?(?:[a-z]\w*)?)/i,identifier:/[a-z_](?:-?[a-z]|[\d_])*/i,operator:[{pattern:/( )\.(?= )/,lookbehind:!0},/\.(?:[=~]|\.\.?)|\.(?:[&|^]|<<|>>>?)\.|:(?:=|:=?)|&&|\|[|>]|<(?:<[>=?]?|-(?:->?|>)?|\+\+?|@@?|%%?|\*\*?|!(?:~?=|--?>|~?~>)?|~(?:~?>|=)?|==?|\^\^?|[\/?]/],punctuation:/[(){}\[\]|.,:;`]/},e.languages.livescript["interpolated-string"].inside.interpolation.inside.rest=e.languages.livescript}e.exports=t,t.displayName="livescript",t.aliases=[]},73070(e){"use strict";function t(e){var t;(t=e).languages.llvm={comment:/;.*/,string:{pattern:/"[^"]*"/,greedy:!0},boolean:/\b(?:true|false)\b/,variable:/[%@!#](?:(?!\d)(?:[-$.\w]|\\[a-f\d]{2})+|\d+)/i,label:/(?!\d)(?:[-$.\w]|\\[a-f\d]{2})+:/i,type:{pattern:/\b(?:double|float|fp128|half|i[1-9]\d*|label|metadata|ppc_fp128|token|void|x86_fp80|x86_mmx)\b/,alias:"class-name"},keyword:/\b[a-z_][a-z_0-9]*\b/,number:/[+-]?\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b|\b0x[\dA-Fa-f]+\b|\b0xK[\dA-Fa-f]{20}\b|\b0x[ML][\dA-Fa-f]{32}\b|\b0xH[\dA-Fa-f]{4}\b/,punctuation:/[{}[\];(),.!*=<>]/}}e.exports=t,t.displayName="llvm",t.aliases=[]},35049(e){"use strict";function t(e){e.languages.log={string:{pattern:/"(?:[^"\\\r\n]|\\.)*"|'(?![st] | \w)(?:[^'\\\r\n]|\\.)*'/,greedy:!0},level:[{pattern:/\b(?:ALERT|CRIT|CRITICAL|EMERG|EMERGENCY|ERR|ERROR|FAILURE|FATAL|SEVERE)\b/,alias:["error","important"]},{pattern:/\b(?:WARN|WARNING|WRN)\b/,alias:["warning","important"]},{pattern:/\b(?:DISPLAY|INF|INFO|NOTICE|STATUS)\b/,alias:["info","keyword"]},{pattern:/\b(?:DBG|DEBUG|FINE)\b/,alias:["debug","keyword"]},{pattern:/\b(?:FINER|FINEST|TRACE|TRC|VERBOSE|VRB)\b/,alias:["trace","comment"]}],property:{pattern:/((?:^|[\]|])[ \t]*)[a-z_](?:[\w-]|\b\/\b)*(?:[. ]\(?\w(?:[\w-]|\b\/\b)*\)?)*:(?=\s)/im,lookbehind:!0},separator:{pattern:/(^|[^-+])-{3,}|={3,}|\*{3,}|- - /m,lookbehind:!0,alias:"comment"},url:/\b(?:https?|ftp|file):\/\/[^\s|,;'"]*[^\s|,;'">.]/,email:{pattern:/(^|\s)[-\w+.]+@[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+(?=\s)/,lookbehind:!0,alias:"url"},"ip-address":{pattern:/\b(?:\d{1,3}(?:\.\d{1,3}){3})\b/i,alias:"constant"},"mac-address":{pattern:/\b[a-f0-9]{2}(?::[a-f0-9]{2}){5}\b/i,alias:"constant"},domain:{pattern:/(^|\s)[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*\.[a-z][a-z0-9-]+(?=\s)/,lookbehind:!0,alias:"constant"},uuid:{pattern:/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i,alias:"constant"},hash:{pattern:/\b(?:[a-f0-9]{32}){1,2}\b/i,alias:"constant"},"file-path":{pattern:/\b[a-z]:[\\/][^\s|,;:(){}\[\]"']+|(^|[\s:\[\](>|])\.{0,2}\/\w[^\s|,;:(){}\[\]"']*/i,lookbehind:!0,greedy:!0,alias:"string"},date:{pattern:RegExp(/\b\d{4}[-/]\d{2}[-/]\d{2}(?:T(?=\d{1,2}:)|(?=\s\d{1,2}:))/.source+"|"+/\b\d{1,4}[-/ ](?:\d{1,2}|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[-/ ]\d{2,4}T?\b/.source+"|"+/\b(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)(?:\s{1,2}(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))?|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s{1,2}\d{1,2}\b/.source,"i"),alias:"number"},time:{pattern:/\b\d{1,2}:\d{1,2}:\d{1,2}(?:[.,:]\d+)?(?:\s?[+-]\d{2}:?\d{2}|Z)?\b/,alias:"number"},boolean:/\b(?:true|false|null)\b/i,number:{pattern:/(^|[^.\w])(?:0x[a-f0-9]+|0o[0-7]+|0b[01]+|v?\d[\da-f]*(?:\.\d+)*(?:e[+-]?\d+)?[a-z]{0,3}\b)\b(?!\.\w)/i,lookbehind:!0},operator:/[;:?<=>~/@!$%&+\-|^(){}*#]/,punctuation:/[\[\].,]/}}e.exports=t,t.displayName="log",t.aliases=[]},8789(e){"use strict";function t(e){e.languages.lolcode={comment:[/\bOBTW\s[\s\S]*?\sTLDR\b/,/\bBTW.+/],string:{pattern:/"(?::.|[^":])*"/,inside:{variable:/:\{[^}]+\}/,symbol:[/:\([a-f\d]+\)/i,/:\[[^\]]+\]/,/:[)>o":]/]},greedy:!0},number:/(?:\B-)?(?:\b\d+(?:\.\d*)?|\B\.\d+)/,symbol:{pattern:/(^|\s)(?:A )?(?:YARN|NUMBR|NUMBAR|TROOF|BUKKIT|NOOB)(?=\s|,|$)/,lookbehind:!0,inside:{keyword:/A(?=\s)/}},label:{pattern:/((?:^|\s)(?:IM IN YR|IM OUTTA YR) )[a-zA-Z]\w*/,lookbehind:!0,alias:"string"},function:{pattern:/((?:^|\s)(?:I IZ|HOW IZ I|IZ) )[a-zA-Z]\w*/,lookbehind:!0},keyword:[{pattern:/(^|\s)(?:O HAI IM|KTHX|HAI|KTHXBYE|I HAS A|ITZ(?: A)?|R|AN|MKAY|SMOOSH|MAEK|IS NOW(?: A)?|VISIBLE|GIMMEH|O RLY\?|YA RLY|NO WAI|OIC|MEBBE|WTF\?|OMG|OMGWTF|GTFO|IM IN YR|IM OUTTA YR|FOUND YR|YR|TIL|WILE|UPPIN|NERFIN|I IZ|HOW IZ I|IF U SAY SO|SRS|HAS A|LIEK(?: A)?|IZ)(?=\s|,|$)/,lookbehind:!0},/'Z(?=\s|,|$)/],boolean:{pattern:/(^|\s)(?:WIN|FAIL)(?=\s|,|$)/,lookbehind:!0},variable:{pattern:/(^|\s)IT(?=\s|,|$)/,lookbehind:!0},operator:{pattern:/(^|\s)(?:NOT|BOTH SAEM|DIFFRINT|(?:SUM|DIFF|PRODUKT|QUOSHUNT|MOD|BIGGR|SMALLR|BOTH|EITHER|WON|ALL|ANY) OF)(?=\s|,|$)/,lookbehind:!0},punctuation:/\.{3}|…|,|!/}}e.exports=t,t.displayName="lolcode",t.aliases=[]},59803(e){"use strict";function t(e){e.languages.lua={comment:/^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n]|\\z(?:\r\n|\s)|\\(?:\r\n|[^z]))*\1|\[(=*)\[[\s\S]*?\]\2\]/,greedy:!0},number:/\b0x[a-f\d]+(?:\.[a-f\d]*)?(?:p[+-]?\d+)?\b|\b\d+(?:\.\B|(?:\.\d*)?(?:e[+-]?\d+)?\b)|\B\.\d+(?:e[+-]?\d+)?\b/i,keyword:/\b(?:and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,function:/(?!\d)\w+(?=\s*(?:[({]))/,operator:[/[-+*%^&|#]|\/\/?|<[<=]?|>[>=]?|[=~]=?/,{pattern:/(^|[^.])\.\.(?!\.)/,lookbehind:!0}],punctuation:/[\[\](){},;]|\.+|:+/}}e.exports=t,t.displayName="lua",t.aliases=[]},33055(e){"use strict";function t(e){e.languages.makefile={comment:{pattern:/(^|[^\\])#(?:\\(?:\r\n|[\s\S])|[^\\\r\n])*/,lookbehind:!0},string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},builtin:/\.[A-Z][^:#=\s]+(?=\s*:(?!=))/,symbol:{pattern:/^(?:[^:=\s]|[ \t]+(?![\s:]))+(?=\s*:(?!=))/m,inside:{variable:/\$+(?:(?!\$)[^(){}:#=\s]+|(?=[({]))/}},variable:/\$+(?:(?!\$)[^(){}:#=\s]+|\([@*%<^+?][DF]\)|(?=[({]))/,keyword:[/-include\b|\b(?:define|else|endef|endif|export|ifn?def|ifn?eq|include|override|private|sinclude|undefine|unexport|vpath)\b/,{pattern:/(\()(?:addsuffix|abspath|and|basename|call|dir|error|eval|file|filter(?:-out)?|findstring|firstword|flavor|foreach|guile|if|info|join|lastword|load|notdir|or|origin|patsubst|realpath|shell|sort|strip|subst|suffix|value|warning|wildcard|word(?:s|list)?)(?=[ \t])/,lookbehind:!0}],operator:/(?:::|[?:+!])?=|[|@]/,punctuation:/[:;(){}]/}}e.exports=t,t.displayName="makefile",t.aliases=[]},90542(e){"use strict";function t(e){!function(e){var t=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function n(e){return e=e.replace(//g,function(){return t}),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+e+")")}var r=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,i=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,function(){return r}),a=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source;e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"font-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+i+a+"(?:"+i+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+i+a+")(?:"+i+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(r),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+i+")"+a+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+i+"$"),inside:{"table-header":{pattern:RegExp(r),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n(/\b__(?:(?!_)|_(?:(?!_))+_)+__\b|\*\*(?:(?!\*)|\*(?:(?!\*))+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n(/\b_(?:(?!_)|__(?:(?!_))+__)+_\b|\*(?:(?!\*)|\*\*(?:(?!\*))+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n(/(~~?)(?:(?!~))+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:n(/!?\[(?:(?!\]))+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\]))+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach(function(t){["url","bold","italic","strike","code-snippet"].forEach(function(n){t!==n&&(e.languages.markdown[t].inside.content.inside[n]=e.languages.markdown[n])})}),e.hooks.add("after-tokenize",function(e){("markdown"===e.language||"md"===e.language)&&t(e.tokens);function t(e){if(e&&"string"!=typeof e)for(var n=0,r=e.length;n=a.length);u++){var c=s[u];if("string"==typeof c||c.content&&"string"==typeof c.content){var l=a[i],f=n.tokenStack[l],d="string"==typeof c?c:c.content,h=t(r,l),p=d.indexOf(h);if(p>-1){++i;var b=d.substring(0,p),m=new e.Token(r,e.tokenize(f,n.grammar),"language-"+r,f),g=d.substring(p+h.length),v=[];b&&v.push.apply(v,o([b])),v.push(m),g&&v.push.apply(v,o([g])),"string"==typeof c?s.splice.apply(s,[u,1].concat(v)):c.content=v}}else c.content&&o(c.content)}return s}}}})}(e)}e.exports=t,t.displayName="markupTemplating",t.aliases=[]},2717(e){"use strict";function t(e){e.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},e.languages.markup.tag.inside["attr-value"].inside.entity=e.languages.markup.entity,e.languages.markup.doctype.inside["internal-subset"].inside=e.languages.markup,e.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.value.replace(/&/,"&"))}),Object.defineProperty(e.languages.markup.tag,"addInlined",{value:function(t,n){var r={};r["language-"+n]={pattern:/(^$)/i,lookbehind:!0,inside:e.languages[n]},r.cdata=/^$/i;var i={"included-cdata":{pattern://i,inside:r}};i["language-"+n]={pattern:/[\s\S]+/,inside:e.languages[n]};var a={};a[t]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,function(){return t}),"i"),lookbehind:!0,greedy:!0,inside:i},e.languages.insertBefore("markup","cdata",a)}}),Object.defineProperty(e.languages.markup.tag,"addAttribute",{value:function(t,n){e.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+t+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[n,"language-"+n],inside:e.languages[n]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),e.languages.html=e.languages.markup,e.languages.mathml=e.languages.markup,e.languages.svg=e.languages.markup,e.languages.xml=e.languages.extend("markup",{}),e.languages.ssml=e.languages.xml,e.languages.atom=e.languages.xml,e.languages.rss=e.languages.xml}e.exports=t,t.displayName="markup",t.aliases=["html","mathml","svg","xml","ssml","atom","rss"]},27992(e){"use strict";function t(e){e.languages.matlab={comment:[/%\{[\s\S]*?\}%/,/%.+/],string:{pattern:/\B'(?:''|[^'\r\n])*'/,greedy:!0},number:/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[eE][+-]?\d+)?(?:[ij])?|\b[ij]\b/,keyword:/\b(?:break|case|catch|continue|else|elseif|end|for|function|if|inf|NaN|otherwise|parfor|pause|pi|return|switch|try|while)\b/,function:/\b(?!\d)\w+(?=\s*\()/,operator:/\.?[*^\/\\']|[+\-:@]|[<>=~]=?|&&?|\|\|?/,punctuation:/\.{3}|[.,;\[\](){}!]/}}e.exports=t,t.displayName="matlab",t.aliases=[]},606(e){"use strict";function t(e){e.languages.mel={comment:/\/\/.*/,code:{pattern:/`(?:\\.|[^\\`\r\n])*`/,greedy:!0,alias:"italic",inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"}}},string:{pattern:/"(?:\\.|[^\\"\r\n])*"/,greedy:!0},variable:/\$\w+/,number:/\b0x[\da-fA-F]+\b|\b\d+(?:\.\d*)?|\B\.\d+/,flag:{pattern:/-[^\d\W]\w*/,alias:"operator"},keyword:/\b(?:break|case|continue|default|do|else|float|for|global|if|in|int|matrix|proc|return|string|switch|vector|while)\b/,function:/\b\w+(?=\()|\b(?:about|abs|addAttr|addAttributeEditorNodeHelp|addDynamic|addNewShelfTab|addPP|addPanelCategory|addPrefixToName|advanceToNextDrivenKey|affectedNet|affects|aimConstraint|air|alias|aliasAttr|align|alignCtx|alignCurve|alignSurface|allViewFit|ambientLight|angle|angleBetween|animCone|animCurveEditor|animDisplay|animView|annotate|appendStringArray|applicationName|applyAttrPreset|applyTake|arcLenDimContext|arcLengthDimension|arclen|arrayMapper|art3dPaintCtx|artAttrCtx|artAttrPaintVertexCtx|artAttrSkinPaintCtx|artAttrTool|artBuildPaintMenu|artFluidAttrCtx|artPuttyCtx|artSelectCtx|artSetPaintCtx|artUserPaintCtx|assignCommand|assignInputDevice|assignViewportFactories|attachCurve|attachDeviceAttr|attachSurface|attrColorSliderGrp|attrCompatibility|attrControlGrp|attrEnumOptionMenu|attrEnumOptionMenuGrp|attrFieldGrp|attrFieldSliderGrp|attrNavigationControlGrp|attrPresetEditWin|attributeExists|attributeInfo|attributeMenu|attributeQuery|autoKeyframe|autoPlace|bakeClip|bakeFluidShading|bakePartialHistory|bakeResults|bakeSimulation|basename|basenameEx|batchRender|bessel|bevel|bevelPlus|binMembership|bindSkin|blend2|blendShape|blendShapeEditor|blendShapePanel|blendTwoAttr|blindDataType|boneLattice|boundary|boxDollyCtx|boxZoomCtx|bufferCurve|buildBookmarkMenu|buildKeyframeMenu|button|buttonManip|CBG|cacheFile|cacheFileCombine|cacheFileMerge|cacheFileTrack|camera|cameraView|canCreateManip|canvas|capitalizeString|catch|catchQuiet|ceil|changeSubdivComponentDisplayLevel|changeSubdivRegion|channelBox|character|characterMap|characterOutlineEditor|characterize|chdir|checkBox|checkBoxGrp|checkDefaultRenderGlobals|choice|circle|circularFillet|clamp|clear|clearCache|clip|clipEditor|clipEditorCurrentTimeCtx|clipSchedule|clipSchedulerOutliner|clipTrimBefore|closeCurve|closeSurface|cluster|cmdFileOutput|cmdScrollFieldExecuter|cmdScrollFieldReporter|cmdShell|coarsenSubdivSelectionList|collision|color|colorAtPoint|colorEditor|colorIndex|colorIndexSliderGrp|colorSliderButtonGrp|colorSliderGrp|columnLayout|commandEcho|commandLine|commandPort|compactHairSystem|componentEditor|compositingInterop|computePolysetVolume|condition|cone|confirmDialog|connectAttr|connectControl|connectDynamic|connectJoint|connectionInfo|constrain|constrainValue|constructionHistory|container|containsMultibyte|contextInfo|control|convertFromOldLayers|convertIffToPsd|convertLightmap|convertSolidTx|convertTessellation|convertUnit|copyArray|copyFlexor|copyKey|copySkinWeights|cos|cpButton|cpCache|cpClothSet|cpCollision|cpConstraint|cpConvClothToMesh|cpForces|cpGetSolverAttr|cpPanel|cpProperty|cpRigidCollisionFilter|cpSeam|cpSetEdit|cpSetSolverAttr|cpSolver|cpSolverTypes|cpTool|cpUpdateClothUVs|createDisplayLayer|createDrawCtx|createEditor|createLayeredPsdFile|createMotionField|createNewShelf|createNode|createRenderLayer|createSubdivRegion|cross|crossProduct|ctxAbort|ctxCompletion|ctxEditMode|ctxTraverse|currentCtx|currentTime|currentTimeCtx|currentUnit|curve|curveAddPtCtx|curveCVCtx|curveEPCtx|curveEditorCtx|curveIntersect|curveMoveEPCtx|curveOnSurface|curveSketchCtx|cutKey|cycleCheck|cylinder|dagPose|date|defaultLightListCheckBox|defaultNavigation|defineDataServer|defineVirtualDevice|deformer|deg_to_rad|delete|deleteAttr|deleteShadingGroupsAndMaterials|deleteShelfTab|deleteUI|deleteUnusedBrushes|delrandstr|detachCurve|detachDeviceAttr|detachSurface|deviceEditor|devicePanel|dgInfo|dgdirty|dgeval|dgtimer|dimWhen|directKeyCtx|directionalLight|dirmap|dirname|disable|disconnectAttr|disconnectJoint|diskCache|displacementToPoly|displayAffected|displayColor|displayCull|displayLevelOfDetail|displayPref|displayRGBColor|displaySmoothness|displayStats|displayString|displaySurface|distanceDimContext|distanceDimension|doBlur|dolly|dollyCtx|dopeSheetEditor|dot|dotProduct|doubleProfileBirailSurface|drag|dragAttrContext|draggerContext|dropoffLocator|duplicate|duplicateCurve|duplicateSurface|dynCache|dynControl|dynExport|dynExpression|dynGlobals|dynPaintEditor|dynParticleCtx|dynPref|dynRelEdPanel|dynRelEditor|dynamicLoad|editAttrLimits|editDisplayLayerGlobals|editDisplayLayerMembers|editRenderLayerAdjustment|editRenderLayerGlobals|editRenderLayerMembers|editor|editorTemplate|effector|emit|emitter|enableDevice|encodeString|endString|endsWith|env|equivalent|equivalentTol|erf|error|eval|evalDeferred|evalEcho|event|exactWorldBoundingBox|exclusiveLightCheckBox|exec|executeForEachObject|exists|exp|expression|expressionEditorListen|extendCurve|extendSurface|extrude|fcheck|fclose|feof|fflush|fgetline|fgetword|file|fileBrowserDialog|fileDialog|fileExtension|fileInfo|filetest|filletCurve|filter|filterCurve|filterExpand|filterStudioImport|findAllIntersections|findAnimCurves|findKeyframe|findMenuItem|findRelatedSkinCluster|finder|firstParentOf|fitBspline|flexor|floatEq|floatField|floatFieldGrp|floatScrollBar|floatSlider|floatSlider2|floatSliderButtonGrp|floatSliderGrp|floor|flow|fluidCacheInfo|fluidEmitter|fluidVoxelInfo|flushUndo|fmod|fontDialog|fopen|formLayout|format|fprint|frameLayout|fread|freeFormFillet|frewind|fromNativePath|fwrite|gamma|gauss|geometryConstraint|getApplicationVersionAsFloat|getAttr|getClassification|getDefaultBrush|getFileList|getFluidAttr|getInputDeviceRange|getMayaPanelTypes|getModifiers|getPanel|getParticleAttr|getPluginResource|getenv|getpid|glRender|glRenderEditor|globalStitch|gmatch|goal|gotoBindPose|grabColor|gradientControl|gradientControlNoAttr|graphDollyCtx|graphSelectContext|graphTrackCtx|gravity|grid|gridLayout|group|groupObjectsByName|HfAddAttractorToAS|HfAssignAS|HfBuildEqualMap|HfBuildFurFiles|HfBuildFurImages|HfCancelAFR|HfConnectASToHF|HfCreateAttractor|HfDeleteAS|HfEditAS|HfPerformCreateAS|HfRemoveAttractorFromAS|HfSelectAttached|HfSelectAttractors|HfUnAssignAS|hardenPointCurve|hardware|hardwareRenderPanel|headsUpDisplay|headsUpMessage|help|helpLine|hermite|hide|hilite|hitTest|hotBox|hotkey|hotkeyCheck|hsv_to_rgb|hudButton|hudSlider|hudSliderButton|hwReflectionMap|hwRender|hwRenderLoad|hyperGraph|hyperPanel|hyperShade|hypot|iconTextButton|iconTextCheckBox|iconTextRadioButton|iconTextRadioCollection|iconTextScrollList|iconTextStaticLabel|ikHandle|ikHandleCtx|ikHandleDisplayScale|ikSolver|ikSplineHandleCtx|ikSystem|ikSystemInfo|ikfkDisplayMethod|illustratorCurves|image|imfPlugins|inheritTransform|insertJoint|insertJointCtx|insertKeyCtx|insertKnotCurve|insertKnotSurface|instance|instanceable|instancer|intField|intFieldGrp|intScrollBar|intSlider|intSliderGrp|interToUI|internalVar|intersect|iprEngine|isAnimCurve|isConnected|isDirty|isParentOf|isSameObject|isTrue|isValidObjectName|isValidString|isValidUiName|isolateSelect|itemFilter|itemFilterAttr|itemFilterRender|itemFilterType|joint|jointCluster|jointCtx|jointDisplayScale|jointLattice|keyTangent|keyframe|keyframeOutliner|keyframeRegionCurrentTimeCtx|keyframeRegionDirectKeyCtx|keyframeRegionDollyCtx|keyframeRegionInsertKeyCtx|keyframeRegionMoveKeyCtx|keyframeRegionScaleKeyCtx|keyframeRegionSelectKeyCtx|keyframeRegionSetKeyCtx|keyframeRegionTrackCtx|keyframeStats|lassoContext|lattice|latticeDeformKeyCtx|launch|launchImageEditor|layerButton|layeredShaderPort|layeredTexturePort|layout|layoutDialog|lightList|lightListEditor|lightListPanel|lightlink|lineIntersection|linearPrecision|linstep|listAnimatable|listAttr|listCameras|listConnections|listDeviceAttachments|listHistory|listInputDeviceAxes|listInputDeviceButtons|listInputDevices|listMenuAnnotation|listNodeTypes|listPanelCategories|listRelatives|listSets|listTransforms|listUnselected|listerEditor|loadFluid|loadNewShelf|loadPlugin|loadPluginLanguageResources|loadPrefObjects|localizedPanelLabel|lockNode|loft|log|longNameOf|lookThru|ls|lsThroughFilter|lsType|lsUI|Mayatomr|mag|makeIdentity|makeLive|makePaintable|makeRoll|makeSingleSurface|makeTubeOn|makebot|manipMoveContext|manipMoveLimitsCtx|manipOptions|manipRotateContext|manipRotateLimitsCtx|manipScaleContext|manipScaleLimitsCtx|marker|match|max|memory|menu|menuBarLayout|menuEditor|menuItem|menuItemToShelf|menuSet|menuSetPref|messageLine|min|minimizeApp|mirrorJoint|modelCurrentTimeCtx|modelEditor|modelPanel|mouse|movIn|movOut|move|moveIKtoFK|moveKeyCtx|moveVertexAlongDirection|multiProfileBirailSurface|mute|nParticle|nameCommand|nameField|namespace|namespaceInfo|newPanelItems|newton|nodeCast|nodeIconButton|nodeOutliner|nodePreset|nodeType|noise|nonLinear|normalConstraint|normalize|nurbsBoolean|nurbsCopyUVSet|nurbsCube|nurbsEditUV|nurbsPlane|nurbsSelect|nurbsSquare|nurbsToPoly|nurbsToPolygonsPref|nurbsToSubdiv|nurbsToSubdivPref|nurbsUVSet|nurbsViewDirectionVector|objExists|objectCenter|objectLayer|objectType|objectTypeUI|obsoleteProc|oceanNurbsPreviewPlane|offsetCurve|offsetCurveOnSurface|offsetSurface|openGLExtension|openMayaPref|optionMenu|optionMenuGrp|optionVar|orbit|orbitCtx|orientConstraint|outlinerEditor|outlinerPanel|overrideModifier|paintEffectsDisplay|pairBlend|palettePort|paneLayout|panel|panelConfiguration|panelHistory|paramDimContext|paramDimension|paramLocator|parent|parentConstraint|particle|particleExists|particleInstancer|particleRenderInfo|partition|pasteKey|pathAnimation|pause|pclose|percent|performanceOptions|pfxstrokes|pickWalk|picture|pixelMove|planarSrf|plane|play|playbackOptions|playblast|plugAttr|plugNode|pluginInfo|pluginResourceUtil|pointConstraint|pointCurveConstraint|pointLight|pointMatrixMult|pointOnCurve|pointOnSurface|pointPosition|poleVectorConstraint|polyAppend|polyAppendFacetCtx|polyAppendVertex|polyAutoProjection|polyAverageNormal|polyAverageVertex|polyBevel|polyBlendColor|polyBlindData|polyBoolOp|polyBridgeEdge|polyCacheMonitor|polyCheck|polyChipOff|polyClipboard|polyCloseBorder|polyCollapseEdge|polyCollapseFacet|polyColorBlindData|polyColorDel|polyColorPerVertex|polyColorSet|polyCompare|polyCone|polyCopyUV|polyCrease|polyCreaseCtx|polyCreateFacet|polyCreateFacetCtx|polyCube|polyCut|polyCutCtx|polyCylinder|polyCylindricalProjection|polyDelEdge|polyDelFacet|polyDelVertex|polyDuplicateAndConnect|polyDuplicateEdge|polyEditUV|polyEditUVShell|polyEvaluate|polyExtrudeEdge|polyExtrudeFacet|polyExtrudeVertex|polyFlipEdge|polyFlipUV|polyForceUV|polyGeoSampler|polyHelix|polyInfo|polyInstallAction|polyLayoutUV|polyListComponentConversion|polyMapCut|polyMapDel|polyMapSew|polyMapSewMove|polyMergeEdge|polyMergeEdgeCtx|polyMergeFacet|polyMergeFacetCtx|polyMergeUV|polyMergeVertex|polyMirrorFace|polyMoveEdge|polyMoveFacet|polyMoveFacetUV|polyMoveUV|polyMoveVertex|polyNormal|polyNormalPerVertex|polyNormalizeUV|polyOptUvs|polyOptions|polyOutput|polyPipe|polyPlanarProjection|polyPlane|polyPlatonicSolid|polyPoke|polyPrimitive|polyPrism|polyProjection|polyPyramid|polyQuad|polyQueryBlindData|polyReduce|polySelect|polySelectConstraint|polySelectConstraintMonitor|polySelectCtx|polySelectEditCtx|polySeparate|polySetToFaceNormal|polySewEdge|polyShortestPathCtx|polySmooth|polySoftEdge|polySphere|polySphericalProjection|polySplit|polySplitCtx|polySplitEdge|polySplitRing|polySplitVertex|polyStraightenUVBorder|polySubdivideEdge|polySubdivideFacet|polyToSubdiv|polyTorus|polyTransfer|polyTriangulate|polyUVSet|polyUnite|polyWedgeFace|popen|popupMenu|pose|pow|preloadRefEd|print|progressBar|progressWindow|projFileViewer|projectCurve|projectTangent|projectionContext|projectionManip|promptDialog|propModCtx|propMove|psdChannelOutliner|psdEditTextureFile|psdExport|psdTextureFile|putenv|pwd|python|querySubdiv|quit|rad_to_deg|radial|radioButton|radioButtonGrp|radioCollection|radioMenuItemCollection|rampColorPort|rand|randomizeFollicles|randstate|rangeControl|readTake|rebuildCurve|rebuildSurface|recordAttr|recordDevice|redo|reference|referenceEdit|referenceQuery|refineSubdivSelectionList|refresh|refreshAE|registerPluginResource|rehash|reloadImage|removeJoint|removeMultiInstance|removePanelCategory|rename|renameAttr|renameSelectionList|renameUI|render|renderGlobalsNode|renderInfo|renderLayerButton|renderLayerParent|renderLayerPostProcess|renderLayerUnparent|renderManip|renderPartition|renderQualityNode|renderSettings|renderThumbnailUpdate|renderWindowEditor|renderWindowSelectContext|renderer|reorder|reorderDeformers|requires|reroot|resampleFluid|resetAE|resetPfxToPolyCamera|resetTool|resolutionNode|retarget|reverseCurve|reverseSurface|revolve|rgb_to_hsv|rigidBody|rigidSolver|roll|rollCtx|rootOf|rot|rotate|rotationInterpolation|roundConstantRadius|rowColumnLayout|rowLayout|runTimeCommand|runup|sampleImage|saveAllShelves|saveAttrPreset|saveFluid|saveImage|saveInitialState|saveMenu|savePrefObjects|savePrefs|saveShelf|saveToolSettings|scale|scaleBrushBrightness|scaleComponents|scaleConstraint|scaleKey|scaleKeyCtx|sceneEditor|sceneUIReplacement|scmh|scriptCtx|scriptEditorInfo|scriptJob|scriptNode|scriptTable|scriptToShelf|scriptedPanel|scriptedPanelType|scrollField|scrollLayout|sculpt|searchPathArray|seed|selLoadSettings|select|selectContext|selectCurveCV|selectKey|selectKeyCtx|selectKeyframeRegionCtx|selectMode|selectPref|selectPriority|selectType|selectedNodes|selectionConnection|separator|setAttr|setAttrEnumResource|setAttrMapping|setAttrNiceNameResource|setConstraintRestPosition|setDefaultShadingGroup|setDrivenKeyframe|setDynamic|setEditCtx|setEditor|setFluidAttr|setFocus|setInfinity|setInputDeviceMapping|setKeyCtx|setKeyPath|setKeyframe|setKeyframeBlendshapeTargetWts|setMenuMode|setNodeNiceNameResource|setNodeTypeFlag|setParent|setParticleAttr|setPfxToPolyCamera|setPluginResource|setProject|setStampDensity|setStartupMessage|setState|setToolTo|setUITemplate|setXformManip|sets|shadingConnection|shadingGeometryRelCtx|shadingLightRelCtx|shadingNetworkCompare|shadingNode|shapeCompare|shelfButton|shelfLayout|shelfTabLayout|shellField|shortNameOf|showHelp|showHidden|showManipCtx|showSelectionInTitle|showShadingGroupAttrEditor|showWindow|sign|simplify|sin|singleProfileBirailSurface|size|sizeBytes|skinCluster|skinPercent|smoothCurve|smoothTangentSurface|smoothstep|snap2to2|snapKey|snapMode|snapTogetherCtx|snapshot|soft|softMod|softModCtx|sort|sound|soundControl|source|spaceLocator|sphere|sphrand|spotLight|spotLightPreviewPort|spreadSheetEditor|spring|sqrt|squareSurface|srtContext|stackTrace|startString|startsWith|stitchAndExplodeShell|stitchSurface|stitchSurfacePoints|strcmp|stringArrayCatenate|stringArrayContains|stringArrayCount|stringArrayInsertAtIndex|stringArrayIntersector|stringArrayRemove|stringArrayRemoveAtIndex|stringArrayRemoveDuplicates|stringArrayRemoveExact|stringArrayToString|stringToStringArray|strip|stripPrefixFromName|stroke|subdAutoProjection|subdCleanTopology|subdCollapse|subdDuplicateAndConnect|subdEditUV|subdListComponentConversion|subdMapCut|subdMapSewMove|subdMatchTopology|subdMirror|subdToBlind|subdToPoly|subdTransferUVsToCache|subdiv|subdivCrease|subdivDisplaySmoothness|substitute|substituteAllString|substituteGeometry|substring|surface|surfaceSampler|surfaceShaderList|swatchDisplayPort|switchTable|symbolButton|symbolCheckBox|sysFile|system|tabLayout|tan|tangentConstraint|texLatticeDeformContext|texManipContext|texMoveContext|texMoveUVShellContext|texRotateContext|texScaleContext|texSelectContext|texSelectShortestPathCtx|texSmudgeUVContext|texWinToolCtx|text|textCurves|textField|textFieldButtonGrp|textFieldGrp|textManip|textScrollList|textToShelf|textureDisplacePlane|textureHairColor|texturePlacementContext|textureWindow|threadCount|threePointArcCtx|timeControl|timePort|timerX|toNativePath|toggle|toggleAxis|toggleWindowVisibility|tokenize|tokenizeList|tolerance|tolower|toolButton|toolCollection|toolDropped|toolHasOptions|toolPropertyWindow|torus|toupper|trace|track|trackCtx|transferAttributes|transformCompare|transformLimits|translator|trim|trunc|truncateFluidCache|truncateHairCache|tumble|tumbleCtx|turbulence|twoPointArcCtx|uiRes|uiTemplate|unassignInputDevice|undo|undoInfo|ungroup|uniform|unit|unloadPlugin|untangleUV|untitledFileName|untrim|upAxis|updateAE|userCtx|uvLink|uvSnapshot|validateShelfName|vectorize|view2dToolCtx|viewCamera|viewClipPlane|viewFit|viewHeadOn|viewLookAt|viewManip|viewPlace|viewSet|visor|volumeAxis|vortex|waitCursor|warning|webBrowser|webBrowserPrefs|whatIs|window|windowPref|wire|wireContext|workspace|wrinkle|wrinkleContext|writeTake|xbmLangPathList|xform)\b/,operator:[/\+[+=]?|-[-=]?|&&|\|\||[<>]=|[*\/!=]=?|[%^]/,{pattern:/(^|[^<])<(?!<)/,lookbehind:!0},{pattern:/(^|[^>])>(?!>)/,lookbehind:!0}],punctuation:/<<|>>|[.,:;?\[\](){}]/},e.languages.mel.code.inside.rest=e.languages.mel}e.exports=t,t.displayName="mel",t.aliases=[]},23388(e){"use strict";function t(e){e.languages.mizar={comment:/::.+/,keyword:/@proof\b|\b(?:according|aggregate|all|and|antonym|are|as|associativity|assume|asymmetry|attr|be|begin|being|by|canceled|case|cases|clusters?|coherence|commutativity|compatibility|connectedness|consider|consistency|constructors|contradiction|correctness|def|deffunc|define|definitions?|defpred|do|does|equals|end|environ|ex|exactly|existence|for|from|func|given|hence|hereby|holds|idempotence|identity|iff?|implies|involutiveness|irreflexivity|is|it|let|means|mode|non|not|notations?|now|of|or|otherwise|over|per|pred|prefix|projectivity|proof|provided|qua|reconsider|redefine|reduce|reducibility|reflexivity|registrations?|requirements|reserve|sch|schemes?|section|selector|set|sethood|st|struct|such|suppose|symmetry|synonym|take|that|the|then|theorems?|thesis|thus|to|transitivity|uniqueness|vocabular(?:y|ies)|when|where|with|wrt)\b/,parameter:{pattern:/\$(?:10|\d)/,alias:"variable"},variable:/\b\w+(?=:)/,number:/(?:\b|-)\d+\b/,operator:/\.\.\.|->|&|\.?=/,punctuation:/\(#|#\)|[,:;\[\](){}]/}}e.exports=t,t.displayName="mizar",t.aliases=[]},90596(e){"use strict";function t(e){var t,n,r,i;t=e,r=["ObjectId","Code","BinData","DBRef","Timestamp","NumberLong","NumberDecimal","MaxKey","MinKey","RegExp","ISODate","UUID"],i="(?:"+(n=(n=["$eq","$gt","$gte","$in","$lt","$lte","$ne","$nin","$and","$not","$nor","$or","$exists","$type","$expr","$jsonSchema","$mod","$regex","$text","$where","$geoIntersects","$geoWithin","$near","$nearSphere","$all","$elemMatch","$size","$bitsAllClear","$bitsAllSet","$bitsAnyClear","$bitsAnySet","$comment","$elemMatch","$meta","$slice","$currentDate","$inc","$min","$max","$mul","$rename","$set","$setOnInsert","$unset","$addToSet","$pop","$pull","$push","$pullAll","$each","$position","$slice","$sort","$bit","$addFields","$bucket","$bucketAuto","$collStats","$count","$currentOp","$facet","$geoNear","$graphLookup","$group","$indexStats","$limit","$listLocalSessions","$listSessions","$lookup","$match","$merge","$out","$planCacheStats","$project","$redact","$replaceRoot","$replaceWith","$sample","$set","$skip","$sort","$sortByCount","$unionWith","$unset","$unwind","$abs","$accumulator","$acos","$acosh","$add","$addToSet","$allElementsTrue","$and","$anyElementTrue","$arrayElemAt","$arrayToObject","$asin","$asinh","$atan","$atan2","$atanh","$avg","$binarySize","$bsonSize","$ceil","$cmp","$concat","$concatArrays","$cond","$convert","$cos","$dateFromParts","$dateToParts","$dateFromString","$dateToString","$dayOfMonth","$dayOfWeek","$dayOfYear","$degreesToRadians","$divide","$eq","$exp","$filter","$first","$floor","$function","$gt","$gte","$hour","$ifNull","$in","$indexOfArray","$indexOfBytes","$indexOfCP","$isArray","$isNumber","$isoDayOfWeek","$isoWeek","$isoWeekYear","$last","$last","$let","$literal","$ln","$log","$log10","$lt","$lte","$ltrim","$map","$max","$mergeObjects","$meta","$min","$millisecond","$minute","$mod","$month","$multiply","$ne","$not","$objectToArray","$or","$pow","$push","$radiansToDegrees","$range","$reduce","$regexFind","$regexFindAll","$regexMatch","$replaceOne","$replaceAll","$reverseArray","$round","$rtrim","$second","$setDifference","$setEquals","$setIntersection","$setIsSubset","$setUnion","$size","$sin","$slice","$split","$sqrt","$stdDevPop","$stdDevSamp","$strcasecmp","$strLenBytes","$strLenCP","$substr","$substrBytes","$substrCP","$subtract","$sum","$switch","$tan","$toBool","$toDate","$toDecimal","$toDouble","$toInt","$toLong","$toObjectId","$toString","$toLower","$toUpper","$trim","$trunc","$type","$week","$year","$zip","$comment","$explain","$hint","$max","$maxTimeMS","$min","$orderby","$query","$returnKey","$showDiskLoc","$natural"]).map(function(e){return e.replace("$","\\$")})).join("|")+")\\b",t.languages.mongodb=t.languages.extend("javascript",{}),t.languages.insertBefore("mongodb","string",{property:{pattern:/(?:(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)(?=\s*:)/,greedy:!0,inside:{keyword:RegExp("^(['\"])?"+i+"(?:\\1)?$")}}}),t.languages.mongodb.string.inside={url:{pattern:/https?:\/\/[-\w@:%.+~#=]{1,256}\.[a-z0-9()]{1,6}\b[-\w()@:%+.~#?&/=]*/i,greedy:!0},entity:{pattern:/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/,greedy:!0}},t.languages.insertBefore("mongodb","constant",{builtin:{pattern:RegExp("\\b(?:"+r.join("|")+")\\b"),alias:"keyword"}})}e.exports=t,t.displayName="mongodb",t.aliases=[]},95721(e){"use strict";function t(e){e.languages.monkey={string:/"[^"\r\n]*"/,comment:[{pattern:/^#Rem\s[\s\S]*?^#End/im,greedy:!0},{pattern:/'.+/,greedy:!0}],preprocessor:{pattern:/(^[ \t]*)#.+/m,lookbehind:!0,alias:"comment"},function:/\b\w+(?=\()/,"type-char":{pattern:/(\w)[?%#$]/,lookbehind:!0,alias:"variable"},number:{pattern:/((?:\.\.)?)(?:(?:\b|\B-\.?|\B\.)\d+(?:(?!\.\.)\.\d*)?|\$[\da-f]+)/i,lookbehind:!0},keyword:/\b(?:Void|Strict|Public|Private|Property|Bool|Int|Float|String|Array|Object|Continue|Exit|Import|Extern|New|Self|Super|Try|Catch|Eachin|True|False|Extends|Abstract|Final|Select|Case|Default|Const|Local|Global|Field|Method|Function|Class|End|If|Then|Else|ElseIf|EndIf|While|Wend|Repeat|Until|Forever|For|To|Step|Next|Return|Module|Interface|Implements|Inline|Throw|Null)\b/i,operator:/\.\.|<[=>]?|>=?|:?=|(?:[+\-*\/&~|]|\b(?:Mod|Shl|Shr)\b)=?|\b(?:And|Not|Or)\b/i,punctuation:/[.,:;()\[\]]/}}e.exports=t,t.displayName="monkey",t.aliases=[]},64262(e){"use strict";function t(e){e.languages.moonscript={comment:/--.*/,string:[{pattern:/'[^']*'|\[(=*)\[[\s\S]*?\]\1\]/,greedy:!0},{pattern:/"[^"]*"/,greedy:!0,inside:{interpolation:{pattern:/#\{[^{}]*\}/,inside:{moonscript:{pattern:/(^#\{)[\s\S]+(?=\})/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/#\{|\}/,alias:"punctuation"}}}}}],"class-name":[{pattern:/(\b(?:class|extends)[ \t]+)\w+/,lookbehind:!0},/\b[A-Z]\w*/],keyword:/\b(?:class|continue|do|else|elseif|export|extends|for|from|if|import|in|local|nil|return|self|super|switch|then|unless|using|when|while|with)\b/,variable:/@@?\w*/,property:{pattern:/\b(?!\d)\w+(?=:)|(:)(?!\d)\w+/,lookbehind:!0},function:{pattern:/\b(?:_G|_VERSION|assert|collectgarbage|coroutine\.(?:running|create|resume|status|wrap|yield)|debug\.(?:debug|gethook|getinfo|getlocal|getupvalue|setlocal|setupvalue|sethook|traceback|getfenv|getmetatable|getregistry|setfenv|setmetatable)|dofile|error|getfenv|getmetatable|io\.(?:stdin|stdout|stderr|close|flush|input|lines|open|output|popen|read|tmpfile|type|write)|ipairs|load|loadfile|loadstring|math\.(?:abs|acos|asin|atan|atan2|ceil|sin|cos|tan|deg|exp|floor|log|log10|max|min|fmod|modf|cosh|sinh|tanh|pow|rad|sqrt|frexp|ldexp|random|randomseed|pi)|module|next|os\.(?:clock|date|difftime|execute|exit|getenv|remove|rename|setlocale|time|tmpname)|package\.(?:cpath|loaded|loadlib|path|preload|seeall)|pairs|pcall|print|rawequal|rawget|rawset|require|select|setfenv|setmetatable|string\.(?:byte|char|dump|find|len|lower|rep|sub|upper|format|gsub|gmatch|match|reverse)|table\.(?:maxn|concat|sort|insert|remove)|tonumber|tostring|type|unpack|xpcall)\b/,inside:{punctuation:/\./}},boolean:/\b(?:false|true)\b/,number:/(?:\B\.\d+|\b\d+\.\d+|\b\d+(?=[eE]))(?:[eE][-+]?\d+)?\b|\b(?:0x[a-fA-F\d]+|\d+)(?:U?LL)?\b/,operator:/\.{3}|[-=]>|~=|(?:[-+*/%<>!=]|\.\.)=?|[:#^]|\b(?:and|or)\b=?|\b(?:not)\b/,punctuation:/[.,()[\]{}\\]/},e.languages.moonscript.string[1].inside.interpolation.inside.moonscript.inside=e.languages.moonscript,e.languages.moon=e.languages.moonscript}e.exports=t,t.displayName="moonscript",t.aliases=["moon"]},18190(e){"use strict";function t(e){e.languages.n1ql={comment:/\/\*[\s\S]*?(?:$|\*\/)/,parameter:/\$[\w.]+/,string:{pattern:/(["'])(?:\\[\s\S]|(?!\1)[^\\]|\1\1)*\1/,greedy:!0},identifier:{pattern:/`(?:\\[\s\S]|[^\\`]|``)*`/,greedy:!0},function:/\b(?:ABS|ACOS|ARRAY_AGG|ARRAY_APPEND|ARRAY_AVG|ARRAY_CONCAT|ARRAY_CONTAINS|ARRAY_COUNT|ARRAY_DISTINCT|ARRAY_FLATTEN|ARRAY_IFNULL|ARRAY_INSERT|ARRAY_INTERSECT|ARRAY_LENGTH|ARRAY_MAX|ARRAY_MIN|ARRAY_POSITION|ARRAY_PREPEND|ARRAY_PUT|ARRAY_RANGE|ARRAY_REMOVE|ARRAY_REPEAT|ARRAY_REPLACE|ARRAY_REVERSE|ARRAY_SORT|ARRAY_STAR|ARRAY_SUM|ARRAY_SYMDIFF|ARRAY_SYMDIFFN|ARRAY_UNION|ASIN|ATAN|ATAN2|AVG|BASE64|BASE64_DECODE|BASE64_ENCODE|BITAND|BITCLEAR|BITNOT|BITOR|BITSET|BITSHIFT|BITTEST|BITXOR|CEIL|CLOCK_LOCAL|CLOCK_MILLIS|CLOCK_STR|CLOCK_TZ|CLOCK_UTC|CONTAINS|CONTAINS_TOKEN|CONTAINS_TOKEN_LIKE|CONTAINS_TOKEN_REGEXP|COS|COUNT|CURL|DATE_ADD_MILLIS|DATE_ADD_STR|DATE_DIFF_MILLIS|DATE_DIFF_STR|DATE_FORMAT_STR|DATE_PART_MILLIS|DATE_PART_STR|DATE_RANGE_MILLIS|DATE_RANGE_STR|DATE_TRUNC_MILLIS|DATE_TRUNC_STR|DECODE_JSON|DEGREES|DURATION_TO_STR|E|ENCODED_SIZE|ENCODE_JSON|EXP|FLOOR|GREATEST|HAS_TOKEN|IFINF|IFMISSING|IFMISSINGORNULL|IFNAN|IFNANORINF|IFNULL|INITCAP|ISARRAY|ISATOM|ISBOOLEAN|ISNUMBER|ISOBJECT|ISSTRING|IsBitSET|LEAST|LENGTH|LN|LOG|LOWER|LTRIM|MAX|META|MILLIS|MILLIS_TO_LOCAL|MILLIS_TO_STR|MILLIS_TO_TZ|MILLIS_TO_UTC|MILLIS_TO_ZONE_NAME|MIN|MISSINGIF|NANIF|NEGINFIF|NOW_LOCAL|NOW_MILLIS|NOW_STR|NOW_TZ|NOW_UTC|NULLIF|OBJECT_ADD|OBJECT_CONCAT|OBJECT_INNER_PAIRS|OBJECT_INNER_VALUES|OBJECT_LENGTH|OBJECT_NAMES|OBJECT_PAIRS|OBJECT_PUT|OBJECT_REMOVE|OBJECT_RENAME|OBJECT_REPLACE|OBJECT_UNWRAP|OBJECT_VALUES|PAIRS|PI|POLY_LENGTH|POSINFIF|POSITION|POWER|RADIANS|RANDOM|REGEXP_CONTAINS|REGEXP_LIKE|REGEXP_POSITION|REGEXP_REPLACE|REPEAT|REPLACE|REVERSE|ROUND|RTRIM|SIGN|SIN|SPLIT|SQRT|STR_TO_DURATION|STR_TO_MILLIS|STR_TO_TZ|STR_TO_UTC|STR_TO_ZONE_NAME|SUBSTR|SUFFIXES|SUM|TAN|TITLE|TOARRAY|TOATOM|TOBOOLEAN|TOKENS|TONUMBER|TOOBJECT|TOSTRING|TRIM|TRUNC|TYPE|UPPER|WEEKDAY_MILLIS|WEEKDAY_STR)(?=\s*\()/i,keyword:/\b(?:ALL|ALTER|ANALYZE|AS|ASC|BEGIN|BINARY|BOOLEAN|BREAK|BUCKET|BUILD|BY|CALL|CAST|CLUSTER|COLLATE|COLLECTION|COMMIT|CONNECT|CONTINUE|CORRELATE|COVER|CREATE|DATABASE|DATASET|DATASTORE|DECLARE|DECREMENT|DELETE|DERIVED|DESC|DESCRIBE|DISTINCT|DO|DROP|EACH|ELEMENT|EXCEPT|EXCLUDE|EXECUTE|EXPLAIN|FETCH|FLATTEN|FOR|FORCE|FROM|FUNCTION|GRANT|GROUP|GSI|HAVING|IF|IGNORE|ILIKE|INCLUDE|INCREMENT|INDEX|INFER|INLINE|INNER|INSERT|INTERSECT|INTO|IS|JOIN|KEY|KEYS|KEYSPACE|KNOWN|LAST|LEFT|LET|LETTING|LIMIT|LSM|MAP|MAPPING|MATCHED|MATERIALIZED|MERGE|MINUS|MISSING|NAMESPACE|NEST|NULL|NUMBER|OBJECT|OFFSET|ON|OPTION|ORDER|OUTER|OVER|PARSE|PARTITION|PASSWORD|PATH|POOL|PREPARE|PRIMARY|PRIVATE|PRIVILEGE|PROCEDURE|PUBLIC|RAW|REALM|REDUCE|RENAME|RETURN|RETURNING|REVOKE|RIGHT|ROLE|ROLLBACK|SATISFIES|SCHEMA|SELECT|SELF|SEMI|SET|SHOW|SOME|START|STATISTICS|STRING|SYSTEM|TO|TRANSACTION|TRIGGER|TRUNCATE|UNDER|UNION|UNIQUE|UNKNOWN|UNNEST|UNSET|UPDATE|UPSERT|USE|USER|USING|VALIDATE|VALUE|VALUES|VIA|VIEW|WHERE|WHILE|WITH|WORK|XOR)\b/i,boolean:/\b(?:TRUE|FALSE)\b/i,number:/(?:\b\d+\.|\B\.)\d+e[+\-]?\d+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/%]|!=|==?|\|\||<[>=]?|>=?|\b(?:AND|ANY|ARRAY|BETWEEN|CASE|ELSE|END|EVERY|EXISTS|FIRST|IN|LIKE|NOT|OR|THEN|VALUED|WHEN|WITHIN)\b/i,punctuation:/[;[\](),.{}:]/}}e.exports=t,t.displayName="n1ql",t.aliases=[]},70896(e){"use strict";function t(e){e.languages.n4js=e.languages.extend("javascript",{keyword:/\b(?:any|Array|boolean|break|case|catch|class|const|constructor|continue|debugger|declare|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|module|new|null|number|package|private|protected|public|return|set|static|string|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/}),e.languages.insertBefore("n4js","constant",{annotation:{pattern:/@+\w+/,alias:"operator"}}),e.languages.n4jsd=e.languages.n4js}e.exports=t,t.displayName="n4js",t.aliases=["n4jsd"]},42242(e){"use strict";function t(e){e.languages["nand2tetris-hdl"]={comment:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,keyword:/\b(?:CHIP|IN|OUT|PARTS|BUILTIN|CLOCKED)\b/,boolean:/\b(?:true|false)\b/,function:/\b[A-Za-z][A-Za-z0-9]*(?=\()/,number:/\b\d+\b/,operator:/=|\.\./,punctuation:/[{}[\];(),:]/}}e.exports=t,t.displayName="nand2tetrisHdl",t.aliases=[]},37943(e){"use strict";function t(e){!function(e){var t=/\{[^\r\n\[\]{}]*\}/,n={"quoted-string":{pattern:/"(?:[^"\\]|\\.)*"/,alias:"operator"},"command-param-id":{pattern:/(\s)\w+:/,lookbehind:!0,alias:"property"},"command-param-value":[{pattern:t,alias:"selector"},{pattern:/([\t ])\S+/,lookbehind:!0,greedy:!0,alias:"operator"},{pattern:/\S(?:.*\S)?/,alias:"operator"}]};function r(e){for(var t="[]{}",n=[],r=0;r.+/m,alias:"tag",inside:{value:{pattern:/(^>\w+[\t ]+)(?!\s)[^{}\r\n]+/,lookbehind:!0,alias:"operator"},key:{pattern:/(^>)\w+/,lookbehind:!0}}},label:{pattern:/^([\t ]*)#[\t ]*\w+[\t ]*$/m,lookbehind:!0,alias:"regex"},command:{pattern:/^([\t ]*)@\w+(?=[\t ]|$).*/m,lookbehind:!0,alias:"function",inside:{"command-name":/^@\w+/,expression:{pattern:t,greedy:!0,alias:"selector"},"command-params":{pattern:/\s*\S[\s\S]*/,inside:n}}},"generic-text":{pattern:/(^[ \t]*)[^#@>;\s].*/m,lookbehind:!0,alias:"punctuation",inside:{"escaped-char":/\\[{}\[\]"]/,expression:{pattern:t,greedy:!0,alias:"selector"},"inline-command":{pattern:/\[[\t ]*\w[^\r\n\[\]]*\]/,greedy:!0,alias:"function",inside:{"command-params":{pattern:/(^\[[\t ]*\w+\b)[\s\S]+(?=\]$)/,lookbehind:!0,inside:n},"command-param-name":{pattern:/^(\[[\t ]*)\w+/,lookbehind:!0,alias:"name"},"start-stop-char":/[\[\]]/}}}}},e.languages.nani=e.languages.naniscript,e.hooks.add("after-tokenize",function(e){e.tokens.forEach(function(e){if("string"!=typeof e&&"generic-text"===e.type){var t=i(e);r(t)||(e.type="bad-line",e.content=t)}})})}(e)}e.exports=t,t.displayName="naniscript",t.aliases=[]},293(e){"use strict";function t(e){e.languages.nasm={comment:/;.*$/m,string:/(["'`])(?:\\.|(?!\1)[^\\\r\n])*\1/,label:{pattern:/(^\s*)[A-Za-z._?$][\w.?$@~#]*:/m,lookbehind:!0,alias:"function"},keyword:[/\[?BITS (?:16|32|64)\]?/,{pattern:/(^\s*)section\s*[a-z.]+:?/im,lookbehind:!0},/(?:extern|global)[^;\r\n]*/i,/(?:CPU|FLOAT|DEFAULT).*$/m],register:{pattern:/\b(?:st\d|[xyz]mm\d\d?|[cdt]r\d|r\d\d?[bwd]?|[er]?[abcd]x|[abcd][hl]|[er]?(?:bp|sp|si|di)|[cdefgs]s)\b/i,alias:"variable"},number:/(?:\b|(?=\$))(?:0[hx](?:\.[\da-f]+|[\da-f]+(?:\.[\da-f]+)?)(?:p[+-]?\d+)?|\d[\da-f]+[hx]|\$\d[\da-f]*|0[oq][0-7]+|[0-7]+[oq]|0[by][01]+|[01]+[by]|0[dt]\d+|(?:\d+(?:\.\d+)?|\.\d+)(?:\.?e[+-]?\d+)?[dt]?)\b/i,operator:/[\[\]*+\-\/%<>=&|$!]/}}e.exports=t,t.displayName="nasm",t.aliases=[]},83873(e){"use strict";function t(e){e.languages.neon={comment:{pattern:/#.*/,greedy:!0},datetime:{pattern:/(^|[[{(=:,\s])\d\d\d\d-\d\d?-\d\d?(?:(?:[Tt]| +)\d\d?:\d\d:\d\d(?:\.\d*)? *(?:Z|[-+]\d\d?(?::?\d\d)?)?)?(?=$|[\]}),\s])/,lookbehind:!0,alias:"number"},key:{pattern:/(^|[[{(,\s])[^,:=[\]{}()'"\s]+(?=\s*:(?:$|[\]}),\s])|\s*=)/,lookbehind:!0,alias:"atrule"},number:{pattern:/(^|[[{(=:,\s])[+-]?(?:0x[\da-fA-F]+|0o[0-7]+|0b[01]+|(?:\d+(?:\.\d*)?|\.?\d+)(?:[eE][+-]?\d+)?)(?=$|[\]}),:=\s])/,lookbehind:!0},boolean:{pattern:/(^|[[{(=:,\s])(?:true|false|yes|no)(?=$|[\]}),:=\s])/i,lookbehind:!0},null:{pattern:/(^|[[{(=:,\s])(?:null)(?=$|[\]}),:=\s])/i,lookbehind:!0,alias:"keyword"},string:{pattern:/(^|[[{(=:,\s])(?:('''|""")\r?\n(?:(?:[^\r\n]|\r?\n(?![\t ]*\2))*\r?\n)?[\t ]*\2|'[^'\r\n]*'|"(?:\\.|[^\\"\r\n])*")/,lookbehind:!0,greedy:!0},literal:{pattern:/(^|[[{(=:,\s])(?:[^#"',:=[\]{}()\s`-]|[:-][^"',=[\]{}()\s])(?:[^,:=\]})(\s]|:(?![\s,\]})]|$)|[ \t]+[^#,:=\]})(\s])*/,lookbehind:!0,alias:"string"},punctuation:/[,:=[\]{}()-]/}}e.exports=t,t.displayName="neon",t.aliases=[]},75932(e){"use strict";function t(e){e.languages.nevod={comment:/\/\/.*|(?:\/\*[\s\S]*?(?:\*\/|$))/,string:{pattern:/(?:"(?:""|[^"])*"(?!")|'(?:''|[^'])*'(?!'))!?\*?/,greedy:!0,inside:{"string-attrs":/!$|!\*$|\*$/}},namespace:{pattern:/(@namespace\s+)[a-zA-Z0-9\-.]+(?=\s*\{)/,lookbehind:!0},pattern:{pattern:/(@pattern\s+)?#?[a-zA-Z0-9\-.]+(?:\s*\(\s*(?:~\s*)?[a-zA-Z0-9\-.]+\s*(?:,\s*(?:~\s*)?[a-zA-Z0-9\-.]*)*\))?(?=\s*=)/,lookbehind:!0,inside:{"pattern-name":{pattern:/^#?[a-zA-Z0-9\-.]+/,alias:"class-name"},fields:{pattern:/\(.*\)/,inside:{"field-name":{pattern:/[a-zA-Z0-9\-.]+/,alias:"variable"},punctuation:/[,()]/,operator:{pattern:/~/,alias:"field-hidden-mark"}}}}},search:{pattern:/(@search\s+|#)[a-zA-Z0-9\-.]+(?:\.\*)?(?=\s*;)/,alias:"function",lookbehind:!0},keyword:/@(?:require|namespace|pattern|search|inside|outside|having|where)\b/,"standard-pattern":{pattern:/\b(?:Word|Punct|Symbol|Space|LineBreak|Start|End|Alpha|AlphaNum|Num|NumAlpha|Blank|WordBreak|Any)(?:\([a-zA-Z0-9\-.,\s+]*\))?/,inside:{"standard-pattern-name":{pattern:/^[a-zA-Z0-9\-.]+/,alias:"builtin"},quantifier:{pattern:/\b\d+(?:\s*\+|\s*-\s*\d+)?(?!\w)/,alias:"number"},"standard-pattern-attr":{pattern:/[a-zA-Z0-9\-.]+/,alias:"builtin"},punctuation:/[,()]/}},quantifier:{pattern:/\b\d+(?:\s*\+|\s*-\s*\d+)?(?!\w)/,alias:"number"},operator:[{pattern:/=/,alias:"pattern-def"},{pattern:/&/,alias:"conjunction"},{pattern:/~/,alias:"exception"},{pattern:/\?/,alias:"optionality"},{pattern:/[[\]]/,alias:"repetition"},{pattern:/[{}]/,alias:"variation"},{pattern:/[+_]/,alias:"sequence"},{pattern:/\.{2,3}/,alias:"span"}],"field-capture":[{pattern:/([a-zA-Z0-9\-.]+\s*\()\s*[a-zA-Z0-9\-.]+\s*:\s*[a-zA-Z0-9\-.]+(?:\s*,\s*[a-zA-Z0-9\-.]+\s*:\s*[a-zA-Z0-9\-.]+)*(?=\s*\))/,lookbehind:!0,inside:{"field-name":{pattern:/[a-zA-Z0-9\-.]+/,alias:"variable"},colon:/:/}},{pattern:/[a-zA-Z0-9\-.]+\s*:/,inside:{"field-name":{pattern:/[a-zA-Z0-9\-.]+/,alias:"variable"},colon:/:/}}],punctuation:/[:;,()]/,name:/[a-zA-Z0-9\-.]+/}}e.exports=t,t.displayName="nevod",t.aliases=[]},60221(e){"use strict";function t(e){var t,n;n=/\$(?:\w[a-z\d]*(?:_[^\x00-\x1F\s"'\\()$]*)?|\{[^}\s"'\\]+\})/i,(t=e).languages.nginx={comment:{pattern:/(^|[\s{};])#.*/,lookbehind:!0},directive:{pattern:/(^|\s)\w(?:[^;{}"'\\\s]|\\.|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\s+(?:#.*(?!.)|(?![#\s])))*?(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:{string:{pattern:/((?:^|[^\\])(?:\\\\)*)(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/,lookbehind:!0,inside:{escape:{pattern:/\\["'\\nrt]/,alias:"entity"},variable:n}},comment:{pattern:/(\s)#.*/,lookbehind:!0,greedy:!0},keyword:{pattern:/^\S+/,greedy:!0},boolean:{pattern:/(\s)(?:off|on)(?!\S)/,lookbehind:!0},number:{pattern:/(\s)\d+[a-z]*(?!\S)/i,lookbehind:!0},variable:n}},punctuation:/[{};]/}}e.exports=t,t.displayName="nginx",t.aliases=[]},44188(e){"use strict";function t(e){e.languages.nim={comment:/#.*/,string:{pattern:/(?:(?:\b(?!\d)(?:\w|\\x[8-9a-fA-F][0-9a-fA-F])+)?(?:"""[\s\S]*?"""(?!")|"(?:\\[\s\S]|""|[^"\\])*")|'(?:\\(?:\d+|x[\da-fA-F]{2}|.)|[^'])')/,greedy:!0},number:/\b(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:(?!\.\.)\.[\d_]*)?(?:[eE][+-]?\d[\d_]*)?)(?:'?[iuf]\d*)?/,keyword:/\b(?:addr|as|asm|atomic|bind|block|break|case|cast|concept|const|continue|converter|defer|discard|distinct|do|elif|else|end|enum|except|export|finally|for|from|func|generic|if|import|include|interface|iterator|let|macro|method|mixin|nil|object|out|proc|ptr|raise|ref|return|static|template|try|tuple|type|using|var|when|while|with|without|yield)\b/,function:{pattern:/(?:(?!\d)(?:\w|\\x[8-9a-fA-F][0-9a-fA-F])+|`[^`\r\n]+`)\*?(?:\[[^\]]+\])?(?=\s*\()/,inside:{operator:/\*$/}},ignore:{pattern:/`[^`\r\n]+`/,inside:{punctuation:/`/}},operator:{pattern:/(^|[({\[](?=\.\.)|(?![({\[]\.).)(?:(?:[=+\-*\/<>@$~&%|!?^:\\]|\.\.|\.(?![)}\]]))+|\b(?:and|div|of|or|in|is|isnot|mod|not|notin|shl|shr|xor)\b)/m,lookbehind:!0},punctuation:/[({\[]\.|\.[)}\]]|[`(){}\[\],:]/}}e.exports=t,t.displayName="nim",t.aliases=[]},74426(e){"use strict";function t(e){e.languages.nix={comment:/\/\*[\s\S]*?\*\/|#.*/,string:{pattern:/"(?:[^"\\]|\\[\s\S])*"|''(?:(?!'')[\s\S]|''(?:'|\\|\$\{))*''/,greedy:!0,inside:{interpolation:{pattern:/(^|(?:^|(?!'').)[^\\])\$\{(?:[^{}]|\{[^}]*\})*\}/,lookbehind:!0,inside:{antiquotation:{pattern:/^\$(?=\{)/,alias:"variable"}}}}},url:[/\b(?:[a-z]{3,7}:\/\/)[\w\-+%~\/.:#=?&]+/,{pattern:/([^\/])(?:[\w\-+%~.:#=?&]*(?!\/\/)[\w\-+%~\/.:#=?&])?(?!\/\/)\/[\w\-+%~\/.:#=?&]*/,lookbehind:!0}],antiquotation:{pattern:/\$(?=\{)/,alias:"variable"},number:/\b\d+\b/,keyword:/\b(?:assert|builtins|else|if|in|inherit|let|null|or|then|with)\b/,function:/\b(?:abort|add|all|any|attrNames|attrValues|baseNameOf|compareVersions|concatLists|currentSystem|deepSeq|derivation|dirOf|div|elem(?:At)?|fetch(?:url|Tarball)|filter(?:Source)?|fromJSON|genList|getAttr|getEnv|hasAttr|hashString|head|import|intersectAttrs|is(?:Attrs|Bool|Function|Int|List|Null|String)|length|lessThan|listToAttrs|map|mul|parseDrvName|pathExists|read(?:Dir|File)|removeAttrs|replaceStrings|seq|sort|stringLength|sub(?:string)?|tail|throw|to(?:File|JSON|Path|String|XML)|trace|typeOf)\b|\bfoldl'\B/,boolean:/\b(?:true|false)\b/,operator:/[=!<>]=?|\+\+?|\|\||&&|\/\/|->?|[?@]/,punctuation:/[{}()[\].,:;]/},e.languages.nix.string.inside.interpolation.inside.rest=e.languages.nix}e.exports=t,t.displayName="nix",t.aliases=[]},88447(e){"use strict";function t(e){e.languages.nsis={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|[#;].*)/,lookbehind:!0},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},keyword:{pattern:/(^[\t ]*)(?:Abort|Add(?:BrandingImage|Size)|AdvSplash|Allow(?:RootDirInstall|SkipFiles)|AutoCloseWindow|Banner|BG(?:Font|Gradient|Image)|BrandingText|BringToFront|Call(?:InstDLL)?|Caption|ChangeUI|CheckBitmap|ClearErrors|CompletedText|ComponentText|CopyFiles|CRCCheck|Create(?:Directory|Font|ShortCut)|Delete(?:INISec|INIStr|RegKey|RegValue)?|Detail(?:Print|sButtonText)|Dialer|Dir(?:Text|Var|Verify)|EnableWindow|Enum(?:RegKey|RegValue)|Exch|Exec(?:Shell(?:Wait)?|Wait)?|ExpandEnvStrings|File(?:BufSize|Close|ErrorText|Open|Read|ReadByte|ReadUTF16LE|ReadWord|WriteUTF16LE|Seek|Write|WriteByte|WriteWord)?|Find(?:Close|First|Next|Window)|FlushINI|Get(?:CurInstType|CurrentAddress|DlgItem|DLLVersion(?:Local)?|ErrorLevel|FileTime(?:Local)?|FullPathName|Function(?:Address|End)?|InstDirError|LabelAddress|TempFileName)|Goto|HideWindow|Icon|If(?:Abort|Errors|FileExists|RebootFlag|Silent)|InitPluginsDir|Install(?:ButtonText|Colors|Dir(?:RegKey)?)|InstProgressFlags|Inst(?:Type(?:GetText|SetText)?)|Int(?:64|Ptr)?CmpU?|Int(?:64)?Fmt|Int(?:Ptr)?Op|IsWindow|Lang(?:DLL|String)|License(?:BkColor|Data|ForceSelection|LangString|Text)|LoadLanguageFile|LockWindow|Log(?:Set|Text)|Manifest(?:DPIAware|SupportedOS)|Math|MessageBox|MiscButtonText|Name|Nop|ns(?:Dialogs|Exec)|NSISdl|OutFile|Page(?:Callbacks)?|PE(?:DllCharacteristics|SubsysVer)|Pop|Push|Quit|Read(?:EnvStr|INIStr|RegDWORD|RegStr)|Reboot|RegDLL|Rename|RequestExecutionLevel|ReserveFile|Return|RMDir|SearchPath|Section(?:End|GetFlags|GetInstTypes|GetSize|GetText|Group|In|SetFlags|SetInstTypes|SetSize|SetText)?|SendMessage|Set(?:AutoClose|BrandingImage|Compress|Compressor(?:DictSize)?|CtlColors|CurInstType|DatablockOptimize|DateSave|Details(?:Print|View)|ErrorLevel|Errors|FileAttributes|Font|OutPath|Overwrite|PluginUnload|RebootFlag|RegView|ShellVarContext|Silent)|Show(?:InstDetails|UninstDetails|Window)|Silent(?:Install|UnInstall)|Sleep|SpaceTexts|Splash|StartMenu|Str(?:CmpS?|Cpy|Len)|SubCaption|System|Unicode|Uninstall(?:ButtonText|Caption|Icon|SubCaption|Text)|UninstPage|UnRegDLL|UserInfo|Var|VI(?:AddVersionKey|FileVersion|ProductVersion)|VPatch|WindowIcon|Write(?:INIStr|Reg(?:Bin|DWORD|ExpandStr|MultiStr|None|Str)|Uninstaller)|XPStyle)\b/m,lookbehind:!0},property:/\b(?:admin|all|auto|both|colored|false|force|hide|highest|lastused|leave|listonly|none|normal|notset|off|on|open|print|show|silent|silentlog|smooth|textonly|true|user|ARCHIVE|FILE_(?:ATTRIBUTE_ARCHIVE|ATTRIBUTE_NORMAL|ATTRIBUTE_OFFLINE|ATTRIBUTE_READONLY|ATTRIBUTE_SYSTEM|ATTRIBUTE_TEMPORARY)|HK(?:(?:CR|CU|LM)(?:32|64)?|DD|PD|U)|HKEY_(?:CLASSES_ROOT|CURRENT_CONFIG|CURRENT_USER|DYN_DATA|LOCAL_MACHINE|PERFORMANCE_DATA|USERS)|ID(?:ABORT|CANCEL|IGNORE|NO|OK|RETRY|YES)|MB_(?:ABORTRETRYIGNORE|DEFBUTTON1|DEFBUTTON2|DEFBUTTON3|DEFBUTTON4|ICONEXCLAMATION|ICONINFORMATION|ICONQUESTION|ICONSTOP|OK|OKCANCEL|RETRYCANCEL|RIGHT|RTLREADING|SETFOREGROUND|TOPMOST|USERICON|YESNO)|NORMAL|OFFLINE|READONLY|SHCTX|SHELL_CONTEXT|SYSTEM|TEMPORARY)\b/,constant:/\$\{[\w\.:\^-]+\}|\$\([\w\.:\^-]+\)/i,variable:/\$\w+/i,number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--?|\+\+?|<=?|>=?|==?=?|&&?|\|\|?|[?*\/~^%]/,punctuation:/[{}[\];(),.:]/,important:{pattern:/(^[\t ]*)!(?:addincludedir|addplugindir|appendfile|cd|define|delfile|echo|else|endif|error|execute|finalize|getdllversion|gettlbversion|ifdef|ifmacrodef|ifmacrondef|ifndef|if|include|insertmacro|macroend|macro|makensis|packhdr|pragma|searchparse|searchreplace|system|tempfile|undef|verbose|warning)\b/im,lookbehind:!0}}}e.exports=t,t.displayName="nsis",t.aliases=[]},16032(e,t,n){"use strict";var r=n(65806);function i(e){e.register(r),e.languages.objectivec=e.languages.extend("c",{string:/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|@"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,keyword:/\b(?:asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while|in|self|super)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete e.languages.objectivec["class-name"],e.languages.objc=e.languages.objectivec}e.exports=i,i.displayName="objectivec",i.aliases=["objc"]},33607(e){"use strict";function t(e){e.languages.ocaml={comment:/\(\*[\s\S]*?\*\)/,string:[{pattern:/"(?:\\.|[^\\\r\n"])*"/,greedy:!0},{pattern:/(['`])(?:\\(?:\d+|x[\da-f]+|.)|(?!\1)[^\\\r\n])\1/i,greedy:!0}],number:/\b(?:0x[\da-f][\da-f_]+|(?:0[bo])?\d[\d_]*(?:\.[\d_]*)?(?:e[+-]?[\d_]+)?)/i,directive:{pattern:/\B#\w+/,alias:"important"},label:{pattern:/\B~\w+/,alias:"function"},"type-variable":{pattern:/\B'\w+/,alias:"function"},variant:{pattern:/`\w+/,alias:"variable"},module:{pattern:/\b[A-Z]\w+/,alias:"variable"},keyword:/\b(?:as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|match|method|module|mutable|new|nonrec|object|of|open|private|rec|sig|struct|then|to|try|type|val|value|virtual|when|where|while|with)\b/,boolean:/\b(?:false|true)\b/,operator:/:=|[=<>@^|&+\-*\/$%!?~][!$%&*+\-.\/:<=>?@^|~]*|\b(?:and|asr|land|lor|lsl|lsr|lxor|mod|or)\b/,punctuation:/[(){}\[\]|.,:;]|\b_\b/}}e.exports=t,t.displayName="ocaml",t.aliases=[]},22001(e,t,n){"use strict";var r=n(65806);function i(e){var t,n;e.register(r),(t=e).languages.opencl=t.languages.extend("c",{keyword:/\b(?:__attribute__|(?:__)?(?:constant|global|kernel|local|private|read_only|read_write|write_only)|auto|break|case|complex|const|continue|default|do|(?:float|double)(?:16(?:x(?:1|16|2|4|8))?|1x(?:1|16|2|4|8)|2(?:x(?:1|16|2|4|8))?|3|4(?:x(?:1|16|2|4|8))?|8(?:x(?:1|16|2|4|8))?)?|else|enum|extern|for|goto|(?:u?(?:char|short|int|long)|half|quad|bool)(?:2|3|4|8|16)?|if|imaginary|inline|packed|pipe|register|restrict|return|signed|sizeof|static|struct|switch|typedef|uniform|union|unsigned|void|volatile|while)\b/,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[fuhl]{0,4}/i,boolean:/\b(?:false|true)\b/,"constant-opencl-kernel":{pattern:/\b(?:CHAR_(?:BIT|MAX|MIN)|CLK_(?:ADDRESS_(?:CLAMP(?:_TO_EDGE)?|NONE|REPEAT)|FILTER_(?:LINEAR|NEAREST)|(?:LOCAL|GLOBAL)_MEM_FENCE|NORMALIZED_COORDS_(?:FALSE|TRUE))|CL_(?:BGRA|(?:HALF_)?FLOAT|INTENSITY|LUMINANCE|A?R?G?B?[Ax]?|(?:(?:UN)?SIGNED|[US]NORM)_(?:INT(?:8|16|32))|UNORM_(?:INT_101010|SHORT_(?:555|565)))|(?:DBL|FLT|HALF)_(?:DIG|EPSILON|MANT_DIG|(?:MIN|MAX)(?:(?:_10)?_EXP)?)|FLT_RADIX|HUGE_VALF?|INFINITY|(?:INT|LONG|SCHAR|SHRT)_(?:MAX|MIN)|(?:UCHAR|USHRT|UINT|ULONG)_MAX|MAXFLOAT|M_(?:[12]_PI|2_SQRTPI|E|LN(?:2|10)|LOG(?:10|2)E?|PI(?:_[24])?|SQRT(?:1_2|2))(?:_F|_H)?|NAN)\b/,alias:"constant"}}),t.languages.insertBefore("opencl","class-name",{"builtin-type":{pattern:/\b(?:_cl_(?:command_queue|context|device_id|event|kernel|mem|platform_id|program|sampler)|cl_(?:image_format|mem_fence_flags)|clk_event_t|event_t|image(?:1d_(?:array_|buffer_)?t|2d_(?:array_(?:depth_|msaa_depth_|msaa_)?|depth_|msaa_depth_|msaa_)?t|3d_t)|intptr_t|ndrange_t|ptrdiff_t|queue_t|reserve_id_t|sampler_t|size_t|uintptr_t)\b/,alias:"keyword"}}),n={"type-opencl-host":{pattern:/\b(?:cl_(?:GLenum|GLint|GLuin|addressing_mode|bitfield|bool|buffer_create_type|build_status|channel_(?:order|type)|(?:u?(?:char|short|int|long)|float|double)(?:2|3|4|8|16)?|command_(?:queue(?:_info|_properties)?|type)|context(?:_info|_properties)?|device_(?:exec_capabilities|fp_config|id|info|local_mem_type|mem_cache_type|type)|(?:event|sampler)(?:_info)?|filter_mode|half|image_info|kernel(?:_info|_work_group_info)?|map_flags|mem(?:_flags|_info|_object_type)?|platform_(?:id|info)|profiling_info|program(?:_build_info|_info)?))\b/,alias:"keyword"},"boolean-opencl-host":{pattern:/\bCL_(?:TRUE|FALSE)\b/,alias:"boolean"},"constant-opencl-host":{pattern:/\bCL_(?:A|ABGR|ADDRESS_(?:CLAMP(?:_TO_EDGE)?|MIRRORED_REPEAT|NONE|REPEAT)|ARGB|BGRA|BLOCKING|BUFFER_CREATE_TYPE_REGION|BUILD_(?:ERROR|IN_PROGRESS|NONE|PROGRAM_FAILURE|SUCCESS)|COMMAND_(?:ACQUIRE_GL_OBJECTS|BARRIER|COPY_(?:BUFFER(?:_RECT|_TO_IMAGE)?|IMAGE(?:_TO_BUFFER)?)|FILL_(?:BUFFER|IMAGE)|MAP(?:_BUFFER|_IMAGE)|MARKER|MIGRATE(?:_SVM)?_MEM_OBJECTS|NATIVE_KERNEL|NDRANGE_KERNEL|READ_(?:BUFFER(?:_RECT)?|IMAGE)|RELEASE_GL_OBJECTS|SVM_(?:FREE|MAP|MEMCPY|MEMFILL|UNMAP)|TASK|UNMAP_MEM_OBJECT|USER|WRITE_(?:BUFFER(?:_RECT)?|IMAGE))|COMPILER_NOT_AVAILABLE|COMPILE_PROGRAM_FAILURE|COMPLETE|CONTEXT_(?:DEVICES|INTEROP_USER_SYNC|NUM_DEVICES|PLATFORM|PROPERTIES|REFERENCE_COUNT)|DEPTH(?:_STENCIL)?|DEVICE_(?:ADDRESS_BITS|AFFINITY_DOMAIN_(?:L[1-4]_CACHE|NEXT_PARTITIONABLE|NUMA)|AVAILABLE|BUILT_IN_KERNELS|COMPILER_AVAILABLE|DOUBLE_FP_CONFIG|ENDIAN_LITTLE|ERROR_CORRECTION_SUPPORT|EXECUTION_CAPABILITIES|EXTENSIONS|GLOBAL_(?:MEM_(?:CACHELINE_SIZE|CACHE_SIZE|CACHE_TYPE|SIZE)|VARIABLE_PREFERRED_TOTAL_SIZE)|HOST_UNIFIED_MEMORY|IL_VERSION|IMAGE(?:2D_MAX_(?:HEIGHT|WIDTH)|3D_MAX_(?:DEPTH|HEIGHT|WIDTH)|_BASE_ADDRESS_ALIGNMENT|_MAX_ARRAY_SIZE|_MAX_BUFFER_SIZE|_PITCH_ALIGNMENT|_SUPPORT)|LINKER_AVAILABLE|LOCAL_MEM_SIZE|LOCAL_MEM_TYPE|MAX_(?:CLOCK_FREQUENCY|COMPUTE_UNITS|CONSTANT_ARGS|CONSTANT_BUFFER_SIZE|GLOBAL_VARIABLE_SIZE|MEM_ALLOC_SIZE|NUM_SUB_GROUPS|ON_DEVICE_(?:EVENTS|QUEUES)|PARAMETER_SIZE|PIPE_ARGS|READ_IMAGE_ARGS|READ_WRITE_IMAGE_ARGS|SAMPLERS|WORK_GROUP_SIZE|WORK_ITEM_DIMENSIONS|WORK_ITEM_SIZES|WRITE_IMAGE_ARGS)|MEM_BASE_ADDR_ALIGN|MIN_DATA_TYPE_ALIGN_SIZE|NAME|NATIVE_VECTOR_WIDTH_(?:CHAR|DOUBLE|FLOAT|HALF|INT|LONG|SHORT)|NOT_(?:AVAILABLE|FOUND)|OPENCL_C_VERSION|PARENT_DEVICE|PARTITION_(?:AFFINITY_DOMAIN|BY_AFFINITY_DOMAIN|BY_COUNTS|BY_COUNTS_LIST_END|EQUALLY|FAILED|MAX_SUB_DEVICES|PROPERTIES|TYPE)|PIPE_MAX_(?:ACTIVE_RESERVATIONS|PACKET_SIZE)|PLATFORM|PREFERRED_(?:GLOBAL_ATOMIC_ALIGNMENT|INTEROP_USER_SYNC|LOCAL_ATOMIC_ALIGNMENT|PLATFORM_ATOMIC_ALIGNMENT|VECTOR_WIDTH_(?:CHAR|DOUBLE|FLOAT|HALF|INT|LONG|SHORT))|PRINTF_BUFFER_SIZE|PROFILE|PROFILING_TIMER_RESOLUTION|QUEUE_(?:ON_(?:DEVICE_(?:MAX_SIZE|PREFERRED_SIZE|PROPERTIES)|HOST_PROPERTIES)|PROPERTIES)|REFERENCE_COUNT|SINGLE_FP_CONFIG|SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS|SVM_(?:ATOMICS|CAPABILITIES|COARSE_GRAIN_BUFFER|FINE_GRAIN_BUFFER|FINE_GRAIN_SYSTEM)|TYPE(?:_ACCELERATOR|_ALL|_CPU|_CUSTOM|_DEFAULT|_GPU)?|VENDOR(?:_ID)?|VERSION)|DRIVER_VERSION|EVENT_(?:COMMAND_(?:EXECUTION_STATUS|QUEUE|TYPE)|CONTEXT|REFERENCE_COUNT)|EXEC_(?:KERNEL|NATIVE_KERNEL|STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST)|FILTER_(?:LINEAR|NEAREST)|FLOAT|FP_(?:CORRECTLY_ROUNDED_DIVIDE_SQRT|DENORM|FMA|INF_NAN|ROUND_TO_INF|ROUND_TO_NEAREST|ROUND_TO_ZERO|SOFT_FLOAT)|GLOBAL|HALF_FLOAT|IMAGE_(?:ARRAY_SIZE|BUFFER|DEPTH|ELEMENT_SIZE|FORMAT|FORMAT_MISMATCH|FORMAT_NOT_SUPPORTED|HEIGHT|NUM_MIP_LEVELS|NUM_SAMPLES|ROW_PITCH|SLICE_PITCH|WIDTH)|INTENSITY|INVALID_(?:ARG_INDEX|ARG_SIZE|ARG_VALUE|BINARY|BUFFER_SIZE|BUILD_OPTIONS|COMMAND_QUEUE|COMPILER_OPTIONS|CONTEXT|DEVICE|DEVICE_PARTITION_COUNT|DEVICE_QUEUE|DEVICE_TYPE|EVENT|EVENT_WAIT_LIST|GLOBAL_OFFSET|GLOBAL_WORK_SIZE|GL_OBJECT|HOST_PTR|IMAGE_DESCRIPTOR|IMAGE_FORMAT_DESCRIPTOR|IMAGE_SIZE|KERNEL|KERNEL_ARGS|KERNEL_DEFINITION|KERNEL_NAME|LINKER_OPTIONS|MEM_OBJECT|MIP_LEVEL|OPERATION|PIPE_SIZE|PLATFORM|PROGRAM|PROGRAM_EXECUTABLE|PROPERTY|QUEUE_PROPERTIES|SAMPLER|VALUE|WORK_DIMENSION|WORK_GROUP_SIZE|WORK_ITEM_SIZE)|KERNEL_(?:ARG_(?:ACCESS_(?:NONE|QUALIFIER|READ_ONLY|READ_WRITE|WRITE_ONLY)|ADDRESS_(?:CONSTANT|GLOBAL|LOCAL|PRIVATE|QUALIFIER)|INFO_NOT_AVAILABLE|NAME|TYPE_(?:CONST|NAME|NONE|PIPE|QUALIFIER|RESTRICT|VOLATILE))|ATTRIBUTES|COMPILE_NUM_SUB_GROUPS|COMPILE_WORK_GROUP_SIZE|CONTEXT|EXEC_INFO_SVM_FINE_GRAIN_SYSTEM|EXEC_INFO_SVM_PTRS|FUNCTION_NAME|GLOBAL_WORK_SIZE|LOCAL_MEM_SIZE|LOCAL_SIZE_FOR_SUB_GROUP_COUNT|MAX_NUM_SUB_GROUPS|MAX_SUB_GROUP_SIZE_FOR_NDRANGE|NUM_ARGS|PREFERRED_WORK_GROUP_SIZE_MULTIPLE|PRIVATE_MEM_SIZE|PROGRAM|REFERENCE_COUNT|SUB_GROUP_COUNT_FOR_NDRANGE|WORK_GROUP_SIZE)|LINKER_NOT_AVAILABLE|LINK_PROGRAM_FAILURE|LOCAL|LUMINANCE|MAP_(?:FAILURE|READ|WRITE|WRITE_INVALIDATE_REGION)|MEM_(?:ALLOC_HOST_PTR|ASSOCIATED_MEMOBJECT|CONTEXT|COPY_HOST_PTR|COPY_OVERLAP|FLAGS|HOST_NO_ACCESS|HOST_PTR|HOST_READ_ONLY|HOST_WRITE_ONLY|KERNEL_READ_AND_WRITE|MAP_COUNT|OBJECT_(?:ALLOCATION_FAILURE|BUFFER|IMAGE1D|IMAGE1D_ARRAY|IMAGE1D_BUFFER|IMAGE2D|IMAGE2D_ARRAY|IMAGE3D|PIPE)|OFFSET|READ_ONLY|READ_WRITE|REFERENCE_COUNT|SIZE|SVM_ATOMICS|SVM_FINE_GRAIN_BUFFER|TYPE|USES_SVM_POINTER|USE_HOST_PTR|WRITE_ONLY)|MIGRATE_MEM_OBJECT_(?:CONTENT_UNDEFINED|HOST)|MISALIGNED_SUB_BUFFER_OFFSET|NONE|NON_BLOCKING|OUT_OF_(?:HOST_MEMORY|RESOURCES)|PIPE_(?:MAX_PACKETS|PACKET_SIZE)|PLATFORM_(?:EXTENSIONS|HOST_TIMER_RESOLUTION|NAME|PROFILE|VENDOR|VERSION)|PROFILING_(?:COMMAND_(?:COMPLETE|END|QUEUED|START|SUBMIT)|INFO_NOT_AVAILABLE)|PROGRAM_(?:BINARIES|BINARY_SIZES|BINARY_TYPE(?:_COMPILED_OBJECT|_EXECUTABLE|_LIBRARY|_NONE)?|BUILD_(?:GLOBAL_VARIABLE_TOTAL_SIZE|LOG|OPTIONS|STATUS)|CONTEXT|DEVICES|IL|KERNEL_NAMES|NUM_DEVICES|NUM_KERNELS|REFERENCE_COUNT|SOURCE)|QUEUED|QUEUE_(?:CONTEXT|DEVICE|DEVICE_DEFAULT|ON_DEVICE|ON_DEVICE_DEFAULT|OUT_OF_ORDER_EXEC_MODE_ENABLE|PROFILING_ENABLE|PROPERTIES|REFERENCE_COUNT|SIZE)|R|RA|READ_(?:ONLY|WRITE)_CACHE|RG|RGB|RGBA|RGBx|RGx|RUNNING|Rx|SAMPLER_(?:ADDRESSING_MODE|CONTEXT|FILTER_MODE|LOD_MAX|LOD_MIN|MIP_FILTER_MODE|NORMALIZED_COORDS|REFERENCE_COUNT)|(?:UN)?SIGNED_INT(?:8|16|32)|SNORM_INT(?:8|16)|SUBMITTED|SUCCESS|UNORM_INT(?:16|24|8|_101010|_101010_2)|UNORM_SHORT_(?:555|565)|VERSION_(?:1_0|1_1|1_2|2_0|2_1)|sBGRA|sRGB|sRGBA|sRGBx)\b/,alias:"constant"},"function-opencl-host":{pattern:/\bcl(?:BuildProgram|CloneKernel|CompileProgram|Create(?:Buffer|CommandQueue(?:WithProperties)?|Context|ContextFromType|Image|Image2D|Image3D|Kernel|KernelsInProgram|Pipe|ProgramWith(?:Binary|BuiltInKernels|IL|Source)|Sampler|SamplerWithProperties|SubBuffer|SubDevices|UserEvent)|Enqueue(?:(?:Barrier|Marker)(?:WithWaitList)?|Copy(?:Buffer(?:Rect|ToImage)?|Image(?:ToBuffer)?)|(?:Fill|Map)(?:Buffer|Image)|MigrateMemObjects|NDRangeKernel|NativeKernel|(?:Read|Write)(?:Buffer(?:Rect)?|Image)|SVM(?:Free|Map|MemFill|Memcpy|MigrateMem|Unmap)|Task|UnmapMemObject|WaitForEvents)|Finish|Flush|Get(?:CommandQueueInfo|ContextInfo|Device(?:AndHostTimer|IDs|Info)|Event(?:Profiling)?Info|ExtensionFunctionAddress(?:ForPlatform)?|HostTimer|ImageInfo|Kernel(?:ArgInfo|Info|SubGroupInfo|WorkGroupInfo)|MemObjectInfo|PipeInfo|Platform(?:IDs|Info)|Program(?:Build)?Info|SamplerInfo|SupportedImageFormats)|LinkProgram|(?:Release|Retain)(?:CommandQueue|Context|Device|Event|Kernel|MemObject|Program|Sampler)|SVM(?:Alloc|Free)|Set(?:CommandQueueProperty|DefaultDeviceCommandQueue|EventCallback|Kernel(?:Arg(?:SVMPointer)?|ExecInfo)|Kernel|MemObjectDestructorCallback|UserEventStatus)|Unload(?:Platform)?Compiler|WaitForEvents)\b/,alias:"function"}},t.languages.insertBefore("c","keyword",n),t.languages.cpp&&(n["type-opencl-host-cpp"]={pattern:/\b(?:Buffer|BufferGL|BufferRenderGL|CommandQueue|Context|Device|DeviceCommandQueue|EnqueueArgs|Event|Image|Image1D|Image1DArray|Image1DBuffer|Image2D|Image2DArray|Image2DGL|Image3D|Image3DGL|ImageFormat|ImageGL|Kernel|KernelFunctor|LocalSpaceArg|Memory|NDRange|Pipe|Platform|Program|Sampler|SVMAllocator|SVMTraitAtomic|SVMTraitCoarse|SVMTraitFine|SVMTraitReadOnly|SVMTraitReadWrite|SVMTraitWriteOnly|UserEvent)\b/,alias:"keyword"},t.languages.insertBefore("cpp","keyword",n))}e.exports=i,i.displayName="opencl",i.aliases=[]},22950(e){"use strict";function t(e){e.languages.openqasm={comment:/\/\*[\s\S]*?\*\/|\/\/.*/,string:{pattern:/"[^"\r\n\t]*"|'[^'\r\n\t]*'/,greedy:!0},keyword:/\b(?:barrier|boxas|boxto|break|const|continue|ctrl|def|defcal|defcalgrammar|delay|else|end|for|gate|gphase|if|in|include|inv|kernel|lengthof|let|measure|pow|reset|return|rotary|stretchinf|while|CX|OPENQASM|U)\b|#pragma\b/,"class-name":/\b(?:angle|bit|bool|creg|fixed|float|int|length|qreg|qubit|stretch|uint)\b/,function:/\b(?:sin|cos|tan|exp|ln|sqrt|rotl|rotr|popcount)\b(?=\s*\()/,constant:/\b(?:pi|tau|euler)\b|π|𝜏|ℇ/,number:{pattern:/(^|[^.\w$])(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?(?:dt|ns|us|µs|ms|s)?/i,lookbehind:!0},operator:/->|>>=?|<<=?|&&|\|\||\+\+|--|[!=<>&|~^+\-*/%]=?|@/,punctuation:/[(){}\[\];,:.]/},e.languages.qasm=e.languages.openqasm}e.exports=t,t.displayName="openqasm",t.aliases=["qasm"]},23254(e){"use strict";function t(e){e.languages.oz={comment:/\/\*[\s\S]*?\*\/|%.*/,string:{pattern:/"(?:[^"\\]|\\[\s\S])*"/,greedy:!0},atom:{pattern:/'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,alias:"builtin"},keyword:/\$|\[\]|\b(?:_|at|attr|case|catch|choice|class|cond|declare|define|dis|else(?:case|if)?|end|export|fail|false|feat|finally|from|fun|functor|if|import|in|local|lock|meth|nil|not|of|or|prepare|proc|prop|raise|require|self|skip|then|thread|true|try|unit)\b/,function:[/\b[a-z][A-Za-z\d]*(?=\()/,{pattern:/(\{)[A-Z][A-Za-z\d]*\b/,lookbehind:!0}],number:/\b(?:0[bx][\da-f]+|\d+(?:\.\d*)?(?:e~?\d+)?)\b|&(?:[^\\]|\\(?:\d{3}|.))/i,variable:/\b[A-Z][A-Za-z\d]*|`(?:[^`\\]|\\.)+`/,"attr-name":/\b\w+(?=:)/,operator:/:(?:=|::?)|<[-:=]?|=(?:=|=?:?|\\=:?|!!?|[|#+\-*\/,~^@]|\b(?:andthen|div|mod|orelse)\b/,punctuation:/[\[\](){}.:;?]/}}e.exports=t,t.displayName="oz",t.aliases=[]},92694(e){"use strict";function t(e){var t;e.languages.parigp={comment:/\/\*[\s\S]*?\*\/|\\\\.*/,string:{pattern:/"(?:[^"\\\r\n]|\\.)*"/,greedy:!0},keyword:RegExp("\\b(?:"+(t=(t=["breakpoint","break","dbg_down","dbg_err","dbg_up","dbg_x","forcomposite","fordiv","forell","forpart","forprime","forstep","forsubgroup","forvec","for","iferr","if","local","my","next","return","until","while"]).map(function(e){return e.split("").join(" *")}).join("|"))+")\\b"),function:/\b\w(?:[\w ]*\w)?(?= *\()/,number:{pattern:/((?:\. *\. *)?)(?:\b\d(?: *\d)*(?: *(?!\. *\.)\.(?: *\d)*)?|\. *\d(?: *\d)*)(?: *e *(?:[+-] *)?\d(?: *\d)*)?/i,lookbehind:!0},operator:/\. *\.|[*\/!](?: *=)?|%(?: *=|(?: *#)?(?: *')*)?|\+(?: *[+=])?|-(?: *[-=>])?|<(?: *>|(?: *<)?(?: *=)?)?|>(?: *>)?(?: *=)?|=(?: *=){0,2}|\\(?: *\/)?(?: *=)?|&(?: *&)?|\| *\||['#~^]/,punctuation:/[\[\]{}().,:;|]/}}e.exports=t,t.displayName="parigp",t.aliases=[]},43273(e){"use strict";function t(e){var t,n;n=(t=e).languages.parser=t.languages.extend("markup",{keyword:{pattern:/(^|[^^])(?:\^(?:case|eval|for|if|switch|throw)\b|@(?:BASE|CLASS|GET(?:_DEFAULT)?|OPTIONS|SET_DEFAULT|USE)\b)/,lookbehind:!0},variable:{pattern:/(^|[^^])\B\$(?:\w+|(?=[.{]))(?:(?:\.|::?)\w+)*(?:\.|::?)?/,lookbehind:!0,inside:{punctuation:/\.|:+/}},function:{pattern:/(^|[^^])\B[@^]\w+(?:(?:\.|::?)\w+)*(?:\.|::?)?/,lookbehind:!0,inside:{keyword:{pattern:/(^@)(?:GET_|SET_)/,lookbehind:!0},punctuation:/\.|:+/}},escape:{pattern:/\^(?:[$^;@()\[\]{}"':]|#[a-f\d]*)/i,alias:"builtin"},punctuation:/[\[\](){};]/}),n=t.languages.insertBefore("parser","keyword",{"parser-comment":{pattern:/(\s)#.*/,lookbehind:!0,alias:"comment"},expression:{pattern:/(^|[^^])\((?:[^()]|\((?:[^()]|\((?:[^()])*\))*\))*\)/,greedy:!0,lookbehind:!0,inside:{string:{pattern:/(^|[^^])(["'])(?:(?!\2)[^^]|\^[\s\S])*\2/,lookbehind:!0},keyword:n.keyword,variable:n.variable,function:n.function,boolean:/\b(?:true|false)\b/,number:/\b(?:0x[a-f\d]+|\d+(?:\.\d*)?(?:e[+-]?\d+)?)\b/i,escape:n.escape,operator:/[~+*\/\\%]|!(?:\|\|?|=)?|&&?|\|\|?|==|<[<=]?|>[>=]?|-[fd]?|\b(?:def|eq|ge|gt|in|is|le|lt|ne)\b/,punctuation:n.punctuation}}}),t.languages.insertBefore("inside","punctuation",{expression:n.expression,keyword:n.keyword,variable:n.variable,function:n.function,escape:n.escape,"parser-punctuation":{pattern:n.punctuation,alias:"punctuation"}},n.tag.inside["attr-value"])}e.exports=t,t.displayName="parser",t.aliases=[]},60718(e){"use strict";function t(e){e.languages.pascal={comment:[/\(\*[\s\S]+?\*\)/,/\{[\s\S]+?\}/,/\/\/.*/],string:{pattern:/(?:'(?:''|[^'\r\n])*'(?!')|#[&$%]?[a-f\d]+)+|\^[a-z]/i,greedy:!0},keyword:[{pattern:/(^|[^&])\b(?:absolute|array|asm|begin|case|const|constructor|destructor|do|downto|else|end|file|for|function|goto|if|implementation|inherited|inline|interface|label|nil|object|of|operator|packed|procedure|program|record|reintroduce|repeat|self|set|string|then|to|type|unit|until|uses|var|while|with)\b/i,lookbehind:!0},{pattern:/(^|[^&])\b(?:dispose|exit|false|new|true)\b/i,lookbehind:!0},{pattern:/(^|[^&])\b(?:class|dispinterface|except|exports|finalization|finally|initialization|inline|library|on|out|packed|property|raise|resourcestring|threadvar|try)\b/i,lookbehind:!0},{pattern:/(^|[^&])\b(?:absolute|abstract|alias|assembler|bitpacked|break|cdecl|continue|cppdecl|cvar|default|deprecated|dynamic|enumerator|experimental|export|external|far|far16|forward|generic|helper|implements|index|interrupt|iochecks|local|message|name|near|nodefault|noreturn|nostackframe|oldfpccall|otherwise|overload|override|pascal|platform|private|protected|public|published|read|register|reintroduce|result|safecall|saveregisters|softfloat|specialize|static|stdcall|stored|strict|unaligned|unimplemented|varargs|virtual|write)\b/i,lookbehind:!0}],number:[/(?:[&%]\d+|\$[a-f\d]+)/i,/\b\d+(?:\.\d+)?(?:e[+-]?\d+)?/i],operator:[/\.\.|\*\*|:=|<[<=>]?|>[>=]?|[+\-*\/]=?|[@^=]/i,{pattern:/(^|[^&])\b(?:and|as|div|exclude|in|include|is|mod|not|or|shl|shr|xor)\b/,lookbehind:!0}],punctuation:/\(\.|\.\)|[()\[\]:;,.]/},e.languages.objectpascal=e.languages.pascal}e.exports=t,t.displayName="pascal",t.aliases=["objectpascal"]},39303(e){"use strict";function t(e){var t,n,r,i,a;t=e,n=/\((?:[^()]|\((?:[^()]|\([^()]*\))*\))*\)/.source,r=/(?:\b\w+(?:)?|)/.source.replace(//g,function(){return n}),i=t.languages.pascaligo={comment:/\(\*[\s\S]+?\*\)|\/\/.*/,string:{pattern:/(["'`])(?:\\[\s\S]|(?!\1)[^\\])*\1|\^[a-z]/i,greedy:!0},"class-name":[{pattern:RegExp(/(\btype\s+\w+\s+is\s+)/.source.replace(//g,function(){return r}),"i"),lookbehind:!0,inside:null},{pattern:RegExp(/(?=\s+is\b)/.source.replace(//g,function(){return r}),"i"),inside:null},{pattern:RegExp(/(:\s*)/.source.replace(//g,function(){return r})),lookbehind:!0,inside:null}],keyword:{pattern:/(^|[^&])\b(?:begin|block|case|const|else|end|fail|for|from|function|if|is|nil|of|remove|return|skip|then|type|var|while|with)\b/i,lookbehind:!0},boolean:{pattern:/(^|[^&])\b(?:True|False)\b/i,lookbehind:!0},builtin:{pattern:/(^|[^&])\b(?:bool|int|list|map|nat|record|string|unit)\b/i,lookbehind:!0},function:/\b\w+(?=\s*\()/i,number:[/%[01]+|&[0-7]+|\$[a-f\d]+/i,/\b\d+(?:\.\d+)?(?:e[+-]?\d+)?(?:mtz|n)?/i],operator:/->|=\/=|\.\.|\*\*|:=|<[<=>]?|>[>=]?|[+\-*\/]=?|[@^=|]|\b(?:and|mod|or)\b/,punctuation:/\(\.|\.\)|[()\[\]:;,.{}]/},a=["comment","keyword","builtin","operator","punctuation"].reduce(function(e,t){return e[t]=i[t],e},{}),i["class-name"].forEach(function(e){e.inside=a})}e.exports=t,t.displayName="pascaligo",t.aliases=[]},77393(e){"use strict";function t(e){e.languages.pcaxis={string:/"[^"]*"/,keyword:{pattern:/((?:^|;)\s*)[-A-Z\d]+(?:\s*\[[-\w]+\])?(?:\s*\("[^"]*"(?:,\s*"[^"]*")*\))?(?=\s*=)/,lookbehind:!0,greedy:!0,inside:{keyword:/^[-A-Z\d]+/,language:{pattern:/^(\s*)\[[-\w]+\]/,lookbehind:!0,inside:{punctuation:/^\[|\]$/,property:/[-\w]+/}},"sub-key":{pattern:/^(\s*)\S[\s\S]*/,lookbehind:!0,inside:{parameter:{pattern:/"[^"]*"/,alias:"property"},punctuation:/^\(|\)$|,/}}}},operator:/=/,tlist:{pattern:/TLIST\s*\(\s*\w+(?:(?:\s*,\s*"[^"]*")+|\s*,\s*"[^"]*"-"[^"]*")?\s*\)/,greedy:!0,inside:{function:/^TLIST/,property:{pattern:/^(\s*\(\s*)\w+/,lookbehind:!0},string:/"[^"]*"/,punctuation:/[(),]/,operator:/-/}},punctuation:/[;,]/,number:{pattern:/(^|\s)\d+(?:\.\d+)?(?!\S)/,lookbehind:!0},boolean:/YES|NO/},e.languages.px=e.languages.pcaxis}e.exports=t,t.displayName="pcaxis",t.aliases=["px"]},19023(e){"use strict";function t(e){e.languages.peoplecode={comment:RegExp([/\/\*[\s\S]*?\*\//.source,/\bREM[^;]*;/.source,/<\*(?:[^<*]|\*(?!>)|<(?!\*)|<\*(?:(?!\*>)[\s\S])*\*>)*\*>/.source,/\/\+[\s\S]*?\+\//.source].join("|")),string:{pattern:/'(?:''|[^'\r\n])*'(?!')|"(?:""|[^"\r\n])*"(?!")/,greedy:!0},variable:/%\w+/,"function-definition":{pattern:/((?:^|[^\w-])(?:function|method)\s+)\w+/i,lookbehind:!0,alias:"function"},"class-name":{pattern:/((?:^|[^-\w])(?:as|catch|class|component|create|extends|global|implements|instance|local|of|property|returns)\s+)\w+(?::\w+)*/i,lookbehind:!0,inside:{punctuation:/:/}},keyword:/\b(?:abstract|alias|as|catch|class|component|constant|create|declare|else|end-(?:class|evaluate|for|function|get|if|method|set|try|while)|evaluate|extends|for|function|get|global|implements|import|instance|if|library|local|method|null|of|out|peopleCode|private|program|property|protected|readonly|ref|repeat|returns?|set|step|then|throw|to|try|until|value|when(?:-other)?|while)\b/i,"operator-keyword":{pattern:/\b(?:and|not|or)\b/i,alias:"operator"},function:/[_a-z]\w*(?=\s*\()/i,boolean:/\b(?:false|true)\b/i,number:/\b\d+(?:\.\d+)?\b/,operator:/<>|[<>]=?|!=|\*\*|[-+*/|=@]/,punctuation:/[:.;,()[\]]/},e.languages.pcode=e.languages.peoplecode}e.exports=t,t.displayName="peoplecode",t.aliases=["pcode"]},74212(e){"use strict";function t(e){e.languages.perl={comment:[{pattern:/(^\s*)=\w[\s\S]*?=cut.*/m,lookbehind:!0},{pattern:/(^|[^\\$])#.*/,lookbehind:!0}],string:[{pattern:/\b(?:q|qq|qx|qw)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\((?:[^()\\]|\\[\s\S])*\)/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\{(?:[^{}\\]|\\[\s\S])*\}/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\[(?:[^[\]\\]|\\[\s\S])*\]/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*<(?:[^<>\\]|\\[\s\S])*>/,greedy:!0},{pattern:/("|`)(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/'(?:[^'\\\r\n]|\\.)*'/,greedy:!0}],regex:[{pattern:/\b(?:m|qr)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngc]*/,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s+([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\((?:[^()\\]|\\[\s\S])*\)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\{(?:[^{}\\]|\\[\s\S])*\}\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\[(?:[^[\]\\]|\\[\s\S])*\]\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*<(?:[^<>\\]|\\[\s\S])*>\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/\/(?:[^\/\\\r\n]|\\.)*\/[msixpodualngc]*(?=\s*(?:$|[\r\n,.;})&|\-+*~<>!?^]|(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor|x)\b))/,greedy:!0}],variable:[/[&*$@%]\{\^[A-Z]+\}/,/[&*$@%]\^[A-Z_]/,/[&*$@%]#?(?=\{)/,/[&*$@%]#?(?:(?:::)*'?(?!\d)[\w$]+(?![\w$]))+(?:::)*/i,/[&*$@%]\d+/,/(?!%=)[$@%][!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~]/],filehandle:{pattern:/<(?![<=])\S*>|\b_\b/,alias:"symbol"},vstring:{pattern:/v\d+(?:\.\d+)*|\d+(?:\.\d+){2,}/,alias:"string"},function:{pattern:/sub \w+/i,inside:{keyword:/sub/}},keyword:/\b(?:any|break|continue|default|delete|die|do|else|elsif|eval|for|foreach|given|goto|if|last|local|my|next|our|package|print|redo|require|return|say|state|sub|switch|undef|unless|until|use|when|while)\b/,number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,operator:/-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor)\b/,punctuation:/[{}[\];(),:]/}}e.exports=t,t.displayName="perl",t.aliases=[]},5137(e,t,n){"use strict";var r=n(88262);function i(e){e.register(r),e.languages.insertBefore("php","variable",{this:/\$this\b/,global:/\$(?:_(?:SERVER|GET|POST|FILES|REQUEST|SESSION|ENV|COOKIE)|GLOBALS|HTTP_RAW_POST_DATA|argc|argv|php_errormsg|http_response_header)\b/,scope:{pattern:/\b[\w\\]+::/,inside:{keyword:/static|self|parent/,punctuation:/::|\\/}}})}e.exports=i,i.displayName="phpExtras",i.aliases=[]},88262(e,t,n){"use strict";var r=n(93205);function i(e){var t,n,i,a,o,s,u,c;e.register(r),n=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,i=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],a=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,o=/|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,s=/[{}\[\](),:;]/,(t=e).languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:n,variable:/\$+(?:\w+\b|(?=\{))/i,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:bool|boolean|int|integer|float|string|object|array)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:bool|int|float|string|object|array(?!\s*\()|mixed|self|static|callable|iterable|(?:null|false)(?=\s*\|))\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*[\w|]\|\s*)(?:null|false)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:bool|int|float|string|object|void|array(?!\s*\()|mixed|self|static|callable|iterable|(?:null|false)(?=\s*\|))\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?[\w|]\|\s*)(?:null|false)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:bool|int|float|string|object|void|array(?!\s*\()|mixed|iterable|(?:null|false)(?=\s*\|))\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:null|false)\b/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:__halt_compiler|abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|namespace|match|new|or|parent|print|private|protected|public|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s+)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:i,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:a,operator:o,punctuation:s},c=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:u={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:t.languages.php}}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:u}}],t.languages.insertBefore("php","variable",{string:c,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:n,string:c,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:i,number:a,operator:o,punctuation:s}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),t.hooks.add("before-tokenize",function(e){if(/<\?/.test(e.code)){var n=/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/gi;t.languages["markup-templating"].buildPlaceholders(e,"php",n)}}),t.hooks.add("after-tokenize",function(e){t.languages["markup-templating"].tokenizePlaceholders(e,"php")})}e.exports=i,i.displayName="php",i.aliases=[]},63632(e,t,n){"use strict";var r=n(88262),i=n(9858);function a(e){var t,n;e.register(r),e.register(i),n=/(?:\b[a-zA-Z]\w*|[|\\[\]])+/.source,(t=e).languages.phpdoc=t.languages.extend("javadoclike",{parameter:{pattern:RegExp("(@(?:global|param|property(?:-read|-write)?|var)\\s+(?:"+n+"\\s+)?)\\$\\w+"),lookbehind:!0}}),t.languages.insertBefore("phpdoc","keyword",{"class-name":[{pattern:RegExp("(@(?:global|package|param|property(?:-read|-write)?|return|subpackage|throws|var)\\s+)"+n),lookbehind:!0,inside:{keyword:/\b(?:callback|resource|boolean|integer|double|object|string|array|false|float|mixed|bool|null|self|true|void|int)\b/,punctuation:/[|\\[\]()]/}}]}),t.languages.javadoclike.addSupport("php",t.languages.phpdoc)}e.exports=a,a.displayName="phpdoc",a.aliases=[]},59149(e,t,n){"use strict";var r=n(11114);function i(e){var t,n,i,a;e.register(r),Array.isArray(i=(n=(t=e).languages.plsql=t.languages.extend("sql",{comment:[/\/\*[\s\S]*?\*\//,/--.*/]})).keyword)||(i=n.keyword=[i]),i.unshift(/\b(?:ACCESS|AGENT|AGGREGATE|ARRAY|ARROW|AT|ATTRIBUTE|AUDIT|AUTHID|BFILE_BASE|BLOB_BASE|BLOCK|BODY|BOTH|BOUND|BYTE|CALLING|CHAR_BASE|CHARSET(?:FORM|ID)|CLOB_BASE|COLAUTH|COLLECT|CLUSTERS?|COMPILED|COMPRESS|CONSTANT|CONSTRUCTOR|CONTEXT|CRASH|CUSTOMDATUM|DANGLING|DATE_BASE|DEFINE|DETERMINISTIC|DURATION|ELEMENT|EMPTY|EXCEPTIONS?|EXCLUSIVE|EXTERNAL|FINAL|FORALL|FORM|FOUND|GENERAL|HEAP|HIDDEN|IDENTIFIED|IMMEDIATE|INCLUDING|INCREMENT|INDICATOR|INDEXES|INDICES|INFINITE|INITIAL|ISOPEN|INSTANTIABLE|INTERFACE|INVALIDATE|JAVA|LARGE|LEADING|LENGTH|LIBRARY|LIKE[24C]|LIMITED|LONG|LOOP|MAP|MAXEXTENTS|MAXLEN|MEMBER|MINUS|MLSLABEL|MULTISET|NAME|NAN|NATIVE|NEW|NOAUDIT|NOCOMPRESS|NOCOPY|NOTFOUND|NOWAIT|NUMBER(?:_BASE)?|OBJECT|OCI(?:COLL|DATE|DATETIME|DURATION|INTERVAL|LOBLOCATOR|NUMBER|RAW|REF|REFCURSOR|ROWID|STRING|TYPE)|OFFLINE|ONLINE|ONLY|OPAQUE|OPERATOR|ORACLE|ORADATA|ORGANIZATION|ORL(?:ANY|VARY)|OTHERS|OVERLAPS|OVERRIDING|PACKAGE|PARALLEL_ENABLE|PARAMETERS?|PASCAL|PCTFREE|PIPE(?:LINED)?|PRAGMA|PRIOR|PRIVATE|RAISE|RANGE|RAW|RECORD|REF|REFERENCE|REM|REMAINDER|RESULT|RESOURCE|RETURNING|REVERSE|ROW(?:ID|NUM|TYPE)|SAMPLE|SB[124]|SEGMENT|SELF|SEPARATE|SEQUENCE|SHORT|SIZE(?:_T)?|SPARSE|SQL(?:CODE|DATA|NAME|STATE)|STANDARD|STATIC|STDDEV|STORED|STRING|STRUCT|STYLE|SUBMULTISET|SUBPARTITION|SUBSTITUTABLE|SUBTYPE|SUCCESSFUL|SYNONYM|SYSDATE|TABAUTH|TDO|THE|TIMEZONE_(?:ABBR|HOUR|MINUTE|REGION)|TRAILING|TRANSAC(?:TIONAL)?|TRUSTED|UB[124]|UID|UNDER|UNTRUSTED|VALIDATE|VALIST|VARCHAR2|VARIABLE|VARIANCE|VARRAY|VIEWS|VOID|WHENEVER|WRAPPED|ZONE)\b/i),Array.isArray(a=n.operator)||(a=n.operator=[a]),a.unshift(/:=/)}e.exports=i,i.displayName="plsql",i.aliases=[]},50256(e){"use strict";function t(e){e.languages.powerquery={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:\/\/).*)/,lookbehind:!0},"quoted-identifier":{pattern:/#"(?:[^"\r\n]|"")*"(?!")/,greedy:!0,alias:"variable"},string:{pattern:/"(?:[^"\r\n]|"")*"(?!")/,greedy:!0},constant:[/\bDay\.(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)\b/,/\bTraceLevel\.(?:Critical|Error|Information|Verbose|Warning)\b/,/\bOccurrence\.(?:First|Last|All)\b/,/\bOrder\.(?:Ascending|Descending)\b/,/\bRoundingMode\.(?:AwayFromZero|Down|ToEven|TowardZero|Up)\b/,/\bMissingField\.(?:Error|Ignore|UseNull)\b/,/\bQuoteStyle\.(?:Csv|None)\b/,/\bJoinKind\.(?:Inner|LeftOuter|RightOuter|FullOuter|LeftAnti|RightAnti)\b/,/\bGroupKind\.(?:Global|Local)\b/,/\bExtraValues\.(?:List|Ignore|Error)\b/,/\bJoinAlgorithm\.(?:Dynamic|PairwiseHash|SortMerge|LeftHash|RightHash|LeftIndex|RightIndex)\b/,/\bJoinSide\.(?:Left|Right)\b/,/\bPrecision\.(?:Double|Decimal)\b/,/\bRelativePosition\.From(?:End|Start)\b/,/\bTextEncoding\.(?:Ascii|BigEndianUnicode|Unicode|Utf8|Utf16|Windows)\b/,/\b(?:Any|Binary|Date|DateTime|DateTimeZone|Duration|Int8|Int16|Int32|Int64|Function|List|Logical|None|Number|Record|Table|Text|Time)\.Type\b/,/\bnull\b/],boolean:/\b(?:true|false)\b/,keyword:/\b(?:and|as|each|else|error|if|in|is|let|meta|not|nullable|optional|or|otherwise|section|shared|then|try|type)\b|#(?:binary|date|datetime|datetimezone|duration|infinity|nan|sections|shared|table|time)\b/,function:{pattern:/(^|[^#\w.])(?!\d)[\w.]+(?=\s*\()/,lookbehind:!0},"data-type":{pattern:/\b(?:any|anynonnull|binary|date|datetime|datetimezone|duration|function|list|logical|none|number|record|table|text|time|type)\b/,alias:"variable"},number:{pattern:/\b0x[\da-f]+\b|(?:[+-]?(?:\b\d+\.)?\b\d+|[+-]\.\d+|(^|[^.])\B\.\d+)(?:e[+-]?\d+)?\b/i,lookbehind:!0},operator:/[-+*\/&?@^]|<(?:=>?|>)?|>=?|=>?|\.\.\.?/,punctuation:/[,;\[\](){}]/},e.languages.pq=e.languages.powerquery,e.languages.mscript=e.languages.powerquery}e.exports=t,t.displayName="powerquery",t.aliases=[]},61777(e){"use strict";function t(e){var t,n,r;(r=(n=(t=e).languages.powershell={comment:[{pattern:/(^|[^`])<#[\s\S]*?#>/,lookbehind:!0},{pattern:/(^|[^`])#.*/,lookbehind:!0}],string:[{pattern:/"(?:`[\s\S]|[^`"])*"/,greedy:!0,inside:{function:{pattern:/(^|[^`])\$\((?:\$\([^\r\n()]*\)|(?!\$\()[^\r\n)])*\)/,lookbehind:!0,inside:{}}}},{pattern:/'(?:[^']|'')*'/,greedy:!0}],namespace:/\[[a-z](?:\[(?:\[[^\]]*\]|[^\[\]])*\]|[^\[\]])*\]/i,boolean:/\$(?:true|false)\b/i,variable:/\$\w+\b/,function:[/\b(?:Add|Approve|Assert|Backup|Block|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy|Debug|Deny|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|ForEach|Format|Get|Grant|Group|Hide|Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push|Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke|Save|Search|Select|Send|Set|Show|Skip|Sort|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Tee|Test|Trace|Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Where|Write)-[a-z]+\b/i,/\b(?:ac|cat|chdir|clc|cli|clp|clv|compare|copy|cp|cpi|cpp|cvpa|dbp|del|diff|dir|ebp|echo|epal|epcsv|epsn|erase|fc|fl|ft|fw|gal|gbp|gc|gci|gcs|gdr|gi|gl|gm|gp|gps|group|gsv|gu|gv|gwmi|iex|ii|ipal|ipcsv|ipsn|irm|iwmi|iwr|kill|lp|ls|measure|mi|mount|move|mp|mv|nal|ndr|ni|nv|ogv|popd|ps|pushd|pwd|rbp|rd|rdr|ren|ri|rm|rmdir|rni|rnp|rp|rv|rvpa|rwmi|sal|saps|sasv|sbp|sc|select|set|shcm|si|sl|sleep|sls|sort|sp|spps|spsv|start|sv|swmi|tee|trcm|type|write)\b/i],keyword:/\b(?:Begin|Break|Catch|Class|Continue|Data|Define|Do|DynamicParam|Else|ElseIf|End|Exit|Filter|Finally|For|ForEach|From|Function|If|InlineScript|Parallel|Param|Process|Return|Sequence|Switch|Throw|Trap|Try|Until|Using|Var|While|Workflow)\b/i,operator:{pattern:/(\W?)(?:!|-(?:eq|ne|gt|ge|lt|le|sh[lr]|not|b?(?:and|x?or)|(?:Not)?(?:Like|Match|Contains|In)|Replace|Join|is(?:Not)?|as)\b|-[-=]?|\+[+=]?|[*\/%]=?)/i,lookbehind:!0},punctuation:/[|{}[\];(),.]/}).string[0].inside).boolean=n.boolean,r.variable=n.variable,r.function.inside=n}e.exports=t,t.displayName="powershell",t.aliases=[]},3623(e){"use strict";function t(e){e.languages.processing=e.languages.extend("clike",{keyword:/\b(?:break|catch|case|class|continue|default|else|extends|final|for|if|implements|import|new|null|private|public|return|static|super|switch|this|try|void|while)\b/,operator:/<[<=]?|>[>=]?|&&?|\|\|?|[%?]|[!=+\-*\/]=?/}),e.languages.insertBefore("processing","number",{constant:/\b(?!XML\b)[A-Z][A-Z\d_]+\b/,type:{pattern:/\b(?:boolean|byte|char|color|double|float|int|[A-Z]\w*)\b/,alias:"variable"}}),e.languages.processing.function=/\b\w+(?=\s*\()/,e.languages.processing["class-name"].alias="variable"}e.exports=t,t.displayName="processing",t.aliases=[]},82707(e){"use strict";function t(e){e.languages.prolog={comment:[/%.+/,/\/\*[\s\S]*?\*\//],string:{pattern:/(["'])(?:\1\1|\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},builtin:/\b(?:fx|fy|xf[xy]?|yfx?)\b/,variable:/\b[A-Z_]\w*/,function:/\b[a-z]\w*(?:(?=\()|\/\d+)/,number:/\b\d+(?:\.\d*)?/,operator:/[:\\=><\-?*@\/;+^|!$.]+|\b(?:is|mod|not|xor)\b/,punctuation:/[(){}\[\],]/}}e.exports=t,t.displayName="prolog",t.aliases=[]},59338(e){"use strict";function t(e){var t,n,r;t=e,r=["sum","min","max","avg","group","stddev","stdvar","count","count_values","bottomk","topk","quantile"].concat(n=["on","ignoring","group_right","group_left","by","without"],["offset"]),t.languages.promql={comment:{pattern:/(^[ \t]*)#.*/m,lookbehind:!0},"vector-match":{pattern:RegExp("((?:"+n.join("|")+")\\s*)\\([^)]*\\)"),lookbehind:!0,inside:{"label-key":{pattern:/\b[^,]*\b/,alias:"attr-name"},punctuation:/[(),]/}},"context-labels":{pattern:/\{[^{}]*\}/,inside:{"label-key":{pattern:/\b[a-z_]\w*(?=\s*(?:=|![=~]))/,alias:"attr-name"},"label-value":{pattern:/(["'`])(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0,alias:"attr-value"},punctuation:/\{|\}|=~?|![=~]|,/}},"context-range":[{pattern:/\[[\w\s:]+\]/,inside:{punctuation:/\[|\]|:/,"range-duration":{pattern:/\b(?:\d+(?:[smhdwy]|ms))+\b/i,alias:"number"}}},{pattern:/(\boffset\s+)\w+/,lookbehind:!0,inside:{"range-duration":{pattern:/\b(?:\d+(?:[smhdwy]|ms))+\b/i,alias:"number"}}}],keyword:RegExp("\\b(?:"+r.join("|")+")\\b","i"),function:/\b[a-z_]\w*(?=\s*\()/i,number:/[-+]?(?:(?:\b\d+(?:\.\d+)?|\B\.\d+)(?:e[-+]?\d+)?\b|\b(?:0x[0-9a-f]+|nan|inf)\b)/i,operator:/[\^*/%+-]|==|!=|<=|<|>=|>|\b(?:and|unless|or)\b/i,punctuation:/[{};()`,.[\]]/}}e.exports=t,t.displayName="promql",t.aliases=[]},56267(e){"use strict";function t(e){e.languages.properties={comment:/^[ \t]*[#!].*$/m,"attr-value":{pattern:/(^[ \t]*(?:\\(?:\r\n|[\s\S])|[^\\\s:=])+(?: *[=:] *(?! )| ))(?:\\(?:\r\n|[\s\S])|[^\\\r\n])+/m,lookbehind:!0},"attr-name":/^[ \t]*(?:\\(?:\r\n|[\s\S])|[^\\\s:=])+(?= *[=:]| )/m,punctuation:/[=:]/}}e.exports=t,t.displayName="properties",t.aliases=[]},98809(e){"use strict";function t(e){var t,n;n=/\b(?:double|float|[su]?int(?:32|64)|s?fixed(?:32|64)|bool|string|bytes)\b/,(t=e).languages.protobuf=t.languages.extend("clike",{"class-name":[{pattern:/(\b(?:enum|extend|message|service)\s+)[A-Za-z_]\w*(?=\s*\{)/,lookbehind:!0},{pattern:/(\b(?:rpc\s+\w+|returns)\s*\(\s*(?:stream\s+)?)\.?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*(?=\s*\))/,lookbehind:!0}],keyword:/\b(?:enum|extend|extensions|import|message|oneof|option|optional|package|public|repeated|required|reserved|returns|rpc(?=\s+\w)|service|stream|syntax|to)\b(?!\s*=\s*\d)/,function:/\b[a-z_]\w*(?=\s*\()/i}),t.languages.insertBefore("protobuf","operator",{map:{pattern:/\bmap<\s*[\w.]+\s*,\s*[\w.]+\s*>(?=\s+[a-z_]\w*\s*[=;])/i,alias:"class-name",inside:{punctuation:/[<>.,]/,builtin:n}},builtin:n,"positional-class-name":{pattern:/(?:\b|\B\.)[a-z_]\w*(?:\.[a-z_]\w*)*(?=\s+[a-z_]\w*\s*[=;])/i,alias:"class-name",inside:{punctuation:/\./}},annotation:{pattern:/(\[\s*)[a-z_]\w*(?=\s*=)/i,lookbehind:!0}})}e.exports=t,t.displayName="protobuf",t.aliases=[]},37548(e){"use strict";function t(e){e.languages.psl={comment:{pattern:/#.*/,greedy:!0},string:{pattern:/"(?:\\.|[^\\"])*"/,greedy:!0,inside:{symbol:/\\[ntrbA-Z"\\]/}},"heredoc-string":{pattern:/<<<([a-zA-Z_]\w*)[\r\n](?:.*[\r\n])*?\1\b/,alias:"string",greedy:!0},keyword:/\b(?:__multi|__single|case|default|do|else|elsif|exit|export|for|foreach|function|if|last|line|local|next|requires|return|switch|until|while|word)\b/,constant:/\b(?:ALARM|CHART_ADD_GRAPH|CHART_DELETE_GRAPH|CHART_DESTROY|CHART_LOAD|CHART_PRINT|EOF|FALSE|False|false|NO|No|no|OFFLINE|OK|PSL_PROF_LOG|R_CHECK_HORIZ|R_CHECK_VERT|R_CLICKER|R_COLUMN|R_FRAME|R_ICON|R_LABEL|R_LABEL_CENTER|R_LIST_MULTIPLE|R_LIST_MULTIPLE_ND|R_LIST_SINGLE|R_LIST_SINGLE_ND|R_MENU|R_POPUP|R_POPUP_SCROLLED|R_RADIO_HORIZ|R_RADIO_VERT|R_ROW|R_SCALE_HORIZ|R_SCALE_VERT|R_SPINNER|R_TEXT_FIELD|R_TEXT_FIELD_LABEL|R_TOGGLE|TRIM_LEADING|TRIM_LEADING_AND_TRAILING|TRIM_REDUNDANT|TRIM_TRAILING|TRUE|True|true|VOID|WARN)\b/,variable:/\b(?:errno|exit_status|PslDebug)\b/,builtin:{pattern:/\b(?:acos|add_diary|annotate|annotate_get|asctime|asin|atan|atexit|ascii_to_ebcdic|batch_set|blackout|cat|ceil|chan_exists|change_state|close|code_cvt|cond_signal|cond_wait|console_type|convert_base|convert_date|convert_locale_date|cos|cosh|create|destroy_lock|dump_hist|date|destroy|difference|dget_text|dcget_text|ebcdic_to_ascii|encrypt|event_archive|event_catalog_get|event_check|event_query|event_range_manage|event_range_query|event_report|event_schedule|event_trigger|event_trigger2|execute|exists|exp|fabs|floor|fmod|full_discovery|file|fopen|ftell|fseek|grep|get_vars|getenv|get|get_chan_info|get_ranges|get_text|gethostinfo|getpid|getpname|history_get_retention|history|index|int|is_var|intersection|isnumber|internal|in_transition|join|kill|length|lines|lock|lock_info|log|loge|log10|matchline|msg_check|msg_get_format|msg_get_severity|msg_printf|msg_sprintf|ntharg|num_consoles|nthargf|nthline|nthlinef|num_bytes|print|proc_exists|process|popen|printf|pconfig|poplines|pow|PslExecute|PslFunctionCall|PslFunctionExists|PslSetOptions|random|read|readln|refresh_parameters|remote_check|remote_close|remote_event_query|remote_event_trigger|remote_file_send|remote_open|remove|replace|rindex|sec_check_priv|sec_store_get|sec_store_set|set_alarm_ranges|set_locale|share|sin|sinh|sleep|sopen|sqrt|srandom|subset|set|substr|system|sprintf|sort|snmp_agent_config|_snmp_debug|snmp_agent_stop|snmp_agent_start|snmp_h_set|snmp_h_get_next|snmp_h_get|snmp_set|snmp_walk|snmp_get_next|snmp_get|snmp_config|snmp_close|snmp_open|snmp_trap_receive|snmp_trap_ignore|snmp_trap_listen|snmp_trap_send|snmp_trap_raise_std_trap|snmp_trap_register_im|splitline|strcasecmp|str_repeat|trim|tail|tan|tanh|time|tmpnam|tolower|toupper|trace_psl_process|text_domain|unlock|unique|union|unset|va_arg|va_start|write)\b/,alias:"builtin-function"},"foreach-variable":{pattern:/(\bforeach\s+(?:(?:\w+\b|"(?:\\.|[^\\"])*")\s+){0,2})[_a-zA-Z]\w*(?=\s*\()/,lookbehind:!0,greedy:!0},function:{pattern:/\b[_a-z]\w*\b(?=\s*\()/i},number:/\b(?:0x[0-9a-f]+|[0-9]+(?:\.[0-9]+)?)\b/i,operator:/--|\+\+|&&=?|\|\|=?|<<=?|>>=?|[=!]~|[-+*/%&|^!=<>]=?|\.|[:?]/,punctuation:/[(){}\[\];,]/}}e.exports=t,t.displayName="psl",t.aliases=[]},82161(e){"use strict";function t(e){!function(e){e.languages.pug={comment:{pattern:/(^([\t ]*))\/\/.*(?:(?:\r?\n|\r)\2[\t ].+)*/m,lookbehind:!0},"multiline-script":{pattern:/(^([\t ]*)script\b.*\.[\t ]*)(?:(?:\r?\n|\r(?!\n))(?:\2[\t ].+|\s*?(?=\r?\n|\r)))+/m,lookbehind:!0,inside:e.languages.javascript},filter:{pattern:/(^([\t ]*)):.+(?:(?:\r?\n|\r(?!\n))(?:\2[\t ].+|\s*?(?=\r?\n|\r)))+/m,lookbehind:!0,inside:{"filter-name":{pattern:/^:[\w-]+/,alias:"variable"}}},"multiline-plain-text":{pattern:/(^([\t ]*)[\w\-#.]+\.[\t ]*)(?:(?:\r?\n|\r(?!\n))(?:\2[\t ].+|\s*?(?=\r?\n|\r)))+/m,lookbehind:!0},markup:{pattern:/(^[\t ]*)<.+/m,lookbehind:!0,inside:e.languages.markup},doctype:{pattern:/((?:^|\n)[\t ]*)doctype(?: .+)?/,lookbehind:!0},"flow-control":{pattern:/(^[\t ]*)(?:if|unless|else|case|when|default|each|while)\b(?: .+)?/m,lookbehind:!0,inside:{each:{pattern:/^each .+? in\b/,inside:{keyword:/\b(?:each|in)\b/,punctuation:/,/}},branch:{pattern:/^(?:if|unless|else|case|when|default|while)\b/,alias:"keyword"},rest:e.languages.javascript}},keyword:{pattern:/(^[\t ]*)(?:block|extends|include|append|prepend)\b.+/m,lookbehind:!0},mixin:[{pattern:/(^[\t ]*)mixin .+/m,lookbehind:!0,inside:{keyword:/^mixin/,function:/\w+(?=\s*\(|\s*$)/,punctuation:/[(),.]/}},{pattern:/(^[\t ]*)\+.+/m,lookbehind:!0,inside:{name:{pattern:/^\+\w+/,alias:"function"},rest:e.languages.javascript}}],script:{pattern:/(^[\t ]*script(?:(?:&[^(]+)?\([^)]+\))*[\t ]).+/m,lookbehind:!0,inside:e.languages.javascript},"plain-text":{pattern:/(^[\t ]*(?!-)[\w\-#.]*[\w\-](?:(?:&[^(]+)?\([^)]+\))*\/?[\t ]).+/m,lookbehind:!0},tag:{pattern:/(^[\t ]*)(?!-)[\w\-#.]*[\w\-](?:(?:&[^(]+)?\([^)]+\))*\/?:?/m,lookbehind:!0,inside:{attributes:[{pattern:/&[^(]+\([^)]+\)/,inside:e.languages.javascript},{pattern:/\([^)]+\)/,inside:{"attr-value":{pattern:/(=\s*(?!\s))(?:\{[^}]*\}|[^,)\r\n]+)/,lookbehind:!0,inside:e.languages.javascript},"attr-name":/[\w-]+(?=\s*!?=|\s*[,)])/,punctuation:/[!=(),]+/}}],punctuation:/:/,"attr-id":/#[\w\-]+/,"attr-class":/\.[\w\-]+/}},code:[{pattern:/(^[\t ]*(?:-|!?=)).+/m,lookbehind:!0,inside:e.languages.javascript}],punctuation:/[.\-!=|]+/};for(var t=/(^([\t ]*)):(?:(?:\r?\n|\r(?!\n))(?:\2[\t ].+|\s*?(?=\r?\n|\r)))+/.source,n=[{filter:"atpl",language:"twig"},{filter:"coffee",language:"coffeescript"},"ejs","handlebars","less","livescript","markdown",{filter:"sass",language:"scss"},"stylus"],r={},i=0,a=n.length;i",function(){return o.filter}),"m"),lookbehind:!0,inside:{"filter-name":{pattern:/^:[\w-]+/,alias:"variable"},rest:e.languages[o.language]}})}e.languages.insertBefore("pug","filter",r)}(e)}e.exports=t,t.displayName="pug",t.aliases=[]},80625(e){"use strict";function t(e){var t,n;(t=e).languages.puppet={heredoc:[{pattern:/(@\("([^"\r\n\/):]+)"(?:\/[nrts$uL]*)?\).*(?:\r?\n|\r))(?:.*(?:\r?\n|\r(?!\n)))*?[ \t]*(?:\|[ \t]*)?(?:-[ \t]*)?\2/,lookbehind:!0,alias:"string",inside:{punctuation:/(?=\S).*\S(?= *$)/}},{pattern:/(@\(([^"\r\n\/):]+)(?:\/[nrts$uL]*)?\).*(?:\r?\n|\r))(?:.*(?:\r?\n|\r(?!\n)))*?[ \t]*(?:\|[ \t]*)?(?:-[ \t]*)?\2/,lookbehind:!0,greedy:!0,alias:"string",inside:{punctuation:/(?=\S).*\S(?= *$)/}},{pattern:/@\("?(?:[^"\r\n\/):]+)"?(?:\/[nrts$uL]*)?\)/,alias:"string",inside:{punctuation:{pattern:/(\().+?(?=\))/,lookbehind:!0}}}],"multiline-comment":{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0,greedy:!0,alias:"comment"},regex:{pattern:/((?:\bnode\s+|[~=\(\[\{,]\s*|[=+]>\s*|^\s*))\/(?:[^\/\\]|\\[\s\S])+\/(?:[imx]+\b|\B)/,lookbehind:!0,greedy:!0,inside:{"extended-regex":{pattern:/^\/(?:[^\/\\]|\\[\s\S])+\/[im]*x[im]*$/,inside:{comment:/#.*/}}}},comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},string:{pattern:/(["'])(?:\$\{(?:[^'"}]|(["'])(?:(?!\2)[^\\]|\\[\s\S])*\2)+\}|\$(?!\{)|(?!\1)[^\\$]|\\[\s\S])*\1/,greedy:!0,inside:{"double-quoted":{pattern:/^"[\s\S]*"$/,inside:{}}}},variable:{pattern:/\$(?:::)?\w+(?:::\w+)*/,inside:{punctuation:/::/}},"attr-name":/(?:\b\w+|\*)(?=\s*=>)/,function:[{pattern:/(\.)(?!\d)\w+/,lookbehind:!0},/\b(?:contain|debug|err|fail|include|info|notice|realize|require|tag|warning)\b|\b(?!\d)\w+(?=\()/],number:/\b(?:0x[a-f\d]+|\d+(?:\.\d+)?(?:e-?\d+)?)\b/i,boolean:/\b(?:true|false)\b/,keyword:/\b(?:application|attr|case|class|consumes|default|define|else|elsif|function|if|import|inherits|node|private|produces|type|undef|unless)\b/,datatype:{pattern:/\b(?:Any|Array|Boolean|Callable|Catalogentry|Class|Collection|Data|Default|Enum|Float|Hash|Integer|NotUndef|Numeric|Optional|Pattern|Regexp|Resource|Runtime|Scalar|String|Struct|Tuple|Type|Undef|Variant)\b/,alias:"symbol"},operator:/=[=~>]?|![=~]?|<(?:<\|?|[=~|-])?|>[>=]?|->?|~>|\|>?>?|[*\/%+?]|\b(?:and|in|or)\b/,punctuation:/[\[\]{}().,;]|:+/},n=[{pattern:/(^|[^\\])\$\{(?:[^'"{}]|\{[^}]*\}|(["'])(?:(?!\2)[^\\]|\\[\s\S])*\2)+\}/,lookbehind:!0,inside:{"short-variable":{pattern:/(^\$\{)(?!\w+\()(?:::)?\w+(?:::\w+)*/,lookbehind:!0,alias:"variable",inside:{punctuation:/::/}},delimiter:{pattern:/^\$/,alias:"variable"},rest:t.languages.puppet}},{pattern:/(^|[^\\])\$(?:::)?\w+(?:::\w+)*/,lookbehind:!0,alias:"variable",inside:{punctuation:/::/}}],t.languages.puppet.heredoc[0].inside.interpolation=n,t.languages.puppet.string.inside["double-quoted"].inside.interpolation=n}e.exports=t,t.displayName="puppet",t.aliases=[]},88393(e){"use strict";function t(e){var t,n,r;(t=e).languages.pure={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0},/#!.+/],"inline-lang":{pattern:/%<[\s\S]+?%>/,greedy:!0,inside:{lang:{pattern:/(^%< *)-\*-.+?-\*-/,lookbehind:!0,alias:"comment"},delimiter:{pattern:/^%<.*|%>$/,alias:"punctuation"}}},string:{pattern:/"(?:\\.|[^"\\\r\n])*"/,greedy:!0},number:{pattern:/((?:\.\.)?)(?:\b(?:inf|nan)\b|\b0x[\da-f]+|(?:\b(?:0b)?\d+(?:\.\d+)?|\B\.\d+)(?:e[+-]?\d+)?L?)/i,lookbehind:!0},keyword:/\b(?:ans|break|bt|case|catch|cd|clear|const|def|del|dump|else|end|exit|extern|false|force|help|if|infix[lr]?|interface|let|ls|mem|namespace|nonfix|NULL|of|otherwise|outfix|override|postfix|prefix|private|public|pwd|quit|run|save|show|stats|then|throw|trace|true|type|underride|using|when|with)\b/,function:/\b(?:abs|add_(?:(?:fundef|interface|macdef|typedef)(?:_at)?|addr|constdef|vardef)|all|any|applp?|arity|bigintp?|blob(?:_crc|_size|p)?|boolp?|byte_(?:matrix|pointer)|byte_c?string(?:_pointer)?|calloc|cat|catmap|ceil|char[ps]?|check_ptrtag|chr|clear_sentry|clearsym|closurep?|cmatrixp?|cols?|colcat(?:map)?|colmap|colrev|colvector(?:p|seq)?|complex(?:_float_(?:matrix|pointer)|_matrix(?:_view)?|_pointer|p)?|conj|cookedp?|cst|cstring(?:_(?:dup|list|vector))?|curry3?|cyclen?|del_(?:constdef|fundef|interface|macdef|typedef|vardef)|delete|diag(?:mat)?|dim|dmatrixp?|do|double(?:_matrix(?:_view)?|_pointer|p)?|dowith3?|drop|dropwhile|eval(?:cmd)?|exactp|filter|fix|fixity|flip|float(?:_matrix|_pointer)|floor|fold[lr]1?|frac|free|funp?|functionp?|gcd|get(?:_(?:byte|constdef|double|float|fundef|int(?:64)?|interface(?:_typedef)?|long|macdef|pointer|ptrtag|short|sentry|string|typedef|vardef))?|globsym|hash|head|id|im|imatrixp?|index|inexactp|infp|init|insert|int(?:_matrix(?:_view)?|_pointer|p)?|int64_(?:matrix|pointer)|integerp?|iteraten?|iterwhile|join|keys?|lambdap?|last(?:err(?:pos)?)?|lcd|list[2p]?|listmap|make_ptrtag|malloc|map|matcat|matrixp?|max|member|min|nanp|nargs|nmatrixp?|null|numberp?|ord|pack(?:ed)?|pointer(?:_cast|_tag|_type|p)?|pow|pred|ptrtag|put(?:_(?:byte|double|float|int(?:64)?|long|pointer|short|string))?|rationalp?|re|realp?|realloc|recordp?|redim|reduce(?:_with)?|refp?|repeatn?|reverse|rlistp?|round|rows?|rowcat(?:map)?|rowmap|rowrev|rowvector(?:p|seq)?|same|scan[lr]1?|sentry|sgn|short_(?:matrix|pointer)|slice|smatrixp?|sort|split|str|strcat|stream|stride|string(?:_(?:dup|list|vector)|p)?|subdiag(?:mat)?|submat|subseq2?|substr|succ|supdiag(?:mat)?|symbolp?|tail|take|takewhile|thunkp?|transpose|trunc|tuplep?|typep|ubyte|uint(?:64)?|ulong|uncurry3?|unref|unzip3?|update|ushort|vals?|varp?|vector(?:p|seq)?|void|zip3?|zipwith3?)\b/,special:{pattern:/\b__[a-z]+__\b/i,alias:"builtin"},operator:/(?:[!"#$%&'*+,\-.\/:<=>?@\\^`|~\u00a1-\u00bf\u00d7-\u00f7\u20d0-\u2bff]|\b_+\b)+|\b(?:and|div|mod|not|or)\b/,punctuation:/[(){}\[\];,|]/},r=/%< *-\*- *\d* *-\*-[\s\S]+?%>/.source,(n=["c",{lang:"c++",alias:"cpp"},"fortran"]).forEach(function(e){var n=e;if("string"!=typeof e&&(n=e.alias,e=e.lang),t.languages[n]){var i={};i["inline-lang-"+n]={pattern:RegExp(r.replace("",e.replace(/([.+*?\/\\(){}\[\]])/g,"\\$1")),"i"),inside:t.util.clone(t.languages.pure["inline-lang"].inside)},i["inline-lang-"+n].inside.rest=t.util.clone(t.languages[n]),t.languages.insertBefore("pure","inline-lang",i)}}),t.languages.c&&(t.languages.pure["inline-lang"].inside.rest=t.util.clone(t.languages.c))}e.exports=t,t.displayName="pure",t.aliases=[]},78404(e){"use strict";function t(e){e.languages.purebasic=e.languages.extend("clike",{comment:/;.*/,keyword:/\b(?:declarecdll|declaredll|compilerselect|compilercase|compilerdefault|compilerendselect|compilererror|enableexplicit|disableexplicit|not|and|or|xor|calldebugger|debuglevel|enabledebugger|disabledebugger|restore|read|includepath|includebinary|threaded|runtime|with|endwith|structureunion|endstructureunion|align|newlist|newmap|interface|endinterface|extends|enumeration|endenumeration|swap|foreach|continue|fakereturn|goto|gosub|return|break|module|endmodule|declaremodule|enddeclaremodule|declare|declarec|prototype|prototypec|enableasm|disableasm|dim|redim|data|datasection|enddatasection|to|procedurereturn|debug|default|case|select|endselect|as|import|endimport|importc|compilerif|compilerelse|compilerendif|compilerelseif|end|structure|endstructure|while|wend|for|next|step|if|else|elseif|endif|repeat|until|procedure|proceduredll|procedurec|procedurecdll|endprocedure|protected|shared|static|global|define|includefile|xincludefile|macro|endmacro)\b/i,function:/\b\w+(?:\.\w+)?\s*(?=\()/,number:/(?:\$[\da-f]+|\b-?(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?)\b/i,operator:/(?:@\*?|\?|\*)\w+|-[>-]?|\+\+?|!=?|<>?=?|==?|&&?|\|?\||[~^%?*/@]/}),e.languages.insertBefore("purebasic","keyword",{tag:/#\w+/,asm:{pattern:/(^[\t ]*)!.*/m,lookbehind:!0,alias:"tag",inside:{comment:/;.*/,string:{pattern:/(["'`])(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},"label-reference-anonymous":{pattern:/(!\s*j[a-z]+\s+)@[fb]/i,lookbehind:!0,alias:"fasm-label"},"label-reference-addressed":{pattern:/(!\s*j[a-z]+\s+)[A-Z._?$@][\w.?$@~#]*/i,lookbehind:!0,alias:"fasm-label"},function:{pattern:/^([\t ]*!\s*)[\da-z]+(?=\s|$)/im,lookbehind:!0},"function-inline":{pattern:/(:\s*)[\da-z]+(?=\s)/i,lookbehind:!0,alias:"function"},label:{pattern:/^([\t ]*!\s*)[A-Za-z._?$@][\w.?$@~#]*(?=:)/m,lookbehind:!0,alias:"fasm-label"},keyword:[/\b(?:extern|global)\b[^;\r\n]*/i,/\b(?:CPU|FLOAT|DEFAULT)\b.*/],register:/\b(?:st\d|[xyz]mm\d\d?|[cdt]r\d|r\d\d?[bwd]?|[er]?[abcd]x|[abcd][hl]|[er]?(?:bp|sp|si|di)|[cdefgs]s|mm\d+)\b/i,number:/(?:\b|-|(?=\$))(?:0[hx](?:[\da-f]*\.)?[\da-f]+(?:p[+-]?\d+)?|\d[\da-f]+[hx]|\$\d[\da-f]*|0[oq][0-7]+|[0-7]+[oq]|0[by][01]+|[01]+[by]|0[dt]\d+|(?:\d+(?:\.\d+)?|\.\d+)(?:\.?e[+-]?\d+)?[dt]?)\b/i,operator:/[\[\]*+\-/%<>=&|$!,.:]/}}}),delete e.languages.purebasic["class-name"],delete e.languages.purebasic.boolean,e.languages.pbfasm=e.languages.purebasic}e.exports=t,t.displayName="purebasic",t.aliases=[]},92923(e,t,n){"use strict";var r=n(58090);function i(e){e.register(r),e.languages.purescript=e.languages.extend("haskell",{keyword:/\b(?:ado|case|class|data|derive|do|else|forall|if|in|infixl|infixr|instance|let|module|newtype|of|primitive|then|type|where)\b/,"import-statement":{pattern:/(^[\t ]*)import\s+[A-Z][\w']*(?:\.[A-Z][\w']*)*(?:\s+as\s+[A-Z][\w']*(?:\.[A-Z][\w']*)*)?(?:\s+hiding\b)?/m,lookbehind:!0,inside:{keyword:/\b(?:import|as|hiding)\b/}},builtin:/\b(?:absurd|add|ap|append|apply|between|bind|bottom|clamp|compare|comparing|compose|conj|const|degree|discard|disj|div|eq|flap|flip|gcd|identity|ifM|join|lcm|liftA1|liftM1|map|max|mempty|min|mod|mul|negate|not|notEq|one|otherwise|recip|show|sub|top|unit|unless|unlessM|void|when|whenM|zero)\b/}),e.languages.purs=e.languages.purescript}e.exports=i,i.displayName="purescript",i.aliases=["purs"]},52992(e){"use strict";function t(e){e.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},"string-interpolation":{pattern:/(?:f|rf|fr)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|rb|br)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/im,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:True|False|None)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},e.languages.python["string-interpolation"].inside.interpolation.inside.rest=e.languages.python,e.languages.py=e.languages.python}e.exports=t,t.displayName="python",t.aliases=["py"]},55762(e){"use strict";function t(e){e.languages.q={string:/"(?:\\.|[^"\\\r\n])*"/,comment:[{pattern:/([\t )\]}])\/.*/,lookbehind:!0,greedy:!0},{pattern:/(^|\r?\n|\r)\/[\t ]*(?:(?:\r?\n|\r)(?:.*(?:\r?\n|\r(?!\n)))*?(?:\\(?=[\t ]*(?:\r?\n|\r))|$)|\S.*)/,lookbehind:!0,greedy:!0},{pattern:/^\\[\t ]*(?:\r?\n|\r)[\s\S]+/m,greedy:!0},{pattern:/^#!.+/m,greedy:!0}],symbol:/`(?::\S+|[\w.]*)/,datetime:{pattern:/0N[mdzuvt]|0W[dtz]|\d{4}\.\d\d(?:m|\.\d\d(?:T(?:\d\d(?::\d\d(?::\d\d(?:[.:]\d\d\d)?)?)?)?)?[dz]?)|\d\d:\d\d(?::\d\d(?:[.:]\d\d\d)?)?[uvt]?/,alias:"number"},number:/\b(?![01]:)(?:0[wn]|0W[hj]?|0N[hje]?|0x[\da-fA-F]+|\d+(?:\.\d*)?(?:e[+-]?\d+)?[hjfeb]?)/,keyword:/\\\w+\b|\b(?:abs|acos|aj0?|all|and|any|asc|asin|asof|atan|attr|avgs?|binr?|by|ceiling|cols|cor|cos|count|cov|cross|csv|cut|delete|deltas|desc|dev|differ|distinct|div|do|dsave|ej|enlist|eval|except|exec|exit|exp|fby|fills|first|fkeys|flip|floor|from|get|getenv|group|gtime|hclose|hcount|hdel|hopen|hsym|iasc|identity|idesc|if|ij|in|insert|inter|inv|keys?|last|like|list|ljf?|load|log|lower|lsq|ltime|ltrim|mavg|maxs?|mcount|md5|mdev|med|meta|mins?|mmax|mmin|mmu|mod|msum|neg|next|not|null|or|over|parse|peach|pj|plist|prds?|prev|prior|rand|rank|ratios|raze|read0|read1|reciprocal|reval|reverse|rload|rotate|rsave|rtrim|save|scan|scov|sdev|select|set|setenv|show|signum|sin|sqrt|ssr?|string|sublist|sums?|sv|svar|system|tables|tan|til|trim|txf|type|uj|ungroup|union|update|upper|upsert|value|var|views?|vs|wavg|where|while|within|wj1?|wsum|ww|xasc|xbar|xcols?|xdesc|xexp|xgroup|xkey|xlog|xprev|xrank)\b/,adverb:{pattern:/['\/\\]:?|\beach\b/,alias:"function"},verb:{pattern:/(?:\B\.\B|\b[01]:|<[=>]?|>=?|[:+\-*%,!?~=|$&#@^]):?|\b_\b:?/,alias:"operator"},punctuation:/[(){}\[\];.]/}}e.exports=t,t.displayName="q",t.aliases=[]},4137(e){"use strict";function t(e){!function(e){for(var t=/"(?:\\.|[^\\"\r\n])*"|'(?:\\.|[^\\'\r\n])*'/.source,n=/\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))*\*\//.source,r=/(?:[^\\()[\]{}"'/]||\/(?![*/])||\(*\)|\[*\]|\{*\}|\\[\s\S])/.source.replace(//g,function(){return t}).replace(//g,function(){return n}),i=0;i<2;i++)r=r.replace(//g,function(){return r});r=r.replace(//g,"[^\\s\\S]"),e.languages.qml={comment:{pattern:/\/\/.*|\/\*[\s\S]*?\*\//,greedy:!0},"javascript-function":{pattern:RegExp(/((?:^|;)[ \t]*)function\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*\(*\)\s*\{*\}/.source.replace(//g,function(){return r}),"m"),lookbehind:!0,greedy:!0,alias:"language-javascript",inside:e.languages.javascript},"class-name":{pattern:/((?:^|[:;])[ \t]*)(?!\d)\w+(?=[ \t]*\{|[ \t]+on\b)/m,lookbehind:!0},property:[{pattern:/((?:^|[;{])[ \t]*)(?!\d)\w+(?:\.\w+)*(?=[ \t]*:)/m,lookbehind:!0},{pattern:/((?:^|[;{])[ \t]*)property[ \t]+(?!\d)\w+(?:\.\w+)*[ \t]+(?!\d)\w+(?:\.\w+)*(?=[ \t]*:)/m,lookbehind:!0,inside:{keyword:/^property/,property:/\w+(?:\.\w+)*/}}],"javascript-expression":{pattern:RegExp(/(:[ \t]*)(?![\s;}[])(?:(?!$|[;}]))+/.source.replace(//g,function(){return r}),"m"),lookbehind:!0,greedy:!0,alias:"language-javascript",inside:e.languages.javascript},string:/"(?:\\.|[^\\"\r\n])*"/,keyword:/\b(?:as|import|on)\b/,punctuation:/[{}[\]:;,]/}}(e)}e.exports=t,t.displayName="qml",t.aliases=[]},28260(e){"use strict";function t(e){e.languages.qore=e.languages.extend("clike",{comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:\/\/|#).*)/,lookbehind:!0},string:{pattern:/("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0},keyword:/\b(?:abstract|any|assert|binary|bool|boolean|break|byte|case|catch|char|class|code|const|continue|data|default|do|double|else|enum|extends|final|finally|float|for|goto|hash|if|implements|import|inherits|instanceof|int|interface|long|my|native|new|nothing|null|object|our|own|private|reference|rethrow|return|short|soft(?:int|float|number|bool|string|date|list)|static|strictfp|string|sub|super|switch|synchronized|this|throw|throws|transient|try|void|volatile|while)\b/,boolean:/\b(?:true|false)\b/i,function:/\$?\b(?!\d)\w+(?=\()/,number:/\b(?:0b[01]+|0x(?:[\da-f]*\.)?[\da-fp\-]+|(?:\d+(?:\.\d+)?|\.\d+)(?:e\d+)?[df]|(?:\d+(?:\.\d+)?|\.\d+))\b/i,operator:{pattern:/(^|[^.])(?:\+[+=]?|-[-=]?|[!=](?:==?|~)?|>>?=?|<(?:=>?|<=?)?|&[&=]?|\|[|=]?|[*\/%^]=?|[~?])/,lookbehind:!0},variable:/\$(?!\d)\w+\b/})}e.exports=t,t.displayName="qore",t.aliases=[]},71360(e){"use strict";function t(e){!function(e){function t(e,t){return e.replace(/<<(\d+)>>/g,function(e,n){return"(?:"+t[+n]+")"})}function n(e,n,r){return RegExp(t(e,n),r||"")}function r(e,t){for(var n=0;n>/g,function(){return"(?:"+e+")"});return e.replace(/<>/g,"[^\\s\\S]")}var i={type:"Adj BigInt Bool Ctl Double false Int One Pauli PauliI PauliX PauliY PauliZ Qubit Range Result String true Unit Zero",other:"Adjoint adjoint apply as auto body borrow borrowing Controlled controlled distribute elif else fail fixup for function if in internal intrinsic invert is let mutable namespace new newtype open operation repeat return self set until use using while within"};function a(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var o=RegExp(a(i.type+" "+i.other)),s=/\b[A-Za-z_]\w*\b/.source,u=t(/<<0>>(?:\s*\.\s*<<0>>)*/.source,[s]),c={keyword:o,punctuation:/[<>()?,.:[\]]/},l=/"(?:\\.|[^\\"])*"/.source;e.languages.qsharp=e.languages.extend("clike",{comment:/\/\/.*/,string:[{pattern:n(/(^|[^$\\])<<0>>/.source,[l]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:n(/(\b(?:as|open)\s+)<<0>>(?=\s*(?:;|as\b))/.source,[u]),lookbehind:!0,inside:c},{pattern:n(/(\bnamespace\s+)<<0>>(?=\s*\{)/.source,[u]),lookbehind:!0,inside:c}],keyword:o,number:/(?:\b0(?:x[\da-f]+|b[01]+|o[0-7]+)|(?:\B\.\d+|\b\d+(?:\.\d*)?)(?:e[-+]?\d+)?)l?\b/i,operator:/\band=|\bor=|\band\b|\bor\b|\bnot\b|<[-=]|[-=]>|>>>=?|<<<=?|\^\^\^=?|\|\|\|=?|&&&=?|w\/=?|~~~|[*\/+\-^=!%]=?/,punctuation:/::|[{}[\];(),.:]/}),e.languages.insertBefore("qsharp","number",{range:{pattern:/\.\./,alias:"operator"}});var f=r(t(/\{(?:[^"{}]|<<0>>|<>)*\}/.source,[l]),2);e.languages.insertBefore("qsharp","string",{"interpolation-string":{pattern:n(/\$"(?:\\.|<<0>>|[^\\"{])*"/.source,[f]),greedy:!0,inside:{interpolation:{pattern:n(/((?:^|[^\\])(?:\\\\)*)<<0>>/.source,[f]),lookbehind:!0,inside:{punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-qsharp",inside:e.languages.qsharp}}},string:/[\s\S]+/}}})}(e),e.languages.qs=e.languages.qsharp}e.exports=t,t.displayName="qsharp",t.aliases=["qs"]},29308(e){"use strict";function t(e){e.languages.r={comment:/#.*/,string:{pattern:/(['"])(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},"percent-operator":{pattern:/%[^%\s]*%/,alias:"operator"},boolean:/\b(?:TRUE|FALSE)\b/,ellipsis:/\.\.(?:\.|\d+)/,number:[/\b(?:NaN|Inf)\b/,/(?:\b0x[\dA-Fa-f]+(?:\.\d*)?|\b\d+(?:\.\d*)?|\B\.\d+)(?:[EePp][+-]?\d+)?[iL]?/],keyword:/\b(?:if|else|repeat|while|function|for|in|next|break|NULL|NA|NA_integer_|NA_real_|NA_complex_|NA_character_)\b/,operator:/->?>?|<(?:=|=!]=?|::?|&&?|\|\|?|[+*\/^$@~]/,punctuation:/[(){}\[\],;]/}}e.exports=t,t.displayName="r",t.aliases=[]},32168(e,t,n){"use strict";var r=n(9997);function i(e){e.register(r),e.languages.racket=e.languages.extend("scheme",{"lambda-parameter":{pattern:/([(\[]lambda\s+[(\[])[^()\[\]'\s]+/,lookbehind:!0}}),e.languages.insertBefore("racket","string",{lang:{pattern:/^#lang.+/m,greedy:!0,alias:"keyword"}}),e.languages.rkt=e.languages.racket}e.exports=i,i.displayName="racket",i.aliases=["rkt"]},5755(e){"use strict";function t(e){e.languages.reason=e.languages.extend("clike",{string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},"class-name":/\b[A-Z]\w*/,keyword:/\b(?:and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|method|module|mutable|new|nonrec|object|of|open|or|private|rec|sig|struct|switch|then|to|try|type|val|virtual|when|while|with)\b/,operator:/\.{3}|:[:=]|\|>|->|=(?:==?|>)?|<=?|>=?|[|^?'#!~`]|[+\-*\/]\.?|\b(?:mod|land|lor|lxor|lsl|lsr|asr)\b/}),e.languages.insertBefore("reason","class-name",{character:{pattern:/'(?:\\x[\da-f]{2}|\\o[0-3][0-7][0-7]|\\\d{3}|\\.|[^'\\\r\n])'/,alias:"string"},constructor:{pattern:/\b[A-Z]\w*\b(?!\s*\.)/,alias:"variable"},label:{pattern:/\b[a-z]\w*(?=::)/,alias:"symbol"}}),delete e.languages.reason.function}e.exports=t,t.displayName="reason",t.aliases=[]},54105(e){"use strict";function t(e){var t,n,r,i,a,o,s,u;t=e,n={pattern:/\\[\\(){}[\]^$+*?|.]/,alias:"escape"},i={pattern:/\.|\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},a={pattern:/\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},s=RegExp((o="(?:[^\\\\-]|"+(r=/\\(?:x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]+\}|c[a-zA-Z]|0[0-7]{0,2}|[123][0-7]{2}|.)/).source+")")+"-"+o),u={pattern:/(<|')[^<>']+(?=[>']$)/,lookbehind:!0,alias:"variable"},t.languages.regex={charset:{pattern:/((?:^|[^\\])(?:\\\\)*)\[(?:[^\\\]]|\\[\s\S])*\]/,lookbehind:!0,inside:{"charset-negation":{pattern:/(^\[)\^/,lookbehind:!0,alias:"operator"},"charset-punctuation":{pattern:/^\[|\]$/,alias:"punctuation"},range:{pattern:s,inside:{escape:r,"range-punctuation":{pattern:/-/,alias:"operator"}}},"special-escape":n,charclass:a,escape:r}},"special-escape":n,charclass:i,backreference:[{pattern:/\\(?![123][0-7]{2})[1-9]/,alias:"keyword"},{pattern:/\\k<[^<>']+>/,alias:"keyword",inside:{"group-name":u}}],anchor:{pattern:/[$^]|\\[ABbGZz]/,alias:"function"},escape:r,group:[{pattern:/\((?:\?(?:<[^<>']+>|'[^<>']+'|[>:]|:=]=?|!=|\b_\b/,punctuation:/[,;.\[\]{}()]/}}e.exports=t,t.displayName="rego",t.aliases=[]},35108(e){"use strict";function t(e){e.languages.renpy={comment:{pattern:/(^|[^\\])#.+/,lookbehind:!0},string:{pattern:/("""|''')[\s\S]+?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2|(?:^#?(?:[0-9a-fA-F]{6}|(?:[0-9a-fA-F]){3})$)/m,greedy:!0},function:/\b[a-z_]\w*(?=\()/i,property:/\b(?:insensitive|idle|hover|selected_idle|selected_hover|background|position|alt|xpos|ypos|pos|xanchor|yanchor|anchor|xalign|yalign|align|xcenter|ycenter|xofsset|yoffset|ymaximum|maximum|xmaximum|xminimum|yminimum|minimum|xsize|ysizexysize|xfill|yfill|area|antialias|black_color|bold|caret|color|first_indent|font|size|italic|justify|kerning|language|layout|line_leading|line_overlap_split|line_spacing|min_width|newline_indent|outlines|rest_indent|ruby_style|slow_cps|slow_cps_multiplier|strikethrough|text_align|underline|hyperlink_functions|vertical|hinting|foreground|left_margin|xmargin|top_margin|bottom_margin|ymargin|left_padding|right_padding|xpadding|top_padding|bottom_padding|ypadding|size_group|child|hover_sound|activate_sound|mouse|focus_mask|keyboard_focus|bar_vertical|bar_invert|bar_resizing|left_gutter|right_gutter|top_gutter|bottom_gutter|left_bar|right_bar|top_bar|bottom_bar|thumb|thumb_shadow|thumb_offset|unscrollable|spacing|first_spacing|box_reverse|box_wrap|order_reverse|fit_first|ysize|thumbnail_width|thumbnail_height|help|text_ypos|text_xpos|idle_color|hover_color|selected_idle_color|selected_hover_color|insensitive_color|alpha|insensitive_background|hover_background|zorder|value|width|xadjustment|xanchoraround|xaround|xinitial|xoffset|xzoom|yadjustment|yanchoraround|yaround|yinitial|yzoom|zoom|ground|height|text_style|text_y_fudge|selected_insensitive|has_sound|has_music|has_voice|focus|hovered|image_style|length|minwidth|mousewheel|offset|prefix|radius|range|right_margin|rotate|rotate_pad|developer|screen_width|screen_height|window_title|name|version|windows_icon|default_fullscreen|default_text_cps|default_afm_time|main_menu_music|sample_sound|enter_sound|exit_sound|save_directory|enter_transition|exit_transition|intra_transition|main_game_transition|game_main_transition|end_splash_transition|end_game_transition|after_load_transition|window_show_transition|window_hide_transition|adv_nvl_transition|nvl_adv_transition|enter_yesno_transition|exit_yesno_transition|enter_replay_transition|exit_replay_transition|say_attribute_transition|directory_name|executable_name|include_update|window_icon|modal|google_play_key|google_play_salt|drag_name|drag_handle|draggable|dragged|droppable|dropped|narrator_menu|action|default_afm_enable|version_name|version_tuple|inside|fadeout|fadein|layers|layer_clipping|linear|scrollbars|side_xpos|side_ypos|side_spacing|edgescroll|drag_joined|drag_raise|drop_shadow|drop_shadow_color|subpixel|easein|easeout|time|crop|auto|update|get_installed_packages|can_update|UpdateVersion|Update|overlay_functions|translations|window_left_padding|show_side_image|show_two_window)\b/,tag:/\b(?:label|image|menu|[hv]box|frame|text|imagemap|imagebutton|bar|vbar|screen|textbutton|buttoscreenn|fixed|grid|input|key|mousearea|side|timer|viewport|window|hotspot|hotbar|self|button|drag|draggroup|tag|mm_menu_frame|nvl|block|parallel)\b|\$/,keyword:/\b(?:as|assert|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|pass|print|raise|return|try|while|yield|adjustment|alignaround|allow|angle|around|box_layout|cache|changed|child_size|clicked|clipping|corner1|corner2|default|delay|exclude|scope|slow|slow_abortable|slow_done|sound|style_group|substitute|suffix|transform_anchor|transpose|unhovered|config|theme|mm_root|gm_root|rounded_window|build|disabled_text|disabled|widget_selected|widget_text|widget_hover|widget|updater|behind|call|expression|hide|init|jump|onlayer|python|renpy|scene|set|show|transform|play|queue|stop|pause|define|window|repeat|contains|choice|on|function|event|animation|clockwise|counterclockwise|circles|knot|null|None|random|has|add|use|fade|dissolve|style|store|id|voice|center|left|right|less_rounded|music|movie|clear|persistent|ui)\b/,boolean:/\b(?:[Tt]rue|[Ff]alse)\b/,number:/(?:\b(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*(?:\.\d*)?)|\B\.\d+)(?:e[+-]?\d+)?j?/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not|with|at)\b/,punctuation:/[{}[\];(),.:]/},e.languages.rpy=e.languages.renpy}e.exports=t,t.displayName="renpy",t.aliases=["rpy"]},46678(e){"use strict";function t(e){e.languages.rest={table:[{pattern:/(^[\t ]*)(?:\+[=-]+)+\+(?:\r?\n|\r)(?:\1[+|].+[+|](?:\r?\n|\r))+\1(?:\+[=-]+)+\+/m,lookbehind:!0,inside:{punctuation:/\||(?:\+[=-]+)+\+/}},{pattern:/(^[\t ]*)=+ [ =]*=(?:(?:\r?\n|\r)\1.+)+(?:\r?\n|\r)\1=+ [ =]*=(?=(?:\r?\n|\r){2}|\s*$)/m,lookbehind:!0,inside:{punctuation:/[=-]+/}}],"substitution-def":{pattern:/(^[\t ]*\.\. )\|(?:[^|\s](?:[^|]*[^|\s])?)\| [^:]+::/m,lookbehind:!0,inside:{substitution:{pattern:/^\|(?:[^|\s]|[^|\s][^|]*[^|\s])\|/,alias:"attr-value",inside:{punctuation:/^\||\|$/}},directive:{pattern:/( )(?! )[^:]+::/,lookbehind:!0,alias:"function",inside:{punctuation:/::$/}}}},"link-target":[{pattern:/(^[\t ]*\.\. )\[[^\]]+\]/m,lookbehind:!0,alias:"string",inside:{punctuation:/^\[|\]$/}},{pattern:/(^[\t ]*\.\. )_(?:`[^`]+`|(?:[^:\\]|\\.)+):/m,lookbehind:!0,alias:"string",inside:{punctuation:/^_|:$/}}],directive:{pattern:/(^[\t ]*\.\. )[^:]+::/m,lookbehind:!0,alias:"function",inside:{punctuation:/::$/}},comment:{pattern:/(^[\t ]*\.\.)(?:(?: .+)?(?:(?:\r?\n|\r).+)+| .+)(?=(?:\r?\n|\r){2}|$)/m,lookbehind:!0},title:[{pattern:/^(([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~])\2+)(?:\r?\n|\r).+(?:\r?\n|\r)\1$/m,inside:{punctuation:/^[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~]+|[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~]+$/,important:/.+/}},{pattern:/(^|(?:\r?\n|\r){2}).+(?:\r?\n|\r)([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~])\2+(?=\r?\n|\r|$)/,lookbehind:!0,inside:{punctuation:/[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~]+$/,important:/.+/}}],hr:{pattern:/((?:\r?\n|\r){2})([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~])\2{3,}(?=(?:\r?\n|\r){2})/,lookbehind:!0,alias:"punctuation"},field:{pattern:/(^[\t ]*):[^:\r\n]+:(?= )/m,lookbehind:!0,alias:"attr-name"},"command-line-option":{pattern:/(^[\t ]*)(?:[+-][a-z\d]|(?:--|\/)[a-z\d-]+)(?:[ =](?:[a-z][\w-]*|<[^<>]+>))?(?:, (?:[+-][a-z\d]|(?:--|\/)[a-z\d-]+)(?:[ =](?:[a-z][\w-]*|<[^<>]+>))?)*(?=(?:\r?\n|\r)? {2,}\S)/im,lookbehind:!0,alias:"symbol"},"literal-block":{pattern:/::(?:\r?\n|\r){2}([ \t]+)(?![ \t]).+(?:(?:\r?\n|\r)\1.+)*/,inside:{"literal-block-punctuation":{pattern:/^::/,alias:"punctuation"}}},"quoted-literal-block":{pattern:/::(?:\r?\n|\r){2}([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~]).*(?:(?:\r?\n|\r)\1.*)*/,inside:{"literal-block-punctuation":{pattern:/^(?:::|([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~])\1*)/m,alias:"punctuation"}}},"list-bullet":{pattern:/(^[\t ]*)(?:[*+\-•‣⁃]|\(?(?:\d+|[a-z]|[ivxdclm]+)\)|(?:\d+|[a-z]|[ivxdclm]+)\.)(?= )/im,lookbehind:!0,alias:"punctuation"},"doctest-block":{pattern:/(^[\t ]*)>>> .+(?:(?:\r?\n|\r).+)*/m,lookbehind:!0,inside:{punctuation:/^>>>/}},inline:[{pattern:/(^|[\s\-:\/'"<(\[{])(?::[^:]+:`.*?`|`.*?`:[^:]+:|(\*\*?|``?|\|)(?!\s)(?:(?!\2).)*\S\2(?=[\s\-.,:;!?\\\/'")\]}]|$))/m,lookbehind:!0,inside:{bold:{pattern:/(^\*\*).+(?=\*\*$)/,lookbehind:!0},italic:{pattern:/(^\*).+(?=\*$)/,lookbehind:!0},"inline-literal":{pattern:/(^``).+(?=``$)/,lookbehind:!0,alias:"symbol"},role:{pattern:/^:[^:]+:|:[^:]+:$/,alias:"function",inside:{punctuation:/^:|:$/}},"interpreted-text":{pattern:/(^`).+(?=`$)/,lookbehind:!0,alias:"attr-value"},substitution:{pattern:/(^\|).+(?=\|$)/,lookbehind:!0,alias:"attr-value"},punctuation:/\*\*?|``?|\|/}}],link:[{pattern:/\[[^\[\]]+\]_(?=[\s\-.,:;!?\\\/'")\]}]|$)/,alias:"string",inside:{punctuation:/^\[|\]_$/}},{pattern:/(?:\b[a-z\d]+(?:[_.:+][a-z\d]+)*_?_|`[^`]+`_?_|_`[^`]+`)(?=[\s\-.,:;!?\\\/'")\]}]|$)/i,alias:"string",inside:{punctuation:/^_?`|`$|`?_?_$/}}],punctuation:{pattern:/(^[\t ]*)(?:\|(?= |$)|(?:---?|—|\.\.|__)(?= )|\.\.$)/m,lookbehind:!0}}}e.exports=t,t.displayName="rest",t.aliases=[]},47496(e){"use strict";function t(e){e.languages.rip={comment:/#.*/,keyword:/(?:=>|->)|\b(?:class|if|else|switch|case|return|exit|try|catch|finally|raise)\b/,builtin:/@|\bSystem\b/,boolean:/\b(?:true|false)\b/,date:/\b\d{4}-\d{2}-\d{2}\b/,time:/\b\d{2}:\d{2}:\d{2}\b/,datetime:/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\b/,character:/\B`[^\s`'",.:;#\/\\()<>\[\]{}]\b/,regex:{pattern:/(^|[^/])\/(?!\/)(?:\[[^\n\r\]]*\]|\\.|[^/\\\r\n\[])+\/(?=\s*(?:$|[\r\n,.;})]))/,lookbehind:!0,greedy:!0},symbol:/:[^\d\s`'",.:;#\/\\()<>\[\]{}][^\s`'",.:;#\/\\()<>\[\]{}]*/,string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},number:/[+-]?\b(?:\d+\.\d+|\d+)\b/,punctuation:/(?:\.{2,3})|[`,.:;=\/\\()<>\[\]{}]/,reference:/[^\d\s`'",.:;#\/\\()<>\[\]{}][^\s`'",.:;#\/\\()<>\[\]{}]*/}}e.exports=t,t.displayName="rip",t.aliases=[]},30527(e){"use strict";function t(e){e.languages.roboconf={comment:/#.*/,keyword:{pattern:/(^|\s)(?:(?:facet|instance of)(?=[ \t]+[\w-]+[ \t]*\{)|(?:external|import)\b)/,lookbehind:!0},component:{pattern:/[\w-]+(?=[ \t]*\{)/,alias:"variable"},property:/[\w.-]+(?=[ \t]*:)/,value:{pattern:/(=[ \t]*(?![ \t]))[^,;]+/,lookbehind:!0,alias:"attr-value"},optional:{pattern:/\(optional\)/,alias:"builtin"},wildcard:{pattern:/(\.)\*/,lookbehind:!0,alias:"operator"},punctuation:/[{},.;:=]/}}e.exports=t,t.displayName="roboconf",t.aliases=[]},5261(e){"use strict";function t(e){!function(e){var t={pattern:/(^[ \t]*| {2}|\t)#.*/m,lookbehind:!0,greedy:!0},n={pattern:/((?:^|[^\\])(?:\\{2})*)[$@&%]\{(?:[^{}\r\n]|\{[^{}\r\n]*\})*\}/,lookbehind:!0,inside:{punctuation:/^[$@&%]\{|\}$/}};function r(e,r){var i={};for(var a in i["section-header"]={pattern:/^ ?\*{3}.+?\*{3}/,alias:"keyword"},r)i[a]=r[a];return i.tag={pattern:/([\r\n](?: {2}|\t)[ \t]*)\[[-\w]+\]/,lookbehind:!0,inside:{punctuation:/\[|\]/}},i.variable=n,i.comment=t,{pattern:RegExp(/^ ?\*{3}[ \t]*[ \t]*\*{3}(?:.|[\r\n](?!\*{3}))*/.source.replace(//g,function(){return e}),"im"),alias:"section",inside:i}}var i={pattern:/(\[Documentation\](?: {2}|\t)[ \t]*)(?![ \t]|#)(?:.|(?:\r\n?|\n)[ \t]*\.{3})+/,lookbehind:!0,alias:"string"},a={pattern:/([\r\n] ?)(?!#)(?:\S(?:[ \t]\S)*)+/,lookbehind:!0,alias:"function",inside:{variable:n}},o={pattern:/([\r\n](?: {2}|\t)[ \t]*)(?!\[|\.{3}|#)(?:\S(?:[ \t]\S)*)+/,lookbehind:!0,inside:{variable:n}};e.languages.robotframework={settings:r("Settings",{documentation:{pattern:/([\r\n] ?Documentation(?: {2}|\t)[ \t]*)(?![ \t]|#)(?:.|(?:\r\n?|\n)[ \t]*\.{3})+/,lookbehind:!0,alias:"string"},property:{pattern:/([\r\n] ?)(?!\.{3}|#)(?:\S(?:[ \t]\S)*)+/,lookbehind:!0}}),variables:r("Variables"),"test-cases":r("Test Cases",{"test-name":a,documentation:i,property:o}),keywords:r("Keywords",{"keyword-name":a,documentation:i,property:o}),tasks:r("Tasks",{"task-name":a,documentation:i,property:o}),comment:t},e.languages.robot=e.languages.robotframework}(e)}e.exports=t,t.displayName="robotframework",t.aliases=[]},56939(e){"use strict";function t(e){var t,n;(t=e).languages.ruby=t.languages.extend("clike",{comment:[/#.*/,{pattern:/^=begin\s[\s\S]*?^=end/m,greedy:!0}],"class-name":{pattern:/(\b(?:class)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:alias|and|BEGIN|begin|break|case|class|def|define_method|defined|do|each|else|elsif|END|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|protected|private|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/}),n={pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"tag"},rest:t.languages.ruby}},delete t.languages.ruby.function,t.languages.insertBefore("ruby","keyword",{regex:[{pattern:RegExp(/%r/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/\((?:[^()\\]|\\[\s\S])*\)/.source,/\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}/.source,/\[(?:[^\[\]\\]|\\[\s\S])*\]/.source,/<(?:[^<>\\]|\\[\s\S])*>/.source].join("|")+")"+/[egimnosux]{0,6}/.source),greedy:!0,inside:{interpolation:n}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:n}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:{pattern:/(^|[^:]):[a-zA-Z_]\w*(?:[?!]|\b)/,lookbehind:!0},"method-definition":{pattern:/(\bdef\s+)[\w.]+/,lookbehind:!0,inside:{function:/\w+$/,rest:t.languages.ruby}}}),t.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Stat|Fixnum|Float|Hash|Integer|IO|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|String|Struct|TMS|Symbol|ThreadGroup|Thread|Time|TrueClass)\b/,constant:/\b[A-Z]\w*(?:[?!]|\b)/}),t.languages.ruby.string=[{pattern:RegExp(/%[qQiIwWxs]?/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/\((?:[^()\\]|\\[\s\S])*\)/.source,/\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}/.source,/\[(?:[^\[\]\\]|\\[\s\S])*\]/.source,/<(?:[^<>\\]|\\[\s\S])*>/.source].join("|")+")"),greedy:!0,inside:{interpolation:n}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:n}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|[a-z_]\w*$/i,alias:"symbol",inside:{punctuation:/^<<[-~]?/}},interpolation:n}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|[a-z_]\w*$/i,alias:"symbol",inside:{punctuation:/^<<[-~]?'|'$/}}}}],t.languages.rb=t.languages.ruby}e.exports=t,t.displayName="ruby",t.aliases=["rb"]},83648(e){"use strict";function t(e){!function(e){for(var t=/\/\*(?:[^*/]|\*(?!\/)|\/(?!\*)|)*\*\//.source,n=0;n<2;n++)t=t.replace(//g,function(){return t});t=t.replace(//g,function(){return/[^\s\S]/.source}),e.languages.rust={comment:[{pattern:RegExp(/(^|[^\\])/.source+t),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/,greedy:!0},char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/,greedy:!0,alias:"string"},attribute:{pattern:/#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/,greedy:!0,alias:"attr-name",inside:{string:null}},"closure-params":{pattern:/([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/,lookbehind:!0,greedy:!0,inside:{"closure-punctuation":{pattern:/^\||\|$/,alias:"punctuation"},rest:null}},"lifetime-annotation":{pattern:/'\w+/,alias:"symbol"},"fragment-specifier":{pattern:/(\$\w+:)[a-z]+/,lookbehind:!0,alias:"punctuation"},variable:/\$\w+/,"function-definition":{pattern:/(\bfn\s+)\w+/,lookbehind:!0,alias:"function"},"type-definition":{pattern:/(\b(?:enum|struct|union)\s+)\w+/,lookbehind:!0,alias:"class-name"},"module-declaration":[{pattern:/(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/,lookbehind:!0,alias:"namespace"},{pattern:/(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/,lookbehind:!0,alias:"namespace",inside:{punctuation:/::/}}],keyword:[/\b(?:abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|Self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,/\b(?:[ui](?:8|16|32|64|128|size)|f(?:32|64)|bool|char|str)\b/],function:/\b[a-z_]\w*(?=\s*(?:::\s*<|\())/,macro:{pattern:/\b\w+!/,alias:"property"},constant:/\b[A-Z_][A-Z_\d]+\b/,"class-name":/\b[A-Z]\w*\b/,namespace:{pattern:/(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/,inside:{punctuation:/::/}},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:[iu](?:8|16|32|64|size)?|f32|f64))?\b/,boolean:/\b(?:false|true)\b/,punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<>?=?|[@?]/},e.languages.rust["closure-params"].inside.rest=e.languages.rust,e.languages.rust.attribute.inside.string=e.languages.rust.string}(e)}e.exports=t,t.displayName="rust",t.aliases=[]},16009(e){"use strict";function t(e){var t,n,r,i,a,o,s,u,c,l,f,d,h,p,b,m,g,v,y;t=e,n=/(?:"(?:""|[^"])*"(?!")|'(?:''|[^'])*'(?!'))/.source,r=/\b(?:\d[\da-f]*x|\d+(?:\.\d+)?(?:e[+-]?\d+)?)\b/i,i={pattern:RegExp(n+"[bx]"),alias:"number"},a={pattern:/&[a-z_]\w*/i},o={pattern:/((?:^|\s|=|\())%(?:ABORT|BY|CMS|COPY|DISPLAY|DO|ELSE|END|EVAL|GLOBAL|GO|GOTO|IF|INC|INCLUDE|INDEX|INPUT|KTRIM|LENGTH|LET|LIST|LOCAL|PUT|QKTRIM|QSCAN|QSUBSTR|QSYSFUNC|QUPCASE|RETURN|RUN|SCAN|SUBSTR|SUPERQ|SYMDEL|SYMGLOBL|SYMLOCAL|SYMEXIST|SYSCALL|SYSEVALF|SYSEXEC|SYSFUNC|SYSGET|SYSRPUT|THEN|TO|TSO|UNQUOTE|UNTIL|UPCASE|WHILE|WINDOW)\b/i,lookbehind:!0,alias:"keyword"},s={pattern:/(^|\s)(?:proc\s+\w+|quit|run|data(?!=))\b/i,alias:"keyword",lookbehind:!0},u=[/\/\*[\s\S]*?\*\//,{pattern:/(^[ \t]*|;\s*)\*[^;]*;/m,lookbehind:!0}],c={pattern:RegExp(n),greedy:!0},d={function:f={pattern:/%?\b\w+(?=\()/,alias:"keyword"},"arg-value":{pattern:/(=\s*)[A-Z\.]+/i,lookbehind:!0},operator:/=/,"macro-variable":a,arg:{pattern:/[A-Z]+/i,alias:"keyword"},number:r,"numeric-constant":i,punctuation:l=/[$%@.(){}\[\];,\\]/,string:c},h={pattern:/\b(?:format|put)\b=?[\w'$.]+/im,inside:{keyword:/^(?:format|put)(?==)/i,equals:/=/,format:{pattern:/(?:\w|\$\d)+\.\d?/i,alias:"number"}}},p={pattern:/\b(?:format|put)\s+[\w']+(?:\s+[$.\w]+)+(?=;)/i,inside:{keyword:/^(?:format|put)/i,format:{pattern:/[\w$]+\.\d?/,alias:"number"}}},b={pattern:/((?:^|\s)=?)(?:catname|checkpoint execute_always|dm|endsas|filename|footnote|%include|libname|%list|lock|missing|options|page|resetline|%run|sasfile|skip|sysecho|title\d?)\b/i,lookbehind:!0,alias:"keyword"},m={pattern:/(^|\s)(?:submit(?:\s+(?:load|parseonly|norun))?|endsubmit)\b/i,lookbehind:!0,alias:"keyword"},g=/accessControl|cdm|aggregation|aStore|ruleMining|audio|autotune|bayesianNetClassifier|bioMedImage|boolRule|builtins|cardinality|sccasl|clustering|copula|countreg|dataDiscovery|dataPreprocess|dataSciencePilot|dataStep|decisionTree|deepLearn|deepNeural|varReduce|simSystem|ds2|deduplication|ecm|entityRes|espCluster|explainModel|factmac|fastKnn|fcmpact|fedSql|freqTab|gam|gleam|graphSemiSupLearn|gVarCluster|hiddenMarkovModel|hyperGroup|image|iml|ica|kernalPca|langModel|ldaTopic|sparseML|mlTools|mixed|modelPublishing|mbc|network|optNetwork|neuralNet|nonlinear|nmf|nonParametricBayes|optimization|panel|pls|percentile|pca|phreg|qkb|qlim|quantreg|recommend|tsReconcile|deepRnn|regression|reinforcementLearn|robustPca|sampling|sparkEmbeddedProcess|search(?:Analytics)?|sentimentAnalysis|sequence|configuration|session(?:Prop)?|severity|simple|smartData|sandwich|spatialreg|stabilityMonitoring|spc|loadStreams|svDataDescription|svm|table|conditionalRandomFields|text(?:Rule(?:Develop|Score)|Mining|Parse|Topic|Util|Filters|Frequency)|tsInfo|timeData|transpose|uniTimeSeries/.source,v={pattern:RegExp(/(^|\s)(?:action\s+)?(?:)\.[a-z]+\b[^;]+/.source.replace(//g,function(){return g}),"i"),lookbehind:!0,inside:{keyword:RegExp(/(?:)\.[a-z]+\b/.source.replace(//g,function(){return g}),"i"),action:{pattern:/(?:action)/i,alias:"keyword"},comment:u,function:f,"arg-value":d["arg-value"],operator:d.operator,argument:d.arg,number:r,"numeric-constant":i,punctuation:l,string:c}},y={pattern:/((?:^|\s)=?)(?:after|analysis|and|array|barchart|barwidth|begingraph|by|call|cas|cbarline|cfill|class(?:lev)?|close|column|computed?|contains|continue|data(?==)|define|delete|describe|document|do\s+over|do|dol|drop|dul|end(?:source|comp)?|entryTitle|else|eval(?:uate)?|exec(?:ute)?|exit|fill(?:attrs)?|file(?:name)?|flist|fnc|function(?:list)?|goto|global|group(?:by)?|headline|headskip|histogram|if|infile|keep|keylabel|keyword|label|layout|leave|legendlabel|length|libname|loadactionset|merge|midpoints|name|noobs|nowd|_?null_|ods|options|or|otherwise|out(?:put)?|over(?:lay)?|plot|put|print|raise|ranexp|rannor|rbreak|retain|return|select|set|session|sessref|source|statgraph|sum|summarize|table|temp|terminate|then\s+do|then|title\d?|to|var|when|where|xaxisopts|yaxisopts|y2axisopts)\b/i,lookbehind:!0},t.languages.sas={datalines:{pattern:/^([ \t]*)(?:(?:data)?lines|cards);[\s\S]+?^[ \t]*;/im,lookbehind:!0,alias:"string",inside:{keyword:{pattern:/^(?:(?:data)?lines|cards)/i},punctuation:/;/}},"proc-sql":{pattern:/(^proc\s+(?:fed)?sql(?:\s+[\w|=]+)?;)[\s\S]+?(?=^(?:proc\s+\w+|quit|run|data);|(?![\s\S]))/im,lookbehind:!0,inside:{sql:{pattern:RegExp(/^[ \t]*(?:select|alter\s+table|(?:create|describe|drop)\s+(?:index|table(?:\s+constraints)?|view)|create\s+unique\s+index|insert\s+into|update)(?:|[^;"'])+;/.source.replace(//g,function(){return n}),"im"),alias:"language-sql",inside:t.languages.sql},"global-statements":b,"sql-statements":{pattern:/(^|\s)(?:disconnect\s+from|exec(?:ute)?|begin|commit|rollback|reset|validate)\b/i,lookbehind:!0,alias:"keyword"},number:r,"numeric-constant":i,punctuation:l,string:c}},"proc-groovy":{pattern:/(^proc\s+groovy(?:\s+[\w|=]+)?;)[\s\S]+?(?=^(?:proc\s+\w+|quit|run|data);|(?![\s\S]))/im,lookbehind:!0,inside:{comment:u,groovy:{pattern:RegExp(/(^[ \t]*submit(?:\s+(?:load|parseonly|norun))?)(?:|[^"'])+?(?=endsubmit;)/.source.replace(//g,function(){return n}),"im"),lookbehind:!0,alias:"language-groovy",inside:t.languages.groovy},keyword:y,"submit-statement":m,"global-statements":b,number:r,"numeric-constant":i,punctuation:l,string:c}},"proc-lua":{pattern:/(^proc\s+lua(?:\s+[\w|=]+)?;)[\s\S]+?(?=^(?:proc\s+\w+|quit|run|data);|(?![\s\S]))/im,lookbehind:!0,inside:{comment:u,lua:{pattern:RegExp(/(^[ \t]*submit(?:\s+(?:load|parseonly|norun))?)(?:|[^"'])+?(?=endsubmit;)/.source.replace(//g,function(){return n}),"im"),lookbehind:!0,alias:"language-lua",inside:t.languages.lua},keyword:y,"submit-statement":m,"global-statements":b,number:r,"numeric-constant":i,punctuation:l,string:c}},"proc-cas":{pattern:/(^proc\s+cas(?:\s+[\w|=]+)?;)[\s\S]+?(?=^(?:proc\s+\w+|quit|data);|(?![\s\S]))/im,lookbehind:!0,inside:{comment:u,"statement-var":{pattern:/((?:^|\s)=?)saveresult\s[^;]+/im,lookbehind:!0,inside:{statement:{pattern:/^saveresult\s+\S+/i,inside:{keyword:/^(?:saveresult)/i}},rest:d}},"cas-actions":v,statement:{pattern:/((?:^|\s)=?)(?:default|(?:un)?set|on|output|upload)[^;]+/im,lookbehind:!0,inside:d},step:s,keyword:y,function:f,format:h,altformat:p,"global-statements":b,number:r,"numeric-constant":i,punctuation:l,string:c}},"proc-args":{pattern:RegExp(/(^proc\s+\w+\s+)(?!\s)(?:[^;"']|)+;/.source.replace(//g,function(){return n}),"im"),lookbehind:!0,inside:d},"macro-keyword":o,"macro-variable":a,"macro-string-functions":{pattern:/((?:^|\s|=))%(?:NRBQUOTE|NRQUOTE|NRSTR|BQUOTE|QUOTE|STR)\(.*?(?:[^%]\))/i,lookbehind:!0,inside:{function:{pattern:/%(?:NRBQUOTE|NRQUOTE|NRSTR|BQUOTE|QUOTE|STR)/i,alias:"keyword"},"macro-keyword":o,"macro-variable":a,"escaped-char":{pattern:/%['"()<>=¬^~;,#]/i},punctuation:l}},"macro-declaration":{pattern:/^%macro[^;]+(?=;)/im,inside:{keyword:/%macro/i}},"macro-end":{pattern:/^%mend[^;]+(?=;)/im,inside:{keyword:/%mend/i}},macro:{pattern:/%_\w+(?=\()/,alias:"keyword"},input:{pattern:/\binput\s[-\w\s/*.$&]+;/i,inside:{input:{alias:"keyword",pattern:/^input/i},comment:u,number:r,"numeric-constant":i}},"options-args":{pattern:/(^options)[-'"|/\\<>*+=:()\w\s]*(?=;)/im,lookbehind:!0,inside:d},"cas-actions":v,comment:u,function:f,format:h,altformat:p,"numeric-constant":i,datetime:{pattern:RegExp(n+"(?:dt?|t)"),alias:"number"},string:c,step:s,keyword:y,"operator-keyword":{pattern:/\b(?:eq|ne|gt|lt|ge|le|in|not)\b/i,alias:"operator"},number:r,operator:/\*\*?|\|\|?|!!?|¦¦?|<[>=]?|>[<=]?|[-+\/=&]|[~¬^]=?/i,punctuation:l}}e.exports=t,t.displayName="sas",t.aliases=[]},41720(e){"use strict";function t(e){var t,n,r;(t=e).languages.sass=t.languages.extend("css",{comment:{pattern:/^([ \t]*)\/[\/*].*(?:(?:\r?\n|\r)\1[ \t].+)*/m,lookbehind:!0}}),t.languages.insertBefore("sass","atrule",{"atrule-line":{pattern:/^(?:[ \t]*)[@+=].+/m,inside:{atrule:/(?:@[\w-]+|[+=])/m}}}),delete t.languages.sass.atrule,n=/\$[-\w]+|#\{\$[-\w]+\}/,r=[/[+*\/%]|[=!]=|<=?|>=?|\b(?:and|or|not)\b/,{pattern:/(\s)-(?=\s)/,lookbehind:!0}],t.languages.insertBefore("sass","property",{"variable-line":{pattern:/^[ \t]*\$.+/m,inside:{punctuation:/:/,variable:n,operator:r}},"property-line":{pattern:/^[ \t]*(?:[^:\s]+ *:.*|:[^:\s].*)/m,inside:{property:[/[^:\s]+(?=\s*:)/,{pattern:/(:)[^:\s]+/,lookbehind:!0}],punctuation:/:/,variable:n,operator:r,important:t.languages.sass.important}}}),delete t.languages.sass.property,delete t.languages.sass.important,t.languages.insertBefore("sass","punctuation",{selector:{pattern:/([ \t]*)\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*(?:,(?:\r?\n|\r)\1[ \t]+\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*)*/,lookbehind:!0}})}e.exports=t,t.displayName="sass",t.aliases=[]},6054(e,t,n){"use strict";var r=n(15909);function i(e){e.register(r),e.languages.scala=e.languages.extend("java",{"triple-quoted-string":{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},keyword:/<-|=>|\b(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|null|object|override|package|private|protected|return|sealed|self|super|this|throw|trait|try|type|val|var|while|with|yield)\b/,number:/\b0x(?:[\da-f]*\.)?[\da-f]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e\d+)?[dfl]?/i,builtin:/\b(?:String|Int|Long|Short|Byte|Boolean|Double|Float|Char|Any|AnyRef|AnyVal|Unit|Nothing)\b/,symbol:/'[^\d\s\\]\w*/}),delete e.languages.scala["class-name"],delete e.languages.scala.function}e.exports=i,i.displayName="scala",i.aliases=[]},9997(e){"use strict";function t(e){!function(e){e.languages.scheme={comment:/;.*|#;\s*(?:\((?:[^()]|\([^()]*\))*\)|\[(?:[^\[\]]|\[[^\[\]]*\])*\])|#\|(?:[^#|]|#(?!\|)|\|(?!#)|#\|(?:[^#|]|#(?!\|)|\|(?!#))*\|#)*\|#/,string:{pattern:/"(?:[^"\\]|\\.)*"/,greedy:!0},symbol:{pattern:/'[^()\[\]#'\s]+/,greedy:!0},character:{pattern:/#\\(?:[ux][a-fA-F\d]+\b|[-a-zA-Z]+\b|[\uD800-\uDBFF][\uDC00-\uDFFF]|\S)/,greedy:!0,alias:"string"},"lambda-parameter":[{pattern:/((?:^|[^'`#])[(\[]lambda\s+)(?:[^|()\[\]'\s]+|\|(?:[^\\|]|\\.)*\|)/,lookbehind:!0},{pattern:/((?:^|[^'`#])[(\[]lambda\s+[(\[])[^()\[\]']+/,lookbehind:!0}],keyword:{pattern:/((?:^|[^'`#])[(\[])(?:begin|case(?:-lambda)?|cond(?:-expand)?|define(?:-library|-macro|-record-type|-syntax|-values)?|defmacro|delay(?:-force)?|do|else|export|except|guard|if|import|include(?:-ci|-library-declarations)?|lambda|let(?:rec)?(?:-syntax|-values|\*)?|let\*-values|only|parameterize|prefix|(?:quasi-?)?quote|rename|set!|syntax-(?:case|rules)|unless|unquote(?:-splicing)?|when)(?=[()\[\]\s]|$)/,lookbehind:!0},builtin:{pattern:/((?:^|[^'`#])[(\[])(?:abs|and|append|apply|assoc|ass[qv]|binary-port\?|boolean=?\?|bytevector(?:-append|-copy|-copy!|-length|-u8-ref|-u8-set!|\?)?|caar|cadr|call-with-(?:current-continuation|port|values)|call\/cc|car|cdar|cddr|cdr|ceiling|char(?:->integer|-ready\?|\?|<\?|<=\?|=\?|>\?|>=\?)|close-(?:input-port|output-port|port)|complex\?|cons|current-(?:error|input|output)-port|denominator|dynamic-wind|eof-object\??|eq\?|equal\?|eqv\?|error|error-object(?:-irritants|-message|\?)|eval|even\?|exact(?:-integer-sqrt|-integer\?|\?)?|expt|features|file-error\?|floor(?:-quotient|-remainder|\/)?|flush-output-port|for-each|gcd|get-output-(?:bytevector|string)|inexact\??|input-port(?:-open\?|\?)|integer(?:->char|\?)|lcm|length|list(?:->string|->vector|-copy|-ref|-set!|-tail|\?)?|make-(?:bytevector|list|parameter|string|vector)|map|max|member|memq|memv|min|modulo|negative\?|newline|not|null\?|number(?:->string|\?)|numerator|odd\?|open-(?:input|output)-(?:bytevector|string)|or|output-port(?:-open\?|\?)|pair\?|peek-char|peek-u8|port\?|positive\?|procedure\?|quotient|raise|raise-continuable|rational\?|rationalize|read-(?:bytevector|bytevector!|char|error\?|line|string|u8)|real\?|remainder|reverse|round|set-c[ad]r!|square|string(?:->list|->number|->symbol|->utf8|->vector|-append|-copy|-copy!|-fill!|-for-each|-length|-map|-ref|-set!|\?|<\?|<=\?|=\?|>\?|>=\?)?|substring|symbol(?:->string|\?|=\?)|syntax-error|textual-port\?|truncate(?:-quotient|-remainder|\/)?|u8-ready\?|utf8->string|values|vector(?:->list|->string|-append|-copy|-copy!|-fill!|-for-each|-length|-map|-ref|-set!|\?)?|with-exception-handler|write-(?:bytevector|char|string|u8)|zero\?)(?=[()\[\]\s]|$)/,lookbehind:!0},operator:{pattern:/((?:^|[^'`#])[(\[])(?:[-+*%/]|[<>]=?|=>?)(?=[()\[\]\s]|$)/,lookbehind:!0},number:{pattern:RegExp(t({"":/\d+(?:\/\d+)|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/.source,"":/[+-]?|[+-](?:inf|nan)\.0/.source,"":/[+-](?:|(?:inf|nan)\.0)?i/.source,"":/(?:@|)?|/.source,"":/(?:#d(?:#[ei])?|#[ei](?:#d)?)?/.source,"":/[0-9a-f]+(?:\/[0-9a-f]+)?/.source,"":/[+-]?|[+-](?:inf|nan)\.0/.source,"":/[+-](?:|(?:inf|nan)\.0)?i/.source,"":/(?:@|)?|/.source,"":/#[box](?:#[ei])?|(?:#[ei])?#[box]/.source,"":/(^|[()\[\]\s])(?:|)(?=[()\[\]\s]|$)/.source}),"i"),lookbehind:!0},boolean:{pattern:/(^|[()\[\]\s])#(?:[ft]|false|true)(?=[()\[\]\s]|$)/,lookbehind:!0},function:{pattern:/((?:^|[^'`#])[(\[])(?:[^|()\[\]'\s]+|\|(?:[^\\|]|\\.)*\|)(?=[()\[\]\s]|$)/,lookbehind:!0},identifier:{pattern:/(^|[()\[\]\s])\|(?:[^\\|]|\\.)*\|(?=[()\[\]\s]|$)/,lookbehind:!0,greedy:!0},punctuation:/[()\[\]']/};function t(e){for(var t in e)e[t]=e[t].replace(/<[\w\s]+>/g,function(t){return"(?:"+e[t].trim()+")"});return e[t]}}(e)}e.exports=t,t.displayName="scheme",t.aliases=[]},24296(e){"use strict";function t(e){e.languages.scss=e.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-](?:\([^()]+\)|[^()\s]|\s+(?!\s))*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)?url(?=\()/i,selector:{pattern:/(?=\S)[^@;{}()]?(?:[^@;{}()\s]|\s+(?!\s)|#\{\$[-\w]+\})+(?=\s*\{(?:\}|\s|[^}][^:{}]*[:{][^}]))/m,inside:{parent:{pattern:/&/,alias:"important"},placeholder:/%[-\w]+/,variable:/\$[-\w]+|#\{\$[-\w]+\}/}},property:{pattern:/(?:[-\w]|\$[-\w]|#\{\$[-\w]+\})+(?=\s*:)/,inside:{variable:/\$[-\w]+|#\{\$[-\w]+\}/}}}),e.languages.insertBefore("scss","atrule",{keyword:[/@(?:if|else(?: if)?|forward|for|each|while|import|use|extend|debug|warn|mixin|include|function|return|content)\b/i,{pattern:/( )(?:from|through)(?= )/,lookbehind:!0}]}),e.languages.insertBefore("scss","important",{variable:/\$[-\w]+|#\{\$[-\w]+\}/}),e.languages.insertBefore("scss","function",{"module-modifier":{pattern:/\b(?:as|with|show|hide)\b/i,alias:"keyword"},placeholder:{pattern:/%[-\w]+/,alias:"selector"},statement:{pattern:/\B!(?:default|optional)\b/i,alias:"keyword"},boolean:/\b(?:true|false)\b/,null:{pattern:/\bnull\b/,alias:"keyword"},operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,lookbehind:!0}}),e.languages.scss.atrule.inside.rest=e.languages.scss}e.exports=t,t.displayName="scss",t.aliases=[]},49246(e,t,n){"use strict";var r=n(6979);function i(e){var t,n;e.register(r),n=[/"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/.source,/'[^']*'/.source,/\$'(?:[^'\\]|\\[\s\S])*'/.source,/<<-?\s*(["']?)(\w+)\1\s[\s\S]*?[\r\n]\2/.source].join("|"),(t=e).languages["shell-session"]={command:{pattern:RegExp(/^(?:[^\s@:$#*!/\\]+@[^\r\n@:$#*!/\\]+(?::[^\0-\x1F$#*?"<>:;|]+)?|[^\0-\x1F$#*?"<>@:;|]+)?/.source+/[$#]/.source+/(?:[^\\\r\n'"<$]|\\(?:[^\r]|\r\n?)|\$(?!')|<>)+/.source.replace(/<>/g,function(){return n}),"m"),greedy:!0,inside:{info:{pattern:/^[^#$]+/,alias:"punctuation",inside:{user:/^[^\s@:$#*!/\\]+@[^\r\n@:$#*!/\\]+/,punctuation:/:/,path:/[\s\S]+/}},bash:{pattern:/(^[$#]\s*)\S[\s\S]*/,lookbehind:!0,alias:"language-bash",inside:t.languages.bash},"shell-symbol":{pattern:/^[$#]/,alias:"important"}}},output:/.(?:.*(?:[\r\n]|.$))*/},t.languages["sh-session"]=t.languages.shellsession=t.languages["shell-session"]}e.exports=i,i.displayName="shellSession",i.aliases=[]},18890(e){"use strict";function t(e){e.languages.smali={comment:/#.*/,string:{pattern:/"(?:[^\r\n\\"]|\\.)*"|'(?:[^\r\n\\']|\\(?:.|u[\da-fA-F]{4}))'/,greedy:!0},"class-name":{pattern:/(^|[^L])L(?:(?:\w+|`[^`\r\n]*`)\/)*(?:[\w$]+|`[^`\r\n]*`)(?=\s*;)/,lookbehind:!0,inside:{"class-name":{pattern:/(^L|\/)(?:[\w$]+|`[^`\r\n]*`)$/,lookbehind:!0},namespace:{pattern:/^(L)(?:(?:\w+|`[^`\r\n]*`)\/)+/,lookbehind:!0,inside:{punctuation:/\//}},builtin:/^L/}},builtin:[{pattern:/([();\[])[BCDFIJSVZ]+/,lookbehind:!0},{pattern:/([\w$>]:)[BCDFIJSVZ]/,lookbehind:!0}],keyword:[{pattern:/(\.end\s+)[\w-]+/,lookbehind:!0},{pattern:/(^|[^\w.-])\.(?!\d)[\w-]+/,lookbehind:!0},{pattern:/(^|[^\w.-])(?:abstract|annotation|bridge|constructor|enum|final|interface|private|protected|public|runtime|static|synthetic|system|transient)(?![\w.-])/,lookbehind:!0}],function:{pattern:/(^|[^\w.-])(?:\w+|<[\w$-]+>)(?=\()/,lookbehind:!0},field:{pattern:/[\w$]+(?=:)/,alias:"variable"},register:{pattern:/(^|[^\w.-])[vp]\d(?![\w.-])/,lookbehind:!0,alias:"variable"},boolean:{pattern:/(^|[^\w.-])(?:true|false)(?![\w.-])/,lookbehind:!0},number:{pattern:/(^|[^/\w.-])-?(?:NAN|INFINITY|0x(?:[\dA-F]+(?:\.[\dA-F]*)?|\.[\dA-F]+)(?:p[+-]?[\dA-F]+)?|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?)[dflst]?(?![\w.-])/i,lookbehind:!0},label:{pattern:/(:)\w+/,lookbehind:!0,alias:"property"},operator:/->|\.\.|[\[=]/,punctuation:/[{}(),;:]/}}e.exports=t,t.displayName="smali",t.aliases=[]},11037(e){"use strict";function t(e){e.languages.smalltalk={comment:/"(?:""|[^"])*"/,character:{pattern:/\$./,alias:"string"},string:/'(?:''|[^'])*'/,symbol:/#[\da-z]+|#(?:-|([+\/\\*~<>=@%|&?!])\1?)|#(?=\()/i,"block-arguments":{pattern:/(\[\s*):[^\[|]*\|/,lookbehind:!0,inside:{variable:/:[\da-z]+/i,punctuation:/\|/}},"temporary-variables":{pattern:/\|[^|]+\|/,inside:{variable:/[\da-z]+/i,punctuation:/\|/}},keyword:/\b(?:nil|true|false|self|super|new)\b/,number:[/\d+r-?[\dA-Z]+(?:\.[\dA-Z]+)?(?:e-?\d+)?/,/\b\d+(?:\.\d+)?(?:e-?\d+)?/],operator:/[<=]=?|:=|~[~=]|\/\/?|\\\\|>[>=]?|[!^+\-*&|,@]/,punctuation:/[.;:?\[\](){}]/}}e.exports=t,t.displayName="smalltalk",t.aliases=[]},64020(e,t,n){"use strict";var r=n(93205);function i(e){var t;e.register(r),(t=e).languages.smarty={comment:/\{\*[\s\S]*?\*\}/,delimiter:{pattern:/^\{|\}$/i,alias:"punctuation"},string:/(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/,number:/\b0x[\dA-Fa-f]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee][-+]?\d+)?/,variable:[/\$(?!\d)\w+/,/#(?!\d)\w+#/,{pattern:/(\.|->)(?!\d)\w+/,lookbehind:!0},{pattern:/(\[)(?!\d)\w+(?=\])/,lookbehind:!0}],function:[{pattern:/(\|\s*)@?(?!\d)\w+/,lookbehind:!0},/^\/?(?!\d)\w+/,/(?!\d)\w+(?=\()/],"attr-name":{pattern:/\w+\s*=\s*(?:(?!\d)\w+)?/,inside:{variable:{pattern:/(=\s*)(?!\d)\w+/,lookbehind:!0},operator:/=/}},punctuation:[/[\[\]().,:`]|->/],operator:[/[+\-*\/%]|==?=?|[!<>]=?|&&|\|\|?/,/\bis\s+(?:not\s+)?(?:div|even|odd)(?:\s+by)?\b/,/\b(?:eq|neq?|gt|lt|gt?e|lt?e|not|mod|or|and)\b/],keyword:/\b(?:false|off|on|no|true|yes)\b/},t.hooks.add("before-tokenize",function(e){var n=/\{\*[\s\S]*?\*\}|\{[\s\S]+?\}/g,r="{literal}",i="{/literal}",a=!1;t.languages["markup-templating"].buildPlaceholders(e,"smarty",n,function(e){return e===i&&(a=!1),!a&&(e===r&&(a=!0),!0)})}),t.hooks.add("after-tokenize",function(e){t.languages["markup-templating"].tokenizePlaceholders(e,"smarty")})}e.exports=i,i.displayName="smarty",i.aliases=[]},49760(e){"use strict";function t(e){var t,n;n=/\b(?:abstype|and|andalso|as|case|datatype|do|else|end|eqtype|exception|fn|fun|functor|handle|if|in|include|infix|infixr|let|local|nonfix|of|op|open|orelse|raise|rec|sharing|sig|signature|struct|structure|then|type|val|where|while|with|withtype)\b/i,(t=e).languages.sml={comment:/\(\*(?:[^*(]|\*(?!\))|\((?!\*)|\(\*(?:[^*(]|\*(?!\))|\((?!\*))*\*\))*\*\)/,string:{pattern:/#?"(?:[^"\\]|\\.)*"/,greedy:!0},"class-name":[{pattern:RegExp(/((?:^|[^:]):\s*)(?:\s*(?:(?:\*|->)\s*|,\s*(?:(?=)|(?!)\s+)))*/.source.replace(//g,function(){return/\s*(?:[*,]|->)/.source}).replace(//g,function(){return/(?:'[\w']*||\((?:[^()]|\([^()]*\))*\)|\{(?:[^{}]|\{[^{}]*\})*\})(?:\s+)*/.source}).replace(//g,function(){return/(?!)[a-z\d_][\w'.]*/.source}).replace(//g,function(){return n.source}),"i"),lookbehind:!0,greedy:!0,inside:null},{pattern:/((?:^|[^\w'])(?:datatype|exception|functor|signature|structure|type)\s+)[a-z_][\w'.]*/i,lookbehind:!0}],function:{pattern:/((?:^|[^\w'])fun\s+)[a-z_][\w'.]*/i,lookbehind:!0},keyword:n,variable:{pattern:/(^|[^\w'])'[\w']*/,lookbehind:!0},number:/~?\b(?:\d+(?:\.\d+)?(?:e~?\d+)?|0x[\da-f]+)\b/i,word:{pattern:/\b0w(?:\d+|x[\da-f]+)\b/i,alias:"constant"},boolean:/\b(?:false|true)\b/i,operator:/\.\.\.|:[>=:]|=>?|->|[<>]=?|[!+\-*/^#|@~]/,punctuation:/[(){}\[\].:,;]/},t.languages.sml["class-name"][0].inside=t.languages.sml,t.languages.smlnj=t.languages.sml}e.exports=t,t.displayName="sml",t.aliases=["smlnj"]},33351(e){"use strict";function t(e){e.languages.solidity=e.languages.extend("clike",{"class-name":{pattern:/(\b(?:contract|enum|interface|library|new|struct|using)\s+)(?!\d)[\w$]+/,lookbehind:!0},keyword:/\b(?:_|anonymous|as|assembly|assert|break|calldata|case|constant|constructor|continue|contract|default|delete|do|else|emit|enum|event|external|for|from|function|if|import|indexed|inherited|interface|internal|is|let|library|mapping|memory|modifier|new|payable|pragma|private|public|pure|require|returns?|revert|selfdestruct|solidity|storage|struct|suicide|switch|this|throw|using|var|view|while)\b/,operator:/=>|->|:=|=:|\*\*|\+\+|--|\|\||&&|<<=?|>>=?|[-+*/%^&|<>!=]=?|[~?]/}),e.languages.insertBefore("solidity","keyword",{builtin:/\b(?:address|bool|string|u?int(?:8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?|byte|bytes(?:[1-9]|[12]\d|3[0-2])?)\b/}),e.languages.insertBefore("solidity","number",{version:{pattern:/([<>]=?|\^)\d+\.\d+\.\d+\b/,lookbehind:!0,alias:"number"}}),e.languages.sol=e.languages.solidity}e.exports=t,t.displayName="solidity",t.aliases=["sol"]},13570(e){"use strict";function t(e){var t,n;n={pattern:/\{[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\}/i,alias:"constant",inside:{punctuation:/[{}]/}},(t=e).languages["solution-file"]={comment:{pattern:/#.*/,greedy:!0},string:{pattern:/"[^"\r\n]*"|'[^'\r\n]*'/,greedy:!0,inside:{guid:n}},object:{pattern:/^([ \t]*)(?:([A-Z]\w*)\b(?=.*(?:\r\n?|\n)(?:\1[ \t].*(?:\r\n?|\n))*\1End\2(?=[ \t]*$))|End[A-Z]\w*(?=[ \t]*$))/m,lookbehind:!0,greedy:!0,alias:"keyword"},property:{pattern:/^([ \t]*)(?!\s)[^\r\n"#=()]*[^\s"#=()](?=\s*=)/m,lookbehind:!0,inside:{guid:n}},guid:n,number:/\b\d+(?:\.\d+)*\b/,boolean:/\b(?:FALSE|TRUE)\b/,operator:/=/,punctuation:/[(),]/},t.languages.sln=t.languages["solution-file"]}e.exports=t,t.displayName="solutionFile",t.aliases=[]},38181(e,t,n){"use strict";var r=n(93205);function i(e){var t,n,i;e.register(r),n=/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,i=/\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b|\b0x[\dA-F]+\b/,(t=e).languages.soy={comment:[/\/\*[\s\S]*?\*\//,{pattern:/(\s)\/\/.*/,lookbehind:!0,greedy:!0}],"command-arg":{pattern:/(\{+\/?\s*(?:alias|call|delcall|delpackage|deltemplate|namespace|template)\s+)\.?[\w.]+/,lookbehind:!0,alias:"string",inside:{punctuation:/\./}},parameter:{pattern:/(\{+\/?\s*@?param\??\s+)\.?[\w.]+/,lookbehind:!0,alias:"variable"},keyword:[{pattern:/(\{+\/?[^\S\r\n]*)(?:\\[nrt]|alias|call|case|css|default|delcall|delpackage|deltemplate|else(?:if)?|fallbackmsg|for(?:each)?|if(?:empty)?|lb|let|literal|msg|namespace|nil|@?param\??|rb|sp|switch|template|xid)/,lookbehind:!0},/\b(?:any|as|attributes|bool|css|float|in|int|js|html|list|map|null|number|string|uri)\b/],delimiter:{pattern:/^\{+\/?|\/?\}+$/,alias:"punctuation"},property:/\w+(?==)/,variable:{pattern:/\$[^\W\d]\w*(?:\??(?:\.\w+|\[[^\]]+\]))*/,inside:{string:{pattern:n,greedy:!0},number:i,punctuation:/[\[\].?]/}},string:{pattern:n,greedy:!0},function:[/\w+(?=\()/,{pattern:/(\|[^\S\r\n]*)\w+/,lookbehind:!0}],boolean:/\b(?:true|false)\b/,number:i,operator:/\?:?|<=?|>=?|==?|!=|[+*/%-]|\b(?:and|not|or)\b/,punctuation:/[{}()\[\]|.,:]/},t.hooks.add("before-tokenize",function(e){var n=/\{\{.+?\}\}|\{.+?\}|\s\/\/.*|\/\*[\s\S]*?\*\//g,r="{literal}",i="{/literal}",a=!1;t.languages["markup-templating"].buildPlaceholders(e,"soy",n,function(e){return e===i&&(a=!1),!a&&(e===r&&(a=!0),!0)})}),t.hooks.add("after-tokenize",function(e){t.languages["markup-templating"].tokenizePlaceholders(e,"soy")})}e.exports=i,i.displayName="soy",i.aliases=[]},98774(e,t,n){"use strict";var r=n(24691);function i(e){e.register(r),e.languages.sparql=e.languages.extend("turtle",{boolean:/\b(?:true|false)\b/i,variable:{pattern:/[?$]\w+/,greedy:!0}}),e.languages.insertBefore("sparql","punctuation",{keyword:[/\b(?:A|ADD|ALL|AS|ASC|ASK|BNODE|BY|CLEAR|CONSTRUCT|COPY|CREATE|DATA|DEFAULT|DELETE|DESC|DESCRIBE|DISTINCT|DROP|EXISTS|FILTER|FROM|GROUP|HAVING|INSERT|INTO|LIMIT|LOAD|MINUS|MOVE|NAMED|NOT|NOW|OFFSET|OPTIONAL|ORDER|RAND|REDUCED|SELECT|SEPARATOR|SERVICE|SILENT|STRUUID|UNION|USING|UUID|VALUES|WHERE)\b/i,/\b(?:ABS|AVG|BIND|BOUND|CEIL|COALESCE|CONCAT|CONTAINS|COUNT|DATATYPE|DAY|ENCODE_FOR_URI|FLOOR|GROUP_CONCAT|HOURS|IF|IRI|isBLANK|isIRI|isLITERAL|isNUMERIC|isURI|LANG|LANGMATCHES|LCASE|MAX|MD5|MIN|MINUTES|MONTH|ROUND|REGEX|REPLACE|sameTerm|SAMPLE|SECONDS|SHA1|SHA256|SHA384|SHA512|STR|STRAFTER|STRBEFORE|STRDT|STRENDS|STRLANG|STRLEN|STRSTARTS|SUBSTR|SUM|TIMEZONE|TZ|UCASE|URI|YEAR)\b(?=\s*\()/i,/\b(?:GRAPH|BASE|PREFIX)\b/i]}),e.languages.rq=e.languages.sparql}e.exports=i,i.displayName="sparql",i.aliases=["rq"]},22855(e){"use strict";function t(e){e.languages["splunk-spl"]={comment:/`comment\("(?:\\.|[^\\"])*"\)`/,string:{pattern:/"(?:\\.|[^\\"])*"/,greedy:!0},keyword:/\b(?:abstract|accum|addcoltotals|addinfo|addtotals|analyzefields|anomalies|anomalousvalue|anomalydetection|append|appendcols|appendcsv|appendlookup|appendpipe|arules|associate|audit|autoregress|bin|bucket|bucketdir|chart|cluster|cofilter|collect|concurrency|contingency|convert|correlate|datamodel|dbinspect|dedup|delete|delta|diff|erex|eval|eventcount|eventstats|extract|fieldformat|fields|fieldsummary|filldown|fillnull|findtypes|folderize|foreach|format|from|gauge|gentimes|geom|geomfilter|geostats|head|highlight|history|iconify|input|inputcsv|inputlookup|iplocation|join|kmeans|kv|kvform|loadjob|localize|localop|lookup|makecontinuous|makemv|makeresults|map|mcollect|metadata|metasearch|meventcollect|mstats|multikv|multisearch|mvcombine|mvexpand|nomv|outlier|outputcsv|outputlookup|outputtext|overlap|pivot|predict|rangemap|rare|regex|relevancy|reltime|rename|replace|rest|return|reverse|rex|rtorder|run|savedsearch|script|scrub|search|searchtxn|selfjoin|sendemail|set|setfields|sichart|sirare|sistats|sitimechart|sitop|sort|spath|stats|strcat|streamstats|table|tags|tail|timechart|timewrap|top|transaction|transpose|trendline|tscollect|tstats|typeahead|typelearner|typer|union|uniq|untable|where|x11|xmlkv|xmlunescape|xpath|xyseries)\b/i,"operator-word":{pattern:/\b(?:and|as|by|not|or|xor)\b/i,alias:"operator"},function:/\b\w+(?=\s*\()/,property:/\b\w+(?=\s*=(?!=))/,date:{pattern:/\b\d{1,2}\/\d{1,2}\/\d{1,4}(?:(?::\d{1,2}){3})?\b/,alias:"number"},number:/\b\d+(?:\.\d+)?\b/,boolean:/\b(?:f|false|t|true)\b/i,operator:/[<>=]=?|[-+*/%|]/,punctuation:/[()[\],]/}}e.exports=t,t.displayName="splunkSpl",t.aliases=[]},29611(e){"use strict";function t(e){e.languages.sqf=e.languages.extend("clike",{string:{pattern:/"(?:(?:"")?[^"])*"(?!")|'(?:[^'])*'/,greedy:!0},keyword:/\b(?:breakOut|breakTo|call|case|catch|default|do|echo|else|execVM|execFSM|exitWith|for|forEach|forEachMember|forEachMemberAgent|forEachMemberTeam|from|goto|if|nil|preprocessFile|preprocessFileLineNumbers|private|scopeName|spawn|step|switch|then|throw|to|try|while|with)\b/i,boolean:/\b(?:true|false)\b/i,function:/\b(?:abs|accTime|acos|action|actionIDs|actionKeys|actionKeysImages|actionKeysNames|actionKeysNamesArray|actionName|actionParams|activateAddons|activatedAddons|activateKey|add3DENConnection|add3DENEventHandler|add3DENLayer|addAction|addBackpack|addBackpackCargo|addBackpackCargoGlobal|addBackpackGlobal|addCamShake|addCuratorAddons|addCuratorCameraArea|addCuratorEditableObjects|addCuratorEditingArea|addCuratorPoints|addEditorObject|addEventHandler|addForce|addForceGeneratorRTD|addGoggles|addGroupIcon|addHandgunItem|addHeadgear|addItem|addItemCargo|addItemCargoGlobal|addItemPool|addItemToBackpack|addItemToUniform|addItemToVest|addLiveStats|addMagazine|addMagazineAmmoCargo|addMagazineCargo|addMagazineCargoGlobal|addMagazineGlobal|addMagazinePool|addMagazines|addMagazineTurret|addMenu|addMenuItem|addMissionEventHandler|addMPEventHandler|addMusicEventHandler|addOwnedMine|addPlayerScores|addPrimaryWeaponItem|addPublicVariableEventHandler|addRating|addResources|addScore|addScoreSide|addSecondaryWeaponItem|addSwitchableUnit|addTeamMember|addToRemainsCollector|addTorque|addUniform|addVehicle|addVest|addWaypoint|addWeapon|addWeaponCargo|addWeaponCargoGlobal|addWeaponGlobal|addWeaponItem|addWeaponPool|addWeaponTurret|admin|agent|agents|AGLToASL|aimedAtTarget|aimPos|airDensityCurveRTD|airDensityRTD|airplaneThrottle|airportSide|AISFinishHeal|alive|all3DENEntities|allAirports|allControls|allCurators|allCutLayers|allDead|allDeadMen|allDisplays|allGroups|allMapMarkers|allMines|allMissionObjects|allow3DMode|allowCrewInImmobile|allowCuratorLogicIgnoreAreas|allowDamage|allowDammage|allowFileOperations|allowFleeing|allowGetIn|allowSprint|allPlayers|allSimpleObjects|allSites|allTurrets|allUnits|allUnitsUAV|allVariables|ammo|ammoOnPylon|animate|animateBay|animateDoor|animatePylon|animateSource|animationNames|animationPhase|animationSourcePhase|animationState|append|apply|armoryPoints|arrayIntersect|asin|ASLToAGL|ASLToATL|assert|assignAsCargo|assignAsCargoIndex|assignAsCommander|assignAsDriver|assignAsGunner|assignAsTurret|assignCurator|assignedCargo|assignedCommander|assignedDriver|assignedGunner|assignedItems|assignedTarget|assignedTeam|assignedVehicle|assignedVehicleRole|assignItem|assignTeam|assignToAirport|atan|atan2|atg|ATLToASL|attachedObject|attachedObjects|attachedTo|attachObject|attachTo|attackEnabled|backpack|backpackCargo|backpackContainer|backpackItems|backpackMagazines|backpackSpaceFor|behaviour|benchmark|binocular|blufor|boundingBox|boundingBoxReal|boundingCenter|briefingName|buildingExit|buildingPos|buldozer_EnableRoadDiag|buldozer_IsEnabledRoadDiag|buldozer_LoadNewRoads|buldozer_reloadOperMap|buttonAction|buttonSetAction|cadetMode|callExtension|camCommand|camCommit|camCommitPrepared|camCommitted|camConstuctionSetParams|camCreate|camDestroy|cameraEffect|cameraEffectEnableHUD|cameraInterest|cameraOn|cameraView|campaignConfigFile|camPreload|camPreloaded|camPrepareBank|camPrepareDir|camPrepareDive|camPrepareFocus|camPrepareFov|camPrepareFovRange|camPreparePos|camPrepareRelPos|camPrepareTarget|camSetBank|camSetDir|camSetDive|camSetFocus|camSetFov|camSetFovRange|camSetPos|camSetRelPos|camSetTarget|camTarget|camUseNVG|canAdd|canAddItemToBackpack|canAddItemToUniform|canAddItemToVest|cancelSimpleTaskDestination|canFire|canMove|canSlingLoad|canStand|canSuspend|canTriggerDynamicSimulation|canUnloadInCombat|canVehicleCargo|captive|captiveNum|cbChecked|cbSetChecked|ceil|channelEnabled|cheatsEnabled|checkAIFeature|checkVisibility|civilian|className|clear3DENAttribute|clear3DENInventory|clearAllItemsFromBackpack|clearBackpackCargo|clearBackpackCargoGlobal|clearForcesRTD|clearGroupIcons|clearItemCargo|clearItemCargoGlobal|clearItemPool|clearMagazineCargo|clearMagazineCargoGlobal|clearMagazinePool|clearOverlay|clearRadio|clearVehicleInit|clearWeaponCargo|clearWeaponCargoGlobal|clearWeaponPool|clientOwner|closeDialog|closeDisplay|closeOverlay|collapseObjectTree|collect3DENHistory|collectiveRTD|combatMode|commandArtilleryFire|commandChat|commander|commandFire|commandFollow|commandFSM|commandGetOut|commandingMenu|commandMove|commandRadio|commandStop|commandSuppressiveFire|commandTarget|commandWatch|comment|commitOverlay|compile|compileFinal|completedFSM|composeText|configClasses|configFile|configHierarchy|configName|configNull|configProperties|configSourceAddonList|configSourceMod|configSourceModList|confirmSensorTarget|connectTerminalToUAV|controlNull|controlsGroupCtrl|copyFromClipboard|copyToClipboard|copyWaypoints|cos|count|countEnemy|countFriendly|countSide|countType|countUnknown|create3DENComposition|create3DENEntity|createAgent|createCenter|createDialog|createDiaryLink|createDiaryRecord|createDiarySubject|createDisplay|createGearDialog|createGroup|createGuardedPoint|createLocation|createMarker|createMarkerLocal|createMenu|createMine|createMissionDisplay|createMPCampaignDisplay|createSimpleObject|createSimpleTask|createSite|createSoundSource|createTask|createTeam|createTrigger|createUnit|createVehicle|createVehicleCrew|createVehicleLocal|crew|ctAddHeader|ctAddRow|ctClear|ctCurSel|ctData|ctFindHeaderRows|ctFindRowHeader|ctHeaderControls|ctHeaderCount|ctRemoveHeaders|ctRemoveRows|ctrlActivate|ctrlAddEventHandler|ctrlAngle|ctrlAutoScrollDelay|ctrlAutoScrollRewind|ctrlAutoScrollSpeed|ctrlChecked|ctrlClassName|ctrlCommit|ctrlCommitted|ctrlCreate|ctrlDelete|ctrlEnable|ctrlEnabled|ctrlFade|ctrlHTMLLoaded|ctrlIDC|ctrlIDD|ctrlMapAnimAdd|ctrlMapAnimClear|ctrlMapAnimCommit|ctrlMapAnimDone|ctrlMapCursor|ctrlMapMouseOver|ctrlMapScale|ctrlMapScreenToWorld|ctrlMapWorldToScreen|ctrlModel|ctrlModelDirAndUp|ctrlModelScale|ctrlParent|ctrlParentControlsGroup|ctrlPosition|ctrlRemoveAllEventHandlers|ctrlRemoveEventHandler|ctrlScale|ctrlSetActiveColor|ctrlSetAngle|ctrlSetAutoScrollDelay|ctrlSetAutoScrollRewind|ctrlSetAutoScrollSpeed|ctrlSetBackgroundColor|ctrlSetChecked|ctrlSetDisabledColor|ctrlSetEventHandler|ctrlSetFade|ctrlSetFocus|ctrlSetFont|ctrlSetFontH1|ctrlSetFontH1B|ctrlSetFontH2|ctrlSetFontH2B|ctrlSetFontH3|ctrlSetFontH3B|ctrlSetFontH4|ctrlSetFontH4B|ctrlSetFontH5|ctrlSetFontH5B|ctrlSetFontH6|ctrlSetFontH6B|ctrlSetFontHeight|ctrlSetFontHeightH1|ctrlSetFontHeightH2|ctrlSetFontHeightH3|ctrlSetFontHeightH4|ctrlSetFontHeightH5|ctrlSetFontHeightH6|ctrlSetFontHeightSecondary|ctrlSetFontP|ctrlSetFontPB|ctrlSetFontSecondary|ctrlSetForegroundColor|ctrlSetModel|ctrlSetModelDirAndUp|ctrlSetModelScale|ctrlSetPixelPrecision|ctrlSetPosition|ctrlSetScale|ctrlSetStructuredText|ctrlSetText|ctrlSetTextColor|ctrlSetTextColorSecondary|ctrlSetTextSecondary|ctrlSetTooltip|ctrlSetTooltipColorBox|ctrlSetTooltipColorShade|ctrlSetTooltipColorText|ctrlShow|ctrlShown|ctrlText|ctrlTextHeight|ctrlTextSecondary|ctrlTextWidth|ctrlType|ctrlVisible|ctRowControls|ctRowCount|ctSetCurSel|ctSetData|ctSetHeaderTemplate|ctSetRowTemplate|ctSetValue|ctValue|curatorAddons|curatorCamera|curatorCameraArea|curatorCameraAreaCeiling|curatorCoef|curatorEditableObjects|curatorEditingArea|curatorEditingAreaType|curatorMouseOver|curatorPoints|curatorRegisteredObjects|curatorSelected|curatorWaypointCost|current3DENOperation|currentChannel|currentCommand|currentMagazine|currentMagazineDetail|currentMagazineDetailTurret|currentMagazineTurret|currentMuzzle|currentNamespace|currentTask|currentTasks|currentThrowable|currentVisionMode|currentWaypoint|currentWeapon|currentWeaponMode|currentWeaponTurret|currentZeroing|cursorObject|cursorTarget|customChat|customRadio|cutFadeOut|cutObj|cutRsc|cutText|damage|date|dateToNumber|daytime|deActivateKey|debriefingText|debugFSM|debugLog|deg|delete3DENEntities|deleteAt|deleteCenter|deleteCollection|deleteEditorObject|deleteGroup|deleteGroupWhenEmpty|deleteIdentity|deleteLocation|deleteMarker|deleteMarkerLocal|deleteRange|deleteResources|deleteSite|deleteStatus|deleteTeam|deleteVehicle|deleteVehicleCrew|deleteWaypoint|detach|detectedMines|diag_activeMissionFSMs|diag_activeScripts|diag_activeSQFScripts|diag_activeSQSScripts|diag_captureFrame|diag_captureFrameToFile|diag_captureSlowFrame|diag_codePerformance|diag_drawMode|diag_dynamicSimulationEnd|diag_enable|diag_enabled|diag_fps|diag_fpsMin|diag_frameNo|diag_lightNewLoad|diag_list|diag_log|diag_logSlowFrame|diag_mergeConfigFile|diag_recordTurretLimits|diag_setLightNew|diag_tickTime|diag_toggle|dialog|diarySubjectExists|didJIP|didJIPOwner|difficulty|difficultyEnabled|difficultyEnabledRTD|difficultyOption|direction|directSay|disableAI|disableCollisionWith|disableConversation|disableDebriefingStats|disableMapIndicators|disableNVGEquipment|disableRemoteSensors|disableSerialization|disableTIEquipment|disableUAVConnectability|disableUserInput|displayAddEventHandler|displayCtrl|displayNull|displayParent|displayRemoveAllEventHandlers|displayRemoveEventHandler|displaySetEventHandler|dissolveTeam|distance|distance2D|distanceSqr|distributionRegion|do3DENAction|doArtilleryFire|doFire|doFollow|doFSM|doGetOut|doMove|doorPhase|doStop|doSuppressiveFire|doTarget|doWatch|drawArrow|drawEllipse|drawIcon|drawIcon3D|drawLine|drawLine3D|drawLink|drawLocation|drawPolygon|drawRectangle|drawTriangle|driver|drop|dynamicSimulationDistance|dynamicSimulationDistanceCoef|dynamicSimulationEnabled|dynamicSimulationSystemEnabled|east|edit3DENMissionAttributes|editObject|editorSetEventHandler|effectiveCommander|emptyPositions|enableAI|enableAIFeature|enableAimPrecision|enableAttack|enableAudioFeature|enableAutoStartUpRTD|enableAutoTrimRTD|enableCamShake|enableCaustics|enableChannel|enableCollisionWith|enableCopilot|enableDebriefingStats|enableDiagLegend|enableDynamicSimulation|enableDynamicSimulationSystem|enableEndDialog|enableEngineArtillery|enableEnvironment|enableFatigue|enableGunLights|enableInfoPanelComponent|enableIRLasers|enableMimics|enablePersonTurret|enableRadio|enableReload|enableRopeAttach|enableSatNormalOnDetail|enableSaving|enableSentences|enableSimulation|enableSimulationGlobal|enableStamina|enableStressDamage|enableTeamSwitch|enableTraffic|enableUAVConnectability|enableUAVWaypoints|enableVehicleCargo|enableVehicleSensor|enableWeaponDisassembly|endl|endLoadingScreen|endMission|engineOn|enginesIsOnRTD|enginesPowerRTD|enginesRpmRTD|enginesTorqueRTD|entities|environmentEnabled|estimatedEndServerTime|estimatedTimeLeft|evalObjectArgument|everyBackpack|everyContainer|exec|execEditorScript|exp|expectedDestination|exportJIPMessages|eyeDirection|eyePos|face|faction|fadeMusic|fadeRadio|fadeSound|fadeSpeech|failMission|fillWeaponsFromPool|find|findCover|findDisplay|findEditorObject|findEmptyPosition|findEmptyPositionReady|findIf|findNearestEnemy|finishMissionInit|finite|fire|fireAtTarget|firstBackpack|flag|flagAnimationPhase|flagOwner|flagSide|flagTexture|fleeing|floor|flyInHeight|flyInHeightASL|fog|fogForecast|fogParams|forceAddUniform|forceAtPositionRTD|forcedMap|forceEnd|forceFlagTexture|forceFollowRoad|forceGeneratorRTD|forceMap|forceRespawn|forceSpeed|forceWalk|forceWeaponFire|forceWeatherChange|forgetTarget|format|formation|formationDirection|formationLeader|formationMembers|formationPosition|formationTask|formatText|formLeader|freeLook|fromEditor|fuel|fullCrew|gearIDCAmmoCount|gearSlotAmmoCount|gearSlotData|get3DENActionState|get3DENAttribute|get3DENCamera|get3DENConnections|get3DENEntity|get3DENEntityID|get3DENGrid|get3DENIconsVisible|get3DENLayerEntities|get3DENLinesVisible|get3DENMissionAttribute|get3DENMouseOver|get3DENSelected|getAimingCoef|getAllEnvSoundControllers|getAllHitPointsDamage|getAllOwnedMines|getAllSoundControllers|getAmmoCargo|getAnimAimPrecision|getAnimSpeedCoef|getArray|getArtilleryAmmo|getArtilleryComputerSettings|getArtilleryETA|getAssignedCuratorLogic|getAssignedCuratorUnit|getBackpackCargo|getBleedingRemaining|getBurningValue|getCameraViewDirection|getCargoIndex|getCenterOfMass|getClientState|getClientStateNumber|getCompatiblePylonMagazines|getConnectedUAV|getContainerMaxLoad|getCursorObjectParams|getCustomAimCoef|getDammage|getDescription|getDir|getDirVisual|getDLCAssetsUsage|getDLCAssetsUsageByName|getDLCs|getDLCUsageTime|getEditorCamera|getEditorMode|getEditorObjectScope|getElevationOffset|getEngineTargetRpmRTD|getEnvSoundController|getFatigue|getFieldManualStartPage|getForcedFlagTexture|getFriend|getFSMVariable|getFuelCargo|getGroupIcon|getGroupIconParams|getGroupIcons|getHideFrom|getHit|getHitIndex|getHitPointDamage|getItemCargo|getMagazineCargo|getMarkerColor|getMarkerPos|getMarkerSize|getMarkerType|getMass|getMissionConfig|getMissionConfigValue|getMissionDLCs|getMissionLayerEntities|getMissionLayers|getModelInfo|getMousePosition|getMusicPlayedTime|getNumber|getObjectArgument|getObjectChildren|getObjectDLC|getObjectMaterials|getObjectProxy|getObjectTextures|getObjectType|getObjectViewDistance|getOxygenRemaining|getPersonUsedDLCs|getPilotCameraDirection|getPilotCameraPosition|getPilotCameraRotation|getPilotCameraTarget|getPlateNumber|getPlayerChannel|getPlayerScores|getPlayerUID|getPlayerUIDOld|getPos|getPosASL|getPosASLVisual|getPosASLW|getPosATL|getPosATLVisual|getPosVisual|getPosWorld|getPylonMagazines|getRelDir|getRelPos|getRemoteSensorsDisabled|getRepairCargo|getResolution|getRotorBrakeRTD|getShadowDistance|getShotParents|getSlingLoad|getSoundController|getSoundControllerResult|getSpeed|getStamina|getStatValue|getSuppression|getTerrainGrid|getTerrainHeightASL|getText|getTotalDLCUsageTime|getTrimOffsetRTD|getUnitLoadout|getUnitTrait|getUserMFDText|getUserMFDValue|getVariable|getVehicleCargo|getWeaponCargo|getWeaponSway|getWingsOrientationRTD|getWingsPositionRTD|getWPPos|glanceAt|globalChat|globalRadio|goggles|group|groupChat|groupFromNetId|groupIconSelectable|groupIconsVisible|groupId|groupOwner|groupRadio|groupSelectedUnits|groupSelectUnit|grpNull|gunner|gusts|halt|handgunItems|handgunMagazine|handgunWeapon|handsHit|hasInterface|hasPilotCamera|hasWeapon|hcAllGroups|hcGroupParams|hcLeader|hcRemoveAllGroups|hcRemoveGroup|hcSelected|hcSelectGroup|hcSetGroup|hcShowBar|hcShownBar|headgear|hideBody|hideObject|hideObjectGlobal|hideSelection|hint|hintC|hintCadet|hintSilent|hmd|hostMission|htmlLoad|HUDMovementLevels|humidity|image|importAllGroups|importance|in|inArea|inAreaArray|incapacitatedState|independent|inflame|inflamed|infoPanel|infoPanelComponentEnabled|infoPanelComponents|infoPanels|inGameUISetEventHandler|inheritsFrom|initAmbientLife|inPolygon|inputAction|inRangeOfArtillery|insertEditorObject|intersect|is3DEN|is3DENMultiplayer|isAbleToBreathe|isAgent|isAimPrecisionEnabled|isArray|isAutoHoverOn|isAutonomous|isAutoStartUpEnabledRTD|isAutotest|isAutoTrimOnRTD|isBleeding|isBurning|isClass|isCollisionLightOn|isCopilotEnabled|isDamageAllowed|isDedicated|isDLCAvailable|isEngineOn|isEqualTo|isEqualType|isEqualTypeAll|isEqualTypeAny|isEqualTypeArray|isEqualTypeParams|isFilePatchingEnabled|isFlashlightOn|isFlatEmpty|isForcedWalk|isFormationLeader|isGroupDeletedWhenEmpty|isHidden|isInRemainsCollector|isInstructorFigureEnabled|isIRLaserOn|isKeyActive|isKindOf|isLaserOn|isLightOn|isLocalized|isManualFire|isMarkedForCollection|isMultiplayer|isMultiplayerSolo|isNil|isNull|isNumber|isObjectHidden|isObjectRTD|isOnRoad|isPipEnabled|isPlayer|isRealTime|isRemoteExecuted|isRemoteExecutedJIP|isServer|isShowing3DIcons|isSimpleObject|isSprintAllowed|isStaminaEnabled|isSteamMission|isStreamFriendlyUIEnabled|isStressDamageEnabled|isText|isTouchingGround|isTurnedOut|isTutHintsEnabled|isUAVConnectable|isUAVConnected|isUIContext|isUniformAllowed|isVehicleCargo|isVehicleRadarOn|isVehicleSensorEnabled|isWalking|isWeaponDeployed|isWeaponRested|itemCargo|items|itemsWithMagazines|join|joinAs|joinAsSilent|joinSilent|joinString|kbAddDatabase|kbAddDatabaseTargets|kbAddTopic|kbHasTopic|kbReact|kbRemoveTopic|kbTell|kbWasSaid|keyImage|keyName|knowsAbout|land|landAt|landResult|language|laserTarget|lbAdd|lbClear|lbColor|lbColorRight|lbCurSel|lbData|lbDelete|lbIsSelected|lbPicture|lbPictureRight|lbSelection|lbSetColor|lbSetColorRight|lbSetCurSel|lbSetData|lbSetPicture|lbSetPictureColor|lbSetPictureColorDisabled|lbSetPictureColorSelected|lbSetPictureRight|lbSetPictureRightColor|lbSetPictureRightColorDisabled|lbSetPictureRightColorSelected|lbSetSelectColor|lbSetSelectColorRight|lbSetSelected|lbSetText|lbSetTextRight|lbSetTooltip|lbSetValue|lbSize|lbSort|lbSortByValue|lbText|lbTextRight|lbValue|leader|leaderboardDeInit|leaderboardGetRows|leaderboardInit|leaderboardRequestRowsFriends|leaderboardRequestRowsGlobal|leaderboardRequestRowsGlobalAroundUser|leaderboardsRequestUploadScore|leaderboardsRequestUploadScoreKeepBest|leaderboardState|leaveVehicle|libraryCredits|libraryDisclaimers|lifeState|lightAttachObject|lightDetachObject|lightIsOn|lightnings|limitSpeed|linearConversion|lineBreak|lineIntersects|lineIntersectsObjs|lineIntersectsSurfaces|lineIntersectsWith|linkItem|list|listObjects|listRemoteTargets|listVehicleSensors|ln|lnbAddArray|lnbAddColumn|lnbAddRow|lnbClear|lnbColor|lnbColorRight|lnbCurSelRow|lnbData|lnbDeleteColumn|lnbDeleteRow|lnbGetColumnsPosition|lnbPicture|lnbPictureRight|lnbSetColor|lnbSetColorRight|lnbSetColumnsPos|lnbSetCurSelRow|lnbSetData|lnbSetPicture|lnbSetPictureColor|lnbSetPictureColorRight|lnbSetPictureColorSelected|lnbSetPictureColorSelectedRight|lnbSetPictureRight|lnbSetText|lnbSetTextRight|lnbSetValue|lnbSize|lnbSort|lnbSortByValue|lnbText|lnbTextRight|lnbValue|load|loadAbs|loadBackpack|loadFile|loadGame|loadIdentity|loadMagazine|loadOverlay|loadStatus|loadUniform|loadVest|local|localize|locationNull|locationPosition|lock|lockCameraTo|lockCargo|lockDriver|locked|lockedCargo|lockedDriver|lockedTurret|lockIdentity|lockTurret|lockWP|log|logEntities|logNetwork|logNetworkTerminate|lookAt|lookAtPos|magazineCargo|magazines|magazinesAllTurrets|magazinesAmmo|magazinesAmmoCargo|magazinesAmmoFull|magazinesDetail|magazinesDetailBackpack|magazinesDetailUniform|magazinesDetailVest|magazinesTurret|magazineTurretAmmo|mapAnimAdd|mapAnimClear|mapAnimCommit|mapAnimDone|mapCenterOnCamera|mapGridPosition|markAsFinishedOnSteam|markerAlpha|markerBrush|markerColor|markerDir|markerPos|markerShape|markerSize|markerText|markerType|max|members|menuAction|menuAdd|menuChecked|menuClear|menuCollapse|menuData|menuDelete|menuEnable|menuEnabled|menuExpand|menuHover|menuPicture|menuSetAction|menuSetCheck|menuSetData|menuSetPicture|menuSetValue|menuShortcut|menuShortcutText|menuSize|menuSort|menuText|menuURL|menuValue|min|mineActive|mineDetectedBy|missionConfigFile|missionDifficulty|missionName|missionNamespace|missionStart|missionVersion|modelToWorld|modelToWorldVisual|modelToWorldVisualWorld|modelToWorldWorld|modParams|moonIntensity|moonPhase|morale|move|move3DENCamera|moveInAny|moveInCargo|moveInCommander|moveInDriver|moveInGunner|moveInTurret|moveObjectToEnd|moveOut|moveTime|moveTo|moveToCompleted|moveToFailed|musicVolume|name|nameSound|nearEntities|nearestBuilding|nearestLocation|nearestLocations|nearestLocationWithDubbing|nearestObject|nearestObjects|nearestTerrainObjects|nearObjects|nearObjectsReady|nearRoads|nearSupplies|nearTargets|needReload|netId|netObjNull|newOverlay|nextMenuItemIndex|nextWeatherChange|nMenuItems|numberOfEnginesRTD|numberToDate|objectCurators|objectFromNetId|objectParent|objNull|objStatus|onBriefingGear|onBriefingGroup|onBriefingNotes|onBriefingPlan|onBriefingTeamSwitch|onCommandModeChanged|onDoubleClick|onEachFrame|onGroupIconClick|onGroupIconOverEnter|onGroupIconOverLeave|onHCGroupSelectionChanged|onMapSingleClick|onPlayerConnected|onPlayerDisconnected|onPreloadFinished|onPreloadStarted|onShowNewObject|onTeamSwitch|openCuratorInterface|openDLCPage|openDSInterface|openMap|openSteamApp|openYoutubeVideo|opfor|orderGetIn|overcast|overcastForecast|owner|param|params|parseNumber|parseSimpleArray|parseText|parsingNamespace|particlesQuality|pi|pickWeaponPool|pitch|pixelGrid|pixelGridBase|pixelGridNoUIScale|pixelH|pixelW|playableSlotsNumber|playableUnits|playAction|playActionNow|player|playerRespawnTime|playerSide|playersNumber|playGesture|playMission|playMove|playMoveNow|playMusic|playScriptedMission|playSound|playSound3D|position|positionCameraToWorld|posScreenToWorld|posWorldToScreen|ppEffectAdjust|ppEffectCommit|ppEffectCommitted|ppEffectCreate|ppEffectDestroy|ppEffectEnable|ppEffectEnabled|ppEffectForceInNVG|precision|preloadCamera|preloadObject|preloadSound|preloadTitleObj|preloadTitleRsc|primaryWeapon|primaryWeaponItems|primaryWeaponMagazine|priority|processDiaryLink|processInitCommands|productVersion|profileName|profileNamespace|profileNameSteam|progressLoadingScreen|progressPosition|progressSetPosition|publicVariable|publicVariableClient|publicVariableServer|pushBack|pushBackUnique|putWeaponPool|queryItemsPool|queryMagazinePool|queryWeaponPool|rad|radioChannelAdd|radioChannelCreate|radioChannelRemove|radioChannelSetCallSign|radioChannelSetLabel|radioVolume|rain|rainbow|random|rank|rankId|rating|rectangular|registeredTasks|registerTask|reload|reloadEnabled|remoteControl|remoteExec|remoteExecCall|remoteExecutedOwner|remove3DENConnection|remove3DENEventHandler|remove3DENLayer|removeAction|removeAll3DENEventHandlers|removeAllActions|removeAllAssignedItems|removeAllContainers|removeAllCuratorAddons|removeAllCuratorCameraAreas|removeAllCuratorEditingAreas|removeAllEventHandlers|removeAllHandgunItems|removeAllItems|removeAllItemsWithMagazines|removeAllMissionEventHandlers|removeAllMPEventHandlers|removeAllMusicEventHandlers|removeAllOwnedMines|removeAllPrimaryWeaponItems|removeAllWeapons|removeBackpack|removeBackpackGlobal|removeCuratorAddons|removeCuratorCameraArea|removeCuratorEditableObjects|removeCuratorEditingArea|removeDrawIcon|removeDrawLinks|removeEventHandler|removeFromRemainsCollector|removeGoggles|removeGroupIcon|removeHandgunItem|removeHeadgear|removeItem|removeItemFromBackpack|removeItemFromUniform|removeItemFromVest|removeItems|removeMagazine|removeMagazineGlobal|removeMagazines|removeMagazinesTurret|removeMagazineTurret|removeMenuItem|removeMissionEventHandler|removeMPEventHandler|removeMusicEventHandler|removeOwnedMine|removePrimaryWeaponItem|removeSecondaryWeaponItem|removeSimpleTask|removeSwitchableUnit|removeTeamMember|removeUniform|removeVest|removeWeapon|removeWeaponAttachmentCargo|removeWeaponCargo|removeWeaponGlobal|removeWeaponTurret|reportRemoteTarget|requiredVersion|resetCamShake|resetSubgroupDirection|resistance|resize|resources|respawnVehicle|restartEditorCamera|reveal|revealMine|reverse|reversedMouseY|roadAt|roadsConnectedTo|roleDescription|ropeAttachedObjects|ropeAttachedTo|ropeAttachEnabled|ropeAttachTo|ropeCreate|ropeCut|ropeDestroy|ropeDetach|ropeEndPosition|ropeLength|ropes|ropeUnwind|ropeUnwound|rotorsForcesRTD|rotorsRpmRTD|round|runInitScript|safeZoneH|safeZoneW|safeZoneWAbs|safeZoneX|safeZoneXAbs|safeZoneY|save3DENInventory|saveGame|saveIdentity|saveJoysticks|saveOverlay|saveProfileNamespace|saveStatus|saveVar|savingEnabled|say|say2D|say3D|score|scoreSide|screenshot|screenToWorld|scriptDone|scriptName|scriptNull|scudState|secondaryWeapon|secondaryWeaponItems|secondaryWeaponMagazine|select|selectBestPlaces|selectDiarySubject|selectedEditorObjects|selectEditorObject|selectionNames|selectionPosition|selectLeader|selectMax|selectMin|selectNoPlayer|selectPlayer|selectRandom|selectRandomWeighted|selectWeapon|selectWeaponTurret|sendAUMessage|sendSimpleCommand|sendTask|sendTaskResult|sendUDPMessage|serverCommand|serverCommandAvailable|serverCommandExecutable|serverName|serverTime|set|set3DENAttribute|set3DENAttributes|set3DENGrid|set3DENIconsVisible|set3DENLayer|set3DENLinesVisible|set3DENLogicType|set3DENMissionAttribute|set3DENMissionAttributes|set3DENModelsVisible|set3DENObjectType|set3DENSelected|setAccTime|setActualCollectiveRTD|setAirplaneThrottle|setAirportSide|setAmmo|setAmmoCargo|setAmmoOnPylon|setAnimSpeedCoef|setAperture|setApertureNew|setArmoryPoints|setAttributes|setAutonomous|setBehaviour|setBleedingRemaining|setBrakesRTD|setCameraInterest|setCamShakeDefParams|setCamShakeParams|setCamUseTI|setCaptive|setCenterOfMass|setCollisionLight|setCombatMode|setCompassOscillation|setConvoySeparation|setCuratorCameraAreaCeiling|setCuratorCoef|setCuratorEditingAreaType|setCuratorWaypointCost|setCurrentChannel|setCurrentTask|setCurrentWaypoint|setCustomAimCoef|setCustomWeightRTD|setDamage|setDammage|setDate|setDebriefingText|setDefaultCamera|setDestination|setDetailMapBlendPars|setDir|setDirection|setDrawIcon|setDriveOnPath|setDropInterval|setDynamicSimulationDistance|setDynamicSimulationDistanceCoef|setEditorMode|setEditorObjectScope|setEffectCondition|setEngineRpmRTD|setFace|setFaceAnimation|setFatigue|setFeatureType|setFlagAnimationPhase|setFlagOwner|setFlagSide|setFlagTexture|setFog|setForceGeneratorRTD|setFormation|setFormationTask|setFormDir|setFriend|setFromEditor|setFSMVariable|setFuel|setFuelCargo|setGroupIcon|setGroupIconParams|setGroupIconsSelectable|setGroupIconsVisible|setGroupId|setGroupIdGlobal|setGroupOwner|setGusts|setHideBehind|setHit|setHitIndex|setHitPointDamage|setHorizonParallaxCoef|setHUDMovementLevels|setIdentity|setImportance|setInfoPanel|setLeader|setLightAmbient|setLightAttenuation|setLightBrightness|setLightColor|setLightDayLight|setLightFlareMaxDistance|setLightFlareSize|setLightIntensity|setLightnings|setLightUseFlare|setLocalWindParams|setMagazineTurretAmmo|setMarkerAlpha|setMarkerAlphaLocal|setMarkerBrush|setMarkerBrushLocal|setMarkerColor|setMarkerColorLocal|setMarkerDir|setMarkerDirLocal|setMarkerPos|setMarkerPosLocal|setMarkerShape|setMarkerShapeLocal|setMarkerSize|setMarkerSizeLocal|setMarkerText|setMarkerTextLocal|setMarkerType|setMarkerTypeLocal|setMass|setMimic|setMousePosition|setMusicEffect|setMusicEventHandler|setName|setNameSound|setObjectArguments|setObjectMaterial|setObjectMaterialGlobal|setObjectProxy|setObjectTexture|setObjectTextureGlobal|setObjectViewDistance|setOvercast|setOwner|setOxygenRemaining|setParticleCircle|setParticleClass|setParticleFire|setParticleParams|setParticleRandom|setPilotCameraDirection|setPilotCameraRotation|setPilotCameraTarget|setPilotLight|setPiPEffect|setPitch|setPlateNumber|setPlayable|setPlayerRespawnTime|setPos|setPosASL|setPosASL2|setPosASLW|setPosATL|setPosition|setPosWorld|setPylonLoadOut|setPylonsPriority|setRadioMsg|setRain|setRainbow|setRandomLip|setRank|setRectangular|setRepairCargo|setRotorBrakeRTD|setShadowDistance|setShotParents|setSide|setSimpleTaskAlwaysVisible|setSimpleTaskCustomData|setSimpleTaskDescription|setSimpleTaskDestination|setSimpleTaskTarget|setSimpleTaskType|setSimulWeatherLayers|setSize|setSkill|setSlingLoad|setSoundEffect|setSpeaker|setSpeech|setSpeedMode|setStamina|setStaminaScheme|setStatValue|setSuppression|setSystemOfUnits|setTargetAge|setTaskMarkerOffset|setTaskResult|setTaskState|setTerrainGrid|setText|setTimeMultiplier|setTitleEffect|setToneMapping|setToneMappingParams|setTrafficDensity|setTrafficDistance|setTrafficGap|setTrafficSpeed|setTriggerActivation|setTriggerArea|setTriggerStatements|setTriggerText|setTriggerTimeout|setTriggerType|setType|setUnconscious|setUnitAbility|setUnitLoadout|setUnitPos|setUnitPosWeak|setUnitRank|setUnitRecoilCoefficient|setUnitTrait|setUnloadInCombat|setUserActionText|setUserMFDText|setUserMFDValue|setVariable|setVectorDir|setVectorDirAndUp|setVectorUp|setVehicleAmmo|setVehicleAmmoDef|setVehicleArmor|setVehicleCargo|setVehicleId|setVehicleInit|setVehicleLock|setVehiclePosition|setVehicleRadar|setVehicleReceiveRemoteTargets|setVehicleReportOwnPosition|setVehicleReportRemoteTargets|setVehicleTIPars|setVehicleVarName|setVelocity|setVelocityModelSpace|setVelocityTransformation|setViewDistance|setVisibleIfTreeCollapsed|setWantedRpmRTD|setWaves|setWaypointBehaviour|setWaypointCombatMode|setWaypointCompletionRadius|setWaypointDescription|setWaypointForceBehaviour|setWaypointFormation|setWaypointHousePosition|setWaypointLoiterRadius|setWaypointLoiterType|setWaypointName|setWaypointPosition|setWaypointScript|setWaypointSpeed|setWaypointStatements|setWaypointTimeout|setWaypointType|setWaypointVisible|setWeaponReloadingTime|setWind|setWindDir|setWindForce|setWindStr|setWingForceScaleRTD|setWPPos|show3DIcons|showChat|showCinemaBorder|showCommandingMenu|showCompass|showCuratorCompass|showGPS|showHUD|showLegend|showMap|shownArtilleryComputer|shownChat|shownCompass|shownCuratorCompass|showNewEditorObject|shownGPS|shownHUD|shownMap|shownPad|shownRadio|shownScoretable|shownUAVFeed|shownWarrant|shownWatch|showPad|showRadio|showScoretable|showSubtitles|showUAVFeed|showWarrant|showWatch|showWaypoint|showWaypoints|side|sideAmbientLife|sideChat|sideEmpty|sideEnemy|sideFriendly|sideLogic|sideRadio|sideUnknown|simpleTasks|simulationEnabled|simulCloudDensity|simulCloudOcclusion|simulInClouds|simulWeatherSync|sin|size|sizeOf|skill|skillFinal|skipTime|sleep|sliderPosition|sliderRange|sliderSetPosition|sliderSetRange|sliderSetSpeed|sliderSpeed|slingLoadAssistantShown|soldierMagazines|someAmmo|sort|soundVolume|speaker|speed|speedMode|splitString|sqrt|squadParams|stance|startLoadingScreen|stop|stopEngineRTD|stopped|str|sunOrMoon|supportInfo|suppressFor|surfaceIsWater|surfaceNormal|surfaceType|swimInDepth|switchableUnits|switchAction|switchCamera|switchGesture|switchLight|switchMove|synchronizedObjects|synchronizedTriggers|synchronizedWaypoints|synchronizeObjectsAdd|synchronizeObjectsRemove|synchronizeTrigger|synchronizeWaypoint|systemChat|systemOfUnits|tan|targetKnowledge|targets|targetsAggregate|targetsQuery|taskAlwaysVisible|taskChildren|taskCompleted|taskCustomData|taskDescription|taskDestination|taskHint|taskMarkerOffset|taskNull|taskParent|taskResult|taskState|taskType|teamMember|teamMemberNull|teamName|teams|teamSwitch|teamSwitchEnabled|teamType|terminate|terrainIntersect|terrainIntersectASL|terrainIntersectAtASL|text|textLog|textLogFormat|tg|time|timeMultiplier|titleCut|titleFadeOut|titleObj|titleRsc|titleText|toArray|toFixed|toLower|toString|toUpper|triggerActivated|triggerActivation|triggerArea|triggerAttachedVehicle|triggerAttachObject|triggerAttachVehicle|triggerDynamicSimulation|triggerStatements|triggerText|triggerTimeout|triggerTimeoutCurrent|triggerType|turretLocal|turretOwner|turretUnit|tvAdd|tvClear|tvCollapse|tvCollapseAll|tvCount|tvCurSel|tvData|tvDelete|tvExpand|tvExpandAll|tvPicture|tvPictureRight|tvSetColor|tvSetCurSel|tvSetData|tvSetPicture|tvSetPictureColor|tvSetPictureColorDisabled|tvSetPictureColorSelected|tvSetPictureRight|tvSetPictureRightColor|tvSetPictureRightColorDisabled|tvSetPictureRightColorSelected|tvSetSelectColor|tvSetText|tvSetTooltip|tvSetValue|tvSort|tvSortByValue|tvText|tvTooltip|tvValue|type|typeName|typeOf|UAVControl|uiNamespace|uiSleep|unassignCurator|unassignItem|unassignTeam|unassignVehicle|underwater|uniform|uniformContainer|uniformItems|uniformMagazines|unitAddons|unitAimPosition|unitAimPositionVisual|unitBackpack|unitIsUAV|unitPos|unitReady|unitRecoilCoefficient|units|unitsBelowHeight|unlinkItem|unlockAchievement|unregisterTask|updateDrawIcon|updateMenuItem|updateObjectTree|useAIOperMapObstructionTest|useAISteeringComponent|useAudioTimeForMoves|userInputDisabled|vectorAdd|vectorCos|vectorCrossProduct|vectorDiff|vectorDir|vectorDirVisual|vectorDistance|vectorDistanceSqr|vectorDotProduct|vectorFromTo|vectorMagnitude|vectorMagnitudeSqr|vectorModelToWorld|vectorModelToWorldVisual|vectorMultiply|vectorNormalized|vectorUp|vectorUpVisual|vectorWorldToModel|vectorWorldToModelVisual|vehicle|vehicleCargoEnabled|vehicleChat|vehicleRadio|vehicleReceiveRemoteTargets|vehicleReportOwnPosition|vehicleReportRemoteTargets|vehicles|vehicleVarName|velocity|velocityModelSpace|verifySignature|vest|vestContainer|vestItems|vestMagazines|viewDistance|visibleCompass|visibleGPS|visibleMap|visiblePosition|visiblePositionASL|visibleScoretable|visibleWatch|waitUntil|waves|waypointAttachedObject|waypointAttachedVehicle|waypointAttachObject|waypointAttachVehicle|waypointBehaviour|waypointCombatMode|waypointCompletionRadius|waypointDescription|waypointForceBehaviour|waypointFormation|waypointHousePosition|waypointLoiterRadius|waypointLoiterType|waypointName|waypointPosition|waypoints|waypointScript|waypointsEnabledUAV|waypointShow|waypointSpeed|waypointStatements|waypointTimeout|waypointTimeoutCurrent|waypointType|waypointVisible|weaponAccessories|weaponAccessoriesCargo|weaponCargo|weaponDirection|weaponInertia|weaponLowered|weapons|weaponsItems|weaponsItemsCargo|weaponState|weaponsTurret|weightRTD|west|WFSideText|wind|windDir|windRTD|windStr|wingsForcesRTD|worldName|worldSize|worldToModel|worldToModelVisual|worldToScreen)\b/i,number:/(?:\$|\b0x)[\da-f]+\b|(?:\B\.\d+|\b\d+(?:\.\d+)?)(?:e[+-]?\d+)?\b/i,operator:/##|>>|&&|\|\||[!=<>]=?|[-+*/%#^]|\b(?:and|mod|not|or)\b/i,"magic-variable":{pattern:/\b(?:_exception|_fnc_scriptName|_fnc_scriptNameParent|_forEachIndex|_this|_thisEventHandler|_thisFSM|_thisScript|_x|this|thisList|thisTrigger)\b/i,alias:"keyword"},constant:/\bDIK(?:_[a-z\d]+)+\b/i}),e.languages.insertBefore("sqf","string",{macro:{pattern:/(^[ \t]*)#[a-z](?:[^\r\n\\]|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{directive:{pattern:/#[a-z]+\b/i,alias:"keyword"},comment:e.languages.sqf.comment}}}),delete e.languages.sqf["class-name"]}e.exports=t,t.displayName="sqf",t.aliases=[]},11114(e){"use strict";function t(e){e.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:_INSERT|COL)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:S|ING)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:TRUE|FALSE|NULL)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|IN|ILIKE|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}}e.exports=t,t.displayName="sql",t.aliases=[]},67386(e){"use strict";function t(e){e.languages.squirrel=e.languages.extend("clike",{comment:[e.languages.clike.comment[0],{pattern:/(^|[^\\:])(?:\/\/|#).*/,lookbehind:!0,greedy:!0}],string:[{pattern:/(^|[^\\"'@])(?:@"(?:[^"]|"")*"(?!")|"(?:[^\\\r\n"]|\\.)*")/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\"'])'(?:[^\\']|\\(?:[xuU][0-9a-fA-F]{0,8}|[\s\S]))'/,lookbehind:!0,greedy:!0}],"class-name":{pattern:/(\b(?:class|enum|extends|instanceof)\s+)\w+(?:\.\w+)*/,lookbehind:!0,inside:{punctuation:/\./}},keyword:/\b(?:base|break|case|catch|class|clone|const|constructor|continue|default|delete|else|enum|extends|for|foreach|function|if|in|instanceof|local|null|resume|return|static|switch|this|throw|try|typeof|while|yield|__LINE__|__FILE__)\b/,number:/\b(?:0x[0-9a-fA-F]+|\d+(?:\.(?:\d+|[eE][+-]?\d+))?)\b/,operator:/\+\+|--|<=>|<[-<]|>>>?|&&?|\|\|?|[-+*/%!=<>]=?|[~^]|::?/,punctuation:/[(){}\[\],;.]/}),e.languages.insertBefore("squirrel","operator",{"attribute-punctuation":{pattern:/<\/|\/>/,alias:"important"},lambda:{pattern:/@(?=\()/,alias:"operator"}})}e.exports=t,t.displayName="squirrel",t.aliases=[]},28067(e){"use strict";function t(e){e.languages.stan={comment:/\/\/.*|\/\*[\s\S]*?\*\/|#(?!include).*/,string:{pattern:/"[\x20\x21\x23-\x5B\x5D-\x7E]*"/,greedy:!0},directive:{pattern:/^([ \t]*)#include\b.*/m,lookbehind:!0,alias:"property"},"function-arg":{pattern:/(\b(?:algebra_solver|integrate_1d|integrate_ode|integrate_ode_bdf|integrate_ode_rk45|map_rect)\s*\(\s*)[a-zA-Z]\w*/,lookbehind:!0,alias:"function"},constraint:{pattern:/(\b(?:int|matrix|real|row_vector|vector)\s*)<[^<>]*>/,lookbehind:!0,inside:{expression:{pattern:/(=\s*)\S(?:\S|\s+(?!\s))*?(?=\s*(?:>$|,\s*\w+\s*=))/,lookbehind:!0,inside:null},property:/\b[a-z]\w*(?=\s*=)/i,operator:/=/,punctuation:/^<|>$|,/}},keyword:[/\b(?:break|cholesky_factor_corr|cholesky_factor_cov|continue|corr_matrix|cov_matrix|data|else|for|functions|generated|if|in|increment_log_prob|int|matrix|model|ordered|parameters|positive_ordered|print|quantities|real|reject|return|row_vector|simplex|target|transformed|unit_vector|vector|void|while)\b/,/\b(?:algebra_solver|integrate_1d|integrate_ode|integrate_ode_bdf|integrate_ode_rk45|map_rect)\b/],function:/\b[a-z]\w*(?=\s*\()/i,number:/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:E[+-]?\d+)?\b/i,boolean:/\b(?:false|true)\b/,operator:/<-|\.[*/]=?|\|\|?|&&|[!=<>+\-*/]=?|['^%~?:]/,punctuation:/[()\[\]{},;]/},e.languages.stan.constraint.inside.expression.inside=e.languages.stan}e.exports=t,t.displayName="stan",t.aliases=[]},49168(e){"use strict";function t(e){var t,n,r,i;t=e,(i={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},url:{pattern:/\burl\((["']?).*?\1\)/i,greedy:!0},string:{pattern:/("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*\1/,greedy:!0},interpolation:null,func:null,important:/\B!(?:important|optional)\b/i,keyword:{pattern:/(^|\s+)(?:(?:if|else|for|return|unless)(?=\s|$)|@[\w-]+)/,lookbehind:!0},hexcode:/#[\da-f]{3,6}/i,color:[/\b(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)\b/i,{pattern:/\b(?:rgb|hsl)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:rgb|hsl)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:n={pattern:/(\b\d+)(?:%|[a-z]+)/,lookbehind:!0},number:r={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0},function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:n,boolean:/\b(?:true|false)\b/,operator:[/~|[+!\/%<>?=]=?|[-:]=|\*[*=]?|\.{2,3}|&&|\|\||\B-\B|\b(?:and|in|is(?: a| defined| not|nt)?|not|or)\b/],number:r,punctuation:/[{}()\[\];:,]/}).interpolation={pattern:/\{[^\r\n}:]+\}/,alias:"variable",inside:{delimiter:{pattern:/^\{|\}$/,alias:"punctuation"},rest:i}},i.func={pattern:/[\w-]+\([^)]*\).*/,inside:{function:/^[^(]+/,rest:i}},t.languages.stylus={"atrule-declaration":{pattern:/(^[ \t]*)@.+/m,lookbehind:!0,inside:{atrule:/^@[\w-]+/,rest:i}},"variable-declaration":{pattern:/(^[ \t]*)[\w$-]+\s*.?=[ \t]*(?:\{[^{}]*\}|\S.*|$)/m,lookbehind:!0,inside:{variable:/^\S+/,rest:i}},statement:{pattern:/(^[ \t]*)(?:if|else|for|return|unless)[ \t].+/m,lookbehind:!0,inside:{keyword:/^\S+/,rest:i}},"property-declaration":{pattern:/((?:^|\{)([ \t]*))(?:[\w-]|\{[^}\r\n]+\})+(?:\s*:\s*|[ \t]+)(?!\s)[^{\r\n]*(?:;|[^{\r\n,]$(?!(?:\r?\n|\r)(?:\{|\2[ \t])))/m,lookbehind:!0,inside:{property:{pattern:/^[^\s:]+/,inside:{interpolation:i.interpolation}},rest:i}},selector:{pattern:/(^[ \t]*)(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\)|(?![\w-]))|\{[^}\r\n]+\})+)(?:(?:\r?\n|\r)(?:\1(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\)|(?![\w-]))|\{[^}\r\n]+\})+)))*(?:,$|\{|(?=(?:\r?\n|\r)(?:\{|\1[ \t])))/m,lookbehind:!0,inside:{interpolation:i.interpolation,comment:i.comment,punctuation:/[{},]/}},func:i.func,string:i.string,comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0,greedy:!0},interpolation:i.interpolation,punctuation:/[{}()\[\];:.]/}}e.exports=t,t.displayName="stylus",t.aliases=[]},23651(e){"use strict";function t(e){e.languages.swift=e.languages.extend("clike",{string:{pattern:/("|')(?:\\(?:\((?:[^()]|\([^)]+\))+\)|\r\n|[^(])|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{interpolation:{pattern:/\\\((?:[^()]|\([^)]+\))+\)/,inside:{delimiter:{pattern:/^\\\(|\)$/,alias:"variable"}}}}},keyword:/\b(?:as|associativity|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic(?:Type)?|else|enum|extension|fallthrough|final|for|func|get|guard|if|import|in|infix|init|inout|internal|is|lazy|left|let|mutating|new|none|nonmutating|operator|optional|override|postfix|precedence|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|Self|set|some|static|struct|subscript|super|switch|throws?|try|Type|typealias|unowned|unsafe|var|weak|where|while|willSet|__(?:COLUMN__|FILE__|FUNCTION__|LINE__))\b/,number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,constant:/\b(?:nil|[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,atrule:/@\b(?:IB(?:Outlet|Designable|Action|Inspectable)|class_protocol|exported|noreturn|NS(?:Copying|Managed)|objc|UIApplicationMain|auto_closure)\b/,builtin:/\b(?:[A-Z]\S+|abs|advance|alignof(?:Value)?|assert|contains|count(?:Elements)?|debugPrint(?:ln)?|distance|drop(?:First|Last)|dump|enumerate|equal|filter|find|first|getVaList|indices|isEmpty|join|last|lexicographicalCompare|map|max(?:Element)?|min(?:Element)?|numericCast|overlaps|partition|print(?:ln)?|reduce|reflect|reverse|sizeof(?:Value)?|sort(?:ed)?|split|startsWith|stride(?:of(?:Value)?)?|suffix|swap|toDebugString|toString|transcode|underestimateCount|unsafeBitCast|with(?:ExtendedLifetime|Unsafe(?:MutablePointers?|Pointers?)|VaList))\b/}),e.languages.swift.string.inside.interpolation.inside.rest=e.languages.swift}e.exports=t,t.displayName="swift",t.aliases=[]},32268(e,t,n){"use strict";var r=n(2329),i=n(61958);function a(e){e.register(r),e.register(i),e.languages.t4=e.languages["t4-cs"]=e.languages["t4-templating"].createT4("csharp")}e.exports=a,a.displayName="t4Cs",a.aliases=[]},2329(e){"use strict";function t(e){!function(e){function t(e,t,n){return{pattern:RegExp("<#"+e+"[\\s\\S]*?#>"),alias:"block",inside:{delimiter:{pattern:RegExp("^<#"+e+"|#>$"),alias:"important"},content:{pattern:/[\s\S]+/,inside:t,alias:n}}}}function n(n){var r=e.languages[n],i="language-"+n;return{block:{pattern:/<#[\s\S]+?#>/,inside:{directive:t("@",{"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/,inside:{punctuation:/^=|^["']|["']$/}},keyword:/\b\w+(?=\s)/,"attr-name":/\b\w+/}),expression:t("=",r,i),"class-feature":t("\\+",r,i),standard:t("",r,i)}}}}e.languages["t4-templating"]=Object.defineProperty({},"createT4",{value:n})}(e)}e.exports=t,t.displayName="t4Templating",t.aliases=[]},82996(e,t,n){"use strict";var r=n(2329),i=n(53813);function a(e){e.register(r),e.register(i),e.languages["t4-vb"]=e.languages["t4-templating"].createT4("vbnet")}e.exports=a,a.displayName="t4Vb",a.aliases=[]},17290(e,t,n){"use strict";var r=n(65039);function i(e){e.register(r),e.languages.tap={fail:/not ok[^#{\n\r]*/,pass:/ok[^#{\n\r]*/,pragma:/pragma [+-][a-z]+/,bailout:/bail out!.*/i,version:/TAP version \d+/i,plan:/\b\d+\.\.\d+(?: +#.*)?/,subtest:{pattern:/# Subtest(?:: .*)?/,greedy:!0},punctuation:/[{}]/,directive:/#.*/,yamlish:{pattern:/(^[ \t]*)---[\s\S]*?[\r\n][ \t]*\.\.\.$/m,lookbehind:!0,inside:e.languages.yaml,alias:"language-yaml"}}}e.exports=i,i.displayName="tap",i.aliases=[]},67989(e){"use strict";function t(e){e.languages.tcl={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},string:{pattern:/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"/,greedy:!0},variable:[{pattern:/(\$)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/,lookbehind:!0},{pattern:/(\$)\{[^}]+\}/,lookbehind:!0},{pattern:/(^[\t ]*set[ \t]+)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/m,lookbehind:!0}],function:{pattern:/(^[\t ]*proc[ \t]+)\S+/m,lookbehind:!0},builtin:[{pattern:/(^[\t ]*)(?:proc|return|class|error|eval|exit|for|foreach|if|switch|while|break|continue)\b/m,lookbehind:!0},/\b(?:elseif|else)\b/],scope:{pattern:/(^[\t ]*)(?:global|upvar|variable)\b/m,lookbehind:!0,alias:"constant"},keyword:{pattern:/(^[\t ]*|\[)(?:after|append|apply|array|auto_(?:execok|import|load|mkindex|qualify|reset)|automkindex_old|bgerror|binary|catch|cd|chan|clock|close|concat|dde|dict|encoding|eof|exec|expr|fblocked|fconfigure|fcopy|file(?:event|name)?|flush|gets|glob|history|http|incr|info|interp|join|lappend|lassign|lindex|linsert|list|llength|load|lrange|lrepeat|lreplace|lreverse|lsearch|lset|lsort|math(?:func|op)|memory|msgcat|namespace|open|package|parray|pid|pkg_mkIndex|platform|puts|pwd|re_syntax|read|refchan|regexp|registry|regsub|rename|Safe_Base|scan|seek|set|socket|source|split|string|subst|Tcl|tcl(?:_endOfWord|_findLibrary|startOf(?:Next|Previous)Word|wordBreak(?:After|Before)|test|vars)|tell|time|tm|trace|unknown|unload|unset|update|uplevel|vwait)\b/m,lookbehind:!0},operator:/!=?|\*\*?|==|&&?|\|\|?|<[=<]?|>[=>]?|[-+~\/%?^]|\b(?:eq|ne|in|ni)\b/,punctuation:/[{}()\[\]]/}}e.exports=t,t.displayName="tcl",t.aliases=[]},31065(e){"use strict";function t(e){!function(e){var t=/\([^|()\n]+\)|\[[^\]\n]+\]|\{[^}\n]+\}/.source,n=/\)|\((?![^|()\n]+\))/.source;function r(e,r){return RegExp(e.replace(//g,function(){return"(?:"+t+")"}).replace(//g,function(){return"(?:"+n+")"}),r||"")}var i={css:{pattern:/\{[^{}]+\}/,inside:{rest:e.languages.css}},"class-id":{pattern:/(\()[^()]+(?=\))/,lookbehind:!0,alias:"attr-value"},lang:{pattern:/(\[)[^\[\]]+(?=\])/,lookbehind:!0,alias:"attr-value"},punctuation:/[\\\/]\d+|\S/},a=e.languages.textile=e.languages.extend("markup",{phrase:{pattern:/(^|\r|\n)\S[\s\S]*?(?=$|\r?\n\r?\n|\r\r)/,lookbehind:!0,inside:{"block-tag":{pattern:r(/^[a-z]\w*(?:||[<>=])*\./.source),inside:{modifier:{pattern:r(/(^[a-z]\w*)(?:||[<>=])+(?=\.)/.source),lookbehind:!0,inside:i},tag:/^[a-z]\w*/,punctuation:/\.$/}},list:{pattern:r(/^[*#]+*\s+\S.*/.source,"m"),inside:{modifier:{pattern:r(/(^[*#]+)+/.source),lookbehind:!0,inside:i},punctuation:/^[*#]+/}},table:{pattern:r(/^(?:(?:||[<>=^~])+\.\s*)?(?:\|(?:(?:||[<>=^~_]|[\\/]\d+)+\.|(?!(?:||[<>=^~_]|[\\/]\d+)+\.))[^|]*)+\|/.source,"m"),inside:{modifier:{pattern:r(/(^|\|(?:\r?\n|\r)?)(?:||[<>=^~_]|[\\/]\d+)+(?=\.)/.source),lookbehind:!0,inside:i},punctuation:/\||^\./}},inline:{pattern:r(/(^|[^a-zA-Z\d])(\*\*|__|\?\?|[*_%@+\-^~])*.+?\2(?![a-zA-Z\d])/.source),lookbehind:!0,inside:{bold:{pattern:r(/(^(\*\*?)*).+?(?=\2)/.source),lookbehind:!0},italic:{pattern:r(/(^(__?)*).+?(?=\2)/.source),lookbehind:!0},cite:{pattern:r(/(^\?\?*).+?(?=\?\?)/.source),lookbehind:!0,alias:"string"},code:{pattern:r(/(^@*).+?(?=@)/.source),lookbehind:!0,alias:"keyword"},inserted:{pattern:r(/(^\+*).+?(?=\+)/.source),lookbehind:!0},deleted:{pattern:r(/(^-*).+?(?=-)/.source),lookbehind:!0},span:{pattern:r(/(^%*).+?(?=%)/.source),lookbehind:!0},modifier:{pattern:r(/(^\*\*|__|\?\?|[*_%@+\-^~])+/.source),lookbehind:!0,inside:i},punctuation:/[*_%?@+\-^~]+/}},"link-ref":{pattern:/^\[[^\]]+\]\S+$/m,inside:{string:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0},url:{pattern:/(^\])\S+$/,lookbehind:!0},punctuation:/[\[\]]/}},link:{pattern:r(/"*[^"]+":.+?(?=[^\w/]?(?:\s|$))/.source),inside:{text:{pattern:r(/(^"*)[^"]+(?=")/.source),lookbehind:!0},modifier:{pattern:r(/(^")+/.source),lookbehind:!0,inside:i},url:{pattern:/(:).+/,lookbehind:!0},punctuation:/[":]/}},image:{pattern:r(/!(?:||[<>=])*(?![<>=])[^!\s()]+(?:\([^)]+\))?!(?::.+?(?=[^\w/]?(?:\s|$)))?/.source),inside:{source:{pattern:r(/(^!(?:||[<>=])*)(?![<>=])[^!\s()]+(?:\([^)]+\))?(?=!)/.source),lookbehind:!0,alias:"url"},modifier:{pattern:r(/(^!)(?:||[<>=])+/.source),lookbehind:!0,inside:i},url:{pattern:/(:).+/,lookbehind:!0},punctuation:/[!:]/}},footnote:{pattern:/\b\[\d+\]/,alias:"comment",inside:{punctuation:/\[|\]/}},acronym:{pattern:/\b[A-Z\d]+\([^)]+\)/,inside:{comment:{pattern:/(\()[^()]+(?=\))/,lookbehind:!0},punctuation:/[()]/}},mark:{pattern:/\b\((?:TM|R|C)\)/,alias:"comment",inside:{punctuation:/[()]/}}}}}),o=a.phrase.inside,s={inline:o.inline,link:o.link,image:o.image,footnote:o.footnote,acronym:o.acronym,mark:o.mark};a.tag.pattern=/<\/?(?!\d)[a-z0-9]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i;var u=o.inline.inside;u.bold.inside=s,u.italic.inside=s,u.inserted.inside=s,u.deleted.inside=s,u.span.inside=s;var c=o.table.inside;c.inline=s.inline,c.link=s.link,c.image=s.image,c.footnote=s.footnote,c.acronym=s.acronym,c.mark=s.mark}(e)}e.exports=t,t.displayName="textile",t.aliases=[]},85572(e){"use strict";function t(e){!function(e){var t=/(?:[\w-]+|'[^'\n\r]*'|"(?:\\.|[^\\"\r\n])*")/.source;function n(e){return e.replace(/__/g,function(){return t})}e.languages.toml={comment:{pattern:/#.*/,greedy:!0},table:{pattern:RegExp(n(/(^[\t ]*\[\s*(?:\[\s*)?)__(?:\s*\.\s*__)*(?=\s*\])/.source),"m"),lookbehind:!0,greedy:!0,alias:"class-name"},key:{pattern:RegExp(n(/(^[\t ]*|[{,]\s*)__(?:\s*\.\s*__)*(?=\s*=)/.source),"m"),lookbehind:!0,greedy:!0,alias:"property"},string:{pattern:/"""(?:\\[\s\S]|[^\\])*?"""|'''[\s\S]*?'''|'[^'\n\r]*'|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},date:[{pattern:/\b\d{4}-\d{2}-\d{2}(?:[T\s]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?\b/i,alias:"number"},{pattern:/\b\d{2}:\d{2}:\d{2}(?:\.\d+)?\b/,alias:"number"}],number:/(?:\b0(?:x[\da-zA-Z]+(?:_[\da-zA-Z]+)*|o[0-7]+(?:_[0-7]+)*|b[10]+(?:_[10]+)*))\b|[-+]?\b\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?\b|[-+]?\b(?:inf|nan)\b/,boolean:/\b(?:true|false)\b/,punctuation:/[.,=[\]{}]/}}(e)}e.exports=t,t.displayName="toml",t.aliases=[]},87041(e,t,n){"use strict";var r=n(96412),i=n(4979);function a(e){var t,n,a;e.register(r),e.register(i),n=(t=e).util.clone(t.languages.typescript),t.languages.tsx=t.languages.extend("jsx",n),(a=t.languages.tsx.tag).pattern=RegExp(/(^|[^\w$]|(?=<\/))/.source+"(?:"+a.pattern.source+")",a.pattern.flags),a.lookbehind=!0}e.exports=a,a.displayName="tsx",a.aliases=[]},61028(e,t,n){"use strict";var r=n(93205);function i(e){var t;e.register(r),(t=e).languages.tt2=t.languages.extend("clike",{comment:/#.*|\[%#[\s\S]*?%\]/,keyword:/\b(?:BLOCK|CALL|CASE|CATCH|CLEAR|DEBUG|DEFAULT|ELSE|ELSIF|END|FILTER|FINAL|FOREACH|GET|IF|IN|INCLUDE|INSERT|LAST|MACRO|META|NEXT|PERL|PROCESS|RAWPERL|RETURN|SET|STOP|TAGS|THROW|TRY|SWITCH|UNLESS|USE|WHILE|WRAPPER)\b/,punctuation:/[[\]{},()]/}),t.languages.insertBefore("tt2","number",{operator:/=[>=]?|!=?|<=?|>=?|&&|\|\|?|\b(?:and|or|not)\b/,variable:{pattern:/\b[a-z]\w*(?:\s*\.\s*(?:\d+|\$?[a-z]\w*))*\b/i}}),t.languages.insertBefore("tt2","keyword",{delimiter:{pattern:/^(?:\[%|%%)-?|-?%\]$/,alias:"punctuation"}}),t.languages.insertBefore("tt2","string",{"single-quoted-string":{pattern:/'[^\\']*(?:\\[\s\S][^\\']*)*'/,greedy:!0,alias:"string"},"double-quoted-string":{pattern:/"[^\\"]*(?:\\[\s\S][^\\"]*)*"/,greedy:!0,alias:"string",inside:{variable:{pattern:/\$(?:[a-z]\w*(?:\.(?:\d+|\$?[a-z]\w*))*)/i}}}}),delete t.languages.tt2.string,t.hooks.add("before-tokenize",function(e){var n=/\[%[\s\S]+?%\]/g;t.languages["markup-templating"].buildPlaceholders(e,"tt2",n)}),t.hooks.add("after-tokenize",function(e){t.languages["markup-templating"].tokenizePlaceholders(e,"tt2")})}e.exports=i,i.displayName="tt2",i.aliases=[]},24691(e){"use strict";function t(e){e.languages.turtle={comment:{pattern:/#.*/,greedy:!0},"multiline-string":{pattern:/"""(?:(?:""?)?(?:[^"\\]|\\.))*"""|'''(?:(?:''?)?(?:[^'\\]|\\.))*'''/,greedy:!0,alias:"string",inside:{comment:/#.*/}},string:{pattern:/"(?:[^\\"\r\n]|\\.)*"|'(?:[^\\'\r\n]|\\.)*'/,greedy:!0},url:{pattern:/<(?:[^\x00-\x20<>"{}|^`\\]|\\(?:u[\da-fA-F]{4}|U[\da-fA-F]{8}))*>/,greedy:!0,inside:{punctuation:/[<>]/}},function:{pattern:/(?:(?![-.\d\xB7])[-.\w\xB7\xC0-\uFFFD]+)?:(?:(?![-.])(?:[-.:\w\xC0-\uFFFD]|%[\da-f]{2}|\\.)+)?/i,inside:{"local-name":{pattern:/([^:]*:)[\s\S]+/,lookbehind:!0},prefix:{pattern:/[\s\S]+/,inside:{punctuation:/:/}}}},number:/[+-]?\b\d+(?:\.\d*)?(?:e[+-]?\d+)?/i,punctuation:/[{}.,;()[\]]|\^\^/,boolean:/\b(?:true|false)\b/,keyword:[/(?:\ba|@prefix|@base)\b|=/,/\b(?:graph|base|prefix)\b/i],tag:{pattern:/@[a-z]+(?:-[a-z\d]+)*/i,inside:{punctuation:/@/}}},e.languages.trig=e.languages.turtle}e.exports=t,t.displayName="turtle",t.aliases=[]},19892(e){"use strict";function t(e){e.languages.twig={comment:/\{#[\s\S]*?#\}/,tag:{pattern:/\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}/,inside:{ld:{pattern:/^(?:\{\{-?|\{%-?\s*\w+)/,inside:{punctuation:/^(?:\{\{|\{%)-?/,keyword:/\w+/}},rd:{pattern:/-?(?:%\}|\}\})$/,inside:{punctuation:/.+/}},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,inside:{punctuation:/^['"]|['"]$/}},keyword:/\b(?:even|if|odd)\b/,boolean:/\b(?:true|false|null)\b/,number:/\b0x[\dA-Fa-f]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee][-+]?\d+)?/,operator:[{pattern:/(\s)(?:and|b-and|b-xor|b-or|ends with|in|is|matches|not|or|same as|starts with)(?=\s)/,lookbehind:!0},/[=<>]=?|!=|\*\*?|\/\/?|\?:?|[-+~%|]/],property:/\b[a-zA-Z_]\w*\b/,punctuation:/[()\[\]{}:.,]/}},other:{pattern:/\S(?:[\s\S]*\S)?/,inside:e.languages.markup}}}e.exports=t,t.displayName="twig",t.aliases=[]},4979(e){"use strict";function t(e){var t,n;(t=e).languages.typescript=t.languages.extend("javascript",{"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:string|Function|any|number|boolean|Array|symbol|console|Promise|unknown|never)\b/}),t.languages.typescript.keyword.push(/\b(?:abstract|as|declare|implements|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)(?!\s*[^\s_${}*a-zA-Z\xA0-\uFFFF])/),delete t.languages.typescript.parameter,delete(n=t.languages.extend("typescript",{}))["class-name"],t.languages.typescript["class-name"].inside=n,t.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:n}}}}),t.languages.ts=t.languages.typescript}e.exports=t,t.displayName="typescript",t.aliases=["ts"]},23159(e){"use strict";function t(e){var t,n;n=/\b(?:ACT|ACTIFSUB|CARRAY|CASE|CLEARGIF|COA|COA_INT|CONSTANTS|CONTENT|CUR|EDITPANEL|EFFECT|EXT|FILE|FLUIDTEMPLATE|FORM|FRAME|FRAMESET|GIFBUILDER|GMENU|GMENU_FOLDOUT|GMENU_LAYERS|GP|HMENU|HRULER|HTML|IENV|IFSUB|IMAGE|IMGMENU|IMGMENUITEM|IMGTEXT|IMG_RESOURCE|INCLUDE_TYPOSCRIPT|JSMENU|JSMENUITEM|LLL|LOAD_REGISTER|NO|PAGE|RECORDS|RESTORE_REGISTER|TEMPLATE|TEXT|TMENU|TMENUITEM|TMENU_LAYERS|USER|USER_INT|_GIFBUILDER|global|globalString|globalVar)\b/,(t=e).languages.typoscript={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:= \t]|(?:^|[^= \t])[ \t]+)\/\/.*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^"'])#.*/,lookbehind:!0,greedy:!0}],function:[{pattern://,inside:{string:{pattern:/"[^"\r\n]*"|'[^'\r\n]*'/,inside:{keyword:n}},keyword:{pattern:/INCLUDE_TYPOSCRIPT/}}},{pattern:/@import\s*(?:"[^"\r\n]*"|'[^'\r\n]*')/,inside:{string:/"[^"\r\n]*"|'[^'\r\n]*'/}}],string:{pattern:/^([^=]*=[< ]?)(?:(?!\]\n).)*/,lookbehind:!0,inside:{function:/\{\$.*\}/,keyword:n,number:/^[0-9]+$/,punctuation:/[,|:]/}},keyword:n,number:{pattern:/\b[0-9]+\s*[.{=]/,inside:{operator:/[.{=]/}},tag:{pattern:/\.?[-\w\\]+\.?/,inside:{punctuation:/\./}},punctuation:/[{}[\];(),.:|]/,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/},t.languages.tsconfig=t.languages.typoscript}e.exports=t,t.displayName="typoscript",t.aliases=["tsconfig"]},34966(e){"use strict";function t(e){e.languages.unrealscript={comment:/\/\/.*|\/\*[\s\S]*?\*\//,string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},category:{pattern:/(\b(?:(?:autoexpand|hide|show)categories|var)\s*\()[^()]+(?=\))/,lookbehind:!0,greedy:!0,alias:"property"},metadata:{pattern:/(\w\s*)<\s*\w+\s*=[^<>|=\r\n]+(?:\|\s*\w+\s*=[^<>|=\r\n]+)*>/,lookbehind:!0,greedy:!0,inside:{property:/\b\w+(?=\s*=)/,operator:/=/,punctuation:/[<>|]/}},macro:{pattern:/`\w+/,alias:"property"},"class-name":{pattern:/(\b(?:class|enum|extends|interface|state(?:\(\))?|struct|within)\s+)\w+/,lookbehind:!0},keyword:/\b(?:abstract|actor|array|auto|autoexpandcategories|bool|break|byte|case|class|classgroup|client|coerce|collapsecategories|config|const|continue|default|defaultproperties|delegate|dependson|deprecated|do|dontcollapsecategories|editconst|editinlinenew|else|enum|event|exec|export|extends|final|float|for|forcescriptorder|foreach|function|goto|guid|hidecategories|hidedropdown|if|ignores|implements|inherits|input|int|interface|iterator|latent|local|material|name|native|nativereplication|noexport|nontransient|noteditinlinenew|notplaceable|operator|optional|out|pawn|perobjectconfig|perobjectlocalized|placeable|postoperator|preoperator|private|protected|reliable|replication|return|server|showcategories|simulated|singular|state|static|string|struct|structdefault|structdefaultproperties|switch|texture|transient|travel|unreliable|until|var|vector|while|within)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,boolean:/\b(?:false|true)\b/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/>>|<<|--|\+\+|\*\*|[-+*/~!=<>$@]=?|&&?|\|\|?|\^\^?|[?:%]|\b(?:Cross|Dot|ClockwiseFrom)\b/,punctuation:/[()[\]{};,.]/},e.languages.uc=e.languages.uscript=e.languages.unrealscript}e.exports=t,t.displayName="unrealscript",t.aliases=["uc","uscript"]},38521(e){"use strict";function t(e){e.languages.uri={scheme:{pattern:/^[a-z][a-z0-9+.-]*:/im,greedy:!0,inside:{"scheme-delimiter":/:$/}},fragment:{pattern:/#[\w\-.~!$&'()*+,;=%:@/?]*/,inside:{"fragment-delimiter":/^#/}},query:{pattern:/\?[\w\-.~!$&'()*+,;=%:@/?]*/,inside:{"query-delimiter":{pattern:/^\?/,greedy:!0},"pair-delimiter":/[&;]/,pair:{pattern:/^[^=][\s\S]*/,inside:{key:/^[^=]+/,value:{pattern:/(^=)[\s\S]+/,lookbehind:!0}}}}},authority:{pattern:RegExp(/^\/\//.source+/(?:[\w\-.~!$&'()*+,;=%:]*@)?/.source+("(?:"+/\[(?:[0-9a-fA-F:.]{2,48}|v[0-9a-fA-F]+\.[\w\-.~!$&'()*+,;=]+)\]/.source+"|")+/[\w\-.~!$&'()*+,;=%]*/.source+")"+/(?::\d*)?/.source,"m"),inside:{"authority-delimiter":/^\/\//,"user-info-segment":{pattern:/^[\w\-.~!$&'()*+,;=%:]*@/,inside:{"user-info-delimiter":/@$/,"user-info":/^[\w\-.~!$&'()*+,;=%:]+/}},"port-segment":{pattern:/:\d*$/,inside:{"port-delimiter":/^:/,port:/^\d+/}},host:{pattern:/[\s\S]+/,inside:{"ip-literal":{pattern:/^\[[\s\S]+\]$/,inside:{"ip-literal-delimiter":/^\[|\]$/,"ipv-future":/^v[\s\S]+/,"ipv6-address":/^[\s\S]+/}},"ipv4-address":/^(?:(?:[03-9]\d?|[12]\d{0,2})\.){3}(?:[03-9]\d?|[12]{0,2})$/}}}},path:{pattern:/^[\w\-.~!$&'()*+,;=%:@/]+/m,inside:{"path-separator":/\//}}},e.languages.url=e.languages.uri}e.exports=t,t.displayName="uri",t.aliases=["url"]},7255(e){"use strict";function t(e){var t,n;n={pattern:/[\s\S]+/,inside:null},(t=e).languages.v=t.languages.extend("clike",{string:[{pattern:/`(?:\\`|\\?[^`]{1,2})`/,alias:"rune"},{pattern:/r?(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,alias:"quoted-string",greedy:!0,inside:{interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$(?:\{[^{}]*\}|\w+(?:\.\w+(?:\([^\(\)]*\))?|\[[^\[\]]+\])*)/,lookbehind:!0,inside:{"interpolation-variable":{pattern:/^\$\w[\s\S]*$/,alias:"variable"},"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},"interpolation-expression":n}}}}],"class-name":{pattern:/(\b(?:enum|interface|struct|type)\s+)(?:C\.)?\w+/,lookbehind:!0},keyword:/(?:\b(?:as|asm|assert|atomic|break|chan|const|continue|defer|else|embed|enum|fn|for|__global|go(?:to)?|if|import|in|interface|is|lock|match|module|mut|none|or|pub|return|rlock|select|shared|sizeof|static|struct|type(?:of)?|union|unsafe)|\$(?:if|else|for)|#(?:include|flag))\b/,number:/\b(?:0x[a-f\d]+(?:_[a-f\d]+)*|0b[01]+(?:_[01]+)*|0o[0-7]+(?:_[0-7]+)*|\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?)\b/i,operator:/~|\?|[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\.?/,builtin:/\b(?:any(?:_int|_float)?|bool|byte(?:ptr)?|charptr|f(?:32|64)|i(?:8|16|nt|64|128)|rune|size_t|string|u(?:16|32|64|128)|voidptr)\b/}),n.inside=t.languages.v,t.languages.insertBefore("v","operator",{attribute:{pattern:/(^[\t ]*)\[(?:deprecated|unsafe_fn|typedef|live|inline|flag|ref_only|windows_stdcall|direct_array_access)\]/m,lookbehind:!0,alias:"annotation",inside:{punctuation:/[\[\]]/,keyword:/\w+/}},generic:{pattern:/<\w+>(?=\s*[\)\{])/,inside:{punctuation:/[<>]/,"class-name":/\w+/}}}),t.languages.insertBefore("v","function",{"generic-function":{pattern:/\b\w+\s*<\w+>(?=\()/,inside:{function:/^\w+/,generic:{pattern:/<\w+>/,inside:t.languages.v.generic.inside}}}})}e.exports=t,t.displayName="v",t.aliases=[]},28173(e){"use strict";function t(e){e.languages.vala=e.languages.extend("clike",{"class-name":[{pattern:/\b[A-Z]\w*(?:\.\w+)*\b(?=(?:\?\s+|\*?\s+\*?)\w)/,inside:{punctuation:/\./}},{pattern:/(\[)[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}},{pattern:/(\b(?:class|interface)\s+[A-Z]\w*(?:\.\w+)*\s*:\s*)[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}},{pattern:/((?:\b(?:class|interface|new|struct|enum)\s+)|(?:catch\s+\())[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}}],keyword:/\b(?:bool|char|double|float|null|size_t|ssize_t|string|unichar|void|int|int8|int16|int32|int64|long|short|uchar|uint|uint8|uint16|uint32|uint64|ulong|ushort|class|delegate|enum|errordomain|interface|namespace|struct|break|continue|do|for|foreach|return|while|else|if|switch|assert|case|default|abstract|const|dynamic|ensures|extern|inline|internal|override|private|protected|public|requires|signal|static|virtual|volatile|weak|async|owned|unowned|try|catch|finally|throw|as|base|construct|delete|get|in|is|lock|new|out|params|ref|sizeof|set|this|throws|typeof|using|value|var|yield)\b/i,function:/\b\w+(?=\s*\()/,number:/(?:\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)(?:f|u?l?)?/i,operator:/\+\+|--|&&|\|\||<<=?|>>=?|=>|->|~|[+\-*\/%&^|=!<>]=?|\?\??|\.\.\./,punctuation:/[{}[\];(),.:]/,constant:/\b[A-Z0-9_]+\b/}),e.languages.insertBefore("vala","string",{"raw-string":{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string"},"template-string":{pattern:/@"[\s\S]*?"/,greedy:!0,inside:{interpolation:{pattern:/\$(?:\([^)]*\)|[a-zA-Z]\w*)/,inside:{delimiter:{pattern:/^\$\(?|\)$/,alias:"punctuation"},rest:e.languages.vala}},string:/[\s\S]+/}}}),e.languages.insertBefore("vala","keyword",{regex:{pattern:/\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[imsx]{0,4}(?=\s*(?:$|[\r\n,.;})\]]))/,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:e.languages.regex},"regex-delimiter":/^\//,"regex-flags":/^[a-z]+$/}}})}e.exports=t,t.displayName="vala",t.aliases=[]},53813(e,t,n){"use strict";var r=n(46241);function i(e){e.register(r),e.languages.vbnet=e.languages.extend("basic",{comment:[{pattern:/(?:!|REM\b).+/i,inside:{keyword:/^REM/i}},{pattern:/(^|[^\\:])'.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(^|[^"])"(?:""|[^"])*"(?!")/i,lookbehind:!0,greedy:!0},keyword:/(?:\b(?:ADDHANDLER|ADDRESSOF|ALIAS|AND|ANDALSO|AS|BEEP|BLOAD|BOOLEAN|BSAVE|BYREF|BYTE|BYVAL|CALL(?: ABSOLUTE)?|CASE|CATCH|CBOOL|CBYTE|CCHAR|CDATE|CDEC|CDBL|CHAIN|CHAR|CHDIR|CINT|CLASS|CLEAR|CLNG|CLOSE|CLS|COBJ|COM|COMMON|CONST|CONTINUE|CSBYTE|CSHORT|CSNG|CSTR|CTYPE|CUINT|CULNG|CUSHORT|DATA|DATE|DECIMAL|DECLARE|DEFAULT|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DELEGATE|DIM|DIRECTCAST|DO|DOUBLE|ELSE|ELSEIF|END|ENUM|ENVIRON|ERASE|ERROR|EVENT|EXIT|FALSE|FIELD|FILES|FINALLY|FOR(?: EACH)?|FRIEND|FUNCTION|GET|GETTYPE|GETXMLNAMESPACE|GLOBAL|GOSUB|GOTO|HANDLES|IF|IMPLEMENTS|IMPORTS|IN|INHERITS|INPUT|INTEGER|INTERFACE|IOCTL|IS|ISNOT|KEY|KILL|LINE INPUT|LET|LIB|LIKE|LOCATE|LOCK|LONG|LOOP|LSET|ME|MKDIR|MOD|MODULE|MUSTINHERIT|MUSTOVERRIDE|MYBASE|MYCLASS|NAME|NAMESPACE|NARROWING|NEW|NEXT|NOT|NOTHING|NOTINHERITABLE|NOTOVERRIDABLE|OBJECT|OF|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPERATOR|OPEN|OPTION(?: BASE)?|OPTIONAL|OR|ORELSE|OUT|OVERLOADS|OVERRIDABLE|OVERRIDES|PARAMARRAY|PARTIAL|POKE|PRIVATE|PROPERTY|PROTECTED|PUBLIC|PUT|RAISEEVENT|READ|READONLY|REDIM|REM|REMOVEHANDLER|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SBYTE|SELECT(?: CASE)?|SET|SHADOWS|SHARED|SHORT|SINGLE|SHELL|SLEEP|STATIC|STEP|STOP|STRING|STRUCTURE|SUB|SYNCLOCK|SWAP|SYSTEM|THEN|THROW|TIMER|TO|TROFF|TRON|TRUE|TRY|TRYCAST|TYPE|TYPEOF|UINTEGER|ULONG|UNLOCK|UNTIL|USHORT|USING|VIEW PRINT|WAIT|WEND|WHEN|WHILE|WIDENING|WITH|WITHEVENTS|WRITE|WRITEONLY|XOR)|\B(?:#CONST|#ELSE|#ELSEIF|#END|#IF))(?:\$|\b)/i,punctuation:/[,;:(){}]/})}e.exports=i,i.displayName="vbnet",i.aliases=[]},46891(e){"use strict";function t(e){var t,n;(t=e).languages.velocity=t.languages.extend("markup",{}),(n={variable:{pattern:/(^|[^\\](?:\\\\)*)\$!?(?:[a-z][\w-]*(?:\([^)]*\))?(?:\.[a-z][\w-]*(?:\([^)]*\))?|\[[^\]]+\])*|\{[^}]+\})/i,lookbehind:!0,inside:{}},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},number:/\b\d+\b/,boolean:/\b(?:true|false)\b/,operator:/[=!<>]=?|[+*/%-]|&&|\|\||\.\.|\b(?:eq|g[et]|l[et]|n(?:e|ot))\b/,punctuation:/[(){}[\]:,.]/}).variable.inside={string:n.string,function:{pattern:/([^\w-])[a-z][\w-]*(?=\()/,lookbehind:!0},number:n.number,boolean:n.boolean,punctuation:n.punctuation},t.languages.insertBefore("velocity","comment",{unparsed:{pattern:/(^|[^\\])#\[\[[\s\S]*?\]\]#/,lookbehind:!0,greedy:!0,inside:{punctuation:/^#\[\[|\]\]#$/}},"velocity-comment":[{pattern:/(^|[^\\])#\*[\s\S]*?\*#/,lookbehind:!0,greedy:!0,alias:"comment"},{pattern:/(^|[^\\])##.*/,lookbehind:!0,greedy:!0,alias:"comment"}],directive:{pattern:/(^|[^\\](?:\\\\)*)#@?(?:[a-z][\w-]*|\{[a-z][\w-]*\})(?:\s*\((?:[^()]|\([^()]*\))*\))?/i,lookbehind:!0,inside:{keyword:{pattern:/^#@?(?:[a-z][\w-]*|\{[a-z][\w-]*\})|\bin\b/,inside:{punctuation:/[{}]/}},rest:n}},variable:n.variable}),t.languages.velocity.tag.inside["attr-value"].inside.rest=t.languages.velocity}e.exports=t,t.displayName="velocity",t.aliases=[]},91824(e){"use strict";function t(e){e.languages.verilog={comment:/\/\/.*|\/\*[\s\S]*?\*\//,string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},property:/\B\$\w+\b/,constant:/\B`\w+\b/,function:/\b\w+(?=\()/,keyword:/\b(?:alias|and|assert|assign|assume|automatic|before|begin|bind|bins|binsof|bit|break|buf|bufif0|bufif1|byte|class|case|casex|casez|cell|chandle|clocking|cmos|config|const|constraint|context|continue|cover|covergroup|coverpoint|cross|deassign|default|defparam|design|disable|dist|do|edge|else|end|endcase|endclass|endclocking|endconfig|endfunction|endgenerate|endgroup|endinterface|endmodule|endpackage|endprimitive|endprogram|endproperty|endspecify|endsequence|endtable|endtask|enum|event|expect|export|extends|extern|final|first_match|for|force|foreach|forever|fork|forkjoin|function|generate|genvar|highz0|highz1|if|iff|ifnone|ignore_bins|illegal_bins|import|incdir|include|initial|inout|input|inside|instance|int|integer|interface|intersect|join|join_any|join_none|large|liblist|library|local|localparam|logic|longint|macromodule|matches|medium|modport|module|nand|negedge|new|nmos|nor|noshowcancelled|not|notif0|notif1|null|or|output|package|packed|parameter|pmos|posedge|primitive|priority|program|property|protected|pull0|pull1|pulldown|pullup|pulsestyle_onevent|pulsestyle_ondetect|pure|rand|randc|randcase|randsequence|rcmos|real|realtime|ref|reg|release|repeat|return|rnmos|rpmos|rtran|rtranif0|rtranif1|scalared|sequence|shortint|shortreal|showcancelled|signed|small|solve|specify|specparam|static|string|strong0|strong1|struct|super|supply0|supply1|table|tagged|task|this|throughout|time|timeprecision|timeunit|tran|tranif0|tranif1|tri|tri0|tri1|triand|trior|trireg|type|typedef|union|unique|unsigned|use|uwire|var|vectored|virtual|void|wait|wait_order|wand|weak0|weak1|while|wildcard|wire|with|within|wor|xnor|xor)\b/,important:/\b(?:always_latch|always_comb|always_ff|always)\b ?@?/,number:/\B##?\d+|(?:\b\d+)?'[odbh] ?[\da-fzx_?]+|\b(?:\d*[._])?\d+(?:e[-+]?\d+)?/i,operator:/[-+{}^~%*\/?=!<>&|]+/,punctuation:/[[\];(),.:]/}}e.exports=t,t.displayName="verilog",t.aliases=[]},9447(e){"use strict";function t(e){e.languages.vhdl={comment:/--.+/,"vhdl-vectors":{pattern:/\b[oxb]"[\da-f_]+"|"[01uxzwlh-]+"/i,alias:"number"},"quoted-function":{pattern:/"\S+?"(?=\()/,alias:"function"},string:/"(?:[^\\"\r\n]|\\(?:\r\n|[\s\S]))*"/,constant:/\b(?:use|library)\b/i,keyword:/\b(?:'active|'ascending|'base|'delayed|'driving|'driving_value|'event|'high|'image|'instance_name|'last_active|'last_event|'last_value|'left|'leftof|'length|'low|'path_name|'pos|'pred|'quiet|'range|'reverse_range|'right|'rightof|'simple_name|'stable|'succ|'transaction|'val|'value|access|after|alias|all|architecture|array|assert|attribute|begin|block|body|buffer|bus|case|component|configuration|constant|disconnect|downto|else|elsif|end|entity|exit|file|for|function|generate|generic|group|guarded|if|impure|in|inertial|inout|is|label|library|linkage|literal|loop|map|new|next|null|of|on|open|others|out|package|port|postponed|procedure|process|pure|range|record|register|reject|report|return|select|severity|shared|signal|subtype|then|to|transport|type|unaffected|units|until|use|variable|wait|when|while|with)\b/i,boolean:/\b(?:true|false)\b/i,function:/\w+(?=\()/,number:/'[01uxzwlh-]'|\b(?:\d+#[\da-f_.]+#|\d[\d_.]*)(?:e[-+]?\d+)?/i,operator:/[<>]=?|:=|[-+*/&=]|\b(?:abs|not|mod|rem|sll|srl|sla|sra|rol|ror|and|or|nand|xnor|xor|nor)\b/i,punctuation:/[{}[\];(),.:]/}}e.exports=t,t.displayName="vhdl",t.aliases=[]},53062(e){"use strict";function t(e){e.languages.vim={string:/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\r\n]|'')*'/,comment:/".*/,function:/\b\w+(?=\()/,keyword:/\b(?:ab|abbreviate|abc|abclear|abo|aboveleft|al|all|arga|argadd|argd|argdelete|argdo|arge|argedit|argg|argglobal|argl|arglocal|ar|args|argu|argument|as|ascii|bad|badd|ba|ball|bd|bdelete|be|bel|belowright|bf|bfirst|bl|blast|bm|bmodified|bn|bnext|bN|bNext|bo|botright|bp|bprevious|brea|break|breaka|breakadd|breakd|breakdel|breakl|breaklist|br|brewind|bro|browse|bufdo|b|buffer|buffers|bun|bunload|bw|bwipeout|ca|cabbrev|cabc|cabclear|caddb|caddbuffer|cad|caddexpr|caddf|caddfile|cal|call|cat|catch|cb|cbuffer|cc|ccl|cclose|cd|ce|center|cex|cexpr|cf|cfile|cfir|cfirst|cgetb|cgetbuffer|cgete|cgetexpr|cg|cgetfile|c|change|changes|chd|chdir|che|checkpath|checkt|checktime|cla|clast|cl|clist|clo|close|cmapc|cmapclear|cnew|cnewer|cn|cnext|cN|cNext|cnf|cnfile|cNfcNfile|cnorea|cnoreabbrev|col|colder|colo|colorscheme|comc|comclear|comp|compiler|conf|confirm|con|continue|cope|copen|co|copy|cpf|cpfile|cp|cprevious|cq|cquit|cr|crewind|cuna|cunabbrev|cu|cunmap|cw|cwindow|debugg|debuggreedy|delc|delcommand|d|delete|delf|delfunction|delm|delmarks|diffg|diffget|diffoff|diffpatch|diffpu|diffput|diffsplit|diffthis|diffu|diffupdate|dig|digraphs|di|display|dj|djump|dl|dlist|dr|drop|ds|dsearch|dsp|dsplit|earlier|echoe|echoerr|echom|echomsg|echon|e|edit|el|else|elsei|elseif|em|emenu|endfo|endfor|endf|endfunction|endfun|en|endif|endt|endtry|endw|endwhile|ene|enew|ex|exi|exit|exu|exusage|f|file|files|filetype|fina|finally|fin|find|fini|finish|fir|first|fix|fixdel|fo|fold|foldc|foldclose|folddoc|folddoclosed|foldd|folddoopen|foldo|foldopen|for|fu|fun|function|go|goto|gr|grep|grepa|grepadd|ha|hardcopy|h|help|helpf|helpfind|helpg|helpgrep|helpt|helptags|hid|hide|his|history|ia|iabbrev|iabc|iabclear|if|ij|ijump|il|ilist|imapc|imapclear|in|inorea|inoreabbrev|isearch|isp|isplit|iuna|iunabbrev|iu|iunmap|j|join|ju|jumps|k|keepalt|keepj|keepjumps|kee|keepmarks|laddb|laddbuffer|lad|laddexpr|laddf|laddfile|lan|language|la|last|later|lb|lbuffer|lc|lcd|lch|lchdir|lcl|lclose|let|left|lefta|leftabove|lex|lexpr|lf|lfile|lfir|lfirst|lgetb|lgetbuffer|lgete|lgetexpr|lg|lgetfile|lgr|lgrep|lgrepa|lgrepadd|lh|lhelpgrep|l|list|ll|lla|llast|lli|llist|lmak|lmake|lm|lmap|lmapc|lmapclear|lnew|lnewer|lne|lnext|lN|lNext|lnf|lnfile|lNf|lNfile|ln|lnoremap|lo|loadview|loc|lockmarks|lockv|lockvar|lol|lolder|lop|lopen|lpf|lpfile|lp|lprevious|lr|lrewind|ls|lt|ltag|lu|lunmap|lv|lvimgrep|lvimgrepa|lvimgrepadd|lw|lwindow|mak|make|ma|mark|marks|mat|match|menut|menutranslate|mk|mkexrc|mks|mksession|mksp|mkspell|mkvie|mkview|mkv|mkvimrc|mod|mode|m|move|mzf|mzfile|mz|mzscheme|nbkey|new|n|next|N|Next|nmapc|nmapclear|noh|nohlsearch|norea|noreabbrev|nu|number|nun|nunmap|omapc|omapclear|on|only|o|open|opt|options|ou|ounmap|pc|pclose|ped|pedit|pe|perl|perld|perldo|po|pop|popu|popup|pp|ppop|pre|preserve|prev|previous|p|print|P|Print|profd|profdel|prof|profile|promptf|promptfind|promptr|promptrepl|ps|psearch|pta|ptag|ptf|ptfirst|ptj|ptjump|ptl|ptlast|ptn|ptnext|ptN|ptNext|ptp|ptprevious|ptr|ptrewind|pts|ptselect|pu|put|pw|pwd|pyf|pyfile|py|python|qa|qall|q|quit|quita|quitall|r|read|rec|recover|redi|redir|red|redo|redr|redraw|redraws|redrawstatus|reg|registers|res|resize|ret|retab|retu|return|rew|rewind|ri|right|rightb|rightbelow|rub|ruby|rubyd|rubydo|rubyf|rubyfile|ru|runtime|rv|rviminfo|sal|sall|san|sandbox|sa|sargument|sav|saveas|sba|sball|sbf|sbfirst|sbl|sblast|sbm|sbmodified|sbn|sbnext|sbN|sbNext|sbp|sbprevious|sbr|sbrewind|sb|sbuffer|scripte|scriptencoding|scrip|scriptnames|se|set|setf|setfiletype|setg|setglobal|setl|setlocal|sf|sfind|sfir|sfirst|sh|shell|sign|sil|silent|sim|simalt|sla|slast|sl|sleep|sm|smagic|smap|smapc|smapclear|sme|smenu|sn|snext|sN|sNext|sni|sniff|sno|snomagic|snor|snoremap|snoreme|snoremenu|sor|sort|so|source|spelld|spelldump|spe|spellgood|spelli|spellinfo|spellr|spellrepall|spellu|spellundo|spellw|spellwrong|sp|split|spr|sprevious|sre|srewind|sta|stag|startg|startgreplace|star|startinsert|startr|startreplace|stj|stjump|st|stop|stopi|stopinsert|sts|stselect|sun|sunhide|sunm|sunmap|sus|suspend|sv|sview|syncbind|t|tab|tabc|tabclose|tabd|tabdo|tabe|tabedit|tabf|tabfind|tabfir|tabfirst|tabl|tablast|tabm|tabmove|tabnew|tabn|tabnext|tabN|tabNext|tabo|tabonly|tabp|tabprevious|tabr|tabrewind|tabs|ta|tag|tags|tc|tcl|tcld|tcldo|tclf|tclfile|te|tearoff|tf|tfirst|th|throw|tj|tjump|tl|tlast|tm|tmenu|tn|tnext|tN|tNext|to|topleft|tp|tprevious|tr|trewind|try|ts|tselect|tu|tunmenu|una|unabbreviate|u|undo|undoj|undojoin|undol|undolist|unh|unhide|unlet|unlo|unlockvar|unm|unmap|up|update|verb|verbose|ve|version|vert|vertical|vie|view|vim|vimgrep|vimgrepa|vimgrepadd|vi|visual|viu|viusage|vmapc|vmapclear|vne|vnew|vs|vsplit|vu|vunmap|wa|wall|wh|while|winc|wincmd|windo|winp|winpos|win|winsize|wn|wnext|wN|wNext|wp|wprevious|wq|wqa|wqall|w|write|ws|wsverb|wv|wviminfo|X|xa|xall|x|xit|xm|xmap|xmapc|xmapclear|xme|xmenu|XMLent|XMLns|xn|xnoremap|xnoreme|xnoremenu|xu|xunmap|y|yank)\b/,builtin:/\b(?:autocmd|acd|ai|akm|aleph|allowrevins|altkeymap|ambiwidth|ambw|anti|antialias|arab|arabic|arabicshape|ari|arshape|autochdir|autoindent|autoread|autowrite|autowriteall|aw|awa|background|backspace|backup|backupcopy|backupdir|backupext|backupskip|balloondelay|ballooneval|balloonexpr|bdir|bdlay|beval|bex|bexpr|bg|bh|bin|binary|biosk|bioskey|bk|bkc|bomb|breakat|brk|browsedir|bs|bsdir|bsk|bt|bufhidden|buflisted|buftype|casemap|ccv|cdpath|cedit|cfu|ch|charconvert|ci|cin|cindent|cink|cinkeys|cino|cinoptions|cinw|cinwords|clipboard|cmdheight|cmdwinheight|cmp|cms|columns|com|comments|commentstring|compatible|complete|completefunc|completeopt|consk|conskey|copyindent|cot|cpo|cpoptions|cpt|cscopepathcomp|cscopeprg|cscopequickfix|cscopetag|cscopetagorder|cscopeverbose|cspc|csprg|csqf|cst|csto|csverb|cuc|cul|cursorcolumn|cursorline|cwh|debug|deco|def|define|delcombine|dex|dg|dict|dictionary|diff|diffexpr|diffopt|digraph|dip|dir|directory|dy|ea|ead|eadirection|eb|ed|edcompatible|ef|efm|ei|ek|enc|encoding|endofline|eol|ep|equalalways|equalprg|errorbells|errorfile|errorformat|esckeys|et|eventignore|expandtab|exrc|fcl|fcs|fdc|fde|fdi|fdl|fdls|fdm|fdn|fdo|fdt|fen|fenc|fencs|fex|ff|ffs|fileencoding|fileencodings|fileformat|fileformats|fillchars|fk|fkmap|flp|fml|fmr|foldcolumn|foldenable|foldexpr|foldignore|foldlevel|foldlevelstart|foldmarker|foldmethod|foldminlines|foldnestmax|foldtext|formatexpr|formatlistpat|formatoptions|formatprg|fp|fs|fsync|ft|gcr|gd|gdefault|gfm|gfn|gfs|gfw|ghr|gp|grepformat|grepprg|gtl|gtt|guicursor|guifont|guifontset|guifontwide|guiheadroom|guioptions|guipty|guitablabel|guitabtooltip|helpfile|helpheight|helplang|hf|hh|hi|hidden|highlight|hk|hkmap|hkmapp|hkp|hl|hlg|hls|hlsearch|ic|icon|iconstring|ignorecase|im|imactivatekey|imak|imc|imcmdline|imd|imdisable|imi|iminsert|ims|imsearch|inc|include|includeexpr|incsearch|inde|indentexpr|indentkeys|indk|inex|inf|infercase|insertmode|isf|isfname|isi|isident|isk|iskeyword|isprint|joinspaces|js|key|keymap|keymodel|keywordprg|km|kmp|kp|langmap|langmenu|laststatus|lazyredraw|lbr|lcs|linebreak|lines|linespace|lisp|lispwords|listchars|loadplugins|lpl|lsp|lz|macatsui|magic|makeef|makeprg|matchpairs|matchtime|maxcombine|maxfuncdepth|maxmapdepth|maxmem|maxmempattern|maxmemtot|mco|mef|menuitems|mfd|mh|mis|mkspellmem|ml|mls|mm|mmd|mmp|mmt|modeline|modelines|modifiable|modified|more|mouse|mousef|mousefocus|mousehide|mousem|mousemodel|mouses|mouseshape|mouset|mousetime|mp|mps|msm|mzq|mzquantum|nf|nrformats|numberwidth|nuw|odev|oft|ofu|omnifunc|opendevice|operatorfunc|opfunc|osfiletype|pa|para|paragraphs|paste|pastetoggle|patchexpr|patchmode|path|pdev|penc|pex|pexpr|pfn|ph|pheader|pi|pm|pmbcs|pmbfn|popt|preserveindent|previewheight|previewwindow|printdevice|printencoding|printexpr|printfont|printheader|printmbcharset|printmbfont|printoptions|prompt|pt|pumheight|pvh|pvw|qe|quoteescape|readonly|remap|report|restorescreen|revins|rightleft|rightleftcmd|rl|rlc|ro|rs|rtp|ruf|ruler|rulerformat|runtimepath|sbo|sc|scb|scr|scroll|scrollbind|scrolljump|scrolloff|scrollopt|scs|sect|sections|secure|sel|selection|selectmode|sessionoptions|sft|shcf|shellcmdflag|shellpipe|shellquote|shellredir|shellslash|shelltemp|shelltype|shellxquote|shiftround|shiftwidth|shm|shortmess|shortname|showbreak|showcmd|showfulltag|showmatch|showmode|showtabline|shq|si|sidescroll|sidescrolloff|siso|sj|slm|smartcase|smartindent|smarttab|smc|smd|softtabstop|sol|spc|spell|spellcapcheck|spellfile|spelllang|spellsuggest|spf|spl|splitbelow|splitright|sps|sr|srr|ss|ssl|ssop|stal|startofline|statusline|stl|stmp|su|sua|suffixes|suffixesadd|sw|swapfile|swapsync|swb|swf|switchbuf|sws|sxq|syn|synmaxcol|syntax|tabline|tabpagemax|tabstop|tagbsearch|taglength|tagrelative|tagstack|tal|tb|tbi|tbidi|tbis|tbs|tenc|term|termbidi|termencoding|terse|textauto|textmode|textwidth|tgst|thesaurus|tildeop|timeout|timeoutlen|title|titlelen|titleold|titlestring|toolbar|toolbariconsize|top|tpm|tsl|tsr|ttimeout|ttimeoutlen|ttm|tty|ttybuiltin|ttyfast|ttym|ttymouse|ttyscroll|ttytype|tw|tx|uc|ul|undolevels|updatecount|updatetime|ut|vb|vbs|vdir|verbosefile|vfile|viewdir|viewoptions|viminfo|virtualedit|visualbell|vop|wak|warn|wb|wc|wcm|wd|weirdinvert|wfh|wfw|whichwrap|wi|wig|wildchar|wildcharm|wildignore|wildmenu|wildmode|wildoptions|wim|winaltkeys|window|winfixheight|winfixwidth|winheight|winminheight|winminwidth|winwidth|wiv|wiw|wm|wmh|wmnu|wmw|wop|wrap|wrapmargin|wrapscan|writeany|writebackup|writedelay|ww|noacd|noai|noakm|noallowrevins|noaltkeymap|noanti|noantialias|noar|noarab|noarabic|noarabicshape|noari|noarshape|noautochdir|noautoindent|noautoread|noautowrite|noautowriteall|noaw|noawa|nobackup|noballooneval|nobeval|nobin|nobinary|nobiosk|nobioskey|nobk|nobl|nobomb|nobuflisted|nocf|noci|nocin|nocindent|nocompatible|noconfirm|noconsk|noconskey|nocopyindent|nocp|nocscopetag|nocscopeverbose|nocst|nocsverb|nocuc|nocul|nocursorcolumn|nocursorline|nodeco|nodelcombine|nodg|nodiff|nodigraph|nodisable|noea|noeb|noed|noedcompatible|noek|noendofline|noeol|noequalalways|noerrorbells|noesckeys|noet|noex|noexpandtab|noexrc|nofen|nofk|nofkmap|nofoldenable|nogd|nogdefault|noguipty|nohid|nohidden|nohk|nohkmap|nohkmapp|nohkp|nohls|noic|noicon|noignorecase|noim|noimc|noimcmdline|noimd|noincsearch|noinf|noinfercase|noinsertmode|nois|nojoinspaces|nojs|nolazyredraw|nolbr|nolinebreak|nolisp|nolist|noloadplugins|nolpl|nolz|noma|nomacatsui|nomagic|nomh|noml|nomod|nomodeline|nomodifiable|nomodified|nomore|nomousef|nomousefocus|nomousehide|nonu|nonumber|noodev|noopendevice|nopaste|nopi|nopreserveindent|nopreviewwindow|noprompt|nopvw|noreadonly|noremap|norestorescreen|norevins|nori|norightleft|norightleftcmd|norl|norlc|noro|nors|noru|noruler|nosb|nosc|noscb|noscrollbind|noscs|nosecure|nosft|noshellslash|noshelltemp|noshiftround|noshortname|noshowcmd|noshowfulltag|noshowmatch|noshowmode|nosi|nosm|nosmartcase|nosmartindent|nosmarttab|nosmd|nosn|nosol|nospell|nosplitbelow|nosplitright|nospr|nosr|nossl|nosta|nostartofline|nostmp|noswapfile|noswf|nota|notagbsearch|notagrelative|notagstack|notbi|notbidi|notbs|notermbidi|noterse|notextauto|notextmode|notf|notgst|notildeop|notimeout|notitle|noto|notop|notr|nottimeout|nottybuiltin|nottyfast|notx|novb|novisualbell|nowa|nowarn|nowb|noweirdinvert|nowfh|nowfw|nowildmenu|nowinfixheight|nowinfixwidth|nowiv|nowmnu|nowrap|nowrapscan|nowrite|nowriteany|nowritebackup|nows|invacd|invai|invakm|invallowrevins|invaltkeymap|invanti|invantialias|invar|invarab|invarabic|invarabicshape|invari|invarshape|invautochdir|invautoindent|invautoread|invautowrite|invautowriteall|invaw|invawa|invbackup|invballooneval|invbeval|invbin|invbinary|invbiosk|invbioskey|invbk|invbl|invbomb|invbuflisted|invcf|invci|invcin|invcindent|invcompatible|invconfirm|invconsk|invconskey|invcopyindent|invcp|invcscopetag|invcscopeverbose|invcst|invcsverb|invcuc|invcul|invcursorcolumn|invcursorline|invdeco|invdelcombine|invdg|invdiff|invdigraph|invdisable|invea|inveb|inved|invedcompatible|invek|invendofline|inveol|invequalalways|inverrorbells|invesckeys|invet|invex|invexpandtab|invexrc|invfen|invfk|invfkmap|invfoldenable|invgd|invgdefault|invguipty|invhid|invhidden|invhk|invhkmap|invhkmapp|invhkp|invhls|invhlsearch|invic|invicon|invignorecase|invim|invimc|invimcmdline|invimd|invincsearch|invinf|invinfercase|invinsertmode|invis|invjoinspaces|invjs|invlazyredraw|invlbr|invlinebreak|invlisp|invlist|invloadplugins|invlpl|invlz|invma|invmacatsui|invmagic|invmh|invml|invmod|invmodeline|invmodifiable|invmodified|invmore|invmousef|invmousefocus|invmousehide|invnu|invnumber|invodev|invopendevice|invpaste|invpi|invpreserveindent|invpreviewwindow|invprompt|invpvw|invreadonly|invremap|invrestorescreen|invrevins|invri|invrightleft|invrightleftcmd|invrl|invrlc|invro|invrs|invru|invruler|invsb|invsc|invscb|invscrollbind|invscs|invsecure|invsft|invshellslash|invshelltemp|invshiftround|invshortname|invshowcmd|invshowfulltag|invshowmatch|invshowmode|invsi|invsm|invsmartcase|invsmartindent|invsmarttab|invsmd|invsn|invsol|invspell|invsplitbelow|invsplitright|invspr|invsr|invssl|invsta|invstartofline|invstmp|invswapfile|invswf|invta|invtagbsearch|invtagrelative|invtagstack|invtbi|invtbidi|invtbs|invtermbidi|invterse|invtextauto|invtextmode|invtf|invtgst|invtildeop|invtimeout|invtitle|invto|invtop|invtr|invttimeout|invttybuiltin|invttyfast|invtx|invvb|invvisualbell|invwa|invwarn|invwb|invweirdinvert|invwfh|invwfw|invwildmenu|invwinfixheight|invwinfixwidth|invwiv|invwmnu|invwrap|invwrapscan|invwrite|invwriteany|invwritebackup|invws|t_AB|t_AF|t_al|t_AL|t_bc|t_cd|t_ce|t_Ce|t_cl|t_cm|t_Co|t_cs|t_Cs|t_CS|t_CV|t_da|t_db|t_dl|t_DL|t_EI|t_F1|t_F2|t_F3|t_F4|t_F5|t_F6|t_F7|t_F8|t_F9|t_fs|t_IE|t_IS|t_k1|t_K1|t_k2|t_k3|t_K3|t_k4|t_K4|t_k5|t_K5|t_k6|t_K6|t_k7|t_K7|t_k8|t_K8|t_k9|t_K9|t_KA|t_kb|t_kB|t_KB|t_KC|t_kd|t_kD|t_KD|t_ke|t_KE|t_KF|t_KG|t_kh|t_KH|t_kI|t_KI|t_KJ|t_KK|t_kl|t_KL|t_kN|t_kP|t_kr|t_ks|t_ku|t_le|t_mb|t_md|t_me|t_mr|t_ms|t_nd|t_op|t_RI|t_RV|t_Sb|t_se|t_Sf|t_SI|t_so|t_sr|t_te|t_ti|t_ts|t_ue|t_us|t_ut|t_vb|t_ve|t_vi|t_vs|t_WP|t_WS|t_xs|t_ZH|t_ZR)\b/,number:/\b(?:0x[\da-f]+|\d+(?:\.\d+)?)\b/i,operator:/\|\||&&|[-+.]=?|[=!](?:[=~][#?]?)?|[<>]=?[#?]?|[*\/%?]|\b(?:is(?:not)?)\b/,punctuation:/[{}[\](),;:]/}}e.exports=t,t.displayName="vim",t.aliases=[]},46215(e){"use strict";function t(e){e.languages["visual-basic"]={comment:{pattern:/(?:['‘’]|REM\b)(?:[^\r\n_]|_(?:\r\n?|\n)?)*/i,inside:{keyword:/^REM/i}},directive:{pattern:/#(?:Const|Else|ElseIf|End|ExternalChecksum|ExternalSource|If|Region)(?:[^\S\r\n]_[^\S\r\n]*(?:\r\n?|\n)|.)+/i,alias:"comment",greedy:!0},string:{pattern:/\$?["“”](?:["“”]{2}|[^"“”])*["“”]C?/i,greedy:!0},date:{pattern:/#[^\S\r\n]*(?:\d+([/-])\d+\1\d+(?:[^\S\r\n]+(?:\d+[^\S\r\n]*(?:AM|PM)|\d+:\d+(?::\d+)?(?:[^\S\r\n]*(?:AM|PM))?))?|\d+[^\S\r\n]*(?:AM|PM)|\d+:\d+(?::\d+)?(?:[^\S\r\n]*(?:AM|PM))?)[^\S\r\n]*#/i,alias:"builtin"},number:/(?:(?:\b\d+(?:\.\d+)?|\.\d+)(?:E[+-]?\d+)?|&[HO][\dA-F]+)(?:U?[ILS]|[FRD])?/i,boolean:/\b(?:True|False|Nothing)\b/i,keyword:/\b(?:AddHandler|AddressOf|Alias|And(?:Also)?|As|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|C(?:Bool|Byte|Char|Date|Dbl|Dec|Int|Lng|Obj|SByte|Short|Sng|Str|Type|UInt|ULng|UShort)|Char|Class|Const|Continue|Currency|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else(?:If)?|End(?:If)?|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get(?:Type|XMLNamespace)?|Global|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|IsNot|Let|Lib|Like|Long|Loop|Me|Mod|Module|Must(?:Inherit|Override)|My(?:Base|Class)|Namespace|Narrowing|New|Next|Not(?:Inheritable|Overridable)?|Object|Of|On|Operator|Option(?:al)?|Or(?:Else)?|Out|Overloads|Overridable|Overrides|ParamArray|Partial|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|SByte|Select|Set|Shadows|Shared|short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TryCast|Type|TypeOf|U(?:Integer|Long|Short)|Using|Variant|Wend|When|While|Widening|With(?:Events)?|WriteOnly|Until|Xor)\b/i,operator:[/[+\-*/\\^<=>&#@$%!]/,{pattern:/([^\S\r\n])_(?=[^\S\r\n]*[\r\n])/,lookbehind:!0}],punctuation:/[{}().,:?]/},e.languages.vb=e.languages["visual-basic"],e.languages.vba=e.languages["visual-basic"]}e.exports=t,t.displayName="visualBasic",t.aliases=[]},10784(e){"use strict";function t(e){e.languages.warpscript={comment:/#.*|\/\/.*|\/\*[\s\S]*?\*\//,string:{pattern:/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'|<'(?:[^\\']|'(?!>)|\\.)*'>/,greedy:!0},variable:/\$\S+/,macro:{pattern:/@\S+/,alias:"property"},keyword:/\b(?:BREAK|CHECKMACRO|CONTINUE|CUDF|DEFINED|DEFINEDMACRO|EVAL|FAIL|FOR|FOREACH|FORSTEP|IFT|IFTE|MSGFAIL|NRETURN|RETHROW|RETURN|SWITCH|TRY|UDF|UNTIL|WHILE)\b/,number:/[+-]?\b(?:NaN|Infinity|\d+(?:\.\d*)?(?:[Ee][+-]?\d+)?|0x[\da-fA-F]+|0b[01]+)\b/,boolean:/\b(?:false|true|F|T)\b/,punctuation:/<%|%>|[{}[\]()]/,operator:/==|&&?|\|\|?|\*\*?|>>>?|<<|[<>!~]=?|[-/%^]|\+!?|\b(?:AND|NOT|OR)\b/}}e.exports=t,t.displayName="warpscript",t.aliases=[]},17684(e){"use strict";function t(e){e.languages.wasm={comment:[/\(;[\s\S]*?;\)/,{pattern:/;;.*/,greedy:!0}],string:{pattern:/"(?:\\[\s\S]|[^"\\])*"/,greedy:!0},keyword:[{pattern:/\b(?:align|offset)=/,inside:{operator:/=/}},{pattern:/\b(?:(?:f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))?|memory\.(?:grow|size))\b/,inside:{punctuation:/\./}},/\b(?:anyfunc|block|br(?:_if|_table)?|call(?:_indirect)?|data|drop|elem|else|end|export|func|get_(?:global|local)|global|if|import|local|loop|memory|module|mut|nop|offset|param|result|return|select|set_(?:global|local)|start|table|tee_local|then|type|unreachable)\b/],variable:/\$[\w!#$%&'*+\-./:<=>?@\\^`|~]+/i,number:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/,punctuation:/[()]/}}e.exports=t,t.displayName="wasm",t.aliases=[]},18191(e){"use strict";function t(e){e.languages.wiki=e.languages.extend("markup",{"block-comment":{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0,alias:"comment"},heading:{pattern:/^(=+)[^=\r\n].*?\1/m,inside:{punctuation:/^=+|=+$/,important:/.+/}},emphasis:{pattern:/('{2,5}).+?\1/,inside:{"bold-italic":{pattern:/(''''').+?(?=\1)/,lookbehind:!0,alias:["bold","italic"]},bold:{pattern:/(''')[^'](?:.*?[^'])?(?=\1)/,lookbehind:!0},italic:{pattern:/('')[^'](?:.*?[^'])?(?=\1)/,lookbehind:!0},punctuation:/^''+|''+$/}},hr:{pattern:/^-{4,}/m,alias:"punctuation"},url:[/ISBN +(?:97[89][ -]?)?(?:\d[ -]?){9}[\dx]\b|(?:RFC|PMID) +\d+/i,/\[\[.+?\]\]|\[.+?\]/],variable:[/__[A-Z]+__/,/\{{3}.+?\}{3}/,/\{\{.+?\}\}/],symbol:[/^#redirect/im,/~{3,5}/],"table-tag":{pattern:/((?:^|[|!])[|!])[^|\r\n]+\|(?!\|)/m,lookbehind:!0,inside:{"table-bar":{pattern:/\|$/,alias:"punctuation"},rest:e.languages.markup.tag.inside}},punctuation:/^(?:\{\||\|\}|\|-|[*#:;!|])|\|\||!!/m}),e.languages.insertBefore("wiki","tag",{nowiki:{pattern:/<(nowiki|pre|source)\b[^>]*>[\s\S]*?<\/\1>/i,inside:{tag:{pattern:/<(?:nowiki|pre|source)\b[^>]*>|<\/(?:nowiki|pre|source)>/i,inside:e.languages.markup.tag.inside}}}})}e.exports=t,t.displayName="wiki",t.aliases=[]},75242(e){"use strict";function t(e){e.languages.wolfram={comment:/\(\*(?:\(\*(?:[^*]|\*(?!\)))*\*\)|(?!\(\*)[\s\S])*?\*\)/,string:{pattern:/"(?:\\.|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:Abs|AbsArg|Accuracy|Block|Do|For|Function|If|Manipulate|Module|Nest|NestList|None|Return|Switch|Table|Which|While)\b/,context:{pattern:/\w+`+\w*/,alias:"class-name"},blank:{pattern:/\b\w+_\b/,alias:"regex"},"global-variable":{pattern:/\$\w+/,alias:"variable"},boolean:/\b(?:True|False)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/\/\.|;|=\.|\^=|\^:=|:=|<<|>>|<\||\|>|:>|\|->|->|<-|@@@|@@|@|\/@|=!=|===|==|=|\+|-|\^|\[\/-+%=\]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[\|{}[\];(),.:]/},e.languages.mathematica=e.languages.wolfram,e.languages.wl=e.languages.wolfram,e.languages.nb=e.languages.wolfram}e.exports=t,t.displayName="wolfram",t.aliases=["mathematica","wl","nb"]},97202(e){"use strict";function t(e){var t;(t=e).languages.xeora=t.languages.extend("markup",{constant:{pattern:/\$(?:DomainContents|PageRenderDuration)\$/,inside:{punctuation:{pattern:/\$/}}},variable:{pattern:/\$@?(?:#+|[-+*~=^])?[\w.]+\$/,inside:{punctuation:{pattern:/[$.]/},operator:{pattern:/#+|[-+*~=^@]/}}},"function-inline":{pattern:/\$F:[-\w.]+\?[-\w.]+(?:,(?:(?:@[-#]*\w+\.[\w+.]\.*)*\|)*(?:(?:[\w+]|[-#*.~^]+[\w+]|=\S)(?:[^$=]|=+[^=])*=*|(?:@[-#]*\w+\.[\w+.]\.*)+(?:(?:[\w+]|[-#*~^][-#*.~^]*[\w+]|=\S)(?:[^$=]|=+[^=])*=*)?)?)?\$/,inside:{variable:{pattern:/(?:[,|])@?(?:#+|[-+*~=^])?[\w.]+/,inside:{punctuation:{pattern:/[,.|]/},operator:{pattern:/#+|[-+*~=^@]/}}},punctuation:{pattern:/\$\w:|[$:?.,|]/}},alias:"function"},"function-block":{pattern:/\$XF:\{[-\w.]+\?[-\w.]+(?:,(?:(?:@[-#]*\w+\.[\w+.]\.*)*\|)*(?:(?:[\w+]|[-#*.~^]+[\w+]|=\S)(?:[^$=]|=+[^=])*=*|(?:@[-#]*\w+\.[\w+.]\.*)+(?:(?:[\w+]|[-#*~^][-#*.~^]*[\w+]|=\S)(?:[^$=]|=+[^=])*=*)?)?)?\}:XF\$/,inside:{punctuation:{pattern:/[$:{}?.,|]/}},alias:"function"},"directive-inline":{pattern:/\$\w(?:#\d+\+?)?(?:\[[-\w.]+\])?:[-\/\w.]+\$/,inside:{punctuation:{pattern:/\$(?:\w:|C(?:\[|#\d))?|[:{[\]]/,inside:{tag:{pattern:/#\d/}}}},alias:"function"},"directive-block-open":{pattern:/\$\w+:\{|\$\w(?:#\d+\+?)?(?:\[[-\w.]+\])?:[-\w.]+:\{(?:![A-Z]+)?/,inside:{punctuation:{pattern:/\$(?:\w:|C(?:\[|#\d))?|[:{[\]]/,inside:{tag:{pattern:/#\d/}}},attribute:{pattern:/![A-Z]+$/,inside:{punctuation:{pattern:/!/}},alias:"keyword"}},alias:"function"},"directive-block-separator":{pattern:/\}:[-\w.]+:\{/,inside:{punctuation:{pattern:/[:{}]/}},alias:"function"},"directive-block-close":{pattern:/\}:[-\w.]+\$/,inside:{punctuation:{pattern:/[:{}$]/}},alias:"function"}}),t.languages.insertBefore("inside","punctuation",{variable:t.languages.xeora["function-inline"].inside.variable},t.languages.xeora["function-block"]),t.languages.xeoracube=t.languages.xeora}e.exports=t,t.displayName="xeora",t.aliases=["xeoracube"]},13808(e){"use strict";function t(e){!function(e){function t(t,n){e.languages[t]&&e.languages.insertBefore(t,"comment",{"doc-comment":n})}var n=e.languages.markup.tag,r={pattern:/\/\/\/.*/,greedy:!0,alias:"comment",inside:{tag:n}},i={pattern:/'''.*/,greedy:!0,alias:"comment",inside:{tag:n}};t("csharp",r),t("fsharp",r),t("vbnet",i)}(e)}e.exports=t,t.displayName="xmlDoc",t.aliases=[]},21301(e){"use strict";function t(e){e.languages.xojo={comment:{pattern:/(?:'|\/\/|Rem\b).+/i},string:{pattern:/"(?:""|[^"])*"/,greedy:!0},number:[/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:E[+-]?\d+)?/i,/&[bchou][a-z\d]+/i],symbol:/#(?:If|Else|ElseIf|Endif|Pragma)\b/i,keyword:/\b(?:AddHandler|App|Array|As(?:signs)?|Auto|By(?:Ref|Val)|Boolean|Break|Byte|Call|Case|Catch|CFStringRef|CGFloat|Class|Color|Const|Continue|CString|Currency|CurrentMethodName|Declare|Delegate|Dim|Do(?:uble|wnTo)?|Each|Else(?:If)?|End|Enumeration|Event|Exception|Exit|Extends|False|Finally|For|Function|Get|GetTypeInfo|Global|GOTO|If|Implements|In|Inherits|Int(?:erface|eger|8|16|32|64)?|Lib|Loop|Me|Module|Next|Nil|Object|Optional|OSType|ParamArray|Private|Property|Protected|PString|Ptr|Raise(?:Event)?|ReDim|RemoveHandler|Return|Select(?:or)?|Self|Set|Single|Shared|Short|Soft|Static|Step|String|Sub|Super|Text|Then|To|True|Try|Ubound|UInt(?:eger|8|16|32|64)?|Until|Using|Var(?:iant)?|Wend|While|WindowPtr|WString)\b/i,operator:/<[=>]?|>=?|[+\-*\/\\^=]|\b(?:AddressOf|And|Ctype|IsA?|Mod|New|Not|Or|Xor|WeakAddressOf)\b/i,punctuation:/[.,;:()]/}}e.exports=t,t.displayName="xojo",t.aliases=[]},20349(e){"use strict";function t(e){var t,n,r;(t=e).languages.xquery=t.languages.extend("markup",{"xquery-comment":{pattern:/\(:[\s\S]*?:\)/,greedy:!0,alias:"comment"},string:{pattern:/(["'])(?:\1\1|(?!\1)[\s\S])*\1/,greedy:!0},extension:{pattern:/\(#.+?#\)/,alias:"symbol"},variable:/\$[-\w:]+/,axis:{pattern:/(^|[^-])(?:ancestor(?:-or-self)?|attribute|child|descendant(?:-or-self)?|following(?:-sibling)?|parent|preceding(?:-sibling)?|self)(?=::)/,lookbehind:!0,alias:"operator"},"keyword-operator":{pattern:/(^|[^:-])\b(?:and|castable as|div|eq|except|ge|gt|idiv|instance of|intersect|is|le|lt|mod|ne|or|union)\b(?=$|[^:-])/,lookbehind:!0,alias:"operator"},keyword:{pattern:/(^|[^:-])\b(?:as|ascending|at|base-uri|boundary-space|case|cast as|collation|construction|copy-namespaces|declare|default|descending|else|empty (?:greatest|least)|encoding|every|external|for|function|if|import|in|inherit|lax|let|map|module|namespace|no-inherit|no-preserve|option|order(?: by|ed|ing)?|preserve|return|satisfies|schema|some|stable|strict|strip|then|to|treat as|typeswitch|unordered|validate|variable|version|where|xquery)\b(?=$|[^:-])/,lookbehind:!0},function:/[\w-]+(?::[\w-]+)*(?=\s*\()/,"xquery-element":{pattern:/(element\s+)[\w-]+(?::[\w-]+)*/,lookbehind:!0,alias:"tag"},"xquery-attribute":{pattern:/(attribute\s+)[\w-]+(?::[\w-]+)*/,lookbehind:!0,alias:"attr-name"},builtin:{pattern:/(^|[^:-])\b(?:attribute|comment|document|element|processing-instruction|text|xs:(?:anyAtomicType|anyType|anyURI|base64Binary|boolean|byte|date|dateTime|dayTimeDuration|decimal|double|duration|ENTITIES|ENTITY|float|gDay|gMonth|gMonthDay|gYear|gYearMonth|hexBinary|ID|IDREFS?|int|integer|language|long|Name|NCName|negativeInteger|NMTOKENS?|nonNegativeInteger|nonPositiveInteger|normalizedString|NOTATION|positiveInteger|QName|short|string|time|token|unsigned(?:Byte|Int|Long|Short)|untyped(?:Atomic)?|yearMonthDuration))\b(?=$|[^:-])/,lookbehind:!0},number:/\b\d+(?:\.\d+)?(?:E[+-]?\d+)?/,operator:[/[+*=?|@]|\.\.?|:=|!=|<[=<]?|>[=>]?/,{pattern:/(\s)-(?=\s)/,lookbehind:!0}],punctuation:/[[\](){},;:/]/}),t.languages.xquery.tag.pattern=/<\/?(?!\d)[^\s>\/=$<%]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|\{(?!\{)(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\}|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,t.languages.xquery.tag.inside["attr-value"].pattern=/=(?:("|')(?:\\[\s\S]|\{(?!\{)(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\}|(?!\1)[^\\])*\1|[^\s'">=]+)/i,t.languages.xquery.tag.inside["attr-value"].inside.punctuation=/^="|"$/,t.languages.xquery.tag.inside["attr-value"].inside.expression={pattern:/\{(?!\{)(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\}/,inside:t.languages.xquery,alias:"language-xquery"},n=function(e){return"string"==typeof e?e:"string"==typeof e.content?e.content:e.content.map(n).join("")},r=function(e){for(var i=[],a=0;a0&&i[i.length-1].tagName===n(o.content[0].content[1])&&i.pop():"/>"===o.content[o.content.length-1].content||i.push({tagName:n(o.content[0].content[1]),openedBraces:0}):!(i.length>0)||"punctuation"!==o.type||"{"!==o.content||e[a+1]&&"punctuation"===e[a+1].type&&"{"===e[a+1].content||e[a-1]&&"plain-text"===e[a-1].type&&"{"===e[a-1].content?i.length>0&&i[i.length-1].openedBraces>0&&"punctuation"===o.type&&"}"===o.content?i[i.length-1].openedBraces--:"comment"!==o.type&&(s=!0):i[i.length-1].openedBraces++),(s||"string"==typeof o)&&i.length>0&&0===i[i.length-1].openedBraces){var u=n(o);a0&&("string"==typeof e[a-1]||"plain-text"===e[a-1].type)&&(u=n(e[a-1])+u,e.splice(a-1,1),a--),/^\s+$/.test(u)?e[a]=u:e[a]=new t.Token("plain-text",u,null,u)}o.content&&"string"!=typeof o.content&&r(o.content)}},t.hooks.add("after-tokenize",function(e){"xquery"===e.language&&r(e.tokens)})}e.exports=t,t.displayName="xquery",t.aliases=[]},65039(e){"use strict";function t(e){!function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ ]+"+t.source+")?|"+t.source+"(?:[ ]+"+n.source+")?)",i=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//g,function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source}),a=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function o(e,t){return t=(t||"").replace(/m/g,"")+"m",RegExp(/([:\-,[{]\s*(?:\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,function(){return r}).replace(/<>/g,function(){return e}),t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,function(){return r})),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,function(){return r}).replace(/<>/g,function(){return"(?:"+i+"|"+a+")"})),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:o(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:o(/true|false/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:o(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:o(a),lookbehind:!0,greedy:!0},number:{pattern:o(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(e)}e.exports=t,t.displayName="yaml",t.aliases=["yml"]},80741(e){"use strict";function t(e){e.languages.yang={comment:/\/\*[\s\S]*?\*\/|\/\/.*/,string:{pattern:/"(?:[^\\"]|\\.)*"|'[^']*'/,greedy:!0},keyword:{pattern:/(^|[{};\r\n][ \t]*)[a-z_][\w.-]*/i,lookbehind:!0},namespace:{pattern:/(\s)[a-z_][\w.-]*(?=:)/i,lookbehind:!0},boolean:/\b(?:false|true)\b/,operator:/\+/,punctuation:/[{};:]/}}e.exports=t,t.displayName="yang",t.aliases=[]},86528(e){"use strict";function t(e){!function(e){function t(e){return function(){return e}}var n=/\b(?:align|allowzero|and|asm|async|await|break|cancel|catch|comptime|const|continue|defer|else|enum|errdefer|error|export|extern|fn|for|if|inline|linksection|nakedcc|noalias|null|or|orelse|packed|promise|pub|resume|return|stdcallcc|struct|suspend|switch|test|threadlocal|try|undefined|union|unreachable|usingnamespace|var|volatile|while)\b/,r="\\b(?!"+n.source+")(?!\\d)\\w+\\b",i=/align\s*\((?:[^()]|\([^()]*\))*\)/.source,a=/(?:\?|\bpromise->|(?:\[[^[\]]*\]|\*(?!\*)|\*\*)(?:\s*|\s*const\b|\s*volatile\b|\s*allowzero\b)*)/.source.replace(//g,t(i)),o=/(?:\bpromise\b|(?:\berror\.)?(?:\.)*(?!\s+))/.source.replace(//g,t(r)),s="(?!\\s)(?:!?\\s*(?:"+a+"\\s*)*"+o+")+";e.languages.zig={comment:[{pattern:/\/{3}.*/,alias:"doc-comment"},/\/{2}.*/],string:[{pattern:/(^|[^\\@])c?"(?:[^"\\\r\n]|\\.)*"/,lookbehind:!0,greedy:!0},{pattern:/([\r\n])([ \t]+c?\\{2}).*(?:(?:\r\n?|\n)\2.*)*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\])'(?:[^'\\\r\n]|\\(?:.|x[a-fA-F\d]{2}|u\{[a-fA-F\d]{1,6}\}))'/,lookbehind:!0,greedy:!0}],builtin:/\B@(?!\d)\w+(?=\s*\()/,label:{pattern:/(\b(?:break|continue)\s*:\s*)\w+\b|\b(?!\d)\w+\b(?=\s*:\s*(?:\{|while\b))/,lookbehind:!0},"class-name":[/\b(?!\d)\w+(?=\s*=\s*(?:(?:extern|packed)\s+)?(?:enum|struct|union)\s*[({])/,{pattern:RegExp(/(:\s*)(?=\s*(?:\s*)?[=;,)])|(?=\s*(?:\s*)?\{)/.source.replace(//g,t(s)).replace(//g,t(i))),lookbehind:!0,inside:null},{pattern:RegExp(/(\)\s*)(?=\s*(?:\s*)?;)/.source.replace(//g,t(s)).replace(//g,t(i))),lookbehind:!0,inside:null}],"builtin-types":{pattern:/\b(?:anyerror|bool|c_u?(?:short|int|long|longlong)|c_longdouble|c_void|comptime_(?:float|int)|[iu](?:8|16|32|64|128|size)|f(?:16|32|64|128)|noreturn|type|void)\b/,alias:"keyword"},keyword:n,function:/\b(?!\d)\w+(?=\s*\()/,number:/\b(?:0b[01]+|0o[0-7]+|0x[a-fA-F\d]+(?:\.[a-fA-F\d]*)?(?:[pP][+-]?[a-fA-F\d]+)?|\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)\b/,boolean:/\b(?:false|true)\b/,operator:/\.[*?]|\.{2,3}|[-=]>|\*\*|\+\+|\|\||(?:<<|>>|[-+*]%|[-+*/%^&|<>!=])=?|[?~]/,punctuation:/[.:,;(){}[\]]/},e.languages.zig["class-name"].forEach(function(t){null===t.inside&&(t.inside=e.languages.zig)})}(e)}e.exports=t,t.displayName="zig",t.aliases=[]},59216(e,t,n){var r=function(e){var t=/\blang(?:uage)?-([\w-]+)\b/i,n=0,r={},i={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(t){return t instanceof a?new a(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=f.reach));S+=E.value.length,E=E.next){var k,x=E.value;if(t.length>e.length)return;if(!(x instanceof a)){var T=1;if(v){if(!(k=o(_,S,e,g)))break;var M=k.index,O=k.index+k[0].length,A=S;for(A+=E.value.length;M>=A;)A+=(E=E.next).value.length;if(A-=E.value.length,S=A,E.value instanceof a)continue;for(var L=E;L!==t.tail&&(Af.reach&&(f.reach=N);var P=E.prev;I&&(P=c(t,P,I),S+=I.length),l(t,P,T);var R=new a(d,m?i.tokenize(C,m):C,y,C);if(E=c(t,P,R),D&&c(t,E,D),T>1){var j={cause:d+","+p,reach:N};s(e,t,n,E.prev,S,j),f&&j.reach>f.reach&&(f.reach=j.reach)}}}}}}function u(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function c(e,t,n){var r=t.next,i={value:n,prev:t,next:r};return t.next=i,r.prev=i,e.length++,i}function l(e,t,n){for(var r=t.next,i=0;i"+a.content+""},!e.document)return e.addEventListener&&(i.disableWorkerMessageHandler||e.addEventListener("message",function(t){var n=JSON.parse(t.data),r=n.language,a=n.code,o=n.immediateClose;e.postMessage(i.highlight(a,i.languages[r],r)),o&&e.close()},!1)),i;var d=i.util.currentScript();function h(){i.manual||i.highlightAll()}if(d&&(i.filename=d.src,d.hasAttribute("data-manual")&&(i.manual=!0)),!i.manual){var p=document.readyState;"loading"===p||"interactive"===p&&d&&d.defer?document.addEventListener("DOMContentLoaded",h):window.requestAnimationFrame?window.requestAnimationFrame(h):window.setTimeout(h,16)}return i}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{});e.exports&&(e.exports=r),void 0!==n.g&&(n.g.Prism=r)},89509(e,t,n){/*! safe-buffer. MIT License. Feross Aboukhadijeh */ var r=n(48764),i=r.Buffer;function a(e,t){for(var n in e)t[n]=e[n]}function o(e,t,n){return i(e,t,n)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?e.exports=r:(a(r,t),t.Buffer=o),o.prototype=Object.create(i.prototype),a(i,o),o.from=function(e,t,n){if("number"==typeof e)throw TypeError("Argument must not be a number");return i(e,t,n)},o.alloc=function(e,t,n){if("number"!=typeof e)throw TypeError("Argument must be a number");var r=i(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},o.allocUnsafe=function(e){if("number"!=typeof e)throw TypeError("Argument must be a number");return i(e)},o.allocUnsafeSlow=function(e){if("number"!=typeof e)throw TypeError("Argument must be a number");return r.SlowBuffer(e)}},60053(e,t){"use strict";if(/** @license React v0.18.0 + * scheduler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ Object.defineProperty(t,"__esModule",{value:!0}),"undefined"==typeof window||"function"!=typeof MessageChannel){var n,r,i,a,o,s=null,u=null,c=function(){if(null!==s)try{var e=t.unstable_now();s(!0,e),s=null}catch(n){throw setTimeout(c,0),n}},l=Date.now();t.unstable_now=function(){return Date.now()-l},n=function(e){null!==s?setTimeout(n,0,e):(s=e,setTimeout(c,0))},r=function(e,t){u=setTimeout(e,t)},i=function(){clearTimeout(u)},a=function(){return!1},o=t.unstable_forceFrameRate=function(){}}else{var f=window.performance,d=window.Date,h=window.setTimeout,p=window.clearTimeout;if("undefined"!=typeof console){var b=window.cancelAnimationFrame;"function"!=typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),"function"!=typeof b&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills")}if("object"==typeof f&&"function"==typeof f.now)t.unstable_now=function(){return f.now()};else{var m=d.now();t.unstable_now=function(){return d.now()-m}}var g=!1,v=null,y=-1,w=5,_=0;a=function(){return t.unstable_now()>=_},o=function(){},t.unstable_forceFrameRate=function(e){0>e||125M(o,n))void 0!==u&&0>M(u,o)?(e[r]=u,e[s]=n,r=s):(e[r]=o,e[a]=n,r=a);else if(void 0!==u&&0>M(u,n))e[r]=u,e[s]=n,r=s;else break a}}return t}return null}function M(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var O=[],A=[],L=1,C=null,I=3,D=!1,N=!1,P=!1;function R(e){for(var t=x(A);null!==t;){if(null===t.callback)T(A);else if(t.startTime<=e)T(A),t.sortIndex=t.expirationTime,k(O,t);else break;t=x(A)}}function j(e){if(P=!1,R(e),!N){if(null!==x(O))N=!0,n(F);else{var t=x(A);null!==t&&r(j,t.startTime-e)}}}function F(e,n){N=!1,P&&(P=!1,i()),D=!0;var o=I;try{for(R(n),C=x(O);null!==C&&(!(C.expirationTime>n)||e&&!a());){var s=C.callback;if(null!==s){C.callback=null,I=C.priorityLevel;var u=s(C.expirationTime<=n);n=t.unstable_now(),"function"==typeof u?C.callback=u:C===x(O)&&T(O),R(n)}else T(O);C=x(O)}if(null!==C)var c=!0;else{var l=x(A);null!==l&&r(j,l.startTime-n),c=!1}return c}finally{C=null,I=o,D=!1}}function Y(e){switch(e){case 1:return -1;case 2:return 250;case 5:return 1073741823;case 4:return 1e4;default:return 5e3}}var B=o;t.unstable_ImmediatePriority=1,t.unstable_UserBlockingPriority=2,t.unstable_NormalPriority=3,t.unstable_IdlePriority=5,t.unstable_LowPriority=4,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=I;I=e;try{return t()}finally{I=n}},t.unstable_next=function(e){switch(I){case 1:case 2:case 3:var t=3;break;default:t=I}var n=I;I=t;try{return e()}finally{I=n}},t.unstable_scheduleCallback=function(e,a,o){var s=t.unstable_now();if("object"==typeof o&&null!==o){var u=o.delay;u="number"==typeof u&&0s?(e.sortIndex=u,k(A,e),null===x(O)&&e===x(A)&&(P?i():P=!0,r(j,u-s))):(e.sortIndex=o,k(O,e),N||D||(N=!0,n(F))),e},t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_wrapCallback=function(e){var t=I;return function(){var n=I;I=t;try{return e.apply(this,arguments)}finally{I=n}}},t.unstable_getCurrentPriorityLevel=function(){return I},t.unstable_shouldYield=function(){var e=t.unstable_now();R(e);var n=x(O);return n!==C&&null!==C&&null!==n&&null!==n.callback&&n.startTime<=e&&n.expirationTime>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function c(e,t,n){var r=t.length-1;if(r=0?(i>0&&(e.lastNeed=i-1),i):--r=0?(i>0&&(e.lastNeed=i-2),i):--r=0?(i>0&&(2===i?i=0:e.lastNeed=i-3),i):0}function l(e,t,n){if((192&t[0])!=128)return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if((192&t[1])!=128)return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&(192&t[2])!=128)return e.lastNeed=2,"�"}}function f(e){var t=this.lastTotal-this.lastNeed,n=l(this,e,t);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):void(e.copy(this.lastChar,t,0,e.length),this.lastNeed-=e.length)}function d(e,t){var n=c(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)}function h(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+"�":t}function p(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function b(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function m(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function g(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function v(e){return e.toString(this.encoding)}function y(e){return e&&e.length?this.write(e):""}t.s=s,s.prototype.write=function(e){var t,n;if(0===e.length)return"";if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return nOB});var r,i,a,o,s,u,c,l=n(67294),f=n.t(l,2),d=n(97779),h=n(47886),p=n(57209),b=n(32316),m=n(95880),g=n(17051),v=n(71381),y=n(81701),w=n(3022),_=n(60323),E=n(87591),S=n(25649),k=n(28902),x=n(71426),T=n(48884),M=n(94184),O=n.n(M),A=n(55977),L=n(73935),C=function(){if("undefined"!=typeof Map)return Map;function e(e,t){var n=-1;return e.some(function(e,r){return e[0]===t&&(n=r,!0)}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(t){var n=e(this.__entries__,t),r=this.__entries__[n];return r&&r[1]},t.prototype.set=function(t,n){var r=e(this.__entries__,t);~r?this.__entries__[r][1]=n:this.__entries__.push([t,n])},t.prototype.delete=function(t){var n=this.__entries__,r=e(n,t);~r&&n.splice(r,1)},t.prototype.has=function(t){return!!~e(this.__entries__,t)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(e,t){void 0===t&&(t=null);for(var n=0,r=this.__entries__;n0},e.prototype.connect_=function(){I&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),Y?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){I&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t;F.some(function(e){return!!~n.indexOf(e)})&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),U=function(e,t){for(var n=0,r=Object.keys(t);n0},e}(),er="undefined"!=typeof WeakMap?new WeakMap:new C,ei=function(){function e(t){if(!(this instanceof e))throw TypeError("Cannot call a class as a function.");if(!arguments.length)throw TypeError("1 argument required, but only 0 present.");var n=B.getInstance(),r=new en(t,n,this);er.set(this,r)}return e}();["observe","unobserve","disconnect"].forEach(function(e){ei.prototype[e]=function(){var t;return(t=er.get(this))[e].apply(t,arguments)}});var ea=void 0!==D.ResizeObserver?D.ResizeObserver:ei;let eo=ea;var es=function(e){var t=[],n=null,r=function(){for(var r=arguments.length,i=Array(r),a=0;a=t||n<0||f&&r>=a}function g(){var e=eb();if(m(e))return v(e);s=setTimeout(g,b(e))}function v(e){return(s=void 0,d&&r)?h(e):(r=i=void 0,o)}function y(){void 0!==s&&clearTimeout(s),c=0,r=u=i=s=void 0}function w(){return void 0===s?o:v(eb())}function _(){var e=eb(),n=m(e);if(r=arguments,i=this,u=e,n){if(void 0===s)return p(u);if(f)return clearTimeout(s),s=setTimeout(g,t),h(u)}return void 0===s&&(s=setTimeout(g,t)),o}return t=ez(t)||0,ed(n)&&(l=!!n.leading,a=(f="maxWait"in n)?eW(ez(n.maxWait)||0,t):a,d="trailing"in n?!!n.trailing:d),_.cancel=y,_.flush=w,_}let eq=eV;var eZ="Expected a function";function eX(e,t,n){var r=!0,i=!0;if("function"!=typeof e)throw TypeError(eZ);return ed(n)&&(r="leading"in n?!!n.leading:r,i="trailing"in n?!!n.trailing:i),eq(e,t,{leading:r,maxWait:t,trailing:i})}let eJ=eX;var eQ={debounce:eq,throttle:eJ},e1=function(e){return eQ[e]},e0=function(e){return"function"==typeof e},e2=function(){return"undefined"==typeof window},e3=function(e){return e instanceof Element||e instanceof HTMLDocument};function e4(e){return(e4="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function e5(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function e6(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&l.createElement(tG.Z,{variant:"indeterminate",classes:r}))};tK.propTypes={fetchCount:el().number.isRequired};let tV=(0,b.withStyles)(tW)(tK);var tq=n(5536);let tZ=n.p+"ba8bbf16ebf8e1d05bef.svg";function tX(){return(tX=Object.assign||function(e){for(var t=1;t120){for(var d=Math.floor(u/80),h=u%80,p=[],b=0;b0},name:{enumerable:!1},nodes:{enumerable:!1},source:{enumerable:!1},positions:{enumerable:!1},originalError:{enumerable:!1}}),null!=s&&s.stack)?(Object.defineProperty(nf(b),"stack",{value:s.stack,writable:!0,configurable:!0}),nl(b)):(Error.captureStackTrace?Error.captureStackTrace(nf(b),n):Object.defineProperty(nf(b),"stack",{value:Error().stack,writable:!0,configurable:!0}),b)}return ns(n,[{key:"toString",value:function(){return nw(this)}},{key:t4.YF,get:function(){return"Object"}}]),n}(nd(Error));function ny(e){return void 0===e||0===e.length?void 0:e}function nw(e){var t=e.message;if(e.nodes)for(var n=0,r=e.nodes;n",EOF:"",BANG:"!",DOLLAR:"$",AMP:"&",PAREN_L:"(",PAREN_R:")",SPREAD:"...",COLON:":",EQUALS:"=",AT:"@",BRACKET_L:"[",BRACKET_R:"]",BRACE_L:"{",PIPE:"|",BRACE_R:"}",NAME:"Name",INT:"Int",FLOAT:"Float",STRING:"String",BLOCK_STRING:"BlockString",COMMENT:"Comment"}),nx=n(10143),nT=Object.freeze({QUERY:"QUERY",MUTATION:"MUTATION",SUBSCRIPTION:"SUBSCRIPTION",FIELD:"FIELD",FRAGMENT_DEFINITION:"FRAGMENT_DEFINITION",FRAGMENT_SPREAD:"FRAGMENT_SPREAD",INLINE_FRAGMENT:"INLINE_FRAGMENT",VARIABLE_DEFINITION:"VARIABLE_DEFINITION",SCHEMA:"SCHEMA",SCALAR:"SCALAR",OBJECT:"OBJECT",FIELD_DEFINITION:"FIELD_DEFINITION",ARGUMENT_DEFINITION:"ARGUMENT_DEFINITION",INTERFACE:"INTERFACE",UNION:"UNION",ENUM:"ENUM",ENUM_VALUE:"ENUM_VALUE",INPUT_OBJECT:"INPUT_OBJECT",INPUT_FIELD_DEFINITION:"INPUT_FIELD_DEFINITION"}),nM=n(87392),nO=function(){function e(e){var t=new nS.WU(nk.SOF,0,0,0,0,null);this.source=e,this.lastToken=t,this.token=t,this.line=1,this.lineStart=0}var t=e.prototype;return t.advance=function(){return this.lastToken=this.token,this.token=this.lookahead()},t.lookahead=function(){var e,t=this.token;if(t.kind!==nk.EOF)do t=null!==(e=t.next)&&void 0!==e?e:t.next=nC(this,t);while(t.kind===nk.COMMENT)return t},e}();function nA(e){return e===nk.BANG||e===nk.DOLLAR||e===nk.AMP||e===nk.PAREN_L||e===nk.PAREN_R||e===nk.SPREAD||e===nk.COLON||e===nk.EQUALS||e===nk.AT||e===nk.BRACKET_L||e===nk.BRACKET_R||e===nk.BRACE_L||e===nk.PIPE||e===nk.BRACE_R}function nL(e){return isNaN(e)?nk.EOF:e<127?JSON.stringify(String.fromCharCode(e)):'"\\u'.concat(("00"+e.toString(16).toUpperCase()).slice(-4),'"')}function nC(e,t){for(var n=e.source,r=n.body,i=r.length,a=t.end;a31||9===a))return new nS.WU(nk.COMMENT,t,s,n,r,i,o.slice(t+1,s))}function nN(e,t,n,r,i,a){var o=e.body,s=n,u=t,c=!1;if(45===s&&(s=o.charCodeAt(++u)),48===s){if((s=o.charCodeAt(++u))>=48&&s<=57)throw n_(e,u,"Invalid number, unexpected digit after 0: ".concat(nL(s),"."))}else u=nP(e,u,s),s=o.charCodeAt(u);if(46===s&&(c=!0,s=o.charCodeAt(++u),u=nP(e,u,s),s=o.charCodeAt(u)),(69===s||101===s)&&(c=!0,(43===(s=o.charCodeAt(++u))||45===s)&&(s=o.charCodeAt(++u)),u=nP(e,u,s),s=o.charCodeAt(u)),46===s||nU(s))throw n_(e,u,"Invalid number, expected digit but got: ".concat(nL(s),"."));return new nS.WU(c?nk.FLOAT:nk.INT,t,u,r,i,a,o.slice(t,u))}function nP(e,t,n){var r=e.body,i=t,a=n;if(a>=48&&a<=57){do a=r.charCodeAt(++i);while(a>=48&&a<=57)return i}throw n_(e,i,"Invalid number, expected digit but got: ".concat(nL(a),"."))}function nR(e,t,n,r,i){for(var a=e.body,o=t+1,s=o,u=0,c="";o=48&&e<=57?e-48:e>=65&&e<=70?e-55:e>=97&&e<=102?e-87:-1}function nB(e,t,n,r,i){for(var a=e.body,o=a.length,s=t+1,u=0;s!==o&&!isNaN(u=a.charCodeAt(s))&&(95===u||u>=48&&u<=57||u>=65&&u<=90||u>=97&&u<=122);)++s;return new nS.WU(nk.NAME,t,s,n,r,i,a.slice(t,s))}function nU(e){return 95===e||e>=65&&e<=90||e>=97&&e<=122}function nH(e,t){return new n$(e,t).parseDocument()}var n$=function(){function e(e,t){var n=(0,nx.T)(e)?e:new nx.H(e);this._lexer=new nO(n),this._options=t}var t=e.prototype;return t.parseName=function(){var e=this.expectToken(nk.NAME);return{kind:nE.h.NAME,value:e.value,loc:this.loc(e)}},t.parseDocument=function(){var e=this._lexer.token;return{kind:nE.h.DOCUMENT,definitions:this.many(nk.SOF,this.parseDefinition,nk.EOF),loc:this.loc(e)}},t.parseDefinition=function(){if(this.peek(nk.NAME))switch(this._lexer.token.value){case"query":case"mutation":case"subscription":return this.parseOperationDefinition();case"fragment":return this.parseFragmentDefinition();case"schema":case"scalar":case"type":case"interface":case"union":case"enum":case"input":case"directive":return this.parseTypeSystemDefinition();case"extend":return this.parseTypeSystemExtension()}else if(this.peek(nk.BRACE_L))return this.parseOperationDefinition();else if(this.peekDescription())return this.parseTypeSystemDefinition();throw this.unexpected()},t.parseOperationDefinition=function(){var e,t=this._lexer.token;if(this.peek(nk.BRACE_L))return{kind:nE.h.OPERATION_DEFINITION,operation:"query",name:void 0,variableDefinitions:[],directives:[],selectionSet:this.parseSelectionSet(),loc:this.loc(t)};var n=this.parseOperationType();return this.peek(nk.NAME)&&(e=this.parseName()),{kind:nE.h.OPERATION_DEFINITION,operation:n,name:e,variableDefinitions:this.parseVariableDefinitions(),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(t)}},t.parseOperationType=function(){var e=this.expectToken(nk.NAME);switch(e.value){case"query":return"query";case"mutation":return"mutation";case"subscription":return"subscription"}throw this.unexpected(e)},t.parseVariableDefinitions=function(){return this.optionalMany(nk.PAREN_L,this.parseVariableDefinition,nk.PAREN_R)},t.parseVariableDefinition=function(){var e=this._lexer.token;return{kind:nE.h.VARIABLE_DEFINITION,variable:this.parseVariable(),type:(this.expectToken(nk.COLON),this.parseTypeReference()),defaultValue:this.expectOptionalToken(nk.EQUALS)?this.parseValueLiteral(!0):void 0,directives:this.parseDirectives(!0),loc:this.loc(e)}},t.parseVariable=function(){var e=this._lexer.token;return this.expectToken(nk.DOLLAR),{kind:nE.h.VARIABLE,name:this.parseName(),loc:this.loc(e)}},t.parseSelectionSet=function(){var e=this._lexer.token;return{kind:nE.h.SELECTION_SET,selections:this.many(nk.BRACE_L,this.parseSelection,nk.BRACE_R),loc:this.loc(e)}},t.parseSelection=function(){return this.peek(nk.SPREAD)?this.parseFragment():this.parseField()},t.parseField=function(){var e,t,n=this._lexer.token,r=this.parseName();return this.expectOptionalToken(nk.COLON)?(e=r,t=this.parseName()):t=r,{kind:nE.h.FIELD,alias:e,name:t,arguments:this.parseArguments(!1),directives:this.parseDirectives(!1),selectionSet:this.peek(nk.BRACE_L)?this.parseSelectionSet():void 0,loc:this.loc(n)}},t.parseArguments=function(e){var t=e?this.parseConstArgument:this.parseArgument;return this.optionalMany(nk.PAREN_L,t,nk.PAREN_R)},t.parseArgument=function(){var e=this._lexer.token,t=this.parseName();return this.expectToken(nk.COLON),{kind:nE.h.ARGUMENT,name:t,value:this.parseValueLiteral(!1),loc:this.loc(e)}},t.parseConstArgument=function(){var e=this._lexer.token;return{kind:nE.h.ARGUMENT,name:this.parseName(),value:(this.expectToken(nk.COLON),this.parseValueLiteral(!0)),loc:this.loc(e)}},t.parseFragment=function(){var e=this._lexer.token;this.expectToken(nk.SPREAD);var t=this.expectOptionalKeyword("on");return!t&&this.peek(nk.NAME)?{kind:nE.h.FRAGMENT_SPREAD,name:this.parseFragmentName(),directives:this.parseDirectives(!1),loc:this.loc(e)}:{kind:nE.h.INLINE_FRAGMENT,typeCondition:t?this.parseNamedType():void 0,directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(e)}},t.parseFragmentDefinition=function(){var e,t=this._lexer.token;return(this.expectKeyword("fragment"),(null===(e=this._options)||void 0===e?void 0:e.experimentalFragmentVariables)===!0)?{kind:nE.h.FRAGMENT_DEFINITION,name:this.parseFragmentName(),variableDefinitions:this.parseVariableDefinitions(),typeCondition:(this.expectKeyword("on"),this.parseNamedType()),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(t)}:{kind:nE.h.FRAGMENT_DEFINITION,name:this.parseFragmentName(),typeCondition:(this.expectKeyword("on"),this.parseNamedType()),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(t)}},t.parseFragmentName=function(){if("on"===this._lexer.token.value)throw this.unexpected();return this.parseName()},t.parseValueLiteral=function(e){var t=this._lexer.token;switch(t.kind){case nk.BRACKET_L:return this.parseList(e);case nk.BRACE_L:return this.parseObject(e);case nk.INT:return this._lexer.advance(),{kind:nE.h.INT,value:t.value,loc:this.loc(t)};case nk.FLOAT:return this._lexer.advance(),{kind:nE.h.FLOAT,value:t.value,loc:this.loc(t)};case nk.STRING:case nk.BLOCK_STRING:return this.parseStringLiteral();case nk.NAME:switch(this._lexer.advance(),t.value){case"true":return{kind:nE.h.BOOLEAN,value:!0,loc:this.loc(t)};case"false":return{kind:nE.h.BOOLEAN,value:!1,loc:this.loc(t)};case"null":return{kind:nE.h.NULL,loc:this.loc(t)};default:return{kind:nE.h.ENUM,value:t.value,loc:this.loc(t)}}case nk.DOLLAR:if(!e)return this.parseVariable()}throw this.unexpected()},t.parseStringLiteral=function(){var e=this._lexer.token;return this._lexer.advance(),{kind:nE.h.STRING,value:e.value,block:e.kind===nk.BLOCK_STRING,loc:this.loc(e)}},t.parseList=function(e){var t=this,n=this._lexer.token,r=function(){return t.parseValueLiteral(e)};return{kind:nE.h.LIST,values:this.any(nk.BRACKET_L,r,nk.BRACKET_R),loc:this.loc(n)}},t.parseObject=function(e){var t=this,n=this._lexer.token,r=function(){return t.parseObjectField(e)};return{kind:nE.h.OBJECT,fields:this.any(nk.BRACE_L,r,nk.BRACE_R),loc:this.loc(n)}},t.parseObjectField=function(e){var t=this._lexer.token,n=this.parseName();return this.expectToken(nk.COLON),{kind:nE.h.OBJECT_FIELD,name:n,value:this.parseValueLiteral(e),loc:this.loc(t)}},t.parseDirectives=function(e){for(var t=[];this.peek(nk.AT);)t.push(this.parseDirective(e));return t},t.parseDirective=function(e){var t=this._lexer.token;return this.expectToken(nk.AT),{kind:nE.h.DIRECTIVE,name:this.parseName(),arguments:this.parseArguments(e),loc:this.loc(t)}},t.parseTypeReference=function(){var e,t=this._lexer.token;return(this.expectOptionalToken(nk.BRACKET_L)?(e=this.parseTypeReference(),this.expectToken(nk.BRACKET_R),e={kind:nE.h.LIST_TYPE,type:e,loc:this.loc(t)}):e=this.parseNamedType(),this.expectOptionalToken(nk.BANG))?{kind:nE.h.NON_NULL_TYPE,type:e,loc:this.loc(t)}:e},t.parseNamedType=function(){var e=this._lexer.token;return{kind:nE.h.NAMED_TYPE,name:this.parseName(),loc:this.loc(e)}},t.parseTypeSystemDefinition=function(){var e=this.peekDescription()?this._lexer.lookahead():this._lexer.token;if(e.kind===nk.NAME)switch(e.value){case"schema":return this.parseSchemaDefinition();case"scalar":return this.parseScalarTypeDefinition();case"type":return this.parseObjectTypeDefinition();case"interface":return this.parseInterfaceTypeDefinition();case"union":return this.parseUnionTypeDefinition();case"enum":return this.parseEnumTypeDefinition();case"input":return this.parseInputObjectTypeDefinition();case"directive":return this.parseDirectiveDefinition()}throw this.unexpected(e)},t.peekDescription=function(){return this.peek(nk.STRING)||this.peek(nk.BLOCK_STRING)},t.parseDescription=function(){if(this.peekDescription())return this.parseStringLiteral()},t.parseSchemaDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("schema");var n=this.parseDirectives(!0),r=this.many(nk.BRACE_L,this.parseOperationTypeDefinition,nk.BRACE_R);return{kind:nE.h.SCHEMA_DEFINITION,description:t,directives:n,operationTypes:r,loc:this.loc(e)}},t.parseOperationTypeDefinition=function(){var e=this._lexer.token,t=this.parseOperationType();this.expectToken(nk.COLON);var n=this.parseNamedType();return{kind:nE.h.OPERATION_TYPE_DEFINITION,operation:t,type:n,loc:this.loc(e)}},t.parseScalarTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("scalar");var n=this.parseName(),r=this.parseDirectives(!0);return{kind:nE.h.SCALAR_TYPE_DEFINITION,description:t,name:n,directives:r,loc:this.loc(e)}},t.parseObjectTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("type");var n=this.parseName(),r=this.parseImplementsInterfaces(),i=this.parseDirectives(!0),a=this.parseFieldsDefinition();return{kind:nE.h.OBJECT_TYPE_DEFINITION,description:t,name:n,interfaces:r,directives:i,fields:a,loc:this.loc(e)}},t.parseImplementsInterfaces=function(){var e;if(!this.expectOptionalKeyword("implements"))return[];if((null===(e=this._options)||void 0===e?void 0:e.allowLegacySDLImplementsInterfaces)===!0){var t=[];this.expectOptionalToken(nk.AMP);do t.push(this.parseNamedType());while(this.expectOptionalToken(nk.AMP)||this.peek(nk.NAME))return t}return this.delimitedMany(nk.AMP,this.parseNamedType)},t.parseFieldsDefinition=function(){var e;return(null===(e=this._options)||void 0===e?void 0:e.allowLegacySDLEmptyFields)===!0&&this.peek(nk.BRACE_L)&&this._lexer.lookahead().kind===nk.BRACE_R?(this._lexer.advance(),this._lexer.advance(),[]):this.optionalMany(nk.BRACE_L,this.parseFieldDefinition,nk.BRACE_R)},t.parseFieldDefinition=function(){var e=this._lexer.token,t=this.parseDescription(),n=this.parseName(),r=this.parseArgumentDefs();this.expectToken(nk.COLON);var i=this.parseTypeReference(),a=this.parseDirectives(!0);return{kind:nE.h.FIELD_DEFINITION,description:t,name:n,arguments:r,type:i,directives:a,loc:this.loc(e)}},t.parseArgumentDefs=function(){return this.optionalMany(nk.PAREN_L,this.parseInputValueDef,nk.PAREN_R)},t.parseInputValueDef=function(){var e,t=this._lexer.token,n=this.parseDescription(),r=this.parseName();this.expectToken(nk.COLON);var i=this.parseTypeReference();this.expectOptionalToken(nk.EQUALS)&&(e=this.parseValueLiteral(!0));var a=this.parseDirectives(!0);return{kind:nE.h.INPUT_VALUE_DEFINITION,description:n,name:r,type:i,defaultValue:e,directives:a,loc:this.loc(t)}},t.parseInterfaceTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("interface");var n=this.parseName(),r=this.parseImplementsInterfaces(),i=this.parseDirectives(!0),a=this.parseFieldsDefinition();return{kind:nE.h.INTERFACE_TYPE_DEFINITION,description:t,name:n,interfaces:r,directives:i,fields:a,loc:this.loc(e)}},t.parseUnionTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("union");var n=this.parseName(),r=this.parseDirectives(!0),i=this.parseUnionMemberTypes();return{kind:nE.h.UNION_TYPE_DEFINITION,description:t,name:n,directives:r,types:i,loc:this.loc(e)}},t.parseUnionMemberTypes=function(){return this.expectOptionalToken(nk.EQUALS)?this.delimitedMany(nk.PIPE,this.parseNamedType):[]},t.parseEnumTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("enum");var n=this.parseName(),r=this.parseDirectives(!0),i=this.parseEnumValuesDefinition();return{kind:nE.h.ENUM_TYPE_DEFINITION,description:t,name:n,directives:r,values:i,loc:this.loc(e)}},t.parseEnumValuesDefinition=function(){return this.optionalMany(nk.BRACE_L,this.parseEnumValueDefinition,nk.BRACE_R)},t.parseEnumValueDefinition=function(){var e=this._lexer.token,t=this.parseDescription(),n=this.parseName(),r=this.parseDirectives(!0);return{kind:nE.h.ENUM_VALUE_DEFINITION,description:t,name:n,directives:r,loc:this.loc(e)}},t.parseInputObjectTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("input");var n=this.parseName(),r=this.parseDirectives(!0),i=this.parseInputFieldsDefinition();return{kind:nE.h.INPUT_OBJECT_TYPE_DEFINITION,description:t,name:n,directives:r,fields:i,loc:this.loc(e)}},t.parseInputFieldsDefinition=function(){return this.optionalMany(nk.BRACE_L,this.parseInputValueDef,nk.BRACE_R)},t.parseTypeSystemExtension=function(){var e=this._lexer.lookahead();if(e.kind===nk.NAME)switch(e.value){case"schema":return this.parseSchemaExtension();case"scalar":return this.parseScalarTypeExtension();case"type":return this.parseObjectTypeExtension();case"interface":return this.parseInterfaceTypeExtension();case"union":return this.parseUnionTypeExtension();case"enum":return this.parseEnumTypeExtension();case"input":return this.parseInputObjectTypeExtension()}throw this.unexpected(e)},t.parseSchemaExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("schema");var t=this.parseDirectives(!0),n=this.optionalMany(nk.BRACE_L,this.parseOperationTypeDefinition,nk.BRACE_R);if(0===t.length&&0===n.length)throw this.unexpected();return{kind:nE.h.SCHEMA_EXTENSION,directives:t,operationTypes:n,loc:this.loc(e)}},t.parseScalarTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("scalar");var t=this.parseName(),n=this.parseDirectives(!0);if(0===n.length)throw this.unexpected();return{kind:nE.h.SCALAR_TYPE_EXTENSION,name:t,directives:n,loc:this.loc(e)}},t.parseObjectTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("type");var t=this.parseName(),n=this.parseImplementsInterfaces(),r=this.parseDirectives(!0),i=this.parseFieldsDefinition();if(0===n.length&&0===r.length&&0===i.length)throw this.unexpected();return{kind:nE.h.OBJECT_TYPE_EXTENSION,name:t,interfaces:n,directives:r,fields:i,loc:this.loc(e)}},t.parseInterfaceTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("interface");var t=this.parseName(),n=this.parseImplementsInterfaces(),r=this.parseDirectives(!0),i=this.parseFieldsDefinition();if(0===n.length&&0===r.length&&0===i.length)throw this.unexpected();return{kind:nE.h.INTERFACE_TYPE_EXTENSION,name:t,interfaces:n,directives:r,fields:i,loc:this.loc(e)}},t.parseUnionTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("union");var t=this.parseName(),n=this.parseDirectives(!0),r=this.parseUnionMemberTypes();if(0===n.length&&0===r.length)throw this.unexpected();return{kind:nE.h.UNION_TYPE_EXTENSION,name:t,directives:n,types:r,loc:this.loc(e)}},t.parseEnumTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("enum");var t=this.parseName(),n=this.parseDirectives(!0),r=this.parseEnumValuesDefinition();if(0===n.length&&0===r.length)throw this.unexpected();return{kind:nE.h.ENUM_TYPE_EXTENSION,name:t,directives:n,values:r,loc:this.loc(e)}},t.parseInputObjectTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("input");var t=this.parseName(),n=this.parseDirectives(!0),r=this.parseInputFieldsDefinition();if(0===n.length&&0===r.length)throw this.unexpected();return{kind:nE.h.INPUT_OBJECT_TYPE_EXTENSION,name:t,directives:n,fields:r,loc:this.loc(e)}},t.parseDirectiveDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("directive"),this.expectToken(nk.AT);var n=this.parseName(),r=this.parseArgumentDefs(),i=this.expectOptionalKeyword("repeatable");this.expectKeyword("on");var a=this.parseDirectiveLocations();return{kind:nE.h.DIRECTIVE_DEFINITION,description:t,name:n,arguments:r,repeatable:i,locations:a,loc:this.loc(e)}},t.parseDirectiveLocations=function(){return this.delimitedMany(nk.PIPE,this.parseDirectiveLocation)},t.parseDirectiveLocation=function(){var e=this._lexer.token,t=this.parseName();if(void 0!==nT[t.value])return t;throw this.unexpected(e)},t.loc=function(e){var t;if((null===(t=this._options)||void 0===t?void 0:t.noLocation)!==!0)return new nS.Ye(e,this._lexer.lastToken,this._lexer.source)},t.peek=function(e){return this._lexer.token.kind===e},t.expectToken=function(e){var t=this._lexer.token;if(t.kind===e)return this._lexer.advance(),t;throw n_(this._lexer.source,t.start,"Expected ".concat(nG(e),", found ").concat(nz(t),"."))},t.expectOptionalToken=function(e){var t=this._lexer.token;if(t.kind===e)return this._lexer.advance(),t},t.expectKeyword=function(e){var t=this._lexer.token;if(t.kind===nk.NAME&&t.value===e)this._lexer.advance();else throw n_(this._lexer.source,t.start,'Expected "'.concat(e,'", found ').concat(nz(t),"."))},t.expectOptionalKeyword=function(e){var t=this._lexer.token;return t.kind===nk.NAME&&t.value===e&&(this._lexer.advance(),!0)},t.unexpected=function(e){var t=null!=e?e:this._lexer.token;return n_(this._lexer.source,t.start,"Unexpected ".concat(nz(t),"."))},t.any=function(e,t,n){this.expectToken(e);for(var r=[];!this.expectOptionalToken(n);)r.push(t.call(this));return r},t.optionalMany=function(e,t,n){if(this.expectOptionalToken(e)){var r=[];do r.push(t.call(this));while(!this.expectOptionalToken(n))return r}return[]},t.many=function(e,t,n){this.expectToken(e);var r=[];do r.push(t.call(this));while(!this.expectOptionalToken(n))return r},t.delimitedMany=function(e,t){this.expectOptionalToken(e);var n=[];do n.push(t.call(this));while(this.expectOptionalToken(e))return n},e}();function nz(e){var t=e.value;return nG(e.kind)+(null!=t?' "'.concat(t,'"'):"")}function nG(e){return nA(e)?'"'.concat(e,'"'):e}var nW=new Map,nK=new Map,nV=!0,nq=!1;function nZ(e){return e.replace(/[\s,]+/g," ").trim()}function nX(e){return nZ(e.source.body.substring(e.start,e.end))}function nJ(e){var t=new Set,n=[];return e.definitions.forEach(function(e){if("FragmentDefinition"===e.kind){var r=e.name.value,i=nX(e.loc),a=nK.get(r);a&&!a.has(i)?nV&&console.warn("Warning: fragment with name "+r+" already exists.\ngraphql-tag enforces all fragment names across your application to be unique; read more about\nthis in the docs: http://dev.apollodata.com/core/fragments.html#unique-names"):a||nK.set(r,a=new Set),a.add(i),t.has(i)||(t.add(i),n.push(e))}else n.push(e)}),(0,t0.pi)((0,t0.pi)({},e),{definitions:n})}function nQ(e){var t=new Set(e.definitions);t.forEach(function(e){e.loc&&delete e.loc,Object.keys(e).forEach(function(n){var r=e[n];r&&"object"==typeof r&&t.add(r)})});var n=e.loc;return n&&(delete n.startToken,delete n.endToken),e}function n1(e){var t=nZ(e);if(!nW.has(t)){var n=nH(e,{experimentalFragmentVariables:nq,allowLegacyFragmentVariables:nq});if(!n||"Document"!==n.kind)throw Error("Not a valid GraphQL document.");nW.set(t,nQ(nJ(n)))}return nW.get(t)}function n0(e){for(var t=[],n=1;n, or pass an ApolloClient instance in via options.'):(0,n7.kG)(!!n,32),n}var rb=n(10542),rm=n(53712),rg=n(21436),rv=Object.prototype.hasOwnProperty;function ry(e,t){return void 0===t&&(t=Object.create(null)),rw(rp(t.client),e).useQuery(t)}function rw(e,t){var n=(0,l.useRef)();n.current&&e===n.current.client&&t===n.current.query||(n.current=new r_(e,t,n.current));var r=n.current,i=(0,l.useState)(0),a=(i[0],i[1]);return r.forceUpdate=function(){a(function(e){return e+1})},r}var r_=function(){function e(e,t,n){this.client=e,this.query=t,this.ssrDisabledResult=(0,rb.J)({loading:!0,data:void 0,error:void 0,networkStatus:rc.I.loading}),this.skipStandbyResult=(0,rb.J)({loading:!1,data:void 0,error:void 0,networkStatus:rc.I.ready}),this.toQueryResultCache=new(re.mr?WeakMap:Map),rh(t,r.Query);var i=n&&n.result,a=i&&i.data;a&&(this.previousData=a)}return e.prototype.forceUpdate=function(){__DEV__&&n7.kG.warn("Calling default no-op implementation of InternalState#forceUpdate")},e.prototype.executeQuery=function(e){var t,n=this;e.query&&Object.assign(this,{query:e.query}),this.watchQueryOptions=this.createWatchQueryOptions(this.queryHookOptions=e);var r=this.observable.reobserveAsConcast(this.getObsQueryOptions());return this.previousData=(null===(t=this.result)||void 0===t?void 0:t.data)||this.previousData,this.result=void 0,this.forceUpdate(),new Promise(function(e){var t;r.subscribe({next:function(e){t=e},error:function(){e(n.toQueryResult(n.observable.getCurrentResult()))},complete:function(){e(n.toQueryResult(t))}})})},e.prototype.useQuery=function(e){var t=this;this.renderPromises=(0,l.useContext)((0,rs.K)()).renderPromises,this.useOptions(e);var n=this.useObservableQuery(),r=rn((0,l.useCallback)(function(){if(t.renderPromises)return function(){};var e=function(){var e=t.result,r=n.getCurrentResult();!(e&&e.loading===r.loading&&e.networkStatus===r.networkStatus&&(0,ra.D)(e.data,r.data))&&t.setResult(r)},r=function(a){var o=n.last;i.unsubscribe();try{n.resetLastResults(),i=n.subscribe(e,r)}finally{n.last=o}if(!rv.call(a,"graphQLErrors"))throw a;var s=t.result;(!s||s&&s.loading||!(0,ra.D)(a,s.error))&&t.setResult({data:s&&s.data,error:a,loading:!1,networkStatus:rc.I.error})},i=n.subscribe(e,r);return function(){return setTimeout(function(){return i.unsubscribe()})}},[n,this.renderPromises,this.client.disableNetworkFetches,]),function(){return t.getCurrentResult()},function(){return t.getCurrentResult()});return this.unsafeHandlePartialRefetch(r),this.toQueryResult(r)},e.prototype.useOptions=function(t){var n,r=this.createWatchQueryOptions(this.queryHookOptions=t),i=this.watchQueryOptions;!(0,ra.D)(r,i)&&(this.watchQueryOptions=r,i&&this.observable&&(this.observable.reobserve(this.getObsQueryOptions()),this.previousData=(null===(n=this.result)||void 0===n?void 0:n.data)||this.previousData,this.result=void 0)),this.onCompleted=t.onCompleted||e.prototype.onCompleted,this.onError=t.onError||e.prototype.onError,(this.renderPromises||this.client.disableNetworkFetches)&&!1===this.queryHookOptions.ssr&&!this.queryHookOptions.skip?this.result=this.ssrDisabledResult:this.queryHookOptions.skip||"standby"===this.watchQueryOptions.fetchPolicy?this.result=this.skipStandbyResult:(this.result===this.ssrDisabledResult||this.result===this.skipStandbyResult)&&(this.result=void 0)},e.prototype.getObsQueryOptions=function(){var e=[],t=this.client.defaultOptions.watchQuery;return t&&e.push(t),this.queryHookOptions.defaultOptions&&e.push(this.queryHookOptions.defaultOptions),e.push((0,rm.o)(this.observable&&this.observable.options,this.watchQueryOptions)),e.reduce(ro.J)},e.prototype.createWatchQueryOptions=function(e){void 0===e&&(e={});var t,n=e.skip,r=Object.assign((e.ssr,e.onCompleted,e.onError,e.defaultOptions,(0,n8._T)(e,["skip","ssr","onCompleted","onError","defaultOptions"])),{query:this.query});if(this.renderPromises&&("network-only"===r.fetchPolicy||"cache-and-network"===r.fetchPolicy)&&(r.fetchPolicy="cache-first"),r.variables||(r.variables={}),n){var i=r.fetchPolicy,a=void 0===i?this.getDefaultFetchPolicy():i,o=r.initialFetchPolicy;Object.assign(r,{initialFetchPolicy:void 0===o?a:o,fetchPolicy:"standby"})}else r.fetchPolicy||(r.fetchPolicy=(null===(t=this.observable)||void 0===t?void 0:t.options.initialFetchPolicy)||this.getDefaultFetchPolicy());return r},e.prototype.getDefaultFetchPolicy=function(){var e,t;return(null===(e=this.queryHookOptions.defaultOptions)||void 0===e?void 0:e.fetchPolicy)||(null===(t=this.client.defaultOptions.watchQuery)||void 0===t?void 0:t.fetchPolicy)||"cache-first"},e.prototype.onCompleted=function(e){},e.prototype.onError=function(e){},e.prototype.useObservableQuery=function(){var e=this.observable=this.renderPromises&&this.renderPromises.getSSRObservable(this.watchQueryOptions)||this.observable||this.client.watchQuery(this.getObsQueryOptions());this.obsQueryFields=(0,l.useMemo)(function(){return{refetch:e.refetch.bind(e),reobserve:e.reobserve.bind(e),fetchMore:e.fetchMore.bind(e),updateQuery:e.updateQuery.bind(e),startPolling:e.startPolling.bind(e),stopPolling:e.stopPolling.bind(e),subscribeToMore:e.subscribeToMore.bind(e)}},[e]);var t=!(!1===this.queryHookOptions.ssr||this.queryHookOptions.skip);return this.renderPromises&&t&&(this.renderPromises.registerSSRObservable(e),e.getCurrentResult().loading&&this.renderPromises.addObservableQueryPromise(e)),e},e.prototype.setResult=function(e){var t=this.result;t&&t.data&&(this.previousData=t.data),this.result=e,this.forceUpdate(),this.handleErrorOrCompleted(e)},e.prototype.handleErrorOrCompleted=function(e){var t=this;if(!e.loading){var n=this.toApolloError(e);Promise.resolve().then(function(){n?t.onError(n):e.data&&t.onCompleted(e.data)}).catch(function(e){__DEV__&&n7.kG.warn(e)})}},e.prototype.toApolloError=function(e){return(0,rg.O)(e.errors)?new ru.cA({graphQLErrors:e.errors}):e.error},e.prototype.getCurrentResult=function(){return this.result||this.handleErrorOrCompleted(this.result=this.observable.getCurrentResult()),this.result},e.prototype.toQueryResult=function(e){var t=this.toQueryResultCache.get(e);if(t)return t;var n=e.data,r=(e.partial,(0,n8._T)(e,["data","partial"]));return this.toQueryResultCache.set(e,t=(0,n8.pi)((0,n8.pi)((0,n8.pi)({data:n},r),this.obsQueryFields),{client:this.client,observable:this.observable,variables:this.observable.variables,called:!this.queryHookOptions.skip,previousData:this.previousData})),!t.error&&(0,rg.O)(e.errors)&&(t.error=new ru.cA({graphQLErrors:e.errors})),t},e.prototype.unsafeHandlePartialRefetch=function(e){e.partial&&this.queryHookOptions.partialRefetch&&!e.loading&&(!e.data||0===Object.keys(e.data).length)&&"cache-only"!==this.observable.options.fetchPolicy&&(Object.assign(e,{loading:!0,networkStatus:rc.I.refetch}),this.observable.refetch())},e}();function rE(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:{};return ry(i$,e)},iG=function(){var e=iF(),t=parseInt(e.get("page")||"1",10),n=parseInt(e.get("per")||"50",10),r=iz({variables:{offset:(t-1)*n,limit:n},fetchPolicy:"network-only"}),i=r.data,a=r.loading,o=r.error;return a?l.createElement(ij,null):o?l.createElement(iN,{error:o}):i?l.createElement(iD,{chains:i.chains.results,page:t,pageSize:n,total:i.chains.metadata.total}):null},iW=n(67932),iK=n(8126),iV="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function iq(e){if(iZ())return Intl.DateTimeFormat.supportedLocalesOf(e)[0]}function iZ(){return("undefined"==typeof Intl?"undefined":iV(Intl))==="object"&&"function"==typeof Intl.DateTimeFormat}var iX="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},iJ=function(){function e(e,t){for(var n=0;n=i.length)break;s=i[o++]}else{if((o=i.next()).done)break;s=o.value}var s,u=s;if((void 0===e?"undefined":iX(e))!=="object")return;e=e[u]}return e}},{key:"put",value:function(){for(var e=arguments.length,t=Array(e),n=0;n=o.length)break;c=o[u++]}else{if((u=o.next()).done)break;c=u.value}var c,l=c;"object"!==iX(a[l])&&(a[l]={}),a=a[l]}return a[i]=r}}]),e}();let i0=i1;var i2=new i0;function i3(e,t){if(!iZ())return function(e){return e.toString()};var n=i5(e),r=JSON.stringify(t),i=i2.get(String(n),r)||i2.put(String(n),r,new Intl.DateTimeFormat(n,t));return function(e){return i.format(e)}}var i4={};function i5(e){var t=e.toString();return i4[t]?i4[t]:i4[t]=iq(e)}var i6="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function i9(e){return i8(e)?e:new Date(e)}function i8(e){return e instanceof Date||i7(e)}function i7(e){return(void 0===e?"undefined":i6(e))==="object"&&"function"==typeof e.getTime}var ae=n(54087),at=n.n(ae);function an(e,t){if(0===e.length)return 0;for(var n=0,r=e.length-1,i=void 0;n<=r;){var a=t(e[i=Math.floor((r+n)/2)]);if(0===a)return i;if(a<0){if((n=i+1)>r)return n}else if((r=i-1)=t.nextUpdateTime)ao(t,this.instances);else break}},scheduleNextTick:function(){var e=this;this.scheduledTick=at()(function(){e.tick(),e.scheduleNextTick()})},start:function(){this.scheduleNextTick()},stop:function(){at().cancel(this.scheduledTick)}};function aa(e){var t=ar(e.getNextValue(),2),n=t[0],r=t[1];e.setValue(n),e.nextUpdateTime=r}function ao(e,t){aa(e),au(t,e),as(t,e)}function as(e,t){var n=ac(e,t);e.splice(n,0,t)}function au(e,t){var n=e.indexOf(t);e.splice(n,1)}function ac(e,t){var n=t.nextUpdateTime;return an(e,function(e){return e.nextUpdateTime===n?0:e.nextUpdateTime>n?1:-1})}var al=(0,ec.oneOfType)([(0,ec.shape)({minTime:ec.number,formatAs:ec.string.isRequired}),(0,ec.shape)({test:ec.func,formatAs:ec.string.isRequired}),(0,ec.shape)({minTime:ec.number,format:ec.func.isRequired}),(0,ec.shape)({test:ec.func,format:ec.func.isRequired})]),af=(0,ec.oneOfType)([ec.string,(0,ec.shape)({steps:(0,ec.arrayOf)(al).isRequired,labels:(0,ec.oneOfType)([ec.string,(0,ec.arrayOf)(ec.string)]).isRequired,round:ec.string})]),ad=Object.assign||function(e){for(var t=1;t=0)&&Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}function ab(e){var t=e.date,n=e.future,r=e.timeStyle,i=e.round,a=e.minTimeLeft,o=e.tooltip,s=e.component,u=e.container,c=e.wrapperComponent,f=e.wrapperProps,d=e.locale,h=e.locales,p=e.formatVerboseDate,b=e.verboseDateFormat,m=e.updateInterval,g=e.tick,v=ap(e,["date","future","timeStyle","round","minTimeLeft","tooltip","component","container","wrapperComponent","wrapperProps","locale","locales","formatVerboseDate","verboseDateFormat","updateInterval","tick"]),y=(0,l.useMemo)(function(){return d&&(h=[d]),h.concat(iK.Z.getDefaultLocale())},[d,h]),w=(0,l.useMemo)(function(){return new iK.Z(y)},[y]);t=(0,l.useMemo)(function(){return i9(t)},[t]);var _=(0,l.useCallback)(function(){var e=Date.now(),o=void 0;if(n&&e>=t.getTime()&&(e=t.getTime(),o=!0),void 0!==a){var s=t.getTime()-1e3*a;e>s&&(e=s,o=!0)}var u=w.format(t,r,{getTimeToNextUpdate:!0,now:e,future:n,round:i}),c=ah(u,2),l=c[0],f=c[1];return f=o?av:m||f||6e4,[l,e+f]},[t,n,r,m,i,a,w]),E=(0,l.useRef)();E.current=_;var S=(0,l.useMemo)(_,[]),k=ah(S,2),x=k[0],T=k[1],M=(0,l.useState)(x),O=ah(M,2),A=O[0],L=O[1],C=ah((0,l.useState)(),2),I=C[0],D=C[1],N=(0,l.useRef)();(0,l.useEffect)(function(){if(g)return N.current=ai.add({getNextValue:function(){return E.current()},setValue:L,nextUpdateTime:T}),function(){return N.current.stop()}},[g]),(0,l.useEffect)(function(){if(N.current)N.current.forceUpdate();else{var e=_(),t=ah(e,1)[0];L(t)}},[_]),(0,l.useEffect)(function(){D(!0)},[]);var P=(0,l.useMemo)(function(){if("undefined"!=typeof window)return i3(y,b)},[y,b]),R=(0,l.useMemo)(function(){if("undefined"!=typeof window)return p?p(t):P(t)},[t,p,P]),j=l.createElement(s,ad({date:t,verboseDate:I?R:void 0,tooltip:o},v),A),F=c||u;return F?l.createElement(F,ad({},f,{verboseDate:I?R:void 0}),j):j}ab.propTypes={date:el().oneOfType([el().instanceOf(Date),el().number]).isRequired,locale:el().string,locales:el().arrayOf(el().string),future:el().bool,timeStyle:af,round:el().string,minTimeLeft:el().number,component:el().elementType.isRequired,tooltip:el().bool.isRequired,formatVerboseDate:el().func,verboseDateFormat:el().object,updateInterval:el().oneOfType([el().number,el().arrayOf(el().shape({threshold:el().number,interval:el().number.isRequired}))]),tick:el().bool,wrapperComponent:el().func,wrapperProps:el().object},ab.defaultProps={locales:[],component:ay,tooltip:!0,verboseDateFormat:{weekday:"long",day:"numeric",month:"long",year:"numeric",hour:"numeric",minute:"2-digit",second:"2-digit"},tick:!0},ab=l.memo(ab);let am=ab;var ag,av=31536e9;function ay(e){var t=e.date,n=e.verboseDate,r=e.tooltip,i=e.children,a=ap(e,["date","verboseDate","tooltip","children"]),o=(0,l.useMemo)(function(){return t.toISOString()},[t]);return l.createElement("time",ad({},a,{dateTime:o,title:r?n:void 0}),i)}ay.propTypes={date:el().instanceOf(Date).isRequired,verboseDate:el().string,tooltip:el().bool.isRequired,children:el().string.isRequired};var aw=n(30381),a_=n.n(aw),aE=n(31657);function aS(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function ak(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0?new ru.cA({graphQLErrors:i}):void 0;if(u===s.current.mutationId&&!c.ignoreResults){var f={called:!0,loading:!1,data:r,error:l,client:a};s.current.isMounted&&!(0,ra.D)(s.current.result,f)&&o(s.current.result=f)}var d=e.onCompleted||(null===(n=s.current.options)||void 0===n?void 0:n.onCompleted);return null==d||d(t.data,c),t}).catch(function(t){if(u===s.current.mutationId&&s.current.isMounted){var n,r={loading:!1,error:t,data:void 0,called:!0,client:a};(0,ra.D)(s.current.result,r)||o(s.current.result=r)}var i=e.onError||(null===(n=s.current.options)||void 0===n?void 0:n.onError);if(i)return i(t,c),{data:void 0,errors:t};throw t})},[]),c=(0,l.useCallback)(function(){s.current.isMounted&&o({called:!1,loading:!1,client:n})},[]);return(0,l.useEffect)(function(){return s.current.isMounted=!0,function(){s.current.isMounted=!1}},[]),[u,(0,n8.pi)({reset:c},a)]}var ou=n(59067),oc=n(28428),ol=n(11186),of=n(78513);function od(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var oh=function(e){return(0,b.createStyles)({paper:{display:"flex",margin:"".concat(2.5*e.spacing.unit,"px 0"),padding:"".concat(3*e.spacing.unit,"px ").concat(3.5*e.spacing.unit,"px")},content:{flex:1,width:"100%"},actions:od({marginTop:-(1.5*e.spacing.unit),marginLeft:-(4*e.spacing.unit)},e.breakpoints.up("sm"),{marginLeft:0,marginRight:-(1.5*e.spacing.unit)}),itemBlock:{border:"1px solid rgba(224, 224, 224, 1)",borderRadius:e.shape.borderRadius,padding:2*e.spacing.unit,marginTop:e.spacing.unit},itemBlockText:{overflowWrap:"anywhere"}})},op=(0,b.withStyles)(oh)(function(e){var t=e.actions,n=e.children,r=e.classes;return l.createElement(ia.default,{className:r.paper},l.createElement("div",{className:r.content},n),t&&l.createElement("div",{className:r.actions},t))}),ob=function(e){var t=e.title;return l.createElement(x.default,{variant:"subtitle2",gutterBottom:!0},t)},om=function(e){var t=e.children,n=e.value;return l.createElement(x.default,{variant:"body1",noWrap:!0},t||n)},og=(0,b.withStyles)(oh)(function(e){var t=e.children,n=e.classes,r=e.value;return l.createElement("div",{className:n.itemBlock},l.createElement(x.default,{variant:"body1",className:n.itemBlockText},t||r))});function ov(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]-1}let sZ=sq;function sX(e,t){var n=this.__data__,r=s$(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this}let sJ=sX;function sQ(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t-1&&e%1==0&&e-1&&e%1==0&&e<=cI}let cN=cD;var cP="[object Arguments]",cR="[object Array]",cj="[object Boolean]",cF="[object Date]",cY="[object Error]",cB="[object Function]",cU="[object Map]",cH="[object Number]",c$="[object Object]",cz="[object RegExp]",cG="[object Set]",cW="[object String]",cK="[object WeakMap]",cV="[object ArrayBuffer]",cq="[object DataView]",cZ="[object Float64Array]",cX="[object Int8Array]",cJ="[object Int16Array]",cQ="[object Int32Array]",c1="[object Uint8Array]",c0="[object Uint8ClampedArray]",c2="[object Uint16Array]",c3="[object Uint32Array]",c4={};function c5(e){return eD(e)&&cN(e.length)&&!!c4[eC(e)]}c4["[object Float32Array]"]=c4[cZ]=c4[cX]=c4[cJ]=c4[cQ]=c4[c1]=c4[c0]=c4[c2]=c4[c3]=!0,c4[cP]=c4[cR]=c4[cV]=c4[cj]=c4[cq]=c4[cF]=c4[cY]=c4[cB]=c4[cU]=c4[cH]=c4[c$]=c4[cz]=c4[cG]=c4[cW]=c4[cK]=!1;let c6=c5;function c9(e){return function(t){return e(t)}}let c8=c9;var c7=n(79730),le=c7.Z&&c7.Z.isTypedArray,lt=le?c8(le):c6;let ln=lt;var lr=Object.prototype.hasOwnProperty;function li(e,t){var n=cT(e),r=!n&&ck(e),i=!n&&!r&&(0,cM.Z)(e),a=!n&&!r&&!i&&ln(e),o=n||r||i||a,s=o?cm(e.length,String):[],u=s.length;for(var c in e)(t||lr.call(e,c))&&!(o&&("length"==c||i&&("offset"==c||"parent"==c)||a&&("buffer"==c||"byteLength"==c||"byteOffset"==c)||cC(c,u)))&&s.push(c);return s}let la=li;var lo=Object.prototype;function ls(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||lo)}let lu=ls;var lc=sM(Object.keys,Object);let ll=lc;var lf=Object.prototype.hasOwnProperty;function ld(e){if(!lu(e))return ll(e);var t=[];for(var n in Object(e))lf.call(e,n)&&"constructor"!=n&&t.push(n);return t}let lh=ld;function lp(e){return null!=e&&cN(e.length)&&!ui(e)}let lb=lp;function lm(e){return lb(e)?la(e):lh(e)}let lg=lm;function lv(e,t){return e&&cp(t,lg(t),e)}let ly=lv;function lw(e){var t=[];if(null!=e)for(var n in Object(e))t.push(n);return t}let l_=lw;var lE=Object.prototype.hasOwnProperty;function lS(e){if(!ed(e))return l_(e);var t=lu(e),n=[];for(var r in e)"constructor"==r&&(t||!lE.call(e,r))||n.push(r);return n}let lk=lS;function lx(e){return lb(e)?la(e,!0):lk(e)}let lT=lx;function lM(e,t){return e&&cp(t,lT(t),e)}let lO=lM;var lA=n(42896);function lL(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n=0||(i[n]=e[n]);return i}function hc(e){if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}var hl=function(e){return Array.isArray(e)&&0===e.length},hf=function(e){return"function"==typeof e},hd=function(e){return null!==e&&"object"==typeof e},hh=function(e){return String(Math.floor(Number(e)))===e},hp=function(e){return"[object String]"===Object.prototype.toString.call(e)},hb=function(e){return 0===l.Children.count(e)},hm=function(e){return hd(e)&&hf(e.then)};function hg(e,t,n,r){void 0===r&&(r=0);for(var i=d8(t);e&&r=0?[]:{}}}return(0===a?e:i)[o[a]]===n?e:(void 0===n?delete i[o[a]]:i[o[a]]=n,0===a&&void 0===n&&delete r[o[a]],r)}function hy(e,t,n,r){void 0===n&&(n=new WeakMap),void 0===r&&(r={});for(var i=0,a=Object.keys(e);i0?t.map(function(t){return x(t,hg(e,t))}):[Promise.resolve("DO_NOT_DELETE_YOU_WILL_BE_FIRED")]).then(function(e){return e.reduce(function(e,n,r){return"DO_NOT_DELETE_YOU_WILL_BE_FIRED"===n||n&&(e=hv(e,t[r],n)),e},{})})},[x]),M=(0,l.useCallback)(function(e){return Promise.all([T(e),h.validationSchema?k(e):{},h.validate?S(e):{}]).then(function(e){var t=e[0],n=e[1],r=e[2];return sx.all([t,n,r],{arrayMerge:hC})})},[h.validate,h.validationSchema,T,S,k]),O=hP(function(e){return void 0===e&&(e=_.values),E({type:"SET_ISVALIDATING",payload:!0}),M(e).then(function(e){return v.current&&(E({type:"SET_ISVALIDATING",payload:!1}),sh()(_.errors,e)||E({type:"SET_ERRORS",payload:e})),e})});(0,l.useEffect)(function(){o&&!0===v.current&&sh()(p.current,h.initialValues)&&O(p.current)},[o,O]);var A=(0,l.useCallback)(function(e){var t=e&&e.values?e.values:p.current,n=e&&e.errors?e.errors:b.current?b.current:h.initialErrors||{},r=e&&e.touched?e.touched:m.current?m.current:h.initialTouched||{},i=e&&e.status?e.status:g.current?g.current:h.initialStatus;p.current=t,b.current=n,m.current=r,g.current=i;var a=function(){E({type:"RESET_FORM",payload:{isSubmitting:!!e&&!!e.isSubmitting,errors:n,touched:r,status:i,values:t,isValidating:!!e&&!!e.isValidating,submitCount:e&&e.submitCount&&"number"==typeof e.submitCount?e.submitCount:0}})};if(h.onReset){var o=h.onReset(_.values,V);hm(o)?o.then(a):a()}else a()},[h.initialErrors,h.initialStatus,h.initialTouched]);(0,l.useEffect)(function(){!0===v.current&&!sh()(p.current,h.initialValues)&&(c&&(p.current=h.initialValues,A()),o&&O(p.current))},[c,h.initialValues,A,o,O]),(0,l.useEffect)(function(){c&&!0===v.current&&!sh()(b.current,h.initialErrors)&&(b.current=h.initialErrors||hk,E({type:"SET_ERRORS",payload:h.initialErrors||hk}))},[c,h.initialErrors]),(0,l.useEffect)(function(){c&&!0===v.current&&!sh()(m.current,h.initialTouched)&&(m.current=h.initialTouched||hx,E({type:"SET_TOUCHED",payload:h.initialTouched||hx}))},[c,h.initialTouched]),(0,l.useEffect)(function(){c&&!0===v.current&&!sh()(g.current,h.initialStatus)&&(g.current=h.initialStatus,E({type:"SET_STATUS",payload:h.initialStatus}))},[c,h.initialStatus,h.initialTouched]);var L=hP(function(e){if(y.current[e]&&hf(y.current[e].validate)){var t=hg(_.values,e),n=y.current[e].validate(t);return hm(n)?(E({type:"SET_ISVALIDATING",payload:!0}),n.then(function(e){return e}).then(function(t){E({type:"SET_FIELD_ERROR",payload:{field:e,value:t}}),E({type:"SET_ISVALIDATING",payload:!1})})):(E({type:"SET_FIELD_ERROR",payload:{field:e,value:n}}),Promise.resolve(n))}return h.validationSchema?(E({type:"SET_ISVALIDATING",payload:!0}),k(_.values,e).then(function(e){return e}).then(function(t){E({type:"SET_FIELD_ERROR",payload:{field:e,value:t[e]}}),E({type:"SET_ISVALIDATING",payload:!1})})):Promise.resolve()}),C=(0,l.useCallback)(function(e,t){var n=t.validate;y.current[e]={validate:n}},[]),I=(0,l.useCallback)(function(e){delete y.current[e]},[]),D=hP(function(e,t){return E({type:"SET_TOUCHED",payload:e}),(void 0===t?i:t)?O(_.values):Promise.resolve()}),N=(0,l.useCallback)(function(e){E({type:"SET_ERRORS",payload:e})},[]),P=hP(function(e,t){var r=hf(e)?e(_.values):e;return E({type:"SET_VALUES",payload:r}),(void 0===t?n:t)?O(r):Promise.resolve()}),R=(0,l.useCallback)(function(e,t){E({type:"SET_FIELD_ERROR",payload:{field:e,value:t}})},[]),j=hP(function(e,t,r){return E({type:"SET_FIELD_VALUE",payload:{field:e,value:t}}),(void 0===r?n:r)?O(hv(_.values,e,t)):Promise.resolve()}),F=(0,l.useCallback)(function(e,t){var n,r=t,i=e;if(!hp(e)){e.persist&&e.persist();var a=e.target?e.target:e.currentTarget,o=a.type,s=a.name,u=a.id,c=a.value,l=a.checked,f=(a.outerHTML,a.options),d=a.multiple;r=t||s||u,i=/number|range/.test(o)?(n=parseFloat(c),isNaN(n)?"":n):/checkbox/.test(o)?hD(hg(_.values,r),l,c):d?hI(f):c}r&&j(r,i)},[j,_.values]),Y=hP(function(e){if(hp(e))return function(t){return F(t,e)};F(e)}),B=hP(function(e,t,n){return void 0===t&&(t=!0),E({type:"SET_FIELD_TOUCHED",payload:{field:e,value:t}}),(void 0===n?i:n)?O(_.values):Promise.resolve()}),U=(0,l.useCallback)(function(e,t){e.persist&&e.persist();var n,r=e.target,i=r.name,a=r.id;r.outerHTML,B(t||i||a,!0)},[B]),H=hP(function(e){if(hp(e))return function(t){return U(t,e)};U(e)}),$=(0,l.useCallback)(function(e){hf(e)?E({type:"SET_FORMIK_STATE",payload:e}):E({type:"SET_FORMIK_STATE",payload:function(){return e}})},[]),z=(0,l.useCallback)(function(e){E({type:"SET_STATUS",payload:e})},[]),G=(0,l.useCallback)(function(e){E({type:"SET_ISSUBMITTING",payload:e})},[]),W=hP(function(){return E({type:"SUBMIT_ATTEMPT"}),O().then(function(e){var t,n=e instanceof Error;if(!n&&0===Object.keys(e).length){try{if(void 0===(t=q()))return}catch(r){throw r}return Promise.resolve(t).then(function(e){return v.current&&E({type:"SUBMIT_SUCCESS"}),e}).catch(function(e){if(v.current)throw E({type:"SUBMIT_FAILURE"}),e})}if(v.current&&(E({type:"SUBMIT_FAILURE"}),n))throw e})}),K=hP(function(e){e&&e.preventDefault&&hf(e.preventDefault)&&e.preventDefault(),e&&e.stopPropagation&&hf(e.stopPropagation)&&e.stopPropagation(),W().catch(function(e){console.warn("Warning: An unhandled error was caught from submitForm()",e)})}),V={resetForm:A,validateForm:O,validateField:L,setErrors:N,setFieldError:R,setFieldTouched:B,setFieldValue:j,setStatus:z,setSubmitting:G,setTouched:D,setValues:P,setFormikState:$,submitForm:W},q=hP(function(){return f(_.values,V)}),Z=hP(function(e){e&&e.preventDefault&&hf(e.preventDefault)&&e.preventDefault(),e&&e.stopPropagation&&hf(e.stopPropagation)&&e.stopPropagation(),A()}),X=(0,l.useCallback)(function(e){return{value:hg(_.values,e),error:hg(_.errors,e),touched:!!hg(_.touched,e),initialValue:hg(p.current,e),initialTouched:!!hg(m.current,e),initialError:hg(b.current,e)}},[_.errors,_.touched,_.values]),J=(0,l.useCallback)(function(e){return{setValue:function(t,n){return j(e,t,n)},setTouched:function(t,n){return B(e,t,n)},setError:function(t){return R(e,t)}}},[j,B,R]),Q=(0,l.useCallback)(function(e){var t=hd(e),n=t?e.name:e,r=hg(_.values,n),i={name:n,value:r,onChange:Y,onBlur:H};if(t){var a=e.type,o=e.value,s=e.as,u=e.multiple;"checkbox"===a?void 0===o?i.checked=!!r:(i.checked=!!(Array.isArray(r)&&~r.indexOf(o)),i.value=o):"radio"===a?(i.checked=r===o,i.value=o):"select"===s&&u&&(i.value=i.value||[],i.multiple=!0)}return i},[H,Y,_.values]),ee=(0,l.useMemo)(function(){return!sh()(p.current,_.values)},[p.current,_.values]),et=(0,l.useMemo)(function(){return void 0!==s?ee?_.errors&&0===Object.keys(_.errors).length:!1!==s&&hf(s)?s(h):s:_.errors&&0===Object.keys(_.errors).length},[s,ee,_.errors,h]);return ho({},_,{initialValues:p.current,initialErrors:b.current,initialTouched:m.current,initialStatus:g.current,handleBlur:H,handleChange:Y,handleReset:Z,handleSubmit:K,resetForm:A,setErrors:N,setFormikState:$,setFieldTouched:B,setFieldValue:j,setFieldError:R,setStatus:z,setSubmitting:G,setTouched:D,setValues:P,submitForm:W,validateForm:O,validateField:L,isValid:et,dirty:ee,unregisterField:I,registerField:C,getFieldProps:Q,getFieldMeta:X,getFieldHelpers:J,validateOnBlur:i,validateOnChange:n,validateOnMount:o})}function hM(e){var t=hT(e),n=e.component,r=e.children,i=e.render,a=e.innerRef;return(0,l.useImperativeHandle)(a,function(){return t}),(0,l.createElement)(h_,{value:t},n?(0,l.createElement)(n,t):i?i(t):r?hf(r)?r(t):hb(r)?null:l.Children.only(r):null)}function hO(e){var t={};if(e.inner){if(0===e.inner.length)return hv(t,e.path,e.message);for(var n=e.inner,r=Array.isArray(n),i=0,n=r?n:n[Symbol.iterator]();;){if(r){if(i>=n.length)break;a=n[i++]}else{if((i=n.next()).done)break;a=i.value}var a,o=a;hg(t,o.path)||(t=hv(t,o.path,o.message))}}return t}function hA(e,t,n,r){void 0===n&&(n=!1),void 0===r&&(r={});var i=hL(e);return t[n?"validateSync":"validate"](i,{abortEarly:!1,context:r})}function hL(e){var t=Array.isArray(e)?[]:{};for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var r=String(n);!0===Array.isArray(e[r])?t[r]=e[r].map(function(e){return!0===Array.isArray(e)||sj(e)?hL(e):""!==e?e:void 0}):sj(e[r])?t[r]=hL(e[r]):t[r]=""!==e[r]?e[r]:void 0}return t}function hC(e,t,n){var r=e.slice();return t.forEach(function(t,i){if(void 0===r[i]){var a=!1!==n.clone&&n.isMergeableObject(t);r[i]=a?sx(Array.isArray(t)?[]:{},t,n):t}else n.isMergeableObject(t)?r[i]=sx(e[i],t,n):-1===e.indexOf(t)&&r.push(t)}),r}function hI(e){return Array.from(e).filter(function(e){return e.selected}).map(function(e){return e.value})}function hD(e,t,n){if("boolean"==typeof e)return Boolean(t);var r=[],i=!1,a=-1;if(Array.isArray(e))r=e,i=(a=e.indexOf(n))>=0;else if(!n||"true"==n||"false"==n)return Boolean(t);return t&&n&&!i?r.concat(n):i?r.slice(0,a).concat(r.slice(a+1)):r}var hN="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?l.useLayoutEffect:l.useEffect;function hP(e){var t=(0,l.useRef)(e);return hN(function(){t.current=e}),(0,l.useCallback)(function(){for(var e=arguments.length,n=Array(e),r=0;re?t:e},0);return Array.from(ho({},e,{length:t+1}))};(function(e){function t(t){var n;return(n=e.call(this,t)||this).updateArrayField=function(e,t,r){var i=n.props,a=i.name;(0,i.formik.setFormikState)(function(n){var i="function"==typeof r?r:e,o="function"==typeof t?t:e,s=hv(n.values,a,e(hg(n.values,a))),u=r?i(hg(n.errors,a)):void 0,c=t?o(hg(n.touched,a)):void 0;return hl(u)&&(u=void 0),hl(c)&&(c=void 0),ho({},n,{values:s,errors:r?hv(n.errors,a,u):n.errors,touched:t?hv(n.touched,a,c):n.touched})})},n.push=function(e){return n.updateArrayField(function(t){return[].concat(hH(t),[ha(e)])},!1,!1)},n.handlePush=function(e){return function(){return n.push(e)}},n.swap=function(e,t){return n.updateArrayField(function(n){return hY(n,e,t)},!0,!0)},n.handleSwap=function(e,t){return function(){return n.swap(e,t)}},n.move=function(e,t){return n.updateArrayField(function(n){return hF(n,e,t)},!0,!0)},n.handleMove=function(e,t){return function(){return n.move(e,t)}},n.insert=function(e,t){return n.updateArrayField(function(n){return hB(n,e,t)},function(t){return hB(t,e,null)},function(t){return hB(t,e,null)})},n.handleInsert=function(e,t){return function(){return n.insert(e,t)}},n.replace=function(e,t){return n.updateArrayField(function(n){return hU(n,e,t)},!1,!1)},n.handleReplace=function(e,t){return function(){return n.replace(e,t)}},n.unshift=function(e){var t=-1;return n.updateArrayField(function(n){var r=n?[e].concat(n):[e];return t<0&&(t=r.length),r},function(e){var n=e?[null].concat(e):[null];return t<0&&(t=n.length),n},function(e){var n=e?[null].concat(e):[null];return t<0&&(t=n.length),n}),t},n.handleUnshift=function(e){return function(){return n.unshift(e)}},n.handleRemove=function(e){return function(){return n.remove(e)}},n.handlePop=function(){return function(){return n.pop()}},n.remove=n.remove.bind(hc(n)),n.pop=n.pop.bind(hc(n)),n}hs(t,e);var n=t.prototype;return n.componentDidUpdate=function(e){this.props.validateOnChange&&this.props.formik.validateOnChange&&!sh()(hg(e.formik.values,e.name),hg(this.props.formik.values,this.props.name))&&this.props.formik.validateForm(this.props.formik.values)},n.remove=function(e){var t;return this.updateArrayField(function(n){var r=n?hH(n):[];return t||(t=r[e]),hf(r.splice)&&r.splice(e,1),r},!0,!0),t},n.pop=function(){var e;return this.updateArrayField(function(t){var n=t;return e||(e=n&&n.pop&&n.pop()),n},!0,!0),e},n.render=function(){var e={push:this.push,pop:this.pop,swap:this.swap,move:this.move,insert:this.insert,replace:this.replace,unshift:this.unshift,remove:this.remove,handlePush:this.handlePush,handlePop:this.handlePop,handleSwap:this.handleSwap,handleMove:this.handleMove,handleInsert:this.handleInsert,handleReplace:this.handleReplace,handleUnshift:this.handleUnshift,handleRemove:this.handleRemove},t=this.props,n=t.component,r=t.render,i=t.children,a=t.name,o=hu(t.formik,["validate","validationSchema"]),s=ho({},e,{form:o,name:a});return n?(0,l.createElement)(n,s):r?r(s):i?"function"==typeof i?i(s):hb(i)?null:l.Children.only(i):null},t})(l.Component).defaultProps={validateOnChange:!0},l.Component,l.Component;var h$=n(24802),hz=n(71209),hG=n(91750),hW=n(11970),hK=n(4689),hV=n(67598),hq=function(){return(hq=Object.assign||function(e){for(var t,n=1,r=arguments.length;nt.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,r=Object.getOwnPropertySymbols(e);it.indexOf(r[i])&&(n[r[i]]=e[r[i]]);return n}function hX(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hZ(n,["onBlur"]),a=e.form,o=a.isSubmitting,s=a.touched,u=a.errors,c=e.onBlur,l=e.helperText,f=hZ(e,["disabled","field","form","onBlur","helperText"]),d=hg(u,i.name),h=hg(s,i.name)&&!!d;return hq(hq({variant:f.variant,error:h,helperText:h?d:l,disabled:null!=t?t:o,onBlur:null!=c?c:function(e){r(null!=e?e:i.name)}},i),f)}function hJ(e){var t=e.children,n=hZ(e,["children"]);return(0,l.createElement)(i_.Z,hq({},hX(n)),t)}function hQ(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hZ(n,["onBlur"]),a=e.form.isSubmitting,o=(e.type,e.onBlur),s=hZ(e,["disabled","field","form","type","onBlur"]);return hq(hq({disabled:null!=t?t:a,onBlur:null!=o?o:function(e){r(null!=e?e:i.name)}},i),s)}function h1(e){return(0,l.createElement)(h$.Z,hq({},hQ(e)))}function h0(e){var t,n=e.disabled,r=e.field,i=r.onBlur,a=hZ(r,["onBlur"]),o=e.form.isSubmitting,s=(e.type,e.onBlur),u=hZ(e,["disabled","field","form","type","onBlur"]);return hq(hq({disabled:null!=n?n:o,indeterminate:!Array.isArray(a.value)&&null==a.value,onBlur:null!=s?s:function(e){i(null!=e?e:a.name)}},a),u)}function h2(e){return(0,l.createElement)(hz.Z,hq({},h0(e)))}function h3(e){var t=e.Label,n=hZ(e,["Label"]);return(0,l.createElement)(hG.Z,hq({control:(0,l.createElement)(hz.Z,hq({},h0(n)))},t))}function h4(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hZ(n,["onBlur"]),a=e.form.isSubmitting,o=e.onBlur,s=hZ(e,["disabled","field","form","onBlur"]);return hq(hq({disabled:null!=t?t:a,onBlur:null!=o?o:function(e){r(null!=e?e:i.name)}},i),s)}function h5(e){return(0,l.createElement)(hW.default,hq({},h4(e)))}function h6(e){var t=e.field,n=t.onBlur,r=hZ(t,["onBlur"]),i=(e.form,e.onBlur),a=hZ(e,["field","form","onBlur"]);return hq(hq({onBlur:null!=i?i:function(e){n(null!=e?e:r.name)}},r),a)}function h9(e){return(0,l.createElement)(hK.Z,hq({},h6(e)))}function h8(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hZ(n,["onBlur"]),a=e.form.isSubmitting,o=e.onBlur,s=hZ(e,["disabled","field","form","onBlur"]);return hq(hq({disabled:null!=t?t:a,onBlur:null!=o?o:function(e){r(null!=e?e:i.name)}},i),s)}function h7(e){return(0,l.createElement)(hV.default,hq({},h8(e)))}hJ.displayName="FormikMaterialUITextField",h1.displayName="FormikMaterialUISwitch",h2.displayName="FormikMaterialUICheckbox",h3.displayName="FormikMaterialUICheckboxWithLabel",h5.displayName="FormikMaterialUISelect",h9.displayName="FormikMaterialUIRadioGroup",h7.displayName="FormikMaterialUIInputBase";try{a=Map}catch(pe){}try{o=Set}catch(pt){}function pn(e,t,n){if(!e||"object"!=typeof e||"function"==typeof e)return e;if(e.nodeType&&"cloneNode"in e)return e.cloneNode(!0);if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return RegExp(e);if(Array.isArray(e))return e.map(pr);if(a&&e instanceof a)return new Map(Array.from(e.entries()));if(o&&e instanceof o)return new Set(Array.from(e.values()));if(e instanceof Object){t.push(e);var r=Object.create(e);for(var i in n.push(r),e){var s=t.findIndex(function(t){return t===e[i]});r[i]=s>-1?n[s]:pn(e[i],t,n)}return r}return e}function pr(e){return pn(e,[],[])}let pi=Object.prototype.toString,pa=Error.prototype.toString,po=RegExp.prototype.toString,ps="undefined"!=typeof Symbol?Symbol.prototype.toString:()=>"",pu=/^Symbol\((.*)\)(.*)$/;function pc(e){if(e!=+e)return"NaN";let t=0===e&&1/e<0;return t?"-0":""+e}function pl(e,t=!1){if(null==e||!0===e||!1===e)return""+e;let n=typeof e;if("number"===n)return pc(e);if("string"===n)return t?`"${e}"`:e;if("function"===n)return"[Function "+(e.name||"anonymous")+"]";if("symbol"===n)return ps.call(e).replace(pu,"Symbol($1)");let r=pi.call(e).slice(8,-1);return"Date"===r?isNaN(e.getTime())?""+e:e.toISOString(e):"Error"===r||e instanceof Error?"["+pa.call(e)+"]":"RegExp"===r?po.call(e):null}function pf(e,t){let n=pl(e,t);return null!==n?n:JSON.stringify(e,function(e,n){let r=pl(this[e],t);return null!==r?r:n},2)}let pd={default:"${path} is invalid",required:"${path} is a required field",oneOf:"${path} must be one of the following values: ${values}",notOneOf:"${path} must not be one of the following values: ${values}",notType({path:e,type:t,value:n,originalValue:r}){let i=null!=r&&r!==n,a=`${e} must be a \`${t}\` type, but the final value was: \`${pf(n,!0)}\``+(i?` (cast from the value \`${pf(r,!0)}\`).`:".");return null===n&&(a+='\n If "null" is intended as an empty value be sure to mark the schema as `.nullable()`'),a},defined:"${path} must be defined"},ph={length:"${path} must be exactly ${length} characters",min:"${path} must be at least ${min} characters",max:"${path} must be at most ${max} characters",matches:'${path} must match the following: "${regex}"',email:"${path} must be a valid email",url:"${path} must be a valid URL",uuid:"${path} must be a valid UUID",trim:"${path} must be a trimmed string",lowercase:"${path} must be a lowercase string",uppercase:"${path} must be a upper case string"},pp={min:"${path} must be greater than or equal to ${min}",max:"${path} must be less than or equal to ${max}",lessThan:"${path} must be less than ${less}",moreThan:"${path} must be greater than ${more}",positive:"${path} must be a positive number",negative:"${path} must be a negative number",integer:"${path} must be an integer"},pb={min:"${path} field must be later than ${min}",max:"${path} field must be at earlier than ${max}"},pm={isValue:"${path} field must be ${value}"},pg={noUnknown:"${path} field has unspecified keys: ${unknown}"},pv={min:"${path} field must have at least ${min} items",max:"${path} field must have less than or equal to ${max} items",length:"${path} must be have ${length} items"};Object.assign(Object.create(null),{mixed:pd,string:ph,number:pp,date:pb,object:pg,array:pv,boolean:pm});var py=n(18721),pw=n.n(py);let p_=e=>e&&e.__isYupSchema__;class pE{constructor(e,t){if(this.refs=e,this.refs=e,"function"==typeof t){this.fn=t;return}if(!pw()(t,"is"))throw TypeError("`is:` is required for `when()` conditions");if(!t.then&&!t.otherwise)throw TypeError("either `then:` or `otherwise:` is required for `when()` conditions");let{is:n,then:r,otherwise:i}=t,a="function"==typeof n?n:(...e)=>e.every(e=>e===n);this.fn=function(...e){let t=e.pop(),n=e.pop(),o=a(...e)?r:i;if(o)return"function"==typeof o?o(n):n.concat(o.resolve(t))}}resolve(e,t){let n=this.refs.map(e=>e.getValue(null==t?void 0:t.value,null==t?void 0:t.parent,null==t?void 0:t.context)),r=this.fn.apply(e,n.concat(e,t));if(void 0===r||r===e)return e;if(!p_(r))throw TypeError("conditions must return a schema object");return r.resolve(t)}}let pS=pE;function pk(e){return null==e?[]:[].concat(e)}function px(){return(px=Object.assign||function(e){for(var t=1;tpf(t[n])):"function"==typeof e?e(t):e}static isError(e){return e&&"ValidationError"===e.name}constructor(e,t,n,r){super(),this.name="ValidationError",this.value=t,this.path=n,this.type=r,this.errors=[],this.inner=[],pk(e).forEach(e=>{pM.isError(e)?(this.errors.push(...e.errors),this.inner=this.inner.concat(e.inner.length?e.inner:e)):this.errors.push(e)}),this.message=this.errors.length>1?`${this.errors.length} errors occurred`:this.errors[0],Error.captureStackTrace&&Error.captureStackTrace(this,pM)}}let pO=e=>{let t=!1;return(...n)=>{t||(t=!0,e(...n))}};function pA(e,t){let{endEarly:n,tests:r,args:i,value:a,errors:o,sort:s,path:u}=e,c=pO(t),l=r.length,f=[];if(o=o||[],!l)return o.length?c(new pM(o,a,u)):c(null,a);for(let d=0;d=0||(i[n]=e[n]);return i}function pj(e){function t(t,n){let{value:r,path:i="",label:a,options:o,originalValue:s,sync:u}=t,c=pR(t,["value","path","label","options","originalValue","sync"]),{name:l,test:f,params:d,message:h}=e,{parent:p,context:b}=o;function m(e){return pN.isRef(e)?e.getValue(r,p,b):e}function g(e={}){let t=pC()(pP({value:r,originalValue:s,label:a,path:e.path||i},d,e.params),m),n=new pM(pM.formatError(e.message||h,t),r,t.path,e.type||l);return n.params=t,n}let v=pP({path:i,parent:p,type:l,createError:g,resolve:m,options:o,originalValue:s},c);if(!u){try{Promise.resolve(f.call(v,r,v)).then(e=>{pM.isError(e)?n(e):e?n(null,e):n(g())})}catch(y){n(y)}return}let w;try{var _;if(w=f.call(v,r,v),"function"==typeof(null==(_=w)?void 0:_.then))throw Error(`Validation test of type: "${v.type}" returned a Promise during a synchronous validate. This test will finish after the validate call has returned`)}catch(E){n(E);return}pM.isError(w)?n(w):w?n(null,w):n(g())}return t.OPTIONS=e,t}pN.prototype.__isYupRef=!0;let pF=e=>e.substr(0,e.length-1).substr(1);function pY(e,t,n,r=n){let i,a,o;return t?((0,pI.forEach)(t,(s,u,c)=>{let l=u?pF(s):s;if((e=e.resolve({context:r,parent:i,value:n})).innerType){let f=c?parseInt(l,10):0;if(n&&f>=n.length)throw Error(`Yup.reach cannot resolve an array item at index: ${s}, in the path: ${t}. because there is no value at that index. `);i=n,n=n&&n[f],e=e.innerType}if(!c){if(!e.fields||!e.fields[l])throw Error(`The schema does not contain the path: ${t}. (failed at: ${o} which is a type: "${e._type}")`);i=n,n=n&&n[l],e=e.fields[l]}a=l,o=u?"["+s+"]":"."+s}),{schema:e,parent:i,parentPath:a}):{parent:i,parentPath:t,schema:e}}class pB{constructor(){this.list=new Set,this.refs=new Map}get size(){return this.list.size+this.refs.size}describe(){let e=[];for(let t of this.list)e.push(t);for(let[,n]of this.refs)e.push(n.describe());return e}toArray(){return Array.from(this.list).concat(Array.from(this.refs.values()))}add(e){pN.isRef(e)?this.refs.set(e.key,e):this.list.add(e)}delete(e){pN.isRef(e)?this.refs.delete(e.key):this.list.delete(e)}has(e,t){if(this.list.has(e))return!0;let n,r=this.refs.values();for(;!(n=r.next()).done;)if(t(n.value)===e)return!0;return!1}clone(){let e=new pB;return e.list=new Set(this.list),e.refs=new Map(this.refs),e}merge(e,t){let n=this.clone();return e.list.forEach(e=>n.add(e)),e.refs.forEach(e=>n.add(e)),t.list.forEach(e=>n.delete(e)),t.refs.forEach(e=>n.delete(e)),n}}function pU(){return(pU=Object.assign||function(e){for(var t=1;t{this.typeError(pd.notType)}),this.type=(null==e?void 0:e.type)||"mixed",this.spec=pU({strip:!1,strict:!1,abortEarly:!0,recursive:!0,nullable:!1,presence:"optional"},null==e?void 0:e.spec)}get _type(){return this.type}_typeCheck(e){return!0}clone(e){if(this._mutate)return e&&Object.assign(this.spec,e),this;let t=Object.create(Object.getPrototypeOf(this));return t.type=this.type,t._typeError=this._typeError,t._whitelistError=this._whitelistError,t._blacklistError=this._blacklistError,t._whitelist=this._whitelist.clone(),t._blacklist=this._blacklist.clone(),t.exclusiveTests=pU({},this.exclusiveTests),t.deps=[...this.deps],t.conditions=[...this.conditions],t.tests=[...this.tests],t.transforms=[...this.transforms],t.spec=pr(pU({},this.spec,e)),t}label(e){var t=this.clone();return t.spec.label=e,t}meta(...e){if(0===e.length)return this.spec.meta;let t=this.clone();return t.spec.meta=Object.assign(t.spec.meta||{},e[0]),t}withMutation(e){let t=this._mutate;this._mutate=!0;let n=e(this);return this._mutate=t,n}concat(e){if(!e||e===this)return this;if(e.type!==this.type&&"mixed"!==this.type)throw TypeError(`You cannot \`concat()\` schema's of different types: ${this.type} and ${e.type}`);let t=this,n=e.clone(),r=pU({},t.spec,n.spec);return n.spec=r,n._typeError||(n._typeError=t._typeError),n._whitelistError||(n._whitelistError=t._whitelistError),n._blacklistError||(n._blacklistError=t._blacklistError),n._whitelist=t._whitelist.merge(e._whitelist,e._blacklist),n._blacklist=t._blacklist.merge(e._blacklist,e._whitelist),n.tests=t.tests,n.exclusiveTests=t.exclusiveTests,n.withMutation(t=>{e.tests.forEach(e=>{t.test(e.OPTIONS)})}),n}isType(e){return!!this.spec.nullable&&null===e||this._typeCheck(e)}resolve(e){let t=this;if(t.conditions.length){let n=t.conditions;(t=t.clone()).conditions=[],t=(t=n.reduce((t,n)=>n.resolve(t,e),t)).resolve(e)}return t}cast(e,t={}){let n=this.resolve(pU({value:e},t)),r=n._cast(e,t);if(void 0!==e&&!1!==t.assert&&!0!==n.isType(r)){let i=pf(e),a=pf(r);throw TypeError(`The value of ${t.path||"field"} could not be cast to a value that satisfies the schema type: "${n._type}". + +attempted value: ${i} +`+(a!==i?`result of cast: ${a}`:""))}return r}_cast(e,t){let n=void 0===e?e:this.transforms.reduce((t,n)=>n.call(this,t,e,this),e);return void 0===n&&(n=this.getDefault()),n}_validate(e,t={},n){let{sync:r,path:i,from:a=[],originalValue:o=e,strict:s=this.spec.strict,abortEarly:u=this.spec.abortEarly}=t,c=e;s||(c=this._cast(c,pU({assert:!1},t)));let l={value:c,path:i,options:t,originalValue:o,schema:this,label:this.spec.label,sync:r,from:a},f=[];this._typeError&&f.push(this._typeError),this._whitelistError&&f.push(this._whitelistError),this._blacklistError&&f.push(this._blacklistError),pA({args:l,value:c,path:i,sync:r,tests:f,endEarly:u},e=>{if(e)return void n(e,c);pA({tests:this.tests,args:l,path:i,sync:r,value:c,endEarly:u},n)})}validate(e,t,n){let r=this.resolve(pU({},t,{value:e}));return"function"==typeof n?r._validate(e,t,n):new Promise((n,i)=>r._validate(e,t,(e,t)=>{e?i(e):n(t)}))}validateSync(e,t){let n;return this.resolve(pU({},t,{value:e}))._validate(e,pU({},t,{sync:!0}),(e,t)=>{if(e)throw e;n=t}),n}isValid(e,t){return this.validate(e,t).then(()=>!0,e=>{if(pM.isError(e))return!1;throw e})}isValidSync(e,t){try{return this.validateSync(e,t),!0}catch(n){if(pM.isError(n))return!1;throw n}}_getDefault(){let e=this.spec.default;return null==e?e:"function"==typeof e?e.call(this):pr(e)}getDefault(e){return this.resolve(e||{})._getDefault()}default(e){return 0===arguments.length?this._getDefault():this.clone({default:e})}strict(e=!0){var t=this.clone();return t.spec.strict=e,t}_isPresent(e){return null!=e}defined(e=pd.defined){return this.test({message:e,name:"defined",exclusive:!0,test:e=>void 0!==e})}required(e=pd.required){return this.clone({presence:"required"}).withMutation(t=>t.test({message:e,name:"required",exclusive:!0,test(e){return this.schema._isPresent(e)}}))}notRequired(){var e=this.clone({presence:"optional"});return e.tests=e.tests.filter(e=>"required"!==e.OPTIONS.name),e}nullable(e=!0){return this.clone({nullable:!1!==e})}transform(e){var t=this.clone();return t.transforms.push(e),t}test(...e){let t;if(void 0===(t=1===e.length?"function"==typeof e[0]?{test:e[0]}:e[0]:2===e.length?{name:e[0],test:e[1]}:{name:e[0],message:e[1],test:e[2]}).message&&(t.message=pd.default),"function"!=typeof t.test)throw TypeError("`test` is a required parameters");let n=this.clone(),r=pj(t),i=t.exclusive||t.name&&!0===n.exclusiveTests[t.name];if(t.exclusive&&!t.name)throw TypeError("Exclusive tests must provide a unique `name` identifying the test");return t.name&&(n.exclusiveTests[t.name]=!!t.exclusive),n.tests=n.tests.filter(e=>e.OPTIONS.name!==t.name||!i&&e.OPTIONS.test!==r.OPTIONS.test),n.tests.push(r),n}when(e,t){Array.isArray(e)||"string"==typeof e||(t=e,e=".");let n=this.clone(),r=pk(e).map(e=>new pN(e));return r.forEach(e=>{e.isSibling&&n.deps.push(e.key)}),n.conditions.push(new pS(r,t)),n}typeError(e){var t=this.clone();return t._typeError=pj({message:e,name:"typeError",test(e){return!!(void 0===e||this.schema.isType(e))||this.createError({params:{type:this.schema._type}})}}),t}oneOf(e,t=pd.oneOf){var n=this.clone();return e.forEach(e=>{n._whitelist.add(e),n._blacklist.delete(e)}),n._whitelistError=pj({message:t,name:"oneOf",test(e){if(void 0===e)return!0;let t=this.schema._whitelist;return!!t.has(e,this.resolve)||this.createError({params:{values:t.toArray().join(", ")}})}}),n}notOneOf(e,t=pd.notOneOf){var n=this.clone();return e.forEach(e=>{n._blacklist.add(e),n._whitelist.delete(e)}),n._blacklistError=pj({message:t,name:"notOneOf",test(e){let t=this.schema._blacklist;return!t.has(e,this.resolve)||this.createError({params:{values:t.toArray().join(", ")}})}}),n}strip(e=!0){let t=this.clone();return t.spec.strip=e,t}describe(){let e=this.clone(),{label:t,meta:n}=e.spec,r={meta:n,label:t,type:e.type,oneOf:e._whitelist.describe(),notOneOf:e._blacklist.describe(),tests:e.tests.map(e=>({name:e.OPTIONS.name,params:e.OPTIONS.params})).filter((e,t,n)=>n.findIndex(t=>t.name===e.name)===t)};return r}}for(let p$ of(pH.prototype.__isYupSchema__=!0,["validate","validateSync"]))pH.prototype[`${p$}At`]=function(e,t,n={}){let{parent:r,parentPath:i,schema:a}=pY(this,e,t,n.context);return a[p$](r&&r[i],pU({},n,{parent:r,path:e}))};for(let pz of["equals","is"])pH.prototype[pz]=pH.prototype.oneOf;for(let pG of["not","nope"])pH.prototype[pG]=pH.prototype.notOneOf;pH.prototype.optional=pH.prototype.notRequired;let pW=pH;function pK(){return new pW}pK.prototype=pW.prototype;let pV=e=>null==e;function pq(){return new pZ}class pZ extends pH{constructor(){super({type:"boolean"}),this.withMutation(()=>{this.transform(function(e){if(!this.isType(e)){if(/^(true|1)$/i.test(String(e)))return!0;if(/^(false|0)$/i.test(String(e)))return!1}return e})})}_typeCheck(e){return e instanceof Boolean&&(e=e.valueOf()),"boolean"==typeof e}isTrue(e=pm.isValue){return this.test({message:e,name:"is-value",exclusive:!0,params:{value:"true"},test:e=>pV(e)||!0===e})}isFalse(e=pm.isValue){return this.test({message:e,name:"is-value",exclusive:!0,params:{value:"false"},test:e=>pV(e)||!1===e})}}pq.prototype=pZ.prototype;let pX=/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,pJ=/^((https?|ftp):)?\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i,pQ=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i,p1=e=>pV(e)||e===e.trim(),p0=({}).toString();function p2(){return new p3}class p3 extends pH{constructor(){super({type:"string"}),this.withMutation(()=>{this.transform(function(e){if(this.isType(e)||Array.isArray(e))return e;let t=null!=e&&e.toString?e.toString():e;return t===p0?e:t})})}_typeCheck(e){return e instanceof String&&(e=e.valueOf()),"string"==typeof e}_isPresent(e){return super._isPresent(e)&&!!e.length}length(e,t=ph.length){return this.test({message:t,name:"length",exclusive:!0,params:{length:e},test(t){return pV(t)||t.length===this.resolve(e)}})}min(e,t=ph.min){return this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(t){return pV(t)||t.length>=this.resolve(e)}})}max(e,t=ph.max){return this.test({name:"max",exclusive:!0,message:t,params:{max:e},test(t){return pV(t)||t.length<=this.resolve(e)}})}matches(e,t){let n=!1,r,i;return t&&("object"==typeof t?{excludeEmptyString:n=!1,message:r,name:i}=t:r=t),this.test({name:i||"matches",message:r||ph.matches,params:{regex:e},test:t=>pV(t)||""===t&&n||-1!==t.search(e)})}email(e=ph.email){return this.matches(pX,{name:"email",message:e,excludeEmptyString:!0})}url(e=ph.url){return this.matches(pJ,{name:"url",message:e,excludeEmptyString:!0})}uuid(e=ph.uuid){return this.matches(pQ,{name:"uuid",message:e,excludeEmptyString:!1})}ensure(){return this.default("").transform(e=>null===e?"":e)}trim(e=ph.trim){return this.transform(e=>null!=e?e.trim():e).test({message:e,name:"trim",test:p1})}lowercase(e=ph.lowercase){return this.transform(e=>pV(e)?e:e.toLowerCase()).test({message:e,name:"string_case",exclusive:!0,test:e=>pV(e)||e===e.toLowerCase()})}uppercase(e=ph.uppercase){return this.transform(e=>pV(e)?e:e.toUpperCase()).test({message:e,name:"string_case",exclusive:!0,test:e=>pV(e)||e===e.toUpperCase()})}}p2.prototype=p3.prototype;let p4=e=>e!=+e;function p5(){return new p6}class p6 extends pH{constructor(){super({type:"number"}),this.withMutation(()=>{this.transform(function(e){let t=e;if("string"==typeof t){if(""===(t=t.replace(/\s/g,"")))return NaN;t=+t}return this.isType(t)?t:parseFloat(t)})})}_typeCheck(e){return e instanceof Number&&(e=e.valueOf()),"number"==typeof e&&!p4(e)}min(e,t=pp.min){return this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(t){return pV(t)||t>=this.resolve(e)}})}max(e,t=pp.max){return this.test({message:t,name:"max",exclusive:!0,params:{max:e},test(t){return pV(t)||t<=this.resolve(e)}})}lessThan(e,t=pp.lessThan){return this.test({message:t,name:"max",exclusive:!0,params:{less:e},test(t){return pV(t)||tthis.resolve(e)}})}positive(e=pp.positive){return this.moreThan(0,e)}negative(e=pp.negative){return this.lessThan(0,e)}integer(e=pp.integer){return this.test({name:"integer",message:e,test:e=>pV(e)||Number.isInteger(e)})}truncate(){return this.transform(e=>pV(e)?e:0|e)}round(e){var t,n=["ceil","floor","round","trunc"];if("trunc"===(e=(null==(t=e)?void 0:t.toLowerCase())||"round"))return this.truncate();if(-1===n.indexOf(e.toLowerCase()))throw TypeError("Only valid options for round() are: "+n.join(", "));return this.transform(t=>pV(t)?t:Math[e](t))}}p5.prototype=p6.prototype;var p9=/^(\d{4}|[+\-]\d{6})(?:-?(\d{2})(?:-?(\d{2}))?)?(?:[ T]?(\d{2}):?(\d{2})(?::?(\d{2})(?:[,\.](\d{1,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?)?)?$/;function p8(e){var t,n,r=[1,4,5,6,7,10,11],i=0;if(n=p9.exec(e)){for(var a,o=0;a=r[o];++o)n[a]=+n[a]||0;n[2]=(+n[2]||1)-1,n[3]=+n[3]||1,n[7]=n[7]?String(n[7]).substr(0,3):0,(void 0===n[8]||""===n[8])&&(void 0===n[9]||""===n[9])?t=+new Date(n[1],n[2],n[3],n[4],n[5],n[6],n[7]):("Z"!==n[8]&&void 0!==n[9]&&(i=60*n[10]+n[11],"+"===n[9]&&(i=0-i)),t=Date.UTC(n[1],n[2],n[3],n[4],n[5]+i,n[6],n[7]))}else t=Date.parse?Date.parse(e):NaN;return t}let p7=new Date(""),be=e=>"[object Date]"===Object.prototype.toString.call(e);function bt(){return new bn}class bn extends pH{constructor(){super({type:"date"}),this.withMutation(()=>{this.transform(function(e){return this.isType(e)?e:(e=p8(e),isNaN(e)?p7:new Date(e))})})}_typeCheck(e){return be(e)&&!isNaN(e.getTime())}prepareParam(e,t){let n;if(pN.isRef(e))n=e;else{let r=this.cast(e);if(!this._typeCheck(r))throw TypeError(`\`${t}\` must be a Date or a value that can be \`cast()\` to a Date`);n=r}return n}min(e,t=pb.min){let n=this.prepareParam(e,"min");return this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(e){return pV(e)||e>=this.resolve(n)}})}max(e,t=pb.max){var n=this.prepareParam(e,"max");return this.test({message:t,name:"max",exclusive:!0,params:{max:e},test(e){return pV(e)||e<=this.resolve(n)}})}}bn.INVALID_DATE=p7,bt.prototype=bn.prototype,bt.INVALID_DATE=p7;var br=n(11865),bi=n.n(br),ba=n(68929),bo=n.n(ba),bs=n(67523),bu=n.n(bs),bc=n(94633),bl=n.n(bc);function bf(e,t=[]){let n=[],r=[];function i(e,i){var a=(0,pI.split)(e)[0];~r.indexOf(a)||r.push(a),~t.indexOf(`${i}-${a}`)||n.push([i,a])}for(let a in e)if(pw()(e,a)){let o=e[a];~r.indexOf(a)||r.push(a),pN.isRef(o)&&o.isSibling?i(o.path,a):p_(o)&&"deps"in o&&o.deps.forEach(e=>i(e,a))}return bl().array(r,n).reverse()}function bd(e,t){let n=1/0;return e.some((e,r)=>{var i;if((null==(i=t.path)?void 0:i.indexOf(e))!==-1)return n=r,!0}),n}function bh(e){return(t,n)=>bd(e,t)-bd(e,n)}function bp(){return(bp=Object.assign||function(e){for(var t=1;t"[object Object]"===Object.prototype.toString.call(e);function bm(e,t){let n=Object.keys(e.fields);return Object.keys(t).filter(e=>-1===n.indexOf(e))}let bg=bh([]);class bv extends pH{constructor(e){super({type:"object"}),this.fields=Object.create(null),this._sortErrors=bg,this._nodes=[],this._excludedEdges=[],this.withMutation(()=>{this.transform(function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=null}return this.isType(e)?e:null}),e&&this.shape(e)})}_typeCheck(e){return bb(e)||"function"==typeof e}_cast(e,t={}){var n;let r=super._cast(e,t);if(void 0===r)return this.getDefault();if(!this._typeCheck(r))return r;let i=this.fields,a=null!=(n=t.stripUnknown)?n:this.spec.noUnknown,o=this._nodes.concat(Object.keys(r).filter(e=>-1===this._nodes.indexOf(e))),s={},u=bp({},t,{parent:s,__validating:t.__validating||!1}),c=!1;for(let l of o){let f=i[l],d=pw()(r,l);if(f){let h,p=r[l];u.path=(t.path?`${t.path}.`:"")+l;let b="spec"in(f=f.resolve({value:p,context:t.context,parent:s}))?f.spec:void 0,m=null==b?void 0:b.strict;if(null==b?void 0:b.strip){c=c||l in r;continue}void 0!==(h=t.__validating&&m?r[l]:f.cast(r[l],u))&&(s[l]=h)}else d&&!a&&(s[l]=r[l]);s[l]!==r[l]&&(c=!0)}return c?s:r}_validate(e,t={},n){let r=[],{sync:i,from:a=[],originalValue:o=e,abortEarly:s=this.spec.abortEarly,recursive:u=this.spec.recursive}=t;a=[{schema:this,value:o},...a],t.__validating=!0,t.originalValue=o,t.from=a,super._validate(e,t,(e,c)=>{if(e){if(!pM.isError(e)||s)return void n(e,c);r.push(e)}if(!u||!bb(c)){n(r[0]||null,c);return}o=o||c;let l=this._nodes.map(e=>(n,r)=>{let i=-1===e.indexOf(".")?(t.path?`${t.path}.`:"")+e:`${t.path||""}["${e}"]`,s=this.fields[e];if(s&&"validate"in s){s.validate(c[e],bp({},t,{path:i,from:a,strict:!0,parent:c,originalValue:o[e]}),r);return}r(null)});pA({sync:i,tests:l,value:c,errors:r,endEarly:s,sort:this._sortErrors,path:t.path},n)})}clone(e){let t=super.clone(e);return t.fields=bp({},this.fields),t._nodes=this._nodes,t._excludedEdges=this._excludedEdges,t._sortErrors=this._sortErrors,t}concat(e){let t=super.concat(e),n=t.fields;for(let[r,i]of Object.entries(this.fields)){let a=n[r];void 0===a?n[r]=i:a instanceof pH&&i instanceof pH&&(n[r]=i.concat(a))}return t.withMutation(()=>t.shape(n))}getDefaultFromShape(){let e={};return this._nodes.forEach(t=>{let n=this.fields[t];e[t]="default"in n?n.getDefault():void 0}),e}_getDefault(){return"default"in this.spec?super._getDefault():this._nodes.length?this.getDefaultFromShape():void 0}shape(e,t=[]){let n=this.clone(),r=Object.assign(n.fields,e);if(n.fields=r,n._sortErrors=bh(Object.keys(r)),t.length){Array.isArray(t[0])||(t=[t]);let i=t.map(([e,t])=>`${e}-${t}`);n._excludedEdges=n._excludedEdges.concat(i)}return n._nodes=bf(r,n._excludedEdges),n}pick(e){let t={};for(let n of e)this.fields[n]&&(t[n]=this.fields[n]);return this.clone().withMutation(e=>(e.fields={},e.shape(t)))}omit(e){let t=this.clone(),n=t.fields;for(let r of(t.fields={},e))delete n[r];return t.withMutation(()=>t.shape(n))}from(e,t,n){let r=(0,pI.getter)(e,!0);return this.transform(i=>{if(null==i)return i;let a=i;return pw()(i,e)&&(a=bp({},i),n||delete a[e],a[t]=r(i)),a})}noUnknown(e=!0,t=pg.noUnknown){"string"==typeof e&&(t=e,e=!0);let n=this.test({name:"noUnknown",exclusive:!0,message:t,test(t){if(null==t)return!0;let n=bm(this.schema,t);return!e||0===n.length||this.createError({params:{unknown:n.join(", ")}})}});return n.spec.noUnknown=e,n}unknown(e=!0,t=pg.noUnknown){return this.noUnknown(!e,t)}transformKeys(e){return this.transform(t=>t&&bu()(t,(t,n)=>e(n)))}camelCase(){return this.transformKeys(bo())}snakeCase(){return this.transformKeys(bi())}constantCase(){return this.transformKeys(e=>bi()(e).toUpperCase())}describe(){let e=super.describe();return e.fields=pC()(this.fields,e=>e.describe()),e}}function by(e){return new bv(e)}function bw(){return(bw=Object.assign||function(e){for(var t=1;t{this.transform(function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=null}return this.isType(e)?e:null})})}_typeCheck(e){return Array.isArray(e)}get _subType(){return this.innerType}_cast(e,t){let n=super._cast(e,t);if(!this._typeCheck(n)||!this.innerType)return n;let r=!1,i=n.map((e,n)=>{let i=this.innerType.cast(e,bw({},t,{path:`${t.path||""}[${n}]`}));return i!==e&&(r=!0),i});return r?i:n}_validate(e,t={},n){var r,i;let a=[],o=t.sync,s=t.path,u=this.innerType,c=null!=(r=t.abortEarly)?r:this.spec.abortEarly,l=null!=(i=t.recursive)?i:this.spec.recursive,f=null!=t.originalValue?t.originalValue:e;super._validate(e,t,(e,r)=>{if(e){if(!pM.isError(e)||c)return void n(e,r);a.push(e)}if(!l||!u||!this._typeCheck(r)){n(a[0]||null,r);return}f=f||r;let i=Array(r.length);for(let d=0;du.validate(h,b,t)}pA({sync:o,path:s,value:r,errors:a,endEarly:c,tests:i},n)})}clone(e){let t=super.clone(e);return t.innerType=this.innerType,t}concat(e){let t=super.concat(e);return t.innerType=this.innerType,e.innerType&&(t.innerType=t.innerType?t.innerType.concat(e.innerType):e.innerType),t}of(e){let t=this.clone();if(!p_(e))throw TypeError("`array.of()` sub-schema must be a valid yup schema not: "+pf(e));return t.innerType=e,t}length(e,t=pv.length){return this.test({message:t,name:"length",exclusive:!0,params:{length:e},test(t){return pV(t)||t.length===this.resolve(e)}})}min(e,t){return t=t||pv.min,this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(t){return pV(t)||t.length>=this.resolve(e)}})}max(e,t){return t=t||pv.max,this.test({message:t,name:"max",exclusive:!0,params:{max:e},test(t){return pV(t)||t.length<=this.resolve(e)}})}ensure(){return this.default(()=>[]).transform((e,t)=>this._typeCheck(e)?e:null==t?[]:[].concat(t))}compact(e){let t=e?(t,n,r)=>!e(t,n,r):e=>!!e;return this.transform(e=>null!=e?e.filter(t):e)}describe(){let e=super.describe();return this.innerType&&(e.innerType=this.innerType.describe()),e}nullable(e=!0){return super.nullable(e)}defined(){return super.defined()}required(e){return super.required(e)}}b_.prototype=bE.prototype;var bS=by().shape({name:p2().required("Required"),url:p2().required("Required")}),bk=function(e){var t=e.initialValues,n=e.onSubmit,r=e.submitButtonText,i=e.nameDisabled,a=void 0!==i&&i;return l.createElement(hM,{initialValues:t,validationSchema:bS,onSubmit:n},function(e){var t=e.isSubmitting;return l.createElement(l.Fragment,null,l.createElement(hj,{"data-testid":"bridge-form",noValidate:!0},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(hR,{component:hJ,id:"name",name:"name",label:"Name",disabled:a,required:!0,fullWidth:!0,FormHelperTextProps:{"data-testid":"name-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(hR,{component:hJ,id:"url",name:"url",label:"Bridge URL",placeholder:"https://",required:!0,fullWidth:!0,FormHelperTextProps:{"data-testid":"url-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:7},l.createElement(hR,{component:hJ,id:"minimumContractPayment",name:"minimumContractPayment",label:"Minimum Contract Payment",placeholder:"0",fullWidth:!0,inputProps:{min:0},FormHelperTextProps:{"data-testid":"minimumContractPayment-helper-text"}})),l.createElement(d.Z,{item:!0,xs:7},l.createElement(hR,{component:hJ,id:"confirmations",name:"confirmations",label:"Confirmations",placeholder:"0",type:"number",fullWidth:!0,inputProps:{min:0},FormHelperTextProps:{"data-testid":"confirmations-helper-text"}})))),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(ox.default,{variant:"contained",color:"primary",type:"submit",disabled:t,size:"large"},r)))))})},bx=function(e){var t=e.bridge,n=e.onSubmit,r={name:t.name,url:t.url,minimumContractPayment:t.minimumContractPayment,confirmations:t.confirmations};return l.createElement(iv,null,l.createElement(d.Z,{container:!0,spacing:40},l.createElement(d.Z,{item:!0,xs:12,md:11,lg:9},l.createElement(r9.Z,null,l.createElement(sf.Z,{title:"Edit Bridge",action:l.createElement(aL.Z,{component:tz,href:"/bridges/".concat(t.id)},"Cancel")}),l.createElement(aK.Z,null,l.createElement(bk,{nameDisabled:!0,initialValues:r,onSubmit:n,submitButtonText:"Save Bridge"}))))))};function bT(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]&&arguments[0],t=e?function(){return l.createElement(x.default,{variant:"body1"},"Loading...")}:function(){return null};return{isLoading:e,LoadingPlaceholder:t}},ml=n(76023);function mf(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]=0||(i[n]=e[n]);return i}function mB(e,t){if(null==e)return{};var n,r,i=mY(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function mU(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n=4?[e[0],e[1],e[2],e[3],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[2],".").concat(e[3]),"".concat(e[3],".").concat(e[0]),"".concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[0]),"".concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[3],".").concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[2],".").concat(e[1],".").concat(e[0])]:void 0}var mX={};function mJ(e){if(0===e.length||1===e.length)return e;var t=e.join(".");return mX[t]||(mX[t]=mZ(e)),mX[t]}function mQ(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return mJ(e.filter(function(e){return"token"!==e})).reduce(function(e,t){return mV({},e,n[t])},t)}function m1(e){return e.join(" ")}function m0(e,t){var n=0;return function(r){return n+=1,r.map(function(r,i){return m2({node:r,stylesheet:e,useInlineStyles:t,key:"code-segment-".concat(n,"-").concat(i)})})}}function m2(e){var t=e.node,n=e.stylesheet,r=e.style,i=void 0===r?{}:r,a=e.useInlineStyles,o=e.key,s=t.properties,u=t.type,c=t.tagName,f=t.value;if("text"===u)return f;if(c){var d,h=m0(n,a);if(a){var p=Object.keys(n).reduce(function(e,t){return t.split(".").forEach(function(t){e.includes(t)||e.push(t)}),e},[]),b=s.className&&s.className.includes("token")?["token"]:[],m=s.className&&b.concat(s.className.filter(function(e){return!p.includes(e)}));d=mV({},s,{className:m1(m)||void 0,style:mQ(s.className,Object.assign({},s.style,i),n)})}else d=mV({},s,{className:m1(s.className)});var g=h(t.children);return l.createElement(c,mq({key:o},d),g)}}let m3=function(e,t){return -1!==e.listLanguages().indexOf(t)};var m4=/\n/g;function m5(e){return e.match(m4)}function m6(e){var t=e.lines,n=e.startingLineNumber,r=e.style;return t.map(function(e,t){var i=t+n;return l.createElement("span",{key:"line-".concat(t),className:"react-syntax-highlighter-line-number",style:"function"==typeof r?r(i):r},"".concat(i,"\n"))})}function m9(e){var t=e.codeString,n=e.codeStyle,r=e.containerStyle,i=void 0===r?{float:"left",paddingRight:"10px"}:r,a=e.numberStyle,o=void 0===a?{}:a,s=e.startingLineNumber;return l.createElement("code",{style:Object.assign({},n,i)},m6({lines:t.replace(/\n$/,"").split("\n"),style:o,startingLineNumber:s}))}function m8(e){return"".concat(e.toString().length,".25em")}function m7(e,t){return{type:"element",tagName:"span",properties:{key:"line-number--".concat(e),className:["comment","linenumber","react-syntax-highlighter-line-number"],style:t},children:[{type:"text",value:e}]}}function ge(e,t,n){var r,i={display:"inline-block",minWidth:m8(n),paddingRight:"1em",textAlign:"right",userSelect:"none"};return mV({},i,"function"==typeof e?e(t):e)}function gt(e){var t=e.children,n=e.lineNumber,r=e.lineNumberStyle,i=e.largestLineNumber,a=e.showInlineLineNumbers,o=e.lineProps,s=void 0===o?{}:o,u=e.className,c=void 0===u?[]:u,l=e.showLineNumbers,f=e.wrapLongLines,d="function"==typeof s?s(n):s;if(d.className=c,n&&a){var h=ge(r,n,i);t.unshift(m7(n,h))}return f&l&&(d.style=mV({},d.style,{display:"flex"})),{type:"element",tagName:"span",properties:d,children:t}}function gn(e){for(var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=0;r2&&void 0!==arguments[2]?arguments[2]:[];return gt({children:e,lineNumber:t,lineNumberStyle:s,largestLineNumber:o,showInlineLineNumbers:i,lineProps:n,className:a,showLineNumbers:r,wrapLongLines:u})}function b(e,t){if(r&&t&&i){var n=ge(s,t,o);e.unshift(m7(t,n))}return e}function m(e,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];return t||r.length>0?p(e,n,r):b(e,n)}for(var g=function(){var e=l[h],t=e.children[0].value;if(m5(t)){var n=t.split("\n");n.forEach(function(t,i){var o=r&&f.length+a,s={type:"text",value:"".concat(t,"\n")};if(0===i){var u=l.slice(d+1,h).concat(gt({children:[s],className:e.properties.className})),c=m(u,o);f.push(c)}else if(i===n.length-1){if(l[h+1]&&l[h+1].children&&l[h+1].children[0]){var p={type:"text",value:"".concat(t)},b=gt({children:[p],className:e.properties.className});l.splice(h+1,0,b)}else{var g=[s],v=m(g,o,e.properties.className);f.push(v)}}else{var y=[s],w=m(y,o,e.properties.className);f.push(w)}}),d=h}h++};h code[class*="language-"]':{background:"#f5f2f0",padding:".1em",borderRadius:".3em",whiteSpace:"normal"},comment:{color:"slategray"},prolog:{color:"slategray"},doctype:{color:"slategray"},cdata:{color:"slategray"},punctuation:{color:"#999"},namespace:{Opacity:".7"},property:{color:"#905"},tag:{color:"#905"},boolean:{color:"#905"},number:{color:"#905"},constant:{color:"#905"},symbol:{color:"#905"},deleted:{color:"#905"},selector:{color:"#690"},"attr-name":{color:"#690"},string:{color:"#690"},char:{color:"#690"},builtin:{color:"#690"},inserted:{color:"#690"},operator:{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},entity:{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)",cursor:"help"},url:{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},".language-css .token.string":{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},".style .token.string":{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},atrule:{color:"#07a"},"attr-value":{color:"#07a"},keyword:{color:"#07a"},function:{color:"#DD4A68"},"class-name":{color:"#DD4A68"},regex:{color:"#e90"},important:{color:"#e90",fontWeight:"bold"},variable:{color:"#e90"},bold:{fontWeight:"bold"},italic:{fontStyle:"italic"}};var gc=n(98695),gl=n.n(gc);let gf=["abap","abnf","actionscript","ada","agda","al","antlr4","apacheconf","apl","applescript","aql","arduino","arff","asciidoc","asm6502","aspnet","autohotkey","autoit","bash","basic","batch","bbcode","birb","bison","bnf","brainfuck","brightscript","bro","bsl","c","cil","clike","clojure","cmake","coffeescript","concurnas","cpp","crystal","csharp","csp","css-extras","css","cypher","d","dart","dax","dhall","diff","django","dns-zone-file","docker","ebnf","editorconfig","eiffel","ejs","elixir","elm","erb","erlang","etlua","excel-formula","factor","firestore-security-rules","flow","fortran","fsharp","ftl","gcode","gdscript","gedcom","gherkin","git","glsl","gml","go","graphql","groovy","haml","handlebars","haskell","haxe","hcl","hlsl","hpkp","hsts","http","ichigojam","icon","iecst","ignore","inform7","ini","io","j","java","javadoc","javadoclike","javascript","javastacktrace","jolie","jq","js-extras","js-templates","jsdoc","json","json5","jsonp","jsstacktrace","jsx","julia","keyman","kotlin","latex","latte","less","lilypond","liquid","lisp","livescript","llvm","lolcode","lua","makefile","markdown","markup-templating","markup","matlab","mel","mizar","mongodb","monkey","moonscript","n1ql","n4js","nand2tetris-hdl","naniscript","nasm","neon","nginx","nim","nix","nsis","objectivec","ocaml","opencl","oz","parigp","parser","pascal","pascaligo","pcaxis","peoplecode","perl","php-extras","php","phpdoc","plsql","powerquery","powershell","processing","prolog","properties","protobuf","pug","puppet","pure","purebasic","purescript","python","q","qml","qore","r","racket","reason","regex","renpy","rest","rip","roboconf","robotframework","ruby","rust","sas","sass","scala","scheme","scss","shell-session","smali","smalltalk","smarty","sml","solidity","solution-file","soy","sparql","splunk-spl","sqf","sql","stan","stylus","swift","t4-cs","t4-templating","t4-vb","tap","tcl","textile","toml","tsx","tt2","turtle","twig","typescript","typoscript","unrealscript","vala","vbnet","velocity","verilog","vhdl","vim","visual-basic","warpscript","wasm","wiki","xeora","xml-doc","xojo","xquery","yaml","yang","zig"];var gd=gs(gl(),gu);gd.supportedLanguages=gf;let gh=gd;var gp=n(64566),gb=n(68239);function gm(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function gg(){var e=gm(["\n query FetchConfigV2 {\n configv2 {\n user\n effective\n }\n }\n"]);return gg=function(){return e},e}var gv=function(){var e="[[TelemetryIngress.Endpoints]] \nNetwork = '...' # e.g. EVM. Solana, Starknet, Cosmos \nChainID = '...' # e.g. 1, 5, devnet, mainnet-beta URL\nURL = '...'\nServerPubKey = '...'";return l.createElement(r9.Z,null,l.createElement(sf.Z,{title:"Deprecation warning"}),l.createElement(aK.Z,null,l.createElement(x.default,{variant:"h5",gutterBottom:!0},"Starting in ",l.createElement("code",null,"v2.9.0"),":"),l.createElement(w.default,{dense:!0},l.createElement(_.default,null,l.createElement(ol.Z,null,l.createElement(gb.Z,null)),l.createElement(x.default,{variant:"subtitle2",gutterBottom:!0},l.createElement("code",null,"TelemetryIngress.URL")," and"," ",l.createElement("code",null,"TelemetryIngress.ServerPubKey")," will no longer be allowed. Please switch to ",l.createElement("code",null,"TelemetryIngress.Endpoints"),":",l.createElement(gh,{language:"toml",style:gu},e))),l.createElement(_.default,null,l.createElement(ol.Z,null,l.createElement(gb.Z,null)),l.createElement(x.default,{variant:"subtitle2",gutterBottom:!0},l.createElement("code",null,"P2P.V1")," will no longer be supported and must not be set in TOML configuration in order to boot. Use"," ",l.createElement("code",null,"P2P.V2")," instead. If you are using both,"," ",l.createElement("code",null,"V1")," can simply be removed.")))))},gy=n0(gg()),gw=function(e){var t=e.children;return l.createElement(ii.Z,null,l.createElement(ie.default,{component:"th",scope:"row",colSpan:3},t))},g_=function(){return l.createElement(gw,null,"...")},gE=function(e){var t=e.children;return l.createElement(gw,null,t)},gS=function(e){var t=e.loading,n=e.toml,r=e.error,i=void 0===r?"":r,a=e.title,o=e.expanded;if(i)return l.createElement(gE,null,i);if(t)return l.createElement(g_,null);a||(a="TOML");var s={display:"block"};return l.createElement(x.default,null,l.createElement(mR.Z,{defaultExpanded:o},l.createElement(mj.Z,{expandIcon:l.createElement(gp.Z,null)},a),l.createElement(mF.Z,{style:s},l.createElement(gh,{language:"toml",style:gu},n))))},gk=function(){var e=ry(gy,{fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error;return(null==t?void 0:t.configv2.effective)=="N/A"?l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12},l.createElement(r9.Z,null,l.createElement(sf.Z,{title:"TOML Configuration"}),l.createElement(gS,{title:"V2 config dump:",error:null==r?void 0:r.message,loading:n,toml:null==t?void 0:t.configv2.user,showHead:!0})))):l.createElement(l.Fragment,null,l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(gv,null)),l.createElement(d.Z,{item:!0,xs:12},l.createElement(r9.Z,null,l.createElement(sf.Z,{title:"TOML Configuration"}),l.createElement(gS,{title:"User specified:",error:null==r?void 0:r.message,loading:n,toml:null==t?void 0:t.configv2.user,showHead:!0,expanded:!0}),l.createElement(gS,{title:"Effective (with defaults):",error:null==r?void 0:r.message,loading:n,toml:null==t?void 0:t.configv2.effective,showHead:!0})))))},gx=n(34823),gT=function(e){return(0,b.createStyles)({cell:{paddingTop:1.5*e.spacing.unit,paddingBottom:1.5*e.spacing.unit}})},gM=(0,b.withStyles)(gT)(function(e){var t=e.classes,n=(0,A.I0)();(0,l.useEffect)(function(){n((0,ty.DQ)())});var r=(0,A.v9)(gx.N,A.wU);return l.createElement(r9.Z,null,l.createElement(sf.Z,{title:"Node"}),l.createElement(r8.Z,null,l.createElement(r7.Z,null,l.createElement(ii.Z,null,l.createElement(ie.default,{className:t.cell},l.createElement(x.default,null,"Version"),l.createElement(x.default,{variant:"subtitle1",color:"textSecondary"},r.version))),l.createElement(ii.Z,null,l.createElement(ie.default,{className:t.cell},l.createElement(x.default,null,"SHA"),l.createElement(x.default,{variant:"subtitle1",color:"textSecondary"},r.commitSHA))))))}),gO=function(){return l.createElement(iv,null,l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,sm:12,md:8},l.createElement(d.Z,{container:!0},l.createElement(gk,null))),l.createElement(d.Z,{item:!0,sm:12,md:4},l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(gM,null)),l.createElement(d.Z,{item:!0,xs:12},l.createElement(mP,null)),l.createElement(d.Z,{item:!0,xs:12},l.createElement(mS,null))))))},gA=function(){return l.createElement(gO,null)},gL=function(){return l.createElement(gA,null)},gC=n(44431),gI=1e18,gD=function(e){return new gC.BigNumber(e).dividedBy(gI).toFixed(8)},gN=function(e){var t=e.keys,n=e.chainID,r=e.hideHeaderTitle;return l.createElement(l.Fragment,null,l.createElement(sf.Z,{title:!r&&"Account Balances",subheader:"Chain ID "+n}),l.createElement(aK.Z,null,l.createElement(w.default,{dense:!1,disablePadding:!0},t&&t.map(function(e,r){return l.createElement(l.Fragment,null,l.createElement(_.default,{disableGutters:!0,key:["acc-balance",n.toString(),r.toString()].join("-")},l.createElement(E.Z,{primary:l.createElement(l.Fragment,null,l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12},l.createElement(ob,{title:"Address"}),l.createElement(om,{value:e.address})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(ob,{title:"Native Token Balance"}),l.createElement(om,{value:e.ethBalance||"--"})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(ob,{title:"LINK Balance"}),l.createElement(om,{value:e.linkBalance?gD(e.linkBalance):"--"}))))})),r+1s&&l.createElement(g$.Z,null,l.createElement(ii.Z,null,l.createElement(ie.default,{className:r.footer},l.createElement(aL.Z,{href:"/runs",component:tz},"View More"))))))});function vi(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function va(){var e=vi(["\n ","\n query FetchRecentJobRuns($offset: Int, $limit: Int) {\n jobRuns(offset: $offset, limit: $limit) {\n results {\n ...RecentJobRunsPayload_ResultsFields\n }\n metadata {\n total\n }\n }\n }\n"]);return va=function(){return e},e}var vo=5,vs=n0(va(),vt),vu=function(){var e=ry(vs,{variables:{offset:0,limit:vo},fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error;return l.createElement(vr,{data:t,errorMsg:null==r?void 0:r.message,loading:n,maxRunsSize:vo})},vc=function(e){return(0,b.createStyles)({style:{textAlign:"center",padding:2.5*e.spacing.unit,position:"fixed",left:"0",bottom:"0",width:"100%",borderRadius:0},bareAnchor:{color:e.palette.common.black,textDecoration:"none"}})},vl=(0,b.withStyles)(vc)(function(e){var t=e.classes,n=(0,A.v9)(gx.N,A.wU),r=(0,A.I0)();return(0,l.useEffect)(function(){r((0,ty.DQ)())}),l.createElement(ia.default,{className:t.style},l.createElement(x.default,null,"Chainlink Node ",n.version," at commit"," ",l.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://github.com/smartcontractkit/chainlink/commit/".concat(n.commitSHA),className:t.bareAnchor},n.commitSHA)))}),vf=function(e){return(0,b.createStyles)({cell:{borderColor:e.palette.divider,borderTop:"1px solid",borderBottom:"none",paddingTop:2*e.spacing.unit,paddingBottom:2*e.spacing.unit,paddingLeft:2*e.spacing.unit},block:{display:"block"},overflowEllipsis:{textOverflow:"ellipsis",overflow:"hidden"}})},vd=(0,b.withStyles)(vf)(function(e){var t=e.classes,n=e.job;return l.createElement(ii.Z,null,l.createElement(ie.default,{scope:"row",className:t.cell},l.createElement(d.Z,{container:!0,spacing:0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(ip,{href:"/jobs/".concat(n.id),classes:{linkContent:t.block}},l.createElement(x.default,{className:t.overflowEllipsis,variant:"body1",component:"span",color:"primary"},n.name||n.id))),l.createElement(d.Z,{item:!0,xs:12},l.createElement(x.default,{variant:"body1",color:"textSecondary"},"Created ",l.createElement(aA,{tooltip:!0},n.createdAt))))))});function vh(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function vp(){var e=vh(["\n fragment RecentJobsPayload_ResultsFields on Job {\n id\n name\n createdAt\n }\n"]);return vp=function(){return e},e}var vb=n0(vp()),vm=function(){return(0,b.createStyles)({cardHeader:{borderBottom:0},table:{tableLayout:"fixed"}})},vg=(0,b.withStyles)(vm)(function(e){var t,n,r=e.classes,i=e.data,a=e.errorMsg,o=e.loading;return l.createElement(r9.Z,null,l.createElement(sf.Z,{title:"Recent Jobs",className:r.cardHeader}),l.createElement(r8.Z,{className:r.table},l.createElement(r7.Z,null,l.createElement(gW,{visible:o}),l.createElement(gK,{visible:(null===(t=null==i?void 0:i.jobs.results)||void 0===t?void 0:t.length)===0},"No recently created jobs"),l.createElement(gz,{msg:a}),null===(n=null==i?void 0:i.jobs.results)||void 0===n?void 0:n.map(function(e,t){return l.createElement(vd,{job:e,key:t})}))))});function vv(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function vy(){var e=vv(["\n ","\n query FetchRecentJobs($offset: Int, $limit: Int) {\n jobs(offset: $offset, limit: $limit) {\n results {\n ...RecentJobsPayload_ResultsFields\n }\n }\n }\n"]);return vy=function(){return e},e}var vw=5,v_=n0(vy(),vb),vE=function(){var e=ry(v_,{variables:{offset:0,limit:vw},fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error;return l.createElement(vg,{data:t,errorMsg:null==r?void 0:r.message,loading:n})},vS=function(){return l.createElement(iv,null,l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:8},l.createElement(vu,null)),l.createElement(d.Z,{item:!0,xs:4},l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(gH,null)),l.createElement(d.Z,{item:!0,xs:12},l.createElement(vE,null))))),l.createElement(vl,null))},vk=function(){return l.createElement(vS,null)},vx=function(){return l.createElement(vk,null)},vT=n(87239),vM=function(e){switch(e){case"DirectRequestSpec":return"Direct Request";case"FluxMonitorSpec":return"Flux Monitor";default:return e.replace(/Spec$/,"")}},vO=n(5022),vA=n(78718),vL=n.n(vA);function vC(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n1?t-1:0),r=1;r1?t-1:0),r=1;re.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&n.map(function(e){return l.createElement(ii.Z,{key:e.id,style:{cursor:"pointer"},onClick:function(){return r.push("/runs/".concat(e.id))}},l.createElement(ie.default,{className:t.idCell,scope:"row"},l.createElement("div",{className:t.runDetails},l.createElement(x.default,{variant:"h5",color:"primary",component:"span"},e.id))),l.createElement(ie.default,{className:t.stampCell},l.createElement(x.default,{variant:"body1",color:"textSecondary",className:t.stamp},"Created ",l.createElement(aA,{tooltip:!0},e.createdAt))),l.createElement(ie.default,{className:t.statusCell,scope:"row"},l.createElement(x.default,{variant:"body1",className:O()(t.status,ym(t,e.status))},e.status.toLowerCase())))})))}),yv=n(16839),yy=n.n(yv);function yw(e){var t=e.replace(/\w+\s*=\s*<([^>]|[\r\n])*>/g,""),n=yy().read(t),r=n.edges();return n.nodes().map(function(e){var t={id:e,parentIds:r.filter(function(t){return t.w===e}).map(function(e){return e.v})};return Object.keys(n.node(e)).length>0&&(t.attributes=n.node(e)),t})}var y_=n(94164),yE=function(e){var t=e.data,n=[];return(null==t?void 0:t.attributes)&&Object.keys(t.attributes).forEach(function(e){var r;n.push(l.createElement("div",{key:e},l.createElement(x.default,{variant:"body1",color:"textSecondary",component:"div"},l.createElement("b",null,e,":")," ",null===(r=t.attributes)||void 0===r?void 0:r[e])))}),l.createElement("div",null,t&&l.createElement(x.default,{variant:"body1",color:"textPrimary"},l.createElement("b",null,t.id)),n)},yS=n(73343),yk=n(3379),yx=n.n(yk);function yT(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);nwindow.innerWidth?u-r.getBoundingClientRect().width-a:u+a,n=c+r.getBoundingClientRect().height+i>window.innerHeight?c-r.getBoundingClientRect().height-a:c+a,r.style.opacity=String(1),r.style.top="".concat(n,"px"),r.style.left="".concat(t,"px"),r.style.zIndex=String(1)}},h=function(e){var t=document.getElementById("tooltip-d3-chart-".concat(e));t&&(t.style.opacity=String(0),t.style.zIndex=String(-1))};return l.createElement("div",{style:{fontFamily:"sans-serif",fontWeight:"normal"}},l.createElement(y_.kJ,{id:"task-list-graph-d3",data:i,config:s,onMouseOverNode:d,onMouseOutNode:h},"D3 chart"),n.map(function(e){return l.createElement("div",{key:"d3-tooltip-key-".concat(e.id),id:"tooltip-d3-chart-".concat(e.id),style:{position:"absolute",opacity:"0",border:"1px solid rgba(0, 0, 0, 0.1)",padding:yS.r.spacing.unit,background:"white",borderRadius:5,zIndex:-1,inlineSize:"min-content"}},l.createElement(yE,{data:e}))}))};function yD(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);nyH&&l.createElement("div",{className:t.runDetails},l.createElement(aL.Z,{href:"/jobs/".concat(n.id,"/runs"),component:tz},"View more")))),l.createElement(d.Z,{item:!0,xs:12,sm:6},l.createElement(yU,{observationSource:n.observationSource})))});function yG(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:"";try{return vO.parse(e),!0}catch(t){return!1}})}),wq=function(e){var t=e.initialValues,n=e.onSubmit,r=e.onTOMLChange;return l.createElement(hM,{initialValues:t,validationSchema:wV,onSubmit:n},function(e){var t=e.isSubmitting,n=e.values;return r&&r(n.toml),l.createElement(hj,{"data-testid":"job-form",noValidate:!0},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12},l.createElement(hR,{component:hJ,id:"toml",name:"toml",label:"Job Spec (TOML)",required:!0,fullWidth:!0,multiline:!0,rows:10,rowsMax:25,variant:"outlined",autoComplete:"off",FormHelperTextProps:{"data-testid":"toml-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(ox.default,{variant:"contained",color:"primary",type:"submit",disabled:t,size:"large"},"Create Job"))))})},wZ=n(50109),wX="persistSpec";function wJ(e){var t=e.query,n=new URLSearchParams(t).get("definition");return n?(wZ.t8(wX,n),{toml:n}):{toml:wZ.U2(wX)||""}}var wQ=function(e){var t=e.onSubmit,n=e.onTOMLChange,r=wJ({query:(0,h.TH)().search}),i=function(e){var t=e.replace(/[\u200B-\u200D\uFEFF]/g,"");wZ.t8("".concat(wX),t),n&&n(t)};return l.createElement(r9.Z,null,l.createElement(sf.Z,{title:"New Job"}),l.createElement(aK.Z,null,l.createElement(wq,{initialValues:r,onSubmit:t,onTOMLChange:i})))};function w1(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n1&&void 0!==arguments[1]?arguments[1]:{},n=t.start,r=void 0===n?6:n,i=t.end,a=void 0===i?4:i;return e.substring(0,r)+"..."+e.substring(e.length-a)}function _L(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return ry(_q,e)},_X=function(){var e=_Z({fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error,i=e.refetch;return l.createElement(_z,{loading:n,data:t,errorMsg:null==r?void 0:r.message,refetch:i})},_J=function(e){var t=e.csaKey;return l.createElement(ii.Z,{hover:!0},l.createElement(ie.default,null,l.createElement(x.default,{variant:"body1"},t.publicKey," ",l.createElement(_O,{data:t.publicKey}))))};function _Q(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function _1(){var e=_Q(["\n fragment CSAKeysPayload_ResultsFields on CSAKey {\n id\n publicKey\n }\n"]);return _1=function(){return e},e}var _0=n0(_1()),_2=function(e){var t,n,r,i=e.data,a=e.errorMsg,o=e.loading,s=e.onCreate;return l.createElement(r9.Z,null,l.createElement(sf.Z,{action:(null===(t=null==i?void 0:i.csaKeys.results)||void 0===t?void 0:t.length)===0&&l.createElement(ox.default,{variant:"outlined",color:"primary",onClick:s},"New CSA Key"),title:"CSA Key",subheader:"Manage your CSA Key"}),l.createElement(r8.Z,null,l.createElement(it.Z,null,l.createElement(ii.Z,null,l.createElement(ie.default,null,"Public Key"))),l.createElement(r7.Z,null,l.createElement(gW,{visible:o}),l.createElement(gK,{visible:(null===(n=null==i?void 0:i.csaKeys.results)||void 0===n?void 0:n.length)===0}),l.createElement(gz,{msg:a}),null===(r=null==i?void 0:i.csaKeys.results)||void 0===r?void 0:r.map(function(e,t){return l.createElement(_J,{csaKey:e,key:t})}))))};function _3(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return ry(EL,e)};function EI(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return ry(E0,e)},E6=function(){return os(E2)},E9=function(){return os(E3)},E8=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return ry(E4,e)};function E7(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return ry(SZ,e)};function SJ(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function kX(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}var kJ=function(e){var t=e.run,n=l.useMemo(function(){var e=t.inputs,n=t.outputs,r=t.taskRuns,i=kZ(t,["inputs","outputs","taskRuns"]),a={};try{a=JSON.parse(e)}catch(o){a={}}return kq(kK({},i),{inputs:a,outputs:n,taskRuns:r})},[t]);return l.createElement(r9.Z,null,l.createElement(aK.Z,null,l.createElement(kG,{object:n})))};function kQ(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function k1(e){for(var t=1;t0&&l.createElement(ko,{errors:t.allErrors})),l.createElement(d.Z,{item:!0,xs:12},l.createElement(h.rs,null,l.createElement(h.AW,{path:"".concat(n,"/json")},l.createElement(kJ,{run:t})),l.createElement(h.AW,{path:n},t.taskRuns.length>0&&l.createElement(kj,{taskRuns:t.taskRuns,observationSource:t.job.observationSource}))))))))};function k7(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xe(){var e=k7(["\n ","\n query FetchJobRun($id: ID!) {\n jobRun(id: $id) {\n __typename\n ... on JobRun {\n ...JobRunPayload_Fields\n }\n ... on NotFoundError {\n message\n }\n }\n }\n"]);return xe=function(){return e},e}var xt=n0(xe(),k9),xn=function(){var e=ry(xt,{variables:{id:(0,h.UO)().id}}),t=e.data,n=e.loading,r=e.error;if(n)return l.createElement(ij,null);if(r)return l.createElement(iN,{error:r});var i=null==t?void 0:t.jobRun;switch(null==i?void 0:i.__typename){case"JobRun":return l.createElement(k8,{run:i});case"NotFoundError":return l.createElement(oo,null);default:return null}};function xr(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xi(){var e=xr(["\n fragment JobRunsPayload_ResultsFields on JobRun {\n id\n allErrors\n createdAt\n finishedAt\n status\n job {\n id\n }\n }\n"]);return xi=function(){return e},e}var xa=n0(xi()),xo=function(e){var t=e.loading,n=e.data,r=e.page,i=e.pageSize,a=(0,h.k6)(),o=l.useMemo(function(){return null==n?void 0:n.jobRuns.results.map(function(e){var t,n=e.allErrors,r=e.id,i=e.createdAt;return{id:r,createdAt:i,errors:n,finishedAt:e.finishedAt,status:e.status}})},[n]);return l.createElement(iv,null,l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:12},l.createElement(iw,null,"Job Runs")),t&&l.createElement(ij,null),n&&o&&l.createElement(d.Z,{item:!0,xs:12},l.createElement(r9.Z,null,l.createElement(yg,{runs:o}),l.createElement(ir.Z,{component:"div",count:n.jobRuns.metadata.total,rowsPerPage:i,rowsPerPageOptions:[i],page:r-1,onChangePage:function(e,t){a.push("/runs?page=".concat(t+1,"&per=").concat(i))},onChangeRowsPerPage:function(){},backIconButtonProps:{"aria-label":"prev-page"},nextIconButtonProps:{"aria-label":"next-page"}})))))};function xs(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xu(){var e=xs(["\n ","\n query FetchJobRuns($offset: Int, $limit: Int) {\n jobRuns(offset: $offset, limit: $limit) {\n results {\n ...JobRunsPayload_ResultsFields\n }\n metadata {\n total\n }\n }\n }\n"]);return xu=function(){return e},e}var xc=n0(xu(),xa),xl=function(){var e=iF(),t=parseInt(e.get("page")||"1",10),n=parseInt(e.get("per")||"25",10),r=ry(xc,{variables:{offset:(t-1)*n,limit:n},fetchPolicy:"cache-and-network"}),i=r.data,a=r.loading,o=r.error;return o?l.createElement(iN,{error:o}):l.createElement(xo,{loading:a,data:i,page:t,pageSize:n})},xf=function(){var e=(0,h.$B)().path;return l.createElement(h.rs,null,l.createElement(h.AW,{exact:!0,path:e},l.createElement(xl,null)),l.createElement(h.AW,{path:"".concat(e,"/:id")},l.createElement(xn,null)))},xd=by().shape({name:p2().required("Required"),uri:p2().required("Required"),publicKey:p2().required("Required")}),xh=function(e){var t=e.initialValues,n=e.onSubmit;return l.createElement(hM,{initialValues:t,validationSchema:xd,onSubmit:n},function(e){var t=e.isSubmitting,n=e.submitForm;return l.createElement(hj,{"data-testid":"feeds-manager-form"},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hR,{component:hJ,id:"name",name:"name",label:"Name",required:!0,fullWidth:!0,FormHelperTextProps:{"data-testid":"name-helper-text"}})),l.createElement(d.Z,{item:!0,xs:!1,md:6}),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hR,{component:hJ,id:"uri",name:"uri",label:"URI",required:!0,fullWidth:!0,helperText:"Provided by the Feeds Manager operator",FormHelperTextProps:{"data-testid":"uri-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hR,{component:hJ,id:"publicKey",name:"publicKey",label:"Public Key",required:!0,fullWidth:!0,helperText:"Provided by the Feeds Manager operator",FormHelperTextProps:{"data-testid":"publicKey-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12},l.createElement(ox.default,{variant:"contained",color:"primary",disabled:t,onClick:n},"Submit"))))})},xp=function(e){var t=e.data,n=e.onSubmit,r={name:t.name,uri:t.uri,publicKey:t.publicKey};return l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12,md:11,lg:9},l.createElement(r9.Z,null,l.createElement(sf.Z,{title:"Edit Feeds Manager"}),l.createElement(aK.Z,null,l.createElement(xh,{initialValues:r,onSubmit:n})))))};function xb(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xm(){var e=xb(["\n query FetchFeedsManagers {\n feedsManagers {\n results {\n __typename\n id\n name\n uri\n publicKey\n isConnectionActive\n createdAt\n }\n }\n }\n"]);return xm=function(){return e},e}var xg=n0(xm()),xv=function(){return ry(xg)};function xy(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return ry(xJ,e)};function x1(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0?n.feedsManagers.results[0]:void 0;return n&&a?l.createElement(Tz,{manager:a}):l.createElement(h.l_,{to:{pathname:"/feeds_manager/new",state:{from:e}}})},TW={name:"Chainlink Feeds Manager",uri:"",publicKey:""},TK=function(e){var t=e.onSubmit;return l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12,md:11,lg:9},l.createElement(r9.Z,null,l.createElement(sf.Z,{title:"Register Feeds Manager"}),l.createElement(aK.Z,null,l.createElement(xh,{initialValues:TW,onSubmit:t})))))};function TV(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);nt.version?e:t})},[o]),g=l.useMemo(function(){return Mm(o).sort(function(e,t){return t.version-e.version})},[o]),v=function(e,t,n){switch(e){case"PENDING":return l.createElement(l.Fragment,null,l.createElement(ox.default,{variant:"text",color:"secondary",onClick:function(){return b("reject",t)}},"Reject"),m.id===t&&"DELETED"!==n.status&&"REVOKED"!==n.status&&l.createElement(ox.default,{variant:"contained",color:"primary",onClick:function(){return b("approve",t)}},"Approve"),m.id===t&&"DELETED"===n.status&&n.pendingUpdate&&l.createElement(l.Fragment,null,l.createElement(ox.default,{variant:"contained",color:"primary",onClick:function(){return b("cancel",t)}},"Cancel"),l.createElement(x.default,{color:"error"},"This proposal was deleted. Cancel the spec to delete any running jobs")));case"APPROVED":return l.createElement(l.Fragment,null,l.createElement(ox.default,{variant:"contained",onClick:function(){return b("cancel",t)}},"Cancel"),"DELETED"===n.status&&n.pendingUpdate&&l.createElement(x.default,{color:"error"},"This proposal was deleted. Cancel the spec to delete any running jobs"));case"CANCELLED":if(m.id===t&&"DELETED"!==n.status&&"REVOKED"!==n.status)return l.createElement(ox.default,{variant:"contained",color:"primary",onClick:function(){return b("approve",t)}},"Approve");return null;default:return null}};return l.createElement("div",null,g.map(function(e,n){return l.createElement(mR.Z,{defaultExpanded:0===n,key:n},l.createElement(mj.Z,{expandIcon:l.createElement(gp.Z,null)},l.createElement(x.default,{className:t.versionText},"Version ",e.version),l.createElement(El.Z,{label:e.status,color:"APPROVED"===e.status?"primary":"default",variant:"REJECTED"===e.status||"CANCELLED"===e.status?"outlined":"default"}),l.createElement("div",{className:t.proposedAtContainer},l.createElement(x.default,null,"Proposed ",l.createElement(aA,{tooltip:!0},e.createdAt)))),l.createElement(mF.Z,{className:t.expansionPanelDetails},l.createElement("div",{className:t.actions},l.createElement("div",{className:t.editContainer},0===n&&("PENDING"===e.status||"CANCELLED"===e.status)&&"DELETED"!==s.status&&"REVOKED"!==s.status&&l.createElement(ox.default,{variant:"contained",onClick:function(){return p(!0)}},"Edit")),l.createElement("div",{className:t.actionsContainer},v(e.status,e.id,s))),l.createElement(gh,{language:"toml",style:gu,"data-testid":"codeblock"},e.definition)))}),l.createElement(oI,{open:null!=c,title:c?M_[c.action].title:"",body:c?M_[c.action].body:"",onConfirm:function(){if(c){switch(c.action){case"approve":n(c.id);break;case"cancel":r(c.id);break;case"reject":i(c.id)}f(null)}},cancelButtonText:"Cancel",onCancel:function(){return f(null)}}),l.createElement(Mo,{open:h,onClose:function(){return p(!1)},initialValues:{definition:m.definition,id:m.id},onSubmit:a}))});function MS(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function Mk(){var e=MS(["\n ","\n fragment JobProposalPayloadFields on JobProposal {\n id\n externalJobID\n remoteUUID\n jobID\n specs {\n ...JobProposal_SpecsFields\n }\n status\n pendingUpdate\n }\n"]);return Mk=function(){return e},e}var Mx=n0(Mk(),My),MT=function(e){var t=e.onApprove,n=e.onCancel,r=e.onReject,i=e.onUpdateSpec,a=e.proposal;return l.createElement(iv,null,l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:9},l.createElement(iw,null,"Job Proposal #",a.id))),l.createElement(Me,{proposal:a}),l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:9},l.createElement(T$,null,"Specs"))),l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:12},l.createElement(ME,{proposal:a,specs:a.specs,onReject:r,onApprove:t,onCancel:n,onUpdateSpec:i}))))};function MM(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);nU,tA:()=>$,KL:()=>H,Iw:()=>V,DQ:()=>W,cB:()=>T,LO:()=>M,t5:()=>k,qt:()=>x,Jc:()=>C,L7:()=>Y,EO:()=>B});var r,i,a=n(66289),o=n(41800),s=n.n(o),u=n(67932);(i=r||(r={})).IN_PROGRESS="in_progress",i.PENDING_INCOMING_CONFIRMATIONS="pending_incoming_confirmations",i.PENDING_CONNECTION="pending_connection",i.PENDING_BRIDGE="pending_bridge",i.PENDING_SLEEP="pending_sleep",i.ERRORED="errored",i.COMPLETED="completed";var c=n(87013),l=n(19084),f=n(34823);function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]j,v2:()=>F});var r=n(66289);function i(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var a="/sessions",o="/sessions",s=function e(t){var n=this;i(this,e),this.api=t,this.createSession=function(e){return n.create(e)},this.destroySession=function(){return n.destroy()},this.create=this.api.createResource(a),this.destroy=this.api.deleteResource(o)};function u(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var c="/v2/bulk_delete_runs",l=function e(t){var n=this;u(this,e),this.api=t,this.bulkDeleteJobRuns=function(e){return n.destroy(e)},this.destroy=this.api.deleteResource(c)};function f(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var d="/v2/chains/evm",h="".concat(d,"/:id"),p=function e(t){var n=this;f(this,e),this.api=t,this.getChains=function(){return n.index()},this.createChain=function(e){return n.create(e)},this.destroyChain=function(e){return n.destroy(void 0,{id:e})},this.updateChain=function(e,t){return n.update(t,{id:e})},this.index=this.api.fetchResource(d),this.create=this.api.createResource(d),this.destroy=this.api.deleteResource(h),this.update=this.api.updateResource(h)};function b(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var m="/v2/keys/evm/chain",g=function e(t){var n=this;b(this,e),this.api=t,this.chain=function(e){var t=new URLSearchParams;t.append("address",e.address),t.append("evmChainID",e.evmChainID),null!==e.nextNonce&&t.append("nextNonce",e.nextNonce),null!==e.abandon&&t.append("abandon",String(e.abandon)),null!==e.enabled&&t.append("enabled",String(e.enabled));var r=m+"?"+t.toString();return n.api.createResource(r)()}};function v(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var y="/v2/jobs",w="".concat(y,"/:specId/runs"),_=function e(t){var n=this;v(this,e),this.api=t,this.createJobRunV2=function(e,t){return n.post(t,{specId:e})},this.post=this.api.createResource(w,!0)};function E(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var S="/v2/log",k=function e(t){var n=this;E(this,e),this.api=t,this.getLogConfig=function(){return n.show()},this.updateLogConfig=function(e){return n.update(e)},this.show=this.api.fetchResource(S),this.update=this.api.updateResource(S)};function x(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var T="/v2/nodes",M=function e(t){var n=this;x(this,e),this.api=t,this.getNodes=function(){return n.index()},this.createNode=function(e){return n.create(e)},this.index=this.api.fetchResource(T),this.create=this.api.createResource(T)};function O(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var A="/v2/enroll_webauthn",L=function e(t){var n=this;O(this,e),this.api=t,this.beginKeyRegistration=function(e){return n.create(e)},this.finishKeyRegistration=function(e){return n.put(e)},this.create=this.api.fetchResource(A),this.put=this.api.createResource(A)};function C(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var I="/v2/build_info",D=function e(t){var n=this;C(this,e),this.api=t,this.show=function(){return n.api.GET(I)()}};function N(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var P=function e(t){N(this,e),this.api=t,this.buildInfo=new D(this.api),this.bulkDeleteRuns=new l(this.api),this.chains=new p(this.api),this.logConfig=new k(this.api),this.nodes=new M(this.api),this.jobs=new _(this.api),this.webauthn=new L(this.api),this.evmKeys=new g(this.api)},R=new r.V0({base:void 0}),j=new s(R),F=new P(R)},1398(e,t,n){"use strict";n.d(t,{Z:()=>d});var r=n(67294),i=n(32316),a=n(83638),o=n(94184),s=n.n(o);function u(){return(u=Object.assign||function(e){for(var t=1;tc});var r=n(67294),i=n(32316);function a(){return(a=Object.assign||function(e){for(var t=1;tx,jK:()=>v});var r=n(67294),i=n(55977),a=n(45697),o=n.n(a),s=n(82204),u=n(71426),c=n(94184),l=n.n(c),f=n(32316),d=function(e){var t=e.palette.success||{},n=e.palette.warning||{};return{base:{paddingLeft:5*e.spacing.unit,paddingRight:5*e.spacing.unit},success:{backgroundColor:t.main,color:t.contrastText},error:{backgroundColor:e.palette.error.dark,color:e.palette.error.contrastText},warning:{backgroundColor:n.contrastText,color:n.main}}},h=function(e){var t,n=e.success,r=e.error,i=e.warning,a=e.classes,o=e.className;return n?t=a.success:r?t=a.error:i&&(t=a.warning),l()(a.base,o,t)},p=function(e){return r.createElement(s.Z,{className:h(e),square:!0},r.createElement(u.default,{variant:"body2",color:"inherit",component:"div"},e.children))};p.defaultProps={success:!1,error:!1,warning:!1},p.propTypes={success:o().bool,error:o().bool,warning:o().bool};let b=(0,f.withStyles)(d)(p);var m=function(){return r.createElement(r.Fragment,null,"Unhandled error. Please help us by opening a"," ",r.createElement("a",{href:"https://github.com/smartcontractkit/chainlink/issues/new"},"bug report"))};let g=m;function v(e){return"string"==typeof e?e:e.component?e.component(e.props):r.createElement(g,null)}function y(e,t){var n;return n="string"==typeof e?e:e.component?e.component(e.props):r.createElement(g,null),r.createElement("p",{key:t},n)}var w=function(e){var t=e.notifications;return r.createElement(b,{error:!0},t.map(y))},_=function(e){var t=e.notifications;return r.createElement(b,{success:!0},t.map(y))},E=function(e){var t=e.errors,n=e.successes;return r.createElement("div",null,(null==t?void 0:t.length)>0&&r.createElement(w,{notifications:t}),n.length>0&&r.createElement(_,{notifications:n}))},S=function(e){return{errors:e.notifications.errors,successes:e.notifications.successes}},k=(0,i.$j)(S)(E);let x=k},9409(e,t,n){"use strict";n.d(t,{ZP:()=>j});var r=n(67294),i=n(55977),a=n(47886),o=n(32316),s=n(1398),u=n(82204),c=n(30060),l=n(71426),f=n(60520),d=n(97779),h=n(57209),p=n(26842),b=n(3950),m=n(5536),g=n(45697),v=n.n(g);let y=n.p+"9f6d832ef97e8493764e.svg";function w(){return(w=Object.assign||function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&_.map(function(e,t){return r.createElement(d.Z,{item:!0,xs:12,key:t},r.createElement(u.Z,{raised:!1,className:v.error},r.createElement(c.Z,null,r.createElement(l.default,{variant:"body1",className:v.errorText},(0,b.jK)(e)))))}),r.createElement(d.Z,{item:!0,xs:12},r.createElement(f.Z,{id:"email",label:"Email",margin:"normal",value:n,onChange:m("email"),error:_.length>0,variant:"outlined",fullWidth:!0})),r.createElement(d.Z,{item:!0,xs:12},r.createElement(f.Z,{id:"password",label:"Password",type:"password",autoComplete:"password",margin:"normal",value:h,onChange:m("password"),error:_.length>0,variant:"outlined",fullWidth:!0})),r.createElement(d.Z,{item:!0,xs:12},r.createElement(d.Z,{container:!0,spacing:0,justify:"center"},r.createElement(d.Z,{item:!0},r.createElement(s.Z,{type:"submit",variant:"primary"},"Access Account")))),y&&r.createElement(l.default,{variant:"body1",color:"textSecondary"},"Signing in...")))))))},P=function(e){return{fetching:e.authentication.fetching,authenticated:e.authentication.allowed,errors:e.notifications.errors}},R=(0,i.$j)(P,x({submitSignIn:p.L7}))(N);let j=(0,h.wU)(e)((0,o.withStyles)(D)(R))},16353(e,t,n){"use strict";n.d(t,{ZP:()=>H,rH:()=>U});var r,i=n(55977),a=n(15857),o=n(9541),s=n(19084);function u(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function c(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:h,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case s.Mk.RECEIVE_SIGNOUT_SUCCESS:case s.Mk.RECEIVE_SIGNIN_SUCCESS:var n={allowed:t.authenticated};return o.Ks(n),f(c({},e,n),{errors:[]});case s.Mk.RECEIVE_SIGNIN_FAIL:var r={allowed:!1};return o.Ks(r),f(c({},e,r),{errors:[]});case s.Mk.RECEIVE_SIGNIN_ERROR:case s.Mk.RECEIVE_SIGNOUT_ERROR:var i={allowed:!1};return o.Ks(i),f(c({},e,i),{errors:t.errors||[]});default:return e}};let b=p;function m(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function g(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:_,t=arguments.length>1?arguments[1]:void 0;return t.type?t.type.startsWith(r.REQUEST)?y(g({},e),{count:e.count+1}):t.type.startsWith(r.RECEIVE)?y(g({},e),{count:Math.max(e.count-1,0)}):t.type.startsWith(r.RESPONSE)?y(g({},e),{count:Math.max(e.count-1,0)}):t.type===s.di.REDIRECT?y(g({},e),{count:0}):e:e};let S=E;function k(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function x(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:O,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case s.di.MATCH_ROUTE:return M(x({},O),{currentUrl:t.pathname});case s.Ih.NOTIFY_SUCCESS:var n={component:t.component,props:t.props};return M(x({},e),{successes:[n],errors:[]});case s.Ih.NOTIFY_SUCCESS_MSG:return M(x({},e),{successes:[t.msg],errors:[]});case s.Ih.NOTIFY_ERROR:var r=t.error.errors,i=null==r?void 0:r.map(function(e){return L(t,e)});return M(x({},e),{successes:[],errors:i});case s.Ih.NOTIFY_ERROR_MSG:return M(x({},e),{successes:[],errors:[t.msg]});case s.Mk.RECEIVE_SIGNIN_FAIL:return M(x({},e),{successes:[],errors:["Your email or password is incorrect. Please try again"]});default:return e}};function L(e,t){return{component:e.component,props:{msg:t.detail}}}let C=A;function I(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function D(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:R,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case s.di.REDIRECT:return P(D({},e),{to:t.to});case s.di.MATCH_ROUTE:return P(D({},e),{to:void 0});default:return e}};let F=j;var Y=n(87013),B=(0,a.UY)({authentication:b,fetching:S,notifications:C,redirect:F,buildInfo:Y.Z});B(void 0,{type:"INITIAL_STATE"});var U=i.v9;let H=B},19084(e,t,n){"use strict";var r,i,a,o,s,u,c,l,f,d;n.d(t,{Ih:()=>i,Mk:()=>a,Y0:()=>s,di:()=>r,jp:()=>o}),n(67294),(u=r||(r={})).REDIRECT="REDIRECT",u.MATCH_ROUTE="MATCH_ROUTE",(c=i||(i={})).NOTIFY_SUCCESS="NOTIFY_SUCCESS",c.NOTIFY_SUCCESS_MSG="NOTIFY_SUCCESS_MSG",c.NOTIFY_ERROR="NOTIFY_ERROR",c.NOTIFY_ERROR_MSG="NOTIFY_ERROR_MSG",(l=a||(a={})).REQUEST_SIGNIN="REQUEST_SIGNIN",l.RECEIVE_SIGNIN_SUCCESS="RECEIVE_SIGNIN_SUCCESS",l.RECEIVE_SIGNIN_FAIL="RECEIVE_SIGNIN_FAIL",l.RECEIVE_SIGNIN_ERROR="RECEIVE_SIGNIN_ERROR",l.RECEIVE_SIGNOUT_SUCCESS="RECEIVE_SIGNOUT_SUCCESS",l.RECEIVE_SIGNOUT_ERROR="RECEIVE_SIGNOUT_ERROR",(f=o||(o={})).RECEIVE_CREATE_ERROR="RECEIVE_CREATE_ERROR",f.RECEIVE_CREATE_SUCCESS="RECEIVE_CREATE_SUCCESS",f.RECEIVE_DELETE_ERROR="RECEIVE_DELETE_ERROR",f.RECEIVE_DELETE_SUCCESS="RECEIVE_DELETE_SUCCESS",f.RECEIVE_UPDATE_ERROR="RECEIVE_UPDATE_ERROR",f.RECEIVE_UPDATE_SUCCESS="RECEIVE_UPDATE_SUCCESS",f.REQUEST_CREATE="REQUEST_CREATE",f.REQUEST_DELETE="REQUEST_DELETE",f.REQUEST_UPDATE="REQUEST_UPDATE",f.UPSERT_CONFIGURATION="UPSERT_CONFIGURATION",f.UPSERT_JOB_RUN="UPSERT_JOB_RUN",f.UPSERT_JOB_RUNS="UPSERT_JOB_RUNS",f.UPSERT_TRANSACTION="UPSERT_TRANSACTION",f.UPSERT_TRANSACTIONS="UPSERT_TRANSACTIONS",f.UPSERT_BUILD_INFO="UPSERT_BUILD_INFO",(d=s||(s={})).FETCH_BUILD_INFO_REQUESTED="FETCH_BUILD_INFO_REQUESTED",d.FETCH_BUILD_INFO_SUCCEEDED="FETCH_BUILD_INFO_SUCCEEDED",d.FETCH_BUILD_INFO_FAILED="FETCH_BUILD_INFO_FAILED"},87013(e,t,n){"use strict";n.d(t,{Y:()=>o,Z:()=>u});var r=n(19084);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:o,t=arguments.length>1?arguments[1]:void 0;return t.type===r.Y0.FETCH_BUILD_INFO_SUCCEEDED?a({},t.buildInfo):e};let u=s},34823(e,t,n){"use strict";n.d(t,{N:()=>r});var r=function(e){return e.buildInfo}},73343(e,t,n){"use strict";n.d(t,{r:()=>u});var r=n(19350),i=n(32316),a=n(59114),o=n(5324),s={props:{MuiGrid:{spacing:3*o.default.unit},MuiCardHeader:{titleTypographyProps:{color:"secondary"}}},palette:{action:{hoverOpacity:.3},primary:{light:"#E5F1FF",main:"#3c40c6",contrastText:"#fff"},secondary:{main:"#3d5170"},success:{light:"#e8faf1",main:r.ek.A700,dark:r.ek[700],contrastText:r.y0.white},warning:{light:"#FFFBF1",main:"#fff6b6",contrastText:"#fad27a"},error:{light:"#ffdada",main:"#f44336",dark:"#d32f2f",contrastText:"#fff"},background:{default:"#f5f6f8",appBar:"#3c40c6"},text:{primary:(0,a.darken)(r.BA.A700,.7),secondary:"#818ea3"},listPendingStatus:{background:"#fef7e5",color:"#fecb4c"},listCompletedStatus:{background:"#e9faf2",color:"#4ed495"}},shape:{borderRadius:o.default.unit},overrides:{MuiButton:{root:{borderRadius:o.default.unit/2,textTransform:"none"},sizeLarge:{padding:void 0,fontSize:void 0,paddingTop:o.default.unit,paddingBottom:o.default.unit,paddingLeft:5*o.default.unit,paddingRight:5*o.default.unit}},MuiTableCell:{body:{fontSize:"1rem"},head:{fontSize:"1rem",fontWeight:400}},MuiCardHeader:{root:{borderBottom:"1px solid rgba(0, 0, 0, 0.12)"},action:{marginTop:-2,marginRight:0,"& >*":{marginLeft:2*o.default.unit}},subheader:{marginTop:.5*o.default.unit}}},typography:{useNextVariants:!0,fontFamily:"-apple-system,BlinkMacSystemFont,Roboto,Helvetica,Arial,sans-serif",button:{textTransform:"none",fontSize:"1.2em"},body1:{fontSize:"1.0rem",fontWeight:400,lineHeight:"1.46429em",color:"rgba(0, 0, 0, 0.87)",letterSpacing:-.4},body2:{fontSize:"1.0rem",fontWeight:500,lineHeight:"1.71429em",color:"rgba(0, 0, 0, 0.87)",letterSpacing:-.4},body1Next:{color:"rgb(29, 29, 29)",fontWeight:400,fontSize:"1rem",lineHeight:1.5,letterSpacing:-.4},body2Next:{color:"rgb(29, 29, 29)",fontWeight:400,fontSize:"0.875rem",lineHeight:1.5,letterSpacing:-.4},display1:{color:"#818ea3",fontSize:"2.125rem",fontWeight:400,lineHeight:"1.20588em",letterSpacing:-.4},display2:{color:"#818ea3",fontSize:"2.8125rem",fontWeight:400,lineHeight:"1.13333em",marginLeft:"-.02em",letterSpacing:-.4},display3:{color:"#818ea3",fontSize:"3.5rem",fontWeight:400,lineHeight:"1.30357em",marginLeft:"-.02em",letterSpacing:-.4},display4:{fontSize:14,fontWeightLight:300,fontWeightMedium:500,fontWeightRegular:400,letterSpacing:-.4},h1:{color:"rgb(29, 29, 29)",fontSize:"6rem",fontWeight:300,lineHeight:1},h2:{color:"rgb(29, 29, 29)",fontSize:"3.75rem",fontWeight:300,lineHeight:1},h3:{color:"rgb(29, 29, 29)",fontSize:"3rem",fontWeight:400,lineHeight:1.04},h4:{color:"rgb(29, 29, 29)",fontSize:"2.125rem",fontWeight:400,lineHeight:1.17},h5:{color:"rgb(29, 29, 29)",fontSize:"1.5rem",fontWeight:400,lineHeight:1.33,letterSpacing:-.4},h6:{fontSize:"0.8rem",fontWeight:450,lineHeight:"1.71429em",color:"rgba(0, 0, 0, 0.87)",letterSpacing:-.4},subheading:{color:"rgb(29, 29, 29)",fontSize:"1rem",fontWeight:400,lineHeight:"1.5em",letterSpacing:-.4},subtitle1:{color:"rgb(29, 29, 29)",fontSize:"1rem",fontWeight:400,lineHeight:1.75,letterSpacing:-.4},subtitle2:{color:"rgb(29, 29, 29)",fontSize:"0.875rem",fontWeight:500,lineHeight:1.57,letterSpacing:-.4}},shadows:["none","0px 1px 3px 0px rgba(0, 0, 0, 0.1),0px 1px 1px 0px rgba(0, 0, 0, 0.04),0px 2px 1px -1px rgba(0, 0, 0, 0.02)","0px 1px 5px 0px rgba(0, 0, 0, 0.1),0px 2px 2px 0px rgba(0, 0, 0, 0.04),0px 3px 1px -2px rgba(0, 0, 0, 0.02)","0px 1px 8px 0px rgba(0, 0, 0, 0.1),0px 3px 4px 0px rgba(0, 0, 0, 0.04),0px 3px 3px -2px rgba(0, 0, 0, 0.02)","0px 2px 4px -1px rgba(0, 0, 0, 0.1),0px 4px 5px 0px rgba(0, 0, 0, 0.04),0px 1px 10px 0px rgba(0, 0, 0, 0.02)","0px 3px 5px -1px rgba(0, 0, 0, 0.1),0px 5px 8px 0px rgba(0, 0, 0, 0.04),0px 1px 14px 0px rgba(0, 0, 0, 0.02)","0px 3px 5px -1px rgba(0, 0, 0, 0.1),0px 6px 10px 0px rgba(0, 0, 0, 0.04),0px 1px 18px 0px rgba(0, 0, 0, 0.02)","0px 4px 5px -2px rgba(0, 0, 0, 0.1),0px 7px 10px 1px rgba(0, 0, 0, 0.04),0px 2px 16px 1px rgba(0, 0, 0, 0.02)","0px 5px 5px -3px rgba(0, 0, 0, 0.1),0px 8px 10px 1px rgba(0, 0, 0, 0.04),0px 3px 14px 2px rgba(0, 0, 0, 0.02)","0px 5px 6px -3px rgba(0, 0, 0, 0.1),0px 9px 12px 1px rgba(0, 0, 0, 0.04),0px 3px 16px 2px rgba(0, 0, 0, 0.02)","0px 6px 6px -3px rgba(0, 0, 0, 0.1),0px 10px 14px 1px rgba(0, 0, 0, 0.04),0px 4px 18px 3px rgba(0, 0, 0, 0.02)","0px 6px 7px -4px rgba(0, 0, 0, 0.1),0px 11px 15px 1px rgba(0, 0, 0, 0.04),0px 4px 20px 3px rgba(0, 0, 0, 0.02)","0px 7px 8px -4px rgba(0, 0, 0, 0.1),0px 12px 17px 2px rgba(0, 0, 0, 0.04),0px 5px 22px 4px rgba(0, 0, 0, 0.02)","0px 7px 8px -4px rgba(0, 0, 0, 0.1),0px 13px 19px 2px rgba(0, 0, 0, 0.04),0px 5px 24px 4px rgba(0, 0, 0, 0.02)","0px 7px 9px -4px rgba(0, 0, 0, 0.1),0px 14px 21px 2px rgba(0, 0, 0, 0.04),0px 5px 26px 4px rgba(0, 0, 0, 0.02)","0px 8px 9px -5px rgba(0, 0, 0, 0.1),0px 15px 22px 2px rgba(0, 0, 0, 0.04),0px 6px 28px 5px rgba(0, 0, 0, 0.02)","0px 8px 10px -5px rgba(0, 0, 0, 0.1),0px 16px 24px 2px rgba(0, 0, 0, 0.04),0px 6px 30px 5px rgba(0, 0, 0, 0.02)","0px 8px 11px -5px rgba(0, 0, 0, 0.1),0px 17px 26px 2px rgba(0, 0, 0, 0.04),0px 6px 32px 5px rgba(0, 0, 0, 0.02)","0px 9px 11px -5px rgba(0, 0, 0, 0.1),0px 18px 28px 2px rgba(0, 0, 0, 0.04),0px 7px 34px 6px rgba(0, 0, 0, 0.02)","0px 9px 12px -6px rgba(0, 0, 0, 0.1),0px 19px 29px 2px rgba(0, 0, 0, 0.04),0px 7px 36px 6px rgba(0, 0, 0, 0.02)","0px 10px 13px -6px rgba(0, 0, 0, 0.1),0px 20px 31px 3px rgba(0, 0, 0, 0.04),0px 8px 38px 7px rgba(0, 0, 0, 0.02)","0px 10px 13px -6px rgba(0, 0, 0, 0.1),0px 21px 33px 3px rgba(0, 0, 0, 0.04),0px 8px 40px 7px rgba(0, 0, 0, 0.02)","0px 10px 14px -6px rgba(0, 0, 0, 0.1),0px 22px 35px 3px rgba(0, 0, 0, 0.04),0px 8px 42px 7px rgba(0, 0, 0, 0.02)","0px 11px 14px -7px rgba(0, 0, 0, 0.1),0px 23px 36px 3px rgba(0, 0, 0, 0.04),0px 9px 44px 8px rgba(0, 0, 0, 0.02)","0px 11px 15px -7px rgba(0, 0, 0, 0.1),0px 24px 38px 3px rgba(0, 0, 0, 0.04),0px 9px 46px 8px rgba(0, 0, 0, 0.02)",]},u=(0,i.createMuiTheme)(s)},66289(e,t,n){"use strict";function r(e){if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function i(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function a(){if("undefined"==typeof Reflect||!Reflect.construct||Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(e){return!1}}function o(e,t,n){return(o=a()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var i=new(Function.bind.apply(e,r));return n&&f(i,n.prototype),i}).apply(null,arguments)}function s(e){return(s=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function u(e,t){if("function"!=typeof t&&null!==t)throw TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&f(e,t)}function c(e){return -1!==Function.toString.call(e).indexOf("[native code]")}function l(e,t){return t&&("object"===p(t)||"function"==typeof t)?t:r(e)}function f(e,t){return(f=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}n.d(t,{V0:()=>B,_7:()=>v});var d,h,p=function(e){return e&&"undefined"!=typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};function b(e){var t="function"==typeof Map?new Map:void 0;return(b=function(e){if(null===e||!c(e))return e;if("function"!=typeof e)throw TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return o(e,arguments,s(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),f(n,e)})(e)}function m(){if("undefined"==typeof Reflect||!Reflect.construct||Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(e){return!1}}function g(e){var t=m();return function(){var n,r=s(e);if(t){var i=s(this).constructor;n=Reflect.construct(r,arguments,i)}else n=r.apply(this,arguments);return l(this,n)}}var v=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"AuthenticationError(".concat(e.statusText,")"))).errors=[{status:e.status,detail:e},],r}return n}(b(Error)),y=function(e){u(n,e);var t=g(n);function n(e){var r,a=e.errors;return i(this,n),(r=t.call(this,"BadRequestError")).errors=a,r}return n}(b(Error)),w=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"UnprocessableEntityError")).errors=e,r}return n}(b(Error)),_=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"ServerError")).errors=e,r}return n}(b(Error)),E=function(e){u(n,e);var t=g(n);function n(e){var r,a=e.errors;return i(this,n),(r=t.call(this,"ConflictError")).errors=a,r}return n}(b(Error)),S=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"UnknownResponseError(".concat(e.statusText,")"))).errors=[{status:e.status,detail:e.statusText},],r}return n}(b(Error));function k(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:2e4;return Promise.race([fetch(e,t),new Promise(function(e,t){return setTimeout(function(){return t(Error("timeout"))},n)}),])}function x(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]=200&&e.status<300))return[3,2];return[2,e.json()];case 2:if(400!==e.status)return[3,3];return[2,e.json().then(function(e){throw new y(e)})];case 3:if(401!==e.status)return[3,4];throw new v(e);case 4:if(422!==e.status)return[3,6];return[4,$(e)];case 5:throw n=i.sent(),new w(n);case 6:if(409!==e.status)return[3,7];return[2,e.json().then(function(e){throw new E(e)})];case 7:if(!(e.status>=500))return[3,9];return[4,$(e)];case 8:throw r=i.sent(),new _(r);case 9:throw new S(e);case 10:return[2]}})})).apply(this,arguments)}function $(e){return z.apply(this,arguments)}function z(){return(z=j(function(e){return Y(this,function(t){return[2,e.json().then(function(t){return t.errors?t.errors.map(function(t){return{status:e.status,detail:t.detail}}):G(e)}).catch(function(){return G(e)})]})})).apply(this,arguments)}function G(e){return[{status:e.status,detail:e.statusText},]}},50109(e,t,n){"use strict";n.d(t,{LK:()=>o,U2:()=>i,eT:()=>s,t8:()=>a});var r=n(12795);function i(e){return r.ZP.getItem("chainlink.".concat(e))}function a(e,t){r.ZP.setItem("chainlink.".concat(e),t)}function o(e){var t=i(e),n={};if(t)try{return JSON.parse(t)}catch(r){}return n}function s(e,t){a(e,JSON.stringify(t))}},9541(e,t,n){"use strict";n.d(t,{Ks:()=>u,Tp:()=>a,iR:()=>o,pm:()=>s});var r=n(50109),i="persistURL";function a(){return r.U2(i)||""}function o(e){r.t8(i,e)}function s(){return r.LK("authentication")}function u(e){r.eT("authentication",e)}},67121(e,t,n){"use strict";function r(e){var t,n=e.Symbol;return"function"==typeof n?n.observable?t=n.observable:(t=n("observable"),n.observable=t):t="@@observable",t}n.r(t),n.d(t,{default:()=>o}),e=n.hmd(e),i="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==n.g?n.g:e;var i,a=r(i);let o=a},2177(e,t,n){"use strict";n.d(t,{Z:()=>o});var r=!0,i="Invariant failed";function a(e,t){if(!e){if(r)throw Error(i);throw Error(i+": "+(t||""))}}let o=a},11742(e){e.exports=function(){var e=document.getSelection();if(!e.rangeCount)return function(){};for(var t=document.activeElement,n=[],r=0;ri,pi:()=>a});var r=function(e,t){return(r=Object.setPrototypeOf||({__proto__:[]})instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function i(e,t){if("function"!=typeof t&&null!==t)throw TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var a=function(){return(a=Object.assign||function(e){for(var t,n=1,r=arguments.length;nr})},94927(e,t,n){function r(e,t){if(i("noDeprecation"))return e;var n=!1;function r(){if(!n){if(i("throwDeprecation"))throw Error(t);i("traceDeprecation")?console.trace(t):console.warn(t),n=!0}return e.apply(this,arguments)}return r}function i(e){try{if(!n.g.localStorage)return!1}catch(t){return!1}var r=n.g.localStorage[e];return null!=r&&"true"===String(r).toLowerCase()}e.exports=r},42473(e){"use strict";var t=function(){};e.exports=t},84763(e){e.exports=Worker},47529(e){e.exports=n;var t=Object.prototype.hasOwnProperty;function n(){for(var e={},n=0;nr,O:()=>a}),(i=r||(r={}))[i.loading=1]="loading",i[i.setVariables=2]="setVariables",i[i.fetchMore=3]="fetchMore",i[i.refetch=4]="refetch",i[i.poll=6]="poll",i[i.ready=7]="ready",i[i.error=8]="error"},30990(e,t,n){"use strict";n.d(t,{MS:()=>s,YG:()=>a,cA:()=>c,ls:()=>o});var r=n(23564);n(83952);var i=n(13154),a=Symbol();function o(e){return!!e.extensions&&Array.isArray(e.extensions[a])}function s(e){return e.hasOwnProperty("graphQLErrors")}var u=function(e){var t=(0,r.ev)((0,r.ev)((0,r.ev)([],e.graphQLErrors,!0),e.clientErrors,!0),e.protocolErrors,!0);return e.networkError&&t.push(e.networkError),t.map(function(e){return(0,i.s)(e)&&e.message||"Error message not found."}).join("\n")},c=function(e){function t(n){var r=n.graphQLErrors,i=n.protocolErrors,a=n.clientErrors,o=n.networkError,s=n.errorMessage,c=n.extraInfo,l=e.call(this,s)||this;return l.name="ApolloError",l.graphQLErrors=r||[],l.protocolErrors=i||[],l.clientErrors=a||[],l.networkError=o||null,l.message=s||u(l),l.extraInfo=c,l.__proto__=t.prototype,l}return(0,r.ZT)(t,e),t}(Error)},85317(e,t,n){"use strict";n.d(t,{K:()=>a});var r=n(67294),i=n(30320).aS?Symbol.for("__APOLLO_CONTEXT__"):"__APOLLO_CONTEXT__";function a(){var e=r.createContext[i];return e||(Object.defineProperty(r.createContext,i,{value:e=r.createContext({}),enumerable:!1,writable:!1,configurable:!0}),e.displayName="ApolloContext"),e}},21436(e,t,n){"use strict";n.d(t,{O:()=>i,k:()=>r});var r=Array.isArray;function i(e){return Array.isArray(e)&&e.length>0}},30320(e,t,n){"use strict";n.d(t,{DN:()=>s,JC:()=>l,aS:()=>o,mr:()=>i,sy:()=>a});var r=n(83952),i="function"==typeof WeakMap&&"ReactNative"!==(0,r.wY)(function(){return navigator.product}),a="function"==typeof WeakSet,o="function"==typeof Symbol&&"function"==typeof Symbol.for,s=o&&Symbol.asyncIterator,u="function"==typeof(0,r.wY)(function(){return window.document.createElement}),c=(0,r.wY)(function(){return navigator.userAgent.indexOf("jsdom")>=0})||!1,l=u&&!c},53712(e,t,n){"use strict";function r(){for(var e=[],t=0;tr})},10542(e,t,n){"use strict";n.d(t,{J:()=>o}),n(83952);var r=n(13154);function i(e){var t=new Set([e]);return t.forEach(function(e){(0,r.s)(e)&&a(e)===e&&Object.getOwnPropertyNames(e).forEach(function(n){(0,r.s)(e[n])&&t.add(e[n])})}),e}function a(e){if(__DEV__&&!Object.isFrozen(e))try{Object.freeze(e)}catch(t){if(t instanceof TypeError)return null;throw t}return e}function o(e){return __DEV__&&i(e),e}},14012(e,t,n){"use strict";n.d(t,{J:()=>a});var r=n(23564),i=n(53712);function a(e,t){return(0,i.o)(e,t,t.variables&&{variables:(0,r.pi)((0,r.pi)({},e&&e.variables),t.variables)})}},13154(e,t,n){"use strict";function r(e){return null!==e&&"object"==typeof e}n.d(t,{s:()=>r})},83952(e,t,n){"use strict";n.d(t,{ej:()=>u,kG:()=>c,wY:()=>h});var r,i=n(70655),a="Invariant Violation",o=Object.setPrototypeOf,s=void 0===o?function(e,t){return e.__proto__=t,e}:o,u=function(e){function t(n){void 0===n&&(n=a);var r=e.call(this,"number"==typeof n?a+": "+n+" (see https://github.com/apollographql/invariant-packages)":n)||this;return r.framesToPop=1,r.name=a,s(r,t.prototype),r}return(0,i.ZT)(t,e),t}(Error);function c(e,t){if(!e)throw new u(t)}var l=["debug","log","warn","error","silent"],f=l.indexOf("log");function d(e){return function(){if(l.indexOf(e)>=f)return(console[e]||console.log).apply(console,arguments)}}function h(e){try{return e()}catch(t){}}(r=c||(c={})).debug=d("debug"),r.log=d("log"),r.warn=d("warn"),r.error=d("error");let p=h(function(){return globalThis})||h(function(){return window})||h(function(){return self})||h(function(){return global})||h(function(){return h.constructor("return this")()});var b="__",m=[b,b].join("DEV");function g(){try{return Boolean(__DEV__)}catch(e){return Object.defineProperty(p,m,{value:"production"!==h(function(){return"production"}),enumerable:!1,configurable:!0,writable:!0}),p[m]}}let v=g();function y(e){try{return e()}catch(t){}}var w=y(function(){return globalThis})||y(function(){return window})||y(function(){return self})||y(function(){return global})||y(function(){return y.constructor("return this")()}),_=!1;function E(){!w||y(function(){return"production"})||y(function(){return process})||(Object.defineProperty(w,"process",{value:{env:{NODE_ENV:"production"}},configurable:!0,enumerable:!1,writable:!0}),_=!0)}function S(){_&&(delete w.process,_=!1)}E();var k=n(10143);function x(){return k.H,S()}function T(){__DEV__?c("boolean"==typeof v,v):c("boolean"==typeof v,39)}x(),T()},87462(e,t,n){"use strict";function r(){return(r=Object.assign||function(e){for(var t=1;tr})},25821(e,t,n){"use strict";n.d(t,{Z:()=>s});var r=n(45695);function i(e){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var a=10,o=2;function s(e){return u(e,[])}function u(e,t){switch(i(e)){case"string":return JSON.stringify(e);case"function":return e.name?"[function ".concat(e.name,"]"):"[function]";case"object":if(null===e)return"null";return c(e,t);default:return String(e)}}function c(e,t){if(-1!==t.indexOf(e))return"[Circular]";var n=[].concat(t,[e]),r=d(e);if(void 0!==r){var i=r.call(e);if(i!==e)return"string"==typeof i?i:u(i,n)}else if(Array.isArray(e))return f(e,n);return l(e,n)}function l(e,t){var n=Object.keys(e);return 0===n.length?"{}":t.length>o?"["+h(e)+"]":"{ "+n.map(function(n){var r=u(e[n],t);return n+": "+r}).join(", ")+" }"}function f(e,t){if(0===e.length)return"[]";if(t.length>o)return"[Array]";for(var n=Math.min(a,e.length),r=e.length-n,i=[],s=0;s1&&i.push("... ".concat(r," more items")),"["+i.join(", ")+"]"}function d(e){var t=e[String(r.Z)];return"function"==typeof t?t:"function"==typeof e.inspect?e.inspect:void 0}function h(e){var t=Object.prototype.toString.call(e).replace(/^\[object /,"").replace(/]$/,"");if("Object"===t&&"function"==typeof e.constructor){var n=e.constructor.name;if("string"==typeof n&&""!==n)return n}return t}},45695(e,t,n){"use strict";n.d(t,{Z:()=>i});var r="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):void 0;let i=r},25217(e,t,n){"use strict";function r(e,t){if(!Boolean(e))throw Error(null!=t?t:"Unexpected invariant triggered.")}n.d(t,{Ye:()=>o,WU:()=>s,UG:()=>u});var i=n(45695);function a(e){var t=e.prototype.toJSON;"function"==typeof t||r(0),e.prototype.inspect=t,i.Z&&(e.prototype[i.Z]=t)}var o=function(){function e(e,t,n){this.start=e.start,this.end=t.end,this.startToken=e,this.endToken=t,this.source=n}return e.prototype.toJSON=function(){return{start:this.start,end:this.end}},e}();a(o);var s=function(){function e(e,t,n,r,i,a,o){this.kind=e,this.start=t,this.end=n,this.line=r,this.column=i,this.value=o,this.prev=a,this.next=null}return e.prototype.toJSON=function(){return{kind:this.kind,value:this.value,line:this.line,column:this.column}},e}();function u(e){return null!=e&&"string"==typeof e.kind}a(s)},87392(e,t,n){"use strict";function r(e){var t=e.split(/\r\n|[\n\r]/g),n=a(e);if(0!==n)for(var r=1;ro&&i(t[s-1]);)--s;return t.slice(o,s).join("\n")}function i(e){for(var t=0;t1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=-1===e.indexOf("\n"),i=" "===e[0]||" "===e[0],a='"'===e[e.length-1],o="\\"===e[e.length-1],s=!r||a||o||n,u="";return s&&!(r&&i)&&(u+="\n"+t),u+=t?e.replace(/\n/g,"\n"+t):e,s&&(u+="\n"),'"""'+u.replace(/"""/g,'\\"""')+'"""'}n.d(t,{LZ:()=>o,W7:()=>r})},97359(e,t,n){"use strict";n.d(t,{h:()=>r});var r=Object.freeze({NAME:"Name",DOCUMENT:"Document",OPERATION_DEFINITION:"OperationDefinition",VARIABLE_DEFINITION:"VariableDefinition",SELECTION_SET:"SelectionSet",FIELD:"Field",ARGUMENT:"Argument",FRAGMENT_SPREAD:"FragmentSpread",INLINE_FRAGMENT:"InlineFragment",FRAGMENT_DEFINITION:"FragmentDefinition",VARIABLE:"Variable",INT:"IntValue",FLOAT:"FloatValue",STRING:"StringValue",BOOLEAN:"BooleanValue",NULL:"NullValue",ENUM:"EnumValue",LIST:"ListValue",OBJECT:"ObjectValue",OBJECT_FIELD:"ObjectField",DIRECTIVE:"Directive",NAMED_TYPE:"NamedType",LIST_TYPE:"ListType",NON_NULL_TYPE:"NonNullType",SCHEMA_DEFINITION:"SchemaDefinition",OPERATION_TYPE_DEFINITION:"OperationTypeDefinition",SCALAR_TYPE_DEFINITION:"ScalarTypeDefinition",OBJECT_TYPE_DEFINITION:"ObjectTypeDefinition",FIELD_DEFINITION:"FieldDefinition",INPUT_VALUE_DEFINITION:"InputValueDefinition",INTERFACE_TYPE_DEFINITION:"InterfaceTypeDefinition",UNION_TYPE_DEFINITION:"UnionTypeDefinition",ENUM_TYPE_DEFINITION:"EnumTypeDefinition",ENUM_VALUE_DEFINITION:"EnumValueDefinition",INPUT_OBJECT_TYPE_DEFINITION:"InputObjectTypeDefinition",DIRECTIVE_DEFINITION:"DirectiveDefinition",SCHEMA_EXTENSION:"SchemaExtension",SCALAR_TYPE_EXTENSION:"ScalarTypeExtension",OBJECT_TYPE_EXTENSION:"ObjectTypeExtension",INTERFACE_TYPE_EXTENSION:"InterfaceTypeExtension",UNION_TYPE_EXTENSION:"UnionTypeExtension",ENUM_TYPE_EXTENSION:"EnumTypeExtension",INPUT_OBJECT_TYPE_EXTENSION:"InputObjectTypeExtension"})},10143(e,t,n){"use strict";n.d(t,{H:()=>c,T:()=>l});var r=n(99763),i=n(25821);function a(e,t){if(!Boolean(e))throw Error(t)}let o=function(e,t){return e instanceof t};function s(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:"GraphQL request",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{line:1,column:1};"string"==typeof e||a(0,"Body must be a string. Received: ".concat((0,i.Z)(e),".")),this.body=e,this.name=t,this.locationOffset=n,this.locationOffset.line>0||a(0,"line in locationOffset is 1-indexed and must be positive."),this.locationOffset.column>0||a(0,"column in locationOffset is 1-indexed and must be positive.")}return u(e,[{key:r.YF,get:function(){return"Source"}}]),e}();function l(e){return o(e,c)}},99763(e,t,n){"use strict";n.d(t,{YF:()=>r});var r="function"==typeof Symbol&&null!=Symbol.toStringTag?Symbol.toStringTag:"@@toStringTag"},37452(e){"use strict";e.exports=JSON.parse('{"AElig":"\xc6","AMP":"&","Aacute":"\xc1","Acirc":"\xc2","Agrave":"\xc0","Aring":"\xc5","Atilde":"\xc3","Auml":"\xc4","COPY":"\xa9","Ccedil":"\xc7","ETH":"\xd0","Eacute":"\xc9","Ecirc":"\xca","Egrave":"\xc8","Euml":"\xcb","GT":">","Iacute":"\xcd","Icirc":"\xce","Igrave":"\xcc","Iuml":"\xcf","LT":"<","Ntilde":"\xd1","Oacute":"\xd3","Ocirc":"\xd4","Ograve":"\xd2","Oslash":"\xd8","Otilde":"\xd5","Ouml":"\xd6","QUOT":"\\"","REG":"\xae","THORN":"\xde","Uacute":"\xda","Ucirc":"\xdb","Ugrave":"\xd9","Uuml":"\xdc","Yacute":"\xdd","aacute":"\xe1","acirc":"\xe2","acute":"\xb4","aelig":"\xe6","agrave":"\xe0","amp":"&","aring":"\xe5","atilde":"\xe3","auml":"\xe4","brvbar":"\xa6","ccedil":"\xe7","cedil":"\xb8","cent":"\xa2","copy":"\xa9","curren":"\xa4","deg":"\xb0","divide":"\xf7","eacute":"\xe9","ecirc":"\xea","egrave":"\xe8","eth":"\xf0","euml":"\xeb","frac12":"\xbd","frac14":"\xbc","frac34":"\xbe","gt":">","iacute":"\xed","icirc":"\xee","iexcl":"\xa1","igrave":"\xec","iquest":"\xbf","iuml":"\xef","laquo":"\xab","lt":"<","macr":"\xaf","micro":"\xb5","middot":"\xb7","nbsp":"\xa0","not":"\xac","ntilde":"\xf1","oacute":"\xf3","ocirc":"\xf4","ograve":"\xf2","ordf":"\xaa","ordm":"\xba","oslash":"\xf8","otilde":"\xf5","ouml":"\xf6","para":"\xb6","plusmn":"\xb1","pound":"\xa3","quot":"\\"","raquo":"\xbb","reg":"\xae","sect":"\xa7","shy":"\xad","sup1":"\xb9","sup2":"\xb2","sup3":"\xb3","szlig":"\xdf","thorn":"\xfe","times":"\xd7","uacute":"\xfa","ucirc":"\xfb","ugrave":"\xf9","uml":"\xa8","uuml":"\xfc","yacute":"\xfd","yen":"\xa5","yuml":"\xff"}')},93580(e){"use strict";e.exports=JSON.parse('{"0":"�","128":"€","130":"‚","131":"ƒ","132":"„","133":"…","134":"†","135":"‡","136":"ˆ","137":"‰","138":"Š","139":"‹","140":"Œ","142":"Ž","145":"‘","146":"’","147":"“","148":"”","149":"•","150":"–","151":"—","152":"˜","153":"™","154":"š","155":"›","156":"œ","158":"ž","159":"Ÿ"}')},67946(e){"use strict";e.exports=JSON.parse('{"locale":"en","long":{"year":{"previous":"last year","current":"this year","next":"next year","past":{"one":"{0} year ago","other":"{0} years ago"},"future":{"one":"in {0} year","other":"in {0} years"}},"quarter":{"previous":"last quarter","current":"this quarter","next":"next quarter","past":{"one":"{0} quarter ago","other":"{0} quarters ago"},"future":{"one":"in {0} quarter","other":"in {0} quarters"}},"month":{"previous":"last month","current":"this month","next":"next month","past":{"one":"{0} month ago","other":"{0} months ago"},"future":{"one":"in {0} month","other":"in {0} months"}},"week":{"previous":"last week","current":"this week","next":"next week","past":{"one":"{0} week ago","other":"{0} weeks ago"},"future":{"one":"in {0} week","other":"in {0} weeks"}},"day":{"previous":"yesterday","current":"today","next":"tomorrow","past":{"one":"{0} day ago","other":"{0} days ago"},"future":{"one":"in {0} day","other":"in {0} days"}},"hour":{"current":"this hour","past":{"one":"{0} hour ago","other":"{0} hours ago"},"future":{"one":"in {0} hour","other":"in {0} hours"}},"minute":{"current":"this minute","past":{"one":"{0} minute ago","other":"{0} minutes ago"},"future":{"one":"in {0} minute","other":"in {0} minutes"}},"second":{"current":"now","past":{"one":"{0} second ago","other":"{0} seconds ago"},"future":{"one":"in {0} second","other":"in {0} seconds"}}},"short":{"year":{"previous":"last yr.","current":"this yr.","next":"next yr.","past":"{0} yr. ago","future":"in {0} yr."},"quarter":{"previous":"last qtr.","current":"this qtr.","next":"next qtr.","past":{"one":"{0} qtr. ago","other":"{0} qtrs. ago"},"future":{"one":"in {0} qtr.","other":"in {0} qtrs."}},"month":{"previous":"last mo.","current":"this mo.","next":"next mo.","past":"{0} mo. ago","future":"in {0} mo."},"week":{"previous":"last wk.","current":"this wk.","next":"next wk.","past":"{0} wk. ago","future":"in {0} wk."},"day":{"previous":"yesterday","current":"today","next":"tomorrow","past":{"one":"{0} day ago","other":"{0} days ago"},"future":{"one":"in {0} day","other":"in {0} days"}},"hour":{"current":"this hour","past":"{0} hr. ago","future":"in {0} hr."},"minute":{"current":"this minute","past":"{0} min. ago","future":"in {0} min."},"second":{"current":"now","past":"{0} sec. ago","future":"in {0} sec."}},"narrow":{"year":{"previous":"last yr.","current":"this yr.","next":"next yr.","past":"{0} yr. ago","future":"in {0} yr."},"quarter":{"previous":"last qtr.","current":"this qtr.","next":"next qtr.","past":{"one":"{0} qtr. ago","other":"{0} qtrs. ago"},"future":{"one":"in {0} qtr.","other":"in {0} qtrs."}},"month":{"previous":"last mo.","current":"this mo.","next":"next mo.","past":"{0} mo. ago","future":"in {0} mo."},"week":{"previous":"last wk.","current":"this wk.","next":"next wk.","past":"{0} wk. ago","future":"in {0} wk."},"day":{"previous":"yesterday","current":"today","next":"tomorrow","past":{"one":"{0} day ago","other":"{0} days ago"},"future":{"one":"in {0} day","other":"in {0} days"}},"hour":{"current":"this hour","past":"{0} hr. ago","future":"in {0} hr."},"minute":{"current":"this minute","past":"{0} min. ago","future":"in {0} min."},"second":{"current":"now","past":"{0} sec. ago","future":"in {0} sec."}},"now":{"now":{"current":"now","future":"in a moment","past":"just now"}},"mini":{"year":"{0}yr","month":"{0}mo","week":"{0}wk","day":"{0}d","hour":"{0}h","minute":"{0}m","second":"{0}s","now":"now"},"short-time":{"year":"{0} yr.","month":"{0} mo.","week":"{0} wk.","day":{"one":"{0} day","other":"{0} days"},"hour":"{0} hr.","minute":"{0} min.","second":"{0} sec."},"long-time":{"year":{"one":"{0} year","other":"{0} years"},"month":{"one":"{0} month","other":"{0} months"},"week":{"one":"{0} week","other":"{0} weeks"},"day":{"one":"{0} day","other":"{0} days"},"hour":{"one":"{0} hour","other":"{0} hours"},"minute":{"one":"{0} minute","other":"{0} minutes"},"second":{"one":"{0} second","other":"{0} seconds"}}}')}},__webpack_module_cache__={};function __webpack_require__(e){var t=__webpack_module_cache__[e];if(void 0!==t)return t.exports;var n=__webpack_module_cache__[e]={id:e,loaded:!1,exports:{}};return __webpack_modules__[e].call(n.exports,n,n.exports,__webpack_require__),n.loaded=!0,n.exports}__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},(()=>{var e,t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;__webpack_require__.t=function(n,r){if(1&r&&(n=this(n)),8&r||"object"==typeof n&&n&&(4&r&&n.__esModule||16&r&&"function"==typeof n.then))return n;var i=Object.create(null);__webpack_require__.r(i);var a={};e=e||[null,t({}),t([]),t(t)];for(var o=2&r&&n;"object"==typeof o&&!~e.indexOf(o);o=t(o))Object.getOwnPropertyNames(o).forEach(e=>a[e]=()=>n[e]);return a.default=()=>n,__webpack_require__.d(i,a),i}})(),__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.hmd=e=>((e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set(){throw Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e),__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),__webpack_require__.p="/assets/",__webpack_require__.nc=void 0;var __webpack_exports__={};(()=>{"use strict";var e,t,n,r,i=__webpack_require__(32316),a=__webpack_require__(8126),o=__webpack_require__(5690),s=__webpack_require__(30381),u=__webpack_require__.n(s),c=__webpack_require__(67294),l=__webpack_require__(73935),f=__webpack_require__.n(l),d=__webpack_require__(57209),h=__webpack_require__(55977),p=__webpack_require__(15857),b=__webpack_require__(28500);function m(e){return function(t){var n=t.dispatch,r=t.getState;return function(t){return function(i){return"function"==typeof i?i(n,r,e):t(i)}}}}var g=m();g.withExtraArgument=m;let v=g;var y=__webpack_require__(76489);function w(e){return function(t){return function(n){return function(r){n(r);var i=e||document&&document.cookie||"",a=t.getState();if("MATCH_ROUTE"===r.type&&"/signin"!==a.notifications.currentUrl){var o=(0,y.Q)(i);if(o.explorer)try{var s=JSON.parse(o.explorer);if("error"===s.status){var u=_(s.url);n({type:"NOTIFY_ERROR_MSG",msg:u})}}catch(c){n({type:"NOTIFY_ERROR_MSG",msg:"Invalid explorer status"})}}}}}}function _(e){var t="Can't connect to explorer: ".concat(e);return e.match(/^wss?:.+/)?t:"".concat(t,". You must use a websocket.")}var E=__webpack_require__(16353);function S(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function ei(e,t){if(e){if("string"==typeof e)return ea(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return ea(e,t)}}function ea(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n1,i=!1,a=arguments[1],o=a;return new n(function(n){return t.subscribe({next:function(t){var a=!i;if(i=!0,!a||r)try{o=e(o,t)}catch(s){return n.error(s)}else o=t},error:function(e){n.error(e)},complete:function(){if(!i&&!r)return n.error(TypeError("Cannot reduce an empty sequence"));n.next(o),n.complete()}})})},t.concat=function(){for(var e=this,t=arguments.length,n=Array(t),r=0;r=0&&i.splice(e,1),o()}});i.push(s)},error:function(e){r.error(e)},complete:function(){o()}});function o(){a.closed&&0===i.length&&r.complete()}return function(){i.forEach(function(e){return e.unsubscribe()}),a.unsubscribe()}})},t[ed]=function(){return this},e.from=function(t){var n="function"==typeof this?this:e;if(null==t)throw TypeError(t+" is not an object");var r=ep(t,ed);if(r){var i=r.call(t);if(Object(i)!==i)throw TypeError(i+" is not an object");return em(i)&&i.constructor===n?i:new n(function(e){return i.subscribe(e)})}if(ec("iterator")&&(r=ep(t,ef)))return new n(function(e){ev(function(){if(!e.closed){for(var n,i=er(r.call(t));!(n=i()).done;){var a=n.value;if(e.next(a),e.closed)return}e.complete()}})});if(Array.isArray(t))return new n(function(e){ev(function(){if(!e.closed){for(var n=0;n0))return n.connection.key;var r=n.connection.filter?n.connection.filter:[];r.sort();var i={};return r.forEach(function(e){i[e]=t[e]}),"".concat(n.connection.key,"(").concat(eV(i),")")}var a=e;if(t){var o=eV(t);a+="(".concat(o,")")}return n&&Object.keys(n).forEach(function(e){-1===eW.indexOf(e)&&(n[e]&&Object.keys(n[e]).length?a+="@".concat(e,"(").concat(eV(n[e]),")"):a+="@".concat(e))}),a},{setStringify:function(e){var t=eV;return eV=e,t}}),eV=function(e){return JSON.stringify(e,eq)};function eq(e,t){return(0,eO.s)(t)&&!Array.isArray(t)&&(t=Object.keys(t).sort().reduce(function(e,n){return e[n]=t[n],e},{})),t}function eZ(e,t){if(e.arguments&&e.arguments.length){var n={};return e.arguments.forEach(function(e){var r;return ez(n,e.name,e.value,t)}),n}return null}function eX(e){return e.alias?e.alias.value:e.name.value}function eJ(e,t,n){for(var r,i=0,a=t.selections;it.indexOf(i))throw __DEV__?new Q.ej("illegal argument: ".concat(i)):new Q.ej(27)}return e}function tt(e,t){return t?t(e):eT.of()}function tn(e){return"function"==typeof e?new ta(e):e}function tr(e){return e.request.length<=1}var ti=function(e){function t(t,n){var r=e.call(this,t)||this;return r.link=n,r}return(0,en.ZT)(t,e),t}(Error),ta=function(){function e(e){e&&(this.request=e)}return e.empty=function(){return new e(function(){return eT.of()})},e.from=function(t){return 0===t.length?e.empty():t.map(tn).reduce(function(e,t){return e.concat(t)})},e.split=function(t,n,r){var i=tn(n),a=tn(r||new e(tt));return new e(tr(i)&&tr(a)?function(e){return t(e)?i.request(e)||eT.of():a.request(e)||eT.of()}:function(e,n){return t(e)?i.request(e,n)||eT.of():a.request(e,n)||eT.of()})},e.execute=function(e,t){return e.request(eM(t.context,e7(te(t))))||eT.of()},e.concat=function(t,n){var r=tn(t);if(tr(r))return __DEV__&&Q.kG.warn(new ti("You are calling concat on a terminating link, which will have no effect",r)),r;var i=tn(n);return new e(tr(i)?function(e){return r.request(e,function(e){return i.request(e)||eT.of()})||eT.of()}:function(e,t){return r.request(e,function(e){return i.request(e,t)||eT.of()})||eT.of()})},e.prototype.split=function(t,n,r){return this.concat(e.split(t,n,r||new e(tt)))},e.prototype.concat=function(t){return e.concat(this,t)},e.prototype.request=function(e,t){throw __DEV__?new Q.ej("request is not implemented"):new Q.ej(22)},e.prototype.onError=function(e,t){if(t&&t.error)return t.error(e),!1;throw e},e.prototype.setOnError=function(e){return this.onError=e,this},e}(),to=__webpack_require__(25821),ts=__webpack_require__(25217),tu={Name:[],Document:["definitions"],OperationDefinition:["name","variableDefinitions","directives","selectionSet"],VariableDefinition:["variable","type","defaultValue","directives"],Variable:["name"],SelectionSet:["selections"],Field:["alias","name","arguments","directives","selectionSet"],Argument:["name","value"],FragmentSpread:["name","directives"],InlineFragment:["typeCondition","directives","selectionSet"],FragmentDefinition:["name","variableDefinitions","typeCondition","directives","selectionSet"],IntValue:[],FloatValue:[],StringValue:[],BooleanValue:[],NullValue:[],EnumValue:[],ListValue:["values"],ObjectValue:["fields"],ObjectField:["name","value"],Directive:["name","arguments"],NamedType:["name"],ListType:["type"],NonNullType:["type"],SchemaDefinition:["description","directives","operationTypes"],OperationTypeDefinition:["type"],ScalarTypeDefinition:["description","name","directives"],ObjectTypeDefinition:["description","name","interfaces","directives","fields"],FieldDefinition:["description","name","arguments","type","directives"],InputValueDefinition:["description","name","type","defaultValue","directives"],InterfaceTypeDefinition:["description","name","interfaces","directives","fields"],UnionTypeDefinition:["description","name","directives","types"],EnumTypeDefinition:["description","name","directives","values"],EnumValueDefinition:["description","name","directives"],InputObjectTypeDefinition:["description","name","directives","fields"],DirectiveDefinition:["description","name","arguments","locations"],SchemaExtension:["directives","operationTypes"],ScalarTypeExtension:["name","directives"],ObjectTypeExtension:["name","interfaces","directives","fields"],InterfaceTypeExtension:["name","interfaces","directives","fields"],UnionTypeExtension:["name","directives","types"],EnumTypeExtension:["name","directives","values"],InputObjectTypeExtension:["name","directives","fields"]},tc=Object.freeze({});function tl(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:tu,r=void 0,i=Array.isArray(e),a=[e],o=-1,s=[],u=void 0,c=void 0,l=void 0,f=[],d=[],h=e;do{var p,b=++o===a.length,m=b&&0!==s.length;if(b){if(c=0===d.length?void 0:f[f.length-1],u=l,l=d.pop(),m){if(i)u=u.slice();else{for(var g={},v=0,y=Object.keys(u);v1)for(var r=new tB,i=1;i=0;--a){var o=i[a],s=isNaN(+o)?{}:[];s[o]=t,t=s}n=r.merge(n,t)}),n}var tW=Object.prototype.hasOwnProperty;function tK(e,t){var n,r,i,a,o;return(0,en.mG)(this,void 0,void 0,function(){var s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A;return(0,en.Jh)(this,function(L){switch(L.label){case 0:if(void 0===TextDecoder)throw Error("TextDecoder must be defined in the environment: please import a polyfill.");s=new TextDecoder("utf-8"),u=null===(n=e.headers)||void 0===n?void 0:n.get("content-type"),c="boundary=",l=(null==u?void 0:u.includes(c))?null==u?void 0:u.substring((null==u?void 0:u.indexOf(c))+c.length).replace(/['"]/g,"").replace(/\;(.*)/gm,"").trim():"-",f="\r\n--".concat(l),d="",h=tI(e),p=!0,L.label=1;case 1:if(!p)return[3,3];return[4,h.next()];case 2:for(m=(b=L.sent()).value,g=b.done,v="string"==typeof m?m:s.decode(m),y=d.length-f.length+1,p=!g,d+=v,w=d.indexOf(f,y);w>-1;){if(_=void 0,_=(O=[d.slice(0,w),d.slice(w+f.length),])[0],d=O[1],E=_.indexOf("\r\n\r\n"),(k=(S=tV(_.slice(0,E)))["content-type"])&&-1===k.toLowerCase().indexOf("application/json"))throw Error("Unsupported patch content type: application/json is required.");if(x=_.slice(E))try{T=tq(e,x),Object.keys(T).length>1||"data"in T||"incremental"in T||"errors"in T||"payload"in T?tz(T)?(M={},"payload"in T&&(M=(0,en.pi)({},T.payload)),"errors"in T&&(M=(0,en.pi)((0,en.pi)({},M),{extensions:(0,en.pi)((0,en.pi)({},"extensions"in M?M.extensions:null),((A={})[tN.YG]=T.errors,A))})),null===(r=t.next)||void 0===r||r.call(t,M)):null===(i=t.next)||void 0===i||i.call(t,T):1===Object.keys(T).length&&"hasNext"in T&&!T.hasNext&&(null===(a=t.complete)||void 0===a||a.call(t))}catch(C){tZ(C,t)}w=d.indexOf(f)}return[3,1];case 3:return null===(o=t.complete)||void 0===o||o.call(t),[2]}})})}function tV(e){var t={};return e.split("\n").forEach(function(e){var n=e.indexOf(":");if(n>-1){var r=e.slice(0,n).trim().toLowerCase(),i=e.slice(n+1).trim();t[r]=i}}),t}function tq(e,t){e.status>=300&&tD(e,function(){try{return JSON.parse(t)}catch(e){return t}}(),"Response not successful: Received status code ".concat(e.status));try{return JSON.parse(t)}catch(n){var r=n;throw r.name="ServerParseError",r.response=e,r.statusCode=e.status,r.bodyText=t,r}}function tZ(e,t){var n,r;"AbortError"!==e.name&&(e.result&&e.result.errors&&e.result.data&&(null===(n=t.next)||void 0===n||n.call(t,e.result)),null===(r=t.error)||void 0===r||r.call(t,e))}function tX(e,t,n){tJ(t)(e).then(function(e){var t,r;null===(t=n.next)||void 0===t||t.call(n,e),null===(r=n.complete)||void 0===r||r.call(n)}).catch(function(e){return tZ(e,n)})}function tJ(e){return function(t){return t.text().then(function(e){return tq(t,e)}).then(function(n){return t.status>=300&&tD(t,n,"Response not successful: Received status code ".concat(t.status)),Array.isArray(n)||tW.call(n,"data")||tW.call(n,"errors")||tD(t,n,"Server response was missing for query '".concat(Array.isArray(e)?e.map(function(e){return e.operationName}):e.operationName,"'.")),n})}}var tQ=function(e){if(!e&&"undefined"==typeof fetch)throw __DEV__?new Q.ej("\n\"fetch\" has not been found globally and no fetcher has been configured. To fix this, install a fetch package (like https://www.npmjs.com/package/cross-fetch), instantiate the fetcher, and pass it into your HttpLink constructor. For example:\n\nimport fetch from 'cross-fetch';\nimport { ApolloClient, HttpLink } from '@apollo/client';\nconst client = new ApolloClient({\n link: new HttpLink({ uri: '/graphql', fetch })\n});\n "):new Q.ej(23)},t1=__webpack_require__(87392);function t0(e){return tl(e,{leave:t3})}var t2=80,t3={Name:function(e){return e.value},Variable:function(e){return"$"+e.name},Document:function(e){return t5(e.definitions,"\n\n")+"\n"},OperationDefinition:function(e){var t=e.operation,n=e.name,r=t9("(",t5(e.variableDefinitions,", "),")"),i=t5(e.directives," "),a=e.selectionSet;return n||i||r||"query"!==t?t5([t,t5([n,r]),i,a]," "):a},VariableDefinition:function(e){var t=e.variable,n=e.type,r=e.defaultValue,i=e.directives;return t+": "+n+t9(" = ",r)+t9(" ",t5(i," "))},SelectionSet:function(e){return t6(e.selections)},Field:function(e){var t=e.alias,n=e.name,r=e.arguments,i=e.directives,a=e.selectionSet,o=t9("",t,": ")+n,s=o+t9("(",t5(r,", "),")");return s.length>t2&&(s=o+t9("(\n",t8(t5(r,"\n")),"\n)")),t5([s,t5(i," "),a]," ")},Argument:function(e){var t;return e.name+": "+e.value},FragmentSpread:function(e){var t;return"..."+e.name+t9(" ",t5(e.directives," "))},InlineFragment:function(e){var t=e.typeCondition,n=e.directives,r=e.selectionSet;return t5(["...",t9("on ",t),t5(n," "),r]," ")},FragmentDefinition:function(e){var t=e.name,n=e.typeCondition,r=e.variableDefinitions,i=e.directives,a=e.selectionSet;return"fragment ".concat(t).concat(t9("(",t5(r,", "),")")," ")+"on ".concat(n," ").concat(t9("",t5(i," ")," "))+a},IntValue:function(e){return e.value},FloatValue:function(e){return e.value},StringValue:function(e,t){var n=e.value;return e.block?(0,t1.LZ)(n,"description"===t?"":" "):JSON.stringify(n)},BooleanValue:function(e){return e.value?"true":"false"},NullValue:function(){return"null"},EnumValue:function(e){return e.value},ListValue:function(e){return"["+t5(e.values,", ")+"]"},ObjectValue:function(e){return"{"+t5(e.fields,", ")+"}"},ObjectField:function(e){var t;return e.name+": "+e.value},Directive:function(e){var t;return"@"+e.name+t9("(",t5(e.arguments,", "),")")},NamedType:function(e){return e.name},ListType:function(e){return"["+e.type+"]"},NonNullType:function(e){return e.type+"!"},SchemaDefinition:t4(function(e){var t=e.directives,n=e.operationTypes;return t5(["schema",t5(t," "),t6(n)]," ")}),OperationTypeDefinition:function(e){var t;return e.operation+": "+e.type},ScalarTypeDefinition:t4(function(e){var t;return t5(["scalar",e.name,t5(e.directives," ")]," ")}),ObjectTypeDefinition:t4(function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t5(["type",t,t9("implements ",t5(n," & ")),t5(r," "),t6(i)]," ")}),FieldDefinition:t4(function(e){var t=e.name,n=e.arguments,r=e.type,i=e.directives;return t+(ne(n)?t9("(\n",t8(t5(n,"\n")),"\n)"):t9("(",t5(n,", "),")"))+": "+r+t9(" ",t5(i," "))}),InputValueDefinition:t4(function(e){var t=e.name,n=e.type,r=e.defaultValue,i=e.directives;return t5([t+": "+n,t9("= ",r),t5(i," ")]," ")}),InterfaceTypeDefinition:t4(function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t5(["interface",t,t9("implements ",t5(n," & ")),t5(r," "),t6(i)]," ")}),UnionTypeDefinition:t4(function(e){var t=e.name,n=e.directives,r=e.types;return t5(["union",t,t5(n," "),r&&0!==r.length?"= "+t5(r," | "):""]," ")}),EnumTypeDefinition:t4(function(e){var t=e.name,n=e.directives,r=e.values;return t5(["enum",t,t5(n," "),t6(r)]," ")}),EnumValueDefinition:t4(function(e){var t;return t5([e.name,t5(e.directives," ")]," ")}),InputObjectTypeDefinition:t4(function(e){var t=e.name,n=e.directives,r=e.fields;return t5(["input",t,t5(n," "),t6(r)]," ")}),DirectiveDefinition:t4(function(e){var t=e.name,n=e.arguments,r=e.repeatable,i=e.locations;return"directive @"+t+(ne(n)?t9("(\n",t8(t5(n,"\n")),"\n)"):t9("(",t5(n,", "),")"))+(r?" repeatable":"")+" on "+t5(i," | ")}),SchemaExtension:function(e){var t=e.directives,n=e.operationTypes;return t5(["extend schema",t5(t," "),t6(n)]," ")},ScalarTypeExtension:function(e){var t;return t5(["extend scalar",e.name,t5(e.directives," ")]," ")},ObjectTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t5(["extend type",t,t9("implements ",t5(n," & ")),t5(r," "),t6(i)]," ")},InterfaceTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t5(["extend interface",t,t9("implements ",t5(n," & ")),t5(r," "),t6(i)]," ")},UnionTypeExtension:function(e){var t=e.name,n=e.directives,r=e.types;return t5(["extend union",t,t5(n," "),r&&0!==r.length?"= "+t5(r," | "):""]," ")},EnumTypeExtension:function(e){var t=e.name,n=e.directives,r=e.values;return t5(["extend enum",t,t5(n," "),t6(r)]," ")},InputObjectTypeExtension:function(e){var t=e.name,n=e.directives,r=e.fields;return t5(["extend input",t,t5(n," "),t6(r)]," ")}};function t4(e){return function(t){return t5([t.description,e(t)],"\n")}}function t5(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return null!==(t=null==e?void 0:e.filter(function(e){return e}).join(n))&&void 0!==t?t:""}function t6(e){return t9("{\n",t8(t5(e,"\n")),"\n}")}function t9(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return null!=t&&""!==t?e+t+n:""}function t8(e){return t9(" ",e.replace(/\n/g,"\n "))}function t7(e){return -1!==e.indexOf("\n")}function ne(e){return null!=e&&e.some(t7)}var nt,nn,nr,ni={http:{includeQuery:!0,includeExtensions:!1,preserveHeaderCase:!1},headers:{accept:"*/*","content-type":"application/json"},options:{method:"POST"}},na=function(e,t){return t(e)};function no(e,t){for(var n=[],r=2;rObject.create(null),{forEach:nv,slice:ny}=Array.prototype,{hasOwnProperty:nw}=Object.prototype;class n_{constructor(e=!0,t=ng){this.weakness=e,this.makeData=t}lookup(...e){return this.lookupArray(e)}lookupArray(e){let t=this;return nv.call(e,e=>t=t.getChildTrie(e)),nw.call(t,"data")?t.data:t.data=this.makeData(ny.call(e))}peek(...e){return this.peekArray(e)}peekArray(e){let t=this;for(let n=0,r=e.length;t&&n=0;--o)t.definitions[o].kind===nL.h.OPERATION_DEFINITION&&++a;var s=nN(e),u=e.some(function(e){return e.remove}),c=function(e){return u&&e&&e.some(s)},l=new Map,f=!1,d={enter:function(e){if(c(e.directives))return f=!0,null}},h=tl(t,{Field:d,InlineFragment:d,VariableDefinition:{enter:function(){return!1}},Variable:{enter:function(e,t,n,r,a){var o=i(a);o&&o.variables.add(e.name.value)}},FragmentSpread:{enter:function(e,t,n,r,a){if(c(e.directives))return f=!0,null;var o=i(a);o&&o.fragmentSpreads.add(e.name.value)}},FragmentDefinition:{enter:function(e,t,n,r){l.set(JSON.stringify(r),e)},leave:function(e,t,n,i){return e===l.get(JSON.stringify(i))?e:a>0&&e.selectionSet.selections.every(function(e){return e.kind===nL.h.FIELD&&"__typename"===e.name.value})?(r(e.name.value).removed=!0,f=!0,null):void 0}},Directive:{leave:function(e){if(s(e))return f=!0,null}}});if(!f)return t;var p=function(e){return e.transitiveVars||(e.transitiveVars=new Set(e.variables),e.removed||e.fragmentSpreads.forEach(function(t){p(r(t)).transitiveVars.forEach(function(t){e.transitiveVars.add(t)})})),e},b=new Set;h.definitions.forEach(function(e){e.kind===nL.h.OPERATION_DEFINITION?p(n(e.name&&e.name.value)).fragmentSpreads.forEach(function(e){b.add(e)}):e.kind!==nL.h.FRAGMENT_DEFINITION||0!==a||r(e.name.value).removed||b.add(e.name.value)}),b.forEach(function(e){p(r(e)).fragmentSpreads.forEach(function(e){b.add(e)})});var m=function(e){return!!(!b.has(e)||r(e).removed)},g={enter:function(e){if(m(e.name.value))return null}};return nD(tl(h,{FragmentSpread:g,FragmentDefinition:g,OperationDefinition:{leave:function(e){if(e.variableDefinitions){var t=p(n(e.name&&e.name.value)).transitiveVars;if(t.size0},t.prototype.tearDownQuery=function(){this.isTornDown||(this.concast&&this.observer&&(this.concast.removeObserver(this.observer),delete this.concast,delete this.observer),this.stopPolling(),this.subscriptions.forEach(function(e){return e.unsubscribe()}),this.subscriptions.clear(),this.queryManager.stopQuery(this.queryId),this.observers.clear(),this.isTornDown=!0)},t}(eT);function n4(e){var t=e.options,n=t.fetchPolicy,r=t.nextFetchPolicy;return"cache-and-network"===n||"network-only"===n?e.reobserve({fetchPolicy:"cache-first",nextFetchPolicy:function(){return(this.nextFetchPolicy=r,"function"==typeof r)?r.apply(this,arguments):n}}):e.reobserve()}function n5(e){__DEV__&&Q.kG.error("Unhandled error",e.message,e.stack)}function n6(e){__DEV__&&e&&__DEV__&&Q.kG.debug("Missing cache result fields: ".concat(JSON.stringify(e)),e)}function n9(e){return"network-only"===e||"no-cache"===e||"standby"===e}nK(n3);function n8(e){return e.kind===nL.h.FIELD||e.kind===nL.h.FRAGMENT_SPREAD||e.kind===nL.h.INLINE_FRAGMENT}function n7(e){return e.kind===Kind.SCALAR_TYPE_DEFINITION||e.kind===Kind.OBJECT_TYPE_DEFINITION||e.kind===Kind.INTERFACE_TYPE_DEFINITION||e.kind===Kind.UNION_TYPE_DEFINITION||e.kind===Kind.ENUM_TYPE_DEFINITION||e.kind===Kind.INPUT_OBJECT_TYPE_DEFINITION}function re(e){return e.kind===Kind.SCALAR_TYPE_EXTENSION||e.kind===Kind.OBJECT_TYPE_EXTENSION||e.kind===Kind.INTERFACE_TYPE_EXTENSION||e.kind===Kind.UNION_TYPE_EXTENSION||e.kind===Kind.ENUM_TYPE_EXTENSION||e.kind===Kind.INPUT_OBJECT_TYPE_EXTENSION}var rt=function(){return Object.create(null)},rn=Array.prototype,rr=rn.forEach,ri=rn.slice,ra=function(){function e(e,t){void 0===e&&(e=!0),void 0===t&&(t=rt),this.weakness=e,this.makeData=t}return e.prototype.lookup=function(){for(var e=[],t=0;tclass{constructor(){this.id=["slot",rc++,Date.now(),Math.random().toString(36).slice(2),].join(":")}hasValue(){for(let e=rs;e;e=e.parent)if(this.id in e.slots){let t=e.slots[this.id];if(t===ru)break;return e!==rs&&(rs.slots[this.id]=t),!0}return rs&&(rs.slots[this.id]=ru),!1}getValue(){if(this.hasValue())return rs.slots[this.id]}withValue(e,t,n,r){let i={__proto__:null,[this.id]:e},a=rs;rs={parent:a,slots:i};try{return t.apply(r,n)}finally{rs=a}}static bind(e){let t=rs;return function(){let n=rs;try{return rs=t,e.apply(this,arguments)}finally{rs=n}}}static noContext(e,t,n){if(!rs)return e.apply(n,t);{let r=rs;try{return rs=null,e.apply(n,t)}finally{rs=r}}}};function rf(e){try{return e()}catch(t){}}let rd="@wry/context:Slot",rh=rf(()=>globalThis)||rf(()=>global)||Object.create(null),rp=rh,rb=rp[rd]||Array[rd]||function(e){try{Object.defineProperty(rp,rd,{value:e,enumerable:!1,writable:!1,configurable:!0})}finally{return e}}(rl()),{bind:rm,noContext:rg}=rb;function rv(){}var ry=function(){function e(e,t){void 0===e&&(e=1/0),void 0===t&&(t=rv),this.max=e,this.dispose=t,this.map=new Map,this.newest=null,this.oldest=null}return e.prototype.has=function(e){return this.map.has(e)},e.prototype.get=function(e){var t=this.getNode(e);return t&&t.value},e.prototype.getNode=function(e){var t=this.map.get(e);if(t&&t!==this.newest){var n=t.older,r=t.newer;r&&(r.older=n),n&&(n.newer=r),t.older=this.newest,t.older.newer=t,t.newer=null,this.newest=t,t===this.oldest&&(this.oldest=r)}return t},e.prototype.set=function(e,t){var n=this.getNode(e);return n?n.value=t:(n={key:e,value:t,newer:null,older:this.newest},this.newest&&(this.newest.newer=n),this.newest=n,this.oldest=this.oldest||n,this.map.set(e,n),n.value)},e.prototype.clean=function(){for(;this.oldest&&this.map.size>this.max;)this.delete(this.oldest.key)},e.prototype.delete=function(e){var t=this.map.get(e);return!!t&&(t===this.newest&&(this.newest=t.older),t===this.oldest&&(this.oldest=t.newer),t.newer&&(t.newer.older=t.older),t.older&&(t.older.newer=t.newer),this.map.delete(e),this.dispose(t.value,e),!0)},e}(),rw=new rb,r_=Object.prototype.hasOwnProperty,rE=void 0===(n=Array.from)?function(e){var t=[];return e.forEach(function(e){return t.push(e)}),t}:n;function rS(e){var t=e.unsubscribe;"function"==typeof t&&(e.unsubscribe=void 0,t())}var rk=[],rx=100;function rT(e,t){if(!e)throw Error(t||"assertion failure")}function rM(e,t){var n=e.length;return n>0&&n===t.length&&e[n-1]===t[n-1]}function rO(e){switch(e.length){case 0:throw Error("unknown value");case 1:return e[0];case 2:throw e[1]}}function rA(e){return e.slice(0)}var rL=function(){function e(t){this.fn=t,this.parents=new Set,this.childValues=new Map,this.dirtyChildren=null,this.dirty=!0,this.recomputing=!1,this.value=[],this.deps=null,++e.count}return e.prototype.peek=function(){if(1===this.value.length&&!rN(this))return rC(this),this.value[0]},e.prototype.recompute=function(e){return rT(!this.recomputing,"already recomputing"),rC(this),rN(this)?rI(this,e):rO(this.value)},e.prototype.setDirty=function(){this.dirty||(this.dirty=!0,this.value.length=0,rR(this),rS(this))},e.prototype.dispose=function(){var e=this;this.setDirty(),rH(this),rF(this,function(t,n){t.setDirty(),r$(t,e)})},e.prototype.forget=function(){this.dispose()},e.prototype.dependOn=function(e){e.add(this),this.deps||(this.deps=rk.pop()||new Set),this.deps.add(e)},e.prototype.forgetDeps=function(){var e=this;this.deps&&(rE(this.deps).forEach(function(t){return t.delete(e)}),this.deps.clear(),rk.push(this.deps),this.deps=null)},e.count=0,e}();function rC(e){var t=rw.getValue();if(t)return e.parents.add(t),t.childValues.has(e)||t.childValues.set(e,[]),rN(e)?rY(t,e):rB(t,e),t}function rI(e,t){return rH(e),rw.withValue(e,rD,[e,t]),rz(e,t)&&rP(e),rO(e.value)}function rD(e,t){e.recomputing=!0,e.value.length=0;try{e.value[0]=e.fn.apply(null,t)}catch(n){e.value[1]=n}e.recomputing=!1}function rN(e){return e.dirty||!!(e.dirtyChildren&&e.dirtyChildren.size)}function rP(e){e.dirty=!1,!rN(e)&&rj(e)}function rR(e){rF(e,rY)}function rj(e){rF(e,rB)}function rF(e,t){var n=e.parents.size;if(n)for(var r=rE(e.parents),i=0;i0&&e.childValues.forEach(function(t,n){r$(e,n)}),e.forgetDeps(),rT(null===e.dirtyChildren)}function r$(e,t){t.parents.delete(e),e.childValues.delete(t),rU(e,t)}function rz(e,t){if("function"==typeof e.subscribe)try{rS(e),e.unsubscribe=e.subscribe.apply(null,t)}catch(n){return e.setDirty(),!1}return!0}var rG={setDirty:!0,dispose:!0,forget:!0};function rW(e){var t=new Map,n=e&&e.subscribe;function r(e){var r=rw.getValue();if(r){var i=t.get(e);i||t.set(e,i=new Set),r.dependOn(i),"function"==typeof n&&(rS(i),i.unsubscribe=n(e))}}return r.dirty=function(e,n){var r=t.get(e);if(r){var i=n&&r_.call(rG,n)?n:"setDirty";rE(r).forEach(function(e){return e[i]()}),t.delete(e),rS(r)}},r}function rK(){var e=new ra("function"==typeof WeakMap);return function(){return e.lookupArray(arguments)}}var rV=rK(),rq=new Set;function rZ(e,t){void 0===t&&(t=Object.create(null));var n=new ry(t.max||65536,function(e){return e.dispose()}),r=t.keyArgs,i=t.makeCacheKey||rK(),a=function(){var a=i.apply(null,r?r.apply(null,arguments):arguments);if(void 0===a)return e.apply(null,arguments);var o=n.get(a);o||(n.set(a,o=new rL(e)),o.subscribe=t.subscribe,o.forget=function(){return n.delete(a)});var s=o.recompute(Array.prototype.slice.call(arguments));return n.set(a,o),rq.add(n),rw.hasValue()||(rq.forEach(function(e){return e.clean()}),rq.clear()),s};function o(e){var t=n.get(e);t&&t.setDirty()}function s(e){var t=n.get(e);if(t)return t.peek()}function u(e){return n.delete(e)}return Object.defineProperty(a,"size",{get:function(){return n.map.size},configurable:!1,enumerable:!1}),a.dirtyKey=o,a.dirty=function(){o(i.apply(null,arguments))},a.peekKey=s,a.peek=function(){return s(i.apply(null,arguments))},a.forgetKey=u,a.forget=function(){return u(i.apply(null,arguments))},a.makeCacheKey=i,a.getKey=r?function(){return i.apply(null,r.apply(null,arguments))}:i,Object.freeze(a)}var rX=new rb,rJ=new WeakMap;function rQ(e){var t=rJ.get(e);return t||rJ.set(e,t={vars:new Set,dep:rW()}),t}function r1(e){rQ(e).vars.forEach(function(t){return t.forgetCache(e)})}function r0(e){rQ(e).vars.forEach(function(t){return t.attachCache(e)})}function r2(e){var t=new Set,n=new Set,r=function(a){if(arguments.length>0){if(e!==a){e=a,t.forEach(function(e){rQ(e).dep.dirty(r),r3(e)});var o=Array.from(n);n.clear(),o.forEach(function(t){return t(e)})}}else{var s=rX.getValue();s&&(i(s),rQ(s).dep(r))}return e};r.onNextChange=function(e){return n.add(e),function(){n.delete(e)}};var i=r.attachCache=function(e){return t.add(e),rQ(e).vars.add(r),r};return r.forgetCache=function(e){return t.delete(e)},r}function r3(e){e.broadcastWatches&&e.broadcastWatches()}var r4=function(){function e(e){var t=e.cache,n=e.client,r=e.resolvers,i=e.fragmentMatcher;this.selectionsToResolveCache=new WeakMap,this.cache=t,n&&(this.client=n),r&&this.addResolvers(r),i&&this.setFragmentMatcher(i)}return e.prototype.addResolvers=function(e){var t=this;this.resolvers=this.resolvers||{},Array.isArray(e)?e.forEach(function(e){t.resolvers=tj(t.resolvers,e)}):this.resolvers=tj(this.resolvers,e)},e.prototype.setResolvers=function(e){this.resolvers={},this.addResolvers(e)},e.prototype.getResolvers=function(){return this.resolvers||{}},e.prototype.runResolvers=function(e){var t=e.document,n=e.remoteResult,r=e.context,i=e.variables,a=e.onlyRunForcedResolvers,o=void 0!==a&&a;return(0,en.mG)(this,void 0,void 0,function(){return(0,en.Jh)(this,function(e){return t?[2,this.resolveDocument(t,n.data,r,i,this.fragmentMatcher,o).then(function(e){return(0,en.pi)((0,en.pi)({},n),{data:e.result})})]:[2,n]})})},e.prototype.setFragmentMatcher=function(e){this.fragmentMatcher=e},e.prototype.getFragmentMatcher=function(){return this.fragmentMatcher},e.prototype.clientQuery=function(e){return tb(["client"],e)&&this.resolvers?e:null},e.prototype.serverQuery=function(e){return n$(e)},e.prototype.prepareContext=function(e){var t=this.cache;return(0,en.pi)((0,en.pi)({},e),{cache:t,getCacheKey:function(e){return t.identify(e)}})},e.prototype.addExportedVariables=function(e,t,n){return void 0===t&&(t={}),void 0===n&&(n={}),(0,en.mG)(this,void 0,void 0,function(){return(0,en.Jh)(this,function(r){return e?[2,this.resolveDocument(e,this.buildRootValueFromCache(e,t)||{},this.prepareContext(n),t).then(function(e){return(0,en.pi)((0,en.pi)({},t),e.exportedVariables)})]:[2,(0,en.pi)({},t)]})})},e.prototype.shouldForceResolvers=function(e){var t=!1;return tl(e,{Directive:{enter:function(e){if("client"===e.name.value&&e.arguments&&(t=e.arguments.some(function(e){return"always"===e.name.value&&"BooleanValue"===e.value.kind&&!0===e.value.value})))return tc}}}),t},e.prototype.buildRootValueFromCache=function(e,t){return this.cache.diff({query:nH(e),variables:t,returnPartialData:!0,optimistic:!1}).result},e.prototype.resolveDocument=function(e,t,n,r,i,a){return void 0===n&&(n={}),void 0===r&&(r={}),void 0===i&&(i=function(){return!0}),void 0===a&&(a=!1),(0,en.mG)(this,void 0,void 0,function(){var o,s,u,c,l,f,d,h,p,b,m;return(0,en.Jh)(this,function(g){return o=e9(e),s=e4(e),u=eL(s),c=this.collectSelectionsToResolve(o,u),f=(l=o.operation)?l.charAt(0).toUpperCase()+l.slice(1):"Query",d=this,h=d.cache,p=d.client,b={fragmentMap:u,context:(0,en.pi)((0,en.pi)({},n),{cache:h,client:p}),variables:r,fragmentMatcher:i,defaultOperationType:f,exportedVariables:{},selectionsToResolve:c,onlyRunForcedResolvers:a},m=!1,[2,this.resolveSelectionSet(o.selectionSet,m,t,b).then(function(e){return{result:e,exportedVariables:b.exportedVariables}})]})})},e.prototype.resolveSelectionSet=function(e,t,n,r){return(0,en.mG)(this,void 0,void 0,function(){var i,a,o,s,u,c=this;return(0,en.Jh)(this,function(l){return i=r.fragmentMap,a=r.context,o=r.variables,s=[n],u=function(e){return(0,en.mG)(c,void 0,void 0,function(){var u,c;return(0,en.Jh)(this,function(l){return(t||r.selectionsToResolve.has(e))&&td(e,o)?eQ(e)?[2,this.resolveField(e,t,n,r).then(function(t){var n;void 0!==t&&s.push(((n={})[eX(e)]=t,n))})]:(e1(e)?u=e:(u=i[e.name.value],__DEV__?(0,Q.kG)(u,"No fragment named ".concat(e.name.value)):(0,Q.kG)(u,11)),u&&u.typeCondition&&(c=u.typeCondition.name.value,r.fragmentMatcher(n,c,a)))?[2,this.resolveSelectionSet(u.selectionSet,t,n,r).then(function(e){s.push(e)})]:[2]:[2]})})},[2,Promise.all(e.selections.map(u)).then(function(){return tF(s)})]})})},e.prototype.resolveField=function(e,t,n,r){return(0,en.mG)(this,void 0,void 0,function(){var i,a,o,s,u,c,l,f,d,h=this;return(0,en.Jh)(this,function(p){return n?(i=r.variables,a=e.name.value,o=eX(e),s=a!==o,c=Promise.resolve(u=n[o]||n[a]),(!r.onlyRunForcedResolvers||this.shouldForceResolvers(e))&&(l=n.__typename||r.defaultOperationType,(f=this.resolvers&&this.resolvers[l])&&(d=f[s?a:o])&&(c=Promise.resolve(rX.withValue(this.cache,d,[n,eZ(e,i),r.context,{field:e,fragmentMap:r.fragmentMap},])))),[2,c.then(function(n){if(void 0===n&&(n=u),e.directives&&e.directives.forEach(function(e){"export"===e.name.value&&e.arguments&&e.arguments.forEach(function(e){"as"===e.name.value&&"StringValue"===e.value.kind&&(r.exportedVariables[e.value.value]=n)})}),!e.selectionSet||null==n)return n;var i,a,o=null!==(a=null===(i=e.directives)||void 0===i?void 0:i.some(function(e){return"client"===e.name.value}))&&void 0!==a&&a;return Array.isArray(n)?h.resolveSubSelectedArray(e,t||o,n,r):e.selectionSet?h.resolveSelectionSet(e.selectionSet,t||o,n,r):void 0})]):[2,null]})})},e.prototype.resolveSubSelectedArray=function(e,t,n,r){var i=this;return Promise.all(n.map(function(n){return null===n?null:Array.isArray(n)?i.resolveSubSelectedArray(e,t,n,r):e.selectionSet?i.resolveSelectionSet(e.selectionSet,t,n,r):void 0}))},e.prototype.collectSelectionsToResolve=function(e,t){var n=function(e){return!Array.isArray(e)},r=this.selectionsToResolveCache;function i(e){if(!r.has(e)){var a=new Set;r.set(e,a),tl(e,{Directive:function(e,t,r,i,o){"client"===e.name.value&&o.forEach(function(e){n(e)&&n8(e)&&a.add(e)})},FragmentSpread:function(e,r,o,s,u){var c=t[e.name.value];__DEV__?(0,Q.kG)(c,"No fragment named ".concat(e.name.value)):(0,Q.kG)(c,12);var l=i(c);l.size>0&&(u.forEach(function(e){n(e)&&n8(e)&&a.add(e)}),a.add(e),l.forEach(function(e){a.add(e)}))}})}return r.get(e)}return i(e)},e}(),r5=new(t_.mr?WeakMap:Map);function r6(e,t){var n=e[t];"function"==typeof n&&(e[t]=function(){return r5.set(e,(r5.get(e)+1)%1e15),n.apply(this,arguments)})}function r9(e){e.notifyTimeout&&(clearTimeout(e.notifyTimeout),e.notifyTimeout=void 0)}var r8=function(){function e(e,t){void 0===t&&(t=e.generateQueryId()),this.queryId=t,this.listeners=new Set,this.document=null,this.lastRequestId=1,this.subscriptions=new Set,this.stopped=!1,this.dirty=!1,this.observableQuery=null;var n=this.cache=e.cache;r5.has(n)||(r5.set(n,0),r6(n,"evict"),r6(n,"modify"),r6(n,"reset"))}return e.prototype.init=function(e){var t=e.networkStatus||nZ.I.loading;return this.variables&&this.networkStatus!==nZ.I.loading&&!(0,nm.D)(this.variables,e.variables)&&(t=nZ.I.setVariables),(0,nm.D)(e.variables,this.variables)||(this.lastDiff=void 0),Object.assign(this,{document:e.document,variables:e.variables,networkError:null,graphQLErrors:this.graphQLErrors||[],networkStatus:t}),e.observableQuery&&this.setObservableQuery(e.observableQuery),e.lastRequestId&&(this.lastRequestId=e.lastRequestId),this},e.prototype.reset=function(){r9(this),this.dirty=!1},e.prototype.getDiff=function(e){void 0===e&&(e=this.variables);var t=this.getDiffOptions(e);if(this.lastDiff&&(0,nm.D)(t,this.lastDiff.options))return this.lastDiff.diff;this.updateWatch(this.variables=e);var n=this.observableQuery;if(n&&"no-cache"===n.options.fetchPolicy)return{complete:!1};var r=this.cache.diff(t);return this.updateLastDiff(r,t),r},e.prototype.updateLastDiff=function(e,t){this.lastDiff=e?{diff:e,options:t||this.getDiffOptions()}:void 0},e.prototype.getDiffOptions=function(e){var t;return void 0===e&&(e=this.variables),{query:this.document,variables:e,returnPartialData:!0,optimistic:!0,canonizeResults:null===(t=this.observableQuery)||void 0===t?void 0:t.options.canonizeResults}},e.prototype.setDiff=function(e){var t=this,n=this.lastDiff&&this.lastDiff.diff;this.updateLastDiff(e),this.dirty||(0,nm.D)(n&&n.result,e&&e.result)||(this.dirty=!0,this.notifyTimeout||(this.notifyTimeout=setTimeout(function(){return t.notify()},0)))},e.prototype.setObservableQuery=function(e){var t=this;e!==this.observableQuery&&(this.oqListener&&this.listeners.delete(this.oqListener),this.observableQuery=e,e?(e.queryInfo=this,this.listeners.add(this.oqListener=function(){t.getDiff().fromOptimisticTransaction?e.observe():n4(e)})):delete this.oqListener)},e.prototype.notify=function(){var e=this;r9(this),this.shouldNotify()&&this.listeners.forEach(function(t){return t(e)}),this.dirty=!1},e.prototype.shouldNotify=function(){if(!this.dirty||!this.listeners.size)return!1;if((0,nZ.O)(this.networkStatus)&&this.observableQuery){var e=this.observableQuery.options.fetchPolicy;if("cache-only"!==e&&"cache-and-network"!==e)return!1}return!0},e.prototype.stop=function(){if(!this.stopped){this.stopped=!0,this.reset(),this.cancel(),this.cancel=e.prototype.cancel,this.subscriptions.forEach(function(e){return e.unsubscribe()});var t=this.observableQuery;t&&t.stopPolling()}},e.prototype.cancel=function(){},e.prototype.updateWatch=function(e){var t=this;void 0===e&&(e=this.variables);var n=this.observableQuery;if(!n||"no-cache"!==n.options.fetchPolicy){var r=(0,en.pi)((0,en.pi)({},this.getDiffOptions(e)),{watcher:this,callback:function(e){return t.setDiff(e)}});this.lastWatch&&(0,nm.D)(r,this.lastWatch)||(this.cancel(),this.cancel=this.cache.watch(this.lastWatch=r))}},e.prototype.resetLastWrite=function(){this.lastWrite=void 0},e.prototype.shouldWrite=function(e,t){var n=this.lastWrite;return!(n&&n.dmCount===r5.get(this.cache)&&(0,nm.D)(t,n.variables)&&(0,nm.D)(e.data,n.result.data))},e.prototype.markResult=function(e,t,n,r){var i=this,a=new tB,o=(0,tP.O)(e.errors)?e.errors.slice(0):[];if(this.reset(),"incremental"in e&&(0,tP.O)(e.incremental)){var s=tG(this.getDiff().result,e);e.data=s}else if("hasNext"in e&&e.hasNext){var u=this.getDiff();e.data=a.merge(u.result,e.data)}this.graphQLErrors=o,"no-cache"===n.fetchPolicy?this.updateLastDiff({result:e.data,complete:!0},this.getDiffOptions(n.variables)):0!==r&&(r7(e,n.errorPolicy)?this.cache.performTransaction(function(a){if(i.shouldWrite(e,n.variables))a.writeQuery({query:t,data:e.data,variables:n.variables,overwrite:1===r}),i.lastWrite={result:e,variables:n.variables,dmCount:r5.get(i.cache)};else if(i.lastDiff&&i.lastDiff.diff.complete){e.data=i.lastDiff.diff.result;return}var o=i.getDiffOptions(n.variables),s=a.diff(o);i.stopped||i.updateWatch(n.variables),i.updateLastDiff(s,o),s.complete&&(e.data=s.result)}):this.lastWrite=void 0)},e.prototype.markReady=function(){return this.networkError=null,this.networkStatus=nZ.I.ready},e.prototype.markError=function(e){return this.networkStatus=nZ.I.error,this.lastWrite=void 0,this.reset(),e.graphQLErrors&&(this.graphQLErrors=e.graphQLErrors),e.networkError&&(this.networkError=e.networkError),e},e}();function r7(e,t){void 0===t&&(t="none");var n="ignore"===t||"all"===t,r=!nO(e);return!r&&n&&e.data&&(r=!0),r}var ie=Object.prototype.hasOwnProperty,it=function(){function e(e){var t=e.cache,n=e.link,r=e.defaultOptions,i=e.queryDeduplication,a=void 0!==i&&i,o=e.onBroadcast,s=e.ssrMode,u=void 0!==s&&s,c=e.clientAwareness,l=void 0===c?{}:c,f=e.localState,d=e.assumeImmutableResults;this.clientAwareness={},this.queries=new Map,this.fetchCancelFns=new Map,this.transformCache=new(t_.mr?WeakMap:Map),this.queryIdCounter=1,this.requestIdCounter=1,this.mutationIdCounter=1,this.inFlightLinkObservables=new Map,this.cache=t,this.link=n,this.defaultOptions=r||Object.create(null),this.queryDeduplication=a,this.clientAwareness=l,this.localState=f||new r4({cache:t}),this.ssrMode=u,this.assumeImmutableResults=!!d,(this.onBroadcast=o)&&(this.mutationStore=Object.create(null))}return e.prototype.stop=function(){var e=this;this.queries.forEach(function(t,n){e.stopQueryNoBroadcast(n)}),this.cancelPendingFetches(__DEV__?new Q.ej("QueryManager stopped while query was in flight"):new Q.ej(14))},e.prototype.cancelPendingFetches=function(e){this.fetchCancelFns.forEach(function(t){return t(e)}),this.fetchCancelFns.clear()},e.prototype.mutate=function(e){var t,n,r=e.mutation,i=e.variables,a=e.optimisticResponse,o=e.updateQueries,s=e.refetchQueries,u=void 0===s?[]:s,c=e.awaitRefetchQueries,l=void 0!==c&&c,f=e.update,d=e.onQueryUpdated,h=e.fetchPolicy,p=void 0===h?(null===(t=this.defaultOptions.mutate)||void 0===t?void 0:t.fetchPolicy)||"network-only":h,b=e.errorPolicy,m=void 0===b?(null===(n=this.defaultOptions.mutate)||void 0===n?void 0:n.errorPolicy)||"none":b,g=e.keepRootFields,v=e.context;return(0,en.mG)(this,void 0,void 0,function(){var e,t,n,s,c,h;return(0,en.Jh)(this,function(b){switch(b.label){case 0:if(__DEV__?(0,Q.kG)(r,"mutation option is required. You must specify your GraphQL document in the mutation option."):(0,Q.kG)(r,15),__DEV__?(0,Q.kG)("network-only"===p||"no-cache"===p,"Mutations support only 'network-only' or 'no-cache' fetchPolicy strings. The default `network-only` behavior automatically writes mutation results to the cache. Passing `no-cache` skips the cache write."):(0,Q.kG)("network-only"===p||"no-cache"===p,16),e=this.generateMutationId(),n=(t=this.transform(r)).document,s=t.hasClientExports,r=this.cache.transformForLink(n),i=this.getVariables(r,i),!s)return[3,2];return[4,this.localState.addExportedVariables(r,i,v)];case 1:i=b.sent(),b.label=2;case 2:return c=this.mutationStore&&(this.mutationStore[e]={mutation:r,variables:i,loading:!0,error:null}),a&&this.markMutationOptimistic(a,{mutationId:e,document:r,variables:i,fetchPolicy:p,errorPolicy:m,context:v,updateQueries:o,update:f,keepRootFields:g}),this.broadcastQueries(),h=this,[2,new Promise(function(t,n){return nM(h.getObservableFromLink(r,(0,en.pi)((0,en.pi)({},v),{optimisticResponse:a}),i,!1),function(t){if(nO(t)&&"none"===m)throw new tN.cA({graphQLErrors:nA(t)});c&&(c.loading=!1,c.error=null);var n=(0,en.pi)({},t);return"function"==typeof u&&(u=u(n)),"ignore"===m&&nO(n)&&delete n.errors,h.markMutationResult({mutationId:e,result:n,document:r,variables:i,fetchPolicy:p,errorPolicy:m,context:v,update:f,updateQueries:o,awaitRefetchQueries:l,refetchQueries:u,removeOptimistic:a?e:void 0,onQueryUpdated:d,keepRootFields:g})}).subscribe({next:function(e){h.broadcastQueries(),"hasNext"in e&&!1!==e.hasNext||t(e)},error:function(t){c&&(c.loading=!1,c.error=t),a&&h.cache.removeOptimistic(e),h.broadcastQueries(),n(t instanceof tN.cA?t:new tN.cA({networkError:t}))}})})]}})})},e.prototype.markMutationResult=function(e,t){var n=this;void 0===t&&(t=this.cache);var r=e.result,i=[],a="no-cache"===e.fetchPolicy;if(!a&&r7(r,e.errorPolicy)){if(tU(r)||i.push({result:r.data,dataId:"ROOT_MUTATION",query:e.document,variables:e.variables}),tU(r)&&(0,tP.O)(r.incremental)){var o=t.diff({id:"ROOT_MUTATION",query:this.transform(e.document).asQuery,variables:e.variables,optimistic:!1,returnPartialData:!0}),s=void 0;o.result&&(s=tG(o.result,r)),void 0!==s&&(r.data=s,i.push({result:s,dataId:"ROOT_MUTATION",query:e.document,variables:e.variables}))}var u=e.updateQueries;u&&this.queries.forEach(function(e,a){var o=e.observableQuery,s=o&&o.queryName;if(s&&ie.call(u,s)){var c,l=u[s],f=n.queries.get(a),d=f.document,h=f.variables,p=t.diff({query:d,variables:h,returnPartialData:!0,optimistic:!1}),b=p.result;if(p.complete&&b){var m=l(b,{mutationResult:r,queryName:d&&e3(d)||void 0,queryVariables:h});m&&i.push({result:m,dataId:"ROOT_QUERY",query:d,variables:h})}}})}if(i.length>0||e.refetchQueries||e.update||e.onQueryUpdated||e.removeOptimistic){var c=[];if(this.refetchQueries({updateCache:function(t){a||i.forEach(function(e){return t.write(e)});var o=e.update,s=!t$(r)||tU(r)&&!r.hasNext;if(o){if(!a){var u=t.diff({id:"ROOT_MUTATION",query:n.transform(e.document).asQuery,variables:e.variables,optimistic:!1,returnPartialData:!0});u.complete&&("incremental"in(r=(0,en.pi)((0,en.pi)({},r),{data:u.result}))&&delete r.incremental,"hasNext"in r&&delete r.hasNext)}s&&o(t,r,{context:e.context,variables:e.variables})}a||e.keepRootFields||!s||t.modify({id:"ROOT_MUTATION",fields:function(e,t){var n=t.fieldName,r=t.DELETE;return"__typename"===n?e:r}})},include:e.refetchQueries,optimistic:!1,removeOptimistic:e.removeOptimistic,onQueryUpdated:e.onQueryUpdated||null}).forEach(function(e){return c.push(e)}),e.awaitRefetchQueries||e.onQueryUpdated)return Promise.all(c).then(function(){return r})}return Promise.resolve(r)},e.prototype.markMutationOptimistic=function(e,t){var n=this,r="function"==typeof e?e(t.variables):e;return this.cache.recordOptimisticTransaction(function(e){try{n.markMutationResult((0,en.pi)((0,en.pi)({},t),{result:{data:r}}),e)}catch(i){__DEV__&&Q.kG.error(i)}},t.mutationId)},e.prototype.fetchQuery=function(e,t,n){return this.fetchQueryObservable(e,t,n).promise},e.prototype.getQueryStore=function(){var e=Object.create(null);return this.queries.forEach(function(t,n){e[n]={variables:t.variables,networkStatus:t.networkStatus,networkError:t.networkError,graphQLErrors:t.graphQLErrors}}),e},e.prototype.resetErrors=function(e){var t=this.queries.get(e);t&&(t.networkError=void 0,t.graphQLErrors=[])},e.prototype.transform=function(e){var t=this.transformCache;if(!t.has(e)){var n=this.cache.transformDocument(e),r=nY(n),i=this.localState.clientQuery(n),a=r&&this.localState.serverQuery(r),o={document:n,hasClientExports:tm(n),hasForcedResolvers:this.localState.shouldForceResolvers(n),clientQuery:i,serverQuery:a,defaultVars:e8(e2(n)),asQuery:(0,en.pi)((0,en.pi)({},n),{definitions:n.definitions.map(function(e){return"OperationDefinition"===e.kind&&"query"!==e.operation?(0,en.pi)((0,en.pi)({},e),{operation:"query"}):e})})},s=function(e){e&&!t.has(e)&&t.set(e,o)};s(e),s(n),s(i),s(a)}return t.get(e)},e.prototype.getVariables=function(e,t){return(0,en.pi)((0,en.pi)({},this.transform(e).defaultVars),t)},e.prototype.watchQuery=function(e){void 0===(e=(0,en.pi)((0,en.pi)({},e),{variables:this.getVariables(e.query,e.variables)})).notifyOnNetworkStatusChange&&(e.notifyOnNetworkStatusChange=!1);var t=new r8(this),n=new n3({queryManager:this,queryInfo:t,options:e});return this.queries.set(n.queryId,t),t.init({document:n.query,observableQuery:n,variables:n.variables}),n},e.prototype.query=function(e,t){var n=this;return void 0===t&&(t=this.generateQueryId()),__DEV__?(0,Q.kG)(e.query,"query option is required. You must specify your GraphQL document in the query option."):(0,Q.kG)(e.query,17),__DEV__?(0,Q.kG)("Document"===e.query.kind,'You must wrap the query string in a "gql" tag.'):(0,Q.kG)("Document"===e.query.kind,18),__DEV__?(0,Q.kG)(!e.returnPartialData,"returnPartialData option only supported on watchQuery."):(0,Q.kG)(!e.returnPartialData,19),__DEV__?(0,Q.kG)(!e.pollInterval,"pollInterval option only supported on watchQuery."):(0,Q.kG)(!e.pollInterval,20),this.fetchQuery(t,e).finally(function(){return n.stopQuery(t)})},e.prototype.generateQueryId=function(){return String(this.queryIdCounter++)},e.prototype.generateRequestId=function(){return this.requestIdCounter++},e.prototype.generateMutationId=function(){return String(this.mutationIdCounter++)},e.prototype.stopQueryInStore=function(e){this.stopQueryInStoreNoBroadcast(e),this.broadcastQueries()},e.prototype.stopQueryInStoreNoBroadcast=function(e){var t=this.queries.get(e);t&&t.stop()},e.prototype.clearStore=function(e){return void 0===e&&(e={discardWatches:!0}),this.cancelPendingFetches(__DEV__?new Q.ej("Store reset while query was in flight (not completed in link chain)"):new Q.ej(21)),this.queries.forEach(function(e){e.observableQuery?e.networkStatus=nZ.I.loading:e.stop()}),this.mutationStore&&(this.mutationStore=Object.create(null)),this.cache.reset(e)},e.prototype.getObservableQueries=function(e){var t=this;void 0===e&&(e="active");var n=new Map,r=new Map,i=new Set;return Array.isArray(e)&&e.forEach(function(e){"string"==typeof e?r.set(e,!1):eN(e)?r.set(t.transform(e).document,!1):(0,eO.s)(e)&&e.query&&i.add(e)}),this.queries.forEach(function(t,i){var a=t.observableQuery,o=t.document;if(a){if("all"===e){n.set(i,a);return}var s=a.queryName;if("standby"===a.options.fetchPolicy||"active"===e&&!a.hasObservers())return;("active"===e||s&&r.has(s)||o&&r.has(o))&&(n.set(i,a),s&&r.set(s,!0),o&&r.set(o,!0))}}),i.size&&i.forEach(function(e){var r=nG("legacyOneTimeQuery"),i=t.getQuery(r).init({document:e.query,variables:e.variables}),a=new n3({queryManager:t,queryInfo:i,options:(0,en.pi)((0,en.pi)({},e),{fetchPolicy:"network-only"})});(0,Q.kG)(a.queryId===r),i.setObservableQuery(a),n.set(r,a)}),__DEV__&&r.size&&r.forEach(function(e,t){!e&&__DEV__&&Q.kG.warn("Unknown query ".concat("string"==typeof t?"named ":"").concat(JSON.stringify(t,null,2)," requested in refetchQueries options.include array"))}),n},e.prototype.reFetchObservableQueries=function(e){var t=this;void 0===e&&(e=!1);var n=[];return this.getObservableQueries(e?"all":"active").forEach(function(r,i){var a=r.options.fetchPolicy;r.resetLastResults(),(e||"standby"!==a&&"cache-only"!==a)&&n.push(r.refetch()),t.getQuery(i).setDiff(null)}),this.broadcastQueries(),Promise.all(n)},e.prototype.setObservableQuery=function(e){this.getQuery(e.queryId).setObservableQuery(e)},e.prototype.startGraphQLSubscription=function(e){var t=this,n=e.query,r=e.fetchPolicy,i=e.errorPolicy,a=e.variables,o=e.context,s=void 0===o?{}:o;n=this.transform(n).document,a=this.getVariables(n,a);var u=function(e){return t.getObservableFromLink(n,s,e).map(function(a){"no-cache"!==r&&(r7(a,i)&&t.cache.write({query:n,result:a.data,dataId:"ROOT_SUBSCRIPTION",variables:e}),t.broadcastQueries());var o=nO(a),s=(0,tN.ls)(a);if(o||s){var u={};throw o&&(u.graphQLErrors=a.errors),s&&(u.protocolErrors=a.extensions[tN.YG]),new tN.cA(u)}return a})};if(this.transform(n).hasClientExports){var c=this.localState.addExportedVariables(n,a,s).then(u);return new eT(function(e){var t=null;return c.then(function(n){return t=n.subscribe(e)},e.error),function(){return t&&t.unsubscribe()}})}return u(a)},e.prototype.stopQuery=function(e){this.stopQueryNoBroadcast(e),this.broadcastQueries()},e.prototype.stopQueryNoBroadcast=function(e){this.stopQueryInStoreNoBroadcast(e),this.removeQuery(e)},e.prototype.removeQuery=function(e){this.fetchCancelFns.delete(e),this.queries.has(e)&&(this.getQuery(e).stop(),this.queries.delete(e))},e.prototype.broadcastQueries=function(){this.onBroadcast&&this.onBroadcast(),this.queries.forEach(function(e){return e.notify()})},e.prototype.getLocalState=function(){return this.localState},e.prototype.getObservableFromLink=function(e,t,n,r){var i,a,o=this;void 0===r&&(r=null!==(i=null==t?void 0:t.queryDeduplication)&&void 0!==i?i:this.queryDeduplication);var s=this.transform(e).serverQuery;if(s){var u=this,c=u.inFlightLinkObservables,l=u.link,f={query:s,variables:n,operationName:e3(s)||void 0,context:this.prepareContext((0,en.pi)((0,en.pi)({},t),{forceFetch:!r}))};if(t=f.context,r){var d=c.get(s)||new Map;c.set(s,d);var h=nx(n);if(!(a=d.get(h))){var p=new nq([np(l,f)]);d.set(h,a=p),p.beforeNext(function(){d.delete(h)&&d.size<1&&c.delete(s)})}}else a=new nq([np(l,f)])}else a=new nq([eT.of({data:{}})]),t=this.prepareContext(t);var b=this.transform(e).clientQuery;return b&&(a=nM(a,function(e){return o.localState.runResolvers({document:b,remoteResult:e,context:t,variables:n})})),a},e.prototype.getResultsFromLink=function(e,t,n){var r=e.lastRequestId=this.generateRequestId(),i=this.cache.transformForLink(this.transform(e.document).document);return nM(this.getObservableFromLink(i,n.context,n.variables),function(a){var o=nA(a),s=o.length>0;if(r>=e.lastRequestId){if(s&&"none"===n.errorPolicy)throw e.markError(new tN.cA({graphQLErrors:o}));e.markResult(a,i,n,t),e.markReady()}var u={data:a.data,loading:!1,networkStatus:nZ.I.ready};return s&&"ignore"!==n.errorPolicy&&(u.errors=o,u.networkStatus=nZ.I.error),u},function(t){var n=(0,tN.MS)(t)?t:new tN.cA({networkError:t});throw r>=e.lastRequestId&&e.markError(n),n})},e.prototype.fetchQueryObservable=function(e,t,n){return this.fetchConcastWithInfo(e,t,n).concast},e.prototype.fetchConcastWithInfo=function(e,t,n){var r,i,a=this;void 0===n&&(n=nZ.I.loading);var o=this.transform(t.query).document,s=this.getVariables(o,t.variables),u=this.getQuery(e),c=this.defaultOptions.watchQuery,l=t.fetchPolicy,f=void 0===l?c&&c.fetchPolicy||"cache-first":l,d=t.errorPolicy,h=void 0===d?c&&c.errorPolicy||"none":d,p=t.returnPartialData,b=void 0!==p&&p,m=t.notifyOnNetworkStatusChange,g=void 0!==m&&m,v=t.context,y=void 0===v?{}:v,w=Object.assign({},t,{query:o,variables:s,fetchPolicy:f,errorPolicy:h,returnPartialData:b,notifyOnNetworkStatusChange:g,context:y}),_=function(e){w.variables=e;var r=a.fetchQueryByPolicy(u,w,n);return"standby"!==w.fetchPolicy&&r.sources.length>0&&u.observableQuery&&u.observableQuery.applyNextFetchPolicy("after-fetch",t),r},E=function(){return a.fetchCancelFns.delete(e)};if(this.fetchCancelFns.set(e,function(e){E(),setTimeout(function(){return r.cancel(e)})}),this.transform(w.query).hasClientExports)r=new nq(this.localState.addExportedVariables(w.query,w.variables,w.context).then(_).then(function(e){return e.sources})),i=!0;else{var S=_(w.variables);i=S.fromLink,r=new nq(S.sources)}return r.promise.then(E,E),{concast:r,fromLink:i}},e.prototype.refetchQueries=function(e){var t=this,n=e.updateCache,r=e.include,i=e.optimistic,a=void 0!==i&&i,o=e.removeOptimistic,s=void 0===o?a?nG("refetchQueries"):void 0:o,u=e.onQueryUpdated,c=new Map;r&&this.getObservableQueries(r).forEach(function(e,n){c.set(n,{oq:e,lastDiff:t.getQuery(n).getDiff()})});var l=new Map;return n&&this.cache.batch({update:n,optimistic:a&&s||!1,removeOptimistic:s,onWatchUpdated:function(e,t,n){var r=e.watcher instanceof r8&&e.watcher.observableQuery;if(r){if(u){c.delete(r.queryId);var i=u(r,t,n);return!0===i&&(i=r.refetch()),!1!==i&&l.set(r,i),i}null!==u&&c.set(r.queryId,{oq:r,lastDiff:n,diff:t})}}}),c.size&&c.forEach(function(e,n){var r,i=e.oq,a=e.lastDiff,o=e.diff;if(u){if(!o){var s=i.queryInfo;s.reset(),o=s.getDiff()}r=u(i,o,a)}u&&!0!==r||(r=i.refetch()),!1!==r&&l.set(i,r),n.indexOf("legacyOneTimeQuery")>=0&&t.stopQueryNoBroadcast(n)}),s&&this.cache.removeOptimistic(s),l},e.prototype.fetchQueryByPolicy=function(e,t,n){var r=this,i=t.query,a=t.variables,o=t.fetchPolicy,s=t.refetchWritePolicy,u=t.errorPolicy,c=t.returnPartialData,l=t.context,f=t.notifyOnNetworkStatusChange,d=e.networkStatus;e.init({document:this.transform(i).document,variables:a,networkStatus:n});var h=function(){return e.getDiff(a)},p=function(t,n){void 0===n&&(n=e.networkStatus||nZ.I.loading);var o=t.result;!__DEV__||c||(0,nm.D)(o,{})||n6(t.missing);var s=function(e){return eT.of((0,en.pi)({data:e,loading:(0,nZ.O)(n),networkStatus:n},t.complete?null:{partial:!0}))};return o&&r.transform(i).hasForcedResolvers?r.localState.runResolvers({document:i,remoteResult:{data:o},context:l,variables:a,onlyRunForcedResolvers:!0}).then(function(e){return s(e.data||void 0)}):"none"===u&&n===nZ.I.refetch&&Array.isArray(t.missing)?s(void 0):s(o)},b="no-cache"===o?0:n===nZ.I.refetch&&"merge"!==s?1:2,m=function(){return r.getResultsFromLink(e,b,{variables:a,context:l,fetchPolicy:o,errorPolicy:u})},g=f&&"number"==typeof d&&d!==n&&(0,nZ.O)(n);switch(o){default:case"cache-first":var v=h();if(v.complete)return{fromLink:!1,sources:[p(v,e.markReady())]};if(c||g)return{fromLink:!0,sources:[p(v),m()]};return{fromLink:!0,sources:[m()]};case"cache-and-network":var v=h();if(v.complete||c||g)return{fromLink:!0,sources:[p(v),m()]};return{fromLink:!0,sources:[m()]};case"cache-only":return{fromLink:!1,sources:[p(h(),e.markReady())]};case"network-only":if(g)return{fromLink:!0,sources:[p(h()),m()]};return{fromLink:!0,sources:[m()]};case"no-cache":if(g)return{fromLink:!0,sources:[p(e.getDiff()),m(),]};return{fromLink:!0,sources:[m()]};case"standby":return{fromLink:!1,sources:[]}}},e.prototype.getQuery=function(e){return e&&!this.queries.has(e)&&this.queries.set(e,new r8(this,e)),this.queries.get(e)},e.prototype.prepareContext=function(e){void 0===e&&(e={});var t=this.localState.prepareContext(e);return(0,en.pi)((0,en.pi)({},t),{clientAwareness:this.clientAwareness})},e}(),ir=__webpack_require__(14012),ii=!1,ia=function(){function e(e){var t=this;this.resetStoreCallbacks=[],this.clearStoreCallbacks=[];var n=e.uri,r=e.credentials,i=e.headers,a=e.cache,o=e.ssrMode,s=void 0!==o&&o,u=e.ssrForceFetchDelay,c=void 0===u?0:u,l=e.connectToDevTools,f=void 0===l?"object"==typeof window&&!window.__APOLLO_CLIENT__&&__DEV__:l,d=e.queryDeduplication,h=void 0===d||d,p=e.defaultOptions,b=e.assumeImmutableResults,m=void 0!==b&&b,g=e.resolvers,v=e.typeDefs,y=e.fragmentMatcher,w=e.name,_=e.version,E=e.link;if(E||(E=n?new nh({uri:n,credentials:r,headers:i}):ta.empty()),!a)throw __DEV__?new Q.ej("To initialize Apollo Client, you must specify a 'cache' property in the options object. \nFor more information, please visit: https://go.apollo.dev/c/docs"):new Q.ej(9);if(this.link=E,this.cache=a,this.disableNetworkFetches=s||c>0,this.queryDeduplication=h,this.defaultOptions=p||Object.create(null),this.typeDefs=v,c&&setTimeout(function(){return t.disableNetworkFetches=!1},c),this.watchQuery=this.watchQuery.bind(this),this.query=this.query.bind(this),this.mutate=this.mutate.bind(this),this.resetStore=this.resetStore.bind(this),this.reFetchObservableQueries=this.reFetchObservableQueries.bind(this),f&&"object"==typeof window&&(window.__APOLLO_CLIENT__=this),!ii&&f&&__DEV__&&(ii=!0,"undefined"!=typeof window&&window.document&&window.top===window.self&&!window.__APOLLO_DEVTOOLS_GLOBAL_HOOK__)){var S=window.navigator,k=S&&S.userAgent,x=void 0;"string"==typeof k&&(k.indexOf("Chrome/")>-1?x="https://chrome.google.com/webstore/detail/apollo-client-developer-t/jdkknkkbebbapilgoeccciglkfbmbnfm":k.indexOf("Firefox/")>-1&&(x="https://addons.mozilla.org/en-US/firefox/addon/apollo-developer-tools/")),x&&__DEV__&&Q.kG.log("Download the Apollo DevTools for a better development experience: "+x)}this.version=nb,this.localState=new r4({cache:a,client:this,resolvers:g,fragmentMatcher:y}),this.queryManager=new it({cache:this.cache,link:this.link,defaultOptions:this.defaultOptions,queryDeduplication:h,ssrMode:s,clientAwareness:{name:w,version:_},localState:this.localState,assumeImmutableResults:m,onBroadcast:f?function(){t.devToolsHookCb&&t.devToolsHookCb({action:{},state:{queries:t.queryManager.getQueryStore(),mutations:t.queryManager.mutationStore||{}},dataWithOptimisticResults:t.cache.extract(!0)})}:void 0})}return e.prototype.stop=function(){this.queryManager.stop()},e.prototype.watchQuery=function(e){return this.defaultOptions.watchQuery&&(e=(0,ir.J)(this.defaultOptions.watchQuery,e)),this.disableNetworkFetches&&("network-only"===e.fetchPolicy||"cache-and-network"===e.fetchPolicy)&&(e=(0,en.pi)((0,en.pi)({},e),{fetchPolicy:"cache-first"})),this.queryManager.watchQuery(e)},e.prototype.query=function(e){return this.defaultOptions.query&&(e=(0,ir.J)(this.defaultOptions.query,e)),__DEV__?(0,Q.kG)("cache-and-network"!==e.fetchPolicy,"The cache-and-network fetchPolicy does not work with client.query, because client.query can only return a single result. Please use client.watchQuery to receive multiple results from the cache and the network, or consider using a different fetchPolicy, such as cache-first or network-only."):(0,Q.kG)("cache-and-network"!==e.fetchPolicy,10),this.disableNetworkFetches&&"network-only"===e.fetchPolicy&&(e=(0,en.pi)((0,en.pi)({},e),{fetchPolicy:"cache-first"})),this.queryManager.query(e)},e.prototype.mutate=function(e){return this.defaultOptions.mutate&&(e=(0,ir.J)(this.defaultOptions.mutate,e)),this.queryManager.mutate(e)},e.prototype.subscribe=function(e){return this.queryManager.startGraphQLSubscription(e)},e.prototype.readQuery=function(e,t){return void 0===t&&(t=!1),this.cache.readQuery(e,t)},e.prototype.readFragment=function(e,t){return void 0===t&&(t=!1),this.cache.readFragment(e,t)},e.prototype.writeQuery=function(e){var t=this.cache.writeQuery(e);return!1!==e.broadcast&&this.queryManager.broadcastQueries(),t},e.prototype.writeFragment=function(e){var t=this.cache.writeFragment(e);return!1!==e.broadcast&&this.queryManager.broadcastQueries(),t},e.prototype.__actionHookForDevTools=function(e){this.devToolsHookCb=e},e.prototype.__requestRaw=function(e){return np(this.link,e)},e.prototype.resetStore=function(){var e=this;return Promise.resolve().then(function(){return e.queryManager.clearStore({discardWatches:!1})}).then(function(){return Promise.all(e.resetStoreCallbacks.map(function(e){return e()}))}).then(function(){return e.reFetchObservableQueries()})},e.prototype.clearStore=function(){var e=this;return Promise.resolve().then(function(){return e.queryManager.clearStore({discardWatches:!0})}).then(function(){return Promise.all(e.clearStoreCallbacks.map(function(e){return e()}))})},e.prototype.onResetStore=function(e){var t=this;return this.resetStoreCallbacks.push(e),function(){t.resetStoreCallbacks=t.resetStoreCallbacks.filter(function(t){return t!==e})}},e.prototype.onClearStore=function(e){var t=this;return this.clearStoreCallbacks.push(e),function(){t.clearStoreCallbacks=t.clearStoreCallbacks.filter(function(t){return t!==e})}},e.prototype.reFetchObservableQueries=function(e){return this.queryManager.reFetchObservableQueries(e)},e.prototype.refetchQueries=function(e){var t=this.queryManager.refetchQueries(e),n=[],r=[];t.forEach(function(e,t){n.push(t),r.push(e)});var i=Promise.all(r);return i.queries=n,i.results=r,i.catch(function(e){__DEV__&&Q.kG.debug("In client.refetchQueries, Promise.all promise rejected with error ".concat(e))}),i},e.prototype.getObservableQueries=function(e){return void 0===e&&(e="active"),this.queryManager.getObservableQueries(e)},e.prototype.extract=function(e){return this.cache.extract(e)},e.prototype.restore=function(e){return this.cache.restore(e)},e.prototype.addResolvers=function(e){this.localState.addResolvers(e)},e.prototype.setResolvers=function(e){this.localState.setResolvers(e)},e.prototype.getResolvers=function(){return this.localState.getResolvers()},e.prototype.setLocalStateFragmentMatcher=function(e){this.localState.setFragmentMatcher(e)},e.prototype.setLink=function(e){this.link=this.queryManager.link=e},e}(),io=function(){function e(){this.getFragmentDoc=rZ(eA)}return e.prototype.batch=function(e){var t,n=this,r="string"==typeof e.optimistic?e.optimistic:!1===e.optimistic?null:void 0;return this.performTransaction(function(){return t=e.update(n)},r),t},e.prototype.recordOptimisticTransaction=function(e,t){this.performTransaction(e,t)},e.prototype.transformDocument=function(e){return e},e.prototype.transformForLink=function(e){return e},e.prototype.identify=function(e){},e.prototype.gc=function(){return[]},e.prototype.modify=function(e){return!1},e.prototype.readQuery=function(e,t){return void 0===t&&(t=!!e.optimistic),this.read((0,en.pi)((0,en.pi)({},e),{rootId:e.id||"ROOT_QUERY",optimistic:t}))},e.prototype.readFragment=function(e,t){return void 0===t&&(t=!!e.optimistic),this.read((0,en.pi)((0,en.pi)({},e),{query:this.getFragmentDoc(e.fragment,e.fragmentName),rootId:e.id,optimistic:t}))},e.prototype.writeQuery=function(e){var t=e.id,n=e.data,r=(0,en._T)(e,["id","data"]);return this.write(Object.assign(r,{dataId:t||"ROOT_QUERY",result:n}))},e.prototype.writeFragment=function(e){var t=e.id,n=e.data,r=e.fragment,i=e.fragmentName,a=(0,en._T)(e,["id","data","fragment","fragmentName"]);return this.write(Object.assign(a,{query:this.getFragmentDoc(r,i),dataId:t,result:n}))},e.prototype.updateQuery=function(e,t){return this.batch({update:function(n){var r=n.readQuery(e),i=t(r);return null==i?r:(n.writeQuery((0,en.pi)((0,en.pi)({},e),{data:i})),i)}})},e.prototype.updateFragment=function(e,t){return this.batch({update:function(n){var r=n.readFragment(e),i=t(r);return null==i?r:(n.writeFragment((0,en.pi)((0,en.pi)({},e),{data:i})),i)}})},e}(),is=function(e){function t(n,r,i,a){var o,s=e.call(this,n)||this;if(s.message=n,s.path=r,s.query=i,s.variables=a,Array.isArray(s.path)){s.missing=s.message;for(var u=s.path.length-1;u>=0;--u)s.missing=((o={})[s.path[u]]=s.missing,o)}else s.missing=s.path;return s.__proto__=t.prototype,s}return(0,en.ZT)(t,e),t}(Error),iu=__webpack_require__(10542),ic=Object.prototype.hasOwnProperty;function il(e){return null==e}function id(e,t){var n=e.__typename,r=e.id,i=e._id;if("string"==typeof n&&(t&&(t.keyObject=il(r)?il(i)?void 0:{_id:i}:{id:r}),il(r)&&!il(i)&&(r=i),!il(r)))return"".concat(n,":").concat("number"==typeof r||"string"==typeof r?r:JSON.stringify(r))}var ih={dataIdFromObject:id,addTypename:!0,resultCaching:!0,canonizeResults:!1};function ip(e){return(0,n1.o)(ih,e)}function ib(e){var t=e.canonizeResults;return void 0===t?ih.canonizeResults:t}function im(e,t){return eD(t)?e.get(t.__ref,"__typename"):t&&t.__typename}var ig=/^[_a-z][_0-9a-z]*/i;function iv(e){var t=e.match(ig);return t?t[0]:e}function iy(e,t,n){return!!(0,eO.s)(t)&&((0,tP.k)(t)?t.every(function(t){return iy(e,t,n)}):e.selections.every(function(e){if(eQ(e)&&td(e,n)){var r=eX(e);return ic.call(t,r)&&(!e.selectionSet||iy(e.selectionSet,t[r],n))}return!0}))}function iw(e){return(0,eO.s)(e)&&!eD(e)&&!(0,tP.k)(e)}function i_(){return new tB}function iE(e,t){var n=eL(e4(e));return{fragmentMap:n,lookupFragment:function(e){var r=n[e];return!r&&t&&(r=t.lookup(e)),r||null}}}var iS=Object.create(null),ik=function(){return iS},ix=Object.create(null),iT=function(){function e(e,t){var n=this;this.policies=e,this.group=t,this.data=Object.create(null),this.rootIds=Object.create(null),this.refs=Object.create(null),this.getFieldValue=function(e,t){return(0,iu.J)(eD(e)?n.get(e.__ref,t):e&&e[t])},this.canRead=function(e){return eD(e)?n.has(e.__ref):"object"==typeof e},this.toReference=function(e,t){if("string"==typeof e)return eI(e);if(eD(e))return e;var r=n.policies.identify(e)[0];if(r){var i=eI(r);return t&&n.merge(r,e),i}}}return e.prototype.toObject=function(){return(0,en.pi)({},this.data)},e.prototype.has=function(e){return void 0!==this.lookup(e,!0)},e.prototype.get=function(e,t){if(this.group.depend(e,t),ic.call(this.data,e)){var n=this.data[e];if(n&&ic.call(n,t))return n[t]}return"__typename"===t&&ic.call(this.policies.rootTypenamesById,e)?this.policies.rootTypenamesById[e]:this instanceof iL?this.parent.get(e,t):void 0},e.prototype.lookup=function(e,t){return(t&&this.group.depend(e,"__exists"),ic.call(this.data,e))?this.data[e]:this instanceof iL?this.parent.lookup(e,t):this.policies.rootTypenamesById[e]?Object.create(null):void 0},e.prototype.merge=function(e,t){var n,r=this;eD(e)&&(e=e.__ref),eD(t)&&(t=t.__ref);var i="string"==typeof e?this.lookup(n=e):e,a="string"==typeof t?this.lookup(n=t):t;if(a){__DEV__?(0,Q.kG)("string"==typeof n,"store.merge expects a string ID"):(0,Q.kG)("string"==typeof n,1);var o=new tB(iI).merge(i,a);if(this.data[n]=o,o!==i&&(delete this.refs[n],this.group.caching)){var s=Object.create(null);i||(s.__exists=1),Object.keys(a).forEach(function(e){if(!i||i[e]!==o[e]){s[e]=1;var t=iv(e);t===e||r.policies.hasKeyArgs(o.__typename,t)||(s[t]=1),void 0!==o[e]||r instanceof iL||delete o[e]}}),s.__typename&&!(i&&i.__typename)&&this.policies.rootTypenamesById[n]===o.__typename&&delete s.__typename,Object.keys(s).forEach(function(e){return r.group.dirty(n,e)})}}},e.prototype.modify=function(e,t){var n=this,r=this.lookup(e);if(r){var i=Object.create(null),a=!1,o=!0,s={DELETE:iS,INVALIDATE:ix,isReference:eD,toReference:this.toReference,canRead:this.canRead,readField:function(t,r){return n.policies.readField("string"==typeof t?{fieldName:t,from:r||eI(e)}:t,{store:n})}};if(Object.keys(r).forEach(function(u){var c=iv(u),l=r[u];if(void 0!==l){var f="function"==typeof t?t:t[u]||t[c];if(f){var d=f===ik?iS:f((0,iu.J)(l),(0,en.pi)((0,en.pi)({},s),{fieldName:c,storeFieldName:u,storage:n.getStorage(e,u)}));d===ix?n.group.dirty(e,u):(d===iS&&(d=void 0),d!==l&&(i[u]=d,a=!0,l=d))}void 0!==l&&(o=!1)}}),a)return this.merge(e,i),o&&(this instanceof iL?this.data[e]=void 0:delete this.data[e],this.group.dirty(e,"__exists")),!0}return!1},e.prototype.delete=function(e,t,n){var r,i=this.lookup(e);if(i){var a=this.getFieldValue(i,"__typename"),o=t&&n?this.policies.getStoreFieldName({typename:a,fieldName:t,args:n}):t;return this.modify(e,o?((r={})[o]=ik,r):ik)}return!1},e.prototype.evict=function(e,t){var n=!1;return e.id&&(ic.call(this.data,e.id)&&(n=this.delete(e.id,e.fieldName,e.args)),this instanceof iL&&this!==t&&(n=this.parent.evict(e,t)||n),(e.fieldName||n)&&this.group.dirty(e.id,e.fieldName||"__exists")),n},e.prototype.clear=function(){this.replace(null)},e.prototype.extract=function(){var e=this,t=this.toObject(),n=[];return this.getRootIdSet().forEach(function(t){ic.call(e.policies.rootTypenamesById,t)||n.push(t)}),n.length&&(t.__META={extraRootIds:n.sort()}),t},e.prototype.replace=function(e){var t=this;if(Object.keys(this.data).forEach(function(n){e&&ic.call(e,n)||t.delete(n)}),e){var n=e.__META,r=(0,en._T)(e,["__META"]);Object.keys(r).forEach(function(e){t.merge(e,r[e])}),n&&n.extraRootIds.forEach(this.retain,this)}},e.prototype.retain=function(e){return this.rootIds[e]=(this.rootIds[e]||0)+1},e.prototype.release=function(e){if(this.rootIds[e]>0){var t=--this.rootIds[e];return t||delete this.rootIds[e],t}return 0},e.prototype.getRootIdSet=function(e){return void 0===e&&(e=new Set),Object.keys(this.rootIds).forEach(e.add,e),this instanceof iL?this.parent.getRootIdSet(e):Object.keys(this.policies.rootTypenamesById).forEach(e.add,e),e},e.prototype.gc=function(){var e=this,t=this.getRootIdSet(),n=this.toObject();t.forEach(function(r){ic.call(n,r)&&(Object.keys(e.findChildRefIds(r)).forEach(t.add,t),delete n[r])});var r=Object.keys(n);if(r.length){for(var i=this;i instanceof iL;)i=i.parent;r.forEach(function(e){return i.delete(e)})}return r},e.prototype.findChildRefIds=function(e){if(!ic.call(this.refs,e)){var t=this.refs[e]=Object.create(null),n=this.data[e];if(!n)return t;var r=new Set([n]);r.forEach(function(e){eD(e)&&(t[e.__ref]=!0),(0,eO.s)(e)&&Object.keys(e).forEach(function(t){var n=e[t];(0,eO.s)(n)&&r.add(n)})})}return this.refs[e]},e.prototype.makeCacheKey=function(){return this.group.keyMaker.lookupArray(arguments)},e}(),iM=function(){function e(e,t){void 0===t&&(t=null),this.caching=e,this.parent=t,this.d=null,this.resetCaching()}return e.prototype.resetCaching=function(){this.d=this.caching?rW():null,this.keyMaker=new n_(t_.mr)},e.prototype.depend=function(e,t){if(this.d){this.d(iO(e,t));var n=iv(t);n!==t&&this.d(iO(e,n)),this.parent&&this.parent.depend(e,t)}},e.prototype.dirty=function(e,t){this.d&&this.d.dirty(iO(e,t),"__exists"===t?"forget":"setDirty")},e}();function iO(e,t){return t+"#"+e}function iA(e,t){iD(e)&&e.group.depend(t,"__exists")}!function(e){var t=function(e){function t(t){var n=t.policies,r=t.resultCaching,i=void 0===r||r,a=t.seed,o=e.call(this,n,new iM(i))||this;return o.stump=new iC(o),o.storageTrie=new n_(t_.mr),a&&o.replace(a),o}return(0,en.ZT)(t,e),t.prototype.addLayer=function(e,t){return this.stump.addLayer(e,t)},t.prototype.removeLayer=function(){return this},t.prototype.getStorage=function(){return this.storageTrie.lookupArray(arguments)},t}(e);e.Root=t}(iT||(iT={}));var iL=function(e){function t(t,n,r,i){var a=e.call(this,n.policies,i)||this;return a.id=t,a.parent=n,a.replay=r,a.group=i,r(a),a}return(0,en.ZT)(t,e),t.prototype.addLayer=function(e,n){return new t(e,this,n,this.group)},t.prototype.removeLayer=function(e){var t=this,n=this.parent.removeLayer(e);return e===this.id?(this.group.caching&&Object.keys(this.data).forEach(function(e){var r=t.data[e],i=n.lookup(e);i?r?r!==i&&Object.keys(r).forEach(function(n){(0,nm.D)(r[n],i[n])||t.group.dirty(e,n)}):(t.group.dirty(e,"__exists"),Object.keys(i).forEach(function(n){t.group.dirty(e,n)})):t.delete(e)}),n):n===this.parent?this:n.addLayer(this.id,this.replay)},t.prototype.toObject=function(){return(0,en.pi)((0,en.pi)({},this.parent.toObject()),this.data)},t.prototype.findChildRefIds=function(t){var n=this.parent.findChildRefIds(t);return ic.call(this.data,t)?(0,en.pi)((0,en.pi)({},n),e.prototype.findChildRefIds.call(this,t)):n},t.prototype.getStorage=function(){for(var e=this.parent;e.parent;)e=e.parent;return e.getStorage.apply(e,arguments)},t}(iT),iC=function(e){function t(t){return e.call(this,"EntityStore.Stump",t,function(){},new iM(t.group.caching,t.group))||this}return(0,en.ZT)(t,e),t.prototype.removeLayer=function(){return this},t.prototype.merge=function(){return this.parent.merge.apply(this.parent,arguments)},t}(iL);function iI(e,t,n){var r=e[n],i=t[n];return(0,nm.D)(r,i)?r:i}function iD(e){return!!(e instanceof iT&&e.group.caching)}function iN(e){return[e.selectionSet,e.objectOrReference,e.context,e.context.canonizeResults,]}var iP=function(){function e(e){var t=this;this.knownResults=new(t_.mr?WeakMap:Map),this.config=(0,n1.o)(e,{addTypename:!1!==e.addTypename,canonizeResults:ib(e)}),this.canon=e.canon||new nk,this.executeSelectionSet=rZ(function(e){var n,r=e.context.canonizeResults,i=iN(e);i[3]=!r;var a=(n=t.executeSelectionSet).peek.apply(n,i);return a?r?(0,en.pi)((0,en.pi)({},a),{result:t.canon.admit(a.result)}):a:(iA(e.context.store,e.enclosingRef.__ref),t.execSelectionSetImpl(e))},{max:this.config.resultCacheMaxSize,keyArgs:iN,makeCacheKey:function(e,t,n,r){if(iD(n.store))return n.store.makeCacheKey(e,eD(t)?t.__ref:t,n.varString,r)}}),this.executeSubSelectedArray=rZ(function(e){return iA(e.context.store,e.enclosingRef.__ref),t.execSubSelectedArrayImpl(e)},{max:this.config.resultCacheMaxSize,makeCacheKey:function(e){var t=e.field,n=e.array,r=e.context;if(iD(r.store))return r.store.makeCacheKey(t,n,r.varString)}})}return e.prototype.resetCanon=function(){this.canon=new nk},e.prototype.diffQueryAgainstStore=function(e){var t,n=e.store,r=e.query,i=e.rootId,a=void 0===i?"ROOT_QUERY":i,o=e.variables,s=e.returnPartialData,u=void 0===s||s,c=e.canonizeResults,l=void 0===c?this.config.canonizeResults:c,f=this.config.cache.policies;o=(0,en.pi)((0,en.pi)({},e8(e5(r))),o);var d=eI(a),h=this.executeSelectionSet({selectionSet:e9(r).selectionSet,objectOrReference:d,enclosingRef:d,context:(0,en.pi)({store:n,query:r,policies:f,variables:o,varString:nx(o),canonizeResults:l},iE(r,this.config.fragments))});if(h.missing&&(t=[new is(iR(h.missing),h.missing,r,o)],!u))throw t[0];return{result:h.result,complete:!t,missing:t}},e.prototype.isFresh=function(e,t,n,r){if(iD(r.store)&&this.knownResults.get(e)===n){var i=this.executeSelectionSet.peek(n,t,r,this.canon.isKnown(e));if(i&&e===i.result)return!0}return!1},e.prototype.execSelectionSetImpl=function(e){var t,n=this,r=e.selectionSet,i=e.objectOrReference,a=e.enclosingRef,o=e.context;if(eD(i)&&!o.policies.rootTypenamesById[i.__ref]&&!o.store.has(i.__ref))return{result:this.canon.empty,missing:"Dangling reference to missing ".concat(i.__ref," object")};var s=o.variables,u=o.policies,c=o.store.getFieldValue(i,"__typename"),l=[],f=new tB;function d(e,n){var r;return e.missing&&(t=f.merge(t,((r={})[n]=e.missing,r))),e.result}this.config.addTypename&&"string"==typeof c&&!u.rootIdsByTypename[c]&&l.push({__typename:c});var h=new Set(r.selections);h.forEach(function(e){var r,p;if(td(e,s)){if(eQ(e)){var b=u.readField({fieldName:e.name.value,field:e,variables:o.variables,from:i},o),m=eX(e);void 0===b?nj.added(e)||(t=f.merge(t,((r={})[m]="Can't find field '".concat(e.name.value,"' on ").concat(eD(i)?i.__ref+" object":"object "+JSON.stringify(i,null,2)),r))):(0,tP.k)(b)?b=d(n.executeSubSelectedArray({field:e,array:b,enclosingRef:a,context:o}),m):e.selectionSet?null!=b&&(b=d(n.executeSelectionSet({selectionSet:e.selectionSet,objectOrReference:b,enclosingRef:eD(b)?b:a,context:o}),m)):o.canonizeResults&&(b=n.canon.pass(b)),void 0!==b&&l.push(((p={})[m]=b,p))}else{var g=eC(e,o.lookupFragment);if(!g&&e.kind===nL.h.FRAGMENT_SPREAD)throw __DEV__?new Q.ej("No fragment named ".concat(e.name.value)):new Q.ej(5);g&&u.fragmentMatches(g,c)&&g.selectionSet.selections.forEach(h.add,h)}}});var p={result:tF(l),missing:t},b=o.canonizeResults?this.canon.admit(p):(0,iu.J)(p);return b.result&&this.knownResults.set(b.result,r),b},e.prototype.execSubSelectedArrayImpl=function(e){var t,n=this,r=e.field,i=e.array,a=e.enclosingRef,o=e.context,s=new tB;function u(e,n){var r;return e.missing&&(t=s.merge(t,((r={})[n]=e.missing,r))),e.result}return r.selectionSet&&(i=i.filter(o.store.canRead)),i=i.map(function(e,t){return null===e?null:(0,tP.k)(e)?u(n.executeSubSelectedArray({field:r,array:e,enclosingRef:a,context:o}),t):r.selectionSet?u(n.executeSelectionSet({selectionSet:r.selectionSet,objectOrReference:e,enclosingRef:eD(e)?e:a,context:o}),t):(__DEV__&&ij(o.store,r,e),e)}),{result:o.canonizeResults?this.canon.admit(i):i,missing:t}},e}();function iR(e){try{JSON.stringify(e,function(e,t){if("string"==typeof t)throw t;return t})}catch(t){return t}}function ij(e,t,n){if(!t.selectionSet){var r=new Set([n]);r.forEach(function(n){(0,eO.s)(n)&&(__DEV__?(0,Q.kG)(!eD(n),"Missing selection set for object of type ".concat(im(e,n)," returned for query field ").concat(t.name.value)):(0,Q.kG)(!eD(n),6),Object.values(n).forEach(r.add,r))})}}function iF(e){var t=nG("stringifyForDisplay");return JSON.stringify(e,function(e,n){return void 0===n?t:n}).split(JSON.stringify(t)).join("")}var iY=Object.create(null);function iB(e){var t=JSON.stringify(e);return iY[t]||(iY[t]=Object.create(null))}function iU(e){var t=iB(e);return t.keyFieldsFn||(t.keyFieldsFn=function(t,n){var r=function(e,t){return n.readField(t,e)},i=n.keyObject=i$(e,function(e){var i=iW(n.storeObject,e,r);return void 0===i&&t!==n.storeObject&&ic.call(t,e[0])&&(i=iW(t,e,iG)),__DEV__?(0,Q.kG)(void 0!==i,"Missing field '".concat(e.join("."),"' while extracting keyFields from ").concat(JSON.stringify(t))):(0,Q.kG)(void 0!==i,2),i});return"".concat(n.typename,":").concat(JSON.stringify(i))})}function iH(e){var t=iB(e);return t.keyArgsFn||(t.keyArgsFn=function(t,n){var r=n.field,i=n.variables,a=n.fieldName,o=JSON.stringify(i$(e,function(e){var n=e[0],a=n.charAt(0);if("@"===a){if(r&&(0,tP.O)(r.directives)){var o=n.slice(1),s=r.directives.find(function(e){return e.name.value===o}),u=s&&eZ(s,i);return u&&iW(u,e.slice(1))}return}if("$"===a){var c=n.slice(1);if(i&&ic.call(i,c)){var l=e.slice(0);return l[0]=c,iW(i,l)}return}if(t)return iW(t,e)}));return(t||"{}"!==o)&&(a+=":"+o),a})}function i$(e,t){var n=new tB;return iz(e).reduce(function(e,r){var i,a=t(r);if(void 0!==a){for(var o=r.length-1;o>=0;--o)a=((i={})[r[o]]=a,i);e=n.merge(e,a)}return e},Object.create(null))}function iz(e){var t=iB(e);if(!t.paths){var n=t.paths=[],r=[];e.forEach(function(t,i){(0,tP.k)(t)?(iz(t).forEach(function(e){return n.push(r.concat(e))}),r.length=0):(r.push(t),(0,tP.k)(e[i+1])||(n.push(r.slice(0)),r.length=0))})}return t.paths}function iG(e,t){return e[t]}function iW(e,t,n){return n=n||iG,iK(t.reduce(function e(t,r){return(0,tP.k)(t)?t.map(function(t){return e(t,r)}):t&&n(t,r)},e))}function iK(e){return(0,eO.s)(e)?(0,tP.k)(e)?e.map(iK):i$(Object.keys(e).sort(),function(t){return iW(e,t)}):e}function iV(e){return void 0!==e.args?e.args:e.field?eZ(e.field,e.variables):null}eK.setStringify(nx);var iq=function(){},iZ=function(e,t){return t.fieldName},iX=function(e,t,n){return(0,n.mergeObjects)(e,t)},iJ=function(e,t){return t},iQ=function(){function e(e){this.config=e,this.typePolicies=Object.create(null),this.toBeAdded=Object.create(null),this.supertypeMap=new Map,this.fuzzySubtypes=new Map,this.rootIdsByTypename=Object.create(null),this.rootTypenamesById=Object.create(null),this.usingPossibleTypes=!1,this.config=(0,en.pi)({dataIdFromObject:id},e),this.cache=this.config.cache,this.setRootTypename("Query"),this.setRootTypename("Mutation"),this.setRootTypename("Subscription"),e.possibleTypes&&this.addPossibleTypes(e.possibleTypes),e.typePolicies&&this.addTypePolicies(e.typePolicies)}return e.prototype.identify=function(e,t){var n,r,i=this,a=t&&(t.typename||(null===(n=t.storeObject)||void 0===n?void 0:n.__typename))||e.__typename;if(a===this.rootTypenamesById.ROOT_QUERY)return["ROOT_QUERY"];for(var o=t&&t.storeObject||e,s=(0,en.pi)((0,en.pi)({},t),{typename:a,storeObject:o,readField:t&&t.readField||function(){var e=i0(arguments,o);return i.readField(e,{store:i.cache.data,variables:e.variables})}}),u=a&&this.getTypePolicy(a),c=u&&u.keyFn||this.config.dataIdFromObject;c;){var l=c((0,en.pi)((0,en.pi)({},e),o),s);if((0,tP.k)(l))c=iU(l);else{r=l;break}}return r=r?String(r):void 0,s.keyObject?[r,s.keyObject]:[r]},e.prototype.addTypePolicies=function(e){var t=this;Object.keys(e).forEach(function(n){var r=e[n],i=r.queryType,a=r.mutationType,o=r.subscriptionType,s=(0,en._T)(r,["queryType","mutationType","subscriptionType"]);i&&t.setRootTypename("Query",n),a&&t.setRootTypename("Mutation",n),o&&t.setRootTypename("Subscription",n),ic.call(t.toBeAdded,n)?t.toBeAdded[n].push(s):t.toBeAdded[n]=[s]})},e.prototype.updateTypePolicy=function(e,t){var n=this,r=this.getTypePolicy(e),i=t.keyFields,a=t.fields;function o(e,t){e.merge="function"==typeof t?t:!0===t?iX:!1===t?iJ:e.merge}o(r,t.merge),r.keyFn=!1===i?iq:(0,tP.k)(i)?iU(i):"function"==typeof i?i:r.keyFn,a&&Object.keys(a).forEach(function(t){var r=n.getFieldPolicy(e,t,!0),i=a[t];if("function"==typeof i)r.read=i;else{var s=i.keyArgs,u=i.read,c=i.merge;r.keyFn=!1===s?iZ:(0,tP.k)(s)?iH(s):"function"==typeof s?s:r.keyFn,"function"==typeof u&&(r.read=u),o(r,c)}r.read&&r.merge&&(r.keyFn=r.keyFn||iZ)})},e.prototype.setRootTypename=function(e,t){void 0===t&&(t=e);var n="ROOT_"+e.toUpperCase(),r=this.rootTypenamesById[n];t!==r&&(__DEV__?(0,Q.kG)(!r||r===e,"Cannot change root ".concat(e," __typename more than once")):(0,Q.kG)(!r||r===e,3),r&&delete this.rootIdsByTypename[r],this.rootIdsByTypename[t]=n,this.rootTypenamesById[n]=t)},e.prototype.addPossibleTypes=function(e){var t=this;this.usingPossibleTypes=!0,Object.keys(e).forEach(function(n){t.getSupertypeSet(n,!0),e[n].forEach(function(e){t.getSupertypeSet(e,!0).add(n);var r=e.match(ig);r&&r[0]===e||t.fuzzySubtypes.set(e,RegExp(e))})})},e.prototype.getTypePolicy=function(e){var t=this;if(!ic.call(this.typePolicies,e)){var n=this.typePolicies[e]=Object.create(null);n.fields=Object.create(null);var r=this.supertypeMap.get(e);r&&r.size&&r.forEach(function(e){var r=t.getTypePolicy(e),i=r.fields;Object.assign(n,(0,en._T)(r,["fields"])),Object.assign(n.fields,i)})}var i=this.toBeAdded[e];return i&&i.length&&i.splice(0).forEach(function(n){t.updateTypePolicy(e,n)}),this.typePolicies[e]},e.prototype.getFieldPolicy=function(e,t,n){if(e){var r=this.getTypePolicy(e).fields;return r[t]||n&&(r[t]=Object.create(null))}},e.prototype.getSupertypeSet=function(e,t){var n=this.supertypeMap.get(e);return!n&&t&&this.supertypeMap.set(e,n=new Set),n},e.prototype.fragmentMatches=function(e,t,n,r){var i=this;if(!e.typeCondition)return!0;if(!t)return!1;var a=e.typeCondition.name.value;if(t===a)return!0;if(this.usingPossibleTypes&&this.supertypeMap.has(a))for(var o=this.getSupertypeSet(t,!0),s=[o],u=function(e){var t=i.getSupertypeSet(e,!1);t&&t.size&&0>s.indexOf(t)&&s.push(t)},c=!!(n&&this.fuzzySubtypes.size),l=!1,f=0;f1?a:t}:(r=(0,en.pi)({},i),ic.call(r,"from")||(r.from=t)),__DEV__&&void 0===r.from&&__DEV__&&Q.kG.warn("Undefined 'from' passed to readField with arguments ".concat(iF(Array.from(e)))),void 0===r.variables&&(r.variables=n),r}function i2(e){return function(t,n){if((0,tP.k)(t)||(0,tP.k)(n))throw __DEV__?new Q.ej("Cannot automatically merge arrays"):new Q.ej(4);if((0,eO.s)(t)&&(0,eO.s)(n)){var r=e.getFieldValue(t,"__typename"),i=e.getFieldValue(n,"__typename");if(r&&i&&r!==i)return n;if(eD(t)&&iw(n))return e.merge(t.__ref,n),t;if(iw(t)&&eD(n))return e.merge(t,n.__ref),n;if(iw(t)&&iw(n))return(0,en.pi)((0,en.pi)({},t),n)}return n}}function i3(e,t,n){var r="".concat(t).concat(n),i=e.flavors.get(r);return i||e.flavors.set(r,i=e.clientOnly===t&&e.deferred===n?e:(0,en.pi)((0,en.pi)({},e),{clientOnly:t,deferred:n})),i}var i4=function(){function e(e,t,n){this.cache=e,this.reader=t,this.fragments=n}return e.prototype.writeToStore=function(e,t){var n=this,r=t.query,i=t.result,a=t.dataId,o=t.variables,s=t.overwrite,u=e2(r),c=i_();o=(0,en.pi)((0,en.pi)({},e8(u)),o);var l=(0,en.pi)((0,en.pi)({store:e,written:Object.create(null),merge:function(e,t){return c.merge(e,t)},variables:o,varString:nx(o)},iE(r,this.fragments)),{overwrite:!!s,incomingById:new Map,clientOnly:!1,deferred:!1,flavors:new Map}),f=this.processSelectionSet({result:i||Object.create(null),dataId:a,selectionSet:u.selectionSet,mergeTree:{map:new Map},context:l});if(!eD(f))throw __DEV__?new Q.ej("Could not identify object ".concat(JSON.stringify(i))):new Q.ej(7);return l.incomingById.forEach(function(t,r){var i=t.storeObject,a=t.mergeTree,o=t.fieldNodeSet,s=eI(r);if(a&&a.map.size){var u=n.applyMerges(a,s,i,l);if(eD(u))return;i=u}if(__DEV__&&!l.overwrite){var c=Object.create(null);o.forEach(function(e){e.selectionSet&&(c[e.name.value]=!0)});var f=function(e){return!0===c[iv(e)]},d=function(e){var t=a&&a.map.get(e);return Boolean(t&&t.info&&t.info.merge)};Object.keys(i).forEach(function(e){f(e)&&!d(e)&&at(s,i,e,l.store)})}e.merge(r,i)}),e.retain(f.__ref),f},e.prototype.processSelectionSet=function(e){var t=this,n=e.dataId,r=e.result,i=e.selectionSet,a=e.context,o=e.mergeTree,s=this.cache.policies,u=Object.create(null),c=n&&s.rootTypenamesById[n]||eJ(r,i,a.fragmentMap)||n&&a.store.get(n,"__typename");"string"==typeof c&&(u.__typename=c);var l=function(){var e=i0(arguments,u,a.variables);if(eD(e.from)){var t=a.incomingById.get(e.from.__ref);if(t){var n=s.readField((0,en.pi)((0,en.pi)({},e),{from:t.storeObject}),a);if(void 0!==n)return n}}return s.readField(e,a)},f=new Set;this.flattenFields(i,r,a,c).forEach(function(e,n){var i,a=r[eX(n)];if(f.add(n),void 0!==a){var d=s.getStoreFieldName({typename:c,fieldName:n.name.value,field:n,variables:e.variables}),h=i6(o,d),p=t.processFieldValue(a,n,n.selectionSet?i3(e,!1,!1):e,h),b=void 0;n.selectionSet&&(eD(p)||iw(p))&&(b=l("__typename",p));var m=s.getMergeFunction(c,n.name.value,b);m?h.info={field:n,typename:c,merge:m}:i7(o,d),u=e.merge(u,((i={})[d]=p,i))}else __DEV__&&!e.clientOnly&&!e.deferred&&!nj.added(n)&&!s.getReadFunction(c,n.name.value)&&__DEV__&&Q.kG.error("Missing field '".concat(eX(n),"' while writing result ").concat(JSON.stringify(r,null,2)).substring(0,1e3))});try{var d=s.identify(r,{typename:c,selectionSet:i,fragmentMap:a.fragmentMap,storeObject:u,readField:l}),h=d[0],p=d[1];n=n||h,p&&(u=a.merge(u,p))}catch(b){if(!n)throw b}if("string"==typeof n){var m=eI(n),g=a.written[n]||(a.written[n]=[]);if(g.indexOf(i)>=0||(g.push(i),this.reader&&this.reader.isFresh(r,m,i,a)))return m;var v=a.incomingById.get(n);return v?(v.storeObject=a.merge(v.storeObject,u),v.mergeTree=i9(v.mergeTree,o),f.forEach(function(e){return v.fieldNodeSet.add(e)})):a.incomingById.set(n,{storeObject:u,mergeTree:i8(o)?void 0:o,fieldNodeSet:f}),m}return u},e.prototype.processFieldValue=function(e,t,n,r){var i=this;return t.selectionSet&&null!==e?(0,tP.k)(e)?e.map(function(e,a){var o=i.processFieldValue(e,t,n,i6(r,a));return i7(r,a),o}):this.processSelectionSet({result:e,selectionSet:t.selectionSet,context:n,mergeTree:r}):__DEV__?nJ(e):e},e.prototype.flattenFields=function(e,t,n,r){void 0===r&&(r=eJ(t,e,n.fragmentMap));var i=new Map,a=this.cache.policies,o=new n_(!1);return function e(s,u){var c=o.lookup(s,u.clientOnly,u.deferred);c.visited||(c.visited=!0,s.selections.forEach(function(o){if(td(o,n.variables)){var s=u.clientOnly,c=u.deferred;if(!(s&&c)&&(0,tP.O)(o.directives)&&o.directives.forEach(function(e){var t=e.name.value;if("client"===t&&(s=!0),"defer"===t){var r=eZ(e,n.variables);r&&!1===r.if||(c=!0)}}),eQ(o)){var l=i.get(o);l&&(s=s&&l.clientOnly,c=c&&l.deferred),i.set(o,i3(n,s,c))}else{var f=eC(o,n.lookupFragment);if(!f&&o.kind===nL.h.FRAGMENT_SPREAD)throw __DEV__?new Q.ej("No fragment named ".concat(o.name.value)):new Q.ej(8);f&&a.fragmentMatches(f,r,t,n.variables)&&e(f.selectionSet,i3(n,s,c))}}}))}(e,n),i},e.prototype.applyMerges=function(e,t,n,r,i){var a=this;if(e.map.size&&!eD(n)){var o,s,u=!(0,tP.k)(n)&&(eD(t)||iw(t))?t:void 0,c=n;u&&!i&&(i=[eD(u)?u.__ref:u]);var l=function(e,t){return(0,tP.k)(e)?"number"==typeof t?e[t]:void 0:r.store.getFieldValue(e,String(t))};e.map.forEach(function(e,t){var n=l(u,t),o=l(c,t);if(void 0!==o){i&&i.push(t);var f=a.applyMerges(e,n,o,r,i);f!==o&&(s=s||new Map).set(t,f),i&&(0,Q.kG)(i.pop()===t)}}),s&&(n=(0,tP.k)(c)?c.slice(0):(0,en.pi)({},c),s.forEach(function(e,t){n[t]=e}))}return e.info?this.cache.policies.runMergeFunction(t,n,e.info,r,i&&(o=r.store).getStorage.apply(o,i)):n},e}(),i5=[];function i6(e,t){var n=e.map;return n.has(t)||n.set(t,i5.pop()||{map:new Map}),n.get(t)}function i9(e,t){if(e===t||!t||i8(t))return e;if(!e||i8(e))return t;var n=e.info&&t.info?(0,en.pi)((0,en.pi)({},e.info),t.info):e.info||t.info,r=e.map.size&&t.map.size,i=r?new Map:e.map.size?e.map:t.map,a={info:n,map:i};if(r){var o=new Set(t.map.keys());e.map.forEach(function(e,n){a.map.set(n,i9(e,t.map.get(n))),o.delete(n)}),o.forEach(function(n){a.map.set(n,i9(t.map.get(n),e.map.get(n)))})}return a}function i8(e){return!e||!(e.info||e.map.size)}function i7(e,t){var n=e.map,r=n.get(t);r&&i8(r)&&(i5.push(r),n.delete(t))}var ae=new Set;function at(e,t,n,r){var i=function(e){var t=r.getFieldValue(e,n);return"object"==typeof t&&t},a=i(e);if(a){var o=i(t);if(!(!o||eD(a)||(0,nm.D)(a,o)||Object.keys(a).every(function(e){return void 0!==r.getFieldValue(o,e)}))){var s=r.getFieldValue(e,"__typename")||r.getFieldValue(t,"__typename"),u=iv(n),c="".concat(s,".").concat(u);if(!ae.has(c)){ae.add(c);var l=[];(0,tP.k)(a)||(0,tP.k)(o)||[a,o].forEach(function(e){var t=r.getFieldValue(e,"__typename");"string"!=typeof t||l.includes(t)||l.push(t)}),__DEV__&&Q.kG.warn("Cache data may be lost when replacing the ".concat(u," field of a ").concat(s," object.\n\nThis could cause additional (usually avoidable) network requests to fetch data that were otherwise cached.\n\nTo address this problem (which is not a bug in Apollo Client), ").concat(l.length?"either ensure all objects of type "+l.join(" and ")+" have an ID or a custom merge function, or ":"","define a custom merge function for the ").concat(c," field, so InMemoryCache can safely merge these objects:\n\n existing: ").concat(JSON.stringify(a).slice(0,1e3),"\n incoming: ").concat(JSON.stringify(o).slice(0,1e3),"\n\nFor more information about these options, please refer to the documentation:\n\n * Ensuring entity objects have IDs: https://go.apollo.dev/c/generating-unique-identifiers\n * Defining custom merge functions: https://go.apollo.dev/c/merging-non-normalized-objects\n"))}}}}var an=function(e){function t(t){void 0===t&&(t={});var n=e.call(this)||this;return n.watches=new Set,n.typenameDocumentCache=new Map,n.makeVar=r2,n.txCount=0,n.config=ip(t),n.addTypename=!!n.config.addTypename,n.policies=new iQ({cache:n,dataIdFromObject:n.config.dataIdFromObject,possibleTypes:n.config.possibleTypes,typePolicies:n.config.typePolicies}),n.init(),n}return(0,en.ZT)(t,e),t.prototype.init=function(){var e=this.data=new iT.Root({policies:this.policies,resultCaching:this.config.resultCaching});this.optimisticData=e.stump,this.resetResultCache()},t.prototype.resetResultCache=function(e){var t=this,n=this.storeReader,r=this.config.fragments;this.storeWriter=new i4(this,this.storeReader=new iP({cache:this,addTypename:this.addTypename,resultCacheMaxSize:this.config.resultCacheMaxSize,canonizeResults:ib(this.config),canon:e?void 0:n&&n.canon,fragments:r}),r),this.maybeBroadcastWatch=rZ(function(e,n){return t.broadcastWatch(e,n)},{max:this.config.resultCacheMaxSize,makeCacheKey:function(e){var n=e.optimistic?t.optimisticData:t.data;if(iD(n)){var r=e.optimistic,i=e.id,a=e.variables;return n.makeCacheKey(e.query,e.callback,nx({optimistic:r,id:i,variables:a}))}}}),new Set([this.data.group,this.optimisticData.group,]).forEach(function(e){return e.resetCaching()})},t.prototype.restore=function(e){return this.init(),e&&this.data.replace(e),this},t.prototype.extract=function(e){return void 0===e&&(e=!1),(e?this.optimisticData:this.data).extract()},t.prototype.read=function(e){var t=e.returnPartialData,n=void 0!==t&&t;try{return this.storeReader.diffQueryAgainstStore((0,en.pi)((0,en.pi)({},e),{store:e.optimistic?this.optimisticData:this.data,config:this.config,returnPartialData:n})).result||null}catch(r){if(r instanceof is)return null;throw r}},t.prototype.write=function(e){try{return++this.txCount,this.storeWriter.writeToStore(this.data,e)}finally{--this.txCount||!1===e.broadcast||this.broadcastWatches()}},t.prototype.modify=function(e){if(ic.call(e,"id")&&!e.id)return!1;var t=e.optimistic?this.optimisticData:this.data;try{return++this.txCount,t.modify(e.id||"ROOT_QUERY",e.fields)}finally{--this.txCount||!1===e.broadcast||this.broadcastWatches()}},t.prototype.diff=function(e){return this.storeReader.diffQueryAgainstStore((0,en.pi)((0,en.pi)({},e),{store:e.optimistic?this.optimisticData:this.data,rootId:e.id||"ROOT_QUERY",config:this.config}))},t.prototype.watch=function(e){var t=this;return this.watches.size||r0(this),this.watches.add(e),e.immediate&&this.maybeBroadcastWatch(e),function(){t.watches.delete(e)&&!t.watches.size&&r1(t),t.maybeBroadcastWatch.forget(e)}},t.prototype.gc=function(e){nx.reset();var t=this.optimisticData.gc();return e&&!this.txCount&&(e.resetResultCache?this.resetResultCache(e.resetResultIdentities):e.resetResultIdentities&&this.storeReader.resetCanon()),t},t.prototype.retain=function(e,t){return(t?this.optimisticData:this.data).retain(e)},t.prototype.release=function(e,t){return(t?this.optimisticData:this.data).release(e)},t.prototype.identify=function(e){if(eD(e))return e.__ref;try{return this.policies.identify(e)[0]}catch(t){__DEV__&&Q.kG.warn(t)}},t.prototype.evict=function(e){if(!e.id){if(ic.call(e,"id"))return!1;e=(0,en.pi)((0,en.pi)({},e),{id:"ROOT_QUERY"})}try{return++this.txCount,this.optimisticData.evict(e,this.data)}finally{--this.txCount||!1===e.broadcast||this.broadcastWatches()}},t.prototype.reset=function(e){var t=this;return this.init(),nx.reset(),e&&e.discardWatches?(this.watches.forEach(function(e){return t.maybeBroadcastWatch.forget(e)}),this.watches.clear(),r1(this)):this.broadcastWatches(),Promise.resolve()},t.prototype.removeOptimistic=function(e){var t=this.optimisticData.removeLayer(e);t!==this.optimisticData&&(this.optimisticData=t,this.broadcastWatches())},t.prototype.batch=function(e){var t,n=this,r=e.update,i=e.optimistic,a=void 0===i||i,o=e.removeOptimistic,s=e.onWatchUpdated,u=function(e){var i=n,a=i.data,o=i.optimisticData;++n.txCount,e&&(n.data=n.optimisticData=e);try{return t=r(n)}finally{--n.txCount,n.data=a,n.optimisticData=o}},c=new Set;return s&&!this.txCount&&this.broadcastWatches((0,en.pi)((0,en.pi)({},e),{onWatchUpdated:function(e){return c.add(e),!1}})),"string"==typeof a?this.optimisticData=this.optimisticData.addLayer(a,u):!1===a?u(this.data):u(),"string"==typeof o&&(this.optimisticData=this.optimisticData.removeLayer(o)),s&&c.size?(this.broadcastWatches((0,en.pi)((0,en.pi)({},e),{onWatchUpdated:function(e,t){var n=s.call(this,e,t);return!1!==n&&c.delete(e),n}})),c.size&&c.forEach(function(e){return n.maybeBroadcastWatch.dirty(e)})):this.broadcastWatches(e),t},t.prototype.performTransaction=function(e,t){return this.batch({update:e,optimistic:t||null!==t})},t.prototype.transformDocument=function(e){if(this.addTypename){var t=this.typenameDocumentCache.get(e);return t||(t=nj(e),this.typenameDocumentCache.set(e,t),this.typenameDocumentCache.set(t,t)),t}return e},t.prototype.transformForLink=function(e){var t=this.config.fragments;return t?t.transform(e):e},t.prototype.broadcastWatches=function(e){var t=this;this.txCount||this.watches.forEach(function(n){return t.maybeBroadcastWatch(n,e)})},t.prototype.broadcastWatch=function(e,t){var n=e.lastDiff,r=this.diff(e);(!t||(e.optimistic&&"string"==typeof t.optimistic&&(r.fromOptimisticTransaction=!0),!t.onWatchUpdated||!1!==t.onWatchUpdated.call(this,e,r,n)))&&(n&&(0,nm.D)(n.result,r.result)||e.callback(e.lastDiff=r,n))},t}(io),ar={possibleTypes:{ApproveJobProposalSpecPayload:["ApproveJobProposalSpecSuccess","JobAlreadyExistsError","NotFoundError"],BridgePayload:["Bridge","NotFoundError"],CancelJobProposalSpecPayload:["CancelJobProposalSpecSuccess","NotFoundError"],ChainPayload:["Chain","NotFoundError"],CreateAPITokenPayload:["CreateAPITokenSuccess","InputErrors"],CreateBridgePayload:["CreateBridgeSuccess"],CreateCSAKeyPayload:["CSAKeyExistsError","CreateCSAKeySuccess"],CreateFeedsManagerChainConfigPayload:["CreateFeedsManagerChainConfigSuccess","InputErrors","NotFoundError"],CreateFeedsManagerPayload:["CreateFeedsManagerSuccess","InputErrors","NotFoundError","SingleFeedsManagerError"],CreateJobPayload:["CreateJobSuccess","InputErrors"],CreateOCR2KeyBundlePayload:["CreateOCR2KeyBundleSuccess"],CreateOCRKeyBundlePayload:["CreateOCRKeyBundleSuccess"],CreateP2PKeyPayload:["CreateP2PKeySuccess"],DeleteAPITokenPayload:["DeleteAPITokenSuccess","InputErrors"],DeleteBridgePayload:["DeleteBridgeConflictError","DeleteBridgeInvalidNameError","DeleteBridgeSuccess","NotFoundError"],DeleteCSAKeyPayload:["DeleteCSAKeySuccess","NotFoundError"],DeleteFeedsManagerChainConfigPayload:["DeleteFeedsManagerChainConfigSuccess","NotFoundError"],DeleteJobPayload:["DeleteJobSuccess","NotFoundError"],DeleteOCR2KeyBundlePayload:["DeleteOCR2KeyBundleSuccess","NotFoundError"],DeleteOCRKeyBundlePayload:["DeleteOCRKeyBundleSuccess","NotFoundError"],DeleteP2PKeyPayload:["DeleteP2PKeySuccess","NotFoundError"],DeleteVRFKeyPayload:["DeleteVRFKeySuccess","NotFoundError"],DismissJobErrorPayload:["DismissJobErrorSuccess","NotFoundError"],Error:["CSAKeyExistsError","DeleteBridgeConflictError","DeleteBridgeInvalidNameError","InputError","JobAlreadyExistsError","NotFoundError","RunJobCannotRunError","SingleFeedsManagerError"],EthTransactionPayload:["EthTransaction","NotFoundError"],FeaturesPayload:["Features"],FeedsManagerPayload:["FeedsManager","NotFoundError"],GetSQLLoggingPayload:["SQLLogging"],GlobalLogLevelPayload:["GlobalLogLevel"],JobPayload:["Job","NotFoundError"],JobProposalPayload:["JobProposal","NotFoundError"],JobRunPayload:["JobRun","NotFoundError"],JobSpec:["BlockHeaderFeederSpec","BlockhashStoreSpec","BootstrapSpec","CronSpec","DirectRequestSpec","FluxMonitorSpec","GatewaySpec","KeeperSpec","OCR2Spec","OCRSpec","VRFSpec","WebhookSpec"],NodePayload:["Node","NotFoundError"],PaginatedPayload:["BridgesPayload","ChainsPayload","EthTransactionAttemptsPayload","EthTransactionsPayload","JobRunsPayload","JobsPayload","NodesPayload"],RejectJobProposalSpecPayload:["NotFoundError","RejectJobProposalSpecSuccess"],RunJobPayload:["NotFoundError","RunJobCannotRunError","RunJobSuccess"],SetGlobalLogLevelPayload:["InputErrors","SetGlobalLogLevelSuccess"],SetSQLLoggingPayload:["SetSQLLoggingSuccess"],SetServicesLogLevelsPayload:["InputErrors","SetServicesLogLevelsSuccess"],UpdateBridgePayload:["NotFoundError","UpdateBridgeSuccess"],UpdateFeedsManagerChainConfigPayload:["InputErrors","NotFoundError","UpdateFeedsManagerChainConfigSuccess"],UpdateFeedsManagerPayload:["InputErrors","NotFoundError","UpdateFeedsManagerSuccess"],UpdateJobProposalSpecDefinitionPayload:["NotFoundError","UpdateJobProposalSpecDefinitionSuccess"],UpdatePasswordPayload:["InputErrors","UpdatePasswordSuccess"],VRFKeyPayload:["NotFoundError","VRFKeySuccess"]}};let ai=ar;var aa=(r=void 0,location.origin),ao=new nh({uri:"".concat(aa,"/query"),credentials:"include"}),as=new ia({cache:new an({possibleTypes:ai.possibleTypes}),link:ao});if(a.Z.locale(o),u().defaultFormat="YYYY-MM-DD h:mm:ss A","undefined"!=typeof document){var au,ac,al=f().hydrate;ac=X,al(c.createElement(et,{client:as},c.createElement(d.zj,null,c.createElement(i.MuiThemeProvider,{theme:J.r},c.createElement(ac,null)))),document.getElementById("root"))}})()})(); \ No newline at end of file diff --git a/core/web/assets/main.b0b6f79f7f4a94560e37.js.gz b/core/web/assets/main.b0b6f79f7f4a94560e37.js.gz new file mode 100644 index 00000000000..c505ea9eead Binary files /dev/null and b/core/web/assets/main.b0b6f79f7f4a94560e37.js.gz differ diff --git a/core/web/auth/auth.go b/core/web/auth/auth.go index a0a9df58c79..c2458f52627 100644 --- a/core/web/auth/auth.go +++ b/core/web/auth/auth.go @@ -78,6 +78,9 @@ func AuthenticateByToken(c *gin.Context, authr Authenticator) error { AccessKey: c.GetHeader(APIKey), Secret: c.GetHeader(APISecret), } + if token.AccessKey == "" { + return auth.ErrorAuthFailed + } if token.AccessKey == "" { return auth.ErrorAuthFailed @@ -86,7 +89,7 @@ func AuthenticateByToken(c *gin.Context, authr Authenticator) error { // We need to first load the user row so we can compare tokens using the stored salt user, err := authr.FindUserByAPIToken(token.AccessKey) if err != nil { - if errors.Is(err, sql.ErrNoRows) { + if errors.Is(err, sql.ErrNoRows) || errors.Is(err, clsessions.ErrUserSessionExpired) { return auth.ErrorAuthFailed } return err diff --git a/core/web/auth/auth_test.go b/core/web/auth/auth_test.go index 896542915ae..f0b4e5068fb 100644 --- a/core/web/auth/auth_test.go +++ b/core/web/auth/auth_test.go @@ -33,7 +33,7 @@ func authSuccess(*gin.Context, webauth.Authenticator) error { } type userFindFailer struct { - sessions.ORM + sessions.AuthenticationProvider err error } @@ -46,7 +46,7 @@ func (u userFindFailer) FindUserByAPIToken(token string) (sessions.User, error) } type userFindSuccesser struct { - sessions.ORM + sessions.AuthenticationProvider user sessions.User } diff --git a/core/web/auth/gql_test.go b/core/web/auth/gql_test.go index 4688f62a336..4f3f8e27baf 100644 --- a/core/web/auth/gql_test.go +++ b/core/web/auth/gql_test.go @@ -21,7 +21,7 @@ import ( func Test_AuthenticateGQL_Unauthenticated(t *testing.T) { t.Parallel() - sessionORM := mocks.NewORM(t) + sessionORM := mocks.NewAuthenticationProvider(t) sessionStore := cookie.NewStore([]byte("secret")) r := gin.Default() @@ -44,7 +44,7 @@ func Test_AuthenticateGQL_Unauthenticated(t *testing.T) { func Test_AuthenticateGQL_Authenticated(t *testing.T) { t.Parallel() - sessionORM := mocks.NewORM(t) + sessionORM := mocks.NewAuthenticationProvider(t) sessionStore := cookie.NewStore([]byte(cltest.SessionSecret)) sessionID := "sessionID" diff --git a/core/web/bridge_types_controller.go b/core/web/bridge_types_controller.go index eca99f1a20a..f320a525234 100644 --- a/core/web/bridge_types_controller.go +++ b/core/web/bridge_types_controller.go @@ -8,7 +8,7 @@ import ( "github.com/jackc/pgconn" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" diff --git a/core/web/bridge_types_controller_test.go b/core/web/bridge_types_controller_test.go index 6459cad0545..a7873ad9f75 100644 --- a/core/web/bridge_types_controller_test.go +++ b/core/web/bridge_types_controller_test.go @@ -6,7 +6,7 @@ import ( "net/http" "testing" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" diff --git a/core/web/chains_controller.go b/core/web/chains_controller.go index c11d457c49c..e547cf0150e 100644 --- a/core/web/chains_controller.go +++ b/core/web/chains_controller.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" "github.com/manyminds/api2go/jsonapi" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" diff --git a/core/web/common.go b/core/web/common.go index ae2158d153b..66159d8b60a 100644 --- a/core/web/common.go +++ b/core/web/common.go @@ -5,7 +5,7 @@ import ( "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" ) var ( @@ -15,7 +15,7 @@ var ( ErrMultipleChains = errors.New("more than one chain available, you must specify chain id parameter") ) -func getChain(legacyChains evm.LegacyChainContainer, chainIDstr string) (chain evm.Chain, err error) { +func getChain(legacyChains legacyevm.LegacyChainContainer, chainIDstr string) (chain legacyevm.Chain, err error) { if chainIDstr != "" && chainIDstr != "" { // evm keys are expected to be parsable as a big int diff --git a/core/web/cors_test.go b/core/web/cors_test.go index cfd82dd8b71..fcd5d9b3874 100644 --- a/core/web/cors_test.go +++ b/core/web/cors_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" ) diff --git a/core/web/cosmos_chains_controller_test.go b/core/web/cosmos_chains_controller_test.go index f3f5909940f..5491b33c359 100644 --- a/core/web/cosmos_chains_controller_test.go +++ b/core/web/cosmos_chains_controller_test.go @@ -10,13 +10,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/types" coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - "github.com/smartcontractkit/chainlink-relay/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/web" @@ -76,7 +75,7 @@ Nodes = [] t.Run(tc.name, func(t *testing.T) { t.Parallel() - controller := setupCosmosChainsControllerTestV2(t, &cosmos.CosmosConfig{ + controller := setupCosmosChainsControllerTestV2(t, &coscfg.TOMLConfig{ ChainID: ptr(validId), Enabled: ptr(true), Chain: coscfg.Chain{ @@ -106,7 +105,7 @@ Nodes = [] func Test_CosmosChainsController_Index(t *testing.T) { t.Parallel() - chainA := &cosmos.CosmosConfig{ + chainA := &coscfg.TOMLConfig{ ChainID: ptr("a" + cosmostest.RandomChainID()), Enabled: ptr(true), Chain: coscfg.Chain{ @@ -114,7 +113,7 @@ func Test_CosmosChainsController_Index(t *testing.T) { }, } - chainB := &cosmos.CosmosConfig{ + chainB := &coscfg.TOMLConfig{ ChainID: ptr("b" + cosmostest.RandomChainID()), Enabled: ptr(true), Chain: coscfg.Chain{ @@ -174,7 +173,7 @@ type TestCosmosChainsController struct { client cltest.HTTPClientCleaner } -func setupCosmosChainsControllerTestV2(t *testing.T, cfgs ...*cosmos.CosmosConfig) *TestCosmosChainsController { +func setupCosmosChainsControllerTestV2(t *testing.T, cfgs ...*coscfg.TOMLConfig) *TestCosmosChainsController { for i := range cfgs { cfgs[i].SetDefaults() } diff --git a/core/web/cosmos_transfer_controller.go b/core/web/cosmos_transfer_controller.go index afe0fe16d1e..965f694fc1b 100644 --- a/core/web/cosmos_transfer_controller.go +++ b/core/web/cosmos_transfer_controller.go @@ -1,26 +1,26 @@ package web import ( + "fmt" "net/http" + "slices" sdk "github.com/cosmos/cosmos-sdk/types" - bank "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/gin-gonic/gin" + "github.com/google/uuid" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/client" + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" + "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/db" "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/denom" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/chains/cosmos" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" cosmosmodels "github.com/smartcontractkit/chainlink/v2/core/store/models/cosmos" "github.com/smartcontractkit/chainlink/v2/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 - // CosmosTransfersController can send LINK tokens to another address type CosmosTransfersController struct { App chainlink.Application @@ -28,9 +28,9 @@ type CosmosTransfersController struct { // Create sends native coins from the Chainlink's account to a specified address. func (tc *CosmosTransfersController) Create(c *gin.Context) { - cosmosChains := tc.App.GetRelayers().LegacyCosmosChains() - if cosmosChains == nil { - jsonAPIError(c, http.StatusBadRequest, ErrCosmosNotEnabled) + relayers := tc.App.GetRelayers().List(chainlink.FilterRelayersByType(relay.Cosmos)) + if relayers == nil { + jsonAPIError(c, http.StatusBadRequest, ErrSolanaNotEnabled) return } @@ -43,91 +43,51 @@ func (tc *CosmosTransfersController) Create(c *gin.Context) { jsonAPIError(c, http.StatusBadRequest, errors.New("missing cosmosChainID")) return } - // TODO what about ctx in Get? ctx was used here but not in ETH calls. maybe better to make the interface require ctx and - // put in TODOs in ETH... - chain, err := cosmosChains.Get(tr.CosmosChainID) //cosmosChains.Chain(c.Request.Context(), tr.CosmosChainID) - if errors.Is(err, cosmos.ErrChainIDInvalid) || errors.Is(err, cosmos.ErrChainIDEmpty) { - jsonAPIError(c, http.StatusBadRequest, err) + if tr.FromAddress.Empty() { + jsonAPIError(c, http.StatusUnprocessableEntity, errors.Errorf("withdrawal source address is missing: %v", tr.FromAddress)) return - } else if err != nil { + } + + relayerID := relay.ID{Network: relay.Cosmos, ChainID: tr.CosmosChainID} + relayer, err := relayers.Get(relayerID) + if err != nil { + if errors.Is(err, chainlink.ErrNoSuchRelayer) { + jsonAPIError(c, http.StatusBadRequest, err) + return + } jsonAPIError(c, http.StatusInternalServerError, err) return } - - if tr.FromAddress.Empty() { - jsonAPIError(c, http.StatusUnprocessableEntity, errors.Errorf("withdrawal source address is missing: %v", tr.FromAddress)) + var gasToken string + cfgs := tc.App.GetConfig().CosmosConfigs() + if i := slices.IndexFunc(cfgs, func(config *coscfg.TOMLConfig) bool { return *config.ChainID == tr.CosmosChainID }); i != -1 { + gasToken = cfgs[i].GasToken() + } else { + jsonAPIError(c, http.StatusInternalServerError, fmt.Errorf("no config for chain id: %s", tr.CosmosChainID)) return } - coin, err := denom.ConvertDecCoinToDenom(sdk.NewDecCoinFromDec(tr.Token, tr.Amount), chain.Config().GasToken()) + + //TODO move this inside? + coin, err := denom.ConvertDecCoinToDenom(sdk.NewDecCoinFromDec(tr.Token, tr.Amount), gasToken) if err != nil { - jsonAPIError(c, http.StatusBadRequest, errors.Errorf("unable to convert %s to %s: %v", tr.Token, chain.Config().GasToken(), err)) + jsonAPIError(c, http.StatusBadRequest, errors.Errorf("unable to convert %s to %s: %v", tr.Token, gasToken, 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, err2 := txm.GasPrice() - if err2 != nil { - jsonAPIError(c, http.StatusInternalServerError, errors.Errorf("gas price unavailable: %v", err2)) - return - } - - err = cosmosValidateBalance(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) + err = relayer.Transact(c, tr.FromAddress.String(), tr.DestinationAddress.String(), coin.Amount.BigInt(), !tr.AllowHigherAmounts) if err != nil { - jsonAPIError(c, http.StatusInternalServerError, errors.Errorf("transaction failed: %v", err)) + jsonAPIError(c, http.StatusInternalServerError, errors.Errorf("failed to send transaction: %v", err)) return } - resource := presenters.NewCosmosMsgResource(msgID, tr.CosmosChainID, "") - 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) + resource := presenters.NewCosmosMsgResource("cosmos_transfer_"+uuid.New().String(), tr.CosmosChainID, "") + resource.State = string(db.Unstarted) tc.App.GetAuditLogger().Audit(audit.CosmosTransactionCreated, map[string]interface{}{ "cosmosTransactionResource": resource, }) jsonAPIResponse(c, resource, "cosmos_msg") } - -// cosmosValidateBalance validates that fromAddr's balance can cover coin, including fees at gasPrice. -func cosmosValidateBalance(reader client.Reader, gasPrice sdk.DecCoin, fromAddr sdk.AccAddress, coin sdk.Coin) error { - balance, err := reader.Balance(fromAddr, coin.GetDenom()) - 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/eth_keys_controller.go b/core/web/eth_keys_controller.go index 28afe8c43bf..d99992c0f56 100644 --- a/core/web/eth_keys_controller.go +++ b/core/web/eth_keys_controller.go @@ -9,8 +9,9 @@ import ( "strconv" "strings" - "github.com/smartcontractkit/chainlink/v2/core/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/config/toml" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" @@ -364,13 +365,13 @@ func (ekc *ETHKeysController) getEthBalance(ctx context.Context, state ethkey.St } -func (ekc *ETHKeysController) setLinkBalance(bal *assets.Link) presenters.NewETHKeyOption { +func (ekc *ETHKeysController) setLinkBalance(bal *commonassets.Link) presenters.NewETHKeyOption { return presenters.SetETHKeyLinkBalance(bal) } // queries the EthClient for the LINK balance at the address associated with state -func (ekc *ETHKeysController) getLinkBalance(ctx context.Context, state ethkey.State) *assets.Link { - var bal *assets.Link +func (ekc *ETHKeysController) getLinkBalance(ctx context.Context, state ethkey.State) *commonassets.Link { + var bal *commonassets.Link chainID := state.EVMChainID.ToInt() chain, err := ekc.app.GetRelayers().LegacyEVMChains().Get(chainID.String()) if err != nil { @@ -411,7 +412,7 @@ func (ekc *ETHKeysController) getKeyMaxGasPriceWei(state ethkey.State, keyAddres // getChain is a convenience wrapper to retrieve a chain for a given request // and call the corresponding API response error function for 400, 404 and 500 results -func (ekc *ETHKeysController) getChain(c *gin.Context, chainIDstr string) (chain evm.Chain, ok bool) { +func (ekc *ETHKeysController) getChain(c *gin.Context, chainIDstr string) (chain legacyevm.Chain, ok bool) { chain, err := getChain(ekc.app.GetRelayers().LegacyEVMChains(), chainIDstr) if err != nil { if errors.Is(err, ErrInvalidChainID) || errors.Is(err, ErrMultipleChains) { diff --git a/core/web/eth_keys_controller_test.go b/core/web/eth_keys_controller_test.go index 98641721737..d0ad27262f2 100644 --- a/core/web/eth_keys_controller_test.go +++ b/core/web/eth_keys_controller_test.go @@ -8,13 +8,13 @@ import ( "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/assets" commontxmmocks "github.com/smartcontractkit/chainlink/v2/common/txmgr/types/mocks" commonmocks "github.com/smartcontractkit/chainlink/v2/common/types/mocks" - "github.com/smartcontractkit/chainlink/v2/core/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" diff --git a/core/web/evm_chains_controller_test.go b/core/web/evm_chains_controller_test.go index 4ebf06f2b6d..3d5a4e3eedd 100644 --- a/core/web/evm_chains_controller_test.go +++ b/core/web/evm_chains_controller_test.go @@ -14,7 +14,7 @@ import ( evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/utils" diff --git a/core/web/evm_forwarders_controller_test.go b/core/web/evm_forwarders_controller_test.go index 46820b42337..31e49f20ecc 100644 --- a/core/web/evm_forwarders_controller_test.go +++ b/core/web/evm_forwarders_controller_test.go @@ -13,7 +13,7 @@ import ( evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/v2/core/web" diff --git a/core/web/evm_transactions_controller_test.go b/core/web/evm_transactions_controller_test.go index 9d8336325e2..9135be432de 100644 --- a/core/web/evm_transactions_controller_test.go +++ b/core/web/evm_transactions_controller_test.go @@ -6,7 +6,7 @@ import ( "testing" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" diff --git a/core/web/evm_transfer_controller.go b/core/web/evm_transfer_controller.go index f5973a20f2f..6ab621661a6 100644 --- a/core/web/evm_transfer_controller.go +++ b/core/web/evm_transfer_controller.go @@ -11,9 +11,9 @@ import ( "github.com/pkg/errors" commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/store/models" @@ -92,7 +92,7 @@ func (tc *EVMTransfersController) Create(c *gin.Context) { } // ValidateEthBalanceForTransfer validates that the current balance can cover the transaction amount -func ValidateEthBalanceForTransfer(c *gin.Context, chain evm.Chain, fromAddr common.Address, amount assets.Eth) error { +func ValidateEthBalanceForTransfer(c *gin.Context, chain legacyevm.Chain, fromAddr common.Address, amount assets.Eth) error { var err error var balance *big.Int diff --git a/core/web/evm_transfer_controller_test.go b/core/web/evm_transfer_controller_test.go index 0f3cdc8bb69..c41219e1894 100644 --- a/core/web/evm_transfer_controller_test.go +++ b/core/web/evm_transfer_controller_test.go @@ -10,12 +10,12 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/store/models" @@ -125,7 +125,7 @@ func TestTransfersController_CreateSuccess_From_BalanceMonitorDisabled(t *testin ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil) ethClient.On("BalanceAt", mock.Anything, key.Address, (*big.Int)(nil)).Return(balance.ToInt(), nil) - config := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].BalanceMonitor.Enabled = ptr(false) }) @@ -285,7 +285,7 @@ func TestTransfersController_CreateSuccess_eip1559(t *testing.T) { ethClient.On("BalanceAt", mock.Anything, key.Address, (*big.Int)(nil)).Return(balance.ToInt(), nil) ethClient.On("SequenceAt", mock.Anything, mock.Anything, mock.Anything).Return(evmtypes.Nonce(0), nil).Maybe() - config := configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true) c.EVM[0].GasEstimator.Mode = ptr("FixedPrice") c.EVM[0].ChainID = (*utils.Big)(testutils.FixtureChainID) diff --git a/core/web/external_initiators_controller_test.go b/core/web/external_initiators_controller_test.go index 2229b40b7ef..6a1b715b728 100644 --- a/core/web/external_initiators_controller_test.go +++ b/core/web/external_initiators_controller_test.go @@ -10,7 +10,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -69,7 +69,7 @@ func TestExternalInitiatorsController_Index(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithConfig(t, - configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.JobPipeline.ExternalInitiatorsEnabled = ptr(true) })) require.NoError(t, app.Start(testutils.Context(t))) @@ -135,7 +135,7 @@ func TestExternalInitiatorsController_Create_success(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithConfig(t, - configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.JobPipeline.ExternalInitiatorsEnabled = ptr(true) })) require.NoError(t, app.Start(testutils.Context(t))) @@ -163,7 +163,7 @@ func TestExternalInitiatorsController_Create_without_URL(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithConfig(t, - configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.JobPipeline.ExternalInitiatorsEnabled = ptr(true) })) require.NoError(t, app.Start(testutils.Context(t))) @@ -191,7 +191,7 @@ func TestExternalInitiatorsController_Create_invalid(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithConfig(t, - configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.JobPipeline.ExternalInitiatorsEnabled = ptr(true) })) require.NoError(t, app.Start(testutils.Context(t))) @@ -209,7 +209,7 @@ func TestExternalInitiatorsController_Delete(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithConfig(t, - configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.JobPipeline.ExternalInitiatorsEnabled = ptr(true) })) require.NoError(t, app.Start(testutils.Context(t))) @@ -231,7 +231,7 @@ func TestExternalInitiatorsController_DeleteNotFound(t *testing.T) { t.Parallel() app := cltest.NewApplicationWithConfig(t, - configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.JobPipeline.ExternalInitiatorsEnabled = ptr(true) })) require.NoError(t, app.Start(testutils.Context(t))) diff --git a/core/web/features_controller_test.go b/core/web/features_controller_test.go index 8ef2e08d394..727d7db5476 100644 --- a/core/web/features_controller_test.go +++ b/core/web/features_controller_test.go @@ -6,7 +6,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/web" "github.com/smartcontractkit/chainlink/v2/core/web/presenters" diff --git a/core/web/gui_assets_test.go b/core/web/gui_assets_test.go index 784b0958f59..137b1231984 100644 --- a/core/web/gui_assets_test.go +++ b/core/web/gui_assets_test.go @@ -8,7 +8,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" clhttptest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/httptest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/web" diff --git a/core/web/jobs_controller.go b/core/web/jobs_controller.go index 4c8a77d370e..0f97e0b53d3 100644 --- a/core/web/jobs_controller.go +++ b/core/web/jobs_controller.go @@ -71,7 +71,7 @@ func (jc *JobsController) Show(c *gin.Context) { jobSpec, err = jc.App.JobORM().FindJobByExternalJobID(externalJobID, pg.WithParentCtx(c.Request.Context())) } else if pErr = jobSpec.SetID(c.Param("ID")); pErr == nil { // Find a job by job ID - jobSpec, err = jc.App.JobORM().FindJobTx(jobSpec.ID) + jobSpec, err = jc.App.JobORM().FindJobTx(c, jobSpec.ID) } else { jsonAPIError(c, http.StatusUnprocessableEntity, pErr) return diff --git a/core/web/jobs_controller_test.go b/core/web/jobs_controller_test.go index 345662909a4..2e3f3a83693 100644 --- a/core/web/jobs_controller_test.go +++ b/core/web/jobs_controller_test.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "math/big" "net/http" "net/url" "strconv" @@ -19,13 +20,15 @@ import ( p2ppeer "github.com/libp2p/go-libp2p-core/peer" "github.com/pelletier/go-toml" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/sqlx" + "github.com/jmoiron/sqlx" + evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/directrequest" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -44,7 +47,7 @@ func TestJobsController_Create_ValidationFailure_OffchainReportingSpec(t *testin contractAddress = cltest.NewEIP55Address() ) - peerID, err := p2ppeer.Decode("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") + peerID, err := p2ppeer.Decode(configtest.DefaultPeerID) require.NoError(t, err) randomBytes := testutils.Random32Byte() @@ -658,7 +661,8 @@ func setupJobsControllerTests(t *testing.T) (ta *cltest.TestApplication, cc clte c.P2P.V1.Enabled = ptr(true) c.P2P.PeerID = &cltest.DefaultP2PPeerID }) - app := cltest.NewApplicationWithConfigAndKey(t, cfg, cltest.DefaultP2PKey) + ec := setupEthClientForControllerTests(t) + app := cltest.NewApplicationWithConfigAndKey(t, cfg, cltest.DefaultP2PKey, ec) require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient(nil) @@ -668,6 +672,14 @@ func setupJobsControllerTests(t *testing.T) (ta *cltest.TestApplication, cc clte return app, client } +func setupEthClientForControllerTests(t *testing.T) *evmclimocks.Client { + ec := cltest.NewEthMocksWithStartupAssertions(t) + ec.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil).Maybe() + ec.On("LatestBlockHeight", mock.Anything).Return(big.NewInt(100), nil).Maybe() + ec.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Once().Return(big.NewInt(0), nil).Maybe() + return ec +} + func setupJobSpecsControllerTestsWithJobs(t *testing.T) (*cltest.TestApplication, cltest.HTTPClientCleaner, job.Job, int32, job.Job, int32) { cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.OCR.Enabled = ptr(true) diff --git a/core/web/loader/getters.go b/core/web/loader/getters.go index 8ca29cc9624..27a39181ff8 100644 --- a/core/web/loader/getters.go +++ b/core/web/loader/getters.go @@ -7,7 +7,7 @@ import ( "github.com/pkg/errors" "go.uber.org/multierr" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -21,7 +21,7 @@ import ( var ErrInvalidType = errors.New("invalid type") // GetChainByID fetches the chain by it's id. -func GetChainByID(ctx context.Context, id string) (*relaytypes.ChainStatus, error) { +func GetChainByID(ctx context.Context, id string) (*commontypes.ChainStatus, error) { ldr := For(ctx) thunk := ldr.ChainsByIDLoader.Load(ctx, dataloader.StringKey(id)) @@ -30,7 +30,7 @@ func GetChainByID(ctx context.Context, id string) (*relaytypes.ChainStatus, erro return nil, err } - chain, ok := result.(relaytypes.ChainStatus) + chain, ok := result.(commontypes.ChainStatus) if !ok { return nil, ErrInvalidType } diff --git a/core/web/loader/loader_test.go b/core/web/loader/loader_test.go index 0dd45a1735d..9bd1feb05bf 100644 --- a/core/web/loader/loader_test.go +++ b/core/web/loader/loader_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/chains" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" @@ -26,7 +26,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/job" jobORMMocks "github.com/smartcontractkit/chainlink/v2/core/services/job/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -51,12 +50,12 @@ func TestLoader_Chains(t *testing.T) { assert.Len(t, results, 3) config2, err := chain2.TOMLString() require.NoError(t, err) - want2 := relaytypes.ChainStatus{ID: "2", Enabled: true, Config: config2} - assert.Equal(t, want2, results[0].Data.(relaytypes.ChainStatus)) + want2 := commontypes.ChainStatus{ID: "2", Enabled: true, Config: config2} + assert.Equal(t, want2, results[0].Data.(commontypes.ChainStatus)) config1, err := chain.TOMLString() require.NoError(t, err) - want1 := relaytypes.ChainStatus{ID: "1", Enabled: true, Config: config1} - assert.Equal(t, want1, results[1].Data.(relaytypes.ChainStatus)) + want1 := commontypes.ChainStatus{ID: "1", Enabled: true, Config: config1} + assert.Equal(t, want1, results[1].Data.(commontypes.ChainStatus)) assert.Nil(t, results[2].Data) assert.Error(t, results[2].Error) assert.ErrorIs(t, results[2].Error, chains.ErrNotFound) @@ -69,21 +68,16 @@ func TestLoader_Nodes(t *testing.T) { ctx := InjectDataloader(testutils.Context(t), app) chainID1, chainID2, notAnID := big.NewInt(1), big.NewInt(2), big.NewInt(3) - relayID1 := relay.ID{Network: relay.EVM, ChainID: relay.ChainID(chainID1.String())} - relayID2 := relay.ID{Network: relay.EVM, ChainID: relay.ChainID(chainID2.String())} - notARelayID := relay.ID{Network: relay.EVM, ChainID: relay.ChainID(notAnID.String())} - genNodeStat := func(id string) relaytypes.NodeStatus { - return relaytypes.NodeStatus{ + genNodeStat := func(id string) commontypes.NodeStatus { + return commontypes.NodeStatus{ Name: "test-node-" + id, ChainID: id, } } - rcInterops := chainlinkmocks.NewRelayerChainInteroperators(t) - rcInterops.On("NodeStatuses", mock.Anything, 0, -1, - relayID2, relayID1, notARelayID).Return([]relaytypes.NodeStatus{ + rcInterops := &chainlinkmocks.FakeRelayerChainInteroperators{Nodes: []commontypes.NodeStatus{ genNodeStat(chainID2.String()), genNodeStat(chainID1.String()), - }, 2, nil) + }} app.On("GetRelayers").Return(rcInterops) batcher := nodeBatcher{app} @@ -92,9 +86,9 @@ func TestLoader_Nodes(t *testing.T) { found := batcher.loadByChainIDs(ctx, keys) require.Len(t, found, 3) - assert.Equal(t, []relaytypes.NodeStatus{genNodeStat(chainID2.String())}, found[0].Data) - assert.Equal(t, []relaytypes.NodeStatus{genNodeStat(chainID1.String())}, found[1].Data) - assert.Equal(t, []relaytypes.NodeStatus{}, found[2].Data) + assert.Equal(t, []commontypes.NodeStatus{genNodeStat(chainID2.String())}, found[0].Data) + assert.Equal(t, []commontypes.NodeStatus{genNodeStat(chainID1.String())}, found[1].Data) + assert.Equal(t, []commontypes.NodeStatus{}, found[2].Data) } func TestLoader_FeedsManagers(t *testing.T) { diff --git a/core/web/loader/node.go b/core/web/loader/node.go index 9ea6062dc29..ef8e363d9f3 100644 --- a/core/web/loader/node.go +++ b/core/web/loader/node.go @@ -5,7 +5,7 @@ import ( "github.com/graph-gophers/dataloader" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/relay" diff --git a/core/web/log_controller_test.go b/core/web/log_controller_test.go index e4cd1768cef..dbb95361b98 100644 --- a/core/web/log_controller_test.go +++ b/core/web/log_controller_test.go @@ -14,7 +14,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/config/toml" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/web" "github.com/smartcontractkit/chainlink/v2/core/web/presenters" diff --git a/core/web/loop_registry_test.go b/core/web/loop_registry_test.go index 59a4d0df686..94e2bc856d6 100644 --- a/core/web/loop_registry_test.go +++ b/core/web/loop_registry_test.go @@ -14,10 +14,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" ) diff --git a/core/web/middleware.go b/core/web/middleware.go index 91c618cecbd..17bd7e65eba 100644 --- a/core/web/middleware.go +++ b/core/web/middleware.go @@ -21,6 +21,7 @@ import ( // inside this module. To achieve this, we direct webpack to output all of the compiled assets // in this module's folder under the "assets" directory. +//go:generate ../../operator_ui/install.sh //go:embed "assets" var uiEmbedFs embed.FS diff --git a/core/web/nodes_controller.go b/core/web/nodes_controller.go index 3547b150bc7..04c4693983f 100644 --- a/core/web/nodes_controller.go +++ b/core/web/nodes_controller.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" "github.com/manyminds/api2go/jsonapi" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" diff --git a/core/web/pipeline_runs_controller_test.go b/core/web/pipeline_runs_controller_test.go index 5b17fcb007e..c44ee9ae8da 100644 --- a/core/web/pipeline_runs_controller_test.go +++ b/core/web/pipeline_runs_controller_test.go @@ -19,7 +19,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/webhook" diff --git a/core/web/presenters/bridges.go b/core/web/presenters/bridges.go index 3317895e166..82f4a961c94 100644 --- a/core/web/presenters/bridges.go +++ b/core/web/presenters/bridges.go @@ -3,7 +3,7 @@ package presenters import ( "time" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" ) diff --git a/core/web/presenters/bridges_test.go b/core/web/presenters/bridges_test.go index b9fefd022d2..746d361958a 100644 --- a/core/web/presenters/bridges_test.go +++ b/core/web/presenters/bridges_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/store/models" ) diff --git a/core/web/presenters/cosmos_chain.go b/core/web/presenters/cosmos_chain.go index d65a110287d..c3b006e5c7e 100644 --- a/core/web/presenters/cosmos_chain.go +++ b/core/web/presenters/cosmos_chain.go @@ -1,7 +1,7 @@ package presenters import ( - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" ) // CosmosChainResource is an Cosmos chain JSONAPI resource. diff --git a/core/web/presenters/cosmos_msg.go b/core/web/presenters/cosmos_msg.go index d4fbc905bd6..5bf0bb9b4f8 100644 --- a/core/web/presenters/cosmos_msg.go +++ b/core/web/presenters/cosmos_msg.go @@ -15,9 +15,9 @@ func (CosmosMsgResource) GetName() string { } // NewCosmosMsgResource returns a new partial CosmosMsgResource. -func NewCosmosMsgResource(id int64, chainID string, contractID string) CosmosMsgResource { +func NewCosmosMsgResource(id string, chainID string, contractID string) CosmosMsgResource { return CosmosMsgResource{ - JAID: NewJAIDInt64(id), + JAID: NewJAID(id), ChainID: chainID, ContractID: contractID, } diff --git a/core/web/presenters/eth_key.go b/core/web/presenters/eth_key.go index 167679513ef..3d952dabeda 100644 --- a/core/web/presenters/eth_key.go +++ b/core/web/presenters/eth_key.go @@ -3,7 +3,8 @@ package presenters import ( "time" - "github.com/smartcontractkit/chainlink/v2/core/assets" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -12,14 +13,14 @@ import ( // representation of the address plus its ETH & LINK balances type ETHKeyResource struct { JAID - EVMChainID utils.Big `json:"evmChainID"` - Address string `json:"address"` - EthBalance *assets.Eth `json:"ethBalance"` - LinkBalance *assets.Link `json:"linkBalance"` - Disabled bool `json:"disabled"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - MaxGasPriceWei *utils.Big `json:"maxGasPriceWei"` + EVMChainID utils.Big `json:"evmChainID"` + Address string `json:"address"` + EthBalance *assets.Eth `json:"ethBalance"` + LinkBalance *commonassets.Link `json:"linkBalance"` + Disabled bool `json:"disabled"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + MaxGasPriceWei *utils.Big `json:"maxGasPriceWei"` } // GetName implements the api2go EntityNamer interface @@ -62,7 +63,7 @@ func SetETHKeyEthBalance(ethBalance *assets.Eth) NewETHKeyOption { } } -func SetETHKeyLinkBalance(linkBalance *assets.Link) NewETHKeyOption { +func SetETHKeyLinkBalance(linkBalance *commonassets.Link) NewETHKeyOption { return func(r *ETHKeyResource) { r.LinkBalance = linkBalance } diff --git a/core/web/presenters/eth_key_test.go b/core/web/presenters/eth_key_test.go index 7afb55068fb..85d005cf610 100644 --- a/core/web/presenters/eth_key_test.go +++ b/core/web/presenters/eth_key_test.go @@ -5,7 +5,8 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/v2/core/assets" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -39,12 +40,12 @@ func TestETHKeyResource(t *testing.T) { r := NewETHKeyResource(key, state, SetETHKeyEthBalance(assets.NewEth(1)), - SetETHKeyLinkBalance(assets.NewLinkFromJuels(1)), + SetETHKeyLinkBalance(commonassets.NewLinkFromJuels(1)), SetETHKeyMaxGasPriceWei(utils.NewBigI(12345)), ) assert.Equal(t, assets.NewEth(1), r.EthBalance) - assert.Equal(t, assets.NewLinkFromJuels(1), r.LinkBalance) + assert.Equal(t, commonassets.NewLinkFromJuels(1), r.LinkBalance) assert.Equal(t, utils.NewBigI(12345), r.MaxGasPriceWei) b, err := jsonapi.Marshal(r) diff --git a/core/web/presenters/eth_tx.go b/core/web/presenters/eth_tx.go index 8878733d708..2c2b5b90ff2 100644 --- a/core/web/presenters/eth_tx.go +++ b/core/web/presenters/eth_tx.go @@ -6,7 +6,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/utils" ) diff --git a/core/web/presenters/eth_tx_test.go b/core/web/presenters/eth_tx_test.go index 10f4ceab006..2ed8e23c76a 100644 --- a/core/web/presenters/eth_tx_test.go +++ b/core/web/presenters/eth_tx_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" diff --git a/core/web/presenters/evm_chain.go b/core/web/presenters/evm_chain.go index 25862875ee9..8cc6da46a77 100644 --- a/core/web/presenters/evm_chain.go +++ b/core/web/presenters/evm_chain.go @@ -1,6 +1,6 @@ package presenters -import "github.com/smartcontractkit/chainlink-relay/pkg/types" +import "github.com/smartcontractkit/chainlink-common/pkg/types" // EVMChainResource is an EVM chain JSONAPI resource. type EVMChainResource struct { diff --git a/core/web/presenters/job.go b/core/web/presenters/job.go index 2aa97730881..dc5bf623330 100644 --- a/core/web/presenters/job.go +++ b/core/web/presenters/job.go @@ -7,7 +7,8 @@ import ( "github.com/lib/pq" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink/v2/core/assets" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" clnull "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" @@ -41,26 +42,24 @@ const ( // DirectRequestSpec defines the spec details of a DirectRequest Job type DirectRequestSpec struct { - ContractAddress ethkey.EIP55Address `json:"contractAddress"` - MinIncomingConfirmations clnull.Uint32 `json:"minIncomingConfirmations"` - MinIncomingConfirmationsEnv bool `json:"minIncomingConfirmationsEnv,omitempty"` - MinContractPayment *assets.Link `json:"minContractPaymentLinkJuels"` - Requesters models.AddressCollection `json:"requesters"` - Initiator string `json:"initiator"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - EVMChainID *utils.Big `json:"evmChainID"` + ContractAddress ethkey.EIP55Address `json:"contractAddress"` + MinIncomingConfirmations clnull.Uint32 `json:"minIncomingConfirmations"` + MinContractPayment *commonassets.Link `json:"minContractPaymentLinkJuels"` + Requesters models.AddressCollection `json:"requesters"` + Initiator string `json:"initiator"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + EVMChainID *utils.Big `json:"evmChainID"` } // NewDirectRequestSpec initializes a new DirectRequestSpec from a // job.DirectRequestSpec func NewDirectRequestSpec(spec *job.DirectRequestSpec) *DirectRequestSpec { return &DirectRequestSpec{ - ContractAddress: spec.ContractAddress, - MinIncomingConfirmations: spec.MinIncomingConfirmations, - MinIncomingConfirmationsEnv: spec.MinIncomingConfirmationsEnv, - MinContractPayment: spec.MinContractPayment, - Requesters: spec.Requesters, + ContractAddress: spec.ContractAddress, + MinIncomingConfirmations: spec.MinIncomingConfirmations, + MinContractPayment: spec.MinContractPayment, + Requesters: spec.Requesters, // This is hardcoded to runlog. When we support other initiators, we need // to change this Initiator: "runlog", @@ -82,7 +81,7 @@ type FluxMonitorSpec struct { DrumbeatEnabled bool `json:"drumbeatEnabled"` DrumbeatSchedule *string `json:"drumbeatSchedule"` DrumbeatRandomDelay *string `json:"drumbeatRandomDelay"` - MinPayment *assets.Link `json:"minPayment"` + MinPayment *commonassets.Link `json:"minPayment"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` EVMChainID *utils.Big `json:"evmChainID"` @@ -120,64 +119,48 @@ func NewFluxMonitorSpec(spec *job.FluxMonitorSpec) *FluxMonitorSpec { // OffChainReportingSpec defines the spec details of a OffChainReporting Job type OffChainReportingSpec struct { - ContractAddress ethkey.EIP55Address `json:"contractAddress"` - P2PBootstrapPeers pq.StringArray `json:"p2pBootstrapPeers"` - P2PV2Bootstrappers pq.StringArray `json:"p2pv2Bootstrappers"` - IsBootstrapPeer bool `json:"isBootstrapPeer"` - EncryptedOCRKeyBundleID *models.Sha256Hash `json:"keyBundleID"` - TransmitterAddress *ethkey.EIP55Address `json:"transmitterAddress"` - ObservationTimeout models.Interval `json:"observationTimeout"` - ObservationTimeoutEnv bool `json:"observationTimeoutEnv,omitempty"` - BlockchainTimeout models.Interval `json:"blockchainTimeout"` - BlockchainTimeoutEnv bool `json:"blockchainTimeoutEnv,omitempty"` - ContractConfigTrackerSubscribeInterval models.Interval `json:"contractConfigTrackerSubscribeInterval"` - ContractConfigTrackerSubscribeIntervalEnv bool `json:"contractConfigTrackerSubscribeIntervalEnv,omitempty"` - ContractConfigTrackerPollInterval models.Interval `json:"contractConfigTrackerPollInterval"` - ContractConfigTrackerPollIntervalEnv bool `json:"contractConfigTrackerPollIntervalEnv,omitempty"` - ContractConfigConfirmations uint16 `json:"contractConfigConfirmations"` - ContractConfigConfirmationsEnv bool `json:"contractConfigConfirmationsEnv,omitempty"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - EVMChainID *utils.Big `json:"evmChainID"` - DatabaseTimeout *models.Interval `json:"databaseTimeout"` - DatabaseTimeoutEnv bool `json:"databaseTimeoutEnv,omitempty"` - ObservationGracePeriod *models.Interval `json:"observationGracePeriod"` - ObservationGracePeriodEnv bool `json:"observationGracePeriodEnv,omitempty"` - ContractTransmitterTransmitTimeout *models.Interval `json:"contractTransmitterTransmitTimeout"` - ContractTransmitterTransmitTimeoutEnv bool `json:"contractTransmitterTransmitTimeoutEnv,omitempty"` - CollectTelemetry bool `json:"collectTelemetry,omitempty"` + ContractAddress ethkey.EIP55Address `json:"contractAddress"` + P2PBootstrapPeers pq.StringArray `json:"p2pBootstrapPeers"` + P2PV2Bootstrappers pq.StringArray `json:"p2pv2Bootstrappers"` + IsBootstrapPeer bool `json:"isBootstrapPeer"` + EncryptedOCRKeyBundleID *models.Sha256Hash `json:"keyBundleID"` + TransmitterAddress *ethkey.EIP55Address `json:"transmitterAddress"` + ObservationTimeout models.Interval `json:"observationTimeout"` + BlockchainTimeout models.Interval `json:"blockchainTimeout"` + ContractConfigTrackerSubscribeInterval models.Interval `json:"contractConfigTrackerSubscribeInterval"` + ContractConfigTrackerPollInterval models.Interval `json:"contractConfigTrackerPollInterval"` + ContractConfigConfirmations uint16 `json:"contractConfigConfirmations"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + EVMChainID *utils.Big `json:"evmChainID"` + DatabaseTimeout *models.Interval `json:"databaseTimeout"` + ObservationGracePeriod *models.Interval `json:"observationGracePeriod"` + ContractTransmitterTransmitTimeout *models.Interval `json:"contractTransmitterTransmitTimeout"` + CollectTelemetry bool `json:"collectTelemetry,omitempty"` } // NewOffChainReportingSpec initializes a new OffChainReportingSpec from a // job.OCROracleSpec func NewOffChainReportingSpec(spec *job.OCROracleSpec) *OffChainReportingSpec { return &OffChainReportingSpec{ - ContractAddress: spec.ContractAddress, - P2PBootstrapPeers: spec.P2PBootstrapPeers, - P2PV2Bootstrappers: spec.P2PV2Bootstrappers, - IsBootstrapPeer: spec.IsBootstrapPeer, - EncryptedOCRKeyBundleID: spec.EncryptedOCRKeyBundleID, - TransmitterAddress: spec.TransmitterAddress, - ObservationTimeout: spec.ObservationTimeout, - ObservationTimeoutEnv: spec.ObservationTimeoutEnv, - BlockchainTimeout: spec.BlockchainTimeout, - BlockchainTimeoutEnv: spec.BlockchainTimeoutEnv, - ContractConfigTrackerSubscribeInterval: spec.ContractConfigTrackerSubscribeInterval, - ContractConfigTrackerSubscribeIntervalEnv: spec.ContractConfigTrackerSubscribeIntervalEnv, - ContractConfigTrackerPollInterval: spec.ContractConfigTrackerPollInterval, - ContractConfigTrackerPollIntervalEnv: spec.ContractConfigTrackerPollIntervalEnv, - ContractConfigConfirmations: spec.ContractConfigConfirmations, - ContractConfigConfirmationsEnv: spec.ContractConfigConfirmationsEnv, - CreatedAt: spec.CreatedAt, - UpdatedAt: spec.UpdatedAt, - EVMChainID: spec.EVMChainID, - DatabaseTimeout: spec.DatabaseTimeout, - DatabaseTimeoutEnv: spec.DatabaseTimeoutEnv, - ObservationGracePeriod: spec.ObservationGracePeriod, - ObservationGracePeriodEnv: spec.ObservationGracePeriodEnv, - ContractTransmitterTransmitTimeout: spec.ContractTransmitterTransmitTimeout, - ContractTransmitterTransmitTimeoutEnv: spec.ContractTransmitterTransmitTimeoutEnv, - CollectTelemetry: spec.CaptureEATelemetry, + ContractAddress: spec.ContractAddress, + P2PBootstrapPeers: spec.P2PBootstrapPeers, + P2PV2Bootstrappers: spec.P2PV2Bootstrappers, + IsBootstrapPeer: spec.IsBootstrapPeer, + EncryptedOCRKeyBundleID: spec.EncryptedOCRKeyBundleID, + TransmitterAddress: spec.TransmitterAddress, + ObservationTimeout: spec.ObservationTimeout, + BlockchainTimeout: spec.BlockchainTimeout, + ContractConfigTrackerSubscribeInterval: spec.ContractConfigTrackerSubscribeInterval, + ContractConfigTrackerPollInterval: spec.ContractConfigTrackerPollInterval, + ContractConfigConfirmations: spec.ContractConfigConfirmations, + CreatedAt: spec.CreatedAt, + UpdatedAt: spec.UpdatedAt, + EVMChainID: spec.EVMChainID, + DatabaseTimeout: spec.DatabaseTimeout, + ObservationGracePeriod: spec.ObservationGracePeriod, + ContractTransmitterTransmitTimeout: spec.ContractTransmitterTransmitTimeout, + CollectTelemetry: spec.CaptureEATelemetry, } } diff --git a/core/web/presenters/job_test.go b/core/web/presenters/job_test.go index bb79c8e953c..7e997c899c5 100644 --- a/core/web/presenters/job_test.go +++ b/core/web/presenters/job_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" clnull "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" diff --git a/core/web/presenters/solana_chain.go b/core/web/presenters/solana_chain.go index ba6cace7c3d..f04d2b65d55 100644 --- a/core/web/presenters/solana_chain.go +++ b/core/web/presenters/solana_chain.go @@ -1,7 +1,7 @@ package presenters import ( - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" ) // SolanaChainResource is an Solana chain JSONAPI resource. diff --git a/core/web/presenters/starknet_chain.go b/core/web/presenters/starknet_chain.go index 2a9e49e74c5..ec1cd453a55 100644 --- a/core/web/presenters/starknet_chain.go +++ b/core/web/presenters/starknet_chain.go @@ -1,7 +1,7 @@ package presenters import ( - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" ) // StarkNetChainResource is an StarkNet chain JSONAPI resource. diff --git a/core/web/resolver/api_token_test.go b/core/web/resolver/api_token_test.go index b5ed52be3c5..fae0204caf5 100644 --- a/core/web/resolver/api_token_test.go +++ b/core/web/resolver/api_token_test.go @@ -39,6 +39,11 @@ func TestResolver_CreateAPIToken(t *testing.T) { "password": defaultPassword, }, } + variablesIncorrect := map[string]interface{}{ + "input": map[string]interface{}{ + "password": "wrong-password", + }, + } gError := errors.New("error") testCases := []GQLTestCase{ @@ -56,12 +61,13 @@ func TestResolver_CreateAPIToken(t *testing.T) { session.User.HashedPassword = pwd - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, nil) - f.Mocks.sessionsORM.On("CreateAndSetAuthToken", session.User).Return(&auth.Token{ + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, nil) + f.Mocks.authProvider.On("TestPassword", session.User.Email, defaultPassword).Return(nil) + f.Mocks.authProvider.On("CreateAndSetAuthToken", session.User).Return(&auth.Token{ Secret: "new-secret", AccessKey: "new-access-key", }, nil) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, variables: variables, @@ -83,13 +89,12 @@ func TestResolver_CreateAPIToken(t *testing.T) { require.True(t, ok) require.NotNil(t, session) - session.User.HashedPassword = "wrong-password" - - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, nil) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, nil) + f.Mocks.authProvider.On("TestPassword", session.User.Email, "wrong-password").Return(gError) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, - variables: variables, + variables: variablesIncorrect, result: ` { "createAPIToken": { @@ -114,8 +119,8 @@ func TestResolver_CreateAPIToken(t *testing.T) { session.User.HashedPassword = pwd - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, gError) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, gError) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, variables: variables, @@ -142,9 +147,10 @@ func TestResolver_CreateAPIToken(t *testing.T) { session.User.HashedPassword = pwd - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, nil) - f.Mocks.sessionsORM.On("CreateAndSetAuthToken", session.User).Return(nil, gError) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, nil) + f.Mocks.authProvider.On("TestPassword", session.User.Email, defaultPassword).Return(nil) + f.Mocks.authProvider.On("CreateAndSetAuthToken", session.User).Return(nil, gError) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, variables: variables, @@ -189,6 +195,11 @@ func TestResolver_DeleteAPIToken(t *testing.T) { "password": defaultPassword, }, } + variablesIncorrect := map[string]interface{}{ + "input": map[string]interface{}{ + "password": "wrong-password", + }, + } gError := errors.New("error") testCases := []GQLTestCase{ @@ -208,9 +219,10 @@ func TestResolver_DeleteAPIToken(t *testing.T) { err = session.User.TokenKey.UnmarshalText([]byte("new-access-key")) require.NoError(t, err) - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, nil) - f.Mocks.sessionsORM.On("DeleteAuthToken", session.User).Return(nil) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, nil) + f.Mocks.authProvider.On("TestPassword", session.User.Email, defaultPassword).Return(nil) + f.Mocks.authProvider.On("DeleteAuthToken", session.User).Return(nil) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, variables: variables, @@ -231,13 +243,12 @@ func TestResolver_DeleteAPIToken(t *testing.T) { require.True(t, ok) require.NotNil(t, session) - session.User.HashedPassword = "wrong-password" - - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, nil) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, nil) + f.Mocks.authProvider.On("TestPassword", session.User.Email, "wrong-password").Return(gError) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, - variables: variables, + variables: variablesIncorrect, result: ` { "deleteAPIToken": { @@ -262,8 +273,8 @@ func TestResolver_DeleteAPIToken(t *testing.T) { session.User.HashedPassword = pwd - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, gError) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, gError) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, variables: variables, @@ -290,9 +301,10 @@ func TestResolver_DeleteAPIToken(t *testing.T) { session.User.HashedPassword = pwd - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, nil) - f.Mocks.sessionsORM.On("DeleteAuthToken", session.User).Return(gError) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, nil) + f.Mocks.authProvider.On("TestPassword", session.User.Email, defaultPassword).Return(nil) + f.Mocks.authProvider.On("DeleteAuthToken", session.User).Return(gError) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, variables: variables, diff --git a/core/web/resolver/bridge_test.go b/core/web/resolver/bridge_test.go index b12186e1121..71ef4a2b340 100644 --- a/core/web/resolver/bridge_test.go +++ b/core/web/resolver/bridge_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/store/models" ) diff --git a/core/web/resolver/chain.go b/core/web/resolver/chain.go index 53f1016d72b..32e9a8caac3 100644 --- a/core/web/resolver/chain.go +++ b/core/web/resolver/chain.go @@ -3,7 +3,7 @@ package resolver import ( "github.com/graph-gophers/graphql-go" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" ) // ChainResolver resolves the Chain type. diff --git a/core/web/resolver/eth_key.go b/core/web/resolver/eth_key.go index 868d53565dd..a9c060ef0c1 100644 --- a/core/web/resolver/eth_key.go +++ b/core/web/resolver/eth_key.go @@ -6,7 +6,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/graph-gophers/graphql-go" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/web/loader" ) @@ -14,7 +14,7 @@ import ( type ETHKey struct { state ethkey.State addr ethkey.EIP55Address - chain evm.Chain + chain legacyevm.Chain } type ETHKeyResolver struct { diff --git a/core/web/resolver/eth_key_test.go b/core/web/resolver/eth_key_test.go index 26e061b164f..ea106a4b30c 100644 --- a/core/web/resolver/eth_key_test.go +++ b/core/web/resolver/eth_key_test.go @@ -9,12 +9,13 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/mock" - "github.com/smartcontractkit/chainlink/v2/core/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" mocks2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" @@ -89,18 +90,18 @@ func TestResolver_ETHKeys(t *testing.T) { linkAddr := common.HexToAddress("0x5431F5F973781809D18643b87B44921b11355d81") cfg := configtest.NewGeneralConfig(t, nil) - m := map[string]evm.Chain{states[0].EVMChainID.String(): f.Mocks.chain} - legacyEVMChains := evm.NewLegacyChains(m, cfg.EVMConfigs()) + m := map[string]legacyevm.Chain{states[0].EVMChainID.String(): f.Mocks.chain} + legacyEVMChains := legacyevm.NewLegacyChains(m, cfg.EVMConfigs()) 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("GetAll").Return(keys, nil) - f.Mocks.ethClient.On("LINKBalance", mock.Anything, address, linkAddr).Return(assets.NewLinkFromJuels(12), nil) + f.Mocks.ethClient.On("LINKBalance", mock.Anything, address, linkAddr).Return(commonassets.NewLinkFromJuels(12), nil) f.Mocks.chain.On("Client").Return(f.Mocks.ethClient) f.Mocks.balM.On("GetEthBalance", address).Return(assets.NewEth(1)) f.Mocks.chain.On("BalanceMonitor").Return(f.Mocks.balM) f.Mocks.chain.On("Config").Return(f.Mocks.scfg) - f.Mocks.relayerChainInterops.On("LegacyEVMChains").Return(legacyEVMChains) + f.Mocks.relayerChainInterops.EVMChains = legacyEVMChains f.Mocks.evmORM.PutChains(toml.EVMConfig{ChainID: &chainID}) f.Mocks.keystore.On("Eth").Return(f.Mocks.ethKs) f.App.On("GetKeyStore").Return(f.Mocks.keystore) @@ -149,7 +150,7 @@ func TestResolver_ETHKeys(t *testing.T) { 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("GetAll").Return(keys, nil) - f.Mocks.relayerChainInterops.On("LegacyEVMChains").Return(f.Mocks.legacyEVMChains) + f.Mocks.relayerChainInterops.EVMChains = f.Mocks.legacyEVMChains f.Mocks.evmORM.PutChains(toml.EVMConfig{ChainID: &chainID}) f.Mocks.keystore.On("Eth").Return(f.Mocks.ethKs) f.App.On("GetKeyStore").Return(f.Mocks.keystore) @@ -268,7 +269,7 @@ func TestResolver_ETHKeys(t *testing.T) { f.Mocks.ethKs.On("GetAll").Return(keys, nil) f.Mocks.keystore.On("Eth").Return(f.Mocks.ethKs) f.Mocks.legacyEVMChains.On("Get", states[0].EVMChainID.String()).Return(f.Mocks.chain, gError) - f.Mocks.relayerChainInterops.On("LegacyEVMChains").Return(f.Mocks.legacyEVMChains) + f.Mocks.relayerChainInterops.EVMChains = f.Mocks.legacyEVMChains f.App.On("GetRelayers").Return(f.Mocks.relayerChainInterops) f.App.On("GetKeyStore").Return(f.Mocks.keystore) }, @@ -300,9 +301,9 @@ func TestResolver_ETHKeys(t *testing.T) { f.Mocks.ethKs.On("Get", keys[0].Address.Hex()).Return(keys[0], nil) f.Mocks.ethKs.On("GetAll").Return(keys, nil) f.Mocks.keystore.On("Eth").Return(f.Mocks.ethKs) - f.Mocks.ethClient.On("LINKBalance", mock.Anything, address, linkAddr).Return(assets.NewLinkFromJuels(12), gError) + f.Mocks.ethClient.On("LINKBalance", mock.Anything, address, linkAddr).Return(commonassets.NewLinkFromJuels(12), gError) f.Mocks.legacyEVMChains.On("Get", states[0].EVMChainID.String()).Return(f.Mocks.chain, nil) - f.Mocks.relayerChainInterops.On("LegacyEVMChains").Return(f.Mocks.legacyEVMChains) + f.Mocks.relayerChainInterops.EVMChains = f.Mocks.legacyEVMChains f.Mocks.chain.On("Client").Return(f.Mocks.ethClient) f.Mocks.balM.On("GetEthBalance", address).Return(assets.NewEth(1)) f.Mocks.chain.On("BalanceMonitor").Return(f.Mocks.balM) @@ -353,12 +354,12 @@ func TestResolver_ETHKeys(t *testing.T) { 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("GetAll").Return(keys, nil) - f.Mocks.ethClient.On("LINKBalance", mock.Anything, address, linkAddr).Return(assets.NewLinkFromJuels(12), nil) + f.Mocks.ethClient.On("LINKBalance", mock.Anything, address, linkAddr).Return(commonassets.NewLinkFromJuels(12), nil) f.Mocks.chain.On("Client").Return(f.Mocks.ethClient) f.Mocks.chain.On("BalanceMonitor").Return(nil) f.Mocks.chain.On("Config").Return(f.Mocks.scfg) f.Mocks.legacyEVMChains.On("Get", states[0].EVMChainID.String()).Return(f.Mocks.chain, nil) - f.Mocks.relayerChainInterops.On("LegacyEVMChains").Return(f.Mocks.legacyEVMChains) + f.Mocks.relayerChainInterops.EVMChains = f.Mocks.legacyEVMChains f.Mocks.evmORM.PutChains(toml.EVMConfig{ChainID: &chainID}) f.Mocks.keystore.On("Eth").Return(f.Mocks.ethKs) f.App.On("GetKeyStore").Return(f.Mocks.keystore) diff --git a/core/web/resolver/eth_transaction.go b/core/web/resolver/eth_transaction.go index 31a96727a9c..1292b6bc104 100644 --- a/core/web/resolver/eth_transaction.go +++ b/core/web/resolver/eth_transaction.go @@ -6,7 +6,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/graph-gophers/graphql-go" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/utils/stringutils" "github.com/smartcontractkit/chainlink/v2/core/web/loader" diff --git a/core/web/resolver/eth_transaction_test.go b/core/web/resolver/eth_transaction_test.go index 8a1685e4f72..a719c838e81 100644 --- a/core/web/resolver/eth_transaction_test.go +++ b/core/web/resolver/eth_transaction_test.go @@ -10,7 +10,7 @@ import ( gqlerrors "github.com/graph-gophers/graphql-go/errors" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" diff --git a/core/web/resolver/features_test.go b/core/web/resolver/features_test.go index 1d3f5b8ddaf..f14f71abc90 100644 --- a/core/web/resolver/features_test.go +++ b/core/web/resolver/features_test.go @@ -3,7 +3,7 @@ package resolver import ( "testing" - configtest2 "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" ) @@ -24,7 +24,7 @@ func Test_ToFeatures(t *testing.T) { name: "success", authenticated: true, before: func(f *gqlTestFramework) { - f.App.On("GetConfig").Return(configtest2.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + f.App.On("GetConfig").Return(configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { t, f := true, false c.Feature.UICSAKeys = &f c.Feature.FeedsManager = &t diff --git a/core/web/resolver/helpers.go b/core/web/resolver/helpers.go index 5c855a1c165..36080a8b546 100644 --- a/core/web/resolver/helpers.go +++ b/core/web/resolver/helpers.go @@ -8,7 +8,7 @@ import ( "github.com/graph-gophers/graphql-go" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/utils/stringutils" ) diff --git a/core/web/resolver/mutation.go b/core/web/resolver/mutation.go index 68cbb0b7896..990a6c08058 100644 --- a/core/web/resolver/mutation.go +++ b/core/web/resolver/mutation.go @@ -13,7 +13,7 @@ import ( "go.uber.org/zap/zapcore" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" @@ -882,7 +882,7 @@ func (r *Resolver) UpdateUserPassword(ctx context.Context, args struct { return nil, errors.New("couldn't retrieve user session") } - dbUser, err := r.App.SessionORM().FindUser(session.User.Email) + dbUser, err := r.App.AuthenticationProvider().FindUser(session.User.Email) if err != nil { return nil, err } @@ -895,11 +895,11 @@ func (r *Resolver) UpdateUserPassword(ctx context.Context, args struct { }), nil } - if err = r.App.SessionORM().ClearNonCurrentSessions(session.SessionID); err != nil { + if err = r.App.AuthenticationProvider().ClearNonCurrentSessions(session.SessionID); err != nil { return nil, clearSessionsError{} } - err = r.App.SessionORM().SetPassword(&dbUser, args.Input.NewPassword) + err = r.App.AuthenticationProvider().SetPassword(&dbUser, args.Input.NewPassword) if err != nil { return nil, failedPasswordUpdateError{} } @@ -937,12 +937,13 @@ func (r *Resolver) CreateAPIToken(ctx context.Context, args struct { if !ok { return nil, errors.New("Failed to obtain current user from context") } - dbUser, err := r.App.SessionORM().FindUser(session.User.Email) + dbUser, err := r.App.AuthenticationProvider().FindUser(session.User.Email) if err != nil { return nil, err } - if !utils.CheckPasswordHash(args.Input.Password, dbUser.HashedPassword) { + err = r.App.AuthenticationProvider().TestPassword(dbUser.Email, args.Input.Password) + if err != nil { r.App.GetAuditLogger().Audit(audit.APITokenCreateAttemptPasswordMismatch, map[string]interface{}{"user": dbUser.Email}) return NewCreateAPITokenPayload(nil, map[string]string{ @@ -950,7 +951,7 @@ func (r *Resolver) CreateAPIToken(ctx context.Context, args struct { }), nil } - newToken, err := r.App.SessionORM().CreateAndSetAuthToken(&dbUser) + newToken, err := r.App.AuthenticationProvider().CreateAndSetAuthToken(&dbUser) if err != nil { return nil, err } @@ -970,12 +971,13 @@ func (r *Resolver) DeleteAPIToken(ctx context.Context, args struct { if !ok { return nil, errors.New("Failed to obtain current user from context") } - dbUser, err := r.App.SessionORM().FindUser(session.User.Email) + dbUser, err := r.App.AuthenticationProvider().FindUser(session.User.Email) if err != nil { return nil, err } - if !utils.CheckPasswordHash(args.Input.Password, dbUser.HashedPassword) { + err = r.App.AuthenticationProvider().TestPassword(dbUser.Email, args.Input.Password) + if err != nil { r.App.GetAuditLogger().Audit(audit.APITokenDeleteAttemptPasswordMismatch, map[string]interface{}{"user": dbUser.Email}) return NewDeleteAPITokenPayload(nil, map[string]string{ @@ -983,7 +985,7 @@ func (r *Resolver) DeleteAPIToken(ctx context.Context, args struct { }), nil } - err = r.App.SessionORM().DeleteAuthToken(&dbUser) + err = r.App.AuthenticationProvider().DeleteAuthToken(&dbUser) if err != nil { return nil, err } diff --git a/core/web/resolver/node.go b/core/web/resolver/node.go index 39a85e83b13..8e01b7056be 100644 --- a/core/web/resolver/node.go +++ b/core/web/resolver/node.go @@ -7,7 +7,7 @@ import ( "github.com/graph-gophers/graphql-go" "github.com/pelletier/go-toml/v2" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" evmtoml "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/web/loader" diff --git a/core/web/resolver/node_test.go b/core/web/resolver/node_test.go index e949a67a85b..24a31b986f1 100644 --- a/core/web/resolver/node_test.go +++ b/core/web/resolver/node_test.go @@ -5,10 +5,10 @@ import ( gqlerrors "github.com/graph-gophers/graphql-go/errors" "github.com/pkg/errors" - "github.com/stretchr/testify/mock" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -43,17 +43,16 @@ func TestResolver_Nodes(t *testing.T) { name: "success", authenticated: true, before: func(f *gqlTestFramework) { - f.App.On("GetRelayers").Return(f.Mocks.relayerChainInterops) - f.Mocks.relayerChainInterops.On("NodeStatuses", mock.Anything, PageDefaultOffset, PageDefaultLimit).Return([]types.NodeStatus{ + f.App.On("GetRelayers").Return(chainlink.RelayerChainInteroperators(f.Mocks.relayerChainInterops)) + f.Mocks.relayerChainInterops.Nodes = []types.NodeStatus{ { Name: "node-name", ChainID: chainID.String(), Config: `Name = 'node-name'`, }, - }, 1, nil) + } f.App.On("EVMORM").Return(f.Mocks.evmORM) f.Mocks.evmORM.PutChains(toml.EVMConfig{ChainID: &chainID}) - }, query: query, result: ` @@ -76,7 +75,7 @@ func TestResolver_Nodes(t *testing.T) { name: "generic error", authenticated: true, before: func(f *gqlTestFramework) { - f.Mocks.relayerChainInterops.On("NodeStatuses", mock.Anything, PageDefaultOffset, PageDefaultLimit).Return([]types.NodeStatus{}, 0, gError) + f.Mocks.relayerChainInterops.NodesErr = gError f.App.On("GetRelayers").Return(f.Mocks.relayerChainInterops) }, query: query, diff --git a/core/web/resolver/query.go b/core/web/resolver/query.go index e9fd18cf19a..da15b7f7c26 100644 --- a/core/web/resolver/query.go +++ b/core/web/resolver/query.go @@ -10,7 +10,7 @@ import ( "github.com/graph-gophers/graphql-go" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" diff --git a/core/web/resolver/resolver_test.go b/core/web/resolver/resolver_test.go index d0523d6b968..d5906d99aff 100644 --- a/core/web/resolver/resolver_test.go +++ b/core/web/resolver/resolver_test.go @@ -15,6 +15,7 @@ import ( evmConfigMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/mocks" evmORMMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/mocks" evmtxmgrmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" + legacyEvmORMMocks "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm/mocks" coremocks "github.com/smartcontractkit/chainlink/v2/core/internal/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" @@ -27,7 +28,7 @@ import ( pipelineMocks "github.com/smartcontractkit/chainlink/v2/core/services/pipeline/mocks" webhookmocks "github.com/smartcontractkit/chainlink/v2/core/services/webhook/mocks" clsessions "github.com/smartcontractkit/chainlink/v2/core/sessions" - sessionsMocks "github.com/smartcontractkit/chainlink/v2/core/sessions/mocks" + authProviderMocks "github.com/smartcontractkit/chainlink/v2/core/sessions/mocks" "github.com/smartcontractkit/chainlink/v2/core/web/auth" "github.com/smartcontractkit/chainlink/v2/core/web/loader" "github.com/smartcontractkit/chainlink/v2/core/web/schema" @@ -37,7 +38,7 @@ type mocks struct { bridgeORM *bridgeORMMocks.ORM evmORM *evmtest.TestConfigs jobORM *jobORMMocks.ORM - sessionsORM *sessionsMocks.ORM + authProvider *authProviderMocks.AuthenticationProvider pipelineORM *pipelineMocks.ORM feedsSvc *feedsMocks.Service cfg *chainlinkMocks.GeneralConfig @@ -50,9 +51,9 @@ type mocks struct { p2p *keystoreMocks.P2P vrf *keystoreMocks.VRF solana *keystoreMocks.Solana - chain *evmORMMocks.Chain - legacyEVMChains *evmORMMocks.LegacyChainContainer - relayerChainInterops *chainlinkMocks.RelayerChainInteroperators + chain *legacyEvmORMMocks.Chain + legacyEVMChains *legacyEvmORMMocks.LegacyChainContainer + relayerChainInterops *chainlinkMocks.FakeRelayerChainInteroperators ethClient *evmClientMocks.Client eIMgr *webhookmocks.ExternalInitiatorManager balM *evmORMMocks.BalanceMonitor @@ -97,7 +98,7 @@ func setupFramework(t *testing.T) *gqlTestFramework { evmORM: evmtest.NewTestConfigs(), jobORM: jobORMMocks.NewORM(t), feedsSvc: feedsMocks.NewService(t), - sessionsORM: sessionsMocks.NewORM(t), + authProvider: authProviderMocks.NewAuthenticationProvider(t), pipelineORM: pipelineMocks.NewORM(t), cfg: chainlinkMocks.NewGeneralConfig(t), scfg: evmConfigMocks.NewChainScopedConfig(t), @@ -109,9 +110,9 @@ func setupFramework(t *testing.T) *gqlTestFramework { p2p: keystoreMocks.NewP2P(t), vrf: keystoreMocks.NewVRF(t), solana: keystoreMocks.NewSolana(t), - chain: evmORMMocks.NewChain(t), - legacyEVMChains: evmORMMocks.NewLegacyChainContainer(t), - relayerChainInterops: chainlinkMocks.NewRelayerChainInteroperators(t), + chain: legacyEvmORMMocks.NewChain(t), + legacyEVMChains: legacyEvmORMMocks.NewLegacyChainContainer(t), + relayerChainInterops: &chainlinkMocks.FakeRelayerChainInteroperators{}, ethClient: evmClientMocks.NewClient(t), eIMgr: webhookmocks.NewExternalInitiatorManager(t), balM: evmORMMocks.NewBalanceMonitor(t), diff --git a/core/web/resolver/spec.go b/core/web/resolver/spec.go index 48040d118a7..c9ee5199229 100644 --- a/core/web/resolver/spec.go +++ b/core/web/resolver/spec.go @@ -164,11 +164,6 @@ func (r *DirectRequestSpecResolver) MinIncomingConfirmations() int32 { return 0 } -// EVMChainID resolves the spec's evm chain id. -func (r *DirectRequestSpecResolver) MinIncomingConfirmationsEnv() bool { - return r.spec.MinIncomingConfirmationsEnv -} - // MinContractPaymentLinkJuels resolves the spec's min contract payment link. func (r *DirectRequestSpecResolver) MinContractPaymentLinkJuels() string { return r.spec.MinContractPayment.String() @@ -328,12 +323,6 @@ func (r *OCRSpecResolver) BlockchainTimeout() *string { return &timeout } -// BlockchainTimeoutEnv resolves whether the spec's blockchain timeout comes -// from an env var. -func (r *OCRSpecResolver) BlockchainTimeoutEnv() bool { - return r.spec.BlockchainTimeoutEnv -} - // ContractAddress resolves the spec's contract address. func (r *OCRSpecResolver) ContractAddress() string { return r.spec.ContractAddress.String() @@ -350,12 +339,6 @@ func (r *OCRSpecResolver) ContractConfigConfirmations() *int32 { return &confirmations } -// ContractConfigConfirmationsEnv resolves whether spec's confirmations -// config comes from an env var. -func (r *OCRSpecResolver) ContractConfigConfirmationsEnv() bool { - return r.spec.ContractConfigConfirmationsEnv -} - // ContractConfigTrackerPollInterval resolves the spec's contract tracker poll // interval config. func (r *OCRSpecResolver) ContractConfigTrackerPollInterval() *string { @@ -368,12 +351,6 @@ func (r *OCRSpecResolver) ContractConfigTrackerPollInterval() *string { return &interval } -// ContractConfigTrackerPollIntervalEnv resolves the whether spec's tracker poll -// config comes from an env var. -func (r *OCRSpecResolver) ContractConfigTrackerPollIntervalEnv() bool { - return r.spec.ContractConfigTrackerPollIntervalEnv -} - // ContractConfigTrackerSubscribeInterval resolves the spec's tracker subscribe // interval config. func (r *OCRSpecResolver) ContractConfigTrackerSubscribeInterval() *string { @@ -386,12 +363,6 @@ func (r *OCRSpecResolver) ContractConfigTrackerSubscribeInterval() *string { return &interval } -// ContractConfigTrackerSubscribeIntervalEnv resolves whether spec's tracker -// subscribe interval config comes from an env var. -func (r *OCRSpecResolver) ContractConfigTrackerSubscribeIntervalEnv() bool { - return r.spec.ContractConfigTrackerSubscribeIntervalEnv -} - // CreatedAt resolves the spec's created at timestamp. func (r *OCRSpecResolver) CreatedAt() graphql.Time { return graphql.Time{Time: r.spec.CreatedAt} @@ -413,34 +384,16 @@ func (r *OCRSpecResolver) DatabaseTimeout() string { return r.spec.DatabaseTimeout.Duration().String() } -// DatabaseTimeoutEnv resolves the whether spec's database timeout -// config comes from an env var. -func (r *OCRSpecResolver) DatabaseTimeoutEnv() bool { - return r.spec.DatabaseTimeoutEnv -} - // ObservationGracePeriod resolves the spec's observation grace period. func (r *OCRSpecResolver) ObservationGracePeriod() string { return r.spec.ObservationGracePeriod.Duration().String() } -// ObservationGracePeriodEnv resolves the whether spec's observation grace period -// config comes from an env var. -func (r *OCRSpecResolver) ObservationGracePeriodEnv() bool { - return r.spec.ObservationGracePeriodEnv -} - // ContractTransmitterTransmitTimeout resolves the spec's contract transmitter transmit timeout. func (r *OCRSpecResolver) ContractTransmitterTransmitTimeout() string { return r.spec.ContractTransmitterTransmitTimeout.Duration().String() } -// ContractTransmitterTransmitTimeoutEnv resolves the whether spec's -// contract transmitter transmit timeout config comes from an env var. -func (r *OCRSpecResolver) ContractTransmitterTransmitTimeoutEnv() bool { - return r.spec.ContractTransmitterTransmitTimeoutEnv -} - // IsBootstrapPeer resolves whether spec is a bootstrap peer. func (r *OCRSpecResolver) IsBootstrapPeer() bool { return r.spec.IsBootstrapPeer @@ -468,12 +421,6 @@ func (r *OCRSpecResolver) ObservationTimeout() *string { return &timeout } -// ObservationTimeoutEnv resolves whether spec's observation timeout comes -// from an env var. -func (r *OCRSpecResolver) ObservationTimeoutEnv() bool { - return r.spec.ObservationTimeoutEnv -} - // P2PBootstrapPeers resolves the spec's p2p bootstrap peers func (r *OCRSpecResolver) P2PBootstrapPeers() *[]string { if len(r.spec.P2PBootstrapPeers) == 0 { @@ -631,11 +578,6 @@ func (r *VRFSpecResolver) MinIncomingConfirmations() int32 { return int32(r.spec.MinIncomingConfirmations) } -// MinIncomingConfirmations resolves the spec's min incoming confirmations. -func (r *VRFSpecResolver) MinIncomingConfirmationsEnv() bool { - return r.spec.ConfirmationsEnv -} - // CoordinatorAddress resolves the spec's coordinator address. func (r *VRFSpecResolver) CoordinatorAddress() string { return r.spec.CoordinatorAddress.String() diff --git a/core/web/resolver/spec_test.go b/core/web/resolver/spec_test.go index 04bfffbe05e..4de66f1dcb1 100644 --- a/core/web/resolver/spec_test.go +++ b/core/web/resolver/spec_test.go @@ -9,8 +9,10 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-relay/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/assets" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" clnull "github.com/smartcontractkit/chainlink/v2/core/null" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" @@ -91,13 +93,12 @@ func TestResolver_DirectRequestSpec(t *testing.T) { f.Mocks.jobORM.On("FindJobWithoutSpecErrors", id).Return(job.Job{ Type: job.DirectRequest, DirectRequestSpec: &job.DirectRequestSpec{ - ContractAddress: contractAddress, - CreatedAt: f.Timestamp(), - EVMChainID: utils.NewBigI(42), - MinIncomingConfirmations: clnull.NewUint32(1, true), - MinIncomingConfirmationsEnv: true, - MinContractPayment: assets.NewLinkFromJuels(1000), - Requesters: models.AddressCollection{requesterAddress}, + ContractAddress: contractAddress, + CreatedAt: f.Timestamp(), + EVMChainID: utils.NewBigI(42), + MinIncomingConfirmations: clnull.NewUint32(1, true), + MinContractPayment: commonassets.NewLinkFromJuels(1000), + Requesters: models.AddressCollection{requesterAddress}, }, }, nil) }, @@ -112,7 +113,6 @@ func TestResolver_DirectRequestSpec(t *testing.T) { createdAt evmChainID minIncomingConfirmations - minIncomingConfirmationsEnv minContractPaymentLinkJuels requesters } @@ -130,7 +130,6 @@ func TestResolver_DirectRequestSpec(t *testing.T) { "createdAt": "2021-01-01T00:00:00Z", "evmChainID": "42", "minIncomingConfirmations": 1, - "minIncomingConfirmationsEnv": true, "minContractPaymentLinkJuels": "1000", "requesters": ["0x3cCad4715152693fE3BC4460591e3D3Fbd071b42"] } @@ -165,7 +164,7 @@ func TestResolver_FluxMonitorSpec(t *testing.T) { DrumbeatEnabled: false, IdleTimerDisabled: false, IdleTimerPeriod: time.Duration(1 * time.Hour), - MinPayment: assets.NewLinkFromJuels(1000), + MinPayment: commonassets.NewLinkFromJuels(1000), PollTimerDisabled: false, PollTimerPeriod: time.Duration(1 * time.Minute), }, @@ -234,7 +233,7 @@ func TestResolver_FluxMonitorSpec(t *testing.T) { DrumbeatSchedule: "CRON_TZ=UTC 0 0 1 1 *", IdleTimerDisabled: true, IdleTimerPeriod: time.Duration(1 * time.Hour), - MinPayment: assets.NewLinkFromJuels(1000), + MinPayment: commonassets.NewLinkFromJuels(1000), PollTimerDisabled: true, PollTimerPeriod: time.Duration(1 * time.Minute), }, @@ -373,30 +372,22 @@ func TestResolver_OCRSpec(t *testing.T) { f.Mocks.jobORM.On("FindJobWithoutSpecErrors", id).Return(job.Job{ Type: job.OffchainReporting, OCROracleSpec: &job.OCROracleSpec{ - BlockchainTimeout: models.Interval(1 * time.Minute), - BlockchainTimeoutEnv: false, - ContractAddress: contractAddress, - ContractConfigConfirmations: 1, - ContractConfigConfirmationsEnv: true, - ContractConfigTrackerPollInterval: models.Interval(1 * time.Minute), - ContractConfigTrackerPollIntervalEnv: false, - ContractConfigTrackerSubscribeInterval: models.Interval(2 * time.Minute), - ContractConfigTrackerSubscribeIntervalEnv: true, - DatabaseTimeout: models.NewInterval(3 * time.Second), - DatabaseTimeoutEnv: true, - ObservationGracePeriod: models.NewInterval(4 * time.Second), - ObservationGracePeriodEnv: true, - ContractTransmitterTransmitTimeout: models.NewInterval(555 * time.Millisecond), - ContractTransmitterTransmitTimeoutEnv: true, - CreatedAt: f.Timestamp(), - EVMChainID: utils.NewBigI(42), - IsBootstrapPeer: false, - EncryptedOCRKeyBundleID: &keyBundleID, - ObservationTimeout: models.Interval(2 * time.Minute), - ObservationTimeoutEnv: false, - P2PBootstrapPeers: pq.StringArray{"/dns4/test.com/tcp/2001/p2pkey"}, - P2PV2Bootstrappers: pq.StringArray{"12D3KooWL3XJ9EMCyZvmmGXL2LMiVBtrVa2BuESsJiXkSj7333Jw@localhost:5001"}, - TransmitterAddress: &transmitterAddress, + BlockchainTimeout: models.Interval(1 * time.Minute), + ContractAddress: contractAddress, + ContractConfigConfirmations: 1, + ContractConfigTrackerPollInterval: models.Interval(1 * time.Minute), + ContractConfigTrackerSubscribeInterval: models.Interval(2 * time.Minute), + DatabaseTimeout: models.NewInterval(3 * time.Second), + ObservationGracePeriod: models.NewInterval(4 * time.Second), + ContractTransmitterTransmitTimeout: models.NewInterval(555 * time.Millisecond), + CreatedAt: f.Timestamp(), + EVMChainID: utils.NewBigI(42), + IsBootstrapPeer: false, + EncryptedOCRKeyBundleID: &keyBundleID, + ObservationTimeout: models.Interval(2 * time.Minute), + P2PBootstrapPeers: pq.StringArray{"/dns4/test.com/tcp/2001/p2pkey"}, + P2PV2Bootstrappers: pq.StringArray{"12D3KooWL3XJ9EMCyZvmmGXL2LMiVBtrVa2BuESsJiXkSj7333Jw@localhost:5001"}, + TransmitterAddress: &transmitterAddress, }, }, nil) }, @@ -408,26 +399,18 @@ func TestResolver_OCRSpec(t *testing.T) { __typename ... on OCRSpec { blockchainTimeout - blockchainTimeoutEnv contractAddress contractConfigConfirmations - contractConfigConfirmationsEnv contractConfigTrackerPollInterval - contractConfigTrackerPollIntervalEnv contractConfigTrackerSubscribeInterval - contractConfigTrackerSubscribeIntervalEnv databaseTimeout - databaseTimeoutEnv observationGracePeriod - observationGracePeriodEnv contractTransmitterTransmitTimeout - contractTransmitterTransmitTimeoutEnv createdAt evmChainID isBootstrapPeer keyBundleID observationTimeout - observationTimeoutEnv p2pBootstrapPeers p2pv2Bootstrappers transmitterAddress @@ -443,26 +426,18 @@ func TestResolver_OCRSpec(t *testing.T) { "spec": { "__typename": "OCRSpec", "blockchainTimeout": "1m0s", - "blockchainTimeoutEnv": false, "contractAddress": "0x613a38AC1659769640aaE063C651F48E0250454C", "contractConfigConfirmations": 1, - "contractConfigConfirmationsEnv": true, "contractConfigTrackerPollInterval": "1m0s", - "contractConfigTrackerPollIntervalEnv": false, "contractConfigTrackerSubscribeInterval": "2m0s", - "contractConfigTrackerSubscribeIntervalEnv": true, "databaseTimeout": "3s", - "databaseTimeoutEnv": true, "observationGracePeriod": "4s", - "observationGracePeriodEnv": true, "contractTransmitterTransmitTimeout": "555ms", - "contractTransmitterTransmitTimeoutEnv": true, "createdAt": "2021-01-01T00:00:00Z", "evmChainID": "42", "isBootstrapPeer": false, "keyBundleID": "f5bf259689b26f1374efb3c9a9868796953a0f814bb2d39b968d0e61b58620a5", "observationTimeout": "2m0s", - "observationTimeoutEnv": false, "p2pBootstrapPeers": ["/dns4/test.com/tcp/2001/p2pkey"], "p2pv2Bootstrappers": ["12D3KooWL3XJ9EMCyZvmmGXL2LMiVBtrVa2BuESsJiXkSj7333Jw@localhost:5001"], "transmitterAddress": "0x3cCad4715152693fE3BC4460591e3D3Fbd071b42" diff --git a/core/web/resolver/testdata/config-empty-effective.toml b/core/web/resolver/testdata/config-empty-effective.toml index 48d432138a8..2531e7c281d 100644 --- a/core/web/resolver/testdata/config-empty-effective.toml +++ b/core/web/resolver/testdata/config-empty-effective.toml @@ -61,6 +61,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -73,6 +74,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -117,7 +137,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -212,3 +232,11 @@ Enabled = false CollectorTarget = '' NodeID = '' SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index 4b53396b94c..e98f8602a0c 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -67,6 +67,7 @@ MaxAgeDays = 17 MaxBackups = 9 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = '*' BridgeResponseURL = 'https://bridge.response' BridgeCacheTTL = '10s' @@ -79,6 +80,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '192.158.1.37' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = 'test-rpid' RPOrigin = 'test-rp-origin' @@ -123,7 +143,7 @@ ContractTransmitterTransmitTimeout = '1m0s' DatabaseTimeout = '8s' KeyBundleID = '7a5f66bbe6594259325bf2b4f5b1a9c900000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -218,11 +238,19 @@ Enabled = false CollectorTarget = 'localhost:4317' NodeID = 'NodeID' SamplingRatio = 1.0 +Mode = 'tls' +TLSCertPath = '/path/to/cert.pem' [Tracing.Attributes] env = 'dev' test = 'load' +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1m40s' +MaxStaleAge = '1m41s' +LatestReportDeadline = '1m42s' + [[EVM]] ChainID = '1' Enabled = false @@ -257,7 +285,7 @@ ResendAfterThreshold = '1h0m0s' Enabled = true [EVM.GasEstimator] -Mode = 'L2Suggested' +Mode = 'SuggestedPrice' PriceDefault = '9.223372036854775807 ether' PriceMax = '281.474976710655 micro' PriceMin = '13 wei' @@ -311,6 +339,8 @@ LeaseDuration = '0s' ContractConfirmations = 11 ContractTransmitterTransmitTimeout = '1m0s' DatabaseTimeout = '1s' +DeltaCOverride = '1h0m0s' +DeltaCJitterOverride = '1s' ObservationGracePeriod = '1s' [EVM.OCR2] diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index 1dcbfe3a830..74d83035cd5 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -61,6 +61,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -73,6 +74,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -117,7 +137,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '20s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -212,6 +232,14 @@ Enabled = false CollectorTarget = '' NodeID = '' SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' [[EVM]] ChainID = '1' @@ -283,6 +311,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] @@ -368,6 +398,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] @@ -447,6 +479,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] diff --git a/core/web/resolver/user_test.go b/core/web/resolver/user_test.go index e3808eebcbb..bc64beeb459 100644 --- a/core/web/resolver/user_test.go +++ b/core/web/resolver/user_test.go @@ -53,10 +53,10 @@ func TestResolver_UpdateUserPassword(t *testing.T) { session.User.HashedPassword = pwd - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, nil) - f.Mocks.sessionsORM.On("SetPassword", session.User, "new").Return(nil) - f.Mocks.sessionsORM.On("ClearNonCurrentSessions", session.SessionID).Return(nil) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, nil) + f.Mocks.authProvider.On("SetPassword", session.User, "new").Return(nil) + f.Mocks.authProvider.On("ClearNonCurrentSessions", session.SessionID).Return(nil) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, variables: variables, @@ -79,8 +79,8 @@ func TestResolver_UpdateUserPassword(t *testing.T) { session.User.HashedPassword = "random-string" - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, nil) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, nil) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, variables: variables, @@ -108,11 +108,11 @@ func TestResolver_UpdateUserPassword(t *testing.T) { session.User.HashedPassword = pwd - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, nil) - f.Mocks.sessionsORM.On("ClearNonCurrentSessions", session.SessionID).Return( + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, nil) + f.Mocks.authProvider.On("ClearNonCurrentSessions", session.SessionID).Return( clearSessionsError{}, ) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, variables: variables, @@ -139,10 +139,10 @@ func TestResolver_UpdateUserPassword(t *testing.T) { session.User.HashedPassword = pwd - f.Mocks.sessionsORM.On("FindUser", session.User.Email).Return(*session.User, nil) - f.Mocks.sessionsORM.On("ClearNonCurrentSessions", session.SessionID).Return(nil) - f.Mocks.sessionsORM.On("SetPassword", session.User, "new").Return(failedPasswordUpdateError{}) - f.App.On("SessionORM").Return(f.Mocks.sessionsORM) + f.Mocks.authProvider.On("FindUser", session.User.Email).Return(*session.User, nil) + f.Mocks.authProvider.On("ClearNonCurrentSessions", session.SessionID).Return(nil) + f.Mocks.authProvider.On("SetPassword", session.User, "new").Return(failedPasswordUpdateError{}) + f.App.On("AuthenticationProvider").Return(f.Mocks.authProvider) }, query: mutation, variables: variables, diff --git a/core/web/router.go b/core/web/router.go index a873f14b708..28bd4f2170c 100644 --- a/core/web/router.go +++ b/core/web/router.go @@ -90,7 +90,7 @@ func NewRouter(app chainlink.Application, prometheus *ginprom.Prometheus) (*gin. guiAssetRoutes(engine, config.Insecure().DisableRateLimiting(), app.GetLogger()) api.POST("/query", - auth.AuthenticateGQL(app.SessionORM(), app.GetLogger().Named("GQLHandler")), + auth.AuthenticateGQL(app.AuthenticationProvider(), app.GetLogger().Named("GQLHandler")), loader.Middleware(app), graphqlHandler(app), ) @@ -170,7 +170,7 @@ func secureMiddleware(tlsRedirect bool, tlsHost string, devWebServer bool) gin.H } func debugRoutes(app chainlink.Application, r *gin.RouterGroup) { - group := r.Group("/debug", auth.Authenticate(app.SessionORM(), auth.AuthenticateBySession)) + group := r.Group("/debug", auth.Authenticate(app.AuthenticationProvider(), auth.AuthenticateBySession)) group.GET("/vars", expvar.Handler()) } @@ -207,7 +207,7 @@ func sessionRoutes(app chainlink.Application, r *gin.RouterGroup) { )) sc := NewSessionsController(app) unauth.POST("/sessions", sc.Create) - auth := r.Group("/", auth.Authenticate(app.SessionORM(), auth.AuthenticateBySession)) + auth := r.Group("/", auth.Authenticate(app.AuthenticationProvider(), auth.AuthenticateBySession)) auth.DELETE("/sessions", sc.Destroy) } @@ -231,7 +231,7 @@ func v2Routes(app chainlink.Application, r *gin.RouterGroup) { psec := PipelineJobSpecErrorsController{app} unauthedv2.PATCH("/resume/:runID", prc.Resume) - authv2 := r.Group("/v2", auth.Authenticate(app.SessionORM(), + authv2 := r.Group("/v2", auth.Authenticate(app.AuthenticationProvider(), auth.AuthenticateByToken, auth.AuthenticateBySession, )) @@ -301,7 +301,7 @@ func v2Routes(app chainlink.Application, r *gin.RouterGroup) { // duplicated from above, with `evm` instead of `eth` // legacy ones remain for backwards compatibility - ethKeysGroup := authv2.Group("", auth.Authenticate(app.SessionORM(), + ethKeysGroup := authv2.Group("", auth.Authenticate(app.AuthenticationProvider(), auth.AuthenticateByToken, auth.AuthenticateBySession, )) @@ -427,7 +427,7 @@ func v2Routes(app chainlink.Application, r *gin.RouterGroup) { } ping := PingController{app} - userOrEI := r.Group("/v2", auth.Authenticate(app.SessionORM(), + userOrEI := r.Group("/v2", auth.Authenticate(app.AuthenticationProvider(), auth.AuthenticateExternalInitiator, auth.AuthenticateByToken, auth.AuthenticateBySession, diff --git a/core/web/schema/type/spec.graphql b/core/web/schema/type/spec.graphql index cdcbabf9ef0..98203a1870e 100644 --- a/core/web/schema/type/spec.graphql +++ b/core/web/schema/type/spec.graphql @@ -22,7 +22,6 @@ type DirectRequestSpec { createdAt: Time! evmChainID: String minIncomingConfirmations: Int! - minIncomingConfirmationsEnv: Boolean! minContractPaymentLinkJuels: String! requesters: [String!] } @@ -52,29 +51,21 @@ type KeeperSpec { type OCRSpec { blockchainTimeout: String - blockchainTimeoutEnv: Boolean! contractAddress: String! contractConfigConfirmations: Int - contractConfigConfirmationsEnv: Boolean! contractConfigTrackerPollInterval: String - contractConfigTrackerPollIntervalEnv: Boolean! contractConfigTrackerSubscribeInterval: String - contractConfigTrackerSubscribeIntervalEnv: Boolean! createdAt: Time! evmChainID: String isBootstrapPeer: Boolean! keyBundleID: String observationTimeout: String - observationTimeoutEnv: Boolean! p2pBootstrapPeers: [String!] p2pv2Bootstrappers: [String!] transmitterAddress: String databaseTimeout: String! - databaseTimeoutEnv: Boolean! observationGracePeriod: String! - observationGracePeriodEnv: Boolean! contractTransmitterTransmitTimeout: String! - contractTransmitterTransmitTimeoutEnv: Boolean! } type OCR2Spec { @@ -100,7 +91,6 @@ type VRFSpec { evmChainID: String fromAddresses: [String!] minIncomingConfirmations: Int! - minIncomingConfirmationsEnv: Boolean! pollPeriod: String! publicKey: String! requestedConfsDelay: Int! diff --git a/core/web/sessions_controller.go b/core/web/sessions_controller.go index 6f029456bd1..23ecfd3b798 100644 --- a/core/web/sessions_controller.go +++ b/core/web/sessions_controller.go @@ -39,7 +39,7 @@ func (sc *SessionsController) Create(c *gin.Context) { } // Does this user have 2FA enabled? - userWebAuthnTokens, err := sc.App.SessionORM().GetUserWebAuthn(sr.Email) + userWebAuthnTokens, err := sc.App.AuthenticationProvider().GetUserWebAuthn(sr.Email) if err != nil { sc.App.GetLogger().Errorf("Error loading user WebAuthn data: %s", err) jsonAPIError(c, http.StatusInternalServerError, errors.New("internal Server Error")) @@ -53,7 +53,7 @@ func (sc *SessionsController) Create(c *gin.Context) { sr.WebAuthnConfig = sc.App.GetWebAuthnConfiguration() } - sid, err := sc.App.SessionORM().CreateSession(sr) + sid, err := sc.App.AuthenticationProvider().CreateSession(sr) if err != nil { jsonAPIError(c, http.StatusUnauthorized, err) return @@ -78,7 +78,7 @@ func (sc *SessionsController) Destroy(c *gin.Context) { jsonAPIResponse(c, Session{Authenticated: false}, "session") return } - if err := sc.App.SessionORM().DeleteUserSession(sessionID); err != nil { + if err := sc.App.AuthenticationProvider().DeleteUserSession(sessionID); err != nil { jsonAPIError(c, http.StatusInternalServerError, err) return } diff --git a/core/web/sessions_controller_test.go b/core/web/sessions_controller_test.go index 7184e3f95b4..c2950caf3d1 100644 --- a/core/web/sessions_controller_test.go +++ b/core/web/sessions_controller_test.go @@ -27,7 +27,7 @@ func TestSessionsController_Create(t *testing.T) { require.NoError(t, app.Start(testutils.Context(t))) user := cltest.MustRandomUser(t) - require.NoError(t, app.SessionORM().CreateUser(&user)) + require.NoError(t, app.AuthenticationProvider().CreateUser(&user)) client := clhttptest.NewTestLocalOnlyHTTPClient() tests := []struct { @@ -59,7 +59,7 @@ func TestSessionsController_Create(t *testing.T) { decrypted, err := cltest.DecodeSessionCookie(sessionCookie.Value) require.NoError(t, err) - user, err := app.SessionORM().AuthorizedUserWithSession(decrypted) + user, err := app.AuthenticationProvider().AuthorizedUserWithSession(decrypted) assert.NoError(t, err) assert.Equal(t, test.email, user.Email) @@ -69,7 +69,7 @@ func TestSessionsController_Create(t *testing.T) { } else { require.True(t, resp.StatusCode >= 400, "Should not be able to create session") // Ignore fixture session - sessions, err := app.SessionORM().Sessions(1, 2) + sessions, err := app.AuthenticationProvider().Sessions(1, 2) assert.NoError(t, err) assert.Empty(t, sessions) } @@ -90,7 +90,7 @@ func TestSessionsController_Create_ReapSessions(t *testing.T) { require.NoError(t, app.Start(testutils.Context(t))) user := cltest.MustRandomUser(t) - require.NoError(t, app.SessionORM().CreateUser(&user)) + require.NoError(t, app.AuthenticationProvider().CreateUser(&user)) staleSession := cltest.NewSession() staleSession.LastUsed = time.Now().Add(-cltest.MustParseDuration(t, "241h")) @@ -107,7 +107,7 @@ func TestSessionsController_Create_ReapSessions(t *testing.T) { var s []sessions.Session gomega.NewWithT(t).Eventually(func() []sessions.Session { - s, err = app.SessionORM().Sessions(0, 10) + s, err = app.AuthenticationProvider().Sessions(0, 10) assert.NoError(t, err) return s }).Should(gomega.HaveLen(1)) @@ -124,7 +124,7 @@ func TestSessionsController_Destroy(t *testing.T) { require.NoError(t, app.Start(testutils.Context(t))) user := cltest.MustRandomUser(t) - require.NoError(t, app.SessionORM().CreateUser(&user)) + require.NoError(t, app.AuthenticationProvider().CreateUser(&user)) correctSession := sessions.NewSession() correctSession.Email = user.Email @@ -150,7 +150,7 @@ func TestSessionsController_Destroy(t *testing.T) { resp, err := client.Do(request) assert.NoError(t, err) - _, err = app.SessionORM().AuthorizedUserWithSession(test.sessionID) + _, err = app.AuthenticationProvider().AuthorizedUserWithSession(test.sessionID) assert.Error(t, err) if test.success { assert.Equal(t, http.StatusOK, resp.StatusCode) @@ -170,7 +170,7 @@ func TestSessionsController_Destroy_ReapSessions(t *testing.T) { require.NoError(t, app.Start(testutils.Context(t))) user := cltest.MustRandomUser(t) - require.NoError(t, app.SessionORM().CreateUser(&user)) + require.NoError(t, app.AuthenticationProvider().CreateUser(&user)) correctSession := sessions.NewSession() correctSession.Email = user.Email @@ -192,7 +192,7 @@ func TestSessionsController_Destroy_ReapSessions(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) gomega.NewWithT(t).Eventually(func() []sessions.Session { - sessions, err := app.SessionORM().Sessions(0, 10) + sessions, err := app.AuthenticationProvider().Sessions(0, 10) assert.NoError(t, err) return sessions }).Should(gomega.HaveLen(0)) diff --git a/core/web/solana_chains_controller_test.go b/core/web/solana_chains_controller_test.go index 5d6dc7424a2..1377cb65aba 100644 --- a/core/web/solana_chains_controller_test.go +++ b/core/web/solana_chains_controller_test.go @@ -11,14 +11,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-relay/pkg/types" - "github.com/smartcontractkit/chainlink-relay/pkg/utils" + commoncfg "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - configtest "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest/v2" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/web" "github.com/smartcontractkit/chainlink/v2/core/web/presenters" @@ -84,7 +84,7 @@ Nodes = [] ChainID: ptr(validId), Chain: config.Chain{ SkipPreflight: ptr(false), - TxTimeout: utils.MustNewDuration(time.Hour), + TxTimeout: commoncfg.MustNewDuration(time.Hour), }, }) @@ -114,7 +114,7 @@ func Test_SolanaChainsController_Index(t *testing.T) { chainA := &solana.TOMLConfig{ ChainID: ptr(fmt.Sprintf("ChainlinktestA-%d", rand.Int31n(999999))), Chain: config.Chain{ - TxTimeout: utils.MustNewDuration(time.Hour), + TxTimeout: commoncfg.MustNewDuration(time.Hour), }, } chainB := &solana.TOMLConfig{ diff --git a/core/web/user_controller.go b/core/web/user_controller.go index 115971eafc7..857fff7b37f 100644 --- a/core/web/user_controller.go +++ b/core/web/user_controller.go @@ -30,10 +30,16 @@ type UpdatePasswordRequest struct { NewPassword string `json:"newPassword"` } +var errUnsupportedForAuth = errors.New("action is unsupported with configured authentication provider") + // Index lists all API users func (c *UserController) Index(ctx *gin.Context) { - users, err := c.App.SessionORM().ListUsers() + users, err := c.App.AuthenticationProvider().ListUsers() if err != nil { + if errors.Is(err, clsession.ErrNotSupported) { + jsonAPIError(ctx, http.StatusBadRequest, errUnsupportedForAuth) + return + } c.App.GetLogger().Errorf("Unable to list users", "err", err) jsonAPIError(ctx, http.StatusInternalServerError, err) return @@ -76,7 +82,7 @@ func (c *UserController) Create(ctx *gin.Context) { jsonAPIError(ctx, http.StatusBadRequest, errors.Errorf("error creating API user: %s", err)) return } - if err = c.App.SessionORM().CreateUser(&user); err != nil { + if err = c.App.AuthenticationProvider().CreateUser(&user); err != nil { // If this is a duplicate key error (code 23505), return a nicer error message var pgErr *pgconn.PgError if ok := errors.As(err, &pgErr); ok { @@ -85,6 +91,10 @@ func (c *UserController) Create(ctx *gin.Context) { return } } + if errors.Is(err, clsession.ErrNotSupported) { + jsonAPIError(ctx, http.StatusBadRequest, errUnsupportedForAuth) + return + } c.App.GetLogger().Errorf("Error creating new API user", "err", err) jsonAPIError(ctx, http.StatusInternalServerError, errors.New("error creating API user")) return @@ -132,8 +142,12 @@ func (c *UserController) UpdateRole(ctx *gin.Context) { return } - user, err := c.App.SessionORM().UpdateRole(request.Email, request.NewRole) + user, err := c.App.AuthenticationProvider().UpdateRole(request.Email, request.NewRole) if err != nil { + if errors.Is(err, clsession.ErrNotSupported) { + jsonAPIError(ctx, http.StatusBadRequest, errUnsupportedForAuth) + return + } jsonAPIError(ctx, http.StatusInternalServerError, errors.Wrap(err, "error updating API user")) return } @@ -146,8 +160,12 @@ func (c *UserController) Delete(ctx *gin.Context) { email := ctx.Param("email") // Attempt find user by email - user, err := c.App.SessionORM().FindUser(email) + user, err := c.App.AuthenticationProvider().FindUser(email) if err != nil { + if errors.Is(err, clsession.ErrNotSupported) { + jsonAPIError(ctx, http.StatusBadRequest, errUnsupportedForAuth) + return + } jsonAPIError(ctx, http.StatusBadRequest, errors.Errorf("specified user not found: %s", email)) return } @@ -163,7 +181,11 @@ func (c *UserController) Delete(ctx *gin.Context) { return } - if err = c.App.SessionORM().DeleteUser(email); err != nil { + if err = c.App.AuthenticationProvider().DeleteUser(email); err != nil { + if errors.Is(err, clsession.ErrNotSupported) { + jsonAPIError(ctx, http.StatusBadRequest, errUnsupportedForAuth) + return + } c.App.GetLogger().Errorf("Error deleting API user", "err", err) jsonAPIError(ctx, http.StatusInternalServerError, errors.New("error deleting API user")) return @@ -185,8 +207,12 @@ func (c *UserController) UpdatePassword(ctx *gin.Context) { jsonAPIError(ctx, http.StatusInternalServerError, errors.New("failed to obtain current user from context")) return } - user, err := c.App.SessionORM().FindUser(sessionUser.Email) + user, err := c.App.AuthenticationProvider().FindUser(sessionUser.Email) if err != nil { + if errors.Is(err, clsession.ErrNotSupported) { + jsonAPIError(ctx, http.StatusBadRequest, errUnsupportedForAuth) + return + } c.App.GetLogger().Errorf("failed to obtain current user record: %s", err) jsonAPIError(ctx, http.StatusInternalServerError, errors.New("unable to update password")) return @@ -222,19 +248,29 @@ func (c *UserController) NewAPIToken(ctx *gin.Context) { jsonAPIError(ctx, http.StatusInternalServerError, errors.New("failed to obtain current user from context")) return } - user, err := c.App.SessionORM().FindUser(sessionUser.Email) + user, err := c.App.AuthenticationProvider().FindUser(sessionUser.Email) if err != nil { + if errors.Is(err, clsession.ErrNotSupported) { + jsonAPIError(ctx, http.StatusBadRequest, errUnsupportedForAuth) + return + } c.App.GetLogger().Errorf("failed to obtain current user record: %s", err) - jsonAPIError(ctx, http.StatusInternalServerError, errors.New("unable to creatae API token")) + jsonAPIError(ctx, http.StatusInternalServerError, errors.New("unable to create API token")) return } - if !utils.CheckPasswordHash(request.Password, user.HashedPassword) { + // In order to create an API token, login validation with provided password must succeed + err = c.App.AuthenticationProvider().TestPassword(sessionUser.Email, request.Password) + if err != nil { c.App.GetAuditLogger().Audit(audit.APITokenCreateAttemptPasswordMismatch, map[string]interface{}{"user": user.Email}) jsonAPIError(ctx, http.StatusUnauthorized, errors.New("incorrect password")) return } newToken := auth.NewToken() - if err := c.App.SessionORM().SetAuthToken(&user, newToken); err != nil { + if err := c.App.AuthenticationProvider().SetAuthToken(&user, newToken); err != nil { + if errors.Is(err, clsession.ErrNotSupported) { + jsonAPIError(ctx, http.StatusBadRequest, errUnsupportedForAuth) + return + } jsonAPIError(ctx, http.StatusInternalServerError, err) return } @@ -256,18 +292,27 @@ func (c *UserController) DeleteAPIToken(ctx *gin.Context) { jsonAPIError(ctx, http.StatusInternalServerError, errors.New("failed to obtain current user from context")) return } - user, err := c.App.SessionORM().FindUser(sessionUser.Email) + user, err := c.App.AuthenticationProvider().FindUser(sessionUser.Email) if err != nil { + if errors.Is(err, clsession.ErrNotSupported) { + jsonAPIError(ctx, http.StatusBadRequest, errUnsupportedForAuth) + return + } c.App.GetLogger().Errorf("failed to obtain current user record: %s", err) jsonAPIError(ctx, http.StatusInternalServerError, errors.New("unable to delete API token")) return } - if !utils.CheckPasswordHash(request.Password, user.HashedPassword) { + err = c.App.AuthenticationProvider().TestPassword(sessionUser.Email, request.Password) + if err != nil { c.App.GetAuditLogger().Audit(audit.APITokenDeleteAttemptPasswordMismatch, map[string]interface{}{"user": user.Email}) jsonAPIError(ctx, http.StatusUnauthorized, errors.New("incorrect password")) return } - if err := c.App.SessionORM().DeleteAuthToken(&user); err != nil { + if err := c.App.AuthenticationProvider().DeleteAuthToken(&user); err != nil { + if errors.Is(err, clsession.ErrNotSupported) { + jsonAPIError(ctx, http.StatusBadRequest, errUnsupportedForAuth) + return + } jsonAPIError(ctx, http.StatusInternalServerError, err) return } @@ -291,12 +336,15 @@ func (c *UserController) updateUserPassword(ctx *gin.Context, user *clsession.Us if err != nil { return err } - orm := c.App.SessionORM() + orm := c.App.AuthenticationProvider() if err := orm.ClearNonCurrentSessions(sessionID); err != nil { c.App.GetLogger().Errorf("failed to clear non current user sessions: %s", err) return errors.New("unable to update password") } if err := orm.SetPassword(user, newPassword); err != nil { + if errors.Is(err, clsession.ErrNotSupported) { + return errUnsupportedForAuth + } c.App.GetLogger().Errorf("failed to update current user password: %s", err) return errors.New("unable to update password") } diff --git a/core/web/user_controller_test.go b/core/web/user_controller_test.go index a11082ff6a4..6baab1c396a 100644 --- a/core/web/user_controller_test.go +++ b/core/web/user_controller_test.go @@ -188,7 +188,7 @@ func TestUserController_UpdateRole(t *testing.T) { client := app.NewHTTPClient(nil) user := cltest.MustRandomUser(t) - err := app.SessionORM().CreateUser(&user) + err := app.AuthenticationProvider().CreateUser(&user) require.NoError(t, err) testCases := []struct { @@ -235,7 +235,7 @@ func TestUserController_DeleteUser(t *testing.T) { client := app.NewHTTPClient(nil) user := cltest.MustRandomUser(t) - err := app.SessionORM().CreateUser(&user) + err := app.AuthenticationProvider().CreateUser(&user) require.NoError(t, err) resp, cleanup := client.Delete(fmt.Sprintf("/v2/users/%s", url.QueryEscape(user.Email))) diff --git a/core/web/webauthn_controller.go b/core/web/webauthn_controller.go index 05090013237..41c8f268ad4 100644 --- a/core/web/webauthn_controller.go +++ b/core/web/webauthn_controller.go @@ -36,7 +36,7 @@ func (c *WebAuthnController) BeginRegistration(ctx *gin.Context) { return } - orm := c.App.SessionORM() + orm := c.App.AuthenticationProvider() uwas, err := orm.GetUserWebAuthn(user.Email) if err != nil { c.App.GetLogger().Errorf("failed to obtain current user MFA tokens: error in GetUserWebAuthn: %+v", err) @@ -66,7 +66,7 @@ func (c *WebAuthnController) FinishRegistration(ctx *gin.Context) { return } - orm := c.App.SessionORM() + orm := c.App.AuthenticationProvider() uwas, err := orm.GetUserWebAuthn(user.Email) if err != nil { c.App.GetLogger().Errorf("failed to obtain current user MFA tokens: error in GetUserWebAuthn: %s", err) @@ -83,7 +83,7 @@ func (c *WebAuthnController) FinishRegistration(ctx *gin.Context) { return } - if sessions.AddCredentialToUser(c.App.SessionORM(), user.Email, credential) != nil { + if sessions.AddCredentialToUser(c.App.AuthenticationProvider(), user.Email, credential) != nil { c.App.GetLogger().Errorf("Could not save WebAuthn credential to DB for user: %s", user.Email) jsonAPIError(ctx, http.StatusInternalServerError, errors.New("internal Server Error")) return diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 88149872b4a..a74a199231f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,87 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ... + +## 2.8.0 - 2024-01-24 + +### Added + +- Added distributed tracing in the OpenTelemetry trace format to the node, currently focused at the LOOPP Plugin development effort. This includes a new set of `Tracing` TOML configurations. The default for collecting traces is off - you must explicitly enable traces and setup a valid OpenTelemetry collector. Refer to `.github/tracing/README.md` for more details. +- Added a new, optional WebServer authentication option that supports LDAP as a user identity provider. This enables user login access and user roles to be managed and provisioned via a centralized remote server that supports the LDAP protocol, which can be helpful when running multiple nodes. See the documentation for more information and config setup instructions. There is a new `[WebServer].AuthenticationMethod` config option, when set to `ldap` requires the new `[WebServer.LDAP]` config section to be defined, see the reference `docs/core.toml`. +- New prom metrics for mercury transmit queue: + `mercury_transmit_queue_delete_error_count` + `mercury_transmit_queue_insert_error_count` + `mercury_transmit_queue_push_error_count` + Nops should consider alerting on these. +- Mercury now implements a local cache for fetching prices for fees, which ought to reduce latency and load on the mercury server, as well as increasing performance. It is enabled by default and can be configured with the following new config variables: + ``` + [Mercury] + + # Mercury.Cache controls settings for the price retrieval cache querying a mercury server + [Mercury.Cache] + # LatestReportTTL controls how "stale" we will allow a price to be e.g. if + # set to 1s, a new price will always be fetched if the last result was + # from 1 second ago or older. + # + # Another way of looking at it is such: the cache will _never_ return a + # price that was queried from now-LatestReportTTL or before. + # + # Setting to zero disables caching entirely. + LatestReportTTL = "1s" # Default + # MaxStaleAge is that maximum amount of time that a value can be stale + # before it is deleted from the cache (a form of garbage collection). + # + # This should generally be set to something much larger than + # LatestReportTTL. Setting to zero disables garbage collection. + MaxStaleAge = "1h" # Default + # LatestReportDeadline controls how long to wait for a response from the + # mercury server before retrying. Setting this to zero will wait indefinitely. + LatestReportDeadline = "5s" # Default + ``` +- New prom metrics for the mercury cache: + `mercury_cache_fetch_failure_count` + `mercury_cache_hit_count` + `mercury_cache_wait_count` + `mercury_cache_miss_count` +- Added new `EVM.OCR` TOML config fields `DeltaCOverride` and `DeltaCJitterOverride` for overriding the config DeltaC. +- Mercury v0.2 has improved consensus around current block that uses the most recent 5 blocks instead of only the latest one +- Two new prom metrics for mercury, nops should consider adding alerting on these: + - `mercury_insufficient_blocks_count` + - `mercury_zero_blocks_count` + +### Changed + +- `PromReporter` no longer directly reads txm related status from the db, and instead uses the txStore API. +- `L2Suggested` mode is now called `SuggestedPrice` +- Console logs will now escape (non-whitespace) control characters +- Following EVM Pool metrics were renamed: + - `evm_pool_rpc_node_states` → `multi_node_states` + - `evm_pool_rpc_node_num_transitions_to_alive` → `pool_rpc_node_num_transitions_to_alive` + - `evm_pool_rpc_node_num_transitions_to_in_sync` → `pool_rpc_node_num_transitions_to_in_sync` + - `evm_pool_rpc_node_num_transitions_to_out_of_sync` → `pool_rpc_node_num_transitions_to_out_of_sync` + - `evm_pool_rpc_node_num_transitions_to_unreachable` → `pool_rpc_node_num_transitions_to_unreachable` + - `evm_pool_rpc_node_num_transitions_to_invalid_chain_id` → `pool_rpc_node_num_transitions_to_invalid_chain_id` + - `evm_pool_rpc_node_num_transitions_to_unusable` → `pool_rpc_node_num_transitions_to_unusable` + - `evm_pool_rpc_node_highest_seen_block` → `pool_rpc_node_highest_seen_block` + - `evm_pool_rpc_node_num_seen_blocks` → `pool_rpc_node_num_seen_blocks` + - `evm_pool_rpc_node_polls_total` → `pool_rpc_node_polls_total` + - `evm_pool_rpc_node_polls_failed` → `pool_rpc_node_polls_failed` + - `evm_pool_rpc_node_polls_success` → `pool_rpc_node_polls_success` + +### Removed + +- Removed `Optimism2` as a supported gas estimator mode + +### Fixed + +- Corrected Ethereum Sepolia `LinkContractAddress` to `0x779877A7B0D9E8603169DdbD7836e478b4624789` +- Fixed a bug that caused the Telemetry Manager to report incorrect health + +### Upcoming Required Configuration Changes +Starting in `v2.9.0`: +- `TelemetryIngress.URL` and `TelemetryIngress.ServerPubKey` will no longer be allowed. Any TOML configuration that sets this fields will prevent the node from booting. These fields will be replaced by `[[TelemetryIngress.Endpoints]]` +- `P2P.V1` will no longer be supported and must not be set in TOML configuration in order to boot. Use `P2P.V2` instead. If you are using both, `V1` can simply be removed. + ## 2.7.2 - 2023-12-14 ### Fixed @@ -43,7 +124,7 @@ These will eventually replace `TelemetryIngress.URL` and `TelemetryIngress.Serve - Added bridge_name label to `pipeline_tasks_total_finished` prometheus metric. This should make it easier to see directly what bridge was failing out from the CL NODE perspective. -- LogPoller will now use finality tags to dynamically determine finality on evm chains if `UseFinalityTags=true`, rather than the fixed `FinalityDepth` specified in toml config +- LogPoller will now use finality tags to dynamically determine finality on evm chains if `EVM.FinalityTagEnabled=true`, rather than the fixed `EVM.FinalityDepth` specified in toml config ### Changed @@ -51,9 +132,7 @@ These will eventually replace `TelemetryIngress.URL` and `TelemetryIngress.Serve - `P2P.V2` is now enabled (`Enabled = true`) by default. ### Upcoming Required Configuration Changes - Starting in `v2.9.0`: - - `TelemetryIngress.URL` and `TelemetryIngress.ServerPubKey` will no longer be allowed. Any TOML configuration that sets this fields will prevent the node from booting. These fields will be replaced by `[[TelemetryIngress.Endpoints]]` - `P2P.V1` will no longer be supported and must not be set in TOML configuration in order to boot. Use `P2P.V2` instead. If you are using both, `V1` can simply be removed. diff --git a/docs/CONFIG.md b/docs/CONFIG.md index d97cbabd233..3568abe7a0e 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -459,6 +459,7 @@ MaxBackups determines the maximum number of old log files to retain. Keeping thi ## WebServer ```toml [WebServer] +AuthenticationMethod = 'local' # Default AllowOrigins = 'http://localhost:3000,http://localhost:6688' # Default BridgeCacheTTL = '0s' # Default BridgeResponseURL = 'https://my-chainlink-node.example.com:6688' # Example @@ -473,6 +474,12 @@ ListenIP = '0.0.0.0' # Default ``` +### AuthenticationMethod +```toml +AuthenticationMethod = 'local' # Default +``` +AuthenticationMethod defines which pluggable auth interface to use for user login and role assumption. Options include 'local' and 'ldap'. See docs for more details + ### AllowOrigins ```toml AllowOrigins = 'http://localhost:3000,http://localhost:6688' # Default @@ -546,6 +553,132 @@ ListenIP = '0.0.0.0' # Default ``` ListenIP specifies the IP to bind the HTTP server to +## WebServer.LDAP +```toml +[WebServer.LDAP] +ServerTLS = true # Default +SessionTimeout = '15m0s' # Default +QueryTimeout = '2m0s' # Default +BaseUserAttr = 'uid' # Default +BaseDN = 'dc=custom,dc=example,dc=com' # Example +UsersDN = 'ou=users' # Default +GroupsDN = 'ou=groups' # Default +ActiveAttribute = '' # Default +ActiveAttributeAllowedValue = '' # Default +AdminUserGroupCN = 'NodeAdmins' # Default +EditUserGroupCN = 'NodeEditors' # Default +RunUserGroupCN = 'NodeRunners' # Default +ReadUserGroupCN = 'NodeReadOnly' # Default +UserApiTokenEnabled = false # Default +UserAPITokenDuration = '240h0m0s' # Default +UpstreamSyncInterval = '0s' # Default +UpstreamSyncRateLimit = '2m0s' # Default +``` +Optional LDAP config if WebServer.AuthenticationMethod is set to 'ldap' +LDAP queries are all parameterized to support custom LDAP 'dn', 'cn', and attributes + +### ServerTLS +```toml +ServerTLS = true # Default +``` +ServerTLS defines the option to require the secure ldaps + +### SessionTimeout +```toml +SessionTimeout = '15m0s' # Default +``` +SessionTimeout determines the amount of idle time to elapse before session cookies expire. This signs out GUI users from their sessions. + +### QueryTimeout +```toml +QueryTimeout = '2m0s' # Default +``` +QueryTimeout defines how long queries should wait before timing out, defined in seconds + +### BaseUserAttr +```toml +BaseUserAttr = 'uid' # Default +``` +BaseUserAttr defines the base attribute used to populate LDAP queries such as "uid=$", default is example + +### BaseDN +```toml +BaseDN = 'dc=custom,dc=example,dc=com' # Example +``` +BaseDN defines the base LDAP 'dn' search filter to apply to every LDAP query, replace example,com with the appropriate LDAP server's structure + +### UsersDN +```toml +UsersDN = 'ou=users' # Default +``` +UsersDN defines the 'dn' query to use when querying for the 'users' 'ou' group + +### GroupsDN +```toml +GroupsDN = 'ou=groups' # Default +``` +GroupsDN defines the 'dn' query to use when querying for the 'groups' 'ou' group + +### ActiveAttribute +```toml +ActiveAttribute = '' # Default +``` +ActiveAttribute is an optional user field to check truthiness for if a user is valid/active. This is only required if the LDAP provider lists inactive users as members of groups + +### ActiveAttributeAllowedValue +```toml +ActiveAttributeAllowedValue = '' # Default +``` +ActiveAttributeAllowedValue is the value to check against for the above optional user attribute + +### AdminUserGroupCN +```toml +AdminUserGroupCN = 'NodeAdmins' # Default +``` +AdminUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Admin' role + +### EditUserGroupCN +```toml +EditUserGroupCN = 'NodeEditors' # Default +``` +EditUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Edit' role + +### RunUserGroupCN +```toml +RunUserGroupCN = 'NodeRunners' # Default +``` +RunUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Run' role + +### ReadUserGroupCN +```toml +ReadUserGroupCN = 'NodeReadOnly' # Default +``` +ReadUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Read' role + +### UserApiTokenEnabled +```toml +UserApiTokenEnabled = false # Default +``` +UserApiTokenEnabled enables the users to issue API tokens with the same access of their role + +### UserAPITokenDuration +```toml +UserAPITokenDuration = '240h0m0s' # Default +``` +UserAPITokenDuration is the duration of time an API token is active for before expiring + +### UpstreamSyncInterval +```toml +UpstreamSyncInterval = '0s' # Default +``` +UpstreamSyncInterval is the interval at which the background LDAP sync task will be called. A '0s' value disables the background sync being run on an interval. This check is already performed during login/logout actions, all sessions and API tokens stored in the local ldap tables are updated to match the remote server + +### UpstreamSyncRateLimit +```toml +UpstreamSyncRateLimit = '2m0s' # Default +``` +UpstreamSyncRateLimit defines a duration to limit the number of query/API calls to the upstream LDAP provider. It prevents the sync functionality from being called multiple times within the defined duration + ## WebServer.RateLimit ```toml [WebServer.RateLimit] @@ -757,7 +890,7 @@ ContractTransmitterTransmitTimeout = '10s' # Default DatabaseTimeout = '10s' # Default KeyBundleID = '7a5f66bbe6594259325bf2b4f5b1a9c900000000000000000000000000000000' # Example CaptureEATelemetry = false # Default -CaptureAutomationCustomTelemetry = false # Default +CaptureAutomationCustomTelemetry = true # Default DefaultTransactionQueueDepth = 1 # Default SimulateTransactions = false # Default TraceLogging = false # Default @@ -854,7 +987,7 @@ CaptureEATelemetry toggles collecting extra information from External Adaptares ### CaptureAutomationCustomTelemetry ```toml -CaptureAutomationCustomTelemetry = false # Default +CaptureAutomationCustomTelemetry = true # Default ``` CaptureAutomationCustomTelemetry toggles collecting automation specific telemetry @@ -1462,9 +1595,11 @@ DisableRateLimiting skips ratelimiting on asset requests. ```toml [Tracing] Enabled = false # Default -CollectorTarget = "localhost:4317" # Example -NodeID = "NodeID" # Example +CollectorTarget = 'localhost:4317' # Example +NodeID = 'NodeID' # Example SamplingRatio = 1.0 # Example +Mode = 'tls' # Default +TLSCertPath = '/path/to/cert.pem' # Example ``` @@ -1476,13 +1611,13 @@ Enabled turns trace collection on or off. On requires an OTEL Tracing Collector. ### CollectorTarget ```toml -CollectorTarget = "localhost:4317" # Example +CollectorTarget = 'localhost:4317' # Example ``` CollectorTarget is the logical address of the OTEL Tracing Collector. ### NodeID ```toml -NodeID = "NodeID" # Example +NodeID = 'NodeID' # Example ``` NodeID is an unique name for this node relative to any other node traces are collected for. @@ -1492,19 +1627,76 @@ SamplingRatio = 1.0 # Example ``` SamplingRatio is the ratio of traces to sample for this node. +### Mode +```toml +Mode = 'tls' # Default +``` +Mode is a string value. `tls` or `unencrypted` are the only values allowed. If set to `unencrypted`, `TLSCertPath` can be unset, meaning traces will be sent over plaintext to the collector. + +### TLSCertPath +```toml +TLSCertPath = '/path/to/cert.pem' # Example +``` +TLSCertPath is the file path to the TLS certificate used for secure communication with an OTEL Tracing Collector. + ## Tracing.Attributes ```toml [Tracing.Attributes] -env = "test" # Example +env = 'test' # Example ``` Tracing.Attributes are user specified key-value pairs to associate in the context of the traces ### env ```toml -env = "test" # Example +env = 'test' # Example ``` env is an example user specified key-value pair +## Mercury +```toml +[Mercury] +``` + + +## Mercury.Cache +```toml +[Mercury.Cache] +LatestReportTTL = "1s" # Default +MaxStaleAge = "1h" # Default +LatestReportDeadline = "5s" # Default +``` +Mercury.Cache controls settings for the price retrieval cache querying a mercury server + +### LatestReportTTL +```toml +LatestReportTTL = "1s" # Default +``` +LatestReportTTL controls how "stale" we will allow a price to be e.g. if +set to 1s, a new price will always be fetched if the last result was +from 1 second ago or older. + +Another way of looking at it is such: the cache will _never_ return a +price that was queried from now-LatestReportTTL or before. + +Setting to zero disables caching entirely. + +### MaxStaleAge +```toml +MaxStaleAge = "1h" # Default +``` +MaxStaleAge is that maximum amount of time that a value can be stale +before it is deleted from the cache (a form of garbage collection). + +This should generally be set to something much larger than +LatestReportTTL. Setting to zero disables garbage collection. + +### LatestReportDeadline +```toml +LatestReportDeadline = "5s" # Default +``` +LatestReportDeadline controls how long to wait for a response from the +mercury server before retrying. Setting this to zero will wait indefinitely. + ## EVM EVM defaults depend on ChainID: @@ -1579,6 +1771,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -1658,6 +1852,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -1737,6 +1933,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -1816,6 +2014,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -1896,6 +2096,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -1975,6 +2177,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -2054,6 +2258,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -2134,6 +2340,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -2213,6 +2421,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '2s' DatabaseTimeout = '2s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '500ms' [OCR2] @@ -2291,6 +2501,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -2369,6 +2581,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -2393,9 +2607,496 @@ LogKeepBlocksDepth = 100000 MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true -NoNewHeadsThreshold = '30s' +NoNewHeadsThreshold = '30s' +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 2 + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '1m0s' + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'BlockHistory' +PriceDefault = '5 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 gwei' +LimitDefault = 500000 +LimitMax = 500000 +LimitMultiplier = '1' +LimitTransfer = 21000 +BumpMin = '5 gwei' +BumpPercent = 20 +BumpThreshold = 5 +EIP1559DynamicFees = false +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 24 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[HeadTracker] +HistoryDepth = 100 +MaxBufferSize = 3 +SamplingInterval = '1s' + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 10 +LeaseDuration = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '2s' +DatabaseTimeout = '2s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '500ms' + +[OCR2] +[OCR2.Automation] +GasLimit = 5300000 +``` + +

+ +
xDai Mainnet (100)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +ChainType = 'xdai' +FinalityDepth = 50 +FinalityTagEnabled = false +LinkContractAddress = '0xE2e73A1c69ecF83F464EFCE6A5be353a37cA09b2' +LogBackfillBatchSize = 1000 +LogPollInterval = '5s' +LogKeepBlocksDepth = 100000 +MinIncomingConfirmations = 3 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '3m0s' +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '1m0s' + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'BlockHistory' +PriceDefault = '1 gwei' +PriceMax = '500 gwei' +PriceMin = '1 gwei' +LimitDefault = 500000 +LimitMax = 500000 +LimitMultiplier = '1' +LimitTransfer = 21000 +BumpMin = '5 gwei' +BumpPercent = 20 +BumpThreshold = 3 +EIP1559DynamicFees = false +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 8 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[HeadTracker] +HistoryDepth = 100 +MaxBufferSize = 3 +SamplingInterval = '1s' + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 5 +LeaseDuration = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 5300000 +``` + +

+ +
Heco Mainnet (128)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +FinalityDepth = 50 +FinalityTagEnabled = false +LinkContractAddress = '0x404460C6A5EdE2D891e8297795264fDe62ADBB75' +LogBackfillBatchSize = 1000 +LogPollInterval = '3s' +LogKeepBlocksDepth = 100000 +MinIncomingConfirmations = 3 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '30s' +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 2 + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '1m0s' + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'BlockHistory' +PriceDefault = '5 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 gwei' +LimitDefault = 500000 +LimitMax = 500000 +LimitMultiplier = '1' +LimitTransfer = 21000 +BumpMin = '5 gwei' +BumpPercent = 20 +BumpThreshold = 5 +EIP1559DynamicFees = false +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 24 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[HeadTracker] +HistoryDepth = 100 +MaxBufferSize = 3 +SamplingInterval = '1s' + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 10 +LeaseDuration = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '2s' +DatabaseTimeout = '2s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '500ms' + +[OCR2] +[OCR2.Automation] +GasLimit = 5300000 +``` + +

+ +
Polygon Mainnet (137)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +FinalityDepth = 500 +FinalityTagEnabled = false +LinkContractAddress = '0xb0897686c545045aFc77CF20eC7A532E3120E0F1' +LogBackfillBatchSize = 1000 +LogPollInterval = '1s' +LogKeepBlocksDepth = 100000 +MinIncomingConfirmations = 5 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '30s' +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 10 + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 5000 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '1m0s' + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'BlockHistory' +PriceDefault = '30 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '30 gwei' +LimitDefault = 500000 +LimitMax = 500000 +LimitMultiplier = '1' +LimitTransfer = 21000 +BumpMin = '20 gwei' +BumpPercent = 20 +BumpThreshold = 5 +EIP1559DynamicFees = false +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 24 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[HeadTracker] +HistoryDepth = 2000 +MaxBufferSize = 3 +SamplingInterval = '1s' + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 10 +LeaseDuration = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 5300000 +``` + +

+ +
Fantom Mainnet (250)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +FinalityDepth = 50 +FinalityTagEnabled = false +LinkContractAddress = '0x6F43FF82CCA38001B6699a8AC47A2d0E66939407' +LogBackfillBatchSize = 1000 +LogPollInterval = '1s' +LogKeepBlocksDepth = 100000 +MinIncomingConfirmations = 3 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '30s' +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 2 + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '1m0s' + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'SuggestedPrice' +PriceDefault = '20 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 gwei' +LimitDefault = 500000 +LimitMax = 500000 +LimitMultiplier = '1' +LimitTransfer = 21000 +BumpMin = '5 gwei' +BumpPercent = 20 +BumpThreshold = 3 +EIP1559DynamicFees = false +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 8 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[HeadTracker] +HistoryDepth = 100 +MaxBufferSize = 3 +SamplingInterval = '1s' + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 5 +LeaseDuration = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 3800000 +``` + +

+ +
Kroma Mainnet (255)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +ChainType = 'kroma' +FinalityDepth = 400 +FinalityTagEnabled = false +LogBackfillBatchSize = 1000 +LogPollInterval = '2s' +LogKeepBlocksDepth = 100000 +MinIncomingConfirmations = 1 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '40s' +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '30s' + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'BlockHistory' +PriceDefault = '20 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 wei' +LimitDefault = 500000 +LimitMax = 500000 +LimitMultiplier = '1' +LimitTransfer = 21000 +BumpMin = '100 wei' +BumpPercent = 20 +BumpThreshold = 3 +EIP1559DynamicFees = true +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 24 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[HeadTracker] +HistoryDepth = 400 +MaxBufferSize = 3 +SamplingInterval = '1s' + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 10 +LeaseDuration = '0s' + +[OCR] +ContractConfirmations = 1 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 5300000 +``` + +

+ +
zkSync Goerli (280)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +ChainType = 'zksync' +FinalityDepth = 1 +FinalityTagEnabled = false +LogBackfillBatchSize = 1000 +LogPollInterval = '5s' +LogKeepBlocksDepth = 100000 +MinIncomingConfirmations = 1 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '1m0s' RPCDefaultBatchSize = 250 -RPCBlockQueryDelay = 2 +RPCBlockQueryDelay = 1 [Transactions] ForwardersEnabled = false @@ -2410,16 +3111,16 @@ Enabled = true [GasEstimator] Mode = 'BlockHistory' -PriceDefault = '5 gwei' -PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' -PriceMin = '1 gwei' -LimitDefault = 500000 +PriceDefault = '20 gwei' +PriceMax = '18.446744073709551615 ether' +PriceMin = '0' +LimitDefault = 3500000 LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 BumpMin = '5 gwei' BumpPercent = 20 -BumpThreshold = 5 +BumpThreshold = 3 EIP1559DynamicFees = false FeeCapDefault = '100 gwei' TipCapDefault = '1 wei' @@ -2427,13 +3128,13 @@ TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 24 +BlockHistorySize = 8 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 [HeadTracker] -HistoryDepth = 100 +HistoryDepth = 5 MaxBufferSize = 3 SamplingInterval = '1s' @@ -2441,14 +3142,16 @@ SamplingInterval = '1s' PollFailureThreshold = 5 PollInterval = '10s' SelectionMode = 'HighestHead' -SyncThreshold = 10 +SyncThreshold = 5 LeaseDuration = '0s' [OCR] ContractConfirmations = 4 -ContractTransmitterTransmitTimeout = '2s' -DatabaseTimeout = '2s' -ObservationGracePeriod = '500ms' +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] @@ -2457,23 +3160,22 @@ GasLimit = 5300000

-
xDai Mainnet (100)

+

zkSync Mainnet (324)

```toml AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'xdai' -FinalityDepth = 50 +ChainType = 'zksync' +FinalityDepth = 1 FinalityTagEnabled = false -LinkContractAddress = '0xE2e73A1c69ecF83F464EFCE6A5be353a37cA09b2' LogBackfillBatchSize = 1000 LogPollInterval = '5s' LogKeepBlocksDepth = 100000 -MinIncomingConfirmations = 3 +MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true -NoNewHeadsThreshold = '3m0s' +NoNewHeadsThreshold = '1m0s' RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 @@ -2490,10 +3192,10 @@ Enabled = true [GasEstimator] Mode = 'BlockHistory' -PriceDefault = '1 gwei' -PriceMax = '500 gwei' -PriceMin = '1 gwei' -LimitDefault = 500000 +PriceDefault = '20 gwei' +PriceMax = '18.446744073709551615 ether' +PriceMin = '0' +LimitDefault = 3500000 LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 @@ -2513,7 +3215,7 @@ CheckInclusionPercentile = 90 TransactionPercentile = 60 [HeadTracker] -HistoryDepth = 100 +HistoryDepth = 5 MaxBufferSize = 3 SamplingInterval = '1s' @@ -2528,6 +3230,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -2537,24 +3241,25 @@ GasLimit = 5300000

-
Heco Mainnet (128)

+

Optimism Goerli (420)

```toml AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -FinalityDepth = 50 +ChainType = 'optimismBedrock' +FinalityDepth = 200 FinalityTagEnabled = false -LinkContractAddress = '0x404460C6A5EdE2D891e8297795264fDe62ADBB75' +LinkContractAddress = '0xdc2CC710e42857672E7907CF474a69B63B93089f' LogBackfillBatchSize = 1000 -LogPollInterval = '3s' +LogPollInterval = '2s' LogKeepBlocksDepth = 100000 -MinIncomingConfirmations = 3 +MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true -NoNewHeadsThreshold = '30s' +NoNewHeadsThreshold = '40s' RPCDefaultBatchSize = 250 -RPCBlockQueryDelay = 2 +RPCBlockQueryDelay = 1 [Transactions] ForwardersEnabled = false @@ -2562,37 +3267,37 @@ MaxInFlight = 16 MaxQueued = 250 ReaperInterval = '1h0m0s' ReaperThreshold = '168h0m0s' -ResendAfterThreshold = '1m0s' +ResendAfterThreshold = '30s' [BalanceMonitor] Enabled = true [GasEstimator] Mode = 'BlockHistory' -PriceDefault = '5 gwei' +PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' -PriceMin = '1 gwei' +PriceMin = '1 wei' LimitDefault = 500000 LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 -BumpMin = '5 gwei' +BumpMin = '100 wei' BumpPercent = 20 -BumpThreshold = 5 -EIP1559DynamicFees = false +BumpThreshold = 3 +EIP1559DynamicFees = true FeeCapDefault = '100 gwei' TipCapDefault = '1 wei' TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 24 +BlockHistorySize = 60 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 [HeadTracker] -HistoryDepth = 100 +HistoryDepth = 300 MaxBufferSize = 3 SamplingInterval = '1s' @@ -2604,41 +3309,43 @@ SyncThreshold = 10 LeaseDuration = '0s' [OCR] -ContractConfirmations = 4 -ContractTransmitterTransmitTimeout = '2s' -DatabaseTimeout = '2s' -ObservationGracePeriod = '500ms' +ContractConfirmations = 1 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] -GasLimit = 5300000 +GasLimit = 6500000 ```

-
Polygon Mainnet (137)

+

Metis Rinkeby (588)

```toml AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -FinalityDepth = 500 +ChainType = 'metis' +FinalityDepth = 1 FinalityTagEnabled = false -LinkContractAddress = '0xb0897686c545045aFc77CF20eC7A532E3120E0F1' LogBackfillBatchSize = 1000 -LogPollInterval = '1s' +LogPollInterval = '15s' LogKeepBlocksDepth = 100000 -MinIncomingConfirmations = 5 +MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true -NoNewHeadsThreshold = '30s' +NoNewHeadsThreshold = '0s' RPCDefaultBatchSize = 250 -RPCBlockQueryDelay = 10 +RPCBlockQueryDelay = 1 [Transactions] ForwardersEnabled = false MaxInFlight = 16 -MaxQueued = 5000 +MaxQueued = 250 ReaperInterval = '1h0m0s' ReaperThreshold = '168h0m0s' ResendAfterThreshold = '1m0s' @@ -2647,17 +3354,17 @@ ResendAfterThreshold = '1m0s' Enabled = true [GasEstimator] -Mode = 'BlockHistory' -PriceDefault = '30 gwei' +Mode = 'SuggestedPrice' +PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' -PriceMin = '30 gwei' +PriceMin = '0' LimitDefault = 500000 LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 -BumpMin = '20 gwei' +BumpMin = '5 gwei' BumpPercent = 20 -BumpThreshold = 5 +BumpThreshold = 0 EIP1559DynamicFees = false FeeCapDefault = '100 gwei' TipCapDefault = '1 wei' @@ -2665,13 +3372,13 @@ TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 24 +BlockHistorySize = 0 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 [HeadTracker] -HistoryDepth = 2000 +HistoryDepth = 100 MaxBufferSize = 3 SamplingInterval = '1s' @@ -2683,9 +3390,11 @@ SyncThreshold = 10 LeaseDuration = '0s' [OCR] -ContractConfirmations = 4 +ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -2695,24 +3404,23 @@ GasLimit = 5300000

-
Fantom Mainnet (250)

+

Klaytn Testnet (1001)

```toml AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -FinalityDepth = 50 +FinalityDepth = 1 FinalityTagEnabled = false -LinkContractAddress = '0x6F43FF82CCA38001B6699a8AC47A2d0E66939407' LogBackfillBatchSize = 1000 -LogPollInterval = '1s' +LogPollInterval = '15s' LogKeepBlocksDepth = 100000 -MinIncomingConfirmations = 3 +MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' RPCDefaultBatchSize = 250 -RPCBlockQueryDelay = 2 +RPCBlockQueryDelay = 1 [Transactions] ForwardersEnabled = false @@ -2726,8 +3434,8 @@ ResendAfterThreshold = '1m0s' Enabled = true [GasEstimator] -Mode = 'BlockHistory' -PriceDefault = '15 gwei' +Mode = 'SuggestedPrice' +PriceDefault = '750 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' PriceMin = '1 gwei' LimitDefault = 500000 @@ -2736,7 +3444,7 @@ LimitMultiplier = '1' LimitTransfer = 21000 BumpMin = '5 gwei' BumpPercent = 20 -BumpThreshold = 3 +BumpThreshold = 0 EIP1559DynamicFees = false FeeCapDefault = '100 gwei' TipCapDefault = '1 wei' @@ -2762,35 +3470,36 @@ SyncThreshold = 5 LeaseDuration = '0s' [OCR] -ContractConfirmations = 4 +ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] -GasLimit = 3800000 +GasLimit = 5300000 ```

-
Optimism Goerli (420)

+

Metis Mainnet (1088)

```toml AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'optimismBedrock' -FinalityDepth = 200 +ChainType = 'metis' +FinalityDepth = 1 FinalityTagEnabled = false -LinkContractAddress = '0xdc2CC710e42857672E7907CF474a69B63B93089f' LogBackfillBatchSize = 1000 -LogPollInterval = '2s' +LogPollInterval = '15s' LogKeepBlocksDepth = 100000 MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true -NoNewHeadsThreshold = '40s' +NoNewHeadsThreshold = '0s' RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 @@ -2800,37 +3509,37 @@ MaxInFlight = 16 MaxQueued = 250 ReaperInterval = '1h0m0s' ReaperThreshold = '168h0m0s' -ResendAfterThreshold = '30s' +ResendAfterThreshold = '1m0s' [BalanceMonitor] Enabled = true [GasEstimator] -Mode = 'BlockHistory' +Mode = 'SuggestedPrice' PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' -PriceMin = '1 wei' +PriceMin = '0' LimitDefault = 500000 LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 -BumpMin = '100 wei' +BumpMin = '5 gwei' BumpPercent = 20 -BumpThreshold = 3 -EIP1559DynamicFees = true +BumpThreshold = 0 +EIP1559DynamicFees = false FeeCapDefault = '100 gwei' TipCapDefault = '1 wei' TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 60 +BlockHistorySize = 0 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 [HeadTracker] -HistoryDepth = 300 +HistoryDepth = 100 MaxBufferSize = 3 SamplingInterval = '1s' @@ -2845,31 +3554,33 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] -GasLimit = 6500000 +GasLimit = 5300000 ```

-
Metis Rinkeby (588)

+

WeMix Mainnet (1111)

```toml AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'metis' +ChainType = 'wemix' FinalityDepth = 1 FinalityTagEnabled = false LogBackfillBatchSize = 1000 -LogPollInterval = '15s' +LogPollInterval = '3s' LogKeepBlocksDepth = 100000 MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true -NoNewHeadsThreshold = '0s' +NoNewHeadsThreshold = '30s' RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 @@ -2885,25 +3596,25 @@ ResendAfterThreshold = '1m0s' Enabled = true [GasEstimator] -Mode = 'L2Suggested' +Mode = 'BlockHistory' PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' -PriceMin = '0' +PriceMin = '1 gwei' LimitDefault = 500000 LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 BumpMin = '5 gwei' BumpPercent = 20 -BumpThreshold = 0 -EIP1559DynamicFees = false +BumpThreshold = 3 +EIP1559DynamicFees = true FeeCapDefault = '100 gwei' -TipCapDefault = '1 wei' +TipCapDefault = '100 gwei' TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 0 +BlockHistorySize = 8 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 @@ -2917,13 +3628,15 @@ SamplingInterval = '1s' PollFailureThreshold = 5 PollInterval = '10s' SelectionMode = 'HighestHead' -SyncThreshold = 10 +SyncThreshold = 5 LeaseDuration = '0s' [OCR] ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -2933,16 +3646,17 @@ GasLimit = 5300000

-
Klaytn Testnet (1001)

+

WeMix Testnet (1112)

```toml AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false +ChainType = 'wemix' FinalityDepth = 1 FinalityTagEnabled = false LogBackfillBatchSize = 1000 -LogPollInterval = '15s' +LogPollInterval = '3s' LogKeepBlocksDepth = 100000 MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' @@ -2963,8 +3677,8 @@ ResendAfterThreshold = '1m0s' Enabled = true [GasEstimator] -Mode = 'L2Suggested' -PriceDefault = '750 gwei' +Mode = 'BlockHistory' +PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' PriceMin = '1 gwei' LimitDefault = 500000 @@ -2973,10 +3687,10 @@ LimitMultiplier = '1' LimitTransfer = 21000 BumpMin = '5 gwei' BumpPercent = 20 -BumpThreshold = 0 -EIP1559DynamicFees = false +BumpThreshold = 3 +EIP1559DynamicFees = true FeeCapDefault = '100 gwei' -TipCapDefault = '1 wei' +TipCapDefault = '100 gwei' TipCapMin = '1 wei' [GasEstimator.BlockHistory] @@ -3002,6 +3716,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3011,20 +3727,19 @@ GasLimit = 5300000

-
Metis Mainnet (1088)

+

Simulated (1337)

```toml AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'metis' FinalityDepth = 1 FinalityTagEnabled = false LogBackfillBatchSize = 1000 LogPollInterval = '15s' LogKeepBlocksDepth = 100000 MinIncomingConfirmations = 1 -MinContractPayment = '0.00001 link' +MinContractPayment = '100' NonceAutoSync = true NoNewHeadsThreshold = '0s' RPCDefaultBatchSize = 250 @@ -3035,16 +3750,16 @@ ForwardersEnabled = false MaxInFlight = 16 MaxQueued = 250 ReaperInterval = '1h0m0s' -ReaperThreshold = '168h0m0s' -ResendAfterThreshold = '1m0s' +ReaperThreshold = '0s' +ResendAfterThreshold = '0s' [BalanceMonitor] Enabled = true [GasEstimator] -Mode = 'L2Suggested' +Mode = 'FixedPrice' PriceDefault = '20 gwei' -PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMax = '100 micro' PriceMin = '0' LimitDefault = 500000 LimitMax = 500000 @@ -3054,33 +3769,35 @@ BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 0 EIP1559DynamicFees = false -FeeCapDefault = '100 gwei' +FeeCapDefault = '100 micro' TipCapDefault = '1 wei' TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 0 +BlockHistorySize = 8 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 [HeadTracker] -HistoryDepth = 100 -MaxBufferSize = 3 -SamplingInterval = '1s' +HistoryDepth = 10 +MaxBufferSize = 100 +SamplingInterval = '0s' [NodePool] PollFailureThreshold = 5 PollInterval = '10s' SelectionMode = 'HighestHead' -SyncThreshold = 10 +SyncThreshold = 5 LeaseDuration = '0s' [OCR] ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3090,21 +3807,22 @@ GasLimit = 5300000

-
Simulated (1337)

+

Kroma Sepolia (2358)

```toml AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -FinalityDepth = 1 +ChainType = 'kroma' +FinalityDepth = 400 FinalityTagEnabled = false LogBackfillBatchSize = 1000 -LogPollInterval = '15s' +LogPollInterval = '2s' LogKeepBlocksDepth = 100000 MinIncomingConfirmations = 1 -MinContractPayment = '100' +MinContractPayment = '0.00001 link' NonceAutoSync = true -NoNewHeadsThreshold = '0s' +NoNewHeadsThreshold = '40s' RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 @@ -3113,52 +3831,54 @@ ForwardersEnabled = false MaxInFlight = 16 MaxQueued = 250 ReaperInterval = '1h0m0s' -ReaperThreshold = '0s' -ResendAfterThreshold = '0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '30s' [BalanceMonitor] Enabled = true [GasEstimator] -Mode = 'FixedPrice' +Mode = 'BlockHistory' PriceDefault = '20 gwei' -PriceMax = '100 micro' -PriceMin = '0' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 wei' LimitDefault = 500000 LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 -BumpMin = '5 gwei' +BumpMin = '100 wei' BumpPercent = 20 -BumpThreshold = 0 -EIP1559DynamicFees = false -FeeCapDefault = '100 micro' +BumpThreshold = 3 +EIP1559DynamicFees = true +FeeCapDefault = '100 gwei' TipCapDefault = '1 wei' TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 8 +BlockHistorySize = 24 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 [HeadTracker] -HistoryDepth = 10 -MaxBufferSize = 100 -SamplingInterval = '0s' +HistoryDepth = 400 +MaxBufferSize = 3 +SamplingInterval = '1s' [NodePool] PollFailureThreshold = 5 PollInterval = '10s' SelectionMode = 'HighestHead' -SyncThreshold = 5 +SyncThreshold = 10 LeaseDuration = '0s' [OCR] ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3199,8 +3919,8 @@ ResendAfterThreshold = '1m0s' Enabled = true [GasEstimator] -Mode = 'BlockHistory' -PriceDefault = '15 gwei' +Mode = 'SuggestedPrice' +PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' PriceMin = '1 gwei' LimitDefault = 500000 @@ -3238,6 +3958,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3277,7 +3999,7 @@ ResendAfterThreshold = '1m0s' Enabled = true [GasEstimator] -Mode = 'L2Suggested' +Mode = 'SuggestedPrice' PriceDefault = '750 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' PriceMin = '1 gwei' @@ -3316,6 +4038,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3395,6 +4119,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3475,6 +4201,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3554,6 +4282,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3633,6 +4363,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3712,6 +4444,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3791,6 +4525,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3869,6 +4605,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -3947,6 +4685,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4026,6 +4766,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4105,6 +4847,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4185,6 +4929,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4265,6 +5011,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4344,6 +5092,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4383,7 +5133,7 @@ ResendAfterThreshold = '1m0s' Enabled = true [GasEstimator] -Mode = 'L2Suggested' +Mode = 'SuggestedPrice' PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' PriceMin = '0' @@ -4422,6 +5172,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4461,7 +5213,7 @@ ResendAfterThreshold = '1m0s' Enabled = true [GasEstimator] -Mode = 'L2Suggested' +Mode = 'SuggestedPrice' PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' PriceMin = '0' @@ -4500,6 +5252,8 @@ LeaseDuration = '0s' ContractConfirmations = 1 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4517,7 +5271,7 @@ BlockBackfillDepth = 10 BlockBackfillSkip = false FinalityDepth = 50 FinalityTagEnabled = false -LinkContractAddress = '0xb227f007804c16546Bd054dfED2E7A1fD5437678' +LinkContractAddress = '0x779877A7B0D9E8603169DdbD7836e478b4624789' LogBackfillBatchSize = 1000 LogPollInterval = '15s' LogKeepBlocksDepth = 100000 @@ -4579,6 +5333,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4658,6 +5414,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4737,6 +5495,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [OCR2] @@ -4783,7 +5543,7 @@ BlockBackfillSkip enables skipping of very long backfills. ChainType = 'arbitrum' # Example ``` ChainType is automatically detected from chain ID. Set this to force a certain chain type regardless of chain ID. -Available types: arbitrum, metis, optimismBedrock, xdai +Available types: arbitrum, metis, optimismBedrock, xdai, celo, kroma, wemix, zksync ### FinalityDepth ```toml @@ -5004,7 +5764,8 @@ Mode controls what type of gas estimator is used. - `FixedPrice` uses static configured values for gas price (can be set via API call). - `BlockHistory` dynamically adjusts default gas price based on heuristics from mined blocks. -- `L2Suggested` is a special mode only for use with L2 blockchains. This mode will use the gas price suggested by the rpc endpoint via `eth_gasPrice`. +- `L2Suggested` mode is deprecated and replaced with `SuggestedPrice`. +- `SuggestedPrice` is a mode which uses the gas price suggested by the rpc endpoint via `eth_gasPrice`. - `Arbitrum` is a special mode only for use with Arbitrum blockchains. It uses the suggested gas price (up to `ETH_MAX_GAS_PRICE_WEI`, with `1000 gwei` default) as well as an estimated gas limit (up to `ETH_GAS_LIMIT_MAX`, with `1,000,000,000` default). Chainlink nodes decide what gas price to use using an `Estimator`. It ships with several simple and battle-hardened built-in estimators that should work well for almost all use-cases. Note that estimators will change their behaviour slightly depending on if you are in EIP-1559 mode or not. @@ -5410,6 +6171,8 @@ Set to '0s' to disable ContractConfirmations = 4 # Default ContractTransmitterTransmitTimeout = '10s' # Default DatabaseTimeout = '10s' # Default +DeltaCOverride = "168h" # Default +DeltaCJitterOverride = "1h" # Default ObservationGracePeriod = '1s' # Default ``` @@ -5432,6 +6195,22 @@ DatabaseTimeout = '10s' # Default ``` DatabaseTimeout sets `OCR.DatabaseTimeout` for this EVM chain. +### DeltaCOverride +:warning: **_ADVANCED_**: _Do not change this setting unless you know what you are doing._ +```toml +DeltaCOverride = "168h" # Default +``` +DeltaCOverride (and `DeltaCJitterOverride`) determine the config override DeltaC. +DeltaC is the maximum age of the latest report in the contract. If the maximum age is exceeded, a new report will be +created by the report generation protocol. + +### DeltaCJitterOverride +:warning: **_ADVANCED_**: _Do not change this setting unless you know what you are doing._ +```toml +DeltaCJitterOverride = "1h" # Default +``` +DeltaCJitterOverride is the range for jitter to add to `DeltaCOverride`. + ### ObservationGracePeriod ```toml ObservationGracePeriod = '1s' # Default diff --git a/docs/SECRETS.md b/docs/SECRETS.md index af316cab14b..fa7ba76df42 100644 --- a/docs/SECRETS.md +++ b/docs/SECRETS.md @@ -51,6 +51,33 @@ AllowSimplePasswords skips the password complexity check normally enforced on UR Environment variable: `CL_DATABASE_ALLOW_SIMPLE_PASSWORDS` +## WebServer.LDAP +```toml +[WebServer.LDAP] +ServerAddress = 'ldaps://127.0.0.1' # Example +ReadOnlyUserLogin = 'viewer@example.com' # Example +ReadOnlyUserPass = 'password' # Example +``` +Optional LDAP config + +### ServerAddress +```toml +ServerAddress = 'ldaps://127.0.0.1' # Example +``` +ServerAddress is the full ldaps:// address of the ldap server to authenticate with and query + +### ReadOnlyUserLogin +```toml +ReadOnlyUserLogin = 'viewer@example.com' # Example +``` +ReadOnlyUserLogin is the username of the read only root user used to authenticate the requested LDAP queries + +### ReadOnlyUserPass +```toml +ReadOnlyUserPass = 'password' # Example +``` +ReadOnlyUserPass is the password for the above account + ## Password ```toml [Password] diff --git a/go.mod b/go.mod index df970160acc..0343f4e7159 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,20 @@ module github.com/smartcontractkit/chainlink/v2 -go 1.21 +go 1.21.3 require ( - github.com/CosmWasm/wasmd v0.40.1 github.com/Depado/ginprom v1.7.11 github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/sprig/v3 v3.2.3 - github.com/avast/retry-go/v4 v4.5.0 + github.com/avast/retry-go/v4 v4.5.1 github.com/btcsuite/btcd v0.23.4 github.com/cometbft/cometbft v0.37.2 github.com/cosmos/cosmos-sdk v0.47.4 github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e github.com/esote/minmaxheap v1.0.0 github.com/ethereum/go-ethereum v1.12.0 - github.com/fatih/color v1.15.0 - github.com/fxamacker/cbor/v2 v2.4.0 + github.com/fatih/color v1.16.0 + github.com/fxamacker/cbor/v2 v2.5.0 github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27 github.com/getsentry/sentry-go v0.19.0 github.com/gin-contrib/cors v1.4.0 @@ -23,13 +22,12 @@ require ( github.com/gin-contrib/sessions v0.0.5 github.com/gin-contrib/size v0.0.0-20230212012657-e14a14094dc4 github.com/gin-gonic/gin v1.9.1 - github.com/go-webauthn/webauthn v0.8.2 - github.com/gogo/protobuf v1.3.3 - github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 - github.com/google/uuid v1.3.1 - github.com/gorilla/securecookie v1.1.1 - github.com/gorilla/sessions v1.2.1 - github.com/gorilla/websocket v1.5.0 + github.com/go-webauthn/webauthn v0.9.1 + github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 + github.com/google/uuid v1.4.0 + github.com/gorilla/securecookie v1.1.2 + github.com/gorilla/sessions v1.2.2 + github.com/gorilla/websocket v1.5.1 github.com/grafana/pyroscope-go v1.0.4 github.com/graph-gophers/dataloader v5.0.0+incompatible github.com/graph-gophers/graphql-go v1.3.0 @@ -51,37 +49,37 @@ require ( github.com/mr-tron/base58 v1.2.0 github.com/multiformats/go-multiaddr v0.3.3 github.com/olekukonko/tablewriter v0.0.5 - github.com/onsi/gomega v1.27.8 + github.com/onsi/gomega v1.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml/v2 v2.1.0 github.com/pkg/errors v0.9.1 - github.com/pressly/goose/v3 v3.15.1 + github.com/pressly/goose/v3 v3.16.0 github.com/prometheus/client_golang v1.17.0 github.com/prometheus/client_model v0.5.0 - github.com/prometheus/common v0.44.0 - github.com/prometheus/prometheus v0.46.0 + github.com/prometheus/common v0.45.0 + github.com/prometheus/prometheus v0.48.0 github.com/robfig/cron/v3 v3.0.1 github.com/rogpeppe/go-internal v1.11.0 github.com/scylladb/go-reflectx v1.0.1 - github.com/shirou/gopsutil/v3 v3.23.9 + github.com/shirou/gopsutil/v3 v3.23.10 github.com/shopspring/decimal v1.3.1 github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 - github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20230913032705-f924d753cc47 - github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231020230319-2ede955d1dc9 - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231023133638-72f4e799ab05 - github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231024133459-1ef3a11319eb - github.com/smartcontractkit/libocr v0.0.0-20231020123319-d255366a6545 - github.com/smartcontractkit/ocr2keepers v0.7.27 - github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687 - github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb + github.com/smartcontractkit/chainlink-automation v1.0.1 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20231201152724-d550085eb3c4 + github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231117191236-12eab01a4542 + github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231129183458-faee879168b3 + github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231117204155-b253a2f56664 + github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 + github.com/smartcontractkit/libocr v0.0.0-20231107151413-13e0202ae8d7 github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 github.com/smartcontractkit/wsrpc v0.7.2 github.com/spf13/cast v1.5.1 github.com/stretchr/testify v1.8.4 github.com/theodesp/go-heaps v0.0.0-20190520121037-88e35354fe0a - github.com/tidwall/gjson v1.16.0 + github.com/tidwall/gjson v1.17.0 github.com/ugorji/go/codec v1.2.11 github.com/ulule/limiter/v3 v3.11.2 github.com/umbracle/ethgo v0.1.3 @@ -91,14 +89,15 @@ require ( go.dedis.ch/kyber/v3 v3.1.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.14.0 - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 - golang.org/x/sync v0.4.0 - golang.org/x/term v0.13.0 - golang.org/x/text v0.13.0 - golang.org/x/time v0.3.0 - golang.org/x/tools v0.14.0 - gonum.org/v1/gonum v0.13.0 + golang.org/x/crypto v0.16.0 + golang.org/x/exp v0.0.0-20231127185646-65229373498e + golang.org/x/sync v0.5.0 + golang.org/x/term v0.15.0 + golang.org/x/text v0.14.0 + golang.org/x/time v0.5.0 + golang.org/x/tools v0.16.0 + gonum.org/v1/gonum v0.14.0 + google.golang.org/grpc v1.59.0 google.golang.org/protobuf v1.31.0 gopkg.in/guregu/null.v2 v2.1.2 gopkg.in/guregu/null.v4 v4.0.0 @@ -106,6 +105,7 @@ require ( ) require ( + cloud.google.com/go/compute v1.23.3 // indirect contrib.go.opencensus.io/exporter/stackdriver v0.13.5 // indirect cosmossdk.io/api v0.3.1 // indirect cosmossdk.io/core v0.5.1 // indirect @@ -115,7 +115,9 @@ require ( filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect + github.com/CosmWasm/wasmd v0.40.1 // indirect github.com/CosmWasm/wasmvm v1.2.4 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -143,7 +145,7 @@ require ( github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect - github.com/cosmos/gogoproto v1.4.10 // indirect + github.com/cosmos/gogoproto v1.4.11 // indirect github.com/cosmos/iavl v0.20.0 // indirect github.com/cosmos/ibc-go/v7 v7.0.1 // indirect github.com/cosmos/ics23/go v0.9.1-0.20221207100636-b1abd8678aab // indirect @@ -169,30 +171,33 @@ require ( github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect + github.com/go-ldap/ldap/v3 v3.4.5 github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/go-webauthn/revoke v0.1.9 // indirect + github.com/go-webauthn/x v0.1.4 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/uuid v4.3.1+incompatible // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/glog v1.1.0 // indirect + github.com/gogo/protobuf v1.3.3 // indirect + github.com/golang-jwt/jwt/v5 v5.1.0 // indirect + github.com/golang/glog v1.1.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/go-tpm v0.3.3 // indirect + github.com/google/go-tpm v0.9.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/gorilla/context v1.1.1 // indirect @@ -234,9 +239,9 @@ require ( 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.5 // indirect + github.com/jmoiron/sqlx v1.3.5 github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.0 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/koron/go-ssdp v0.0.2 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -281,9 +286,9 @@ require ( github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect github.com/minio/sha256-simd v0.1.1 // indirect @@ -315,6 +320,7 @@ require ( github.com/rivo/uniseg v0.4.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect + github.com/sethvargo/go-retry v0.2.4 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect @@ -349,22 +355,24 @@ require ( go.dedis.ch/protobuf v1.0.11 // indirect go.etcd.io/bbolt v1.3.7 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/sdk v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect + go.opentelemetry.io/otel v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect + go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/sdk v1.21.0 // indirect + go.opentelemetry.io/otel/trace v1.21.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect - golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 // indirect - google.golang.org/grpc v1.58.3 // indirect + golang.org/x/arch v0.6.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 59286787c2e..4bd90da600d 100644 --- a/go.sum +++ b/go.sum @@ -18,23 +18,23 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb 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.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= -cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.9 h1:e7ITSqGFFk4rbz/JFIqZh3G4VEHguhAL4BQcFlWtU68= +cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM= 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/compute v1.22.0 h1:cB8R6FtUtT1TYGl5R3xuxnW6OUIc/DrT2aiR16TTG7Y= -cloud.google.com/go/compute v1.22.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 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/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.4 h1:K6n/GZHFTtEoKT5aUG3l9diPi0VduZNQ1PfdnpkkIFk= +cloud.google.com/go/iam v1.1.4/go.mod h1:l/rg8l1AaA+VFMho/HYx2Vv6xinPSLMF8qfhRPIZ0L8= 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= @@ -79,12 +79,18 @@ github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOv github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 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/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= +github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= +github.com/ClickHouse/clickhouse-go/v2 v2.15.0 h1:G0hTKyO8fXXR1bGnZ0DY3vTG01xYfOGW76zgjg5tmC4= +github.com/ClickHouse/clickhouse-go/v2 v2.15.0/go.mod h1:kXt1SRq0PIRa6aKZD7TnFnY9PQKmc2b13sHtOYcK6cQ= 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/wasmd v0.40.1 h1:LxbO78t/6S8TkeQlUrJ0m5O87HtAwLx4RGHq3rdrOEU= @@ -129,11 +135,15 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy 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/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4= github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= @@ -143,12 +153,12 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/avast/retry-go/v4 v4.5.0 h1:QoRAZZ90cj5oni2Lsgl2GW8mNTnUCnmpx/iKpwVisHg= -github.com/avast/retry-go/v4 v4.5.0/go.mod h1:7hLEXp0oku2Nir2xBAsg0PTphp9z71bN5Aq1fboC3+I= +github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o= +github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc= 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.44.302 h1:ST3ko6GrJKn3Xi+nAvxjG3uk/V1pW8KC52WLeIxqqNk= -github.com/aws/aws-sdk-go v1.44.302/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= +github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= 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= @@ -241,8 +251,8 @@ github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0 github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4= github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= -github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= -github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= +github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= 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= @@ -264,8 +274,8 @@ github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= -github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI= -github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= +github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= +github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= github.com/cosmos/iavl v0.20.0 h1:fTVznVlepH0KK8NyKq8w+U7c2L6jofa27aFX6YGlm38= github.com/cosmos/iavl v0.20.0/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= github.com/cosmos/ibc-go/v7 v7.0.1 h1:NIBNRWjlOoFvFQu1ZlgwkaSeHO5avf4C1YQiWegt8jw= @@ -328,8 +338,12 @@ 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/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= +github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 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-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -340,6 +354,10 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/elastic/go-sysinfo v1.11.1 h1:g9mwl05njS4r69TisC+vwHWTSKywZFYYUu3so3T/Lao= +github.com/elastic/go-sysinfo v1.11.1/go.mod h1:6KQb31j0QeWBDF88jIdWSxE8cwoOB9tO4Y4osN7Q70E= +github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0= +github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss= 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= @@ -364,8 +382,8 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod 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.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -382,8 +400,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= -github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gagliardetto/binary v0.6.1/go.mod h1:aOfYkc20U0deHaHn/LVZXiqlkDbFAX0FpTlDhsXa0S0= @@ -419,10 +437,16 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= +github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= +github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= 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= @@ -433,14 +457,16 @@ github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEai github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= +github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= 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-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= @@ -467,10 +493,10 @@ github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/go-webauthn/revoke v0.1.9 h1:gSJ1ckA9VaKA2GN4Ukp+kiGTk1/EXtaDb1YE8RknbS0= -github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w= -github.com/go-webauthn/webauthn v0.8.2 h1:8KLIbpldjz9KVGHfqEgJNbkhd7bbRXhNw4QWFJE15oA= -github.com/go-webauthn/webauthn v0.8.2/go.mod h1:d+ezx/jMCNDiqSMzOchuynKb9CVU1NM9BumOnokfcVQ= +github.com/go-webauthn/webauthn v0.9.1 h1:KuZjvUX9JTuFjB2n7kZhM6n76BClLUFbFM8SLKnrXpo= +github.com/go-webauthn/webauthn v0.9.1/go.mod h1:m315kRGbUljOytw8b9FGWG9QzErjI5v02pNFCF3lwpI= +github.com/go-webauthn/x v0.1.4 h1:sGmIFhcY70l6k7JIDfnjVBiAAFEssga5lXIUXe0GtAs= +github.com/go-webauthn/x v0.1.4/go.mod h1:75Ug0oK6KYpANh5hDOanfDI+dvPWHk788naJVG/37H8= 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= @@ -488,12 +514,15 @@ github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q8 github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= 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= @@ -551,17 +580,14 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI= -github.com/google/go-tpm v0.3.0/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw= -github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo= -github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51BeghL4= -github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0= -github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4= +github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -586,18 +612,20 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +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.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.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= -github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= 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.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= @@ -611,15 +639,14 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grafana/pyroscope-go v1.0.4 h1:oyQX0BOkL+iARXzHuCdIF5TQ7/sRSel1YFViMHC7Bm0= github.com/grafana/pyroscope-go v1.0.4/go.mod h1:0d7ftwSMBV/Awm7CCiYmHQEG8Y44Ma3YSjt+nWcWztY= github.com/grafana/pyroscope-go/godeltaprof v0.1.4 h1:mDsJ3ngul7UfrHibGQpV66PbZ3q1T8glz/tK3bQKKEk= @@ -827,10 +854,15 @@ github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQ github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw= +github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= 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.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 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 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -855,9 +887,13 @@ github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= 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= @@ -886,8 +922,8 @@ github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= @@ -1163,8 +1199,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -1173,8 +1209,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 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.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= @@ -1216,6 +1252,8 @@ github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8oh 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/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 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= @@ -1313,8 +1351,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= 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= @@ -1323,25 +1361,29 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 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.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM= -github.com/opencontainers/runc v1.1.9/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/opencontainers/runc v1.1.10 h1:EaL5WeO9lv9wmS6SASjszOeQdSctvpbu0DdBQBizE40= +github.com/opencontainers/runc v1.1.10/go.mod h1:+/R6+KmDlh+hOO8NkjmgkG9Qzvypzk0yXxAPYYR65+M= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= +github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -1351,6 +1393,8 @@ github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdU github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -1365,8 +1409,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 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/pressly/goose/v3 v3.15.1 h1:dKaJ1SdLvS/+HtS8PzFT0KBEtICC1jewLXM+b3emlv8= -github.com/pressly/goose/v3 v3.15.1/go.mod h1:0E3Yg/+EwYzO6Rz2P98MlClFgIcoujbVRs575yi3iIM= +github.com/pressly/goose/v3 v3.16.0 h1:xMJUsZdHLqSnCqESyKSqEfcYVYsUuup1nrOhaEFftQg= +github.com/pressly/goose/v3 v3.16.0/go.mod h1:JwdKVnmCRhnF6XLQs2mHEQtucFD49cQBdRM4UiwkxsM= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 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= @@ -1383,16 +1427,16 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q 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.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/prometheus/prometheus v0.46.0 h1:9JSdXnsuT6YsbODEhSQMwxNkGwPExfmzqG73vCMk/Kw= -github.com/prometheus/prometheus v0.46.0/go.mod h1:10L5IJE5CEsjee1FnOcVswYXlPIscDWWt3IJ2UDYrz4= +github.com/prometheus/prometheus v0.48.0 h1:yrBloImGQ7je4h8M10ujGh4R6oxYQJQKlMuETwNskGk= +github.com/prometheus/prometheus v0.48.0/go.mod h1:SRw624aMAxTfryAcP8rOjg4S/sHHaetx2lyJJ2nM83g= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= @@ -1438,11 +1482,15 @@ github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtm 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= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= +github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= 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.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= -github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= +github.com/shirou/gopsutil/v3 v3.23.10 h1:/N42opWlYzegYaVkWejXWJpbzKv2JDy3mrgGzKsh9hM= +github.com/shirou/gopsutil/v3 v3.23.10/go.mod h1:JIE26kpucQi+innVlAUnIEOSBhBUkirr5b44yr55+WE= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -1457,26 +1505,26 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 h1:T3lFWumvbfM1u/etVq42Afwq/jtNSBSOA8n5jntnNPo= github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704/go.mod h1:2QuJdEouTWjh5BDy5o/vgGXQtR4Gz8yH1IYB5eT7u4M= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20230913032705-f924d753cc47 h1:vdieOW3CZGdD2R5zvCSMS+0vksyExPN3/Fa1uVfld/A= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20230913032705-f924d753cc47/go.mod h1:xMwqRdj5vqYhCJXgKVqvyAwdcqM6ZAEhnwEQ4Khsop8= -github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231020230319-2ede955d1dc9 h1:fFD5SgSJtnXvkGLK3CExNKpUIz4sGrNNkKv3Ljw63Hk= -github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231020230319-2ede955d1dc9/go.mod h1:M9U1JV7IQi8Sfj4JR1qSi1tIh6omgW78W/8SHN/8BUQ= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231023133638-72f4e799ab05 h1:DaPSVnxe7oz1QJ+AVIhQWs1W3ubQvwvGo9NbHpMs1OQ= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231023133638-72f4e799ab05/go.mod h1:o0Pn1pbaUluboaK6/yhf8xf7TiFCkyFl6WUOdwqamuU= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231024133459-1ef3a11319eb h1:HiluOfEVGOQTM6BTDImOqYdMZZ7qq7fkZ3TJdmItNr8= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231024133459-1ef3a11319eb/go.mod h1:/30flFG4L/iCYAFeA3DUzR0xuHSxAMONiWTzyzvsNwo= +github.com/smartcontractkit/chainlink-automation v1.0.1 h1:vVjBFq2Zsz21kPy1Pb0wpjF9zrbJX+zjXphDeeR4XZk= +github.com/smartcontractkit/chainlink-automation v1.0.1/go.mod h1:INSchkV3ntyDdlZKGWA030MPDpp6pbeuiRkRKYFCm2k= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20231201152724-d550085eb3c4 h1:qau0/AHvPwMR3p6gWsFWC4qVfEtSEALtBetTOpHA2IU= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20231201152724-d550085eb3c4/go.mod h1:Hrru9i7n+WEYyW2aIt3/YGPhxLX+HEGWnhk3yVXeDF8= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231117191236-12eab01a4542 h1:oewYJtdRkJKUHCNDCj5C2LQe6Oq6qy975g931nfG0cc= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231117191236-12eab01a4542/go.mod h1:EpvRoycRD+kniYlz+pCpRT5e+fmPm0mSD/vmND+0oMg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d h1:w4MsbOtNk6nD/mcXLstHWk9hB6g7QLtcAfhPjhwvOaQ= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d/go.mod h1:YPAfLNowdBwiKiYOwgwtbJHi8AJWbcxkbOY0ItAvkfc= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231129183458-faee879168b3 h1:DudPr8ZNMEVgDwHBvnrpvK96JrGcGCG+LLBnlqaPGnE= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231129183458-faee879168b3/go.mod h1:UfW7/PZKon+iDEHtrHOfvMnS5GfYOW/SdMZ6P97rPEk= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231117204155-b253a2f56664 h1:yxaHuDTtj2xxtsR8b+LJw8xDvyr6v/F6GP3InsP4wPI= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231117204155-b253a2f56664/go.mod h1:3Fa+HQTZ3R3fPC0hUqugvoo+NEeo8Y4J2WOnQfi7O34= +github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 h1:FFdvEzlYwcuVHkdZ8YnZR/XomeMGbz5E2F2HZI3I3w8= +github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868/go.mod h1:Kn1Hape05UzFZ7bOUnm3GVsHzP0TNrVmpfXYNHdqGGs= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 h1:ko88+ZznniNJZbZPWAvHQU8SwKAdHngdDZ+pvVgB5ss= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJif132UCdjo8u43i7iPN1/MFnu49hv7lFGFftCHKU= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f/go.mod h1:MvMXoufZAtqExNexqi4cjrNYE9MefKddKylxjS+//n0= -github.com/smartcontractkit/libocr v0.0.0-20231020123319-d255366a6545 h1:qOsw2ETQD/Sb/W2xuYn2KPWjvvsWA0C+l19rWFq8iNg= -github.com/smartcontractkit/libocr v0.0.0-20231020123319-d255366a6545/go.mod h1:2lyRkw/qLQgUWlrWWmq5nj0y90rWeO6Y+v+fCakRgb0= -github.com/smartcontractkit/ocr2keepers v0.7.27 h1:kwqMrzmEdq6gH4yqNuLQCbdlED0KaIjwZzu3FF+Gves= -github.com/smartcontractkit/ocr2keepers v0.7.27/go.mod h1:1QGzJURnoWpysguPowOe2bshV0hNp1YX10HHlhDEsas= -github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687 h1:NwC3SOc25noBTe1KUQjt45fyTIuInhoE2UfgcHAdihM= -github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687/go.mod h1:YYZq52t4wcHoMQeITksYsorD+tZcOyuVU5+lvot3VFM= -github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb h1:OMaBUb4X9IFPLbGbCHsMU+kw/BPCrewaVwWGIBc0I4A= -github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb/go.mod h1:HNUu4cJekUdsJbwRBCiOybtkPJEfGRELQPe2tkoDEyk= +github.com/smartcontractkit/libocr v0.0.0-20231107151413-13e0202ae8d7 h1:21V61XOYSxpFmFqlhr5IaEh1uQ1F6CewJ30D/U/P34c= +github.com/smartcontractkit/libocr v0.0.0-20231107151413-13e0202ae8d7/go.mod h1:2lyRkw/qLQgUWlrWWmq5nj0y90rWeO6Y+v+fCakRgb0= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 h1:yiKnypAqP8l0OX0P3klzZ7SCcBUxy5KqTAKZmQOvSQE= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:q6f4fe39oZPdsh1i57WznEZgxd8siidMaSFq3wdPmVg= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 h1:Dai1bn+Q5cpeGMQwRdjOdVjG8mmFFROVkSKuUgBErRQ= @@ -1501,7 +1549,6 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -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.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= @@ -1512,7 +1559,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn 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.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= @@ -1559,8 +1605,8 @@ github.com/theodesp/go-heaps v0.0.0-20190520121037-88e35354fe0a/go.mod h1:/sfW47 github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg= -github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= @@ -1605,6 +1651,8 @@ github.com/valyala/fastjson v1.4.1/go.mod h1:nV6MsjxL2IMJQUoHDIrjEI7oLyeqK6aBD7E 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/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw= +github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= 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= @@ -1617,13 +1665,21 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7V github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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 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/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd h1:dzWP1Lu+A40W883dK/Mr3xyDSM/2MggS8GtHT0qgAnE= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= +github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2 h1:E0yUuuX7UmPxXm92+yQCjMveLFO3zfvYFIJVuAqsVRA= +github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2/go.mod h1:fjBLQ2TdQNl4bMjuWl9adoTGBypwUTPoGC+EqYqiIcU= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= @@ -1662,20 +1718,20 @@ 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.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 h1:RsQi0qJ2imFfCvZabqzM9cNXBG8k6gXMv1A0cXRmH6A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0/go.mod h1:G17FHPDLt74bCI7tJ4CMitEk4BXTYG4FW6XUpkPBXa4= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1687,8 +1743,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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= @@ -1709,8 +1765,8 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= +golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1748,8 +1804,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/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= @@ -1760,8 +1817,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 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-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= +golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1788,8 +1845,9 @@ 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -1807,7 +1865,6 @@ golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn 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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1850,8 +1907,9 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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= @@ -1862,8 +1920,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ 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-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= 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= @@ -1876,8 +1934,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -1948,7 +2007,6 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w 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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/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-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1971,17 +2029,18 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 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= @@ -1991,16 +2050,18 @@ 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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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= @@ -2068,8 +2129,9 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 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= @@ -2077,10 +2139,10 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= -gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= +gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= 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= @@ -2101,8 +2163,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 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.132.0 h1:8t2/+qZ26kAOGSmOiHwVycqVaDg7q3JDILrNi/Z6rvc= -google.golang.org/api v0.132.0/go.mod h1:AeTBC6GpJnJSRJjktDcPX0QwtS8pGYZOV6MSuSCusw0= +google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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= @@ -2110,8 +2172,9 @@ 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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 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= @@ -2157,16 +2220,15 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753 h1:+VoAg+OKmWaommL56xmZSE2sUK8A7m6SUO7X89F2tbw= -google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753/go.mod h1:iqkVr8IRpZ53gx1dEnWlCUIEwDWqWARWrbzpasaTNYM= -google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753 h1:lCbbUxUDD+DiXx9Q6F/ttL0aAu7N2pz8XnmMm8ZW4NE= -google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 h1:XUODHrpzJEUeWmVo/jfNTLj0YyVveOo28oE6vkFbkO4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U= +google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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= @@ -2188,8 +2250,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG 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.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc/examples v0.0.0-20210424002626-9572fd6faeae/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= 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= @@ -2262,22 +2324,24 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt 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.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= -modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= -modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= +modernc.org/libc v1.32.0 h1:yXatHTrACp3WaKNRCoZwUK7qj5V8ep1XyY0ka4oYcNc= +modernc.org/libc v1.32.0/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw= -modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8= +modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/integration-tests/.golangci.yml b/integration-tests/.golangci.yml new file mode 100644 index 00000000000..d22b26b8260 --- /dev/null +++ b/integration-tests/.golangci.yml @@ -0,0 +1,78 @@ +run: + timeout: 15m +linters: + enable: + - exhaustive + - exportloopref + - revive + - goimports + - gosec + - misspell + - rowserrcheck + - errorlint +linters-settings: + exhaustive: + default-signifies-exhaustive: true + goimports: + local-prefixes: github.com/smartcontractkit/chainlink + golint: + min-confidence: 0.999 + gosec: + excludes: + - G101 + govet: + # report about shadowed variables + check-shadowing: true + revive: + confidence: 0.8 + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: if-return + - name: increment-decrement + # - name: var-naming // doesn't work with some generated names + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + - name: unreachable-code + - name: redefines-builtin-id + - name: waitgroup-by-value + - name: unconditional-recursion + - name: struct-tag + - name: string-format + - name: string-of-int + - name: range-val-address + - name: range-val-in-closure + - name: modifies-value-receiver + - name: modifies-parameter + - name: identical-branches + - name: get-return + # - name: flag-parameter // probably one we should work on doing better at in the future + # - name: early-return // probably one we should work on doing better at in the future + - name: defer + - name: constant-logical-expr + - name: confusing-naming + - name: confusing-results + - name: bool-literal-in-expr + - name: atomic +issues: + exclude-rules: + - text: "^G404: Use of weak random number generator" + linters: + - gosec + - linters: + - govet + text: "declaration of \"err\" shadows" diff --git a/integration-tests/.tool-versions b/integration-tests/.tool-versions index 68b6d994197..47b73e9de11 100644 --- a/integration-tests/.tool-versions +++ b/integration-tests/.tool-versions @@ -1,4 +1,5 @@ -golang 1.21.1 +golang 1.21.4 k3d 5.4.6 kubectl 1.25.5 nodejs 18.13.0 +golangci-lint 1.55.2 diff --git a/integration-tests/LOG_POLLER.md b/integration-tests/LOG_POLLER.md new file mode 100644 index 00000000000..6e98fba5525 --- /dev/null +++ b/integration-tests/LOG_POLLER.md @@ -0,0 +1,163 @@ +# How to run Log Poller's tests + +## Limitations +* currently they can only be run in Docker, not in Kubernetes +* when using `looped` runner it's not possible to directly control execution time +* WASP's `gun` implementation is imperfect in terms of generated load + +## Configuration +Due to unfinished migration to TOML config tests use a mixed configuration approach: +* network, RPC endpoints, funding keys, etc need to be provided by env vars +* test-specific configuration can be provided by TOML file or via a `Config` struct (to which TOML is parsed anyway) additionally some of it can be overridden by env vars (for ease of use in CI) +** smoke tests use the programmatical approach +** load test uses the TOML approach + +## Approximated test scenario +Different tests might have slightly modified scenarios, but generally they follow this pattern: +* start CL nodes +* setup OCR +* upload Automation Registry 2.1 +* deploy UpKeep Consumers +* deploy test contracts +* register filters for test contracts +* make sure all CL nodes have filters registered +* emit test logs +* wait for log poller to finalise last block in which logs were emitted +** block number is determined either by finality tag or fixed finality depth depending on network configuration +* wait for all CL nodes to have expected log count +* compare logs that present in the EVM node with logs in CL nodes + +All of the checks use fluent waits. + +### Required env vars +* `CHAINLINK_IMAGE` +* `CHAINLINK_VERSION` +* `SELECTED_NETWORKS` + +### Env vars required for live testnet tests +* `EVM_WS_URL` -- RPC websocket +* `EVM_HTTP_URL` -- RPC HTTP +* `EVM_KEYS` -- private keys used for funding + +Since on live testnets we are using existing and canonical LINK contracts funding keys need to contain enough LINK to pay for the test. There's an automated check that fails during setup if there's not enough LINK. Approximately `9 LINK` is required for each UpKeep contract test uses to register a `LogTrigger`. Test contract emits 3 types of events and unless configured otherwise (programmatically!) all of them will be used, which means that due to Automation's limitation we need to register a separate `LogTrigger` for each event type for each contract. So if you want to test with 100 contracts, then you'd need to register 300 UpKeep contracts and thus your funding address needs to have at least 2700 LINK. + +### Programmatical config +There are two load generators available: +* `looped` -- it's a simple generator that just loops over all contracts and emits events at random intervals +* `wasp` -- based on WASP load testing tool, it's more sophisticated and allows to control execution time + +#### Looped config +``` + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 2, # number of test contracts to deploy + EventsPerTx: 4, # number of events to emit in a single transaction + UseFinalityTag: false, # if set to true then Log Poller will use finality tag returned by chain, when determining last finalised block (won't work on a simulated network, it requires eth2) + }, + LoopedConfig: &logpoller.LoopedConfig{ + ContractConfig: logpoller.ContractConfig{ + ExecutionCount: 100, # number of times each contract will be called + }, + FuzzConfig: logpoller.FuzzConfig{ + MinEmitWaitTimeMs: 200, # minimum number of milliseconds to wait before emitting events + MaxEmitWaitTimeMs: 500, # maximum number of milliseconds to wait before emitting events + }, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { # modify that function to emit only logs you want + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit +``` + +Remember that final number of events emitted will be `Contracts * EventsPerTx * ExecutionCount * len(eventToEmit)`. And that that last number by default is equal to `3` (that's because we want to emit different event types, not just one). You can change that by overriding `EventsToEmit` field. + +#### WASP config +``` + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 2, + EventsPerTx: 4, + UseFinalityTag: false, + }, + Wasp: &logpoller.WaspConfig{ + Load: &logpoller.Load{ + RPS: 10, # requests per second + LPS: 0, # logs per second + RateLimitUnitDuration: models.MustNewDuration(5 * time.Minutes), # for how long the load should be limited (ramp-up period) + Duration: models.MustNewDuration(5 * time.Minutes), # how long to generate the load for + CallTimeout: models.MustNewDuration(5 * time.Minutes), # how long to wait for a single call to finish + }, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit +``` + +Remember that you cannot specify both `RPS` and `LPS`. If you want to use `LPS` then omit `RPS` field. Also remember that depending on the events you decide to emit RPS might mean 1 request or might mean 3 requests (if you go with the default `EventsToEmit`). + +For other nuances do check [gun.go][integration-tests/universal/log_poller/gun.go]. + +### TOML config +That config follows the same structure as programmatical config shown above. + +Sample config: [config.toml](integration-tests/load/log_poller/config.toml) + +Use this snippet instead of creating the `Config` struct programmatically: +``` + cfg, err := lp_helpers.ReadConfig(lp_helpers.DefaultConfigFilename) + require.NoError(t, err) +``` + +And remember to add events you want emit: +``` + eventsToEmit := []abi.Event{} + for _, event := range lp_helpers.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit +``` + +### Timeouts +Various checks inside the tests have hardcoded timeouts, which might not be suitable for your execution parameters, for example if you decided to emit 1M logs, then waiting for all of them to be indexed for `1m` might not be enough. Remember to adjust them accordingly. + +Sample snippet: +``` + gom.Eventually(func(g gomega.Gomega) { + logCountMatches, err := clNodesHaveExpectedLogCount(startBlock, endBlock, testEnv.EVMClient.GetChainID(), totalLogsEmitted, expectedFilters, l, coreLogger, testEnv.ClCluster) + if err != nil { + l.Warn().Err(err).Msg("Error checking if CL nodes have expected log count. Retrying...") + } + g.Expect(logCountMatches).To(gomega.BeTrue(), "Not all CL nodes have expected log count") + }, "1m", "30s").Should(gomega.Succeed()) # 1m is the timeout for all nodes to have expected log count +``` + +## Tests +* [Load](integration-tests/load/log_poller/log_poller_test.go) +* [Smoke](integration-tests/smoke/log_poller/log_poller_test.go) + +## Running tests +After setting all the environment variables you can run the test with: +``` +# run in the root folder of chainlink repo +go test -v -test.timeout=2700s -run TestLogPollerReplay integration-tests/smoke/log_poller_test.go +``` + +Remember to adjust test timeout accordingly to match expected duration. + + +## Github Actions +If all of that seems too complicated use this [on-demand workflow](https://github.com/smartcontractkit/chainlink/actions/workflows/on-demand-log-poller.yml). + +Execution time here is an approximation, so depending on network conditions it might be slightly longer or shorter. \ No newline at end of file diff --git a/integration-tests/Makefile b/integration-tests/Makefile index f26518c0076..fb4bfa74f3e 100644 --- a/integration-tests/Makefile +++ b/integration-tests/Makefile @@ -56,6 +56,12 @@ install_gotestfmt: go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest set -euo pipefail +lint: + golangci-lint --color=always run ./... --fix -v + +build: + @go build ./... && SELECTED_NETWORKS=SIMULATED go test -run=^# ./... + # Builds the test image # tag: the tag for the test image being built, example: tag=tate # base_tag: the tag for the base-test-image to use, example: base_tag=latest @@ -118,7 +124,7 @@ test_chaos_verbose: ## Run all smoke tests with verbose logging # Performance .PHONY: test_perf -test_perf: test_need_operator_assets ## Run core node performance tests. +test_perf: ## Run core node performance tests. TEST_LOG_LEVEL="disabled" \ SELECTED_NETWORKS="SIMULATED,SIMULATED_1,SIMULATED_2" \ go test -timeout 1h -count=1 -json $(args) ./performance 2>&1 | tee /tmp/gotest.log | gotestfmt diff --git a/integration-tests/actions/actions.go b/integration-tests/actions/actions.go index dcdca91cc78..624df39c479 100644 --- a/integration-tests/actions/actions.go +++ b/integration-tests/actions/actions.go @@ -2,26 +2,29 @@ package actions import ( + "crypto/ecdsa" "encoding/json" "fmt" "math/big" "strings" "testing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" - "github.com/pkg/errors" "github.com/rs/zerolog/log" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink-env/environment" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/testreporters" - "github.com/smartcontractkit/chainlink-testing-framework/utils" + "github.com/smartcontractkit/chainlink-testing-framework/utils/conversions" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" @@ -46,7 +49,7 @@ func FundChainlinkNodes( msg := ethereum.CallMsg{ From: common.HexToAddress(client.GetDefaultWallet().Address()), To: &recipient, - Value: utils.EtherToWei(amount), + Value: conversions.EtherToWei(amount), } gasEstimates, err := client.EstimateGas(msg) if err != nil { @@ -252,7 +255,6 @@ func GetMockserverInitializerDataForOTPE( func TeardownSuite( t *testing.T, env *environment.Environment, - logsFolderPath string, chainlinkNodes []*client.ChainlinkK8sClient, optionalTestReporter testreporters.TestReporter, // Optionally pass in a test reporter to log further metrics failingLogLevel zapcore.Level, // Examines logs after the test, and fails the test if any Chainlink logs are found at or above provided level @@ -260,7 +262,7 @@ func TeardownSuite( ) error { l := logging.GetTestLogger(t) if err := testreporters.WriteTeardownLogs(t, env, optionalTestReporter, failingLogLevel); err != nil { - return errors.Wrap(err, "Error dumping environment logs, leaving environment running for manual retrieval") + return fmt.Errorf("Error dumping environment logs, leaving environment running for manual retrieval, err: %w", err) } // Delete all jobs to stop depleting the funds err := DeleteAllJobs(chainlinkNodes) @@ -328,16 +330,16 @@ func DeleteAllJobs(chainlinkNodes []*client.ChainlinkK8sClient) error { } jobs, _, err := node.ReadJobs() if err != nil { - return errors.Wrap(err, "error reading jobs from chainlink node") + return fmt.Errorf("error reading jobs from chainlink node, err: %w", err) } for _, maps := range jobs.Data { if _, ok := maps["id"]; !ok { - return errors.Errorf("error reading job id from chainlink node's jobs %+v", jobs.Data) + return fmt.Errorf("error reading job id from chainlink node's jobs %+v", jobs.Data) } id := maps["id"].(string) _, err := node.DeleteJob(id) if err != nil { - return errors.Wrap(err, "error deleting job from chainlink node") + return fmt.Errorf("error deleting job from chainlink node, err: %w", err) } } } @@ -348,7 +350,7 @@ func DeleteAllJobs(chainlinkNodes []*client.ChainlinkK8sClient) error { // all from a remote, k8s style environment func ReturnFunds(chainlinkNodes []*client.ChainlinkK8sClient, blockchainClient blockchain.EVMClient) error { if blockchainClient == nil { - return errors.New("blockchain client is nil, unable to return funds from chainlink nodes") + return fmt.Errorf("blockchain client is nil, unable to return funds from chainlink nodes") } log.Info().Msg("Attempting to return Chainlink node funds to default network wallets") if blockchainClient.NetworkSimulated() { @@ -414,7 +416,7 @@ func UpgradeChainlinkNodeVersions( nodes ...*client.ChainlinkK8sClient, ) error { if newImage == "" && newVersion == "" { - return errors.New("unable to upgrade node version, found empty image and version, must provide either a new image or a new version") + return fmt.Errorf("unable to upgrade node version, found empty image and version, must provide either a new image or a new version") } for _, node := range nodes { if err := node.UpgradeVersion(testEnvironment, newImage, newVersion); err != nil { @@ -443,3 +445,39 @@ func DeployMockETHLinkFeed(cd contracts.ContractDeployer, answer *big.Int) (cont } return mockETHLINKFeed, err } + +// todo - move to CTF +func GenerateWallet() (common.Address, error) { + privateKey, err := crypto.GenerateKey() + if err != nil { + return common.Address{}, err + } + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return common.Address{}, fmt.Errorf("cannot assert type: publicKey is not of type *ecdsa.PublicKey") + } + return crypto.PubkeyToAddress(*publicKeyECDSA), nil +} + +// todo - move to CTF +func FundAddress(client blockchain.EVMClient, sendingKey string, fundingToSendEth *big.Float) error { + address := common.HexToAddress(sendingKey) + gasEstimates, err := client.EstimateGas(ethereum.CallMsg{ + To: &address, + }) + if err != nil { + return err + } + err = client.Fund(sendingKey, fundingToSendEth, gasEstimates) + if err != nil { + return err + } + return nil +} + +// todo - move to CTF +func GetTxFromAddress(tx *types.Transaction) (string, error) { + from, err := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx) + return from.String(), err +} diff --git a/integration-tests/actions/actions_local.go b/integration-tests/actions/actions_local.go index b65bac43bb1..d4913cabd8a 100644 --- a/integration-tests/actions/actions_local.go +++ b/integration-tests/actions/actions_local.go @@ -2,7 +2,8 @@ package actions import ( - "github.com/pkg/errors" + "fmt" + "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" ) @@ -13,10 +14,10 @@ func UpgradeChainlinkNodeVersionsLocal( nodes ...*test_env.ClNode, ) error { if newImage == "" && newVersion == "" { - return errors.New("unable to upgrade node version, found empty image and version, must provide either a new image or a new version") + return fmt.Errorf("unable to upgrade node version, found empty image and version, must provide either a new image or a new version") } for _, node := range nodes { - if err := node.UpgradeVersion(node.NodeConfig, newImage, newVersion); err != nil { + if err := node.UpgradeVersion(newImage, newVersion); err != nil { return err } } diff --git a/integration-tests/actions/automation_ocr_helpers.go b/integration-tests/actions/automation_ocr_helpers.go index 998b1ee89cf..38df2e00b5c 100644 --- a/integration-tests/actions/automation_ocr_helpers.go +++ b/integration-tests/actions/automation_ocr_helpers.go @@ -14,13 +14,15 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-testing-framework/blockchain" - "github.com/smartcontractkit/chainlink-testing-framework/logging" ocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" ocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - ocr2keepers20config "github.com/smartcontractkit/ocr2keepers/pkg/v2/config" - ocr2keepers30config "github.com/smartcontractkit/ocr2keepers/pkg/v3/config" + + ocr2keepers20config "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" + ocr2keepers30config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" + + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" diff --git a/integration-tests/actions/automation_ocr_helpers_local.go b/integration-tests/actions/automation_ocr_helpers_local.go index ccc2eea99d8..e591e75a983 100644 --- a/integration-tests/actions/automation_ocr_helpers_local.go +++ b/integration-tests/actions/automation_ocr_helpers_local.go @@ -8,14 +8,15 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/lib/pq" - "github.com/pkg/errors" "github.com/rs/zerolog" + "gopkg.in/guregu/null.v4" + ocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" ocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - ocr2keepers20config "github.com/smartcontractkit/ocr2keepers/pkg/v2/config" - ocr2keepers30config "github.com/smartcontractkit/ocr2keepers/pkg/v3/config" - "gopkg.in/guregu/null.v4" + + ocr2keepers20config "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" + ocr2keepers30config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" @@ -187,7 +188,7 @@ func CreateOCRKeeperJobsLocal( } else if registryVersion == ethereum.RegistryVersion_2_0 { contractVersion = "v2.0" } else { - return errors.New("v2.0 and v2.1 are the only supported versions") + return fmt.Errorf("v2.0 and v2.1 are the only supported versions") } bootstrapSpec := &client.OCR2TaskJobSpec{ diff --git a/integration-tests/actions/automationv2/actions.go b/integration-tests/actions/automationv2/actions.go new file mode 100644 index 00000000000..febdd892150 --- /dev/null +++ b/integration-tests/actions/automationv2/actions.go @@ -0,0 +1,722 @@ +package automationv2 + +import ( + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/lib/pq" + "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" + ocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" + ocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + "gopkg.in/guregu/null.v4" + + ocr2keepers20config "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" + ocr2keepers30config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" + + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink/integration-tests/client" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" + "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_registrar_wrapper2_1" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registrar_wrapper2_0" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/store/models" + "github.com/smartcontractkit/chainlink/v2/core/utils" + + ctfTestEnv "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" +) + +type NodeDetails struct { + P2PId string + TransmitterAddresses []string + OCR2ConfigPublicKey string + OCR2OffchainPublicKey string + OCR2OnChainPublicKey string + OCR2Id string +} + +type AutomationTest struct { + ChainClient blockchain.EVMClient + Deployer contracts.ContractDeployer + + LinkToken contracts.LinkToken + Transcoder contracts.UpkeepTranscoder + EthLinkFeed contracts.MockETHLINKFeed + GasFeed contracts.MockGasFeed + Registry contracts.KeeperRegistry + Registrar contracts.KeeperRegistrar + + RegistrySettings contracts.KeeperRegistrySettings + RegistrarSettings contracts.KeeperRegistrarSettings + PluginConfig ocr2keepers30config.OffchainConfig + PublicConfig ocr3.PublicConfig + UpkeepPrivilegeManager common.Address + UpkeepIDs []*big.Int + + IsOnk8s bool + + ChainlinkNodesk8s []*client.ChainlinkK8sClient + ChainlinkNodes []*client.ChainlinkClient + + DockerEnv *test_env.CLClusterTestEnv + + NodeDetails []NodeDetails + DefaultP2Pv2Bootstrapper string + MercuryCredentialName string + TransmitterKeyIndex int +} + +type UpkeepConfig struct { + UpkeepName string + EncryptedEmail []byte + UpkeepContract common.Address + GasLimit uint32 + AdminAddress common.Address + TriggerType uint8 + CheckData []byte + TriggerConfig []byte + OffchainConfig []byte + FundingAmount *big.Int +} + +func NewAutomationTestK8s( + chainClient blockchain.EVMClient, + deployer contracts.ContractDeployer, + chainlinkNodes []*client.ChainlinkK8sClient, +) *AutomationTest { + return &AutomationTest{ + ChainClient: chainClient, + Deployer: deployer, + ChainlinkNodesk8s: chainlinkNodes, + IsOnk8s: true, + TransmitterKeyIndex: 0, + UpkeepPrivilegeManager: common.HexToAddress(chainClient.GetDefaultWallet().Address()), + } +} + +func NewAutomationTestDocker( + chainClient blockchain.EVMClient, + deployer contracts.ContractDeployer, + chainlinkNodes []*client.ChainlinkClient, +) *AutomationTest { + return &AutomationTest{ + ChainClient: chainClient, + Deployer: deployer, + ChainlinkNodes: chainlinkNodes, + IsOnk8s: false, + TransmitterKeyIndex: 0, + UpkeepPrivilegeManager: common.HexToAddress(chainClient.GetDefaultWallet().Address()), + } +} + +func (a *AutomationTest) SetIsOnk8s(flag bool) { + a.IsOnk8s = flag +} + +func (a *AutomationTest) SetMercuryCredentialName(name string) { + a.MercuryCredentialName = name +} + +func (a *AutomationTest) SetTransmitterKeyIndex(index int) { + a.TransmitterKeyIndex = index +} + +func (a *AutomationTest) SetUpkeepPrivilegeManager(address string) { + a.UpkeepPrivilegeManager = common.HexToAddress(address) +} + +func (a *AutomationTest) SetDockerEnv(env *test_env.CLClusterTestEnv) { + a.DockerEnv = env +} + +func (a *AutomationTest) DeployLINK() error { + linkToken, err := a.Deployer.DeployLinkTokenContract() + if err != nil { + return err + } + a.LinkToken = linkToken + err = a.ChainClient.WaitForEvents() + if err != nil { + return errors.Join(err, fmt.Errorf("failed waiting for link token contract to deploy")) + } + return nil +} + +func (a *AutomationTest) LoadLINK(address string) error { + linkToken, err := a.Deployer.LoadLinkToken(common.HexToAddress(address)) + if err != nil { + return err + } + a.LinkToken = linkToken + return nil +} + +func (a *AutomationTest) DeployTranscoder() error { + transcoder, err := a.Deployer.DeployUpkeepTranscoder() + if err != nil { + return err + } + a.Transcoder = transcoder + err = a.ChainClient.WaitForEvents() + if err != nil { + return errors.Join(err, fmt.Errorf("failed waiting for transcoder contract to deploy")) + } + return nil +} + +func (a *AutomationTest) LoadTranscoder(address string) error { + transcoder, err := a.Deployer.LoadUpkeepTranscoder(common.HexToAddress(address)) + if err != nil { + return err + } + a.Transcoder = transcoder + return nil +} + +func (a *AutomationTest) DeployEthLinkFeed() error { + ethLinkFeed, err := a.Deployer.DeployMockETHLINKFeed(a.RegistrySettings.FallbackLinkPrice) + if err != nil { + return err + } + a.EthLinkFeed = ethLinkFeed + err = a.ChainClient.WaitForEvents() + if err != nil { + return errors.Join(err, fmt.Errorf("failed waiting for Mock ETH LINK feed to deploy")) + } + return nil +} + +func (a *AutomationTest) LoadEthLinkFeed(address string) error { + ethLinkFeed, err := a.Deployer.LoadETHLINKFeed(common.HexToAddress(address)) + if err != nil { + return err + } + a.EthLinkFeed = ethLinkFeed + return nil +} + +func (a *AutomationTest) DeployGasFeed() error { + gasFeed, err := a.Deployer.DeployMockGasFeed(a.RegistrySettings.FallbackGasPrice) + if err != nil { + return err + } + a.GasFeed = gasFeed + err = a.ChainClient.WaitForEvents() + if err != nil { + return errors.Join(err, fmt.Errorf("failed waiting for mock gas feed to deploy")) + } + return nil +} + +func (a *AutomationTest) LoadEthGasFeed(address string) error { + gasFeed, err := a.Deployer.LoadGasFeed(common.HexToAddress(address)) + if err != nil { + return err + } + a.GasFeed = gasFeed + return nil +} + +func (a *AutomationTest) DeployRegistry() error { + registryOpts := &contracts.KeeperRegistryOpts{ + RegistryVersion: a.RegistrySettings.RegistryVersion, + LinkAddr: a.LinkToken.Address(), + ETHFeedAddr: a.EthLinkFeed.Address(), + GasFeedAddr: a.GasFeed.Address(), + TranscoderAddr: a.Transcoder.Address(), + RegistrarAddr: utils.ZeroAddress.Hex(), + Settings: a.RegistrySettings, + } + registry, err := a.Deployer.DeployKeeperRegistry(registryOpts) + if err != nil { + return err + } + a.Registry = registry + err = a.ChainClient.WaitForEvents() + if err != nil { + return errors.Join(err, fmt.Errorf("failed waiting for registry contract to deploy")) + } + return nil +} + +func (a *AutomationTest) LoadRegistry(address string) error { + registry, err := a.Deployer.LoadKeeperRegistry(common.HexToAddress(address), a.RegistrySettings.RegistryVersion) + if err != nil { + return err + } + a.Registry = registry + return nil +} + +func (a *AutomationTest) DeployRegistrar() error { + if a.Registry == nil { + return fmt.Errorf("registry must be deployed or loaded before registrar") + } + a.RegistrarSettings.RegistryAddr = a.Registry.Address() + registrar, err := a.Deployer.DeployKeeperRegistrar(a.RegistrySettings.RegistryVersion, a.LinkToken.Address(), a.RegistrarSettings) + if err != nil { + return err + } + a.Registrar = registrar + err = a.ChainClient.WaitForEvents() + if err != nil { + return errors.Join(err, fmt.Errorf("failed waiting for registrar contract to deploy")) + } + return nil +} + +func (a *AutomationTest) LoadRegistrar(address string) error { + if a.Registry == nil { + return fmt.Errorf("registry must be deployed or loaded before registrar") + } + a.RegistrarSettings.RegistryAddr = a.Registry.Address() + registrar, err := a.Deployer.LoadKeeperRegistrar(common.HexToAddress(address), a.RegistrySettings.RegistryVersion) + if err != nil { + return err + } + a.Registrar = registrar + return nil +} + +func (a *AutomationTest) CollectNodeDetails() error { + var ( + nodes []*client.ChainlinkClient + ) + if a.IsOnk8s { + for _, node := range a.ChainlinkNodesk8s[:] { + nodes = append(nodes, node.ChainlinkClient) + } + a.ChainlinkNodes = nodes + } else { + nodes = a.ChainlinkNodes[:] + } + + nodeDetails := make([]NodeDetails, 0) + + for i, node := range nodes { + nodeDetail := NodeDetails{} + P2PIds, err := node.MustReadP2PKeys() + if err != nil { + return errors.Join(err, fmt.Errorf("failed to read P2P keys from node %d", i)) + } + nodeDetail.P2PId = P2PIds.Data[0].Attributes.PeerID + + OCR2Keys, err := node.MustReadOCR2Keys() + if err != nil { + return errors.Join(err, fmt.Errorf("failed to read OCR2 keys from node %d", i)) + } + for _, key := range OCR2Keys.Data { + if key.Attributes.ChainType == string(chaintype.EVM) { + nodeDetail.OCR2ConfigPublicKey = key.Attributes.ConfigPublicKey + nodeDetail.OCR2OffchainPublicKey = key.Attributes.OffChainPublicKey + nodeDetail.OCR2OnChainPublicKey = key.Attributes.OnChainPublicKey + nodeDetail.OCR2Id = key.ID + break + } + } + + TransmitterKeys, err := node.EthAddressesForChain(a.ChainClient.GetChainID().String()) + nodeDetail.TransmitterAddresses = make([]string, 0) + if err != nil { + return errors.Join(err, fmt.Errorf("failed to read Transmitter keys from node %d", i)) + } + nodeDetail.TransmitterAddresses = append(nodeDetail.TransmitterAddresses, TransmitterKeys...) + nodeDetails = append(nodeDetails, nodeDetail) + } + a.NodeDetails = nodeDetails + + if a.IsOnk8s { + a.DefaultP2Pv2Bootstrapper = fmt.Sprintf("%s@%s-node-1:%d", a.NodeDetails[0].P2PId, a.ChainlinkNodesk8s[0].Name(), 6690) + } else { + a.DefaultP2Pv2Bootstrapper = fmt.Sprintf("%s@%s:%d", a.NodeDetails[0].P2PId, a.ChainlinkNodes[0].InternalIP(), 6690) + } + return nil +} + +func (a *AutomationTest) AddBootstrapJob() error { + bootstrapSpec := &client.OCR2TaskJobSpec{ + Name: "ocr2 bootstrap node " + a.Registry.Address(), + JobType: "bootstrap", + OCR2OracleSpec: job.OCR2OracleSpec{ + ContractID: a.Registry.Address(), + Relay: "evm", + RelayConfig: map[string]interface{}{ + "chainID": int(a.ChainClient.GetChainID().Int64()), + }, + ContractConfigTrackerPollInterval: *models.NewInterval(time.Second * 15), + }, + } + _, err := a.ChainlinkNodes[0].MustCreateJob(bootstrapSpec) + if err != nil { + return errors.Join(err, fmt.Errorf("failed to create bootstrap job on bootstrap node")) + } + return nil +} + +func (a *AutomationTest) AddAutomationJobs() error { + var contractVersion string + if a.RegistrySettings.RegistryVersion == ethereum.RegistryVersion_2_1 { + contractVersion = "v2.1" + } else if a.RegistrySettings.RegistryVersion == ethereum.RegistryVersion_2_0 { + contractVersion = "v2.0" + } else { + return fmt.Errorf("v2.0 and v2.1 are the only supported versions") + } + for i := 1; i < len(a.ChainlinkNodes); i++ { + autoOCR2JobSpec := client.OCR2TaskJobSpec{ + Name: "automation-" + contractVersion + "-" + a.Registry.Address(), + JobType: "offchainreporting2", + OCR2OracleSpec: job.OCR2OracleSpec{ + PluginType: "ocr2automation", + ContractID: a.Registry.Address(), + Relay: "evm", + RelayConfig: map[string]interface{}{ + "chainID": int(a.ChainClient.GetChainID().Int64()), + }, + PluginConfig: map[string]interface{}{ + "mercuryCredentialName": "\"" + a.MercuryCredentialName + "\"", + "contractVersion": "\"" + contractVersion + "\"", + }, + ContractConfigTrackerPollInterval: *models.NewInterval(time.Second * 15), + TransmitterID: null.StringFrom(a.NodeDetails[i].TransmitterAddresses[a.TransmitterKeyIndex]), + P2PV2Bootstrappers: pq.StringArray{a.DefaultP2Pv2Bootstrapper}, + OCRKeyBundleID: null.StringFrom(a.NodeDetails[i].OCR2Id), + }, + } + _, err := a.ChainlinkNodes[i].MustCreateJob(&autoOCR2JobSpec) + if err != nil { + return errors.Join(err, fmt.Errorf("failed to create OCR2 job on node %d", i+1)) + } + } + return nil +} + +func (a *AutomationTest) SetConfigOnRegistry() error { + donNodes := a.NodeDetails[1:] + S := make([]int, len(donNodes)) + oracleIdentities := make([]confighelper.OracleIdentityExtra, len(donNodes)) + var offC []byte + var signerOnchainPublicKeys []types.OnchainPublicKey + var transmitterAccounts []types.Account + var f uint8 + var offchainConfigVersion uint64 + var offchainConfig []byte + sharedSecretEncryptionPublicKeys := make([]types.ConfigEncryptionPublicKey, len(donNodes)) + eg := &errgroup.Group{} + for i, donNode := range donNodes { + index, chainlinkNode := i, donNode + eg.Go(func() error { + offchainPkBytes, err := hex.DecodeString(strings.TrimPrefix(chainlinkNode.OCR2OffchainPublicKey, "ocr2off_evm_")) + if err != nil { + return err + } + + offchainPkBytesFixed := [ed25519.PublicKeySize]byte{} + n := copy(offchainPkBytesFixed[:], offchainPkBytes) + if n != ed25519.PublicKeySize { + return fmt.Errorf("wrong number of elements copied") + } + + configPkBytes, err := hex.DecodeString(strings.TrimPrefix(chainlinkNode.OCR2ConfigPublicKey, "ocr2cfg_evm_")) + if err != nil { + return err + } + + configPkBytesFixed := [ed25519.PublicKeySize]byte{} + n = copy(configPkBytesFixed[:], configPkBytes) + if n != ed25519.PublicKeySize { + return fmt.Errorf("wrong number of elements copied") + } + + onchainPkBytes, err := hex.DecodeString(strings.TrimPrefix(chainlinkNode.OCR2OnChainPublicKey, "ocr2on_evm_")) + if err != nil { + return err + } + + sharedSecretEncryptionPublicKeys[index] = configPkBytesFixed + oracleIdentities[index] = confighelper.OracleIdentityExtra{ + OracleIdentity: confighelper.OracleIdentity{ + OnchainPublicKey: onchainPkBytes, + OffchainPublicKey: offchainPkBytesFixed, + PeerID: chainlinkNode.P2PId, + TransmitAccount: types.Account(chainlinkNode.TransmitterAddresses[a.TransmitterKeyIndex]), + }, + ConfigEncryptionPublicKey: configPkBytesFixed, + } + S[index] = 1 + return nil + }) + } + err := eg.Wait() + if err != nil { + return errors.Join(err, fmt.Errorf("failed to build oracle identities")) + } + + switch a.RegistrySettings.RegistryVersion { + case ethereum.RegistryVersion_2_0: + offC, err = json.Marshal(ocr2keepers20config.OffchainConfig{ + TargetProbability: a.PluginConfig.TargetProbability, + TargetInRounds: a.PluginConfig.TargetInRounds, + PerformLockoutWindow: a.PluginConfig.PerformLockoutWindow, + GasLimitPerReport: a.PluginConfig.GasLimitPerReport, + GasOverheadPerUpkeep: a.PluginConfig.GasOverheadPerUpkeep, + MinConfirmations: a.PluginConfig.MinConfirmations, + MaxUpkeepBatchSize: a.PluginConfig.MaxUpkeepBatchSize, + }) + if err != nil { + return errors.Join(err, fmt.Errorf("failed to marshal plugin config")) + } + + signerOnchainPublicKeys, transmitterAccounts, f, _, offchainConfigVersion, offchainConfig, err = ocr2.ContractSetConfigArgsForTests( + a.PublicConfig.DeltaProgress, a.PublicConfig.DeltaResend, + a.PublicConfig.DeltaRound, a.PublicConfig.DeltaGrace, + a.PublicConfig.DeltaStage, uint8(a.PublicConfig.RMax), + S, oracleIdentities, offC, + a.PublicConfig.MaxDurationQuery, a.PublicConfig.MaxDurationObservation, + 1200*time.Millisecond, + a.PublicConfig.MaxDurationShouldAcceptAttestedReport, + a.PublicConfig.MaxDurationShouldTransmitAcceptedReport, + a.PublicConfig.F, a.PublicConfig.OnchainConfig, + ) + if err != nil { + return errors.Join(err, fmt.Errorf("failed to build config args")) + } + + case ethereum.RegistryVersion_2_1: + offC, err = json.Marshal(ocr2keepers30config.OffchainConfig{ + TargetProbability: a.PluginConfig.TargetProbability, + TargetInRounds: a.PluginConfig.TargetInRounds, + PerformLockoutWindow: a.PluginConfig.PerformLockoutWindow, + GasLimitPerReport: a.PluginConfig.GasLimitPerReport, + GasOverheadPerUpkeep: a.PluginConfig.GasOverheadPerUpkeep, + MinConfirmations: a.PluginConfig.MinConfirmations, + MaxUpkeepBatchSize: a.PluginConfig.MaxUpkeepBatchSize, + }) + if err != nil { + return errors.Join(err, fmt.Errorf("failed to marshal plugin config")) + } + + signerOnchainPublicKeys, transmitterAccounts, f, _, offchainConfigVersion, offchainConfig, err = ocr3.ContractSetConfigArgsForTests( + a.PublicConfig.DeltaProgress, a.PublicConfig.DeltaResend, a.PublicConfig.DeltaInitial, + a.PublicConfig.DeltaRound, a.PublicConfig.DeltaGrace, a.PublicConfig.DeltaCertifiedCommitRequest, + a.PublicConfig.DeltaStage, a.PublicConfig.RMax, + S, oracleIdentities, offC, + a.PublicConfig.MaxDurationQuery, a.PublicConfig.MaxDurationObservation, + a.PublicConfig.MaxDurationShouldAcceptAttestedReport, + a.PublicConfig.MaxDurationShouldTransmitAcceptedReport, + a.PublicConfig.F, a.PublicConfig.OnchainConfig, + ) + if err != nil { + return errors.Join(err, fmt.Errorf("failed to build config args")) + } + default: + return fmt.Errorf("v2.0 and v2.1 are the only supported versions") + } + + var signers []common.Address + for _, signer := range signerOnchainPublicKeys { + if len(signer) != 20 { + return fmt.Errorf("OnChainPublicKey '%v' has wrong length for address", signer) + } + signers = append(signers, common.BytesToAddress(signer)) + } + + var transmitters []common.Address + for _, transmitter := range transmitterAccounts { + if !common.IsHexAddress(string(transmitter)) { + return fmt.Errorf("TransmitAccount '%s' is not a valid Ethereum address", string(transmitter)) + } + transmitters = append(transmitters, common.HexToAddress(string(transmitter))) + } + + onchainConfig, err := a.RegistrySettings.EncodeOnChainConfig(a.Registrar.Address(), a.UpkeepPrivilegeManager) + if err != nil { + return errors.Join(err, fmt.Errorf("failed to encode onchain config")) + } + + ocrConfig := contracts.OCRv2Config{ + Signers: signers, + Transmitters: transmitters, + F: f, + OnchainConfig: onchainConfig, + OffchainConfigVersion: offchainConfigVersion, + OffchainConfig: offchainConfig, + } + + err = a.Registry.SetConfig(a.RegistrySettings, ocrConfig) + if err != nil { + return errors.Join(err, fmt.Errorf("failed to set config on registry")) + } + return nil +} + +func (a *AutomationTest) RegisterUpkeeps(upkeepConfigs []UpkeepConfig) ([]common.Hash, error) { + var registrarABI *abi.ABI + var err error + var registrationRequest []byte + registrationTxHashes := make([]common.Hash, 0) + + for _, upkeepConfig := range upkeepConfigs { + switch a.RegistrySettings.RegistryVersion { + case ethereum.RegistryVersion_2_0: + registrarABI, err = keeper_registrar_wrapper2_0.KeeperRegistrarMetaData.GetAbi() + if err != nil { + return nil, errors.Join(err, fmt.Errorf("failed to get registrar abi")) + } + registrationRequest, err = registrarABI.Pack( + "register", upkeepConfig.UpkeepName, upkeepConfig.EncryptedEmail, + upkeepConfig.UpkeepContract, upkeepConfig.GasLimit, upkeepConfig.AdminAddress, + upkeepConfig.CheckData, + upkeepConfig.OffchainConfig, upkeepConfig.FundingAmount, + common.HexToAddress(a.ChainClient.GetDefaultWallet().Address())) + if err != nil { + return nil, errors.Join(err, fmt.Errorf("failed to pack registrar request")) + } + case ethereum.RegistryVersion_2_1: + registrarABI, err = automation_registrar_wrapper2_1.AutomationRegistrarMetaData.GetAbi() + if err != nil { + return nil, errors.Join(err, fmt.Errorf("failed to get registrar abi")) + } + registrationRequest, err = registrarABI.Pack( + "register", upkeepConfig.UpkeepName, upkeepConfig.EncryptedEmail, + upkeepConfig.UpkeepContract, upkeepConfig.GasLimit, upkeepConfig.AdminAddress, + upkeepConfig.TriggerType, upkeepConfig.CheckData, upkeepConfig.TriggerConfig, + upkeepConfig.OffchainConfig, upkeepConfig.FundingAmount, + common.HexToAddress(a.ChainClient.GetDefaultWallet().Address())) + if err != nil { + return nil, errors.Join(err, fmt.Errorf("failed to pack registrar request")) + } + default: + return nil, fmt.Errorf("v2.0 and v2.1 are the only supported versions") + } + tx, err := a.LinkToken.TransferAndCall(a.Registrar.Address(), upkeepConfig.FundingAmount, registrationRequest) + if err != nil { + return nil, errors.Join(err, fmt.Errorf("failed to register upkeep")) + } + registrationTxHashes = append(registrationTxHashes, tx.Hash()) + } + return registrationTxHashes, nil +} + +func (a *AutomationTest) ConfirmUpkeepsRegistered(registrationTxHashes []common.Hash) ([]*big.Int, error) { + upkeepIds := make([]*big.Int, 0) + for _, txHash := range registrationTxHashes { + receipt, err := a.ChainClient.GetTxReceipt(txHash) + if err != nil { + return nil, errors.Join(err, fmt.Errorf("failed to confirm upkeep registration")) + } + var upkeepId *big.Int + for _, rawLog := range receipt.Logs { + parsedUpkeepId, err := a.Registry.ParseUpkeepIdFromRegisteredLog(rawLog) + if err == nil { + upkeepId = parsedUpkeepId + break + } + } + if upkeepId == nil { + return nil, fmt.Errorf("failed to parse upkeep id from registration receipt") + } + upkeepIds = append(upkeepIds, upkeepId) + } + a.UpkeepIDs = upkeepIds + return upkeepIds, nil +} + +func (a *AutomationTest) AddJobsAndSetConfig(t *testing.T) { + l := logging.GetTestLogger(t) + err := a.AddBootstrapJob() + require.NoError(t, err, "Error adding bootstrap job") + err = a.AddAutomationJobs() + require.NoError(t, err, "Error adding automation jobs") + + l.Debug(). + Interface("Plugin Config", a.PluginConfig). + Interface("Public Config", a.PublicConfig). + Interface("Registry Settings", a.RegistrySettings). + Interface("Registrar Settings", a.RegistrarSettings). + Msg("Configuring registry") + err = a.SetConfigOnRegistry() + require.NoError(t, err, "Error setting config on registry") + l.Info().Str("Registry Address", a.Registry.Address()).Msg("Successfully setConfig on registry") +} + +func (a *AutomationTest) SetupMercuryMock(t *testing.T, imposters []ctfTestEnv.KillgraveImposter) { + if a.IsOnk8s { + t.Error("mercury mock is not supported on k8s") + } + if a.DockerEnv == nil { + t.Error("docker env is not set") + } + err := a.DockerEnv.MockAdapter.AddImposter(imposters) + if err != nil { + require.NoError(t, err, "Error adding mock imposter") + } +} + +func (a *AutomationTest) SetupAutomationDeployment(t *testing.T) { + l := logging.GetTestLogger(t) + err := a.CollectNodeDetails() + require.NoError(t, err, "Error collecting node details") + l.Info().Msg("Collected Node Details") + l.Debug().Interface("Node Details", a.NodeDetails).Msg("Node Details") + + err = a.DeployLINK() + require.NoError(t, err, "Error deploying link token contract") + + err = a.DeployEthLinkFeed() + require.NoError(t, err, "Error deploying eth link feed contract") + err = a.DeployGasFeed() + require.NoError(t, err, "Error deploying gas feed contract") + + err = a.DeployTranscoder() + require.NoError(t, err, "Error deploying transcoder contract") + + err = a.DeployRegistry() + require.NoError(t, err, "Error deploying registry contract") + err = a.DeployRegistrar() + require.NoError(t, err, "Error deploying registrar contract") + + a.AddJobsAndSetConfig(t) +} + +func (a *AutomationTest) LoadAutomationDeployment(t *testing.T, linkTokenAddress, + ethLinkFeedAddress, gasFeedAddress, transcoderAddress, registryAddress, registrarAddress string) { + l := logging.GetTestLogger(t) + err := a.CollectNodeDetails() + require.NoError(t, err, "Error collecting node details") + l.Info().Msg("Collected Node Details") + l.Debug().Interface("Node Details", a.NodeDetails).Msg("Node Details") + + err = a.LoadLINK(linkTokenAddress) + require.NoError(t, err, "Error loading link token contract") + + err = a.LoadEthLinkFeed(ethLinkFeedAddress) + require.NoError(t, err, "Error loading eth link feed contract") + err = a.LoadEthGasFeed(gasFeedAddress) + require.NoError(t, err, "Error loading gas feed contract") + err = a.LoadTranscoder(transcoderAddress) + require.NoError(t, err, "Error loading transcoder contract") + err = a.LoadRegistry(registryAddress) + require.NoError(t, err, "Error loading registry contract") + err = a.LoadRegistrar(registrarAddress) + require.NoError(t, err, "Error loading registrar contract") + + a.AddJobsAndSetConfig(t) + +} diff --git a/integration-tests/actions/ocr2_helpers.go b/integration-tests/actions/ocr2_helpers.go index aead74f2bdd..7b0700c3452 100644 --- a/integration-tests/actions/ocr2_helpers.go +++ b/integration-tests/actions/ocr2_helpers.go @@ -15,17 +15,19 @@ import ( "golang.org/x/sync/errgroup" "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-testing-framework/blockchain" - ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" - "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" - "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" + + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/store/models" ) // DeployOCRv2Contracts deploys a number of OCRv2 contracts and configures them with defaults @@ -378,3 +380,23 @@ func StartNewOCR2Round( } return nil } + +// WatchNewOCR2Round is the same as StartNewOCR2Round but does NOT explicitly request a new round +// as that can cause odd behavior in tandem with changing adapter values in OCR2 +func WatchNewOCR2Round( + roundNumber int64, + ocrInstances []contracts.OffchainAggregatorV2, + client blockchain.EVMClient, + timeout time.Duration, + logger zerolog.Logger, +) error { + for i := 0; i < len(ocrInstances); i++ { + ocrRound := contracts.NewOffchainAggregatorV2RoundConfirmer(ocrInstances[i], big.NewInt(roundNumber), timeout, logger) + client.AddHeaderEventSubscription(ocrInstances[i].Address(), ocrRound) + err := client.WaitForEvents() + if err != nil { + return fmt.Errorf("failed to wait for event subscriptions of OCR instance %d: %w", i+1, err) + } + } + return nil +} diff --git a/integration-tests/actions/ocr2_helpers_local.go b/integration-tests/actions/ocr2_helpers_local.go index b3fe6eb041f..e1b470bd0d4 100644 --- a/integration-tests/actions/ocr2_helpers_local.go +++ b/integration-tests/actions/ocr2_helpers_local.go @@ -12,18 +12,20 @@ import ( "github.com/google/uuid" "github.com/lib/pq" "github.com/rs/zerolog/log" + "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" + "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "golang.org/x/sync/errgroup" + "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" + "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/testhelpers" "github.com/smartcontractkit/chainlink/v2/core/store/models" - "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" - "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "golang.org/x/sync/errgroup" - "gopkg.in/guregu/null.v4" ) func CreateOCRv2JobsLocal( @@ -272,3 +274,49 @@ func GetOracleIdentitiesWithKeyIndexLocal( return S, oracleIdentities, eg.Wait() } + +// DeleteJobs will delete ALL jobs from the nodes +func DeleteJobs(nodes []*client.ChainlinkClient) error { + for _, node := range nodes { + if node == nil { + return fmt.Errorf("found a nil chainlink node in the list of chainlink nodes while tearing down: %v", nodes) + } + jobs, _, err := node.ReadJobs() + if err != nil { + return fmt.Errorf("error reading jobs from chainlink node: %w", err) + } + for _, maps := range jobs.Data { + if _, ok := maps["id"]; !ok { + return fmt.Errorf("error reading job id from chainlink node's jobs %+v", jobs.Data) + } + id := maps["id"].(string) + _, err2 := node.DeleteJob(id) + if err2 != nil { + return fmt.Errorf("error deleting job from chainlink node: %w", err) + } + } + } + return nil +} + +// DeleteBridges will delete ALL bridges from the nodes +func DeleteBridges(nodes []*client.ChainlinkClient) error { + for _, node := range nodes { + if node == nil { + return fmt.Errorf("found a nil chainlink node in the list of chainlink nodes while tearing down: %v", nodes) + } + + bridges, _, err := node.ReadBridges() + if err != nil { + return err + } + for _, b := range bridges.Data { + _, err = node.DeleteBridge(b.Attributes.Name) + if err != nil { + return err + } + } + + } + return nil +} diff --git a/integration-tests/actions/ocr2vrf_actions/ocr2vrf_config_helpers.go b/integration-tests/actions/ocr2vrf_actions/ocr2vrf_config_helpers.go index ce693964323..58e394cb797 100644 --- a/integration-tests/actions/ocr2vrf_actions/ocr2vrf_config_helpers.go +++ b/integration-tests/actions/ocr2vrf_actions/ocr2vrf_config_helpers.go @@ -16,15 +16,17 @@ import ( "go.dedis.ch/kyber/v3/group/edwards25519" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-vrf/altbn_128" + "github.com/smartcontractkit/chainlink-vrf/dkg" + "github.com/smartcontractkit/chainlink-vrf/ocr2vrf" + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" + "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" - "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/ocr2vrf/altbn_128" - "github.com/smartcontractkit/ocr2vrf/dkg" - "github.com/smartcontractkit/ocr2vrf/ocr2vrf" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" diff --git a/integration-tests/actions/ocr2vrf_actions/ocr2vrf_constants/ocr2vrf_constants.go b/integration-tests/actions/ocr2vrf_actions/ocr2vrf_constants/ocr2vrf_constants.go index 7caceec9414..7f17be3fd84 100644 --- a/integration-tests/actions/ocr2vrf_actions/ocr2vrf_constants/ocr2vrf_constants.go +++ b/integration-tests/actions/ocr2vrf_actions/ocr2vrf_constants/ocr2vrf_constants.go @@ -4,7 +4,7 @@ import ( "math/big" "time" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" ) var ( diff --git a/integration-tests/actions/ocr2vrf_actions/ocr2vrf_models.go b/integration-tests/actions/ocr2vrf_actions/ocr2vrf_models.go index 8f218ab5c7d..08580203380 100644 --- a/integration-tests/actions/ocr2vrf_actions/ocr2vrf_models.go +++ b/integration-tests/actions/ocr2vrf_actions/ocr2vrf_models.go @@ -1,6 +1,6 @@ package ocr2vrf_actions -import ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" +import ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" type DKGKeyConfig struct { DKGEncryptionPublicKey string diff --git a/integration-tests/actions/ocr2vrf_actions/ocr2vrf_steps.go b/integration-tests/actions/ocr2vrf_actions/ocr2vrf_steps.go index c123aaff6a2..c3add16ec67 100644 --- a/integration-tests/actions/ocr2vrf_actions/ocr2vrf_steps.go +++ b/integration-tests/actions/ocr2vrf_actions/ocr2vrf_steps.go @@ -10,10 +10,11 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" - ocr2vrftypes "github.com/smartcontractkit/ocr2vrf/types" + ocr2vrftypes "github.com/smartcontractkit/chainlink-vrf/types" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" chainlinkutils "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -172,7 +173,7 @@ func FundVRFCoordinatorV3Subscription(t *testing.T, linkToken contracts.LinkToke require.NoError(t, err, "Error waiting for TXs to complete") } -func DeployOCR2VRFContracts(t *testing.T, contractDeployer contracts.ContractDeployer, chainClient blockchain.EVMClient, linkToken contracts.LinkToken, mockETHLinkFeed contracts.MockETHLINKFeed, beaconPeriodBlocksCount *big.Int, keyID string) (contracts.DKG, contracts.VRFCoordinatorV3, contracts.VRFBeacon, contracts.VRFBeaconConsumer) { +func DeployOCR2VRFContracts(t *testing.T, contractDeployer contracts.ContractDeployer, chainClient blockchain.EVMClient, linkToken contracts.LinkToken, beaconPeriodBlocksCount *big.Int, keyID string) (contracts.DKG, contracts.VRFCoordinatorV3, contracts.VRFBeacon, contracts.VRFBeaconConsumer) { dkg, err := contractDeployer.DeployDKG() require.NoError(t, err, "Error deploying DKG Contract") @@ -272,14 +273,14 @@ func RequestRandomnessFulfillmentAndWaitForFulfilment( } func getRequestId(t *testing.T, consumer contracts.VRFBeaconConsumer, receipt *types.Receipt, confirmationDelay *big.Int) *big.Int { - periodBlocks, err := consumer.IBeaconPeriodBlocks(nil) + periodBlocks, err := consumer.IBeaconPeriodBlocks(testcontext.Get(t)) require.NoError(t, err, "Error getting Beacon Period block count") blockNumber := receipt.BlockNumber periodOffset := new(big.Int).Mod(blockNumber, periodBlocks) nextBeaconOutputHeight := new(big.Int).Sub(new(big.Int).Add(blockNumber, periodBlocks), periodOffset) - requestID, err := consumer.GetRequestIdsBy(nil, nextBeaconOutputHeight, confirmationDelay) + requestID, err := consumer.GetRequestIdsBy(testcontext.Get(t), nextBeaconOutputHeight, confirmationDelay) require.NoError(t, err, "Error getting requestID from consumer contract") return requestID @@ -305,7 +306,6 @@ func SetupOCR2VRFUniverse( contractDeployer, chainClient, linkToken, - mockETHLinkFeed, ocr2vrf_constants.BeaconPeriodBlocksCount, ocr2vrf_constants.KeyID, ) diff --git a/integration-tests/actions/ocr_helpers.go b/integration-tests/actions/ocr_helpers.go index cfc8cfe589b..4f713dcdd6d 100644 --- a/integration-tests/actions/ocr_helpers.go +++ b/integration-tests/actions/ocr_helpers.go @@ -27,7 +27,6 @@ func DeployOCRContracts( numberOfContracts int, linkTokenContract contracts.LinkToken, contractDeployer contracts.ContractDeployer, - bootstrapNode *client.ChainlinkK8sClient, workerNodes []*client.ChainlinkK8sClient, client blockchain.EVMClient, ) ([]contracts.OffchainAggregator, error) { diff --git a/integration-tests/actions/ocr_helpers_local.go b/integration-tests/actions/ocr_helpers_local.go index 8bb4e834794..fb0fd0bd47d 100644 --- a/integration-tests/actions/ocr_helpers_local.go +++ b/integration-tests/actions/ocr_helpers_local.go @@ -9,11 +9,11 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" - "github.com/pkg/errors" "github.com/rs/zerolog" + "golang.org/x/sync/errgroup" + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" - "golang.org/x/sync/errgroup" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" @@ -146,7 +146,7 @@ func CreateOCRJobsLocal( workerNodes []*client.ChainlinkClient, mockValue int, mockAdapter *test_env.Killgrave, - evmChainID string, + evmChainID *big.Int, ) error { for _, ocrInstance := range ocrInstances { bootstrapP2PIds, err := bootstrapNode.MustReadP2PKeys() @@ -157,7 +157,7 @@ func CreateOCRJobsLocal( bootstrapSpec := &client.OCRBootstrapJobSpec{ Name: fmt.Sprintf("bootstrap-%s", uuid.New().String()), ContractAddress: ocrInstance.Address(), - EVMChainID: evmChainID, + EVMChainID: evmChainID.String(), P2PPeerID: bootstrapP2PId, IsBootstrapPeer: true, } @@ -202,7 +202,7 @@ func CreateOCRJobsLocal( bootstrapPeers := []*client.ChainlinkClient{bootstrapNode} ocrSpec := &client.OCRTaskJobSpec{ ContractAddress: ocrInstance.Address(), - EVMChainID: evmChainID, + EVMChainID: evmChainID.String(), P2PPeerID: nodeP2PId, P2PBootstrapPeers: bootstrapPeers, KeyBundleID: nodeOCRKeyId, @@ -211,7 +211,7 @@ func CreateOCRJobsLocal( } _, err = node.MustCreateJob(ocrSpec) if err != nil { - return fmt.Errorf("creating OCR task job on OCR node have failed: %w", err) + return fmt.Errorf("creating OCR job on OCR node failed: %w", err) } } } @@ -280,7 +280,7 @@ func TrackForwarderLocal( chainID := chainClient.GetChainID() _, _, err := node.TrackForwarder(chainID, authorizedForwarder) if err != nil { - return errors.Wrap(err, "failed to track forwarder") + return fmt.Errorf("failed to track forwarder, err: %w", err) } logger.Info().Str("NodeURL", node.Config.URL). Str("ForwarderAddress", authorizedForwarder.Hex()). @@ -305,7 +305,7 @@ func DeployOCRContractsForwarderFlowLocal( contracts.DefaultOffChainAggregatorOptions(), ) if err != nil { - return nil, errors.Wrap(err, "failed to deploy offchain aggregator") + return nil, fmt.Errorf("failed to deploy offchain aggregator, err: %w", err) } ocrInstances = append(ocrInstances, ocrInstance) err = client.WaitForEvents() @@ -329,7 +329,7 @@ func DeployOCRContractsForwarderFlowLocal( for _, ocrInstance := range ocrInstances { err := ocrInstance.SetPayees(transmitters, payees) if err != nil { - return nil, errors.Wrap(err, "failed to set OCR payees") + return nil, fmt.Errorf("failed to set OCR payees, err: %w", err) } if err := client.WaitForEvents(); err != nil { return nil, err @@ -348,7 +348,7 @@ func DeployOCRContractsForwarderFlowLocal( forwarderAddresses, ) if err != nil { - return nil, errors.Wrap(err, "failed to set on-chain config") + return nil, fmt.Errorf("failed to set on-chain config, err: %w", err) } if err = client.WaitForEvents(); err != nil { return nil, err diff --git a/integration-tests/actions/operator_forwarder_helpers.go b/integration-tests/actions/operator_forwarder_helpers.go index 37b50c4fa9a..e308cd8fd5b 100644 --- a/integration-tests/actions/operator_forwarder_helpers.go +++ b/integration-tests/actions/operator_forwarder_helpers.go @@ -1,7 +1,6 @@ package actions import ( - "context" "math/big" "testing" @@ -13,6 +12,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/operator_factory" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -67,7 +67,7 @@ func AcceptAuthorizedReceiversOperator( err = chainClient.WaitForEvents() require.NoError(t, err, "Waiting for events in nodes shouldn't fail") - senders, err := forwarderInstance.GetAuthorizedSenders(context.Background()) + senders, err := forwarderInstance.GetAuthorizedSenders(testcontext.Get(t)) require.NoError(t, err, "Getting authorized senders shouldn't fail") var nodesAddrs []string for _, o := range nodeAddresses { @@ -75,20 +75,18 @@ func AcceptAuthorizedReceiversOperator( } require.Equal(t, nodesAddrs, senders, "Senders addresses should match node addresses") - owner, err := forwarderInstance.Owner(context.Background()) + owner, err := forwarderInstance.Owner(testcontext.Get(t)) require.NoError(t, err, "Getting authorized forwarder owner shouldn't fail") require.Equal(t, operator.Hex(), owner, "Forwarder owner should match operator") } func ProcessNewEvent( t *testing.T, - eventSub geth.Subscription, operatorCreated chan *operator_factory.OperatorFactoryOperatorCreated, authorizedForwarderCreated chan *operator_factory.OperatorFactoryAuthorizedForwarderCreated, event *types.Log, eventDetails *abi.Event, operatorFactoryInstance contracts.OperatorFactory, - contractABI *abi.ABI, chainClient blockchain.EVMClient, ) { l := logging.GetTestLogger(t) @@ -141,7 +139,7 @@ func SubscribeOperatorFactoryEvents( l := logging.GetTestLogger(t) contractABI, err := operator_factory.OperatorFactoryMetaData.GetAbi() require.NoError(t, err, "Getting contract abi for OperatorFactory shouldn't fail") - latestBlockNum, err := chainClient.LatestBlockNumber(context.Background()) + latestBlockNum, err := chainClient.LatestBlockNumber(testcontext.Get(t)) require.NoError(t, err, "Subscribing to contract event log for OperatorFactory instance shouldn't fail") query := geth.FilterQuery{ FromBlock: big.NewInt(0).SetUint64(latestBlockNum), @@ -149,7 +147,7 @@ func SubscribeOperatorFactoryEvents( } eventLogs := make(chan types.Log) - sub, err := chainClient.SubscribeFilterLogs(context.Background(), query, eventLogs) + sub, err := chainClient.SubscribeFilterLogs(testcontext.Get(t), query, eventLogs) require.NoError(t, err, "Subscribing to contract event log for OperatorFactory instance shouldn't fail") go func() { defer sub.Unsubscribe() @@ -160,14 +158,14 @@ func SubscribeOperatorFactoryEvents( l.Error().Err(err).Msg("Error while watching for new contract events. Retrying Subscription") sub.Unsubscribe() - sub, err = chainClient.SubscribeFilterLogs(context.Background(), query, eventLogs) + sub, err = chainClient.SubscribeFilterLogs(testcontext.Get(t), query, eventLogs) require.NoError(t, err, "Subscribing to contract event log for OperatorFactory instance shouldn't fail") case vLog := <-eventLogs: eventDetails, err := contractABI.EventByID(vLog.Topics[0]) require.NoError(t, err, "Getting event details for OperatorFactory instance shouldn't fail") go ProcessNewEvent( - t, sub, operatorCreated, authorizedForwarderCreated, &vLog, - eventDetails, operatorFactoryInstance, contractABI, chainClient, + t, operatorCreated, authorizedForwarderCreated, &vLog, + eventDetails, operatorFactoryInstance, chainClient, ) if eventDetails.Name == "AuthorizedForwarderCreated" || eventDetails.Name == "OperatorCreated" { remainingExpectedEvents-- diff --git a/integration-tests/actions/vrfv1/actions.go b/integration-tests/actions/vrfv1/actions.go index 68d3e584cee..f8d7190709f 100644 --- a/integration-tests/actions/vrfv1/actions.go +++ b/integration-tests/actions/vrfv1/actions.go @@ -1,7 +1,8 @@ package vrfv1 import ( - "github.com/pkg/errors" + "fmt" + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink/integration-tests/contracts" ) @@ -21,15 +22,15 @@ type Contracts struct { func DeployVRFContracts(cd contracts.ContractDeployer, bc blockchain.EVMClient, lt contracts.LinkToken) (*Contracts, error) { bhs, err := cd.DeployBlockhashStore() if err != nil { - return nil, errors.Wrap(err, ErrDeployBHSV1) + return nil, fmt.Errorf("%s, err %w", ErrDeployBHSV1, err) } coordinator, err := cd.DeployVRFCoordinator(lt.Address(), bhs.Address()) if err != nil { - return nil, errors.Wrap(err, ErrDeployVRFCootrinatorV1) + return nil, fmt.Errorf("%s, err %w", ErrDeployVRFCootrinatorV1, err) } consumer, err := cd.DeployVRFConsumer(lt.Address(), coordinator.Address()) if err != nil { - return nil, errors.Wrap(err, ErrDeployVRFConsumerV1) + return nil, fmt.Errorf("%s, err %w", ErrDeployVRFConsumerV1, err) } if err := bc.WaitForEvents(); err != nil { return nil, err diff --git a/integration-tests/actions/vrfv2_actions/vrfv2_config/config.go b/integration-tests/actions/vrfv2_actions/vrfv2_config/config.go new file mode 100644 index 00000000000..aa2ac7f59ea --- /dev/null +++ b/integration-tests/actions/vrfv2_actions/vrfv2_config/config.go @@ -0,0 +1,54 @@ +package vrfv2_config + +import "time" + +type VRFV2Config struct { + ChainlinkNodeFunding float64 `envconfig:"CHAINLINK_NODE_FUNDING" default:".1"` // Amount of native currency to fund each chainlink node with + CLNodeMaxGasPriceGWei int64 `envconfig:"MAX_GAS_PRICE_GWEI" default:"1000"` // Max gas price in GWei for the chainlink node + IsNativePayment bool `envconfig:"IS_NATIVE_PAYMENT" default:"false"` // Whether to use native payment or LINK token + LinkNativeFeedResponse int64 `envconfig:"LINK_NATIVE_FEED_RESPONSE" default:"1000000000000000000"` // Response of the LINK/ETH feed + MinimumConfirmations uint16 `envconfig:"MINIMUM_CONFIRMATIONS" default:"3"` // Minimum number of confirmations for the VRF Coordinator + SubscriptionFundingAmountLink float64 `envconfig:"SUBSCRIPTION_FUNDING_AMOUNT_LINK" default:"5"` // Amount of LINK to fund the subscription with + NumberOfWords uint32 `envconfig:"NUMBER_OF_WORDS" default:"3"` // Number of words to request + CallbackGasLimit uint32 `envconfig:"CALLBACK_GAS_LIMIT" default:"1000000"` // Gas limit for the callback + MaxGasLimitCoordinatorConfig uint32 `envconfig:"MAX_GAS_LIMIT_COORDINATOR_CONFIG" default:"2500000"` // Max gas limit for the VRF Coordinator config + FallbackWeiPerUnitLink int64 `envconfig:"FALLBACK_WEI_PER_UNIT_LINK" default:"60000000000000000"` // Fallback wei per unit LINK for the VRF Coordinator config + StalenessSeconds uint32 `envconfig:"STALENESS_SECONDS" default:"86400"` // Staleness in seconds for the VRF Coordinator config + GasAfterPaymentCalculation uint32 `envconfig:"GAS_AFTER_PAYMENT_CALCULATION" default:"33825"` // Gas after payment calculation for the VRF Coordinator config + FulfillmentFlatFeeLinkPPMTier1 uint32 `envconfig:"FULFILLMENT_FLAT_FEE_LINK_PPM_TIER_1" default:"500"` + FulfillmentFlatFeeLinkPPMTier2 uint32 `envconfig:"FULFILLMENT_FLAT_FEE_LINK_PPM_TIER_2" default:"500"` + FulfillmentFlatFeeLinkPPMTier3 uint32 `envconfig:"FULFILLMENT_FLAT_FEE_LINK_PPM_TIER_3" default:"500"` + FulfillmentFlatFeeLinkPPMTier4 uint32 `envconfig:"FULFILLMENT_FLAT_FEE_LINK_PPM_TIER_4" default:"500"` + FulfillmentFlatFeeLinkPPMTier5 uint32 `envconfig:"FULFILLMENT_FLAT_FEE_LINK_PPM_TIER_5" default:"500"` + ReqsForTier2 int64 `envconfig:"REQS_FOR_TIER_2" default:"0"` + ReqsForTier3 int64 `envconfig:"REQS_FOR_TIER_3" default:"0"` + ReqsForTier4 int64 `envconfig:"REQS_FOR_TIER_4" default:"0"` + ReqsForTier5 int64 `envconfig:"REQS_FOR_TIER_5" default:"0"` + + NumberOfSubToCreate int `envconfig:"NUMBER_OF_SUB_TO_CREATE" default:"1"` // Number of subscriptions to create + + RandomnessRequestCountPerRequest uint16 `envconfig:"RANDOMNESS_REQUEST_COUNT_PER_REQUEST" default:"1"` // How many randomness requests to send per request + RandomnessRequestCountPerRequestDeviation uint16 `envconfig:"RANDOMNESS_REQUEST_COUNT_PER_REQUEST_DEVIATION" default:"0"` // How many randomness requests to send per request + + RandomWordsFulfilledEventTimeout time.Duration `envconfig:"RANDOM_WORDS_FULFILLED_EVENT_TIMEOUT" default:"2m"` // How long to wait for the RandomWordsFulfilled event to be emitted + + //Wrapper Config + WrapperGasOverhead uint32 `envconfig:"WRAPPER_GAS_OVERHEAD" default:"50000"` + CoordinatorGasOverhead uint32 `envconfig:"COORDINATOR_GAS_OVERHEAD" default:"52000"` + WrapperPremiumPercentage uint8 `envconfig:"WRAPPER_PREMIUM_PERCENTAGE" default:"25"` + WrapperMaxNumberOfWords uint8 `envconfig:"WRAPPER_MAX_NUMBER_OF_WORDS" default:"10"` + WrapperConsumerFundingAmountNativeToken float64 `envconfig:"WRAPPER_CONSUMER_FUNDING_AMOUNT_NATIVE_TOKEN" default:"1"` + WrapperConsumerFundingAmountLink int64 `envconfig:"WRAPPER_CONSUMER_FUNDING_AMOUNT_LINK" default:"10"` + + //LOAD/SOAK Test Config + TestDuration time.Duration `envconfig:"TEST_DURATION" default:"3m"` // How long to run the test for + RPS int64 `envconfig:"RPS" default:"1"` // How many requests per second to send + RateLimitUnitDuration time.Duration `envconfig:"RATE_LIMIT_UNIT_DURATION" default:"1m"` + //Using existing environment and contracts + UseExistingEnv bool `envconfig:"USE_EXISTING_ENV" default:"false"` // Whether to use an existing environment or create a new one + CoordinatorAddress string `envconfig:"COORDINATOR_ADDRESS" default:""` // Coordinator address + ConsumerAddress string `envconfig:"CONSUMER_ADDRESS" default:""` // Consumer address + LinkAddress string `envconfig:"LINK_ADDRESS" default:""` // Link address + SubID uint64 `envconfig:"SUB_ID" default:""` // Subscription ID + KeyHash string `envconfig:"KEY_HASH" default:""` +} diff --git a/integration-tests/actions/vrfv2_actions/vrfv2_constants/constants.go b/integration-tests/actions/vrfv2_actions/vrfv2_constants/constants.go deleted file mode 100644 index 2ec993e4e6e..00000000000 --- a/integration-tests/actions/vrfv2_actions/vrfv2_constants/constants.go +++ /dev/null @@ -1,34 +0,0 @@ -package vrfv2_constants - -import ( - "math/big" - - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2" -) - -var ( - LinkEthFeedResponse = big.NewInt(1e18) - MinimumConfirmations = uint16(3) - RandomnessRequestCountPerRequest = uint16(1) - //todo - get Sub id when creating subscription - need to listen for SubscriptionCreated Log - SubID = uint64(1) - VRFSubscriptionFundingAmountLink = big.NewInt(100) - ChainlinkNodeFundingAmountEth = big.NewFloat(0.1) - NumberOfWords = uint32(3) - MaxGasPriceGWei = 1000 - CallbackGasLimit = uint32(1000000) - MaxGasLimitVRFCoordinatorConfig = uint32(2.5e6) - StalenessSeconds = uint32(86400) - GasAfterPaymentCalculation = uint32(33825) - - VRFCoordinatorV2FeeConfig = vrf_coordinator_v2.VRFCoordinatorV2FeeConfig{ - FulfillmentFlatFeeLinkPPMTier1: 500, - FulfillmentFlatFeeLinkPPMTier2: 500, - FulfillmentFlatFeeLinkPPMTier3: 500, - FulfillmentFlatFeeLinkPPMTier4: 500, - FulfillmentFlatFeeLinkPPMTier5: 500, - ReqsForTier2: big.NewInt(0), - ReqsForTier3: big.NewInt(0), - ReqsForTier4: big.NewInt(0), - ReqsForTier5: big.NewInt(0)} -) diff --git a/integration-tests/actions/vrfv2_actions/vrfv2_models.go b/integration-tests/actions/vrfv2_actions/vrfv2_models.go index 71d119e1dbb..0576d6f7d6e 100644 --- a/integration-tests/actions/vrfv2_actions/vrfv2_models.go +++ b/integration-tests/actions/vrfv2_actions/vrfv2_models.go @@ -18,7 +18,21 @@ type VRFV2JobInfo struct { } type VRFV2Contracts struct { - Coordinator contracts.VRFCoordinatorV2 - BHS contracts.BlockHashStore - LoadTestConsumer contracts.VRFv2LoadTestConsumer + Coordinator contracts.VRFCoordinatorV2 + BHS contracts.BlockHashStore + LoadTestConsumers []contracts.VRFv2LoadTestConsumer +} + +// VRFV2PlusKeyData defines a jobs into and proving key info +type VRFV2KeyData struct { + VRFKey *client.VRFKey + EncodedProvingKey VRFV2EncodedProvingKey + KeyHash [32]byte +} + +type VRFV2Data struct { + VRFV2KeyData + VRFJob *client.Job + PrimaryEthAddress string + ChainID *big.Int } diff --git a/integration-tests/actions/vrfv2_actions/vrfv2_steps.go b/integration-tests/actions/vrfv2_actions/vrfv2_steps.go index 24ac217a334..4ea4a4a8534 100644 --- a/integration-tests/actions/vrfv2_actions/vrfv2_steps.go +++ b/integration-tests/actions/vrfv2_actions/vrfv2_steps.go @@ -4,35 +4,57 @@ import ( "context" "fmt" "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/rs/zerolog" + + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-testing-framework/utils/conversions" + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_config" + "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" + "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2" "github.com/google/uuid" - "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" chainlinkutils "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/integration-tests/actions" - vrfConst "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_constants" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" - "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" - "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" ) var ( - ErrNodePrimaryKey = "error getting node's primary ETH key" - ErrCreatingProvingKeyHash = "error creating a keyHash from the proving key" - ErrCreatingProvingKey = "error creating a keyHash from the proving key" - ErrRegisterProvingKey = "error registering proving keys" - ErrEncodingProvingKey = "error encoding proving key" - ErrCreatingVRFv2Key = "error creating VRFv2 key" - ErrDeployBlockHashStore = "error deploying blockhash store" - ErrDeployCoordinator = "error deploying VRFv2 CoordinatorV2" - ErrAdvancedConsumer = "error deploying VRFv2 Advanced Consumer" - ErrABIEncodingFunding = "error Abi encoding subscriptionID" - ErrSendingLinkToken = "error sending Link token" - ErrCreatingVRFv2Job = "error creating VRFv2 job" - ErrParseJob = "error parsing job definition" + ErrNodePrimaryKey = "error getting node's primary ETH key" + ErrNodeNewTxKey = "error creating node's EVM transaction key" + ErrCreatingProvingKeyHash = "error creating a keyHash from the proving key" + ErrRegisteringProvingKey = "error registering a proving key on Coordinator contract" + ErrRegisterProvingKey = "error registering proving keys" + ErrEncodingProvingKey = "error encoding proving key" + ErrCreatingVRFv2Key = "error creating VRFv2 key" + ErrDeployBlockHashStore = "error deploying blockhash store" + ErrDeployCoordinator = "error deploying VRF CoordinatorV2" + ErrAdvancedConsumer = "error deploying VRFv2 Advanced Consumer" + ErrABIEncodingFunding = "error Abi encoding subscriptionID" + ErrSendingLinkToken = "error sending Link token" + ErrCreatingVRFv2Job = "error creating VRFv2 job" + ErrParseJob = "error parsing job definition" + ErrDeployVRFV2Contracts = "error deploying VRFV2 contracts" + ErrSetVRFCoordinatorConfig = "error setting config for VRF Coordinator contract" + ErrCreateVRFSubscription = "error creating VRF Subscription" + ErrAddConsumerToSub = "error adding consumer to VRF Subscription" + ErrFundSubWithLinkToken = "error funding subscription with Link tokens" + ErrCreateVRFV2Jobs = "error creating VRF V2 Jobs" + ErrGetPrimaryKey = "error getting primary ETH key address" + ErrRestartCLNode = "error restarting CL node" + ErrWaitTXsComplete = "error waiting for TXs to complete" + ErrRequestRandomness = "error requesting randomness" + + ErrWaitRandomWordsRequestedEvent = "error waiting for RandomWordsRequested event" + ErrWaitRandomWordsFulfilledEvent = "error waiting for RandomWordsFulfilled event" ) func DeployVRFV2Contracts( @@ -40,82 +62,80 @@ func DeployVRFV2Contracts( chainClient blockchain.EVMClient, linkTokenContract contracts.LinkToken, linkEthFeedContract contracts.MockETHLINKFeed, + consumerContractsAmount int, ) (*VRFV2Contracts, error) { bhs, err := contractDeployer.DeployBlockhashStore() if err != nil { - return nil, errors.Wrap(err, ErrDeployBlockHashStore) + return nil, fmt.Errorf("%s, err %w", ErrDeployBlockHashStore, err) } - coordinator, err := contractDeployer.DeployVRFCoordinatorV2(linkTokenContract.Address(), bhs.Address(), linkEthFeedContract.Address()) + err = chainClient.WaitForEvents() if err != nil { - return nil, errors.Wrap(err, ErrDeployCoordinator) + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } - loadTestConsumer, err := contractDeployer.DeployVRFv2LoadTestConsumer(coordinator.Address()) + coordinator, err := contractDeployer.DeployVRFCoordinatorV2(linkTokenContract.Address(), bhs.Address(), linkEthFeedContract.Address()) if err != nil { - return nil, errors.Wrap(err, ErrAdvancedConsumer) + return nil, fmt.Errorf("%s, err %w", ErrDeployCoordinator, err) } err = chainClient.WaitForEvents() + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + consumers, err := DeployVRFV2Consumers(contractDeployer, coordinator, consumerContractsAmount) if err != nil { return nil, err } - return &VRFV2Contracts{coordinator, bhs, loadTestConsumer}, nil + err = chainClient.WaitForEvents() + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + return &VRFV2Contracts{coordinator, bhs, consumers}, nil } -func CreateVRFV2Jobs( - chainlinkNodes []*client.ChainlinkClient, - coordinator contracts.VRFCoordinatorV2, - c blockchain.EVMClient, - minIncomingConfirmations uint16, -) ([]VRFV2JobInfo, error) { - jobInfo := make([]VRFV2JobInfo, 0) - for _, chainlinkNode := range chainlinkNodes { - vrfKey, err := chainlinkNode.MustCreateVRFKey() - if err != nil { - return nil, errors.Wrap(err, ErrCreatingVRFv2Key) - } - pubKeyCompressed := vrfKey.Data.ID - jobUUID := uuid.New() - os := &client.VRFV2TxPipelineSpec{ - Address: coordinator.Address(), - } - ost, err := os.String() - if err != nil { - return nil, errors.Wrap(err, ErrParseJob) - } - nativeTokenPrimaryKeyAddress, err := chainlinkNode.PrimaryEthAddress() - if err != nil { - return nil, errors.Wrap(err, ErrNodePrimaryKey) - } - job, err := chainlinkNode.MustCreateJob(&client.VRFV2JobSpec{ - Name: fmt.Sprintf("vrf-%s", jobUUID), - CoordinatorAddress: coordinator.Address(), - FromAddresses: []string{nativeTokenPrimaryKeyAddress}, - EVMChainID: c.GetChainID().String(), - MinIncomingConfirmations: int(minIncomingConfirmations), - PublicKey: pubKeyCompressed, - ExternalJobID: jobUUID.String(), - ObservationSource: ost, - BatchFulfillmentEnabled: false, - }) +func DeployVRFV2Consumers(contractDeployer contracts.ContractDeployer, coordinator contracts.VRFCoordinatorV2, consumerContractsAmount int) ([]contracts.VRFv2LoadTestConsumer, error) { + var consumers []contracts.VRFv2LoadTestConsumer + for i := 1; i <= consumerContractsAmount; i++ { + loadTestConsumer, err := contractDeployer.DeployVRFv2LoadTestConsumer(coordinator.Address()) if err != nil { - return nil, errors.Wrap(err, ErrCreatingVRFv2Job) + return nil, fmt.Errorf("%s, err %w", ErrAdvancedConsumer, err) } - provingKey, err := VRFV2RegisterProvingKey(vrfKey, nativeTokenPrimaryKeyAddress, coordinator) - if err != nil { - return nil, errors.Wrap(err, ErrCreatingProvingKey) - } - keyHash, err := coordinator.HashOfKey(context.Background(), provingKey) - if err != nil { - return nil, errors.Wrap(err, ErrCreatingProvingKeyHash) - } - ji := VRFV2JobInfo{ - Job: job, - VRFKey: vrfKey, - EncodedProvingKey: provingKey, - KeyHash: keyHash, - } - jobInfo = append(jobInfo, ji) + consumers = append(consumers, loadTestConsumer) } - return jobInfo, nil + return consumers, nil +} + +func CreateVRFV2Job( + chainlinkNode *client.ChainlinkClient, + coordinatorAddress string, + nativeTokenKeyAddresses []string, + pubKeyCompressed string, + chainID string, + minIncomingConfirmations uint16, +) (*client.Job, error) { + jobUUID := uuid.New() + os := &client.VRFV2TxPipelineSpec{ + Address: coordinatorAddress, + } + ost, err := os.String() + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrParseJob, err) + } + + job, err := chainlinkNode.MustCreateJob(&client.VRFV2JobSpec{ + Name: fmt.Sprintf("vrf-v2-%s", jobUUID), + CoordinatorAddress: coordinatorAddress, + FromAddresses: nativeTokenKeyAddresses, + EVMChainID: chainID, + MinIncomingConfirmations: int(minIncomingConfirmations), + PublicKey: pubKeyCompressed, + ExternalJobID: jobUUID.String(), + ObservationSource: ost, + BatchFulfillmentEnabled: false, + }) + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrCreatingVRFv2Job, err) + } + + return job, nil } func VRFV2RegisterProvingKey( @@ -125,110 +145,484 @@ func VRFV2RegisterProvingKey( ) (VRFV2EncodedProvingKey, error) { provingKey, err := actions.EncodeOnChainVRFProvingKey(*vrfKey) if err != nil { - return VRFV2EncodedProvingKey{}, errors.Wrap(err, ErrEncodingProvingKey) + return VRFV2EncodedProvingKey{}, fmt.Errorf("%s, err %w", ErrEncodingProvingKey, err) } err = coordinator.RegisterProvingKey( oracleAddress, provingKey, ) if err != nil { - return VRFV2EncodedProvingKey{}, errors.Wrap(err, ErrRegisterProvingKey) + return VRFV2EncodedProvingKey{}, fmt.Errorf("%s, err %w", ErrRegisterProvingKey, err) } return provingKey, nil } -func FundVRFCoordinatorV2Subscription(linkToken contracts.LinkToken, coordinator contracts.VRFCoordinatorV2, chainClient blockchain.EVMClient, subscriptionID uint64, linkFundingAmount *big.Int) error { +func FundVRFCoordinatorV2Subscription( + linkToken contracts.LinkToken, + coordinator contracts.VRFCoordinatorV2, + chainClient blockchain.EVMClient, + subscriptionID uint64, + linkFundingAmountJuels *big.Int, +) error { encodedSubId, err := chainlinkutils.ABIEncode(`[{"type":"uint64"}]`, subscriptionID) if err != nil { - return errors.Wrap(err, ErrABIEncodingFunding) + return fmt.Errorf("%s, err %w", ErrABIEncodingFunding, err) } - _, err = linkToken.TransferAndCall(coordinator.Address(), big.NewInt(0).Mul(linkFundingAmount, big.NewInt(1e18)), encodedSubId) + _, err = linkToken.TransferAndCall(coordinator.Address(), linkFundingAmountJuels, encodedSubId) if err != nil { - return errors.Wrap(err, ErrSendingLinkToken) + return fmt.Errorf("%s, err %w", ErrSendingLinkToken, err) } return chainClient.WaitForEvents() } -/* setup for load tests */ +// SetupVRFV2Environment will create specified number of subscriptions and add the same conumer/s to each of them +func SetupVRFV2Environment( + env *test_env.CLClusterTestEnv, + vrfv2Config vrfv2_config.VRFV2Config, + linkToken contracts.LinkToken, + mockNativeLINKFeed contracts.MockETHLINKFeed, + registerProvingKeyAgainstAddress string, + numberOfTxKeysToCreate int, + numberOfConsumers int, + numberOfSubToCreate int, + l zerolog.Logger, +) (*VRFV2Contracts, []uint64, *VRFV2Data, error) { + l.Info().Msg("Starting VRFV2 environment setup") + l.Info().Msg("Deploying VRFV2 contracts") + vrfv2Contracts, err := DeployVRFV2Contracts( + env.ContractDeployer, + env.EVMClient, + linkToken, + mockNativeLINKFeed, + numberOfConsumers, + ) + if err != nil { + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrDeployVRFV2Contracts, err) + } + vrfCoordinatorV2FeeConfig := vrf_coordinator_v2.VRFCoordinatorV2FeeConfig{ + FulfillmentFlatFeeLinkPPMTier1: vrfv2Config.FulfillmentFlatFeeLinkPPMTier1, + FulfillmentFlatFeeLinkPPMTier2: vrfv2Config.FulfillmentFlatFeeLinkPPMTier2, + FulfillmentFlatFeeLinkPPMTier3: vrfv2Config.FulfillmentFlatFeeLinkPPMTier3, + FulfillmentFlatFeeLinkPPMTier4: vrfv2Config.FulfillmentFlatFeeLinkPPMTier4, + FulfillmentFlatFeeLinkPPMTier5: vrfv2Config.FulfillmentFlatFeeLinkPPMTier5, + ReqsForTier2: big.NewInt(vrfv2Config.ReqsForTier2), + ReqsForTier3: big.NewInt(vrfv2Config.ReqsForTier3), + ReqsForTier4: big.NewInt(vrfv2Config.ReqsForTier4), + ReqsForTier5: big.NewInt(vrfv2Config.ReqsForTier5)} -func SetupLocalLoadTestEnv(nodesFunding *big.Float, subFundingLINK *big.Int) (*test_env.CLClusterTestEnv, *VRFV2Contracts, [32]byte, error) { - env, err := test_env.NewCLTestEnvBuilder(). - WithGeth(). - WithLogWatcher(). - WithMockAdapter(). - WithCLNodes(1). - WithFunding(nodesFunding). - WithLogWatcher(). - Build() + l.Info().Str("Coordinator", vrfv2Contracts.Coordinator.Address()).Msg("Setting Coordinator Config") + err = vrfv2Contracts.Coordinator.SetConfig( + vrfv2Config.MinimumConfirmations, + vrfv2Config.CallbackGasLimit, + vrfv2Config.StalenessSeconds, + vrfv2Config.GasAfterPaymentCalculation, + big.NewInt(vrfv2Config.LinkNativeFeedResponse), + vrfCoordinatorV2FeeConfig, + ) + if err != nil { + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrSetVRFCoordinatorConfig, err) + } + err = env.EVMClient.WaitForEvents() + if err != nil { + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + l.Info(). + Str("Coordinator", vrfv2Contracts.Coordinator.Address()). + Int("Number of Subs to create", numberOfSubToCreate). + Msg("Creating and funding subscriptions, adding consumers") + subIDs, err := CreateFundSubsAndAddConsumers( + env, + vrfv2Config, + linkToken, + vrfv2Contracts.Coordinator, vrfv2Contracts.LoadTestConsumers, numberOfSubToCreate) + if err != nil { + return nil, nil, nil, err + } + l.Info().Str("Node URL", env.ClCluster.NodeAPIs()[0].URL()).Msg("Creating VRF Key on the Node") + vrfKey, err := env.ClCluster.NodeAPIs()[0].MustCreateVRFKey() if err != nil { - return nil, nil, [32]byte{}, err + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrCreatingVRFv2Key, err) } - env.ParallelTransactions(true) + pubKeyCompressed := vrfKey.Data.ID - mockFeed, err := actions.DeployMockETHLinkFeed(env.ContractDeployer, vrfConst.LinkEthFeedResponse) + l.Info().Str("Coordinator", vrfv2Contracts.Coordinator.Address()).Msg("Registering Proving Key") + provingKey, err := VRFV2RegisterProvingKey(vrfKey, registerProvingKeyAgainstAddress, vrfv2Contracts.Coordinator) if err != nil { - return nil, nil, [32]byte{}, err + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrRegisteringProvingKey, err) } - lt, err := actions.DeployLINKToken(env.ContractDeployer) + keyHash, err := vrfv2Contracts.Coordinator.HashOfKey(context.Background(), provingKey) if err != nil { - return nil, nil, [32]byte{}, err + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrCreatingProvingKeyHash, err) } - vrfv2Contracts, err := DeployVRFV2Contracts(env.ContractDeployer, env.EVMClient, lt, mockFeed) + + chainID := env.EVMClient.GetChainID() + newNativeTokenKeyAddresses, err := CreateAndFundSendingKeys(env, vrfv2Config, numberOfTxKeysToCreate, chainID) if err != nil { - return nil, nil, [32]byte{}, err + return nil, nil, nil, err } - err = env.EVMClient.WaitForEvents() + nativeTokenPrimaryKeyAddress, err := env.ClCluster.NodeAPIs()[0].PrimaryEthAddress() if err != nil { - return nil, nil, [32]byte{}, err + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrNodePrimaryKey, err) } - err = vrfv2Contracts.Coordinator.SetConfig( - vrfConst.MinimumConfirmations, - vrfConst.MaxGasLimitVRFCoordinatorConfig, - vrfConst.StalenessSeconds, - vrfConst.GasAfterPaymentCalculation, - vrfConst.LinkEthFeedResponse, - vrfConst.VRFCoordinatorV2FeeConfig, + allNativeTokenKeyAddresses := append(newNativeTokenKeyAddresses, nativeTokenPrimaryKeyAddress) + + l.Info().Msg("Creating VRFV2 Job") + vrfV2job, err := CreateVRFV2Job( + env.ClCluster.NodeAPIs()[0], + vrfv2Contracts.Coordinator.Address(), + allNativeTokenKeyAddresses, + pubKeyCompressed, + chainID.String(), + vrfv2Config.MinimumConfirmations, ) if err != nil { - return nil, nil, [32]byte{}, err + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrCreateVRFV2Jobs, err) } + + // this part is here because VRFv2 can work with only a specific key + // [[EVM.KeySpecific]] + // Key = '...' + nodeConfig := node.NewConfig(env.ClCluster.Nodes[0].NodeConfig, + node.WithVRFv2EVMEstimator(allNativeTokenKeyAddresses, vrfv2Config.CLNodeMaxGasPriceGWei), + ) + l.Info().Msg("Restarting Node with new sending key PriceMax configuration") + err = env.ClCluster.Nodes[0].Restart(nodeConfig) + if err != nil { + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrRestartCLNode, err) + } + + vrfv2KeyData := VRFV2KeyData{ + VRFKey: vrfKey, + EncodedProvingKey: provingKey, + KeyHash: keyHash, + } + + data := VRFV2Data{ + vrfv2KeyData, + vrfV2job, + nativeTokenPrimaryKeyAddress, + chainID, + } + + l.Info().Msg("VRFV2 environment setup is finished") + return vrfv2Contracts, subIDs, &data, nil +} + +func CreateAndFundSendingKeys(env *test_env.CLClusterTestEnv, vrfv2Config vrfv2_config.VRFV2Config, numberOfNativeTokenAddressesToCreate int, chainID *big.Int) ([]string, error) { + var newNativeTokenKeyAddresses []string + for i := 0; i < numberOfNativeTokenAddressesToCreate; i++ { + newTxKey, response, err := env.ClCluster.NodeAPIs()[0].CreateTxKey("evm", chainID.String()) + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrNodeNewTxKey, err) + } + if response.StatusCode != 200 { + return nil, fmt.Errorf("error creating transaction key - response code, err %d", response.StatusCode) + } + newNativeTokenKeyAddresses = append(newNativeTokenKeyAddresses, newTxKey.Data.ID) + err = actions.FundAddress(env.EVMClient, newTxKey.Data.ID, big.NewFloat(vrfv2Config.ChainlinkNodeFunding)) + if err != nil { + return nil, err + } + } + return newNativeTokenKeyAddresses, nil +} + +func CreateFundSubsAndAddConsumers( + env *test_env.CLClusterTestEnv, + vrfv2Config vrfv2_config.VRFV2Config, + linkToken contracts.LinkToken, + coordinator contracts.VRFCoordinatorV2, + consumers []contracts.VRFv2LoadTestConsumer, + numberOfSubToCreate int, +) ([]uint64, error) { + subIDs, err := CreateSubsAndFund(env, vrfv2Config, linkToken, coordinator, numberOfSubToCreate) + if err != nil { + return nil, err + } + subToConsumersMap := map[uint64][]contracts.VRFv2LoadTestConsumer{} + + //each subscription will have the same consumers + for _, subID := range subIDs { + subToConsumersMap[subID] = consumers + } + + err = AddConsumersToSubs( + subToConsumersMap, + coordinator, + ) + if err != nil { + return nil, err + } + err = env.EVMClient.WaitForEvents() if err != nil { - return nil, nil, [32]byte{}, err + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } - err = vrfv2Contracts.Coordinator.CreateSubscription() + return subIDs, nil +} + +func CreateSubsAndFund( + env *test_env.CLClusterTestEnv, + vrfv2Config vrfv2_config.VRFV2Config, + linkToken contracts.LinkToken, + coordinator contracts.VRFCoordinatorV2, + subAmountToCreate int, +) ([]uint64, error) { + subs, err := CreateSubs(env, coordinator, subAmountToCreate) if err != nil { - return nil, nil, [32]byte{}, err + return nil, err } err = env.EVMClient.WaitForEvents() if err != nil { - return nil, nil, [32]byte{}, err + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } - err = vrfv2Contracts.Coordinator.AddConsumer(vrfConst.SubID, vrfv2Contracts.LoadTestConsumer.Address()) + err = FundSubscriptions(env, vrfv2Config, linkToken, coordinator, subs) if err != nil { - return nil, nil, [32]byte{}, err + return nil, err } - err = FundVRFCoordinatorV2Subscription(lt, vrfv2Contracts.Coordinator, env.EVMClient, vrfConst.SubID, subFundingLINK) + return subs, nil +} + +func CreateSubs( + env *test_env.CLClusterTestEnv, + coordinator contracts.VRFCoordinatorV2, + subAmountToCreate int, +) ([]uint64, error) { + var subIDArr []uint64 + + for i := 0; i < subAmountToCreate; i++ { + subID, err := CreateSubAndFindSubID(env, coordinator) + if err != nil { + return nil, err + } + subIDArr = append(subIDArr, subID) + } + return subIDArr, nil +} + +func AddConsumersToSubs( + subToConsumerMap map[uint64][]contracts.VRFv2LoadTestConsumer, + coordinator contracts.VRFCoordinatorV2, +) error { + for subID, consumers := range subToConsumerMap { + for _, consumer := range consumers { + err := coordinator.AddConsumer(subID, consumer.Address()) + if err != nil { + return fmt.Errorf("%s, err %w", ErrAddConsumerToSub, err) + } + } + } + return nil +} + +func CreateSubAndFindSubID(env *test_env.CLClusterTestEnv, coordinator contracts.VRFCoordinatorV2) (uint64, error) { + tx, err := coordinator.CreateSubscription() if err != nil { - return nil, nil, [32]byte{}, err + return 0, fmt.Errorf("%s, err %w", ErrCreateVRFSubscription, err) } - jobs, err := CreateVRFV2Jobs(env.ClCluster.NodeAPIs(), vrfv2Contracts.Coordinator, env.EVMClient, vrfConst.MinimumConfirmations) + err = env.EVMClient.WaitForEvents() if err != nil { - return nil, nil, [32]byte{}, err + return 0, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } - // this part is here because VRFv2 can work with only a specific key - // [[EVM.KeySpecific]] - // Key = '...' - addr, err := env.ClCluster.Nodes[0].API.PrimaryEthAddress() + + receipt, err := env.EVMClient.GetTxReceipt(tx.Hash()) if err != nil { - return nil, nil, [32]byte{}, err + return 0, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } - nodeConfig := node.NewConfig(env.ClCluster.Nodes[0].NodeConfig, - node.WithVRFv2EVMEstimator(addr), + + //SubscriptionsCreated Log should be emitted with the subscription ID + subID := receipt.Logs[0].Topics[1].Big().Uint64() + + return subID, nil +} + +func FundSubscriptions( + env *test_env.CLClusterTestEnv, + vrfv2Config vrfv2_config.VRFV2Config, + linkAddress contracts.LinkToken, + coordinator contracts.VRFCoordinatorV2, + subIDs []uint64, +) error { + for _, subID := range subIDs { + //Link Billing + amountJuels := conversions.EtherToWei(big.NewFloat(vrfv2Config.SubscriptionFundingAmountLink)) + err := FundVRFCoordinatorV2Subscription(linkAddress, coordinator, env.EVMClient, subID, amountJuels) + if err != nil { + return fmt.Errorf("%s, err %w", ErrFundSubWithLinkToken, err) + } + } + err := env.EVMClient.WaitForEvents() + if err != nil { + return fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + return nil +} + +func RequestRandomnessAndWaitForFulfillment( + consumer contracts.VRFv2LoadTestConsumer, + coordinator contracts.VRFCoordinatorV2, + vrfv2Data *VRFV2Data, + subID uint64, + randomnessRequestCountPerRequest uint16, + vrfv2Config vrfv2_config.VRFV2Config, + randomWordsFulfilledEventTimeout time.Duration, + l zerolog.Logger, +) (*vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled, error) { + logRandRequest(consumer.Address(), coordinator.Address(), subID, vrfv2Config, l) + _, err := consumer.RequestRandomness( + vrfv2Data.KeyHash, + subID, + vrfv2Config.MinimumConfirmations, + vrfv2Config.CallbackGasLimit, + vrfv2Config.NumberOfWords, + randomnessRequestCountPerRequest, ) - err = env.ClCluster.Nodes[0].Restart(nodeConfig) if err != nil { - return nil, nil, [32]byte{}, err + return nil, fmt.Errorf("%s, err %w", ErrRequestRandomness, err) } - return env, vrfv2Contracts, jobs[0].KeyHash, nil + + fulfillmentEvents, err := WaitForRequestAndFulfillmentEvents( + consumer.Address(), + coordinator, + vrfv2Data, + subID, + randomWordsFulfilledEventTimeout, + l, + ) + return fulfillmentEvents, err +} + +func WaitForRequestAndFulfillmentEvents( + consumerAddress string, + coordinator contracts.VRFCoordinatorV2, + vrfv2Data *VRFV2Data, + subID uint64, + randomWordsFulfilledEventTimeout time.Duration, + l zerolog.Logger, +) (*vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled, error) { + randomWordsRequestedEvent, err := coordinator.WaitForRandomWordsRequestedEvent( + [][32]byte{vrfv2Data.KeyHash}, + []uint64{subID}, + []common.Address{common.HexToAddress(consumerAddress)}, + time.Minute*1, + ) + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrWaitRandomWordsRequestedEvent, err) + } + + LogRandomnessRequestedEvent(l, coordinator, randomWordsRequestedEvent) + randomWordsFulfilledEvent, err := coordinator.WaitForRandomWordsFulfilledEvent( + []*big.Int{randomWordsRequestedEvent.RequestId}, + randomWordsFulfilledEventTimeout, + ) + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrWaitRandomWordsFulfilledEvent, err) + } + + LogRandomWordsFulfilledEvent(l, coordinator, randomWordsFulfilledEvent) + return randomWordsFulfilledEvent, err +} + +func WaitForRequestCountEqualToFulfilmentCount(consumer contracts.VRFv2LoadTestConsumer, timeout time.Duration, wg *sync.WaitGroup) (*big.Int, *big.Int, error) { + metricsChannel := make(chan *contracts.VRFLoadTestMetrics) + metricsErrorChannel := make(chan error) + + testContext, testCancel := context.WithTimeout(context.Background(), timeout) + defer testCancel() + + ticker := time.NewTicker(time.Second * 1) + var metrics *contracts.VRFLoadTestMetrics + for { + select { + case <-testContext.Done(): + ticker.Stop() + wg.Done() + return metrics.RequestCount, metrics.FulfilmentCount, + fmt.Errorf("timeout waiting for rand request and fulfilments to be equal AFTER performance test was executed. Request Count: %d, Fulfilment Count: %d", + metrics.RequestCount.Uint64(), metrics.FulfilmentCount.Uint64()) + case <-ticker.C: + go retrieveLoadTestMetrics(consumer, metricsChannel, metricsErrorChannel) + case metrics = <-metricsChannel: + if metrics.RequestCount.Cmp(metrics.FulfilmentCount) == 0 { + ticker.Stop() + wg.Done() + return metrics.RequestCount, metrics.FulfilmentCount, nil + } + case err := <-metricsErrorChannel: + ticker.Stop() + wg.Done() + return nil, nil, err + } + } +} + +func retrieveLoadTestMetrics( + consumer contracts.VRFv2LoadTestConsumer, + metricsChannel chan *contracts.VRFLoadTestMetrics, + metricsErrorChannel chan error, +) { + metrics, err := consumer.GetLoadTestMetrics(context.Background()) + if err != nil { + metricsErrorChannel <- err + } + metricsChannel <- metrics +} + +func LogSubDetails(l zerolog.Logger, subscription vrf_coordinator_v2.GetSubscription, subID uint64, coordinator contracts.VRFCoordinatorV2) { + l.Debug(). + Str("Coordinator", coordinator.Address()). + Str("Link Balance", (*commonassets.Link)(subscription.Balance).Link()). + Uint64("Subscription ID", subID). + Str("Subscription Owner", subscription.Owner.String()). + Interface("Subscription Consumers", subscription.Consumers). + Msg("Subscription Data") +} + +func LogRandomnessRequestedEvent( + l zerolog.Logger, + coordinator contracts.VRFCoordinatorV2, + randomWordsRequestedEvent *vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested, +) { + l.Debug(). + Str("Coordinator", coordinator.Address()). + Str("Request ID", randomWordsRequestedEvent.RequestId.String()). + Uint64("Subscription ID", randomWordsRequestedEvent.SubId). + Str("Sender Address", randomWordsRequestedEvent.Sender.String()). + Interface("Keyhash", randomWordsRequestedEvent.KeyHash). + Uint32("Callback Gas Limit", randomWordsRequestedEvent.CallbackGasLimit). + Uint32("Number of Words", randomWordsRequestedEvent.NumWords). + Uint16("Minimum Request Confirmations", randomWordsRequestedEvent.MinimumRequestConfirmations). + Msg("RandomnessRequested Event") +} + +func LogRandomWordsFulfilledEvent( + l zerolog.Logger, + coordinator contracts.VRFCoordinatorV2, + randomWordsFulfilledEvent *vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled, +) { + l.Debug(). + Str("Coordinator", coordinator.Address()). + Str("Total Payment", randomWordsFulfilledEvent.Payment.String()). + Str("TX Hash", randomWordsFulfilledEvent.Raw.TxHash.String()). + Str("Request ID", randomWordsFulfilledEvent.RequestId.String()). + Bool("Success", randomWordsFulfilledEvent.Success). + Msg("RandomWordsFulfilled Event (TX metadata)") +} + +func logRandRequest( + consumer string, + coordinator string, + subID uint64, + vrfv2Config vrfv2_config.VRFV2Config, + l zerolog.Logger, +) { + l.Debug(). + Str("Consumer", consumer). + Str("Coordinator", coordinator). + Uint64("SubID", subID). + Uint16("MinimumConfirmations", vrfv2Config.MinimumConfirmations). + Uint32("CallbackGasLimit", vrfv2Config.CallbackGasLimit). + Uint16("RandomnessRequestCountPerRequest", vrfv2Config.RandomnessRequestCountPerRequest). + Uint16("RandomnessRequestCountPerRequestDeviation", vrfv2Config.RandomnessRequestCountPerRequestDeviation). + Msg("Requesting randomness") } diff --git a/integration-tests/actions/vrfv2plus/vrfv2plus_config/config.go b/integration-tests/actions/vrfv2plus/vrfv2plus_config/config.go index 7a1221eaf8b..8cd6e0a8ce8 100644 --- a/integration-tests/actions/vrfv2plus/vrfv2plus_config/config.go +++ b/integration-tests/actions/vrfv2plus/vrfv2plus_config/config.go @@ -4,11 +4,12 @@ import "time" type VRFV2PlusConfig struct { ChainlinkNodeFunding float64 `envconfig:"CHAINLINK_NODE_FUNDING" default:".1"` // Amount of native currency to fund each chainlink node with + CLNodeMaxGasPriceGWei int64 `envconfig:"MAX_GAS_PRICE_GWEI" default:"1000"` // Max gas price in GWei for the chainlink node IsNativePayment bool `envconfig:"IS_NATIVE_PAYMENT" default:"false"` // Whether to use native payment or LINK token LinkNativeFeedResponse int64 `envconfig:"LINK_NATIVE_FEED_RESPONSE" default:"1000000000000000000"` // Response of the LINK/ETH feed MinimumConfirmations uint16 `envconfig:"MINIMUM_CONFIRMATIONS" default:"3"` // Minimum number of confirmations for the VRF Coordinator - SubscriptionFundingAmountLink int64 `envconfig:"SUBSCRIPTION_FUNDING_AMOUNT_LINK" default:"10"` // Amount of LINK to fund the subscription with - SubscriptionFundingAmountNative int64 `envconfig:"SUBSCRIPTION_FUNDING_AMOUNT_NATIVE" default:"1"` // Amount of native currency to fund the subscription with + SubscriptionFundingAmountLink float64 `envconfig:"SUBSCRIPTION_FUNDING_AMOUNT_LINK" default:"5"` // Amount of LINK to fund the subscription with + SubscriptionFundingAmountNative float64 `envconfig:"SUBSCRIPTION_FUNDING_AMOUNT_NATIVE" default:"1"` // Amount of native currency to fund the subscription with NumberOfWords uint32 `envconfig:"NUMBER_OF_WORDS" default:"3"` // Number of words to request CallbackGasLimit uint32 `envconfig:"CALLBACK_GAS_LIMIT" default:"1000000"` // Gas limit for the callback MaxGasLimitCoordinatorConfig uint32 `envconfig:"MAX_GAS_LIMIT_COORDINATOR_CONFIG" default:"2500000"` // Max gas limit for the VRF Coordinator config @@ -18,9 +19,13 @@ type VRFV2PlusConfig struct { FulfillmentFlatFeeLinkPPM uint32 `envconfig:"FULFILLMENT_FLAT_FEE_LINK_PPM" default:"500"` // Flat fee in ppm for LINK for the VRF Coordinator config FulfillmentFlatFeeNativePPM uint32 `envconfig:"FULFILLMENT_FLAT_FEE_NATIVE_PPM" default:"500"` // Flat fee in ppm for native currency for the VRF Coordinator config + NumberOfSubToCreate int `envconfig:"NUMBER_OF_SUB_TO_CREATE" default:"1"` // Number of subscriptions to create + RandomnessRequestCountPerRequest uint16 `envconfig:"RANDOMNESS_REQUEST_COUNT_PER_REQUEST" default:"1"` // How many randomness requests to send per request RandomnessRequestCountPerRequestDeviation uint16 `envconfig:"RANDOMNESS_REQUEST_COUNT_PER_REQUEST_DEVIATION" default:"0"` // How many randomness requests to send per request + RandomWordsFulfilledEventTimeout time.Duration `envconfig:"RANDOM_WORDS_FULFILLED_EVENT_TIMEOUT" default:"2m"` // How long to wait for the RandomWordsFulfilled event to be emitted + //Wrapper Config WrapperGasOverhead uint32 `envconfig:"WRAPPER_GAS_OVERHEAD" default:"50000"` CoordinatorGasOverhead uint32 `envconfig:"COORDINATOR_GAS_OVERHEAD" default:"52000"` @@ -37,6 +42,7 @@ type VRFV2PlusConfig struct { UseExistingEnv bool `envconfig:"USE_EXISTING_ENV" default:"false"` // Whether to use an existing environment or create a new one CoordinatorAddress string `envconfig:"COORDINATOR_ADDRESS" default:""` // Coordinator address ConsumerAddress string `envconfig:"CONSUMER_ADDRESS" default:""` // Consumer address + LinkAddress string `envconfig:"LINK_ADDRESS" default:""` // Link address SubID string `envconfig:"SUB_ID" default:""` // Subscription ID KeyHash string `envconfig:"KEY_HASH" default:""` } diff --git a/integration-tests/actions/vrfv2plus/vrfv2plus_steps.go b/integration-tests/actions/vrfv2plus/vrfv2plus_steps.go index 3bfa5d4f416..70320530aa8 100644 --- a/integration-tests/actions/vrfv2plus/vrfv2plus_steps.go +++ b/integration-tests/actions/vrfv2plus/vrfv2plus_steps.go @@ -7,13 +7,15 @@ import ( "sync" "time" - "github.com/smartcontractkit/chainlink/v2/core/assets" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink-testing-framework/utils/conversions" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrfv2plus_wrapper_load_test_consumer" "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" - "github.com/pkg/errors" "github.com/rs/zerolog" + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2plus/vrfv2plus_config" @@ -28,6 +30,7 @@ import ( var ( ErrNodePrimaryKey = "error getting node's primary ETH key" + ErrNodeNewTxKey = "error creating node's EVM transaction key" ErrCreatingProvingKeyHash = "error creating a keyHash from the proving key" ErrRegisteringProvingKey = "error registering a proving key on Coordinator contract" ErrRegisterProvingKey = "error registering proving keys" @@ -43,7 +46,6 @@ var ( ErrDeployVRFV2_5Contracts = "error deploying VRFV2_5 contracts" ErrSetVRFCoordinatorConfig = "error setting config for VRF Coordinator contract" ErrCreateVRFSubscription = "error creating VRF Subscription" - ErrFindSubID = "error finding created subscription ID" ErrAddConsumerToSub = "error adding consumer to VRF Subscription" ErrFundSubWithNativeToken = "error funding subscription with native token" ErrSetLinkNativeLinkFeed = "error setting Link and ETH/LINK feed for VRF Coordinator contract" @@ -70,19 +72,19 @@ func DeployVRFV2_5Contracts( ) (*VRFV2_5Contracts, error) { bhs, err := contractDeployer.DeployBlockhashStore() if err != nil { - return nil, errors.Wrap(err, ErrDeployBlockHashStore) + return nil, fmt.Errorf("%s, err %w", ErrDeployBlockHashStore, err) } err = chainClient.WaitForEvents() if err != nil { - return nil, errors.Wrap(err, ErrWaitTXsComplete) + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } coordinator, err := contractDeployer.DeployVRFCoordinatorV2_5(bhs.Address()) if err != nil { - return nil, errors.Wrap(err, ErrDeployCoordinator) + return nil, fmt.Errorf("%s, err %w", ErrDeployCoordinator, err) } err = chainClient.WaitForEvents() if err != nil { - return nil, errors.Wrap(err, ErrWaitTXsComplete) + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } consumers, err := DeployVRFV2PlusConsumers(contractDeployer, coordinator, consumerContractsAmount) if err != nil { @@ -90,58 +92,17 @@ func DeployVRFV2_5Contracts( } err = chainClient.WaitForEvents() if err != nil { - return nil, errors.Wrap(err, ErrWaitTXsComplete) + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } return &VRFV2_5Contracts{coordinator, bhs, consumers}, nil } -func DeployVRFV2PlusDirectFundingContracts( - contractDeployer contracts.ContractDeployer, - chainClient blockchain.EVMClient, - linkTokenAddress string, - linkEthFeedAddress string, - coordinator contracts.VRFCoordinatorV2_5, - consumerContractsAmount int, -) (*VRFV2PlusWrapperContracts, error) { - - vrfv2PlusWrapper, err := contractDeployer.DeployVRFV2PlusWrapper(linkTokenAddress, linkEthFeedAddress, coordinator.Address()) - if err != nil { - return nil, errors.Wrap(err, ErrDeployWrapper) - } - err = chainClient.WaitForEvents() - if err != nil { - return nil, errors.Wrap(err, ErrWaitTXsComplete) - } - - consumers, err := DeployVRFV2PlusWrapperConsumers(contractDeployer, linkTokenAddress, vrfv2PlusWrapper, consumerContractsAmount) - if err != nil { - return nil, err - } - err = chainClient.WaitForEvents() - if err != nil { - return nil, errors.Wrap(err, ErrWaitTXsComplete) - } - return &VRFV2PlusWrapperContracts{vrfv2PlusWrapper, consumers}, nil -} - func DeployVRFV2PlusConsumers(contractDeployer contracts.ContractDeployer, coordinator contracts.VRFCoordinatorV2_5, consumerContractsAmount int) ([]contracts.VRFv2PlusLoadTestConsumer, error) { var consumers []contracts.VRFv2PlusLoadTestConsumer for i := 1; i <= consumerContractsAmount; i++ { loadTestConsumer, err := contractDeployer.DeployVRFv2PlusLoadTestConsumer(coordinator.Address()) if err != nil { - return nil, errors.Wrap(err, ErrAdvancedConsumer) - } - consumers = append(consumers, loadTestConsumer) - } - return consumers, nil -} - -func DeployVRFV2PlusWrapperConsumers(contractDeployer contracts.ContractDeployer, linkTokenAddress string, vrfV2PlusWrapper contracts.VRFV2PlusWrapper, consumerContractsAmount int) ([]contracts.VRFv2PlusWrapperLoadTestConsumer, error) { - var consumers []contracts.VRFv2PlusWrapperLoadTestConsumer - for i := 1; i <= consumerContractsAmount; i++ { - loadTestConsumer, err := contractDeployer.DeployVRFV2PlusWrapperLoadTestConsumer(linkTokenAddress, vrfV2PlusWrapper.Address()) - if err != nil { - return nil, errors.Wrap(err, ErrAdvancedConsumer) + return nil, fmt.Errorf("%s, err %w", ErrAdvancedConsumer, err) } consumers = append(consumers, loadTestConsumer) } @@ -151,7 +112,7 @@ func DeployVRFV2PlusWrapperConsumers(contractDeployer contracts.ContractDeployer func CreateVRFV2PlusJob( chainlinkNode *client.ChainlinkClient, coordinatorAddress string, - nativeTokenPrimaryKeyAddress string, + nativeTokenKeyAddresses []string, pubKeyCompressed string, chainID string, minIncomingConfirmations uint16, @@ -162,22 +123,23 @@ func CreateVRFV2PlusJob( } ost, err := os.String() if err != nil { - return nil, errors.Wrap(err, ErrParseJob) + return nil, fmt.Errorf("%s, err %w", ErrParseJob, err) } job, err := chainlinkNode.MustCreateJob(&client.VRFV2PlusJobSpec{ Name: fmt.Sprintf("vrf-v2-plus-%s", jobUUID), CoordinatorAddress: coordinatorAddress, - FromAddresses: []string{nativeTokenPrimaryKeyAddress}, + FromAddresses: nativeTokenKeyAddresses, EVMChainID: chainID, MinIncomingConfirmations: int(minIncomingConfirmations), PublicKey: pubKeyCompressed, ExternalJobID: jobUUID.String(), ObservationSource: ost, BatchFulfillmentEnabled: false, + PollPeriod: time.Second, }) if err != nil { - return nil, errors.Wrap(err, ErrCreatingVRFv2PlusJob) + return nil, fmt.Errorf("%s, err %w", ErrCreatingVRFv2PlusJob, err) } return job, nil @@ -190,14 +152,14 @@ func VRFV2_5RegisterProvingKey( ) (VRFV2PlusEncodedProvingKey, error) { provingKey, err := actions.EncodeOnChainVRFProvingKey(*vrfKey) if err != nil { - return VRFV2PlusEncodedProvingKey{}, errors.Wrap(err, ErrEncodingProvingKey) + return VRFV2PlusEncodedProvingKey{}, fmt.Errorf("%s, err %w", ErrEncodingProvingKey, err) } err = coordinator.RegisterProvingKey( oracleAddress, provingKey, ) if err != nil { - return VRFV2PlusEncodedProvingKey{}, errors.Wrap(err, ErrRegisterProvingKey) + return VRFV2PlusEncodedProvingKey{}, fmt.Errorf("%s, err %w", ErrRegisterProvingKey, err) } return provingKey, nil } @@ -209,26 +171,32 @@ func VRFV2PlusUpgradedVersionRegisterProvingKey( ) (VRFV2PlusEncodedProvingKey, error) { provingKey, err := actions.EncodeOnChainVRFProvingKey(*vrfKey) if err != nil { - return VRFV2PlusEncodedProvingKey{}, errors.Wrap(err, ErrEncodingProvingKey) + return VRFV2PlusEncodedProvingKey{}, fmt.Errorf("%s, err %w", ErrEncodingProvingKey, err) } err = coordinator.RegisterProvingKey( oracleAddress, provingKey, ) if err != nil { - return VRFV2PlusEncodedProvingKey{}, errors.Wrap(err, ErrRegisterProvingKey) + return VRFV2PlusEncodedProvingKey{}, fmt.Errorf("%s, err %w", ErrRegisterProvingKey, err) } return provingKey, nil } -func FundVRFCoordinatorV2_5Subscription(linkToken contracts.LinkToken, coordinator contracts.VRFCoordinatorV2_5, chainClient blockchain.EVMClient, subscriptionID *big.Int, linkFundingAmount *big.Int) error { +func FundVRFCoordinatorV2_5Subscription( + linkToken contracts.LinkToken, + coordinator contracts.VRFCoordinatorV2_5, + chainClient blockchain.EVMClient, + subscriptionID *big.Int, + linkFundingAmountJuels *big.Int, +) error { encodedSubId, err := chainlinkutils.ABIEncode(`[{"type":"uint256"}]`, subscriptionID) if err != nil { - return errors.Wrap(err, ErrABIEncodingFunding) + return fmt.Errorf("%s, err %w", ErrABIEncodingFunding, err) } - _, err = linkToken.TransferAndCall(coordinator.Address(), big.NewInt(0).Mul(linkFundingAmount, big.NewInt(1e18)), encodedSubId) + _, err = linkToken.TransferAndCall(coordinator.Address(), linkFundingAmountJuels, encodedSubId) if err != nil { - return errors.Wrap(err, ErrSendingLinkToken) + return fmt.Errorf("%s, err %w", ErrSendingLinkToken, err) } return chainClient.WaitForEvents() } @@ -236,18 +204,23 @@ func FundVRFCoordinatorV2_5Subscription(linkToken contracts.LinkToken, coordinat // SetupVRFV2_5Environment will create specified number of subscriptions and add the same conumer/s to each of them func SetupVRFV2_5Environment( env *test_env.CLClusterTestEnv, - vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, linkToken contracts.LinkToken, mockNativeLINKFeed contracts.MockETHLINKFeed, + registerProvingKeyAgainstAddress string, + numberOfTxKeysToCreate int, numberOfConsumers int, numberOfSubToCreate int, + l zerolog.Logger, ) (*VRFV2_5Contracts, []*big.Int, *VRFV2PlusData, error) { - + l.Info().Msg("Starting VRFV2 Plus environment setup") + l.Info().Msg("Deploying VRFV2 Plus contracts") vrfv2_5Contracts, err := DeployVRFV2_5Contracts(env.ContractDeployer, env.EVMClient, numberOfConsumers) if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrDeployVRFV2_5Contracts) + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrDeployVRFV2_5Contracts, err) } + l.Info().Str("Coordinator", vrfv2_5Contracts.Coordinator.Address()).Msg("Setting Coordinator Config") err = vrfv2_5Contracts.Coordinator.SetConfig( vrfv2PlusConfig.MinimumConfirmations, vrfv2PlusConfig.MaxGasLimitCoordinatorConfig, @@ -260,89 +233,82 @@ func SetupVRFV2_5Environment( }, ) if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrSetVRFCoordinatorConfig) + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrSetVRFCoordinatorConfig, err) } + l.Info().Str("Coordinator", vrfv2_5Contracts.Coordinator.Address()).Msg("Setting Link and ETH/LINK feed") err = vrfv2_5Contracts.Coordinator.SetLINKAndLINKNativeFeed(linkToken.Address(), mockNativeLINKFeed.Address()) if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrSetLinkNativeLinkFeed) + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrSetLinkNativeLinkFeed, err) } err = env.EVMClient.WaitForEvents() if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrWaitTXsComplete) - } - - subIDs, err := CreateSubsAndFund(env, vrfv2PlusConfig, linkToken, vrfv2_5Contracts, numberOfSubToCreate) - if err != nil { - return nil, nil, nil, err - } - - subToConsumersMap := map[*big.Int][]contracts.VRFv2PlusLoadTestConsumer{} - - //each subscription will have the same consumers - for _, subID := range subIDs { - subToConsumersMap[subID] = vrfv2_5Contracts.LoadTestConsumers - } - - err = AddConsumersToSubs( - subToConsumersMap, - vrfv2_5Contracts.Coordinator, - ) + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + l.Info(). + Str("Coordinator", vrfv2_5Contracts.Coordinator.Address()). + Int("Number of Subs to create", numberOfSubToCreate). + Msg("Creating and funding subscriptions, adding consumers") + subIDs, err := CreateFundSubsAndAddConsumers( + env, + vrfv2PlusConfig, + linkToken, + vrfv2_5Contracts.Coordinator, vrfv2_5Contracts.LoadTestConsumers, numberOfSubToCreate) if err != nil { return nil, nil, nil, err } - - err = env.EVMClient.WaitForEvents() - if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrWaitTXsComplete) - } - + l.Info().Str("Node URL", env.ClCluster.NodeAPIs()[0].URL()).Msg("Creating VRF Key on the Node") vrfKey, err := env.ClCluster.NodeAPIs()[0].MustCreateVRFKey() if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrCreatingVRFv2PlusKey) + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrCreatingVRFv2PlusKey, err) } pubKeyCompressed := vrfKey.Data.ID - nativeTokenPrimaryKeyAddress, err := env.ClCluster.NodeAPIs()[0].PrimaryEthAddress() - if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrNodePrimaryKey) - } - provingKey, err := VRFV2_5RegisterProvingKey(vrfKey, nativeTokenPrimaryKeyAddress, vrfv2_5Contracts.Coordinator) + l.Info().Str("Coordinator", vrfv2_5Contracts.Coordinator.Address()).Msg("Registering Proving Key") + provingKey, err := VRFV2_5RegisterProvingKey(vrfKey, registerProvingKeyAgainstAddress, vrfv2_5Contracts.Coordinator) if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrRegisteringProvingKey) + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrRegisteringProvingKey, err) } keyHash, err := vrfv2_5Contracts.Coordinator.HashOfKey(context.Background(), provingKey) if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrCreatingProvingKeyHash) + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrCreatingProvingKeyHash, err) } chainID := env.EVMClient.GetChainID() + newNativeTokenKeyAddresses, err := CreateAndFundSendingKeys(env, vrfv2PlusConfig, numberOfTxKeysToCreate, chainID) + if err != nil { + return nil, nil, nil, err + } + nativeTokenPrimaryKeyAddress, err := env.ClCluster.NodeAPIs()[0].PrimaryEthAddress() + if err != nil { + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrNodePrimaryKey, err) + } + allNativeTokenKeyAddresses := append(newNativeTokenKeyAddresses, nativeTokenPrimaryKeyAddress) + l.Info().Msg("Creating VRFV2 Plus Job") job, err := CreateVRFV2PlusJob( env.ClCluster.NodeAPIs()[0], vrfv2_5Contracts.Coordinator.Address(), - nativeTokenPrimaryKeyAddress, + allNativeTokenKeyAddresses, pubKeyCompressed, chainID.String(), vrfv2PlusConfig.MinimumConfirmations, ) if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrCreateVRFV2PlusJobs) + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrCreateVRFV2PlusJobs, err) } // this part is here because VRFv2 can work with only a specific key // [[EVM.KeySpecific]] // Key = '...' - addr, err := env.ClCluster.Nodes[0].API.PrimaryEthAddress() - if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrGetPrimaryKey) - } nodeConfig := node.NewConfig(env.ClCluster.Nodes[0].NodeConfig, - node.WithVRFv2EVMEstimator(addr), + node.WithLogPollInterval(1*time.Second), + node.WithVRFv2EVMEstimator(allNativeTokenKeyAddresses, vrfv2PlusConfig.CLNodeMaxGasPriceGWei), ) + l.Info().Msg("Restarting Node with new sending key PriceMax configuration and log poll period configuration") err = env.ClCluster.Nodes[0].Restart(nodeConfig) if err != nil { - return nil, nil, nil, errors.Wrap(err, ErrRestartCLNode) + return nil, nil, nil, fmt.Errorf("%s, err %w", ErrRestartCLNode, err) } vrfv2PlusKeyData := VRFV2PlusKeyData{ @@ -358,25 +324,79 @@ func SetupVRFV2_5Environment( chainID, } + l.Info().Msg("VRFV2 Plus environment setup is finished") return vrfv2_5Contracts, subIDs, &data, nil } +func CreateAndFundSendingKeys(env *test_env.CLClusterTestEnv, vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, numberOfNativeTokenAddressesToCreate int, chainID *big.Int) ([]string, error) { + var newNativeTokenKeyAddresses []string + for i := 0; i < numberOfNativeTokenAddressesToCreate; i++ { + newTxKey, response, err := env.ClCluster.NodeAPIs()[0].CreateTxKey("evm", chainID.String()) + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrNodeNewTxKey, err) + } + if response.StatusCode != 200 { + return nil, fmt.Errorf("error creating transaction key - response code, err %d", response.StatusCode) + } + newNativeTokenKeyAddresses = append(newNativeTokenKeyAddresses, newTxKey.Data.ID) + err = actions.FundAddress(env.EVMClient, newTxKey.Data.ID, big.NewFloat(vrfv2PlusConfig.ChainlinkNodeFunding)) + if err != nil { + return nil, err + } + } + return newNativeTokenKeyAddresses, nil +} + +func CreateFundSubsAndAddConsumers( + env *test_env.CLClusterTestEnv, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, + linkToken contracts.LinkToken, + coordinator contracts.VRFCoordinatorV2_5, + consumers []contracts.VRFv2PlusLoadTestConsumer, + numberOfSubToCreate int, +) ([]*big.Int, error) { + subIDs, err := CreateSubsAndFund(env, vrfv2PlusConfig, linkToken, coordinator, numberOfSubToCreate) + if err != nil { + return nil, err + } + subToConsumersMap := map[*big.Int][]contracts.VRFv2PlusLoadTestConsumer{} + + //each subscription will have the same consumers + for _, subID := range subIDs { + subToConsumersMap[subID] = consumers + } + + err = AddConsumersToSubs( + subToConsumersMap, + coordinator, + ) + if err != nil { + return nil, err + } + + err = env.EVMClient.WaitForEvents() + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + return subIDs, nil +} + func CreateSubsAndFund( env *test_env.CLClusterTestEnv, - vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, linkToken contracts.LinkToken, - vrfv2_5Contracts *VRFV2_5Contracts, + coordinator contracts.VRFCoordinatorV2_5, subAmountToCreate int, ) ([]*big.Int, error) { - subs, err := CreateSubs(env, vrfv2_5Contracts.Coordinator, subAmountToCreate) + subs, err := CreateSubs(env, coordinator, subAmountToCreate) if err != nil { return nil, err } err = env.EVMClient.WaitForEvents() if err != nil { - return nil, errors.Wrap(err, ErrWaitTXsComplete) + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } - err = FundSubscriptions(env, vrfv2PlusConfig, linkToken, vrfv2_5Contracts.Coordinator, subs) + err = FundSubscriptions(env, vrfv2PlusConfig, linkToken, coordinator, subs) if err != nil { return nil, err } @@ -408,126 +428,73 @@ func AddConsumersToSubs( for _, consumer := range consumers { err := coordinator.AddConsumer(subID, consumer.Address()) if err != nil { - return errors.Wrap(err, ErrAddConsumerToSub) + return fmt.Errorf("%s, err %w", ErrAddConsumerToSub, err) } } } return nil } -func SetupVRFV2PlusWrapperEnvironment( - env *test_env.CLClusterTestEnv, - vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, - linkToken contracts.LinkToken, - mockNativeLINKFeed contracts.MockETHLINKFeed, - coordinator contracts.VRFCoordinatorV2_5, - keyHash [32]byte, - wrapperConsumerContractsAmount int, -) (*VRFV2PlusWrapperContracts, *big.Int, error) { - - wrapperContracts, err := DeployVRFV2PlusDirectFundingContracts( - env.ContractDeployer, - env.EVMClient, - linkToken.Address(), - mockNativeLINKFeed.Address(), - coordinator, - wrapperConsumerContractsAmount, - ) - if err != nil { - return nil, nil, err - } - - err = env.EVMClient.WaitForEvents() - - if err != nil { - return nil, nil, errors.Wrap(err, ErrWaitTXsComplete) - } - err = wrapperContracts.VRFV2PlusWrapper.SetConfig( - vrfv2PlusConfig.WrapperGasOverhead, - vrfv2PlusConfig.CoordinatorGasOverhead, - vrfv2PlusConfig.WrapperPremiumPercentage, - keyHash, - vrfv2PlusConfig.WrapperMaxNumberOfWords, - vrfv2PlusConfig.StalenessSeconds, - big.NewInt(vrfv2PlusConfig.FallbackWeiPerUnitLink), - vrfv2PlusConfig.FulfillmentFlatFeeLinkPPM, - vrfv2PlusConfig.FulfillmentFlatFeeNativePPM, - ) +func CreateSubAndFindSubID(env *test_env.CLClusterTestEnv, coordinator contracts.VRFCoordinatorV2_5) (*big.Int, error) { + tx, err := coordinator.CreateSubscription() if err != nil { - return nil, nil, err + return nil, fmt.Errorf("%s, err %w", ErrCreateVRFSubscription, err) } - err = env.EVMClient.WaitForEvents() if err != nil { - return nil, nil, errors.Wrap(err, ErrWaitTXsComplete) + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } - //fund sub - wrapperSubID, err := wrapperContracts.VRFV2PlusWrapper.GetSubID(context.Background()) + receipt, err := env.EVMClient.GetTxReceipt(tx.Hash()) if err != nil { - return nil, nil, err + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } - err = env.EVMClient.WaitForEvents() - if err != nil { - return nil, nil, errors.Wrap(err, ErrWaitTXsComplete) - } + //SubscriptionsCreated Log should be emitted with the subscription ID + subID := receipt.Logs[0].Topics[1].Big() - err = FundSubscriptions(env, vrfv2PlusConfig, linkToken, coordinator, []*big.Int{wrapperSubID}) - if err != nil { - return nil, nil, err - } - - //fund consumer with Link - err = linkToken.Transfer( - wrapperContracts.LoadTestConsumers[0].Address(), - big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(vrfv2PlusConfig.WrapperConsumerFundingAmountLink)), - ) - if err != nil { - return nil, nil, err - } - err = env.EVMClient.WaitForEvents() - if err != nil { - return nil, nil, errors.Wrap(err, ErrWaitTXsComplete) - } - - //fund consumer with Eth - err = wrapperContracts.LoadTestConsumers[0].Fund(big.NewFloat(vrfv2PlusConfig.WrapperConsumerFundingAmountNativeToken)) - if err != nil { - return nil, nil, err - } - err = env.EVMClient.WaitForEvents() - if err != nil { - return nil, nil, errors.Wrap(err, ErrWaitTXsComplete) - } - return wrapperContracts, wrapperSubID, nil + return subID, nil } -func CreateSubAndFindSubID(env *test_env.CLClusterTestEnv, coordinator contracts.VRFCoordinatorV2_5) (*big.Int, error) { - err := coordinator.CreateSubscription() - if err != nil { - return nil, errors.Wrap(err, ErrCreateVRFSubscription) - } - sub, err := coordinator.WaitForSubscriptionCreatedEvent(time.Second * 10) - if err != nil { - return nil, errors.Wrap(err, ErrFindSubID) +func FundSubscriptions( + env *test_env.CLClusterTestEnv, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, + linkAddress contracts.LinkToken, + coordinator contracts.VRFCoordinatorV2_5, + subIDs []*big.Int, +) error { + for _, subID := range subIDs { + //Native Billing + amountWei := conversions.EtherToWei(big.NewFloat(vrfv2PlusConfig.SubscriptionFundingAmountNative)) + err := coordinator.FundSubscriptionWithNative( + subID, + amountWei, + ) + if err != nil { + return fmt.Errorf("%s, err %w", ErrFundSubWithNativeToken, err) + } + //Link Billing + amountJuels := conversions.EtherToWei(big.NewFloat(vrfv2PlusConfig.SubscriptionFundingAmountLink)) + err = FundVRFCoordinatorV2_5Subscription(linkAddress, coordinator, env.EVMClient, subID, amountJuels) + if err != nil { + return fmt.Errorf("%s, err %w", ErrFundSubWithLinkToken, err) + } } - - err = env.EVMClient.WaitForEvents() + err := env.EVMClient.WaitForEvents() if err != nil { - return nil, errors.Wrap(err, ErrWaitTXsComplete) + return fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) } - return sub.SubId, nil + return nil } func GetUpgradedCoordinatorTotalBalance(coordinator contracts.VRFCoordinatorV2PlusUpgradedVersion) (linkTotalBalance *big.Int, nativeTokenTotalBalance *big.Int, err error) { linkTotalBalance, err = coordinator.GetLinkTotalBalance(context.Background()) if err != nil { - return nil, nil, errors.Wrap(err, ErrLinkTotalBalance) + return nil, nil, fmt.Errorf("%s, err %w", ErrLinkTotalBalance, err) } nativeTokenTotalBalance, err = coordinator.GetNativeTokenTotalBalance(context.Background()) if err != nil { - return nil, nil, errors.Wrap(err, ErrNativeTokenBalance) + return nil, nil, fmt.Errorf("%s, err %w", ErrNativeTokenBalance, err) } return } @@ -535,41 +502,15 @@ func GetUpgradedCoordinatorTotalBalance(coordinator contracts.VRFCoordinatorV2Pl func GetCoordinatorTotalBalance(coordinator contracts.VRFCoordinatorV2_5) (linkTotalBalance *big.Int, nativeTokenTotalBalance *big.Int, err error) { linkTotalBalance, err = coordinator.GetLinkTotalBalance(context.Background()) if err != nil { - return nil, nil, errors.Wrap(err, ErrLinkTotalBalance) + return nil, nil, fmt.Errorf("%s, err %w", ErrLinkTotalBalance, err) } nativeTokenTotalBalance, err = coordinator.GetNativeTokenTotalBalance(context.Background()) if err != nil { - return nil, nil, errors.Wrap(err, ErrNativeTokenBalance) + return nil, nil, fmt.Errorf("%s, err %w", ErrNativeTokenBalance, err) } return } -func FundSubscriptions( - env *test_env.CLClusterTestEnv, - vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, - linkAddress contracts.LinkToken, - coordinator contracts.VRFCoordinatorV2_5, - subIDs []*big.Int, -) error { - for _, subID := range subIDs { - //Native Billing - err := coordinator.FundSubscriptionWithNative(subID, big.NewInt(0).Mul(big.NewInt(vrfv2PlusConfig.SubscriptionFundingAmountNative), big.NewInt(1e18))) - if err != nil { - return errors.Wrap(err, ErrFundSubWithNativeToken) - } - //Link Billing - err = FundVRFCoordinatorV2_5Subscription(linkAddress, coordinator, env.EVMClient, subID, big.NewInt(vrfv2PlusConfig.SubscriptionFundingAmountLink)) - if err != nil { - return errors.Wrap(err, ErrFundSubWithLinkToken) - } - } - err := env.EVMClient.WaitForEvents() - if err != nil { - return errors.Wrap(err, ErrWaitTXsComplete) - } - return nil -} - func RequestRandomnessAndWaitForFulfillment( consumer contracts.VRFv2PlusLoadTestConsumer, coordinator contracts.VRFCoordinatorV2_5, @@ -577,7 +518,8 @@ func RequestRandomnessAndWaitForFulfillment( subID *big.Int, isNativeBilling bool, randomnessRequestCountPerRequest uint16, - vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, + randomWordsFulfilledEventTimeout time.Duration, l zerolog.Logger, ) (*vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsFulfilled, error) { logRandRequest(consumer.Address(), coordinator.Address(), subID, isNativeBilling, vrfv2PlusConfig, l) @@ -591,10 +533,18 @@ func RequestRandomnessAndWaitForFulfillment( randomnessRequestCountPerRequest, ) if err != nil { - return nil, errors.Wrap(err, ErrRequestRandomness) + return nil, fmt.Errorf("%s, err %w", ErrRequestRandomness, err) } - return WaitForRequestAndFulfillmentEvents(consumer.Address(), coordinator, vrfv2PlusData, subID, isNativeBilling, l) + return WaitForRequestAndFulfillmentEvents( + consumer.Address(), + coordinator, + vrfv2PlusData, + subID, + isNativeBilling, + randomWordsFulfilledEventTimeout, + l, + ) } func RequestRandomnessAndWaitForFulfillmentUpgraded( @@ -603,7 +553,7 @@ func RequestRandomnessAndWaitForFulfillmentUpgraded( vrfv2PlusData *VRFV2PlusData, subID *big.Int, isNativeBilling bool, - vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, l zerolog.Logger, ) (*vrf_v2plus_upgraded_version.VRFCoordinatorV2PlusUpgradedVersionRandomWordsFulfilled, error) { logRandRequest(consumer.Address(), coordinator.Address(), subID, isNativeBilling, vrfv2PlusConfig, l) @@ -617,7 +567,7 @@ func RequestRandomnessAndWaitForFulfillmentUpgraded( vrfv2PlusConfig.RandomnessRequestCountPerRequest, ) if err != nil { - return nil, errors.Wrap(err, ErrRequestRandomness) + return nil, fmt.Errorf("%s, err %w", ErrRequestRandomness, err) } randomWordsRequestedEvent, err := coordinator.WaitForRandomWordsRequestedEvent( @@ -627,7 +577,7 @@ func RequestRandomnessAndWaitForFulfillmentUpgraded( time.Minute*1, ) if err != nil { - return nil, errors.Wrap(err, ErrWaitRandomWordsRequestedEvent) + return nil, fmt.Errorf("%s, err %w", ErrWaitRandomWordsRequestedEvent, err) } LogRandomnessRequestedEventUpgraded(l, coordinator, randomWordsRequestedEvent) @@ -638,20 +588,150 @@ func RequestRandomnessAndWaitForFulfillmentUpgraded( time.Minute*2, ) if err != nil { - return nil, errors.Wrap(err, ErrWaitRandomWordsFulfilledEvent) + return nil, fmt.Errorf("%s, err %w", ErrWaitRandomWordsFulfilledEvent, err) } LogRandomWordsFulfilledEventUpgraded(l, coordinator, randomWordsFulfilledEvent) return randomWordsFulfilledEvent, err } +func SetupVRFV2PlusWrapperEnvironment( + env *test_env.CLClusterTestEnv, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, + linkToken contracts.LinkToken, + mockNativeLINKFeed contracts.MockETHLINKFeed, + coordinator contracts.VRFCoordinatorV2_5, + keyHash [32]byte, + wrapperConsumerContractsAmount int, +) (*VRFV2PlusWrapperContracts, *big.Int, error) { + + wrapperContracts, err := DeployVRFV2PlusDirectFundingContracts( + env.ContractDeployer, + env.EVMClient, + linkToken.Address(), + mockNativeLINKFeed.Address(), + coordinator, + wrapperConsumerContractsAmount, + ) + if err != nil { + return nil, nil, err + } + + err = env.EVMClient.WaitForEvents() + + if err != nil { + return nil, nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + err = wrapperContracts.VRFV2PlusWrapper.SetConfig( + vrfv2PlusConfig.WrapperGasOverhead, + vrfv2PlusConfig.CoordinatorGasOverhead, + vrfv2PlusConfig.WrapperPremiumPercentage, + keyHash, + vrfv2PlusConfig.WrapperMaxNumberOfWords, + vrfv2PlusConfig.StalenessSeconds, + big.NewInt(vrfv2PlusConfig.FallbackWeiPerUnitLink), + vrfv2PlusConfig.FulfillmentFlatFeeLinkPPM, + vrfv2PlusConfig.FulfillmentFlatFeeNativePPM, + ) + if err != nil { + return nil, nil, err + } + + err = env.EVMClient.WaitForEvents() + if err != nil { + return nil, nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + + //fund sub + wrapperSubID, err := wrapperContracts.VRFV2PlusWrapper.GetSubID(context.Background()) + if err != nil { + return nil, nil, err + } + + err = env.EVMClient.WaitForEvents() + if err != nil { + return nil, nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + + err = FundSubscriptions(env, vrfv2PlusConfig, linkToken, coordinator, []*big.Int{wrapperSubID}) + if err != nil { + return nil, nil, err + } + + //fund consumer with Link + err = linkToken.Transfer( + wrapperContracts.LoadTestConsumers[0].Address(), + big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(vrfv2PlusConfig.WrapperConsumerFundingAmountLink)), + ) + if err != nil { + return nil, nil, err + } + err = env.EVMClient.WaitForEvents() + if err != nil { + return nil, nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + + //fund consumer with Eth + err = wrapperContracts.LoadTestConsumers[0].Fund(big.NewFloat(vrfv2PlusConfig.WrapperConsumerFundingAmountNativeToken)) + if err != nil { + return nil, nil, err + } + err = env.EVMClient.WaitForEvents() + if err != nil { + return nil, nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + return wrapperContracts, wrapperSubID, nil +} + +func DeployVRFV2PlusWrapperConsumers(contractDeployer contracts.ContractDeployer, linkTokenAddress string, vrfV2PlusWrapper contracts.VRFV2PlusWrapper, consumerContractsAmount int) ([]contracts.VRFv2PlusWrapperLoadTestConsumer, error) { + var consumers []contracts.VRFv2PlusWrapperLoadTestConsumer + for i := 1; i <= consumerContractsAmount; i++ { + loadTestConsumer, err := contractDeployer.DeployVRFV2PlusWrapperLoadTestConsumer(linkTokenAddress, vrfV2PlusWrapper.Address()) + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrAdvancedConsumer, err) + } + consumers = append(consumers, loadTestConsumer) + } + return consumers, nil +} + +func DeployVRFV2PlusDirectFundingContracts( + contractDeployer contracts.ContractDeployer, + chainClient blockchain.EVMClient, + linkTokenAddress string, + linkEthFeedAddress string, + coordinator contracts.VRFCoordinatorV2_5, + consumerContractsAmount int, +) (*VRFV2PlusWrapperContracts, error) { + + vrfv2PlusWrapper, err := contractDeployer.DeployVRFV2PlusWrapper(linkTokenAddress, linkEthFeedAddress, coordinator.Address()) + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrDeployWrapper, err) + } + err = chainClient.WaitForEvents() + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + + consumers, err := DeployVRFV2PlusWrapperConsumers(contractDeployer, linkTokenAddress, vrfv2PlusWrapper, consumerContractsAmount) + if err != nil { + return nil, err + } + err = chainClient.WaitForEvents() + if err != nil { + return nil, fmt.Errorf("%s, err %w", ErrWaitTXsComplete, err) + } + return &VRFV2PlusWrapperContracts{vrfv2PlusWrapper, consumers}, nil +} + func DirectFundingRequestRandomnessAndWaitForFulfillment( consumer contracts.VRFv2PlusWrapperLoadTestConsumer, coordinator contracts.VRFCoordinatorV2_5, vrfv2PlusData *VRFV2PlusData, subID *big.Int, isNativeBilling bool, - vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, + randomWordsFulfilledEventTimeout time.Duration, l zerolog.Logger, ) (*vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsFulfilled, error) { logRandRequest(consumer.Address(), coordinator.Address(), subID, isNativeBilling, vrfv2PlusConfig, l) @@ -663,7 +743,7 @@ func DirectFundingRequestRandomnessAndWaitForFulfillment( vrfv2PlusConfig.RandomnessRequestCountPerRequest, ) if err != nil { - return nil, errors.Wrap(err, ErrRequestRandomnessDirectFundingNativePayment) + return nil, fmt.Errorf("%s, err %w", ErrRequestRandomnessDirectFundingNativePayment, err) } } else { _, err := consumer.RequestRandomness( @@ -673,14 +753,22 @@ func DirectFundingRequestRandomnessAndWaitForFulfillment( vrfv2PlusConfig.RandomnessRequestCountPerRequest, ) if err != nil { - return nil, errors.Wrap(err, ErrRequestRandomnessDirectFundingLinkPayment) + return nil, fmt.Errorf("%s, err %w", ErrRequestRandomnessDirectFundingLinkPayment, err) } } wrapperAddress, err := consumer.GetWrapper(context.Background()) if err != nil { - return nil, errors.Wrap(err, "error getting wrapper address") + return nil, fmt.Errorf("error getting wrapper address, err: %w", err) } - return WaitForRequestAndFulfillmentEvents(wrapperAddress.String(), coordinator, vrfv2PlusData, subID, isNativeBilling, l) + return WaitForRequestAndFulfillmentEvents( + wrapperAddress.String(), + coordinator, + vrfv2PlusData, + subID, + isNativeBilling, + randomWordsFulfilledEventTimeout, + l, + ) } func WaitForRequestAndFulfillmentEvents( @@ -689,6 +777,7 @@ func WaitForRequestAndFulfillmentEvents( vrfv2PlusData *VRFV2PlusData, subID *big.Int, isNativeBilling bool, + randomWordsFulfilledEventTimeout time.Duration, l zerolog.Logger, ) (*vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsFulfilled, error) { randomWordsRequestedEvent, err := coordinator.WaitForRandomWordsRequestedEvent( @@ -698,7 +787,7 @@ func WaitForRequestAndFulfillmentEvents( time.Minute*1, ) if err != nil { - return nil, errors.Wrap(err, ErrWaitRandomWordsRequestedEvent) + return nil, fmt.Errorf("%s, err %w", ErrWaitRandomWordsRequestedEvent, err) } LogRandomnessRequestedEvent(l, coordinator, randomWordsRequestedEvent, isNativeBilling) @@ -706,10 +795,10 @@ func WaitForRequestAndFulfillmentEvents( randomWordsFulfilledEvent, err := coordinator.WaitForRandomWordsFulfilledEvent( []*big.Int{subID}, []*big.Int{randomWordsRequestedEvent.RequestId}, - time.Minute*2, + randomWordsFulfilledEventTimeout, ) if err != nil { - return nil, errors.Wrap(err, ErrWaitRandomWordsFulfilledEvent) + return nil, fmt.Errorf("%s, err %w", ErrWaitRandomWordsFulfilledEvent, err) } LogRandomWordsFulfilledEvent(l, coordinator, randomWordsFulfilledEvent, isNativeBilling) @@ -734,7 +823,7 @@ func WaitForRequestCountEqualToFulfilmentCount(consumer contracts.VRFv2PlusLoadT fmt.Errorf("timeout waiting for rand request and fulfilments to be equal AFTER performance test was executed. Request Count: %d, Fulfilment Count: %d", metrics.RequestCount.Uint64(), metrics.FulfilmentCount.Uint64()) case <-ticker.C: - go getLoadTestMetrics(consumer, metricsChannel, metricsErrorChannel) + go retrieveLoadTestMetrics(consumer, metricsChannel, metricsErrorChannel) case metrics = <-metricsChannel: if metrics.RequestCount.Cmp(metrics.FulfilmentCount) == 0 { ticker.Stop() @@ -749,7 +838,42 @@ func WaitForRequestCountEqualToFulfilmentCount(consumer contracts.VRFv2PlusLoadT } } -func getLoadTestMetrics( +func ReturnFundsForFulfilledRequests(client blockchain.EVMClient, coordinator contracts.VRFCoordinatorV2_5, l zerolog.Logger) error { + linkTotalBalance, err := coordinator.GetLinkTotalBalance(context.Background()) + if err != nil { + return fmt.Errorf("Error getting LINK total balance, err: %w", err) + } + defaultWallet := client.GetDefaultWallet().Address() + l.Info(). + Str("LINK amount", linkTotalBalance.String()). + Str("Returning to", defaultWallet). + Msg("Returning LINK for fulfilled requests") + err = coordinator.OracleWithdraw( + common.HexToAddress(defaultWallet), + linkTotalBalance, + ) + if err != nil { + return fmt.Errorf("Error withdrawing LINK from coordinator to default wallet, err: %w", err) + } + nativeTotalBalance, err := coordinator.GetNativeTokenTotalBalance(context.Background()) + if err != nil { + return fmt.Errorf("Error getting NATIVE total balance, err: %w", err) + } + l.Info(). + Str("Native Token amount", linkTotalBalance.String()). + Str("Returning to", defaultWallet). + Msg("Returning Native Token for fulfilled requests") + err = coordinator.OracleWithdrawNative( + common.HexToAddress(defaultWallet), + nativeTotalBalance, + ) + if err != nil { + return fmt.Errorf("Error withdrawing NATIVE from coordinator to default wallet, err: %w", err) + } + return nil +} + +func retrieveLoadTestMetrics( consumer contracts.VRFv2PlusLoadTestConsumer, metricsChannel chan *contracts.VRFLoadTestMetrics, metricsErrorChannel chan error, @@ -764,7 +888,7 @@ func getLoadTestMetrics( func LogSubDetails(l zerolog.Logger, subscription vrf_coordinator_v2_5.GetSubscription, subID *big.Int, coordinator contracts.VRFCoordinatorV2_5) { l.Debug(). Str("Coordinator", coordinator.Address()). - Str("Link Balance", (*assets.Link)(subscription.Balance).Link()). + Str("Link Balance", (*commonassets.Link)(subscription.Balance).Link()). Str("Native Token Balance", assets.FormatWei(subscription.NativeBalance)). Str("Subscription ID", subID.String()). Str("Subscription Owner", subscription.Owner.String()). @@ -867,11 +991,11 @@ func LogFulfillmentDetailsLinkBilling( randomWordsFulfilledEvent *vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsFulfilled, ) { l.Debug(). - Str("Consumer Balance Before Request (Link)", (*assets.Link)(wrapperConsumerJuelsBalanceBeforeRequest).Link()). - Str("Consumer Balance After Request (Link)", (*assets.Link)(wrapperConsumerJuelsBalanceAfterRequest).Link()). + Str("Consumer Balance Before Request (Link)", (*commonassets.Link)(wrapperConsumerJuelsBalanceBeforeRequest).Link()). + Str("Consumer Balance After Request (Link)", (*commonassets.Link)(wrapperConsumerJuelsBalanceAfterRequest).Link()). Bool("Fulfilment Status", consumerStatus.Fulfilled). - Str("Paid by Consumer Contract (Link)", (*assets.Link)(consumerStatus.Paid).Link()). - Str("Paid by Coordinator Sub (Link)", (*assets.Link)(randomWordsFulfilledEvent.Payment).Link()). + Str("Paid by Consumer Contract (Link)", (*commonassets.Link)(consumerStatus.Paid).Link()). + Str("Paid by Coordinator Sub (Link)", (*commonassets.Link)(randomWordsFulfilledEvent.Payment).Link()). Str("RequestTimestamp", consumerStatus.RequestTimestamp.String()). Str("FulfilmentTimestamp", consumerStatus.FulfilmentTimestamp.String()). Str("RequestBlockNumber", consumerStatus.RequestBlockNumber.String()). @@ -906,7 +1030,7 @@ func logRandRequest( coordinator string, subID *big.Int, isNativeBilling bool, - vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, l zerolog.Logger) { l.Debug(). Str("Consumer", consumer). diff --git a/integration-tests/benchmark/keeper_test.go b/integration-tests/benchmark/keeper_test.go index a3db60f3b30..d5a82a51e07 100644 --- a/integration-tests/benchmark/keeper_test.go +++ b/integration-tests/benchmark/keeper_test.go @@ -11,18 +11,17 @@ import ( "github.com/stretchr/testify/require" - env_client "github.com/smartcontractkit/chainlink-env/client" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/cdk8s/blockscout" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-env/pkg/helm/reorg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + env_client "github.com/smartcontractkit/chainlink-testing-framework/k8s/client" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/blockscout" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/reorg" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" "github.com/smartcontractkit/chainlink/integration-tests/actions" - "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" eth_contracts "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" "github.com/smartcontractkit/chainlink/integration-tests/testsetups" @@ -161,6 +160,7 @@ func TestAutomationBenchmark(t *testing.T) { RegistryVersions: registryVersions, KeeperRegistrySettings: &contracts.KeeperRegistrySettings{ PaymentPremiumPPB: uint32(0), + FlatFeeMicroLINK: uint32(40000), BlockCountPerTurn: big.NewInt(100), CheckGasLimit: uint32(45_000_000), //45M StalenessSeconds: big.NewInt(90_000), @@ -225,7 +225,7 @@ func addRegistry(registryToTest string) []eth_contracts.KeeperRegistryVersion { case "2_0-Multiple": return repeatRegistries(eth_contracts.RegistryVersion_2_0, NumberOfRegistries) case "2_1-Multiple": - return repeatRegistries(eth_contracts.RegistryVersion_1_0, NumberOfRegistries) + return repeatRegistries(eth_contracts.RegistryVersion_2_1, NumberOfRegistries) default: return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_2_0} } @@ -241,13 +241,13 @@ func repeatRegistries(registryVersion eth_contracts.KeeperRegistryVersion, numbe var networkConfig = map[string]NetworkConfig{ "SimulatedGeth": { - upkeepSLA: int64(20), + upkeepSLA: int64(120), //2 minutes blockTime: time.Second, deltaStage: 30 * time.Second, funding: big.NewFloat(100_000), }, "geth": { - upkeepSLA: int64(20), + upkeepSLA: int64(120), //2 minutes blockTime: time.Second, deltaStage: 30 * time.Second, funding: big.NewFloat(100_000), @@ -282,6 +282,18 @@ var networkConfig = map[string]NetworkConfig{ deltaStage: time.Duration(0), funding: big.NewFloat(ChainlinkNodeFunding), }, + "BaseGoerli": { + upkeepSLA: int64(60), + blockTime: 2 * time.Second, + deltaStage: 20 * time.Second, + funding: big.NewFloat(ChainlinkNodeFunding), + }, + "ArbitrumSepolia": { + upkeepSLA: int64(120), + blockTime: time.Second, + deltaStage: 20 * time.Second, + funding: big.NewFloat(ChainlinkNodeFunding), + }, } func getEnv(key, fallback string) string { @@ -298,7 +310,7 @@ func getEnv(key, fallback string) string { func SetupAutomationBenchmarkEnv(t *testing.T) (*environment.Environment, blockchain.EVMNetwork) { l := logging.GetTestLogger(t) - testNetwork := networks.SelectedNetwork // Environment currently being used to run benchmark test on + testNetwork := networks.MustGetSelectedNetworksFromEnv()[0] // Environment currently being used to run benchmark test on blockTime := "1" networkDetailTOML := `MinIncomingConfirmations = 1` @@ -420,7 +432,7 @@ func SetupAutomationBenchmarkEnv(t *testing.T) (*environment.Environment, blockc testNetwork.HTTPURLs = []string{internalHttpURLs[i]} testNetwork.URLs = []string{internalWsURLs[i]} testEnvironment.AddHelm(chainlink.New(i, map[string]any{ - "toml": client.AddNetworkDetailedConfig(keeperBenchmarkBaseTOML, networkDetailTOML, testNetwork), + "toml": networks.AddNetworkDetailedConfig(keeperBenchmarkBaseTOML, networkDetailTOML, testNetwork), "chainlink": chainlinkResources, "db": dbResources, })) diff --git a/integration-tests/chaos/automation_chaos_test.go b/integration-tests/chaos/automation_chaos_test.go index 6f2cacdb03e..06352a93d72 100644 --- a/integration-tests/chaos/automation_chaos_test.go +++ b/integration-tests/chaos/automation_chaos_test.go @@ -1,7 +1,6 @@ package chaos import ( - "context" "fmt" "math/big" "testing" @@ -11,16 +10,16 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink-env/chaos" - "github.com/smartcontractkit/chainlink-env/environment" - a "github.com/smartcontractkit/chainlink-env/pkg/alias" - "github.com/smartcontractkit/chainlink-env/pkg/cdk8s/blockscout" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/chaos" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/blockscout" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" - "github.com/smartcontractkit/chainlink-testing-framework/utils" + "github.com/smartcontractkit/chainlink-testing-framework/utils/ptr" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -42,7 +41,7 @@ ListenAddresses = ["0.0.0.0:6690"]` defaultAutomationSettings = map[string]interface{}{ "replicas": 6, - "toml": client.AddNetworksConfig(baseTOML, networks.SelectedNetwork), + "toml": networks.AddNetworksConfig(baseTOML, networks.MustGetSelectedNetworksFromEnv()[0]), "db": map[string]interface{}{ "stateful": true, "capacity": "1Gi", @@ -60,9 +59,10 @@ ListenAddresses = ["0.0.0.0:6690"]` } defaultEthereumSettings = ðereum.Props{ - NetworkName: networks.SelectedNetwork.Name, - Simulated: networks.SelectedNetwork.Simulated, - WsURLs: networks.SelectedNetwork.URLs, + // utils.MustGetSelectedNetworksFromEnv() + NetworkName: networks.MustGetSelectedNetworksFromEnv()[0].Name, + Simulated: networks.MustGetSelectedNetworksFromEnv()[0].Simulated, + WsURLs: networks.MustGetSelectedNetworksFromEnv()[0].URLs, Values: map[string]interface{}{ "resources": map[string]interface{}{ "requests": map[string]interface{}{ @@ -116,6 +116,7 @@ func TestAutomationChaos(t *testing.T) { } for name, registryVersion := range registryVersions { + registryVersion := registryVersion t.Run(name, func(t *testing.T) { t.Parallel() @@ -131,7 +132,7 @@ func TestAutomationChaos(t *testing.T) { chainlink.New(0, defaultAutomationSettings), chaos.NewFailPods, &chaos.Props{ - LabelsSelector: &map[string]*string{ChaosGroupMinority: a.Str("1")}, + LabelsSelector: &map[string]*string{ChaosGroupMinority: ptr.Ptr("1")}, DurationStr: "1m", }, }, @@ -140,7 +141,7 @@ func TestAutomationChaos(t *testing.T) { chainlink.New(0, defaultAutomationSettings), chaos.NewFailPods, &chaos.Props{ - LabelsSelector: &map[string]*string{ChaosGroupMajority: a.Str("1")}, + LabelsSelector: &map[string]*string{ChaosGroupMajority: ptr.Ptr("1")}, DurationStr: "1m", }, }, @@ -149,9 +150,9 @@ func TestAutomationChaos(t *testing.T) { chainlink.New(0, defaultAutomationSettings), chaos.NewFailPods, &chaos.Props{ - LabelsSelector: &map[string]*string{ChaosGroupMajority: a.Str("1")}, + LabelsSelector: &map[string]*string{ChaosGroupMajority: ptr.Ptr("1")}, DurationStr: "1m", - ContainerNames: &[]*string{a.Str("chainlink-db")}, + ContainerNames: &[]*string{ptr.Ptr("chainlink-db")}, }, }, NetworkChaosFailMajorityNetwork: { @@ -159,8 +160,8 @@ func TestAutomationChaos(t *testing.T) { chainlink.New(0, defaultAutomationSettings), chaos.NewNetworkPartition, &chaos.Props{ - FromLabels: &map[string]*string{ChaosGroupMajority: a.Str("1")}, - ToLabels: &map[string]*string{ChaosGroupMinority: a.Str("1")}, + FromLabels: &map[string]*string{ChaosGroupMajority: ptr.Ptr("1")}, + ToLabels: &map[string]*string{ChaosGroupMinority: ptr.Ptr("1")}, DurationStr: "1m", }, }, @@ -169,19 +170,19 @@ func TestAutomationChaos(t *testing.T) { chainlink.New(0, defaultAutomationSettings), chaos.NewNetworkPartition, &chaos.Props{ - FromLabels: &map[string]*string{"app": a.Str("geth")}, - ToLabels: &map[string]*string{ChaosGroupMajorityPlus: a.Str("1")}, + FromLabels: &map[string]*string{"app": ptr.Ptr("geth")}, + ToLabels: &map[string]*string{ChaosGroupMajorityPlus: ptr.Ptr("1")}, DurationStr: "1m", }, }, } - for n, tst := range testCases { - name := n - testCase := tst + for name, testCase := range testCases { + name := name + testCase := testCase t.Run(fmt.Sprintf("Automation_%s", name), func(t *testing.T) { t.Parallel() - network := networks.SelectedNetwork // Need a new copy of the network for each test + network := networks.MustGetSelectedNetworksFromEnv()[0] // Need a new copy of the network for each test testEnvironment := environment. New(&environment.Config{ @@ -223,7 +224,7 @@ func TestAutomationChaos(t *testing.T) { if chainClient != nil { chainClient.GasStats().PrintStats() } - err := actions.TeardownSuite(t, testEnvironment, utils.ProjectRoot, chainlinkNodes, nil, zapcore.PanicLevel, chainClient) + err := actions.TeardownSuite(t, testEnvironment, chainlinkNodes, nil, zapcore.PanicLevel, chainClient) require.NoError(t, err, "Error tearing down environment") }) @@ -268,7 +269,7 @@ func TestAutomationChaos(t *testing.T) { gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analyzing their counters and checking they are greater than 10 for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) expect := 5 l.Info().Int64("Upkeeps Performed", counter.Int64()).Int("Upkeep ID", i).Msg("Number of upkeeps performed") @@ -283,7 +284,7 @@ func TestAutomationChaos(t *testing.T) { gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analyzing their counters and checking they are greater than 10 for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) expect := 10 l.Info().Int64("Upkeeps Performed", counter.Int64()).Int("Upkeep ID", i).Msg("Number of upkeeps performed") diff --git a/integration-tests/chaos/ocr2vrf_chaos_test.go b/integration-tests/chaos/ocr2vrf_chaos_test.go index 1d7f61f783c..200114ddafb 100644 --- a/integration-tests/chaos/ocr2vrf_chaos_test.go +++ b/integration-tests/chaos/ocr2vrf_chaos_test.go @@ -10,15 +10,15 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink-env/chaos" - "github.com/smartcontractkit/chainlink-env/environment" - a "github.com/smartcontractkit/chainlink-env/pkg/alias" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/chaos" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" - "github.com/smartcontractkit/chainlink-testing-framework/utils" + "github.com/smartcontractkit/chainlink-testing-framework/utils/ptr" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/actions/ocr2vrf_actions" @@ -31,11 +31,11 @@ import ( func TestOCR2VRFChaos(t *testing.T) { t.Parallel() l := logging.GetTestLogger(t) - loadedNetwork := networks.SelectedNetwork + loadedNetwork := networks.MustGetSelectedNetworksFromEnv()[0] defaultOCR2VRFSettings := map[string]interface{}{ "replicas": 6, - "toml": client.AddNetworkDetailedConfig( + "toml": networks.AddNetworkDetailedConfig( config.BaseOCR2Config, config.DefaultOCR2VRFNetworkDetailTomlConfig, loadedNetwork, @@ -68,7 +68,7 @@ func TestOCR2VRFChaos(t *testing.T) { chainlink.New(0, defaultOCR2VRFSettings), chaos.NewFailPods, &chaos.Props{ - LabelsSelector: &map[string]*string{ChaosGroupMinority: a.Str("1")}, + LabelsSelector: &map[string]*string{ChaosGroupMinority: ptr.Ptr("1")}, DurationStr: "1m", }, }, @@ -78,7 +78,7 @@ func TestOCR2VRFChaos(t *testing.T) { // chainlink.New(0, defaultOCR2VRFSettings), // chaos.NewFailPods, // &chaos.Props{ - // LabelsSelector: &map[string]*string{ChaosGroupMajority: a.Str("1")}, + // LabelsSelector: &map[string]*string{ChaosGroupMajority: ptr.Ptr("1")}, // DurationStr: "1m", // }, //}, @@ -88,9 +88,9 @@ func TestOCR2VRFChaos(t *testing.T) { // chainlink.New(0, defaultOCR2VRFSettings), // chaos.NewFailPods, // &chaos.Props{ - // LabelsSelector: &map[string]*string{ChaosGroupMajority: a.Str("1")}, + // LabelsSelector: &map[string]*string{ChaosGroupMajority: ptr.Ptr("1")}, // DurationStr: "1m", - // ContainerNames: &[]*string{a.Str("chainlink-db")}, + // ContainerNames: &[]*string{ptr.Ptr("chainlink-db")}, // }, //}, //NetworkChaosFailMajorityNetwork: { @@ -98,8 +98,8 @@ func TestOCR2VRFChaos(t *testing.T) { // chainlink.New(0, defaultOCR2VRFSettings), // chaos.NewNetworkPartition, // &chaos.Props{ - // FromLabels: &map[string]*string{ChaosGroupMajority: a.Str("1")}, - // ToLabels: &map[string]*string{ChaosGroupMinority: a.Str("1")}, + // FromLabels: &map[string]*string{ChaosGroupMajority: ptr.Ptr("1")}, + // ToLabels: &map[string]*string{ChaosGroupMinority: ptr.Ptr("1")}, // DurationStr: "1m", // }, //}, @@ -108,8 +108,8 @@ func TestOCR2VRFChaos(t *testing.T) { // chainlink.New(0, defaultOCR2VRFSettings), // chaos.NewNetworkPartition, // &chaos.Props{ - // FromLabels: &map[string]*string{"app": a.Str("geth")}, - // ToLabels: &map[string]*string{ChaosGroupMajority: a.Str("1")}, + // FromLabels: &map[string]*string{"app": ptr.Ptr("geth")}, + // ToLabels: &map[string]*string{ChaosGroupMajority: ptr.Ptr("1")}, // DurationStr: "1m", // }, //}, @@ -119,7 +119,7 @@ func TestOCR2VRFChaos(t *testing.T) { testCase := tc t.Run(fmt.Sprintf("OCR2VRF_%s", testCaseName), func(t *testing.T) { t.Parallel() - testNetwork := networks.SelectedNetwork // Need a new copy of the network for each test + testNetwork := networks.MustGetSelectedNetworksFromEnv()[0] // Need a new copy of the network for each test testEnvironment := environment. New(&environment.Config{ NamespacePrefix: fmt.Sprintf( @@ -150,7 +150,7 @@ func TestOCR2VRFChaos(t *testing.T) { require.NoError(t, err, "Retrieving on-chain wallet addresses for chainlink nodes shouldn't fail") t.Cleanup(func() { - err := actions.TeardownSuite(t, testEnvironment, utils.ProjectRoot, chainlinkNodes, nil, zapcore.PanicLevel, chainClient) + err := actions.TeardownSuite(t, testEnvironment, chainlinkNodes, nil, zapcore.PanicLevel, chainClient) require.NoError(t, err, "Error tearing down environment") }) @@ -186,7 +186,7 @@ func TestOCR2VRFChaos(t *testing.T) { ) for i := uint16(0); i < ocr2vrf_constants.NumberOfRandomWordsToRequest; i++ { - randomness, err := consumerContract.GetRandomnessByRequestId(nil, requestID, big.NewInt(int64(i))) + randomness, err := consumerContract.GetRandomnessByRequestId(testcontext.Get(t), requestID, big.NewInt(int64(i))) require.NoError(t, err) l.Info().Interface("Random Number", randomness).Interface("Randomness Number Index", i).Msg("Randomness retrieved from Consumer contract") require.NotEqual(t, 0, randomness.Uint64(), "Randomness retrieved from Consumer contract give an answer other than 0") @@ -213,7 +213,7 @@ func TestOCR2VRFChaos(t *testing.T) { ) for i := uint16(0); i < ocr2vrf_constants.NumberOfRandomWordsToRequest; i++ { - randomness, err := consumerContract.GetRandomnessByRequestId(nil, requestID, big.NewInt(int64(i))) + randomness, err := consumerContract.GetRandomnessByRequestId(testcontext.Get(t), requestID, big.NewInt(int64(i))) require.NoError(t, err, "Error getting Randomness result from Consumer Contract") l.Info().Interface("Random Number", randomness).Interface("Randomness Number Index", i).Msg("Randomness retrieved from Consumer contract") require.NotEqual(t, 0, randomness.Uint64(), "Randomness retrieved from Consumer contract give an answer other than 0") diff --git a/integration-tests/chaos/ocr_chaos_test.go b/integration-tests/chaos/ocr_chaos_test.go index f3ee12046fb..a59a0a028c2 100644 --- a/integration-tests/chaos/ocr_chaos_test.go +++ b/integration-tests/chaos/ocr_chaos_test.go @@ -1,7 +1,6 @@ package chaos import ( - "context" "fmt" "math/big" "os" @@ -11,18 +10,18 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink-env/chaos" - "github.com/smartcontractkit/chainlink-env/environment" - a "github.com/smartcontractkit/chainlink-env/pkg/alias" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/chaos" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" - "github.com/smartcontractkit/chainlink-testing-framework/utils" + "github.com/smartcontractkit/chainlink-testing-framework/utils/ptr" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -53,7 +52,7 @@ var ( ) func TestMain(m *testing.M) { - defaultOCRSettings["toml"] = client.AddNetworksConfig(config.BaseOCRP2PV1Config, networks.SelectedNetwork) + defaultOCRSettings["toml"] = networks.AddNetworksConfig(config.BaseOCR1Config, networks.MustGetSelectedNetworksFromEnv()[0]) os.Exit(m.Run()) } @@ -75,14 +74,14 @@ func TestOCRChaos(t *testing.T) { // and chaos.NewNetworkPartition method (https://chaos-mesh.org/docs/simulate-network-chaos-on-kubernetes/) // in order to regenerate Go bindings if k8s version will be updated // you can pull new CRD spec from your current cluster and check README here - // https://github.com/smartcontractkit/chainlink-env/blob/master/README.md + // https://github.com/smartcontractkit/chainlink-testing-framework/k8s/blob/master/README.md NetworkChaosFailMajorityNetwork: { ethereum.New(nil), chainlink.New(0, defaultOCRSettings), chaos.NewNetworkPartition, &chaos.Props{ - FromLabels: &map[string]*string{ChaosGroupMajority: a.Str("1")}, - ToLabels: &map[string]*string{ChaosGroupMinority: a.Str("1")}, + FromLabels: &map[string]*string{ChaosGroupMajority: ptr.Ptr("1")}, + ToLabels: &map[string]*string{ChaosGroupMinority: ptr.Ptr("1")}, DurationStr: "1m", }, }, @@ -91,8 +90,8 @@ func TestOCRChaos(t *testing.T) { chainlink.New(0, defaultOCRSettings), chaos.NewNetworkPartition, &chaos.Props{ - FromLabels: &map[string]*string{"app": a.Str("geth")}, - ToLabels: &map[string]*string{ChaosGroupMajorityPlus: a.Str("1")}, + FromLabels: &map[string]*string{"app": ptr.Ptr("geth")}, + ToLabels: &map[string]*string{ChaosGroupMajorityPlus: ptr.Ptr("1")}, DurationStr: "1m", }, }, @@ -101,7 +100,7 @@ func TestOCRChaos(t *testing.T) { chainlink.New(0, defaultOCRSettings), chaos.NewFailPods, &chaos.Props{ - LabelsSelector: &map[string]*string{ChaosGroupMinority: a.Str("1")}, + LabelsSelector: &map[string]*string{ChaosGroupMinority: ptr.Ptr("1")}, DurationStr: "1m", }, }, @@ -110,7 +109,7 @@ func TestOCRChaos(t *testing.T) { chainlink.New(0, defaultOCRSettings), chaos.NewFailPods, &chaos.Props{ - LabelsSelector: &map[string]*string{ChaosGroupMajority: a.Str("1")}, + LabelsSelector: &map[string]*string{ChaosGroupMajority: ptr.Ptr("1")}, DurationStr: "1m", }, }, @@ -119,9 +118,9 @@ func TestOCRChaos(t *testing.T) { chainlink.New(0, defaultOCRSettings), chaos.NewFailPods, &chaos.Props{ - LabelsSelector: &map[string]*string{ChaosGroupMajority: a.Str("1")}, + LabelsSelector: &map[string]*string{ChaosGroupMajority: ptr.Ptr("1")}, DurationStr: "1m", - ContainerNames: &[]*string{a.Str("chainlink-db")}, + ContainerNames: &[]*string{ptr.Ptr("chainlink-db")}, }, }, } @@ -165,7 +164,7 @@ func TestOCRChaos(t *testing.T) { if chainClient != nil { chainClient.GasStats().PrintStats() } - err := actions.TeardownSuite(t, testEnvironment, utils.ProjectRoot, chainlinkNodes, nil, zapcore.PanicLevel, chainClient) + err := actions.TeardownSuite(t, testEnvironment, chainlinkNodes, nil, zapcore.PanicLevel, chainClient) require.NoError(t, err, "Error tearing down environment") }) @@ -181,7 +180,7 @@ func TestOCRChaos(t *testing.T) { err = actions.FundChainlinkNodes(chainlinkNodes, chainClient, big.NewFloat(10)) require.NoError(t, err) - ocrInstances, err := actions.DeployOCRContracts(1, lt, cd, bootstrapNode, workerNodes, chainClient) + ocrInstances, err := actions.DeployOCRContracts(1, lt, cd, workerNodes, chainClient) require.NoError(t, err) err = chainClient.WaitForEvents() require.NoError(t, err) @@ -196,7 +195,7 @@ func TestOCRChaos(t *testing.T) { err := ocr.RequestNewRound() require.NoError(t, err, "Error requesting new round") } - round, err := ocrInstances[0].GetLatestRound(context.Background()) + round, err := ocrInstances[0].GetLatestRound(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred()) l.Info().Int64("RoundID", round.RoundId.Int64()).Msg("Latest OCR Round") if round.RoundId.Int64() == chaosStartRound && !chaosApplied { diff --git a/integration-tests/client/chainlink.go b/integration-tests/client/chainlink.go index 8a79cb3ec95..16299a1ac8a 100644 --- a/integration-tests/client/chainlink.go +++ b/integration-tests/client/chainlink.go @@ -5,12 +5,11 @@ import ( "fmt" "math/big" "net/http" + "os" "strings" "sync" "time" - "os" - "github.com/ethereum/go-ethereum/common" "github.com/go-resty/resty/v2" "github.com/rs/zerolog" @@ -117,11 +116,11 @@ func (c *ChainlinkClient) MustCreateJob(spec JobSpec) (*Job, error) { if err != nil { return nil, err } - return job, VerifyStatusCode(resp.StatusCode, http.StatusOK) + return job, VerifyStatusCodeWithResponse(resp, http.StatusOK) } // CreateJob creates a Chainlink job based on the provided spec struct -func (c *ChainlinkClient) CreateJob(spec JobSpec) (*Job, *http.Response, error) { +func (c *ChainlinkClient) CreateJob(spec JobSpec) (*Job, *resty.Response, error) { job := &Job{} specString, err := spec.String() if err != nil { @@ -138,7 +137,7 @@ func (c *ChainlinkClient) CreateJob(spec JobSpec) (*Job, *http.Response, error) if err != nil { return nil, nil, err } - return job, resp.RawResponse, err + return job, resp, err } // ReadJobs reads all jobs from the Chainlink node @@ -302,6 +301,19 @@ func (c *ChainlinkClient) ReadBridge(name string) (*BridgeType, *http.Response, return &bt, resp.RawResponse, err } +// ReadBridges reads bridges from the Chainlink node +func (c *ChainlinkClient) ReadBridges() (*Bridges, *resty.Response, error) { + result := &Bridges{} + c.l.Info().Str(NodeURL, c.Config.URL).Msg("Getting all bridges") + resp, err := c.APIClient.R(). + SetResult(&result). + Get("/v2/bridge_types") + if err != nil { + return nil, nil, err + } + return result, resp, err +} + // DeleteBridge deletes a bridge on the Chainlink node based on the provided name func (c *ChainlinkClient) DeleteBridge(name string) (*http.Response, error) { c.l.Info().Str(NodeURL, c.Config.URL).Str("Name", name).Msg("Deleting Bridge") @@ -881,8 +893,16 @@ func (c *ChainlinkClient) CreateCSAKey() (*CSAKey, *http.Response, error) { return csaKey, resp.RawResponse, err } +func (c *ChainlinkClient) MustReadCSAKeys() (*CSAKeys, *resty.Response, error) { + csaKeys, res, err := c.ReadCSAKeys() + if err != nil { + return nil, res, err + } + return csaKeys, res, VerifyStatusCodeWithResponse(res, http.StatusOK) +} + // ReadCSAKeys reads CSA keys from the Chainlink node -func (c *ChainlinkClient) ReadCSAKeys() (*CSAKeys, *http.Response, error) { +func (c *ChainlinkClient) ReadCSAKeys() (*CSAKeys, *resty.Response, error) { csaKeys := &CSAKeys{} c.l.Info().Str(NodeURL, c.Config.URL).Msg("Reading CSA Keys") resp, err := c.APIClient.R(). @@ -894,7 +914,7 @@ func (c *ChainlinkClient) ReadCSAKeys() (*CSAKeys, *http.Response, error) { if err != nil { return nil, nil, err } - return csaKeys, resp.RawResponse, err + return csaKeys, resp, err } // CreateEI creates an EI on the Chainlink node based on the provided attributes and returns the respective secrets @@ -1094,6 +1114,7 @@ func (c *ChainlinkClient) SetPageSize(size int) { c.pageSize = size } +// VerifyStatusCode verifies the status code of the response. Favor VerifyStatusCodeWithResponse over this for better errors func VerifyStatusCode(actStatusCd, expStatusCd int) error { if actStatusCd != expStatusCd { return fmt.Errorf( @@ -1105,6 +1126,21 @@ func VerifyStatusCode(actStatusCd, expStatusCd int) error { return nil } +// VerifyStatusCodeWithResponse verifies the status code of the response and returns the response as part of the error. +// Favor this over VerifyStatusCode +func VerifyStatusCodeWithResponse(res *resty.Response, expStatusCd int) error { + actStatusCd := res.RawResponse.StatusCode + if actStatusCd != expStatusCd { + return fmt.Errorf( + "unexpected response code, got %d, expected %d, response: %s", + actStatusCd, + expStatusCd, + res.Body(), + ) + } + return nil +} + func CreateNodeKeysBundle(nodes []*ChainlinkClient, chainName string, chainId string) ([]NodeKeysBundle, []*CLNodesWithKeys, error) { nkb := make([]NodeKeysBundle, 0) var clNodes []*CLNodesWithKeys @@ -1213,3 +1249,23 @@ func (c *ChainlinkClient) GetForwarders() (*Forwarders, *http.Response, error) { } return response, resp.RawResponse, err } + +// Replays log poller from block number +func (c *ChainlinkClient) ReplayLogPollerFromBlock(fromBlock, evmChainID int64) (*ReplayResponse, *http.Response, error) { + specObj := &ReplayResponse{} + c.l.Info().Str(NodeURL, c.Config.URL).Int64("From block", fromBlock).Int64("EVM chain ID", evmChainID).Msg("Replaying Log Poller from block") + resp, err := c.APIClient.R(). + SetResult(&specObj). + SetQueryParams(map[string]string{ + "evmChainID": fmt.Sprint(evmChainID), + }). + SetPathParams(map[string]string{ + "fromBlock": fmt.Sprint(fromBlock), + }). + Post("/v2/replay_from_block/{fromBlock}") + if err != nil { + return nil, nil, err + } + + return specObj, resp.RawResponse, err +} diff --git a/integration-tests/client/chainlink_config_builder.go b/integration-tests/client/chainlink_config_builder.go deleted file mode 100644 index 9c1050300b1..00000000000 --- a/integration-tests/client/chainlink_config_builder.go +++ /dev/null @@ -1,52 +0,0 @@ -package client - -import ( - "fmt" - "os" - - "github.com/smartcontractkit/chainlink-env/config" - "github.com/smartcontractkit/chainlink-testing-framework/blockchain" -) - -const ( - pyroscopeTOML = `[Pyroscope] -ServerAddress = '%s' -Environment = '%s'` - - secretTOML = ` -[Mercury.Credentials.cred1] -URL = '%s' -Username = '%s' -Password = '%s' -` -) - -// AddNetworksConfig adds EVM network configurations to a base config TOML. Useful for adding networks with default -// settings. See AddNetworkDetailedConfig for adding more detailed network configuration. -func AddNetworksConfig(baseTOML string, networks ...blockchain.EVMNetwork) string { - networksToml := "" - for _, network := range networks { - networksToml = fmt.Sprintf("%s\n\n%s", networksToml, network.MustChainlinkTOML("")) - } - return fmt.Sprintf("%s\n\n%s\n\n%s", baseTOML, pyroscopeSettings(), networksToml) -} - -func AddSecretTomlConfig(url, username, password string) string { - return fmt.Sprintf(secretTOML, url, username, password) -} - -// AddNetworkDetailedConfig adds EVM config to a base TOML. Also takes a detailed network config TOML where values like -// using transaction forwarders can be included. -// See https://github.com/smartcontractkit/chainlink/blob/develop/docs/CONFIG.md#EVM -func AddNetworkDetailedConfig(baseTOML, detailedNetworkConfig string, network blockchain.EVMNetwork) string { - return fmt.Sprintf("%s\n\n%s\n\n%s", baseTOML, pyroscopeSettings(), network.MustChainlinkTOML(detailedNetworkConfig)) -} - -func pyroscopeSettings() string { - pyroscopeServer := os.Getenv(config.EnvVarPyroscopeServer) - pyroscopeEnv := os.Getenv(config.EnvVarPyroscopeEnvironment) - if pyroscopeServer == "" { - return "" - } - return fmt.Sprintf(pyroscopeTOML, pyroscopeServer, pyroscopeEnv) -} diff --git a/integration-tests/client/chainlink_k8s.go b/integration-tests/client/chainlink_k8s.go index 4aa7c6d0fec..27fd956103e 100644 --- a/integration-tests/client/chainlink_k8s.go +++ b/integration-tests/client/chainlink_k8s.go @@ -8,7 +8,7 @@ import ( "github.com/rs/zerolog/log" - "github.com/smartcontractkit/chainlink-env/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" ) type ChainlinkK8sClient struct { @@ -63,7 +63,7 @@ func (c *ChainlinkK8sClient) UpgradeVersion(testEnvironment *environment.Environ }, }, } - testEnvironment, err := testEnvironment.UpdateHelm(c.ChartName, upgradeVals) + _, err := testEnvironment.UpdateHelm(c.ChartName, upgradeVals) return err } diff --git a/integration-tests/client/chainlink_models.go b/integration-tests/client/chainlink_models.go index 6013e13e0fa..abc6ef30e41 100644 --- a/integration-tests/client/chainlink_models.go +++ b/integration-tests/client/chainlink_models.go @@ -9,6 +9,7 @@ import ( "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/utils" ) // EIServiceConfig represents External Initiator service config @@ -106,6 +107,11 @@ type BridgeTypeData struct { Attributes BridgeTypeAttributes `json:"attributes"` } +// Bridges is the model that represents the bridges when read on a Chainlink node +type Bridges struct { + Data []BridgeTypeData `json:"data"` +} + // BridgeTypeAttributes is the model that represents the bridge when read or created on a Chainlink node type BridgeTypeAttributes struct { Name string `json:"name"` @@ -883,7 +889,7 @@ contractConfigConfirmations ={{if not .ContractConfirmations}} 3 {{el contractConfigTrackerPollInterval ={{if not .TrackerPollInterval}} "1m" {{else}} {{.TrackerPollInterval}} {{end}} contractConfigTrackerSubscribeInterval ={{if not .TrackerSubscribeInterval}} "2m" {{else}} {{.TrackerSubscribeInterval}} {{end}} contractAddress = "{{.ContractAddress}}" -evmChainID = "{{.EVMChainID}}" +evmChainID = "{{.EVMChainID}}" p2pBootstrapPeers = [] isBootstrapPeer = {{.IsBootstrapPeer}} p2pPeerID = "{{.P2PPeerID}}"` @@ -982,22 +988,18 @@ contractConfigConfirmations ={{if not .ContractConfirmations}} 3 {{el contractConfigTrackerPollInterval ={{if not .TrackerPollInterval}} "1m" {{else}} {{.TrackerPollInterval}} {{end}} contractConfigTrackerSubscribeInterval ={{if not .TrackerSubscribeInterval}} "2m" {{else}} {{.TrackerSubscribeInterval}} {{end}} contractAddress = "{{.ContractAddress}}" -evmChainID = "{{.EVMChainID}}" +evmChainID = "{{.EVMChainID}}" {{if .P2PBootstrapPeers}} -p2pBootstrapPeers = [ - {{range $peer := .P2PBootstrapPeers}} - "/dns4/{{$peer.InternalIP}}/tcp/6690/p2p/{{$peer.PeerID}}", - {{end}} -] +p2pv2Bootstrappers = [{{range $peer := .P2PBootstrapPeers}}"{{$peer.PeerID}}@{{$peer.InternalIP}}:6690",{{end}}] {{else}} -p2pBootstrapPeers = [] +p2pv2Bootstrappers = [] {{end}} isBootstrapPeer = {{.IsBootstrapPeer}} p2pPeerID = "{{.P2PPeerID}}" keyBundleID = "{{.KeyBundleID}}" monitoringEndpoint ={{if not .MonitoringEndpoint}} "chain.link:4321" {{else}} "{{.MonitoringEndpoint}}" {{end}} transmitterAddress = "{{.TransmitterAddress}}" -forwardingAllowed = {{.ForwardingAllowed}} +forwardingAllowed = {{.ForwardingAllowed}} observationSource = """ {{.ObservationSource}} """` @@ -1126,6 +1128,7 @@ type VRFV2PlusJobSpec struct { BatchFulfillmentEnabled bool `toml:"batchFulfillmentEnabled"` BackOffInitialDelay time.Duration `toml:"backOffInitialDelay"` BackOffMaxDelay time.Duration `toml:"backOffMaxDelay"` + PollPeriod time.Duration `toml:"pollPeriod"` } // Type returns the type of the job @@ -1407,3 +1410,16 @@ type ForwarderAttributes struct { CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } + +type ReplayResponse struct { + Data ReplayResponseData `json:"data"` +} + +type ReplayResponseData struct { + Attributes ReplayResponseAttributes `json:"attributes"` +} + +type ReplayResponseAttributes struct { + Message string `json:"message"` + EVMChainID *utils.Big `json:"evmChainID"` +} diff --git a/integration-tests/config/config.go b/integration-tests/config/config.go index 44c108b0d7f..5065198b5a6 100644 --- a/integration-tests/config/config.go +++ b/integration-tests/config/config.go @@ -1,7 +1,7 @@ package config var ( - BaseOCRP2PV1Config = `[OCR] + BaseOCR1Config = `[OCR] Enabled = true [P2P] @@ -9,10 +9,9 @@ Enabled = true Enabled = false [P2P] -[P2P.V1] -Enabled = true -ListenIP = '0.0.0.0' -ListenPort = 6690` +[P2P.V2] +AnnounceAddresses = ["0.0.0.0:6690"] +ListenAddresses = ["0.0.0.0:6690"]` BaseOCR2Config = `[Feature] LogPoller = true diff --git a/integration-tests/contracts/contract_deployer.go b/integration-tests/contracts/contract_deployer.go index bdf63d19195..528f07ec68e 100644 --- a/integration-tests/contracts/contract_deployer.go +++ b/integration-tests/contracts/contract_deployer.go @@ -12,11 +12,12 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" ocrConfigHelper "github.com/smartcontractkit/libocr/offchainreporting/confighelper" + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_load_test_client" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_v1_events_mock" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_consumer_benchmark" @@ -45,6 +46,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper2_0" registry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper_2_1" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" + le "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_triggered_streams_lookup_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_upkeep_counter_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_aggregator_proxy" @@ -53,6 +55,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/operator_factory" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/oracle_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/perform_data_checker_wrapper" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/simple_log_upkeep_counter_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/streams_lookup_upkeep_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/test_api_consumer_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/upkeep_counter_wrapper" @@ -86,10 +89,12 @@ type ContractDeployer interface { DeployKeeperRegistrar(registryVersion eth_contracts.KeeperRegistryVersion, linkAddr string, registrarSettings KeeperRegistrarSettings) (KeeperRegistrar, error) LoadKeeperRegistrar(address common.Address, registryVersion eth_contracts.KeeperRegistryVersion) (KeeperRegistrar, error) DeployUpkeepTranscoder() (UpkeepTranscoder, error) + LoadUpkeepTranscoder(address common.Address) (UpkeepTranscoder, error) DeployKeeperRegistry(opts *KeeperRegistryOpts) (KeeperRegistry, error) LoadKeeperRegistry(address common.Address, registryVersion eth_contracts.KeeperRegistryVersion) (KeeperRegistry, error) DeployKeeperConsumer(updateInterval *big.Int) (KeeperConsumer, error) DeployAutomationLogTriggerConsumer(testInterval *big.Int) (KeeperConsumer, error) + DeployAutomationSimpleLogTriggerConsumer() (KeeperConsumer, error) DeployAutomationStreamsLookupUpkeepConsumer(testRange *big.Int, interval *big.Int, useArbBlock bool, staging bool, verify bool) (KeeperConsumer, error) DeployAutomationLogTriggeredStreamsLookupUpkeepConsumer() (KeeperConsumer, error) DeployKeeperConsumerPerformance( @@ -138,6 +143,7 @@ type ContractDeployer interface { DeployMercuryVerifierProxyContract(accessControllerAddr common.Address) (MercuryVerifierProxy, error) DeployMercuryFeeManager(linkAddress common.Address, nativeAddress common.Address, proxyAddress common.Address, rewardManagerAddress common.Address) (MercuryFeeManager, error) DeployMercuryRewardManager(linkAddress common.Address) (MercuryRewardManager, error) + DeployLogEmitterContract() (LogEmitter, error) } // NewContractDeployer returns an instance of a contract deployer based on the client type @@ -169,6 +175,12 @@ func NewContractDeployer(bcClient blockchain.EVMClient, logger zerolog.Logger) ( return &PolygonZkEvmContractDeployer{NewEthereumContractDeployer(clientImpl, logger)}, nil case *blockchain.LineaClient: return &LineaContractDeployer{NewEthereumContractDeployer(clientImpl, logger)}, nil + case *blockchain.FantomClient: + return &FantomContractDeployer{NewEthereumContractDeployer(clientImpl, logger)}, nil + case *blockchain.KromaClient: + return &KromaContractDeployer{NewEthereumContractDeployer(clientImpl, logger)}, nil + case *blockchain.WeMixClient: + return &WeMixContractDeployer{NewEthereumContractDeployer(clientImpl, logger)}, nil } return nil, errors.New("unknown blockchain client implementation for contract deployer, register blockchain client in NewContractDeployer") } @@ -232,6 +244,18 @@ type LineaContractDeployer struct { *EthereumContractDeployer } +type FantomContractDeployer struct { + *EthereumContractDeployer +} + +type KromaContractDeployer struct { + *EthereumContractDeployer +} + +type WeMixContractDeployer struct { + *EthereumContractDeployer +} + // NewEthereumContractDeployer returns an instantiated instance of the ETH contract deployer func NewEthereumContractDeployer(ethClient blockchain.EVMClient, logger zerolog.Logger) *EthereumContractDeployer { return &EthereumContractDeployer{ @@ -740,6 +764,25 @@ func (e *EthereumContractDeployer) DeployUpkeepTranscoder() (UpkeepTranscoder, e }, err } +func (e *EthereumContractDeployer) LoadUpkeepTranscoder(address common.Address) (UpkeepTranscoder, error) { + instance, err := e.client.LoadContract("UpkeepTranscoder", address, func( + address common.Address, + backend bind.ContractBackend, + ) (interface{}, error) { + return upkeep_transcoder.NewUpkeepTranscoder(address, backend) + }) + + if err != nil { + return nil, err + } + + return &EthereumUpkeepTranscoder{ + client: e.client, + transcoder: instance.(*upkeep_transcoder.UpkeepTranscoder), + address: &address, + }, err +} + func (e *EthereumContractDeployer) DeployKeeperRegistrar(registryVersion eth_contracts.KeeperRegistryVersion, linkAddr string, registrarSettings KeeperRegistrarSettings) (KeeperRegistrar, error) { @@ -848,34 +891,41 @@ func (e *EthereumContractDeployer) LoadKeeperRegistrar(address common.Address, r client: e.client, registrar20: instance.(*keeper_registrar_wrapper2_0.KeeperRegistrar), }, err - } else { - instance, err := e.client.LoadContract("AutomationRegistrar", address, func( - address common.Address, - backend bind.ContractBackend, - ) (interface{}, error) { - return registrar21.NewAutomationRegistrar(address, backend) - }) - if err != nil { - return nil, err - } - return &EthereumKeeperRegistrar{ - address: &address, - client: e.client, - registrar21: instance.(*registrar21.AutomationRegistrar), - }, err } + instance, err := e.client.LoadContract("AutomationRegistrar", address, func( + address common.Address, + backend bind.ContractBackend, + ) (interface{}, error) { + return registrar21.NewAutomationRegistrar(address, backend) + }) + if err != nil { + return nil, err + } + return &EthereumKeeperRegistrar{ + address: &address, + client: e.client, + registrar21: instance.(*registrar21.AutomationRegistrar), + }, err } func (e *EthereumContractDeployer) DeployKeeperRegistry( opts *KeeperRegistryOpts, ) (KeeperRegistry, error) { var mode uint8 - switch e.client.GetChainID() { + switch e.client.GetChainID().Int64() { //Arbitrum payment model - case big.NewInt(421613): + //Goerli Arbitrum + case 421613: + mode = uint8(1) + //Sepolia Arbitrum + case 421614: mode = uint8(1) //Optimism payment model - case big.NewInt(420): + //Goerli Optimism + case 420: + mode = uint8(2) + //Goerli Base + case 84531: mode = uint8(2) default: mode = uint8(0) @@ -1162,6 +1212,7 @@ func (e *EthereumContractDeployer) LoadKeeperRegistry(address common.Address, re address: &address, client: e.client, registry1_1: instance.(*keeper_registry_wrapper1_1.KeeperRegistry), + version: registryVersion, }, err case eth_contracts.RegistryVersion_1_2: instance, err := e.client.LoadContract("KeeperRegistry", address, func( @@ -1177,6 +1228,7 @@ func (e *EthereumContractDeployer) LoadKeeperRegistry(address common.Address, re address: &address, client: e.client, registry1_2: instance.(*keeper_registry_wrapper1_2.KeeperRegistry), + version: registryVersion, }, err case eth_contracts.RegistryVersion_1_3: instance, err := e.client.LoadContract("KeeperRegistry", address, func( @@ -1192,6 +1244,7 @@ func (e *EthereumContractDeployer) LoadKeeperRegistry(address common.Address, re address: &address, client: e.client, registry1_3: instance.(*keeper_registry_wrapper1_3.KeeperRegistry), + version: registryVersion, }, err case eth_contracts.RegistryVersion_2_0: instance, err := e.client.LoadContract("KeeperRegistry", address, func( @@ -1207,6 +1260,7 @@ func (e *EthereumContractDeployer) LoadKeeperRegistry(address common.Address, re address: &address, client: e.client, registry2_0: instance.(*keeper_registry_wrapper2_0.KeeperRegistry), + version: registryVersion, }, err case eth_contracts.RegistryVersion_2_1: instance, err := e.client.LoadContract("KeeperRegistry", address, func( @@ -1222,6 +1276,7 @@ func (e *EthereumContractDeployer) LoadKeeperRegistry(address common.Address, re address: &address, client: e.client, registry2_1: instance.(*iregistry21.IKeeperRegistryMaster), + version: registryVersion, }, err default: return nil, fmt.Errorf("keeper registry version %d is not supported", registryVersion) @@ -1264,6 +1319,25 @@ func (e *EthereumContractDeployer) DeployAutomationLogTriggerConsumer(testInterv }, err } +func (e *EthereumContractDeployer) DeployAutomationSimpleLogTriggerConsumer() (KeeperConsumer, error) { + address, _, instance, err := e.client.DeployContract("SimpleLogUpkeepCounter", func( + auth *bind.TransactOpts, + backend bind.ContractBackend, + ) (common.Address, *types.Transaction, interface{}, error) { + return simple_log_upkeep_counter_wrapper.DeploySimpleLogUpkeepCounter( + auth, backend, + ) + }) + if err != nil { + return nil, err + } + return &EthereumAutomationSimpleLogCounterConsumer{ + client: e.client, + consumer: instance.(*simple_log_upkeep_counter_wrapper.SimpleLogUpkeepCounter), + address: address, + }, err +} + func (e *EthereumContractDeployer) DeployAutomationStreamsLookupUpkeepConsumer(testRange *big.Int, interval *big.Int, useArbBlock bool, staging bool, verify bool) (KeeperConsumer, error) { address, _, instance, err := e.client.DeployContract("StreamsLookupUpkeep", func( auth *bind.TransactOpts, @@ -1599,3 +1673,21 @@ func (e *EthereumContractDeployer) DeployWERC20Mock() (WERC20Mock, error) { l: e.l, }, err } + +func (e *EthereumContractDeployer) DeployLogEmitterContract() (LogEmitter, error) { + address, _, instance, err := e.client.DeployContract("Log Emitter", func( + auth *bind.TransactOpts, + backend bind.ContractBackend, + ) (common.Address, *types.Transaction, interface{}, error) { + return le.DeployLogEmitter(auth, backend) + }) + if err != nil { + return nil, err + } + return &LogEmitterContract{ + client: e.client, + instance: instance.(*le.LogEmitter), + address: *address, + l: e.l, + }, err +} diff --git a/integration-tests/contracts/contract_loader.go b/integration-tests/contracts/contract_loader.go index 4dda2d3f0c4..6136e78b367 100644 --- a/integration-tests/contracts/contract_loader.go +++ b/integration-tests/contracts/contract_loader.go @@ -2,6 +2,7 @@ package contracts import ( "errors" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2_5" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_v2plus_load_test_with_metrics" @@ -16,6 +17,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_forwarder" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/operator_wrapper" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_load_test_with_metrics" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/fee_manager" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/reward_manager" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier" @@ -43,6 +46,8 @@ type ContractLoader interface { LoadWERC20Mock(addr common.Address) (WERC20Mock, error) // VRF + LoadVRFCoordinatorV2(addr string) (VRFCoordinatorV2, error) + LoadVRFv2LoadTestConsumer(addr string) (VRFv2LoadTestConsumer, error) LoadVRFCoordinatorV2_5(addr string) (VRFCoordinatorV2_5, error) LoadVRFv2PlusLoadTestConsumer(addr string) (VRFv2PlusLoadTestConsumer, error) } @@ -64,6 +69,8 @@ func NewContractLoader(bcClient blockchain.EVMClient, logger zerolog.Logger) (Co return &OptimismContractLoader{NewEthereumContractLoader(clientImpl, logger)}, nil case *blockchain.PolygonZkEvmClient: return &PolygonZkEvmContractLoader{NewEthereumContractLoader(clientImpl, logger)}, nil + case *blockchain.WeMixClient: + return &WeMixContractLoader{NewEthereumContractLoader(clientImpl, logger)}, nil } return nil, errors.New("unknown blockchain client implementation for contract Loader, register blockchain client in NewContractLoader") } @@ -107,6 +114,11 @@ type PolygonZKEVMContractLoader struct { *EthereumContractLoader } +// WeMixContractLoader wraps for WeMix +type WeMixContractLoader struct { + *EthereumContractLoader +} + // NewEthereumContractLoader returns an instantiated instance of the ETH contract Loader func NewEthereumContractLoader(ethClient blockchain.EVMClient, logger zerolog.Logger) *EthereumContractLoader { return &EthereumContractLoader{ @@ -348,3 +360,39 @@ func (e *EthereumContractLoader) LoadVRFv2PlusLoadTestConsumer(addr string) (VRF address: &address, }, err } + +func (e *EthereumContractLoader) LoadVRFCoordinatorV2(addr string) (VRFCoordinatorV2, error) { + address := common.HexToAddress(addr) + instance, err := e.client.LoadContract("VRFCoordinatorV2", address, func( + address common.Address, + backend bind.ContractBackend, + ) (interface{}, error) { + return vrf_coordinator_v2.NewVRFCoordinatorV2(address, backend) + }) + if err != nil { + return nil, err + } + return &EthereumVRFCoordinatorV2{ + address: &address, + client: e.client, + coordinator: instance.(*vrf_coordinator_v2.VRFCoordinatorV2), + }, err +} + +func (e *EthereumContractLoader) LoadVRFv2LoadTestConsumer(addr string) (VRFv2LoadTestConsumer, error) { + address := common.HexToAddress(addr) + instance, err := e.client.LoadContract("VRFV2LoadTestWithMetrics", address, func( + address common.Address, + backend bind.ContractBackend, + ) (interface{}, error) { + return vrf_load_test_with_metrics.NewVRFV2LoadTestWithMetrics(address, backend) + }) + if err != nil { + return nil, err + } + return &EthereumVRFv2LoadTestConsumer{ + client: e.client, + consumer: instance.(*vrf_load_test_with_metrics.VRFV2LoadTestWithMetrics), + address: &address, + }, err +} diff --git a/integration-tests/contracts/contract_models.go b/integration-tests/contracts/contract_models.go index 51fce7cb120..4c8d610fa1b 100644 --- a/integration-tests/contracts/contract_models.go +++ b/integration-tests/contracts/contract_models.go @@ -400,3 +400,13 @@ type WERC20Mock interface { Transfer(to string, amount *big.Int) error Mint(account common.Address, amount *big.Int) (*types.Transaction, error) } + +type LogEmitter interface { + Address() common.Address + EmitLogInts(ints []int) (*types.Transaction, error) + EmitLogIntsIndexed(ints []int) (*types.Transaction, error) + EmitLogStrings(strings []string) (*types.Transaction, error) + EmitLogInt(payload int) (*types.Transaction, error) + EmitLogIntIndexed(payload int) (*types.Transaction, error) + EmitLogString(strings string) (*types.Transaction, error) +} diff --git a/integration-tests/contracts/contract_vrf_models.go b/integration-tests/contracts/contract_vrf_models.go index 656cabb92e9..548cac252b1 100644 --- a/integration-tests/contracts/contract_vrf_models.go +++ b/integration-tests/contracts/contract_vrf_models.go @@ -49,10 +49,15 @@ type VRFCoordinatorV2 interface { publicProvingKey [2]*big.Int, ) error HashOfKey(ctx context.Context, pubKey [2]*big.Int) ([32]byte, error) - CreateSubscription() error + CreateSubscription() (*types.Transaction, error) AddConsumer(subId uint64, consumerAddress string) error Address() string GetSubscription(ctx context.Context, subID uint64) (vrf_coordinator_v2.GetSubscription, error) + PendingRequestsExist(ctx context.Context, subID uint64) (bool, error) + CancelSubscription(subID uint64, to common.Address) (*types.Transaction, error) + FindSubscriptionID(subID uint64) (uint64, error) + WaitForRandomWordsFulfilledEvent(requestID []*big.Int, timeout time.Duration) (*vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled, error) + WaitForRandomWordsRequestedEvent(keyHash [][32]byte, subID []uint64, sender []common.Address, timeout time.Duration) (*vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested, error) } type VRFCoordinatorV2_5 interface { @@ -73,18 +78,24 @@ type VRFCoordinatorV2_5 interface { publicProvingKey [2]*big.Int, ) error HashOfKey(ctx context.Context, pubKey [2]*big.Int) ([32]byte, error) - CreateSubscription() error + CreateSubscription() (*types.Transaction, error) GetActiveSubscriptionIds(ctx context.Context, startIndex *big.Int, maxCount *big.Int) ([]*big.Int, error) Migrate(subId *big.Int, coordinatorAddress string) error RegisterMigratableCoordinator(migratableCoordinatorAddress string) error AddConsumer(subId *big.Int, consumerAddress string) error FundSubscriptionWithNative(subId *big.Int, nativeTokenAmount *big.Int) error Address() string + PendingRequestsExist(ctx context.Context, subID *big.Int) (bool, error) GetSubscription(ctx context.Context, subID *big.Int) (vrf_coordinator_v2_5.GetSubscription, error) + OwnerCancelSubscription(subID *big.Int) (*types.Transaction, error) + CancelSubscription(subID *big.Int, to common.Address) (*types.Transaction, error) + OracleWithdraw(recipient common.Address, amount *big.Int) error + OracleWithdrawNative(recipient common.Address, amount *big.Int) error GetNativeTokenTotalBalance(ctx context.Context) (*big.Int, error) GetLinkTotalBalance(ctx context.Context) (*big.Int, error) - FindSubscriptionID() (*big.Int, error) + FindSubscriptionID(subID *big.Int) (*big.Int, error) WaitForSubscriptionCreatedEvent(timeout time.Duration) (*vrf_coordinator_v2_5.VRFCoordinatorV25SubscriptionCreated, error) + WaitForSubscriptionCanceledEvent(subID *big.Int, timeout time.Duration) (*vrf_coordinator_v2_5.VRFCoordinatorV25SubscriptionCanceled, error) WaitForRandomWordsFulfilledEvent(subID []*big.Int, requestID []*big.Int, timeout time.Duration) (*vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsFulfilled, error) WaitForRandomWordsRequestedEvent(keyHash [][32]byte, subID []*big.Int, sender []common.Address, timeout time.Duration) (*vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsRequested, error) WaitForMigrationCompletedEvent(timeout time.Duration) (*vrf_coordinator_v2_5.VRFCoordinatorV25MigrationCompleted, error) @@ -159,10 +170,11 @@ type VRFv2Consumer interface { type VRFv2LoadTestConsumer interface { Address() string - RequestRandomness(hash [32]byte, subID uint64, confs uint16, gasLimit uint32, numWords uint32, requestCount uint16) error + RequestRandomness(hash [32]byte, subID uint64, confs uint16, gasLimit uint32, numWords uint32, requestCount uint16) (*types.Transaction, error) GetRequestStatus(ctx context.Context, requestID *big.Int) (vrf_load_test_with_metrics.GetRequestStatus, error) GetLastRequestId(ctx context.Context) (*big.Int, error) GetLoadTestMetrics(ctx context.Context) (*VRFLoadTestMetrics, error) + ResetMetrics() error } type VRFv2PlusLoadTestConsumer interface { @@ -256,12 +268,13 @@ type RequestStatus struct { } type LoadTestRequestStatus struct { - Fulfilled bool - RandomWords []*big.Int - requestTimestamp *big.Int - fulfilmentTimestamp *big.Int - requestBlockNumber *big.Int - fulfilmentBlockNumber *big.Int + Fulfilled bool + RandomWords []*big.Int + // Currently Unused November 8, 2023, Mignt be used in near future, will remove if not. + // requestTimestamp *big.Int + // fulfilmentTimestamp *big.Int + // requestBlockNumber *big.Int + // fulfilmentBlockNumber *big.Int } type VRFLoadTestMetrics struct { diff --git a/integration-tests/contracts/ethereum_contracts.go b/integration-tests/contracts/ethereum_contracts.go index 5b3a93fe0c2..9cb858fe007 100644 --- a/integration-tests/contracts/ethereum_contracts.go +++ b/integration-tests/contracts/ethereum_contracts.go @@ -13,10 +13,14 @@ import ( "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" "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + ocrConfigHelper "github.com/smartcontractkit/libocr/offchainreporting/confighelper" + ocrTypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_coordinator" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_load_test_client" @@ -44,10 +48,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier_proxy" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/werc20_mock" - "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" - "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" - ocrConfigHelper "github.com/smartcontractkit/libocr/offchainreporting/confighelper" - ocrTypes "github.com/smartcontractkit/libocr/offchainreporting/types" "github.com/smartcontractkit/chainlink/integration-tests/client" eth_contracts "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" @@ -940,7 +940,7 @@ func (f *EthereumFluxAggregator) PaymentAmount(ctx context.Context) (*big.Int, e return payment, nil } -func (f *EthereumFluxAggregator) RequestNewRound(ctx context.Context) error { +func (f *EthereumFluxAggregator) RequestNewRound(_ context.Context) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -979,7 +979,7 @@ func (f *EthereumFluxAggregator) WatchSubmissionReceived(ctx context.Context, ev } } -func (f *EthereumFluxAggregator) SetRequesterPermissions(ctx context.Context, addr common.Address, authorized bool, roundsDelay uint32) error { +func (f *EthereumFluxAggregator) SetRequesterPermissions(_ context.Context, addr common.Address, authorized bool, roundsDelay uint32) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -1020,7 +1020,7 @@ func (f *EthereumFluxAggregator) LatestRoundID(ctx context.Context) (*big.Int, e } func (f *EthereumFluxAggregator) WithdrawPayment( - ctx context.Context, + _ context.Context, from common.Address, to common.Address, amount *big.Int) error { @@ -2162,11 +2162,11 @@ func (e *EthereumFunctionsRouter) CreateSubscriptionWithConsumer(consumer string topicOneInputs := abi.Arguments{fabi.Events["SubscriptionCreated"].Inputs[0]} topicOneHash := []common.Hash{r.Logs[0].Topics[1:][0]} if err := abi.ParseTopicsIntoMap(topicsMap, topicOneInputs, topicOneHash); err != nil { - return 0, errors.Wrap(err, "failed to decode topic value") + return 0, fmt.Errorf("failed to decode topic value, err: %w", err) } e.l.Info().Interface("NewTopicsDecoded", topicsMap).Send() if topicsMap["subscriptionId"] == 0 { - return 0, errors.New("failed to decode subscription ID after creation") + return 0, fmt.Errorf("failed to decode subscription ID after creation") } return topicsMap["subscriptionId"].(uint64), nil } diff --git a/integration-tests/contracts/ethereum_keeper_contracts.go b/integration-tests/contracts/ethereum_keeper_contracts.go index 135b016ee55..7519b5de4cf 100644 --- a/integration-tests/contracts/ethereum_keeper_contracts.go +++ b/integration-tests/contracts/ethereum_keeper_contracts.go @@ -35,6 +35,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_triggered_streams_lookup_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_upkeep_counter_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/perform_data_checker_wrapper" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/simple_log_upkeep_counter_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/streams_lookup_upkeep_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/upkeep_perform_counter_restrictive_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/upkeep_transcoder" @@ -250,25 +251,25 @@ func (rcs *KeeperRegistrySettings) EncodeOnChainConfig(registrar string, registr encodedOnchainConfig, err := utilsABI.Methods["_onChainConfig"].Inputs.Pack(&onchainConfigStruct) return encodedOnchainConfig, err - } else { - configType := goabi.MustNewType("tuple(uint32 paymentPremiumPPB,uint32 flatFeeMicroLink,uint32 checkGasLimit,uint24 stalenessSeconds,uint16 gasCeilingMultiplier,uint96 minUpkeepSpend,uint32 maxPerformGas,uint32 maxCheckDataSize,uint32 maxPerformDataSize,uint256 fallbackGasPrice,uint256 fallbackLinkPrice,address transcoder,address registrar)") - onchainConfig, err := goabi.Encode(map[string]interface{}{ - "paymentPremiumPPB": rcs.PaymentPremiumPPB, - "flatFeeMicroLink": rcs.FlatFeeMicroLINK, - "checkGasLimit": rcs.CheckGasLimit, - "stalenessSeconds": rcs.StalenessSeconds, - "gasCeilingMultiplier": rcs.GasCeilingMultiplier, - "minUpkeepSpend": rcs.MinUpkeepSpend, - "maxPerformGas": rcs.MaxPerformGas, - "maxCheckDataSize": rcs.MaxCheckDataSize, - "maxPerformDataSize": rcs.MaxPerformDataSize, - "fallbackGasPrice": rcs.FallbackGasPrice, - "fallbackLinkPrice": rcs.FallbackLinkPrice, - "transcoder": common.Address{}, - "registrar": registrar, - }, configType) - return onchainConfig, err } + configType := goabi.MustNewType("tuple(uint32 paymentPremiumPPB,uint32 flatFeeMicroLink,uint32 checkGasLimit,uint24 stalenessSeconds,uint16 gasCeilingMultiplier,uint96 minUpkeepSpend,uint32 maxPerformGas,uint32 maxCheckDataSize,uint32 maxPerformDataSize,uint256 fallbackGasPrice,uint256 fallbackLinkPrice,address transcoder,address registrar)") + onchainConfig, err := goabi.Encode(map[string]interface{}{ + "paymentPremiumPPB": rcs.PaymentPremiumPPB, + "flatFeeMicroLink": rcs.FlatFeeMicroLINK, + "checkGasLimit": rcs.CheckGasLimit, + "stalenessSeconds": rcs.StalenessSeconds, + "gasCeilingMultiplier": rcs.GasCeilingMultiplier, + "minUpkeepSpend": rcs.MinUpkeepSpend, + "maxPerformGas": rcs.MaxPerformGas, + "maxCheckDataSize": rcs.MaxCheckDataSize, + "maxPerformDataSize": rcs.MaxPerformDataSize, + "fallbackGasPrice": rcs.FallbackGasPrice, + "fallbackLinkPrice": rcs.FallbackLinkPrice, + "transcoder": common.Address{}, + "registrar": registrar, + }, configType) + return onchainConfig, err + } func (v *EthereumKeeperRegistry) RegistryOwnerAddress() common.Address { @@ -276,6 +277,7 @@ func (v *EthereumKeeperRegistry) RegistryOwnerAddress() common.Address { Pending: false, } + //nolint: exhaustive switch v.version { case ethereum.RegistryVersion_2_1: ownerAddress, _ := v.registry2_1.Owner(callOpts) @@ -283,6 +285,8 @@ func (v *EthereumKeeperRegistry) RegistryOwnerAddress() common.Address { case ethereum.RegistryVersion_2_0: ownerAddress, _ := v.registry2_0.Owner(callOpts) return ownerAddress + case ethereum.RegistryVersion_1_0, ethereum.RegistryVersion_1_1, ethereum.RegistryVersion_1_2, ethereum.RegistryVersion_1_3: + return common.HexToAddress(v.client.GetDefaultWallet().Address()) } return common.HexToAddress(v.client.GetDefaultWallet().Address()) @@ -664,7 +668,7 @@ func (v *EthereumKeeperRegistry) GetKeeperInfo(ctx context.Context, keeperAddr s info, err = v.registry1_2.GetKeeperInfo(opts, common.HexToAddress(keeperAddr)) case ethereum.RegistryVersion_1_3: info, err = v.registry1_3.GetKeeperInfo(opts, common.HexToAddress(keeperAddr)) - case ethereum.RegistryVersion_2_0: + case ethereum.RegistryVersion_2_0, ethereum.RegistryVersion_2_1: // this is not used anywhere return nil, fmt.Errorf("not supported") } @@ -710,6 +714,8 @@ func (v *EthereumKeeperRegistry) SetKeepers(keepers []string, payees []string, o ocrConfig.OffchainConfigVersion, ocrConfig.OffchainConfig, ) + case ethereum.RegistryVersion_2_1: + return fmt.Errorf("not supported") } if err != nil { @@ -760,6 +766,8 @@ func (v *EthereumKeeperRegistry) RegisterUpkeep(target string, gasLimit uint32, checkData, nil, //offchain config ) + case ethereum.RegistryVersion_2_1: + return fmt.Errorf("not supported") } if err != nil { @@ -877,6 +885,8 @@ func (v *EthereumKeeperRegistry) GetKeeperList(ctx context.Context) ([]string, e return []string{}, err } list = state.Transmitters + case ethereum.RegistryVersion_2_1: + return nil, fmt.Errorf("not supported") } if err != nil { @@ -1112,6 +1122,7 @@ func (v *EthereumKeeperRegistry) ParseUpkeepPerformedLog(log *types.Log) (*Upkee // ParseStaleUpkeepReportLog Parses Stale upkeep report log func (v *EthereumKeeperRegistry) ParseStaleUpkeepReportLog(log *types.Log) (*StaleUpkeepReportLog, error) { + //nolint:exhaustive switch v.version { case ethereum.RegistryVersion_2_0: parsedLog, err := v.registry2_0.ParseStaleUpkeepReport(*log) @@ -1129,7 +1140,6 @@ func (v *EthereumKeeperRegistry) ParseStaleUpkeepReportLog(log *types.Log) (*Sta return &StaleUpkeepReportLog{ Id: parsedLog.Id, }, nil - } return nil, fmt.Errorf("keeper registry version %d is not supported", v.version) } @@ -1812,6 +1822,32 @@ func (v *EthereumAutomationLogCounterConsumer) Counter(ctx context.Context) (*bi return cnt, nil } +type EthereumAutomationSimpleLogCounterConsumer struct { + client blockchain.EVMClient + consumer *simple_log_upkeep_counter_wrapper.SimpleLogUpkeepCounter + address *common.Address +} + +func (v *EthereumAutomationSimpleLogCounterConsumer) Address() string { + return v.address.Hex() +} + +func (v *EthereumAutomationSimpleLogCounterConsumer) Start() error { + return nil +} + +func (v *EthereumAutomationSimpleLogCounterConsumer) Counter(ctx context.Context) (*big.Int, error) { + opts := &bind.CallOpts{ + From: common.HexToAddress(v.client.GetDefaultWallet().Address()), + Context: ctx, + } + cnt, err := v.consumer.Counter(opts) + if err != nil { + return nil, err + } + return cnt, nil +} + // EthereumKeeperConsumerPerformance represents a more complicated keeper consumer contract, one intended only for // performance tests. type EthereumKeeperConsumerPerformance struct { @@ -1850,7 +1886,7 @@ func (v *EthereumKeeperConsumerPerformance) GetUpkeepCount(ctx context.Context) return eligible, err } -func (v *EthereumKeeperConsumerPerformance) SetCheckGasToBurn(ctx context.Context, gas *big.Int) error { +func (v *EthereumKeeperConsumerPerformance) SetCheckGasToBurn(_ context.Context, gas *big.Int) error { opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) if err != nil { return err @@ -1862,7 +1898,7 @@ func (v *EthereumKeeperConsumerPerformance) SetCheckGasToBurn(ctx context.Contex return v.client.ProcessTransaction(tx) } -func (v *EthereumKeeperConsumerPerformance) SetPerformGasToBurn(ctx context.Context, gas *big.Int) error { +func (v *EthereumKeeperConsumerPerformance) SetPerformGasToBurn(_ context.Context, gas *big.Int) error { opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) if err != nil { return err @@ -1897,7 +1933,7 @@ func (v *EthereumKeeperPerformDataCheckerConsumer) Counter(ctx context.Context) return cnt, nil } -func (v *EthereumKeeperPerformDataCheckerConsumer) SetExpectedData(ctx context.Context, expectedData []byte) error { +func (v *EthereumKeeperPerformDataCheckerConsumer) SetExpectedData(_ context.Context, expectedData []byte) error { opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) if err != nil { return err @@ -2041,31 +2077,23 @@ func (v *EthereumKeeperRegistrar) EncodeRegisterRequest(name string, email []byt common.HexToAddress(senderAddr), ) - if err != nil { - return nil, err - } - return req, nil - } else { - req, err := registrarABI.Pack( - "register", - name, - email, - common.HexToAddress(upkeepAddr), - gasLimit, - common.HexToAddress(adminAddr), - uint8(0), // trigger type - checkData, - []byte{}, // triggerConfig - []byte{}, // offchainConfig - amount, - common.HexToAddress(senderAddr), - ) - - if err != nil { - return nil, err - } - return req, nil + return req, err } + req, err := registrarABI.Pack( + "register", + name, + email, + common.HexToAddress(upkeepAddr), + gasLimit, + common.HexToAddress(adminAddr), + uint8(0), // trigger type + checkData, + []byte{}, // triggerConfig + []byte{}, // offchainConfig + amount, + common.HexToAddress(senderAddr), + ) + return req, err } registryABI, err := abi.JSON(strings.NewReader(keeper_registrar_wrapper1_2.KeeperRegistrarMetaData.ABI)) if err != nil { diff --git a/integration-tests/contracts/ethereum_ocr2vrf_contracts.go b/integration-tests/contracts/ethereum_ocr2vrf_contracts.go index e8149b21251..cb52d1941a8 100644 --- a/integration-tests/contracts/ethereum_ocr2vrf_contracts.go +++ b/integration-tests/contracts/ethereum_ocr2vrf_contracts.go @@ -10,7 +10,6 @@ import ( "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" "github.com/rs/zerolog/log" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" @@ -230,7 +229,7 @@ func (dkgContract *EthereumDKG) WaitForTransmittedEvent(timeout time.Duration) ( case err = <-subscription.Err(): return nil, err case <-time.After(timeout): - return nil, errors.New("timeout waiting for DKGTransmitted event") + return nil, fmt.Errorf("timeout waiting for DKGTransmitted event") case transmittedEvent := <-transmittedEventsChannel: return transmittedEvent, nil } @@ -250,7 +249,7 @@ func (dkgContract *EthereumDKG) WaitForConfigSetEvent(timeout time.Duration) (*d case err = <-subscription.Err(): return nil, err case <-time.After(timeout): - return nil, errors.New("timeout waiting for DKGConfigSet event") + return nil, fmt.Errorf("timeout waiting for DKGConfigSet event") case configSetEvent := <-configSetEventsChannel: return configSetEvent, nil } @@ -451,7 +450,7 @@ func (consumer *EthereumVRFBeaconConsumer) RequestRandomness( ) (*types.Receipt, error) { opts, err := consumer.client.TransactionOpts(consumer.client.GetDefaultWallet()) if err != nil { - return nil, errors.Wrap(err, "TransactionOpts failed") + return nil, fmt.Errorf("TransactionOpts failed, err: %w", err) } tx, err := consumer.vrfBeaconConsumer.TestRequestRandomness( opts, @@ -460,20 +459,20 @@ func (consumer *EthereumVRFBeaconConsumer) RequestRandomness( confirmationDelayArg, ) if err != nil { - return nil, errors.Wrap(err, "TestRequestRandomness failed") + return nil, fmt.Errorf("TestRequestRandomness failed, err: %w", err) } err = consumer.client.ProcessTransaction(tx) if err != nil { - return nil, errors.Wrap(err, "ProcessTransaction failed") + return nil, fmt.Errorf("ProcessTransaction failed, err: %w", err) } err = consumer.client.WaitForEvents() if err != nil { - return nil, errors.Wrap(err, "WaitForEvents failed") + return nil, fmt.Errorf("WaitForEvents failed, err: %w", err) } receipt, err := consumer.client.GetTxReceipt(tx.Hash()) if err != nil { - return nil, errors.Wrap(err, "GetTxReceipt failed") + return nil, fmt.Errorf("GetTxReceipt failed, err: %w", err) } log.Info().Interface("Sub ID", subID). Interface("Number of Words", numWords). @@ -526,20 +525,20 @@ func (consumer *EthereumVRFBeaconConsumer) RequestRandomnessFulfillment( arguments, ) if err != nil { - return nil, errors.Wrap(err, "TestRequestRandomnessFulfillment failed") + return nil, fmt.Errorf("TestRequestRandomnessFulfillment failed, err: %w", err) } err = consumer.client.ProcessTransaction(tx) if err != nil { - return nil, errors.Wrap(err, "ProcessTransaction failed") + return nil, fmt.Errorf("ProcessTransaction failed, err: %w", err) } err = consumer.client.WaitForEvents() if err != nil { - return nil, errors.Wrap(err, "WaitForEvents failed") + return nil, fmt.Errorf("WaitForEvents failed, err: %w", err) } receipt, err := consumer.client.GetTxReceipt(tx.Hash()) if err != nil { - return nil, errors.Wrap(err, "GetTxReceipt failed") + return nil, fmt.Errorf("GetTxReceipt failed, err: %w", err) } log.Info().Interface("Sub ID", subID). Interface("Number of Words", numWords). diff --git a/integration-tests/contracts/ethereum_vrfv2_contracts.go b/integration-tests/contracts/ethereum_vrfv2_contracts.go index 9c7e628dbd9..d1637c93c87 100644 --- a/integration-tests/contracts/ethereum_vrfv2_contracts.go +++ b/integration-tests/contracts/ethereum_vrfv2_contracts.go @@ -3,7 +3,9 @@ package contracts import ( "context" "encoding/hex" + "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -182,16 +184,16 @@ func (v *EthereumVRFCoordinatorV2) RegisterProvingKey( return v.client.ProcessTransaction(tx) } -func (v *EthereumVRFCoordinatorV2) CreateSubscription() error { +func (v *EthereumVRFCoordinatorV2) CreateSubscription() (*types.Transaction, error) { opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) if err != nil { - return err + return nil, err } tx, err := v.coordinator.CreateSubscription(opts) if err != nil { - return err + return nil, err } - return v.client.ProcessTransaction(tx) + return tx, v.client.ProcessTransaction(tx) } func (v *EthereumVRFCoordinatorV2) AddConsumer(subId uint64, consumerAddress string) error { @@ -210,6 +212,94 @@ func (v *EthereumVRFCoordinatorV2) AddConsumer(subId uint64, consumerAddress str return v.client.ProcessTransaction(tx) } +func (v *EthereumVRFCoordinatorV2) PendingRequestsExist(ctx context.Context, subID uint64) (bool, error) { + opts := &bind.CallOpts{ + From: common.HexToAddress(v.client.GetDefaultWallet().Address()), + Context: ctx, + } + pendingRequestExists, err := v.coordinator.PendingRequestExists(opts, subID) + if err != nil { + return false, err + } + return pendingRequestExists, nil +} + +// CancelSubscription cancels subscription by Sub owner, +// return funds to specified address, +// checks if pending requests for a sub exist +func (v *EthereumVRFCoordinatorV2) CancelSubscription(subID uint64, to common.Address) (*types.Transaction, error) { + opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + tx, err := v.coordinator.CancelSubscription( + opts, + subID, + to, + ) + if err != nil { + return nil, err + } + return tx, v.client.ProcessTransaction(tx) +} + +func (v *EthereumVRFCoordinatorV2) FindSubscriptionID(subID uint64) (uint64, error) { + owner := v.client.GetDefaultWallet().Address() + subscriptionIterator, err := v.coordinator.FilterSubscriptionCreated( + nil, + []uint64{subID}, + ) + if err != nil { + return 0, err + } + + if !subscriptionIterator.Next() { + return 0, fmt.Errorf("expected at least 1 subID for the given owner %s", owner) + } + + return subscriptionIterator.Event.SubId, nil +} + +func (v *EthereumVRFCoordinatorV2) WaitForRandomWordsFulfilledEvent(requestID []*big.Int, timeout time.Duration) (*vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled, error) { + randomWordsFulfilledEventsChannel := make(chan *vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled) + subscription, err := v.coordinator.WatchRandomWordsFulfilled(nil, randomWordsFulfilledEventsChannel, requestID) + if err != nil { + return nil, err + } + defer subscription.Unsubscribe() + + for { + select { + case err := <-subscription.Err(): + return nil, err + case <-time.After(timeout): + return nil, fmt.Errorf("timeout waiting for RandomWordsFulfilled event") + case randomWordsFulfilledEvent := <-randomWordsFulfilledEventsChannel: + return randomWordsFulfilledEvent, nil + } + } +} + +func (v *EthereumVRFCoordinatorV2) WaitForRandomWordsRequestedEvent(keyHash [][32]byte, subID []uint64, sender []common.Address, timeout time.Duration) (*vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested, error) { + randomWordsFulfilledEventsChannel := make(chan *vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested) + subscription, err := v.coordinator.WatchRandomWordsRequested(nil, randomWordsFulfilledEventsChannel, keyHash, subID, sender) + if err != nil { + return nil, err + } + defer subscription.Unsubscribe() + + for { + select { + case err := <-subscription.Err(): + return nil, err + case <-time.After(timeout): + return nil, fmt.Errorf("timeout waiting for RandomWordsRequested event") + case randomWordsFulfilledEvent := <-randomWordsFulfilledEventsChannel: + return randomWordsFulfilledEvent, nil + } + } +} + // GetAllRandomWords get all VRFv2 randomness output words func (v *EthereumVRFConsumerV2) GetAllRandomWords(ctx context.Context, num int) ([]*big.Int, error) { words := make([]*big.Int, 0) @@ -351,17 +441,24 @@ func (v *EthereumVRFv2LoadTestConsumer) Address() string { return v.address.Hex() } -func (v *EthereumVRFv2LoadTestConsumer) RequestRandomness(keyHash [32]byte, subID uint64, requestConfirmations uint16, callbackGasLimit uint32, numWords uint32, requestCount uint16) error { +func (v *EthereumVRFv2LoadTestConsumer) RequestRandomness( + keyHash [32]byte, + subID uint64, + requestConfirmations uint16, + callbackGasLimit uint32, + numWords uint32, + requestCount uint16, +) (*types.Transaction, error) { opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) if err != nil { - return err + return nil, err } tx, err := v.consumer.RequestRandomWords(opts, subID, requestConfirmations, keyHash, callbackGasLimit, numWords, requestCount) if err != nil { - return err + return nil, err } - return v.client.ProcessTransaction(tx) + return tx, v.client.ProcessTransaction(tx) } func (v *EthereumVRFv2Consumer) GetRequestStatus(ctx context.Context, requestID *big.Int) (vrf_v2_consumer_wrapper.GetRequestStatus, error) { @@ -392,6 +489,18 @@ func (v *EthereumVRFv2LoadTestConsumer) GetLastRequestId(ctx context.Context) (* }) } +func (v *EthereumVRFv2LoadTestConsumer) ResetMetrics() error { + opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) + if err != nil { + return err + } + tx, err := v.consumer.Reset(opts) + if err != nil { + return err + } + return v.client.ProcessTransaction(tx) +} + func (v *EthereumVRFv2LoadTestConsumer) GetLoadTestMetrics(ctx context.Context) (*VRFLoadTestMetrics, error) { requestCount, err := v.consumer.SRequestCount(&bind.CallOpts{ From: common.HexToAddress(v.client.GetDefaultWallet().Address()), diff --git a/integration-tests/contracts/ethereum_vrfv2plus_contracts.go b/integration-tests/contracts/ethereum_vrfv2plus_contracts.go index df7cca54b2e..330166dc79d 100644 --- a/integration-tests/contracts/ethereum_vrfv2plus_contracts.go +++ b/integration-tests/contracts/ethereum_vrfv2plus_contracts.go @@ -96,6 +96,18 @@ func (v *EthereumVRFCoordinatorV2_5) GetActiveSubscriptionIds(ctx context.Contex return activeSubscriptionIds, nil } +func (v *EthereumVRFCoordinatorV2_5) PendingRequestsExist(ctx context.Context, subID *big.Int) (bool, error) { + opts := &bind.CallOpts{ + From: common.HexToAddress(v.client.GetDefaultWallet().Address()), + Context: ctx, + } + pendingRequestExists, err := v.coordinator.PendingRequestExists(opts, subID) + if err != nil { + return false, err + } + return pendingRequestExists, nil +} + func (v *EthereumVRFCoordinatorV2_5) GetSubscription(ctx context.Context, subID *big.Int) (vrf_coordinator_v2_5.GetSubscription, error) { opts := &bind.CallOpts{ From: common.HexToAddress(v.client.GetDefaultWallet().Address()), @@ -131,6 +143,75 @@ func (v *EthereumVRFCoordinatorV2_5) GetNativeTokenTotalBalance(ctx context.Cont return totalBalance, nil } +// OwnerCancelSubscription cancels subscription by Coordinator owner +// return funds to sub owner, +// does not check if pending requests for a sub exist +func (v *EthereumVRFCoordinatorV2_5) OwnerCancelSubscription(subID *big.Int) (*types.Transaction, error) { + opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + tx, err := v.coordinator.OwnerCancelSubscription( + opts, + subID, + ) + if err != nil { + return nil, err + } + return tx, v.client.ProcessTransaction(tx) +} + +// CancelSubscription cancels subscription by Sub owner, +// return funds to specified address, +// checks if pending requests for a sub exist +func (v *EthereumVRFCoordinatorV2_5) CancelSubscription(subID *big.Int, to common.Address) (*types.Transaction, error) { + opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + tx, err := v.coordinator.CancelSubscription( + opts, + subID, + to, + ) + if err != nil { + return nil, err + } + return tx, v.client.ProcessTransaction(tx) +} + +func (v *EthereumVRFCoordinatorV2_5) OracleWithdraw(recipient common.Address, amount *big.Int) error { + opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) + if err != nil { + return err + } + tx, err := v.coordinator.OracleWithdraw( + opts, + recipient, + amount, + ) + if err != nil { + return err + } + return v.client.ProcessTransaction(tx) +} + +func (v *EthereumVRFCoordinatorV2_5) OracleWithdrawNative(recipient common.Address, amount *big.Int) error { + opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) + if err != nil { + return err + } + tx, err := v.coordinator.OracleWithdrawNative( + opts, + recipient, + amount, + ) + if err != nil { + return err + } + return v.client.ProcessTransaction(tx) +} + func (v *EthereumVRFCoordinatorV2_5) SetConfig(minimumRequestConfirmations uint16, maxGasLimit uint32, stalenessSeconds uint32, gasAfterPaymentCalculation uint32, fallbackWeiPerUnitLink *big.Int, feeConfig vrf_coordinator_v2_5.VRFCoordinatorV25FeeConfig) error { opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) if err != nil { @@ -182,16 +263,16 @@ func (v *EthereumVRFCoordinatorV2_5) RegisterProvingKey( return v.client.ProcessTransaction(tx) } -func (v *EthereumVRFCoordinatorV2_5) CreateSubscription() error { +func (v *EthereumVRFCoordinatorV2_5) CreateSubscription() (*types.Transaction, error) { opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet()) if err != nil { - return err + return nil, err } tx, err := v.coordinator.CreateSubscription(opts) if err != nil { - return err + return nil, err } - return v.client.ProcessTransaction(tx) + return tx, v.client.ProcessTransaction(tx) } func (v *EthereumVRFCoordinatorV2_5) Migrate(subId *big.Int, coordinatorAddress string) error { @@ -250,11 +331,11 @@ func (v *EthereumVRFCoordinatorV2_5) FundSubscriptionWithNative(subId *big.Int, return v.client.ProcessTransaction(tx) } -func (v *EthereumVRFCoordinatorV2_5) FindSubscriptionID() (*big.Int, error) { +func (v *EthereumVRFCoordinatorV2_5) FindSubscriptionID(subID *big.Int) (*big.Int, error) { owner := v.client.GetDefaultWallet().Address() subscriptionIterator, err := v.coordinator.FilterSubscriptionCreated( nil, - nil, + []*big.Int{subID}, ) if err != nil { return nil, err @@ -287,6 +368,26 @@ func (v *EthereumVRFCoordinatorV2_5) WaitForSubscriptionCreatedEvent(timeout tim } } +func (v *EthereumVRFCoordinatorV2_5) WaitForSubscriptionCanceledEvent(subID *big.Int, timeout time.Duration) (*vrf_coordinator_v2_5.VRFCoordinatorV25SubscriptionCanceled, error) { + eventsChannel := make(chan *vrf_coordinator_v2_5.VRFCoordinatorV25SubscriptionCanceled) + subscription, err := v.coordinator.WatchSubscriptionCanceled(nil, eventsChannel, []*big.Int{subID}) + if err != nil { + return nil, err + } + defer subscription.Unsubscribe() + + for { + select { + case err := <-subscription.Err(): + return nil, err + case <-time.After(timeout): + return nil, fmt.Errorf("timeout waiting for SubscriptionCanceled event") + case sub := <-eventsChannel: + return sub, nil + } + } +} + func (v *EthereumVRFCoordinatorV2_5) WaitForRandomWordsFulfilledEvent(subID []*big.Int, requestID []*big.Int, timeout time.Duration) (*vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsFulfilled, error) { randomWordsFulfilledEventsChannel := make(chan *vrf_coordinator_v2_5.VRFCoordinatorV25RandomWordsFulfilled) subscription, err := v.coordinator.WatchRandomWordsFulfilled(nil, randomWordsFulfilledEventsChannel, requestID, subID) diff --git a/integration-tests/contracts/test_contracts.go b/integration-tests/contracts/test_contracts.go new file mode 100644 index 00000000000..3080668da69 --- /dev/null +++ b/integration-tests/contracts/test_contracts.go @@ -0,0 +1,80 @@ +package contracts + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/rs/zerolog" + + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + + le "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter" +) + +type LogEmitterContract struct { + address common.Address + client blockchain.EVMClient + instance *le.LogEmitter + l zerolog.Logger +} + +func (e *LogEmitterContract) Address() common.Address { + return e.address +} + +func (e *LogEmitterContract) EmitLogInts(ints []int) (*types.Transaction, error) { + opts, err := e.client.TransactionOpts(e.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + bigInts := make([]*big.Int, len(ints)) + for i, v := range ints { + bigInts[i] = big.NewInt(int64(v)) + } + tx, err := e.instance.EmitLog1(opts, bigInts) + if err != nil { + return nil, err + } + return tx, e.client.ProcessTransaction(tx) +} + +func (e *LogEmitterContract) EmitLogIntsIndexed(ints []int) (*types.Transaction, error) { + opts, err := e.client.TransactionOpts(e.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + bigInts := make([]*big.Int, len(ints)) + for i, v := range ints { + bigInts[i] = big.NewInt(int64(v)) + } + tx, err := e.instance.EmitLog2(opts, bigInts) + if err != nil { + return nil, err + } + return tx, e.client.ProcessTransaction(tx) +} + +func (e *LogEmitterContract) EmitLogStrings(strings []string) (*types.Transaction, error) { + opts, err := e.client.TransactionOpts(e.client.GetDefaultWallet()) + if err != nil { + return nil, err + } + tx, err := e.instance.EmitLog3(opts, strings) + if err != nil { + return nil, err + } + return tx, e.client.ProcessTransaction(tx) +} + +func (e *LogEmitterContract) EmitLogInt(payload int) (*types.Transaction, error) { + return e.EmitLogInts([]int{payload}) +} + +func (e *LogEmitterContract) EmitLogIntIndexed(payload int) (*types.Transaction, error) { + return e.EmitLogIntsIndexed([]int{payload}) +} + +func (e *LogEmitterContract) EmitLogString(strings string) (*types.Transaction, error) { + return e.EmitLogStrings([]string{strings}) +} diff --git a/integration-tests/docker/cmd/test_env.go b/integration-tests/docker/cmd/test_env.go index 31b7de5dcdd..5fe2001350e 100644 --- a/integration-tests/docker/cmd/test_env.go +++ b/integration-tests/docker/cmd/test_env.go @@ -9,10 +9,11 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" - "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/spf13/cobra" "github.com/testcontainers/testcontainers-go" + + "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" ) func main() { @@ -31,7 +32,7 @@ func main() { Use: "cl-cluster", Short: "Basic CL cluster", RunE: func(cmd *cobra.Command, args []string) error { - utils.SetupCoreDockerEnvLogger() + log.Logger = logging.GetLogger(nil, "CORE_DOCKER_ENV_LOG_LEVEL") log.Info().Msg("Starting CL cluster test environment..") _, err := test_env.NewCLTestEnvBuilder(). @@ -50,6 +51,7 @@ func main() { return nil }, } + startEnvCmd.AddCommand(startFullEnvCmd) // Set default log level for non-testcontainer code diff --git a/integration-tests/docker/test_env/cl_node.go b/integration-tests/docker/test_env/cl_node.go index bf4d3285dcf..7dc077ad34b 100644 --- a/integration-tests/docker/test_env/cl_node.go +++ b/integration-tests/docker/test_env/cl_node.go @@ -1,15 +1,11 @@ package test_env import ( - "context" - "crypto/ed25519" - "encoding/hex" "fmt" "math/big" "net/url" "os" "strings" - "sync" "testing" "time" @@ -17,7 +13,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" "github.com/pelletier/go-toml/v2" - "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" tc "github.com/testcontainers/testcontainers-go" @@ -28,13 +23,12 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/logwatch" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" - "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" "github.com/smartcontractkit/chainlink/integration-tests/client" - "github.com/smartcontractkit/chainlink/integration-tests/utils" + it_utils "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/smartcontractkit/chainlink/integration-tests/utils/templates" ) @@ -49,6 +43,8 @@ type ClNode struct { NodeConfig *chainlink.Config `json:"-"` NodeSecretsConfigTOML string `json:"-"` PostgresDb *test_env.PostgresDb `json:"postgresDb"` + UserEmail string `json:"userEmail"` + UserPassword string `json:"userPassword"` t *testing.T l zerolog.Logger lw *logwatch.LogWatch @@ -86,23 +82,30 @@ func WithLogWatch(lw *logwatch.LogWatch) ClNodeOption { } } -func NewClNode(networks []string, nodeConfig *chainlink.Config, opts ...ClNodeOption) *ClNode { +func NewClNode(networks []string, imageName, imageVersion string, nodeConfig *chainlink.Config, opts ...ClNodeOption) (*ClNode, error) { nodeDefaultCName := fmt.Sprintf("%s-%s", "cl-node", uuid.NewString()[0:8]) pgDefaultCName := fmt.Sprintf("pg-%s", nodeDefaultCName) - pgDb := test_env.NewPostgresDb(networks, test_env.WithPostgresDbContainerName(pgDefaultCName)) + pgDb, err := test_env.NewPostgresDb(networks, test_env.WithPostgresDbContainerName(pgDefaultCName)) + if err != nil { + return nil, err + } n := &ClNode{ EnvComponent: test_env.EnvComponent{ - ContainerName: nodeDefaultCName, - Networks: networks, + ContainerName: nodeDefaultCName, + ContainerImage: imageName, + ContainerVersion: imageVersion, + Networks: networks, }, - NodeConfig: nodeConfig, - PostgresDb: pgDb, - l: log.Logger, + UserEmail: "local@local.com", + UserPassword: "localdevpassword", + NodeConfig: nodeConfig, + PostgresDb: pgDb, + l: log.Logger, } for _, opt := range opts { opt(n) } - return n + return n, nil } func (n *ClNode) SetTestLogger(t *testing.T) { @@ -113,7 +116,7 @@ func (n *ClNode) SetTestLogger(t *testing.T) { // Restart restarts only CL node, DB container is reused func (n *ClNode) Restart(cfg *chainlink.Config) error { - if err := n.Container.Terminate(context.Background()); err != nil { + if err := n.Container.Terminate(testcontext.Get(n.t)); err != nil { return err } n.NodeConfig = cfg @@ -121,12 +124,12 @@ func (n *ClNode) Restart(cfg *chainlink.Config) error { } // UpgradeVersion restarts the cl node with new image and version -func (n *ClNode) UpgradeVersion(cfg *chainlink.Config, newImage, newVersion string) error { +func (n *ClNode) UpgradeVersion(newImage, newVersion string) error { if newVersion == "" { return fmt.Errorf("new version is empty") } if newImage == "" { - newImage = os.Getenv("CHAINLINK_IMAGE") + return fmt.Errorf("new image name is empty") } n.ContainerImage = newImage n.ContainerVersion = newVersion @@ -137,9 +140,9 @@ func (n *ClNode) PrimaryETHAddress() (string, error) { return n.API.PrimaryEthAddress() } -func (n *ClNode) AddBootstrapJob(verifierAddr common.Address, fromBlock uint64, chainId int64, +func (n *ClNode) AddBootstrapJob(verifierAddr common.Address, chainId int64, feedId [32]byte) (*client.Job, error) { - spec := utils.BuildBootstrapSpec(verifierAddr, chainId, fromBlock, feedId) + spec := it_utils.BuildBootstrapSpec(verifierAddr, chainId, feedId) return n.API.MustCreateJob(spec) } @@ -167,7 +170,7 @@ func (n *ClNode) AddMercuryOCRJob(verifierAddr common.Address, fromBlock uint64, } } - bridges := utils.BuildBridges(eaUrls) + bridges := it_utils.BuildBridges(eaUrls) for index := range bridges { err = n.API.MustCreateBridge(&bridges[index]) if err != nil { @@ -182,7 +185,7 @@ func (n *ClNode) AddMercuryOCRJob(verifierAddr common.Address, fromBlock uint64, allowedFaults = 2 } - spec := utils.BuildOCRSpec( + spec := it_utils.BuildOCRSpec( verifierAddr, chainId, fromBlock, feedId, bridges, csaPubKey, mercuryServerUrl, mercuryServerPubKey, nodeOCRKeyId[0], bootstrapUrl, allowedFaults) @@ -191,13 +194,17 @@ func (n *ClNode) AddMercuryOCRJob(verifierAddr common.Address, fromBlock uint64, } func (n *ClNode) GetContainerName() string { - name, err := n.Container.Name(context.Background()) + name, err := n.Container.Name(testcontext.Get(n.t)) if err != nil { return "" } return strings.Replace(name, "/", "", -1) } +func (n *ClNode) GetAPIClient() *client.ChainlinkClient { + return n.API +} + func (n *ClNode) GetPeerUrl() (string, error) { p2pKeys, err := n.API.MustReadP2PKeys() if err != nil { @@ -276,34 +283,39 @@ func (n *ClNode) StartContainer() error { Logger: l, }) if err != nil { - return errors.Wrap(err, ErrStartCLNodeContainer) + return fmt.Errorf("%s err: %w", ErrStartCLNodeContainer, err) } if n.lw != nil { - if err := n.lw.ConnectContainer(context.Background(), container, "cl-node", true); err != nil { + if err := n.lw.ConnectContainer(testcontext.Get(n.t), container, "cl-node", true); err != nil { return err } } - clEndpoint, err := test_env.GetEndpoint(context.Background(), container, "http") + clEndpoint, err := test_env.GetEndpoint(testcontext.Get(n.t), container, "http") if err != nil { return err } - ip, err := container.ContainerIP(context.Background()) + ip, err := container.ContainerIP(testcontext.Get(n.t)) if err != nil { return err } - n.l.Info().Str("containerName", n.ContainerName). + n.l.Info(). + Str("containerName", n.ContainerName). + Str("containerImage", n.ContainerImage). + Str("containerVersion", n.ContainerVersion). Str("clEndpoint", clEndpoint). Str("clInternalIP", ip). + Str("userEmail", n.UserEmail). + Str("userPassword", n.UserPassword). Msg("Started Chainlink Node container") clClient, err := client.NewChainlinkClient(&client.ChainlinkConfig{ URL: clEndpoint, - Email: "local@local.com", - Password: "localdevpassword", + Email: n.UserEmail, + Password: n.UserPassword, InternalIP: ip, }, n.l) if err != nil { - return errors.Wrap(err, ErrConnectNodeClient) + return fmt.Errorf("%s err: %w", ErrConnectNodeClient, err) } clClient.Config.InternalIP = n.ContainerName n.Container = container @@ -360,21 +372,6 @@ func (n *ClNode) getContainerRequest(secrets string) ( adminCredsPath := "/home/admin-credentials.txt" apiCredsPath := "/home/api-credentials.txt" - if n.ContainerImage == "" { - image, ok := os.LookupEnv("CHAINLINK_IMAGE") - if !ok { - return nil, errors.New("CHAINLINK_IMAGE env must be set") - } - n.ContainerImage = image - } - if n.ContainerVersion == "" { - version, ok := os.LookupEnv("CHAINLINK_VERSION") - if !ok { - return nil, errors.New("CHAINLINK_VERSION env must be set") - } - n.ContainerVersion = version - } - return &tc.ContainerRequest{ Name: n.ContainerName, Image: fmt.Sprintf("%s:%s", n.ContainerImage, n.ContainerVersion), @@ -415,83 +412,3 @@ func (n *ClNode) getContainerRequest(secrets string) ( }, }, nil } - -func GetOracleIdentities(chainlinkNodes []*ClNode) ([]int, []confighelper.OracleIdentityExtra) { - S := make([]int, len(chainlinkNodes)) - oracleIdentities := make([]confighelper.OracleIdentityExtra, len(chainlinkNodes)) - sharedSecretEncryptionPublicKeys := make([]ocrtypes.ConfigEncryptionPublicKey, len(chainlinkNodes)) - var wg sync.WaitGroup - for i, cl := range chainlinkNodes { - wg.Add(1) - go func(i int, cl *ClNode) error { - defer wg.Done() - - ocr2Keys, err := cl.API.MustReadOCR2Keys() - if err != nil { - return err - } - var ocr2Config client.OCR2KeyAttributes - for _, key := range ocr2Keys.Data { - if key.Attributes.ChainType == string(chaintype.EVM) { - ocr2Config = key.Attributes - break - } - } - - keys, err := cl.API.MustReadP2PKeys() - if err != nil { - return err - } - p2pKeyID := keys.Data[0].Attributes.PeerID - - offchainPkBytes, err := hex.DecodeString(strings.TrimPrefix(ocr2Config.OffChainPublicKey, "ocr2off_evm_")) - if err != nil { - return err - } - - offchainPkBytesFixed := [ed25519.PublicKeySize]byte{} - copy(offchainPkBytesFixed[:], offchainPkBytes) - if err != nil { - return err - } - - configPkBytes, err := hex.DecodeString(strings.TrimPrefix(ocr2Config.ConfigPublicKey, "ocr2cfg_evm_")) - if err != nil { - return err - } - - configPkBytesFixed := [ed25519.PublicKeySize]byte{} - copy(configPkBytesFixed[:], configPkBytes) - if err != nil { - return err - } - - onchainPkBytes, err := hex.DecodeString(strings.TrimPrefix(ocr2Config.OnChainPublicKey, "ocr2on_evm_")) - if err != nil { - return err - } - - csaKeys, _, err := cl.API.ReadCSAKeys() - if err != nil { - return err - } - - sharedSecretEncryptionPublicKeys[i] = configPkBytesFixed - oracleIdentities[i] = confighelper.OracleIdentityExtra{ - OracleIdentity: confighelper.OracleIdentity{ - OnchainPublicKey: onchainPkBytes, - OffchainPublicKey: offchainPkBytesFixed, - PeerID: p2pKeyID, - TransmitAccount: ocrtypes.Account(csaKeys.Data[0].ID), - }, - ConfigEncryptionPublicKey: configPkBytesFixed, - } - S[i] = 1 - - return nil - }(i, cl) - } - wg.Wait() - - return S, oracleIdentities -} diff --git a/integration-tests/docker/test_env/cl_node_cluster.go b/integration-tests/docker/test_env/cl_node_cluster.go index a717a192649..08122b5744d 100644 --- a/integration-tests/docker/test_env/cl_node_cluster.go +++ b/integration-tests/docker/test_env/cl_node_cluster.go @@ -1,10 +1,12 @@ package test_env import ( + "fmt" + "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/integration-tests/client" "golang.org/x/sync/errgroup" + + "github.com/smartcontractkit/chainlink/integration-tests/client" ) var ( @@ -60,7 +62,7 @@ func (c *ClCluster) NodeCSAKeys() ([]string, error) { for _, n := range c.Nodes { csaKeys, err := n.GetNodeCSAKeys() if err != nil { - return nil, errors.Wrap(err, ErrGetNodeCSAKeys) + return nil, fmt.Errorf("%s, err: %w", ErrGetNodeCSAKeys, err) } keys = append(keys, csaKeys.Data[0].ID) } diff --git a/integration-tests/docker/test_env/test_env.go b/integration-tests/docker/test_env/test_env.go index 9eb9ed9f39b..030a3ae9622 100644 --- a/integration-tests/docker/test_env/test_env.go +++ b/integration-tests/docker/test_env/test_env.go @@ -1,18 +1,17 @@ package test_env import ( - "context" "encoding/json" "fmt" "io" "math/big" "os" "path/filepath" + "runtime/debug" "testing" "time" "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" tc "github.com/testcontainers/testcontainers-go" @@ -23,11 +22,11 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/logwatch" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" - "github.com/smartcontractkit/chainlink/integration-tests/utils" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" ) var ( @@ -40,26 +39,26 @@ type CLClusterTestEnv struct { LogWatch *logwatch.LogWatch /* components */ - ClCluster *ClCluster - Geth *test_env.Geth // for tests using --dev networks - PrivateChain []test_env.PrivateChain // for tests using non-dev networks - MockAdapter *test_env.Killgrave - EVMClient blockchain.EVMClient - ContractDeployer contracts.ContractDeployer - ContractLoader contracts.ContractLoader - l zerolog.Logger - t *testing.T + ClCluster *ClCluster + PrivateChain []test_env.PrivateChain // for tests using non-dev networks -- unify it with new approach + MockAdapter *test_env.Killgrave + EVMClient blockchain.EVMClient + ContractDeployer contracts.ContractDeployer + ContractLoader contracts.ContractLoader + RpcProvider test_env.RpcProvider + PrivateEthereumConfig *test_env.EthereumNetwork // new approach to private chains, supporting eth1 and eth2 + l zerolog.Logger + t *testing.T } func NewTestEnv() (*CLClusterTestEnv, error) { - utils.SetupCoreDockerEnvLogger() + log.Logger = logging.GetLogger(nil, "CORE_DOCKER_ENV_LOG_LEVEL") network, err := docker.CreateNetwork(log.Logger) if err != nil { return nil, err } n := []string{network.Name} return &CLClusterTestEnv{ - Geth: test_env.NewGeth(n), MockAdapter: test_env.NewKillgrave(n, ""), Network: network, l: log.Logger, @@ -67,11 +66,10 @@ func NewTestEnv() (*CLClusterTestEnv, error) { } // WithTestEnvConfig sets the test environment cfg. -// Sets up the Geth and MockAdapter containers with the provided cfg. +// Sets up private ethereum chain and MockAdapter containers with the provided cfg. func (te *CLClusterTestEnv) WithTestEnvConfig(cfg *TestEnvConfig) *CLClusterTestEnv { te.Cfg = cfg n := []string{te.Network.Name} - te.Geth = test_env.NewGeth(n, test_env.WithContainerName(te.Cfg.Geth.ContainerName)) te.MockAdapter = test_env.NewKillgrave(n, te.Cfg.MockAdapter.ImpostersPath, test_env.WithContainerName(te.Cfg.MockAdapter.ContainerName)) return te } @@ -79,7 +77,6 @@ func (te *CLClusterTestEnv) WithTestEnvConfig(cfg *TestEnvConfig) *CLClusterTest func (te *CLClusterTestEnv) WithTestLogger(t *testing.T) *CLClusterTestEnv { te.t = t te.l = logging.GetTestLogger(t) - te.Geth.WithTestLogger(t) te.MockAdapter.WithTestLogger(t) return te } @@ -114,7 +111,7 @@ func (te *CLClusterTestEnv) StartPrivateChain() error { for _, chain := range te.PrivateChain { primaryNode := chain.GetPrimaryNode() if primaryNode == nil { - return errors.WithStack(fmt.Errorf("primary node is nil in PrivateChain interface")) + return fmt.Errorf("primary node is nil in PrivateChain interface, stack: %s", string(debug.Stack())) } err := primaryNode.Start() if err != nil { @@ -128,23 +125,43 @@ func (te *CLClusterTestEnv) StartPrivateChain() error { return nil } -func (te *CLClusterTestEnv) StartGeth() (blockchain.EVMNetwork, test_env.InternalDockerUrls, error) { - return te.Geth.StartContainer() +func (te *CLClusterTestEnv) StartEthereumNetwork(cfg *test_env.EthereumNetwork) (blockchain.EVMNetwork, test_env.RpcProvider, error) { + // if environment is being restored from a previous state, use the existing config + // this might fail terribly if temporary folders with chain data on the host machine were removed + if te.Cfg != nil && te.Cfg.EthereumNetwork != nil { + builder := test_env.NewEthereumNetworkBuilder() + c, err := builder.WithExistingConfig(*te.Cfg.EthereumNetwork). + WithTest(te.t). + Build() + if err != nil { + return blockchain.EVMNetwork{}, test_env.RpcProvider{}, err + } + cfg = &c + } + n, rpc, err := cfg.Start() + + if err != nil { + return blockchain.EVMNetwork{}, test_env.RpcProvider{}, err + } + + return n, rpc, nil } func (te *CLClusterTestEnv) StartMockAdapter() error { return te.MockAdapter.StartContainer() } -func (te *CLClusterTestEnv) StartClCluster(nodeConfig *chainlink.Config, count int, secretsConfig string) error { +func (te *CLClusterTestEnv) StartClCluster(nodeConfig *chainlink.Config, count int, secretsConfig string, opts ...ClNodeOption) error { if te.Cfg != nil && te.Cfg.ClCluster != nil { te.ClCluster = te.Cfg.ClCluster } else { + opts = append(opts, WithSecrets(secretsConfig)) te.ClCluster = &ClCluster{} for i := 0; i < count; i++ { - ocrNode := NewClNode([]string{te.Network.Name}, nodeConfig, - WithSecrets(secretsConfig), - ) + ocrNode, err := NewClNode([]string{te.Network.Name}, os.Getenv("CHAINLINK_IMAGE"), os.Getenv("CHAINLINK_VERSION"), nodeConfig, opts...) + if err != nil { + return err + } te.ClCluster.Nodes = append(te.ClCluster.Nodes, ocrNode) } } @@ -164,8 +181,9 @@ func (te *CLClusterTestEnv) StartClCluster(nodeConfig *chainlink.Config, count i func (te *CLClusterTestEnv) FundChainlinkNodes(amount *big.Float) error { for _, cl := range te.ClCluster.Nodes { if err := cl.Fund(te.EVMClient, amount); err != nil { - return errors.Wrap(err, ErrFundCLNode) + return fmt.Errorf("%s, err: %w", ErrFundCLNode, err) } + time.Sleep(5 * time.Second) } return te.EVMClient.WaitForEvents() } @@ -180,12 +198,14 @@ func (te *CLClusterTestEnv) Terminate() error { func (te *CLClusterTestEnv) Cleanup() error { te.l.Info().Msg("Cleaning up test environment") if te.t == nil { - return errors.New("cannot cleanup test environment without a testing.T") + return fmt.Errorf("cannot cleanup test environment without a testing.T") } if te.ClCluster == nil || len(te.ClCluster.Nodes) == 0 { - return errors.New("chainlink nodes are nil, unable cleanup chainlink nodes") + return fmt.Errorf("chainlink nodes are nil, unable cleanup chainlink nodes") } + te.logWhetherAllContainersAreRunning() + // TODO: This is an imperfect and temporary solution, see TT-590 for a more sustainable solution // Collect logs if the test fails, or if we just want them if te.t.Failed() || os.Getenv("TEST_LOG_COLLECT") == "true" { @@ -195,7 +215,7 @@ func (te *CLClusterTestEnv) Cleanup() error { } if te.EVMClient == nil { - return errors.New("evm client is nil, unable to return funds from chainlink nodes during cleanup") + return fmt.Errorf("evm client is nil, unable to return funds from chainlink nodes during cleanup") } else if te.EVMClient.NetworkSimulated() { te.l.Info(). Str("Network Name", te.EVMClient.GetNetworkName()). @@ -215,6 +235,21 @@ func (te *CLClusterTestEnv) Cleanup() error { return nil } +func (te *CLClusterTestEnv) logWhetherAllContainersAreRunning() { + for _, node := range te.ClCluster.Nodes { + isCLRunning := node.Container.IsRunning() + isDBRunning := node.PostgresDb.Container.IsRunning() + + if !isCLRunning { + te.l.Warn().Str("Node", node.ContainerName).Msg("Chainlink node was not running, when test ended") + } + + if !isDBRunning { + te.l.Warn().Str("Node", node.ContainerName).Msg("Postgres DB is not running, when test ended") + } + } +} + // collectTestLogs collects the logs from all the Chainlink nodes in the test environment and writes them to local files func (te *CLClusterTestEnv) collectTestLogs() error { te.l.Info().Msg("Collecting test logs") @@ -233,7 +268,7 @@ func (te *CLClusterTestEnv) collectTestLogs() error { return err } defer logFile.Close() - logReader, err := node.Container.Logs(context.Background()) + logReader, err := node.Container.Logs(testcontext.Get(te.t)) if err != nil { return err } diff --git a/integration-tests/docker/test_env/test_env_builder.go b/integration-tests/docker/test_env/test_env_builder.go index 072728321bf..685ea5338ae 100644 --- a/integration-tests/docker/test_env/test_env_builder.go +++ b/integration-tests/docker/test_env/test_env_builder.go @@ -4,9 +4,9 @@ import ( "fmt" "math/big" "os" + "runtime/debug" "testing" - "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -17,6 +17,8 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/networks" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" ) @@ -30,22 +32,26 @@ const ( ) type CLTestEnvBuilder struct { - hasLogWatch bool - hasGeth bool - hasKillgrave bool - hasForwarders bool - clNodeConfig *chainlink.Config - secretsConfig string - nonDevGethNetworks []blockchain.EVMNetwork - clNodesCount int - customNodeCsaKeys []string - defaultNodeCsaKeys []string - l zerolog.Logger - t *testing.T - te *CLClusterTestEnv - isNonEVM bool - cleanUpType CleanUpType - cleanUpCustomFn func() + hasLogWatch bool + // hasGeth bool + hasKillgrave bool + hasForwarders bool + clNodeConfig *chainlink.Config + secretsConfig string + nonDevGethNetworks []blockchain.EVMNetwork + clNodesCount int + clNodesOpts []func(*ClNode) + customNodeCsaKeys []string + defaultNodeCsaKeys []string + l zerolog.Logger + t *testing.T + te *CLClusterTestEnv + isNonEVM bool + cleanUpType CleanUpType + cleanUpCustomFn func() + chainOptionsFn []ChainOption + evmClientNetworkOption []EVMClientNetworkOption + ethereumNetwork *test_env.EthereumNetwork /* funding */ ETHFunds *big.Float @@ -105,6 +111,11 @@ func (b *CLTestEnvBuilder) WithCLNodes(clNodesCount int) *CLTestEnvBuilder { return b } +func (b *CLTestEnvBuilder) WithCLNodeOptions(opt ...ClNodeOption) *CLTestEnvBuilder { + b.clNodesOpts = append(b.clNodesOpts, opt...) + return b +} + func (b *CLTestEnvBuilder) WithForwarders() *CLTestEnvBuilder { b.hasForwarders = true return b @@ -115,8 +126,27 @@ func (b *CLTestEnvBuilder) WithFunding(eth *big.Float) *CLTestEnvBuilder { return b } +// deprecated +// left only for backward compatibility func (b *CLTestEnvBuilder) WithGeth() *CLTestEnvBuilder { - b.hasGeth = true + ethBuilder := test_env.NewEthereumNetworkBuilder() + cfg, err := ethBuilder. + WithConsensusType(test_env.ConsensusType_PoW). + WithExecutionLayer(test_env.ExecutionLayer_Geth). + WithTest(b.t). + Build() + + if err != nil { + panic(err) + } + + b.ethereumNetwork = &cfg + + return b +} + +func (b *CLTestEnvBuilder) WithPrivateEthereumNetwork(en test_env.EthereumNetwork) *CLTestEnvBuilder { + b.ethereumNetwork = &en return b } @@ -162,6 +192,24 @@ func (b *CLTestEnvBuilder) WithCustomCleanup(customFn func()) *CLTestEnvBuilder return b } +type ChainOption = func(*evmcfg.Chain) *evmcfg.Chain + +func (b *CLTestEnvBuilder) WithChainOptions(opts ...ChainOption) *CLTestEnvBuilder { + b.chainOptionsFn = make([]ChainOption, 0) + b.chainOptionsFn = append(b.chainOptionsFn, opts...) + + return b +} + +type EVMClientNetworkOption = func(*blockchain.EVMNetwork) *blockchain.EVMNetwork + +func (b *CLTestEnvBuilder) EVMClientNetworkOptions(opts ...EVMClientNetworkOption) *CLTestEnvBuilder { + b.evmClientNetworkOption = make([]EVMClientNetworkOption, 0) + b.evmClientNetworkOption = append(b.evmClientNetworkOption, opts...) + + return b +} + func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { if b.te == nil { var err error @@ -170,13 +218,6 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { return nil, err } } - b.l.Info(). - Bool("hasGeth", b.hasGeth). - Bool("hasKillgrave", b.hasKillgrave). - Int("clNodesCount", b.clNodesCount). - Strs("customNodeCsaKeys", b.customNodeCsaKeys). - Strs("defaultNodeCsaKeys", b.defaultNodeCsaKeys). - Msg("Building CL cluster test environment..") var err error if b.t != nil { @@ -209,7 +250,7 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { case CleanUpTypeNone: b.l.Warn().Msg("test environment won't be cleaned up") case "": - return b.te, errors.WithMessage(errors.New("explicit cleanup type must be set when building test environment"), "test environment builder failed") + return b.te, fmt.Errorf("test environment builder failed: %w", fmt.Errorf("explicit cleanup type must be set when building test environment")) } if b.nonDevGethNetworks != nil { @@ -222,33 +263,45 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { for i, n := range b.te.PrivateChain { primaryNode := n.GetPrimaryNode() if primaryNode == nil { - return b.te, errors.WithStack(fmt.Errorf("primary node is nil in PrivateChain interface")) + return b.te, fmt.Errorf("primary node is nil in PrivateChain interface, stack: %s", string(debug.Stack())) } nonDevNetworks = append(nonDevNetworks, *n.GetNetworkConfig()) nonDevNetworks[i].URLs = []string{primaryNode.GetInternalWsUrl()} nonDevNetworks[i].HTTPURLs = []string{primaryNode.GetInternalHttpUrl()} } if nonDevNetworks == nil { - return nil, errors.New("cannot create nodes with custom config without nonDevNetworks") + return nil, fmt.Errorf("cannot create nodes with custom config without nonDevNetworks") } - err = b.te.StartClCluster(b.clNodeConfig, b.clNodesCount, b.secretsConfig) + err = b.te.StartClCluster(b.clNodeConfig, b.clNodesCount, b.secretsConfig, b.clNodesOpts...) if err != nil { return nil, err } return b.te, nil } - networkConfig := networks.SelectedNetwork - var internalDockerUrls test_env.InternalDockerUrls - if b.hasGeth && networkConfig.Simulated { - networkConfig, internalDockerUrls, err = b.te.StartGeth() + + networkConfig := networks.MustGetSelectedNetworksFromEnv()[0] + var rpcProvider test_env.RpcProvider + if b.ethereumNetwork != nil && networkConfig.Simulated { + // TODO here we should save the ethereum network config to te.Cfg, but it doesn't exist at this point + // in general it seems we have no methods for saving config to file and we only load it from file + // but I don't know how that config file is to be created or whether anyone ever done that + var enCfg test_env.EthereumNetwork + b.ethereumNetwork.DockerNetworkNames = []string{b.te.Network.Name} + networkConfig, rpcProvider, err = b.te.StartEthereumNetwork(b.ethereumNetwork) if err != nil { return nil, err } - + b.te.RpcProvider = rpcProvider + b.te.PrivateEthereumConfig = &enCfg } if !b.isNonEVM { + if b.evmClientNetworkOption != nil && len(b.evmClientNetworkOption) > 0 { + for _, fn := range b.evmClientNetworkOption { + fn(&networkConfig) + } + } bc, err := blockchain.NewEVMClientFromNetwork(networkConfig, b.l) if err != nil { return nil, err @@ -278,7 +331,7 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { } else { cfg = node.NewConfig(node.NewBaseConfig(), node.WithOCR1(), - node.WithP2Pv1(), + node.WithP2Pv2(), ) } @@ -286,17 +339,25 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { var httpUrls []string var wsUrls []string if networkConfig.Simulated { - httpUrls = []string{internalDockerUrls.HttpUrl} - wsUrls = []string{internalDockerUrls.WsUrl} + httpUrls = rpcProvider.PrivateHttpUrls() + wsUrls = rpcProvider.PrivateWsUrsl() } else { httpUrls = networkConfig.HTTPURLs wsUrls = networkConfig.URLs } node.SetChainConfig(cfg, wsUrls, httpUrls, networkConfig, b.hasForwarders) + + if b.chainOptionsFn != nil && len(b.chainOptionsFn) > 0 { + for _, fn := range b.chainOptionsFn { + for _, evmCfg := range cfg.EVM { + fn(&evmCfg.Chain) + } + } + } } - err := b.te.StartClCluster(cfg, b.clNodesCount, b.secretsConfig) + err := b.te.StartClCluster(cfg, b.clNodesCount, b.secretsConfig, b.clNodesOpts...) if err != nil { return nil, err } @@ -308,7 +369,7 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { b.defaultNodeCsaKeys = nodeCsaKeys } - if b.hasGeth && b.clNodesCount > 0 && b.ETHFunds != nil { + if b.ethereumNetwork != nil && b.clNodesCount > 0 && b.ETHFunds != nil { b.te.ParallelTransactions(true) defer b.te.ParallelTransactions(false) if err := b.te.FundChainlinkNodes(b.ETHFunds); err != nil { @@ -316,5 +377,20 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { } } + var enDesc string + if b.te.PrivateEthereumConfig != nil { + enDesc = b.te.PrivateEthereumConfig.Describe() + } else { + enDesc = "none" + } + + b.l.Info(). + Str("privateEthereumNetwork", enDesc). + Bool("hasKillgrave", b.hasKillgrave). + Int("clNodesCount", b.clNodesCount). + Strs("customNodeCsaKeys", b.customNodeCsaKeys). + Strs("defaultNodeCsaKeys", b.defaultNodeCsaKeys). + Msg("Building CL cluster test environment..") + return b.te, nil } diff --git a/integration-tests/docker/test_env/test_env_config.go b/integration-tests/docker/test_env/test_env_config.go index 1a0c8d5c86a..0902deb0c2d 100644 --- a/integration-tests/docker/test_env/test_env_config.go +++ b/integration-tests/docker/test_env/test_env_config.go @@ -3,14 +3,16 @@ package test_env import ( "encoding/json" + cte "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" env "github.com/smartcontractkit/chainlink/integration-tests/types/envcommon" ) type TestEnvConfig struct { - Networks []string `json:"networks"` - Geth GethConfig `json:"geth"` - MockAdapter MockAdapterConfig `json:"mock_adapter"` - ClCluster *ClCluster `json:"clCluster"` + Networks []string `json:"networks"` + Geth GethConfig `json:"geth"` + MockAdapter MockAdapterConfig `json:"mock_adapter"` + ClCluster *ClCluster `json:"clCluster"` + EthereumNetwork *cte.EthereumNetwork `json:"private_ethereum_config"` } type MockAdapterConfig struct { diff --git a/integration-tests/go.mod b/integration-tests/go.mod index b90ba784a38..f95557b2bb1 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -1,40 +1,44 @@ module github.com/smartcontractkit/chainlink/integration-tests -go 1.21 +go 1.21.4 // Make sure we're working with the latest chainlink libs replace github.com/smartcontractkit/chainlink/v2 => ../ require ( + cosmossdk.io/errors v1.0.0 github.com/K-Phoen/grabana v0.21.17 github.com/cli/go-gh/v2 v2.0.0 github.com/ethereum/go-ethereum v1.12.0 github.com/go-resty/resty/v2 v2.7.0 - github.com/google/uuid v1.3.1 + github.com/google/go-cmp v0.6.0 + github.com/google/uuid v1.4.0 + github.com/jmoiron/sqlx v1.3.5 github.com/kelseyhightower/envconfig v1.4.0 github.com/lib/pq v1.10.9 github.com/manifoldco/promptui v0.9.0 - github.com/onsi/gomega v1.27.8 + github.com/onsi/gomega v1.30.0 github.com/pelletier/go-toml/v2 v2.1.0 - github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.30.0 + github.com/scylladb/go-reflectx v1.0.1 github.com/segmentio/ksuid v1.0.4 github.com/slack-go/slack v0.12.2 - github.com/smartcontractkit/chainlink-env v0.38.3 - github.com/smartcontractkit/chainlink-testing-framework v1.17.13 + github.com/smartcontractkit/chainlink-automation v1.0.1 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20231201152724-d550085eb3c4 + github.com/smartcontractkit/chainlink-testing-framework v1.20.0 + github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 - github.com/smartcontractkit/libocr v0.0.0-20231020123319-d255366a6545 - github.com/smartcontractkit/ocr2keepers v0.7.27 - github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687 + github.com/smartcontractkit/libocr v0.0.0-20231107151413-13e0202ae8d7 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 - github.com/smartcontractkit/wasp v0.3.0 + github.com/smartcontractkit/wasp v0.3.6 github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.4 github.com/testcontainers/testcontainers-go v0.23.0 github.com/umbracle/ethgo v0.1.3 go.dedis.ch/kyber/v3 v3.1.0 + go.uber.org/ratelimit v0.2.0 go.uber.org/zap v1.26.0 - golang.org/x/sync v0.4.0 + golang.org/x/sync v0.5.0 gopkg.in/guregu/null.v4 v4.0.0 ) @@ -50,13 +54,17 @@ require ( cosmossdk.io/api v0.3.1 // indirect cosmossdk.io/core v0.5.1 // indirect cosmossdk.io/depinject v1.0.0-alpha.3 // indirect - cosmossdk.io/errors v1.0.0 // indirect cosmossdk.io/math v1.0.1 // indirect dario.cat/mergo v1.0.0 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect github.com/CosmWasm/wasmd v0.40.1 // indirect github.com/CosmWasm/wasmvm v1.2.4 // indirect @@ -70,8 +78,8 @@ require ( github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/avast/retry-go/v4 v4.5.0 // indirect - github.com/aws/aws-sdk-go v1.44.302 // indirect + github.com/avast/retry-go/v4 v4.5.1 // indirect + github.com/aws/aws-sdk-go v1.45.25 // indirect github.com/aws/constructs-go/constructs/v10 v10.1.255 // indirect github.com/aws/jsii-runtime-go v1.75.0 // indirect github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect @@ -82,7 +90,8 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect - github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee // indirect + github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b // indirect + github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 // indirect github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2 v2.7.5 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect @@ -107,7 +116,7 @@ require ( github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect github.com/cosmos/cosmos-sdk v0.47.4 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect - github.com/cosmos/gogoproto v1.4.10 // indirect + github.com/cosmos/gogoproto v1.4.11 // indirect github.com/cosmos/iavl v0.20.0 // indirect github.com/cosmos/ibc-go/v7 v7.0.1 // indirect github.com/cosmos/ics23/go v0.9.1-0.20221207100636-b1abd8678aab // indirect @@ -124,7 +133,7 @@ require ( github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v24.0.5+incompatible // indirect + github.com/docker/docker v24.0.7+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -136,12 +145,12 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // 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.15.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fvbommel/sortorder v1.0.2 // indirect - github.com/fxamacker/cbor/v2 v2.4.0 // indirect + github.com/fxamacker/cbor/v2 v2.5.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gagliardetto/binary v0.7.1 // indirect github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27 // indirect @@ -151,11 +160,13 @@ require ( github.com/gin-contrib/sessions v0.0.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect + github.com/go-ldap/ldap/v3 v3.4.5 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/analysis v0.21.4 // indirect @@ -171,38 +182,37 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/go-webauthn/revoke v0.1.9 // indirect - github.com/go-webauthn/webauthn v0.8.2 // indirect + github.com/go-webauthn/webauthn v0.9.1 // indirect + github.com/go-webauthn/x v0.1.4 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.3 // indirect github.com/gogo/status v1.1.1 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/glog v1.1.0 // indirect + github.com/golang-jwt/jwt/v5 v5.1.0 // indirect + github.com/golang/glog v1.1.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/go-tpm v0.3.3 // indirect + github.com/google/go-tpm v0.9.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect + github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/mux v1.8.0 // indirect - github.com/gorilla/securecookie v1.1.1 // indirect - github.com/gorilla/sessions v1.2.1 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.2.2 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/gosimple/slug v1.13.1 // indirect github.com/gosimple/unidecode v1.0.1 // indirect - github.com/grafana/dskit v0.0.0-20230201083518-528d8a7d52f2 // indirect - github.com/grafana/loki v1.6.2-0.20230403212622-90888a0cc737 // indirect - github.com/grafana/loki/pkg/push v0.0.0-20230127102416-571f88bc5765 // indirect + github.com/grafana/dskit v0.0.0-20231120170505-765e343eda4f // indirect + github.com/grafana/loki v1.6.2-0.20231201111602-11ef833ed3e4 // indirect + github.com/grafana/loki/pkg/push v0.0.0-20231201111602-11ef833ed3e4 // indirect github.com/grafana/pyroscope-go v1.0.4 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.4 // indirect github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect @@ -215,7 +225,7 @@ require ( github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect - github.com/hashicorp/consul/api v1.22.0 // indirect + github.com/hashicorp/consul/api v1.25.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect @@ -256,16 +266,16 @@ require ( github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect - github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect - github.com/klauspost/compress v1.17.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/koron/go-ssdp v0.0.2 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/leanovate/gopter v0.2.10-0.20210127095200-9abe2343507a // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/libp2p/go-addr-util v0.0.2 // indirect @@ -311,13 +321,13 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/miekg/dns v1.1.55 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/miekg/dns v1.1.56 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect @@ -357,43 +367,45 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/otiai10/copy v1.14.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/alertmanager v0.25.1 // indirect + github.com/prometheus/alertmanager v0.26.0 // indirect github.com/prometheus/client_golang v1.17.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect - github.com/prometheus/exporter-toolkit v0.10.0 // indirect + github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/prometheus/prometheus v0.46.0 // indirect + github.com/prometheus/prometheus v0.48.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/russross/blackfriday v1.6.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect - github.com/scylladb/go-reflectx v1.0.1 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect - github.com/sercand/kuberesolver v2.4.0+incompatible // indirect + github.com/sercand/kuberesolver/v5 v5.1.1 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/shirou/gopsutil/v3 v3.23.9 // indirect + github.com/shirou/gopsutil/v3 v3.23.10 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 // indirect - github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20230913032705-f924d753cc47 // indirect - github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231020230319-2ede955d1dc9 // indirect - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231023133638-72f4e799ab05 // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231024133459-1ef3a11319eb // indirect - github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb // indirect + github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231117191236-12eab01a4542 // indirect + github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d // indirect + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231129183458-faee879168b3 // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231117204155-b253a2f56664 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/wsrpc v0.7.2 // indirect + github.com/soheilhy/cmux v0.1.5 // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -407,7 +419,7 @@ require ( github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 // indirect github.com/theodesp/go-heaps v0.0.0-20190520121037-88e35354fe0a // indirect github.com/tidwall/btree v1.6.0 // indirect - github.com/tidwall/gjson v1.16.0 // indirect + github.com/tidwall/gjson v1.17.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect @@ -419,13 +431,10 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 // indirect github.com/valyala/fastjson v1.4.1 // indirect - github.com/weaveworks/common v0.0.0-20221201103051-7c2720a9024d // indirect - github.com/weaveworks/promrus v1.2.0 // indirect 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/xlab/treeprint v1.1.0 // indirect - github.com/yuin/goldmark v1.4.13 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/zondax/hid v0.9.1 // indirect github.com/zondax/ledger-go v0.14.1 // indirect @@ -436,39 +445,39 @@ require ( go.etcd.io/etcd/client/v3 v3.5.7 // indirect go.mongodb.org/mongo-driver v1.12.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/sdk v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.opentelemetry.io/collector/pdata v1.0.0-rcv0016 // indirect + go.opentelemetry.io/collector/semconv v0.87.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect + go.opentelemetry.io/otel v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect + go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/sdk v1.21.0 // indirect + go.opentelemetry.io/otel/trace v1.21.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.starlark.net v0.0.0-20220817180228-f738f5508c12 // indirect go.uber.org/atomic v1.11.0 // indirect - go.uber.org/goleak v1.2.1 // indirect + go.uber.org/goleak v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/ratelimit v0.2.0 // indirect - golang.org/x/arch v0.4.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/mod v0.13.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/arch v0.6.0 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.16.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - gonum.org/v1/gonum v0.13.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 // indirect - google.golang.org/grpc v1.58.3 // indirect + gonum.org/v1/gonum v0.14.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/guregu/null.v2 v2.1.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -477,14 +486,14 @@ require ( 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.1 // indirect - k8s.io/api v0.27.3 // indirect + k8s.io/api v0.28.2 // indirect k8s.io/apiextensions-apiserver v0.25.3 // indirect - k8s.io/apimachinery v0.27.3 // indirect + k8s.io/apimachinery v0.28.2 // indirect k8s.io/cli-runtime v0.25.11 // indirect - k8s.io/client-go v0.27.3 // indirect + k8s.io/client-go v0.28.2 // indirect k8s.io/component-base v0.26.2 // indirect k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515 // indirect + k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect k8s.io/kubectl v0.25.11 // indirect k8s.io/utils v0.0.0-20230711102312-30195339c3c7 // indirect nhooyr.io/websocket v1.8.7 // indirect @@ -512,7 +521,4 @@ replace ( // until merged upstream: https://github.com/mwitkow/grpc-proxy/pull/69 github.com/mwitkow/grpc-proxy => github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f - - github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.43.1-0.20230327151049-211ae4f1f0a2 - github.com/sercand/kuberesolver v2.4.0+incompatible => github.com/sercand/kuberesolver/v5 v5.1.1 ) diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 8c3dc2c65c2..b275f6b653a 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -18,507 +18,35 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb 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.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -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.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= -cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= -cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= -cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= -cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= -cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= -cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= -cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= -cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= -cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= -cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= -cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= -cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= -cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= -cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= -cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= -cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= -cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= -cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= -cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= -cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= -cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= -cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= -cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= -cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= -cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= -cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= -cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= -cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= -cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= -cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= -cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= -cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= -cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= -cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= -cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= -cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= -cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= -cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= -cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= -cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= -cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= -cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= -cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= -cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= -cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= -cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= -cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= -cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= -cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= -cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= -cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= -cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= -cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= -cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go v0.110.9 h1:e7ITSqGFFk4rbz/JFIqZh3G4VEHguhAL4BQcFlWtU68= +cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM= 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/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= -cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= -cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= -cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= -cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= -cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= -cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= -cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= -cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= -cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= -cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= -cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= -cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= -cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= -cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= -cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= -cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= -cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= -cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= -cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= -cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= -cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= -cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= -cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= -cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= -cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= -cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= -cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= -cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= -cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= -cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= -cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= -cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= -cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= -cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= -cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= -cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= -cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= -cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= -cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= -cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= -cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= -cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= -cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= -cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= -cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= -cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= -cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= -cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= -cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= -cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= -cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= -cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= -cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= -cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= -cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= -cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= -cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= -cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= 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/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= -cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= -cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= -cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= -cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= -cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= -cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= -cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= -cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= -cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= -cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= -cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= -cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= -cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= -cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= -cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= -cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= -cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= -cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= -cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= -cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= -cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= -cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= -cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= -cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= -cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= -cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= -cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= -cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= -cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= -cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= -cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= -cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= -cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= -cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= -cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= -cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= -cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= -cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= -cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= -cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= -cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= -cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= -cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= -cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= -cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= -cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= -cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= -cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= -cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= -cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= -cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= -cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= -cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= -cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= -cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= -cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= -cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= -cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= -cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= -cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= -cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= -cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= -cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= -cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= -cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= -cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= -cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= -cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= -cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= -cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= -cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= -cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= -cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= -cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= -cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= -cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= -cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= -cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= -cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= -cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= -cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= -cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= -cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= -cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= -cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= -cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= -cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= -cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= -cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= -cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= -cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= -cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= -cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= -cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= -cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= -cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= -cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= -cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= -cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= -cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= -cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= -cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= -cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= -cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= -cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= -cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= -cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= -cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= -cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= -cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= -cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= -cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= -cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= -cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= -cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= -cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= -cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= -cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= -cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= -cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= -cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= -cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= -cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= -cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= -cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= -cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= -cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= -cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= -cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= -cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= -cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= -cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= -cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= -cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/iam v1.1.4 h1:K6n/GZHFTtEoKT5aUG3l9diPi0VduZNQ1PfdnpkkIFk= +cloud.google.com/go/iam v1.1.4/go.mod h1:l/rg8l1AaA+VFMho/HYx2Vv6xinPSLMF8qfhRPIZ0L8= 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/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= -cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= -cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= -cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= -cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= -cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= -cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= -cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= -cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= -cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= -cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= -cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= -cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= -cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= -cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= -cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= -cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= -cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= -cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= -cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= -cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= -cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= -cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= -cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= -cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= -cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= -cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= -cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= -cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= -cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= -cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= -cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= -cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= -cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= -cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= -cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= -cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= -cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= -cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= -cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= -cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= -cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= -cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= -cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= -cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= -cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= -cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= -cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= -cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= -cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= -cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= -cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= -cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= -cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= -cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= -cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= -cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= -cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= -cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= -cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= -cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= -cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= -cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= -cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= -cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= -cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= -cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= -cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= -cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= -cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= 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= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= -cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= -cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= -cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= -cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= -cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= -cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= -cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= -cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= -cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= -cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= -cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= -cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= -cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= -cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= -cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= -cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= -cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= -cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= -cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= -cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= -cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= -cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= -cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= -cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= -cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= -cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= -cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= -cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= -cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= -cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= -cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= -cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= -cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= -cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= -cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= -cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= -cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= -cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= -cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= -cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= -cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= -cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= -cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= -cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= -cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= -cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= -cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= -cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= contrib.go.opencensus.io/exporter/stackdriver v0.12.6/go.mod h1:8x999/OcIPy5ivx/wDiV7Gx4D+VUPODf0mWRGRc5kSk= contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= contrib.go.opencensus.io/exporter/stackdriver v0.13.5 h1:TNaexHK16gPUoc7uzELKOU7JULqccn1NDuqUxmxSqfo= @@ -543,38 +71,34 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= -gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI= github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE= 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-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 h1:9kDVnTz3vbfweTqAUmk/a/pH5pWFCHtvRpHYC0G/dcA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0/go.mod h1:3Ug6Qzto9anB6mGlEdgYMDF5zHQ+wwhEaYR4s17PHMw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 h1:UPeCRD+XY7QlaGQte2EVI2iOcWvUYA2XY8w5T/8v0NQ= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1/go.mod h1:oGV6NlB0cvi1ZbYRR2UN44QHxWFyGk+iylgD0qaMXjA= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 h1:QM6sE5k2ZT/vI5BEe0r7mqjsUSnhVBFbOsVkEuaEfiA= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1 h1:bWh0Z2rOEDfB/ywv/l0iHN1JgyazE6kW/aIA89+CEK0= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1/go.mod h1:Bzf34hhAE9NSxailk8xVeLEZbUjOXcC+GnU1mMKdhLw= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -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.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= -github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= -github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc= -github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= -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/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= -github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= -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.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -596,7 +120,6 @@ github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= -github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/K-Phoen/grabana v0.21.17 h1:mO/9DvJWC/qpTF/X5jQDm5eKgCBaCGypP/tEfXAvKfg= github.com/K-Phoen/grabana v0.21.17/go.mod h1:vbASQt9UiQhX4lC3/opLpJMJ8m+hsTUU2FwkQMytHK4= @@ -626,12 +149,7 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd 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/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= -github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= -github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE= github.com/alecthomas/participle/v2 v2.0.0-alpha7 h1:cK4vjj0VSgb3lN1nuKA5F7dw+1s1pWBe5bx7nNCnN+c= github.com/alecthomas/participle/v2 v2.0.0-alpha7/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -641,15 +159,14 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= -github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= 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= @@ -662,14 +179,13 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/avast/retry-go/v4 v4.5.0 h1:QoRAZZ90cj5oni2Lsgl2GW8mNTnUCnmpx/iKpwVisHg= -github.com/avast/retry-go/v4 v4.5.0/go.mod h1:7hLEXp0oku2Nir2xBAsg0PTphp9z71bN5Aq1fboC3+I= +github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o= +github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc= 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.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.302 h1:ST3ko6GrJKn3Xi+nAvxjG3uk/V1pW8KC52WLeIxqqNk= -github.com/aws/aws-sdk-go v1.44.302/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= +github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/constructs-go/constructs/v10 v10.1.255 h1:5hARfEmhBqHSTQf/C3QLA3sWOxO2Dfja0iA1W7ZcI7g= github.com/aws/constructs-go/constructs/v10 v10.1.255/go.mod h1:DCdBSjN04Ck2pajCacTD4RKFqSA7Utya8d62XreYctI= github.com/aws/jsii-runtime-go v1.75.0 h1:NhpUfyiL7/wsRuUekFsz8FFBCYLfPD/l61kKg9kL/a4= @@ -690,8 +206,6 @@ github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsy github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= @@ -715,8 +229,10 @@ github.com/bxcodec/faker v2.0.1+incompatible/go.mod h1:BNzfpVdTwnFJ6GtfYTcQu6l6r github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee h1:BnPxIde0gjtTnc9Er7cxvBk8DHLWhEux0SxayC8dP6I= -github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b h1:6+ZFm0flnudZzdSE0JxlhR2hKnGPcNB35BjQf4RYQDY= +github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 h1:SjZ2GvvOononHOpK84APFuMvxqsk3tEIaKH/z4Rpu3g= +github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8/go.mod h1:uEyr4WpAH4hio6LFriaPkL938XnrvLpNPmQHBdrmbIE= github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2 v2.7.5 h1:rvc39Ol6z3MvaBzXkxFC6Nfsnixq/dRypushKDd7Nc0= github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2 v2.7.5/go.mod h1:R/pdNYDYFQk+tuuOo7QES1kkv6OLmp5ze2XBZQIVffM= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -724,8 +240,6 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= 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 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -762,16 +276,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk 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/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/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-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= @@ -791,7 +296,6 @@ github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoG github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -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/coinbase/rosetta-sdk-go/types v1.0.0 h1:jpVIwLcPoOeCR6o1tU+Xv7r5bMONNbHU7MuEHboiFuA= github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c= @@ -814,7 +318,6 @@ github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmf github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -829,8 +332,8 @@ github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= -github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI= -github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= +github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= +github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= github.com/cosmos/iavl v0.20.0 h1:fTVznVlepH0KK8NyKq8w+U7c2L6jofa27aFX6YGlm38= github.com/cosmos/iavl v0.20.0/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= github.com/cosmos/ibc-go/v7 v7.0.1 h1:NIBNRWjlOoFvFQu1ZlgwkaSeHO5avf4C1YQiWegt8jw= @@ -858,8 +361,6 @@ github.com/cucumber/common/gherkin/go/v22 v22.0.0 h1:4K8NqptbvdOrjL9DEea6HFjSpbd github.com/cucumber/common/gherkin/go/v22 v22.0.0/go.mod h1:3mJT10B2GGn3MvVPd3FwR7m2u4tLhSRhWUqJU4KN4Fg= github.com/cucumber/common/messages/go/v17 v17.1.1 h1:RNqopvIFyLWnKv0LfATh34SWBhXeoFTJnSrgm9cT/Ts= github.com/cucumber/common/messages/go/v17 v17.1.1/go.mod h1:bpGxb57tDE385Rb2EohgUadLkAbhoC4IyCFi89u/JQI= -github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= @@ -902,12 +403,14 @@ 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/digitalocean/godo v1.97.0 h1:p9w1yCcWMZcxFSLPToNGXA96WfUVLXqoHti6GzVomL4= -github.com/digitalocean/godo v1.97.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA= +github.com/digitalocean/godo v1.104.1 h1:SZNxjAsskM/su0YW9P8Wx3gU0W1Z13b6tZlYNpl5BnA= +github.com/digitalocean/godo v1.104.1/go.mod h1:VAI/L5YDzMuPRU01lEEUSQ/sp5Z//1HnnFv/RBTEdbg= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= -github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 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-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -930,16 +433,9 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y 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.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= -github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= -github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/esote/minmaxheap v1.0.0 h1:rgA7StnXXpZG6qlM0S7pUmEv1KpWe32rYT4x8J8ntaA= @@ -966,10 +462,9 @@ github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwo 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.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= @@ -977,8 +472,6 @@ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+ github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 h1:u/UEqS66A5ckRmS4yNpjmVH56sVtS/RfclBAYocb4as= 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/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= @@ -990,8 +483,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo= github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= -github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= -github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gagliardetto/binary v0.6.1/go.mod h1:aOfYkc20U0deHaHn/LVZXiqlkDbFAX0FpTlDhsXa0S0= @@ -1027,15 +520,12 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= +github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/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= -github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= 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= @@ -1045,8 +535,8 @@ github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= -github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= +github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= 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.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= @@ -1054,8 +544,8 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= @@ -1100,8 +590,6 @@ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogB github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= -github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -1126,10 +614,10 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/go-webauthn/revoke v0.1.9 h1:gSJ1ckA9VaKA2GN4Ukp+kiGTk1/EXtaDb1YE8RknbS0= -github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w= -github.com/go-webauthn/webauthn v0.8.2 h1:8KLIbpldjz9KVGHfqEgJNbkhd7bbRXhNw4QWFJE15oA= -github.com/go-webauthn/webauthn v0.8.2/go.mod h1:d+ezx/jMCNDiqSMzOchuynKb9CVU1NM9BumOnokfcVQ= +github.com/go-webauthn/webauthn v0.9.1 h1:KuZjvUX9JTuFjB2n7kZhM6n76BClLUFbFM8SLKnrXpo= +github.com/go-webauthn/webauthn v0.9.1/go.mod h1:m315kRGbUljOytw8b9FGWG9QzErjI5v02pNFCF3lwpI= +github.com/go-webauthn/x v0.1.4 h1:sGmIFhcY70l6k7JIDfnjVBiAAFEssga5lXIUXe0GtAs= +github.com/go-webauthn/x v0.1.4/go.mod h1:75Ug0oK6KYpANh5hDOanfDI+dvPWHk788naJVG/37H8= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= @@ -1156,13 +644,15 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -1171,24 +661,23 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= -github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= -github.com/gogo/status v1.0.3/go.mod h1:SavQ51ycCLnc7dGyJxp8YAmudx8xqiVrRf+6IXRsugc= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 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/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= 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= @@ -1202,7 +691,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt 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 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -1236,9 +724,10 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 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= @@ -1252,18 +741,14 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI= -github.com/google/go-tpm v0.3.0/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw= -github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo= -github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51BeghL4= -github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0= -github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4= +github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -1275,8 +760,6 @@ github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo 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/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -1289,75 +772,54 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf 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-20201218002935-b9804c9f04c2/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/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= 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.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.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= 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/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gophercloud/gophercloud v1.2.0 h1:1oXyj4g54KBg/kFtCdMM6jtxSzeIyg8wv4z1HoGPp1E= -github.com/gophercloud/gophercloud v1.2.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= +github.com/gophercloud/gophercloud v1.7.0 h1:fyJGKh0LBvIZKLvBWvQdIgkaV5yTM3Jh9EYUh+UNCAs= +github.com/gophercloud/gophercloud v1.7.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 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.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/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q= github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= -github.com/grafana/dskit v0.0.0-20230201083518-528d8a7d52f2 h1:IOks+FXJ6iO/pfbaVEf4efNw+YzYBYNCkCabyrbkFTM= -github.com/grafana/dskit v0.0.0-20230201083518-528d8a7d52f2/go.mod h1:zj+5BNZAVmQafV583uLTAOzRr963KPdEm4d6NPmtbwg= -github.com/grafana/loki v1.6.2-0.20230403212622-90888a0cc737 h1:o45+fZAYRtTjx+9fFml9LZxsCmLX65l39eWDgvdkIr0= -github.com/grafana/loki v1.6.2-0.20230403212622-90888a0cc737/go.mod h1:kxNnWCr4EMobhndjy7a2Qpm7jkLPnJW2ariYvY77hLE= -github.com/grafana/loki/pkg/push v0.0.0-20230127102416-571f88bc5765 h1:VXitROTlmZtLzvokNe8ZbUKpmwldM4Hy1zdNRO32jKU= -github.com/grafana/loki/pkg/push v0.0.0-20230127102416-571f88bc5765/go.mod h1:DhJMrd2QInI/1CNtTN43BZuTmkccdizW1jZ+F6aHkhY= +github.com/grafana/dskit v0.0.0-20231120170505-765e343eda4f h1:gyojr97YeWZ70pKNakWv5/tKwBHuLy3icnIeCo9gQr4= +github.com/grafana/dskit v0.0.0-20231120170505-765e343eda4f/go.mod h1:8dsy5tQOkeNQyjXpm5mQsbCu3H5uzeBD35MzRQFznKU= +github.com/grafana/loki v1.6.2-0.20231201111602-11ef833ed3e4 h1:YbfISHhpbiDvoFNxDPCicPOU/+9s4rdv9Fq/V3iRvTo= +github.com/grafana/loki v1.6.2-0.20231201111602-11ef833ed3e4/go.mod h1:Tx2uhmS+H0pGmtqX94/KaDkRHWhIowt4iYtJLctAbEY= +github.com/grafana/loki/pkg/push v0.0.0-20231201111602-11ef833ed3e4 h1:wQ0FnSeebhJIBkgYOD06Mxk9HV2KhtEG0hp/7R+5RUQ= +github.com/grafana/loki/pkg/push v0.0.0-20231201111602-11ef833ed3e4/go.mod h1:f3JSoxBTPXX5ec4FxxeC19nTBSxoTz+cBgS3cYLMcr0= github.com/grafana/pyroscope-go v1.0.4 h1:oyQX0BOkL+iARXzHuCdIF5TQ7/sRSel1YFViMHC7Bm0= github.com/grafana/pyroscope-go v1.0.4/go.mod h1:0d7ftwSMBV/Awm7CCiYmHQEG8Y44Ma3YSjt+nWcWztY= github.com/grafana/pyroscope-go/godeltaprof v0.1.4 h1:mDsJ3ngul7UfrHibGQpV66PbZ3q1T8glz/tK3bQKKEk= @@ -1371,7 +833,6 @@ github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLt 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.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0 h1:f4tggROQKKcnh4eItay6z/HbHLqghBxS8g7pyMhmDio= @@ -1382,8 +843,6 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= @@ -1397,13 +856,13 @@ github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIv 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.22.0 h1:ydEvDooB/A0c/xpsBd8GSt7P2/zYPBui4KrNip0xGjE= -github.com/hashicorp/consul/api v1.22.0/go.mod h1:zHpYgZ7TeYqS6zaszjwSt128OwESRpnhU9aGa6ue3Eg= +github.com/hashicorp/consul/api v1.25.1 h1:CqrdhYzc8XZuPnhIYZWH45toM0LB9ZeYr/gvpLVI3PE= +github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= -github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= -github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= +github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= +github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -1428,8 +887,8 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= -github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= +github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -1461,8 +920,8 @@ github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= -github.com/hashicorp/nomad/api v0.0.0-20230308192510-48e7d70fcd4b h1:EkuSTU8c/63q4LMayj8ilgg/4I5PXDFVcnqKfs9qcwI= -github.com/hashicorp/nomad/api v0.0.0-20230308192510-48e7d70fcd4b/go.mod h1:bKUb1ytds5KwUioHdvdq9jmrDqCThv95si0Ub7iNeBg= +github.com/hashicorp/nomad/api v0.0.0-20230721134942-515895c7690c h1:Nc3Mt2BAnq0/VoLEntF/nipX+K1S7pG+RgwiitSv6v0= +github.com/hashicorp/nomad/api v0.0.0-20230721134942-515895c7690c/go.mod h1:O23qLAZuCx4htdY9zBaO4cJPXgleSFEdq6D/sezGgYE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= @@ -1472,8 +931,8 @@ github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7H github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTxs= github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo= -github.com/hetznercloud/hcloud-go v1.41.0 h1:KJGFRRc68QiVu4PrEP5BmCQVveCP2CM26UGQUKGpIUs= -github.com/hetznercloud/hcloud-go v1.41.0/go.mod h1:NaHg47L6C77mngZhwBG652dTAztYrsZ2/iITJKhQkHA= +github.com/hetznercloud/hcloud-go/v2 v2.4.0 h1:MqlAE+w125PLvJRCpAJmEwrIxoVdUdOyuFUhE/Ukbok= +github.com/hetznercloud/hcloud-go/v2 v2.4.0/go.mod h1:l7fA5xsncFBzQTyw29/dw5Yr88yEGKKdc6BHf24ONS0= 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.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= @@ -1488,7 +947,6 @@ github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= -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.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -1500,8 +958,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/ionos-cloud/sdk-go/v6 v6.1.4 h1:BJHhFA8Q1SZC7VOXqKKr2BV2ysQ2/4hlk1e4hZte7GY= -github.com/ionos-cloud/sdk-go/v6 v6.1.4/go.mod h1:Ox3W0iiEz0GHnfY9e5LmAxwklsxguuNFEUSu0gVRTME= +github.com/ionos-cloud/sdk-go/v6 v6.1.9 h1:Iq3VIXzeEbc8EbButuACgfLMiY5TPVWUPNrF+Vsddo4= +github.com/ionos-cloud/sdk-go/v6 v6.1.9/go.mod h1:EzEgRIDxBELvfoa/uBN0kOQaqovLjUWEB7iW4/Q+t4k= 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= @@ -1646,8 +1104,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= @@ -1657,27 +1113,23 @@ github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYb github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= -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= -github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -1921,16 +1373,13 @@ github.com/libp2p/go-yamux/v2 v2.0.0 h1:vSGhAy5u6iHBq11ZDcyHH4Blcf9xlBhT4WQDoOE9 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/linode/linodego v1.14.1 h1:uGxQyy0BidoEpLGdvfi4cPgEW+0YUFsEGrLEhcTfjNc= -github.com/linode/linodego v1.14.1/go.mod h1:NJlzvlNtdMRRkXb0oN6UWzUkj6t+IBsyveHgZ5Ppjyk= +github.com/linode/linodego v1.23.0 h1:s0ReCZtuN9Z1IoUN9w1RLeYO1dMZUGPwOQ/IBFsBHtU= +github.com/linode/linodego v1.23.0/go.mod h1:0U7wj/UQOqBNbKv1FYTXiBUXueR8DY4HvIotwE0ENgg= 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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -1968,19 +1417,18 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 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.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= @@ -1990,15 +1438,13 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju github.com/miekg/dns v1.1.28/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/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= -github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= +github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 h1:QRUSJEgZn2Snx0EmT/QLXibWjSUDjKWvXIT19NBVp94= github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= -github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= -github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= @@ -2006,8 +1452,8 @@ github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+ 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/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 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/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -2149,8 +1595,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= 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= @@ -2159,18 +1605,16 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 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.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk= github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= -github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= -github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -2183,13 +1627,15 @@ github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= -github.com/ovh/go-ovh v1.3.0 h1:mvZaddk4E4kLcXhzb+cxBsMPYp2pHqiQpWYkInsuZPQ= -github.com/ovh/go-ovh v1.3.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= +github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0= +github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= @@ -2201,18 +1647,15 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 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-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/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/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -2221,27 +1664,22 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr 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/pressly/goose/v3 v3.15.1 h1:dKaJ1SdLvS/+HtS8PzFT0KBEtICC1jewLXM+b3emlv8= -github.com/pressly/goose/v3 v3.15.1/go.mod h1:0E3Yg/+EwYzO6Rz2P98MlClFgIcoujbVRs575yi3iIM= -github.com/prometheus/alertmanager v0.25.1 h1:LGBNMspOfv8h7brb+LWj2wnwBCg2ZuuKWTh6CAVw2/Y= -github.com/prometheus/alertmanager v0.25.1/go.mod h1:MEZ3rFVHqKZsw7IcNS/m4AWZeXThmJhumpiWR4eHU/w= +github.com/pressly/goose/v3 v3.16.0 h1:xMJUsZdHLqSnCqESyKSqEfcYVYsUuup1nrOhaEFftQg= +github.com/pressly/goose/v3 v3.16.0/go.mod h1:JwdKVnmCRhnF6XLQs2mHEQtucFD49cQBdRM4UiwkxsM= +github.com/prometheus/alertmanager v0.26.0 h1:uOMJWfIwJguc3NaM3appWNbbrh6G/OjvaHMk22aBBYc= +github.com/prometheus/alertmanager v0.26.0/go.mod h1:rVcnARltVjavgVaNnmevxK7kOn7IZavyf0KNgHkbEpU= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 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.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.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/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.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -2251,29 +1689,22 @@ 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.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= -github.com/prometheus/exporter-toolkit v0.8.2/go.mod h1:00shzmJL7KxcsabLWcONwpyNEuWhREOnFqZW7vadFS0= -github.com/prometheus/exporter-toolkit v0.10.0 h1:yOAzZTi4M22ZzVxD+fhy1URTuNRj/36uQJJ5S8IPza8= -github.com/prometheus/exporter-toolkit v0.10.0/go.mod h1:+sVFzuvV5JDyw+Ih6p3zFxZNVnKQa3x5qPmDSiPu4ZY= +github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97 h1:oHcfzdJnM/SFppy2aUlvomk37GI33x9vgJULihE5Dt8= +github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97/go.mod h1:LoBCZeRh+5hX+fSULNyFnagYlQG/gBsyA/deNzROkq8= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 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.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/prometheus/prometheus v0.43.1-0.20230327151049-211ae4f1f0a2 h1:i5hmbBzR+VeL5pPl1ZncsJ1bpg3SO66bwkE1msJBsMA= -github.com/prometheus/prometheus v0.43.1-0.20230327151049-211ae4f1f0a2/go.mod h1:Mm42Acga98xgA+u5yTaC3ki3i0rJEJWFpbdHN7q2trk= +github.com/prometheus/prometheus v0.48.0 h1:yrBloImGQ7je4h8M10ujGh4R6oxYQJQKlMuETwNskGk= +github.com/prometheus/prometheus v0.48.0/go.mod h1:SRw624aMAxTfryAcP8rOjg4S/sHHaetx2lyJJ2nM83g= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pyroscope-io/client v0.7.1 h1:yFRhj3vbgjBxehvxQmedmUWJQ4CAfCHhn+itPsuWsHw= github.com/pyroscope-io/client v0.7.1/go.mod h1:4h21iOU4pUOq0prKyDlvYRL+SCKsBc5wKiEtV+rJGqU= @@ -2287,7 +1718,6 @@ github.com/regen-network/gocuke v0.6.2 h1:pHviZ0kKAq2U2hN2q3smKNxct6hS0mGByFMHGn github.com/regen-network/gocuke v0.6.2/go.mod h1:zYaqIHZobHyd0xOrHGPQjbhGJsuZ1oElx150u2o1xuk= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -2303,8 +1733,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= -github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= +github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= @@ -2317,15 +1747,13 @@ github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNl 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= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14 h1:yFl3jyaSVLNYXlnNYM5z2pagEk1dYQhfr1p20T1NyKY= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21 h1:yWfiTPwYxB0l5fGMhl/G+liULugVIHD9AU77iNLrURQ= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/scylladb/go-reflectx v1.0.1 h1:b917wZM7189pZdlND9PbIJ6NQxfDPfBvUaQ7cjj1iZQ= github.com/scylladb/go-reflectx v1.0.1/go.mod h1:rWnOfDIRWBGN0miMLIcoPt/Dhi2doCMZqwMCJ3KupFc= @@ -2338,10 +1766,12 @@ github.com/sercand/kuberesolver/v5 v5.1.1/go.mod h1:Fs1KbKhVRnB2aDWN12NjKCB+RgYM github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= +github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= 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.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= -github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= +github.com/shirou/gopsutil/v3 v3.23.10 h1:/N42opWlYzegYaVkWejXWJpbzKv2JDy3mrgGzKsh9hM= +github.com/shirou/gopsutil/v3 v3.23.10/go.mod h1:JIE26kpucQi+innVlAUnIEOSBhBUkirr5b44yr55+WE= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -2360,42 +1790,42 @@ github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 h1:T3lFWumvbfM1u/etVq42Afwq/jtNSBSOA8n5jntnNPo= github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704/go.mod h1:2QuJdEouTWjh5BDy5o/vgGXQtR4Gz8yH1IYB5eT7u4M= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20230913032705-f924d753cc47 h1:vdieOW3CZGdD2R5zvCSMS+0vksyExPN3/Fa1uVfld/A= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20230913032705-f924d753cc47/go.mod h1:xMwqRdj5vqYhCJXgKVqvyAwdcqM6ZAEhnwEQ4Khsop8= -github.com/smartcontractkit/chainlink-env v0.38.3 h1:ZtOnwkG622R0VCTxL5V09AnT/QXhlFwkGTjd0Lsfpfg= -github.com/smartcontractkit/chainlink-env v0.38.3/go.mod h1:7z4sw/hN8TxioQCLwFqQdhK3vaOV0a22Qe99z4bRUcg= -github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231020230319-2ede955d1dc9 h1:fFD5SgSJtnXvkGLK3CExNKpUIz4sGrNNkKv3Ljw63Hk= -github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231020230319-2ede955d1dc9/go.mod h1:M9U1JV7IQi8Sfj4JR1qSi1tIh6omgW78W/8SHN/8BUQ= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231023133638-72f4e799ab05 h1:DaPSVnxe7oz1QJ+AVIhQWs1W3ubQvwvGo9NbHpMs1OQ= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231023133638-72f4e799ab05/go.mod h1:o0Pn1pbaUluboaK6/yhf8xf7TiFCkyFl6WUOdwqamuU= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231024133459-1ef3a11319eb h1:HiluOfEVGOQTM6BTDImOqYdMZZ7qq7fkZ3TJdmItNr8= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231024133459-1ef3a11319eb/go.mod h1:/30flFG4L/iCYAFeA3DUzR0xuHSxAMONiWTzyzvsNwo= -github.com/smartcontractkit/chainlink-testing-framework v1.17.13 h1:C8E+P3/ElUxmsIHE0fOd1hDoSc3AbbqcePmN9sNKYdM= -github.com/smartcontractkit/chainlink-testing-framework v1.17.13/go.mod h1:RWlmjwnjIGbQAnRfKwe02Ife82nNI3rZmdI0zgkfbyk= +github.com/smartcontractkit/chainlink-automation v1.0.1 h1:vVjBFq2Zsz21kPy1Pb0wpjF9zrbJX+zjXphDeeR4XZk= +github.com/smartcontractkit/chainlink-automation v1.0.1/go.mod h1:INSchkV3ntyDdlZKGWA030MPDpp6pbeuiRkRKYFCm2k= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20231201152724-d550085eb3c4 h1:qau0/AHvPwMR3p6gWsFWC4qVfEtSEALtBetTOpHA2IU= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20231201152724-d550085eb3c4/go.mod h1:Hrru9i7n+WEYyW2aIt3/YGPhxLX+HEGWnhk3yVXeDF8= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231117191236-12eab01a4542 h1:oewYJtdRkJKUHCNDCj5C2LQe6Oq6qy975g931nfG0cc= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231117191236-12eab01a4542/go.mod h1:EpvRoycRD+kniYlz+pCpRT5e+fmPm0mSD/vmND+0oMg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d h1:w4MsbOtNk6nD/mcXLstHWk9hB6g7QLtcAfhPjhwvOaQ= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d/go.mod h1:YPAfLNowdBwiKiYOwgwtbJHi8AJWbcxkbOY0ItAvkfc= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231129183458-faee879168b3 h1:DudPr8ZNMEVgDwHBvnrpvK96JrGcGCG+LLBnlqaPGnE= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231129183458-faee879168b3/go.mod h1:UfW7/PZKon+iDEHtrHOfvMnS5GfYOW/SdMZ6P97rPEk= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231117204155-b253a2f56664 h1:yxaHuDTtj2xxtsR8b+LJw8xDvyr6v/F6GP3InsP4wPI= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231117204155-b253a2f56664/go.mod h1:3Fa+HQTZ3R3fPC0hUqugvoo+NEeo8Y4J2WOnQfi7O34= +github.com/smartcontractkit/chainlink-testing-framework v1.20.0 h1:gQPQRKJuMh6QTAIMkqZ7v5WkjEmbcoMIX/V6WPVrvuI= +github.com/smartcontractkit/chainlink-testing-framework v1.20.0/go.mod h1:+FVgkz6phTc+piVT57AcQfr3I8xvZgZ1lOpRPOu/dLQ= +github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 h1:FFdvEzlYwcuVHkdZ8YnZR/XomeMGbz5E2F2HZI3I3w8= +github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868/go.mod h1:Kn1Hape05UzFZ7bOUnm3GVsHzP0TNrVmpfXYNHdqGGs= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 h1:ko88+ZznniNJZbZPWAvHQU8SwKAdHngdDZ+pvVgB5ss= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJif132UCdjo8u43i7iPN1/MFnu49hv7lFGFftCHKU= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f/go.mod h1:MvMXoufZAtqExNexqi4cjrNYE9MefKddKylxjS+//n0= -github.com/smartcontractkit/libocr v0.0.0-20231020123319-d255366a6545 h1:qOsw2ETQD/Sb/W2xuYn2KPWjvvsWA0C+l19rWFq8iNg= -github.com/smartcontractkit/libocr v0.0.0-20231020123319-d255366a6545/go.mod h1:2lyRkw/qLQgUWlrWWmq5nj0y90rWeO6Y+v+fCakRgb0= -github.com/smartcontractkit/ocr2keepers v0.7.27 h1:kwqMrzmEdq6gH4yqNuLQCbdlED0KaIjwZzu3FF+Gves= -github.com/smartcontractkit/ocr2keepers v0.7.27/go.mod h1:1QGzJURnoWpysguPowOe2bshV0hNp1YX10HHlhDEsas= -github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687 h1:NwC3SOc25noBTe1KUQjt45fyTIuInhoE2UfgcHAdihM= -github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687/go.mod h1:YYZq52t4wcHoMQeITksYsorD+tZcOyuVU5+lvot3VFM= -github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb h1:OMaBUb4X9IFPLbGbCHsMU+kw/BPCrewaVwWGIBc0I4A= -github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb/go.mod h1:HNUu4cJekUdsJbwRBCiOybtkPJEfGRELQPe2tkoDEyk= +github.com/smartcontractkit/libocr v0.0.0-20231107151413-13e0202ae8d7 h1:21V61XOYSxpFmFqlhr5IaEh1uQ1F6CewJ30D/U/P34c= +github.com/smartcontractkit/libocr v0.0.0-20231107151413-13e0202ae8d7/go.mod h1:2lyRkw/qLQgUWlrWWmq5nj0y90rWeO6Y+v+fCakRgb0= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 h1:yiKnypAqP8l0OX0P3klzZ7SCcBUxy5KqTAKZmQOvSQE= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:q6f4fe39oZPdsh1i57WznEZgxd8siidMaSFq3wdPmVg= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 h1:Dai1bn+Q5cpeGMQwRdjOdVjG8mmFFROVkSKuUgBErRQ= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:G5Sd/yzHWf26rQ+X0nG9E0buKPqRGPMJAfk2gwCzOOw= -github.com/smartcontractkit/wasp v0.3.0 h1:mueeLvpb6HyGNwILxCOKShDR6q18plQn7Gb1j3G/Qkk= -github.com/smartcontractkit/wasp v0.3.0/go.mod h1:skquNdMbKxIrHi5O8Kyukf66AaaXuEpEEaSTxfHbhak= +github.com/smartcontractkit/wasp v0.3.6 h1:1TLWfrTzqZwNvyyoKzPZ8FLQat2lNz640eM+mMh2YxM= +github.com/smartcontractkit/wasp v0.3.6/go.mod h1:L/cyUGfpaWxy/2twOVJLRt2mySJEIqGrFj9nyvRLpSo= github.com/smartcontractkit/wsrpc v0.7.2 h1:iBXzMeg7vc5YoezIQBq896y25BARw7OKbhrb6vPbtRQ= github.com/smartcontractkit/wsrpc v0.7.2/go.mod h1:sj7QX2NQibhkhxTfs3KOhAj/5xwgqMipTvJVSssT9i0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 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 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= @@ -2403,17 +1833,13 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 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.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= 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 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.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= @@ -2424,7 +1850,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn 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.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= @@ -2476,8 +1901,8 @@ github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e/go.mod h1: github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg= -github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -2493,10 +1918,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/uber/jaeger-client-go v2.28.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -2529,10 +1952,6 @@ github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+ github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= -github.com/weaveworks/common v0.0.0-20221201103051-7c2720a9024d h1:9Z/HiqeGN+LOnmotAMpFEQjuXZ4AGAVFG0rC1laP5Go= -github.com/weaveworks/common v0.0.0-20221201103051-7c2720a9024d/go.mod h1:Fnq3+U51tMkPRMC6Wr7zKGUeFFYX4YjNrNK50iU0fcE= -github.com/weaveworks/promrus v1.2.0 h1:jOLf6pe6/vss4qGHjXmGz4oDJQA+AOCqEL3FvvZGz7M= -github.com/weaveworks/promrus v1.2.0/go.mod h1:SaE82+OJ91yqjrE1rsvBWVzNZKcHYFtMUyS1+Ogs/KA= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= 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= @@ -2554,7 +1973,6 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= @@ -2571,13 +1989,9 @@ 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.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= @@ -2614,28 +2028,29 @@ 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/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 h1:RsQi0qJ2imFfCvZabqzM9cNXBG8k6gXMv1A0cXRmH6A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0/go.mod h1:XiYsayHc36K3EByOO6nbAXnAWbrUxdjUROCEeeROOH8= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0/go.mod h1:G17FHPDLt74bCI7tJ4CMitEk4BXTYG4FW6XUpkPBXa4= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/collector/pdata v1.0.0-rcv0016 h1:qCPXSQCoD3qeWFb1RuIks8fw9Atxpk78bmtVdi15KhE= +go.opentelemetry.io/collector/pdata v1.0.0-rcv0016/go.mod h1:OdN0alYOlYhHXu6BDlGehrZWgtBuiDsz/rlNeJeXiNg= +go.opentelemetry.io/collector/semconv v0.87.0 h1:BsG1jdLLRCBRlvUujk4QA86af7r/ZXnizczQpEs/gg8= +go.opentelemetry.io/collector/semconv v0.87.0/go.mod h1:j/8THcqVxFna1FpvA2zYIsUperEtOaRaqoLYIN4doWw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.starlark.net v0.0.0-20220817180228-f738f5508c12 h1:xOBJXWGEDwU5xSDxH6macxO11Us0AH2fTa9rmsbbF7g= @@ -2643,15 +2058,14 @@ go.starlark.net v0.0.0-20220817180228-f738f5508c12/go.mod h1:VZcBMdr3cT3PnBoWunT 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.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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= @@ -2672,8 +2086,8 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= -golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= +golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -2711,42 +2125,26 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -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/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= 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-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= +golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 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/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 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= @@ -2758,7 +2156,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu 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 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= 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= @@ -2771,14 +2168,10 @@ golang.org/x/mod v0.3.0/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/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -2796,7 +2189,6 @@ golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn 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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -2825,9 +2217,9 @@ golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81R 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-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-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -2836,36 +2228,20 @@ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 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-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-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-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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= @@ -2875,28 +2251,10 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ 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-20210413134643-5e61552d6c78/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-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= 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= @@ -2909,13 +2267,10 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ 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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -2983,11 +2338,8 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w 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-20210225134936-a50acf3fe073/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-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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2999,67 +2351,43 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w 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-20210616045830-e2b7044e8c71/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-20210629170331-7dc0b73dc9fb/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-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/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-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-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-20211210111614-af8b64212486/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 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= @@ -3071,26 +2399,20 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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= 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= @@ -3112,7 +2434,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn 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= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/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= @@ -3150,7 +2471,6 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc 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-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/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= @@ -3158,41 +2478,25 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/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.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 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= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= -gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= -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= -gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= -gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= +gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= 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= @@ -3213,44 +2517,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 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.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.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= -google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.147.0 h1:Can3FaQo9LlVqxJCodNmeZW/ib3/qKAY3rFeXiHo5gc= +google.golang.org/api v0.147.0/go.mod h1:pQ/9j83DcmPd/5C9e2nFOdjjNkDZ1G+zkbK2uvdkJMs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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= @@ -3258,8 +2526,9 @@ 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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 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= @@ -3302,107 +2571,19 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D 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-20210108203827-ffc7fda8c3d7/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-20210226172003-ab064af71705/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-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -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-20211118181313-81c1377c94b1/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-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= -google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= -google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= -google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753 h1:+VoAg+OKmWaommL56xmZSE2sUK8A7m6SUO7X89F2tbw= -google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753/go.mod h1:iqkVr8IRpZ53gx1dEnWlCUIEwDWqWARWrbzpasaTNYM= -google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753 h1:lCbbUxUDD+DiXx9Q6F/ttL0aAu7N2pz8XnmMm8ZW4NE= -google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 h1:XUODHrpzJEUeWmVo/jfNTLj0YyVveOo28oE6vkFbkO4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U= +google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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= @@ -3424,29 +2605,10 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG 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.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc/examples v0.0.0-20210424002626-9572fd6faeae/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= 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= @@ -3461,9 +2623,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba 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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -3531,8 +2690,8 @@ k8s.io/api v0.25.11 h1:4mjYDfE3yp22jrytjH0knwgzjXKkxHX4D01ZCAazvZM= k8s.io/api v0.25.11/go.mod h1:bK4UvD4bthtutNlvensrfBX21PRQ/vs2cIYggHkOOAo= k8s.io/apiextensions-apiserver v0.25.3 h1:bfI4KS31w2f9WM1KLGwnwuVlW3RSRPuIsfNF/3HzR0k= k8s.io/apiextensions-apiserver v0.25.3/go.mod h1:ZJqwpCkxIx9itilmZek7JgfUAM0dnTsA48I4krPqRmo= -k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= -k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= +k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= k8s.io/cli-runtime v0.25.11 h1:GE2yNZm1tN+MJtw1SGMOLesLF7Kp7NVAVqRSTbXfu4o= k8s.io/cli-runtime v0.25.11/go.mod h1:r/nEINuHVEpgGhcd2WamU7hD1t/lMnSz8XM44Autltc= k8s.io/client-go v0.25.11 h1:DJQ141UsbNRI6wYSlcYLP5J5BW5Wq7Bgm42Ztq2SW70= @@ -3547,40 +2706,6 @@ k8s.io/kubectl v0.25.11 h1:6bsft5Gan6BCvQ7cJbDRFjTm4Zfq8GuUYpsWAdVngYE= k8s.io/kubectl v0.25.11/go.mod h1:8mIfgkFgT+yJ8/TlmPW1qoRh46H2si9q5nW8id7i9iM= k8s.io/utils v0.0.0-20230711102312-30195339c3c7 h1:ZgnF1KZsYxWIifwSNZFZgNtWE89WI5yiP5WwlfDoIyc= k8s.io/utils v0.0.0-20230711102312-30195339c3c7/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= -modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= -modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA= diff --git a/integration-tests/k8s/connect.go b/integration-tests/k8s/connect.go new file mode 100644 index 00000000000..34eafc42e24 --- /dev/null +++ b/integration-tests/k8s/connect.go @@ -0,0 +1,103 @@ +package k8s + +import ( + "fmt" + "os" + "time" + + "github.com/pelletier/go-toml/v2" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + client2 "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink/integration-tests/client" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" +) + +const ( + DefaultConfigFilePath = "../../../charts/chainlink-cluster/connect.toml" + ErrReadConnectionConfig = "failed to read TOML environment connection config" + ErrUnmarshalConnectionConfig = "failed to unmarshal TOML environment connection config" +) + +type ConnectionVars struct { + Namespace string `toml:"namespace"` + NetworkName string `toml:"network_name"` + NetworkChainID int64 `toml:"network_chain_id"` + NetworkPrivateKey string `toml:"network_private_key"` + NetworkWSURL string `toml:"network_ws_url"` + NetworkHTTPURL string `toml:"network_http_url"` + CLNodesNum int `toml:"cl_nodes_num"` + CLNodeURLTemplate string `toml:"cl_node_url_template"` + CLNodeInternalDNSRecordTemplate string `toml:"cl_node_internal_dns_record_template"` + CLNodeUser string `toml:"cl_node_user"` + CLNodePassword string `toml:"cl_node_password"` + MockServerURL string `toml:"mockserver_url"` +} + +// ConnectRemote connects to a local environment, see charts/chainlink-cluster +func ConnectRemote(l zerolog.Logger) (blockchain.EVMClient, *client2.MockserverClient, contracts.ContractDeployer, *client.ChainlinkK8sClient, []*client.ChainlinkK8sClient, error) { + cfg, err := ReadConfig() + if err != nil { + return nil, nil, nil, nil, nil, err + } + net := &blockchain.EVMNetwork{ + Name: cfg.NetworkName, + Simulated: true, + SupportsEIP1559: true, + ClientImplementation: blockchain.EthereumClientImplementation, + ChainID: 1337, + PrivateKeys: []string{ + cfg.NetworkPrivateKey, + }, + URLs: []string{cfg.NetworkWSURL}, + HTTPURLs: []string{cfg.NetworkHTTPURL}, + ChainlinkTransactionLimit: 500000, + Timeout: blockchain.JSONStrDuration{Duration: 2 * time.Minute}, + MinimumConfirmations: 1, + GasEstimationBuffer: 10000, + } + cc, err := blockchain.NewEVMClientFromNetwork(*net, l) + if err != nil { + return nil, nil, nil, nil, nil, err + } + cd, err := contracts.NewContractDeployer(cc, l) + if err != nil { + return nil, nil, nil, nil, nil, err + } + clClients := make([]*client.ChainlinkK8sClient, 0) + for i := 1; i <= cfg.CLNodesNum; i++ { + c, err := client.NewChainlinkK8sClient(&client.ChainlinkConfig{ + URL: fmt.Sprintf(cfg.CLNodeURLTemplate, i), + Email: cfg.CLNodeUser, + InternalIP: fmt.Sprintf(cfg.CLNodeInternalDNSRecordTemplate, i), + Password: cfg.CLNodePassword, + }, fmt.Sprintf(cfg.CLNodeInternalDNSRecordTemplate, i), cfg.Namespace) + if err != nil { + return nil, nil, nil, nil, nil, err + } + clClients = append(clClients, c) + } + msClient := client2.NewMockserverClient(&client2.MockserverConfig{ + LocalURL: cfg.MockServerURL, + ClusterURL: cfg.MockServerURL, + }) + return cc, msClient, cd, clClients[0], clClients[1:], nil +} + +func ReadConfig() (*ConnectionVars, error) { + var cfg *ConnectionVars + var d []byte + var err error + d, err = os.ReadFile(DefaultConfigFilePath) + if err != nil { + return nil, fmt.Errorf("%s, err: %w", ErrReadConnectionConfig, err) + } + err = toml.Unmarshal(d, &cfg) + if err != nil { + return nil, fmt.Errorf("%s, err: %w", ErrUnmarshalConnectionConfig, err) + } + log.Info().Interface("Config", cfg).Msg("Connecting to environment from config") + return cfg, nil +} diff --git a/integration-tests/load/automationv2_1/automationv2_1_test.go b/integration-tests/load/automationv2_1/automationv2_1_test.go new file mode 100644 index 00000000000..5fde9befba5 --- /dev/null +++ b/integration-tests/load/automationv2_1/automationv2_1_test.go @@ -0,0 +1,573 @@ +package automationv2_1 + +import ( + "context" + "fmt" + "math" + "math/big" + "os" + "strconv" + "strings" + "testing" + "time" + + geth "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/slack-go/slack" + "github.com/stretchr/testify/require" + + ocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" + "github.com/smartcontractkit/wasp" + + ocr2keepers30config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/config" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/networks" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/simple_log_upkeep_counter_wrapper" + + "github.com/smartcontractkit/chainlink/integration-tests/actions" + "github.com/smartcontractkit/chainlink/integration-tests/actions/automationv2" + "github.com/smartcontractkit/chainlink/integration-tests/client" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" + contractseth "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" + "github.com/smartcontractkit/chainlink/integration-tests/testreporters" +) + +const ( + StartupWaitTime = 30 * time.Second + StopWaitTime = 60 * time.Second +) + +var ( + baseTOML = `[Feature] +LogPoller = true + +[OCR2] +Enabled = true + +[P2P] +[P2P.V2] +Enabled = true +AnnounceAddresses = ["0.0.0.0:6690"] +ListenAddresses = ["0.0.0.0:6690"]` + + minimumNodeSpec = map[string]interface{}{ + "resources": map[string]interface{}{ + "requests": map[string]interface{}{ + "cpu": "2000m", + "memory": "4Gi", + }, + "limits": map[string]interface{}{ + "cpu": "2000m", + "memory": "4Gi", + }, + }, + } + + minimumDbSpec = map[string]interface{}{ + "resources": map[string]interface{}{ + "requests": map[string]interface{}{ + "cpu": "1000m", + "memory": "1Gi", + }, + "limits": map[string]interface{}{ + "cpu": "1000m", + "memory": "1Gi", + }, + }, + "stateful": true, + "capacity": "5Gi", + } + + recNodeSpec = map[string]interface{}{ + "resources": map[string]interface{}{ + "requests": map[string]interface{}{ + "cpu": "4000m", + "memory": "8Gi", + }, + "limits": map[string]interface{}{ + "cpu": "4000m", + "memory": "8Gi", + }, + }, + } + + recDbSpec = map[string]interface{}{ + "resources": map[string]interface{}{ + "requests": map[string]interface{}{ + "cpu": "2000m", + "memory": "2Gi", + }, + "limits": map[string]interface{}{ + "cpu": "2000m", + "memory": "2Gi", + }, + }, + "stateful": true, + "capacity": "10Gi", + } +) + +var ( + numberofNodes, _ = strconv.Atoi(getEnv("NUMBEROFNODES", "6")) // Number of nodes in the DON + numberOfUpkeeps, _ = strconv.Atoi(getEnv("NUMBEROFUPKEEPS", "100")) // Number of log triggered upkeeps + duration, _ = strconv.Atoi(getEnv("DURATION", "900")) // Test duration in seconds + blockTime, _ = strconv.Atoi(getEnv("BLOCKTIME", "1")) // Block time in seconds for geth simulated dev network + numberOfEvents, _ = strconv.Atoi(getEnv("NUMBEROFEVENTS", "1")) // Number of events to emit per trigger + specType = getEnv("SPECTYPE", "minimum") // minimum, recommended, local specs for the test + logLevel = getEnv("LOGLEVEL", "info") // log level for the chainlink nodes + pyroscope, _ = strconv.ParseBool(getEnv("PYROSCOPE", "false")) // enable pyroscope for the chainlink nodes +) + +func TestLogTrigger(t *testing.T) { + ctx := tests.Context(t) + l := logging.GetTestLogger(t) + + l.Info().Msg("Starting automation v2.1 log trigger load test") + l.Info().Str("TEST_INPUTS", os.Getenv("TEST_INPUTS")).Int("Number of Nodes", numberofNodes). + Int("Number of Upkeeps", numberOfUpkeeps). + Int("Duration", duration). + Int("Block Time", blockTime). + Int("Number of Events", numberOfEvents). + Str("Spec Type", specType). + Str("Log Level", logLevel). + Str("Image", os.Getenv(config.EnvVarCLImage)). + Str("Tag", os.Getenv(config.EnvVarCLTag)). + Msg("Test Config") + + testConfig := fmt.Sprintf("Number of Nodes: %d\nNumber of Upkeeps: %d\nDuration: %d\nBlock Time: %d\n"+ + "Number of Events: %d\nSpec Type: %s\nLog Level: %s\nImage: %s\nTag: %s\n", numberofNodes, numberOfUpkeeps, duration, + blockTime, numberOfEvents, specType, logLevel, os.Getenv(config.EnvVarCLImage), os.Getenv(config.EnvVarCLTag)) + + testNetwork := networks.MustGetSelectedNetworksFromEnv()[0] + testType := "load" + loadDuration := time.Duration(duration) * time.Second + automationDefaultLinkFunds := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(int64(10000))) //10000 LINK + automationDefaultUpkeepGasLimit := uint32(1_000_000) + + registrySettings := &contracts.KeeperRegistrySettings{ + PaymentPremiumPPB: uint32(0), + FlatFeeMicroLINK: uint32(40_000), + BlockCountPerTurn: big.NewInt(100), + CheckGasLimit: uint32(45_000_000), //45M + StalenessSeconds: big.NewInt(90_000), + GasCeilingMultiplier: uint16(2), + MaxPerformGas: uint32(5_000_000), + MinUpkeepSpend: big.NewInt(0), + FallbackGasPrice: big.NewInt(2e11), + FallbackLinkPrice: big.NewInt(2e18), + MaxCheckDataSize: uint32(5_000), + MaxPerformDataSize: uint32(5_000), + RegistryVersion: contractseth.RegistryVersion_2_1, + } + + testEnvironment := environment.New(&environment.Config{ + TTL: time.Hour * 24, // 1 day, + NamespacePrefix: fmt.Sprintf( + "automation-%s-%s", + testType, + strings.ReplaceAll(strings.ToLower(testNetwork.Name), " ", "-"), + ), + Test: t, + PreventPodEviction: true, + }) + + if testEnvironment.WillUseRemoteRunner() { + key := "TEST_INPUTS" + err := os.Setenv(fmt.Sprintf("TEST_%s", key), os.Getenv(key)) + require.NoError(t, err, "failed to set the environment variable TEST_INPUTS for remote runner") + + key = config.EnvVarPyroscopeServer + err = os.Setenv(fmt.Sprintf("TEST_%s", key), os.Getenv(key)) + require.NoError(t, err, "failed to set the environment variable PYROSCOPE_SERVER for remote runner") + + key = config.EnvVarPyroscopeKey + err = os.Setenv(fmt.Sprintf("TEST_%s", key), os.Getenv(key)) + require.NoError(t, err, "failed to set the environment variable PYROSCOPE_KEY for remote runner") + + key = "GRAFANA_DASHBOARD_URL" + err = os.Setenv(fmt.Sprintf("TEST_%s", key), getEnv(key, "")) + require.NoError(t, err, "failed to set the environment variable GRAFANA_DASHBOARD_URL for remote runner") + } + + testEnvironment. + AddHelm(ethereum.New(ðereum.Props{ + NetworkName: testNetwork.Name, + Simulated: testNetwork.Simulated, + WsURLs: testNetwork.URLs, + Values: map[string]interface{}{ + "resources": map[string]interface{}{ + "requests": map[string]interface{}{ + "cpu": "4000m", + "memory": "4Gi", + }, + "limits": map[string]interface{}{ + "cpu": "8000m", + "memory": "8Gi", + }, + }, + "geth": map[string]interface{}{ + "blocktime": blockTime, + "capacity": "10Gi", + }, + }, + })) + + err := testEnvironment.Run() + require.NoError(t, err, "Error launching test environment") + + if testEnvironment.WillUseRemoteRunner() { + return + } + + var ( + nodeSpec = minimumNodeSpec + dbSpec = minimumDbSpec + ) + + switch specType { + case "recommended": + nodeSpec = recNodeSpec + dbSpec = recDbSpec + case "local": + nodeSpec = map[string]interface{}{} + dbSpec = map[string]interface{}{"stateful": true} + default: + // minimum: + + } + + if !pyroscope { + err = os.Setenv(config.EnvVarPyroscopeServer, "") + require.NoError(t, err, "Error setting pyroscope server env var") + } + + err = os.Setenv(config.EnvVarPyroscopeEnvironment, testEnvironment.Cfg.Namespace) + require.NoError(t, err, "Error setting pyroscope environment env var") + + for i := 0; i < numberofNodes+1; i++ { // +1 for the OCR boot node + var nodeTOML string + if i == 1 || i == 3 { + nodeTOML = fmt.Sprintf("%s\n\n[Log]\nLevel = \"%s\"", baseTOML, logLevel) + } else { + nodeTOML = fmt.Sprintf("%s\n\n[Log]\nLevel = \"info\"", baseTOML) + } + nodeTOML = networks.AddNetworksConfig(nodeTOML, testNetwork) + testEnvironment.AddHelm(chainlink.New(i, map[string]any{ + "toml": nodeTOML, + "chainlink": nodeSpec, + "db": dbSpec, + })) + } + + err = testEnvironment.Run() + require.NoError(t, err, "Error running chainlink DON") + + chainClient, err := blockchain.NewEVMClient(testNetwork, testEnvironment, l) + require.NoError(t, err, "Error building chain client") + + contractDeployer, err := contracts.NewContractDeployer(chainClient, l) + require.NoError(t, err, "Error building contract deployer") + + chainlinkNodes, err := client.ConnectChainlinkNodes(testEnvironment) + require.NoError(t, err, "Error connecting to chainlink nodes") + + chainClient.ParallelTransactions(true) + + a := automationv2.NewAutomationTestK8s(chainClient, contractDeployer, chainlinkNodes) + a.RegistrySettings = *registrySettings + a.RegistrarSettings = contracts.KeeperRegistrarSettings{ + AutoApproveConfigType: uint8(2), + AutoApproveMaxAllowed: math.MaxUint16, + MinLinkJuels: big.NewInt(0), + } + a.PluginConfig = ocr2keepers30config.OffchainConfig{ + TargetProbability: "0.999", + TargetInRounds: 1, + PerformLockoutWindow: 80_000, // Copied from arbitrum mainnet prod value + GasLimitPerReport: 10_300_000, + GasOverheadPerUpkeep: 300_000, + MinConfirmations: 0, + MaxUpkeepBatchSize: 10, + } + a.PublicConfig = ocr3.PublicConfig{ + DeltaProgress: 10 * time.Second, + DeltaResend: 15 * time.Second, + DeltaInitial: 500 * time.Millisecond, + DeltaRound: 1000 * time.Millisecond, + DeltaGrace: 200 * time.Millisecond, + DeltaCertifiedCommitRequest: 300 * time.Millisecond, + DeltaStage: 15 * time.Second, + RMax: 24, + MaxDurationQuery: 20 * time.Millisecond, + MaxDurationObservation: 20 * time.Millisecond, + MaxDurationShouldAcceptAttestedReport: 1200 * time.Millisecond, + MaxDurationShouldTransmitAcceptedReport: 20 * time.Millisecond, + F: 1, + } + + a.SetupAutomationDeployment(t) + + err = actions.FundChainlinkNodesAddress(chainlinkNodes[1:], chainClient, big.NewFloat(100), 0) + require.NoError(t, err, "Error funding chainlink nodes") + + consumerContracts := make([]contracts.KeeperConsumer, 0) + triggerContracts := make([]contracts.LogEmitter, 0) + + utilsABI, err := automation_utils_2_1.AutomationUtilsMetaData.GetAbi() + require.NoError(t, err, "Error getting automation utils abi") + emitterABI, err := log_emitter.LogEmitterMetaData.GetAbi() + require.NoError(t, err, "Error getting log emitter abi") + consumerABI, err := simple_log_upkeep_counter_wrapper.SimpleLogUpkeepCounterMetaData.GetAbi() + require.NoError(t, err, "Error getting consumer abi") + + var bytes0 = [32]byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + } + + upkeepConfigs := make([]automationv2.UpkeepConfig, 0) + + for i := 0; i < numberOfUpkeeps; i++ { + consumerContract, err := contractDeployer.DeployAutomationSimpleLogTriggerConsumer() + require.NoError(t, err, "Error deploying automation consumer contract") + consumerContracts = append(consumerContracts, consumerContract) + l.Debug(). + Str("Contract Address", consumerContract.Address()). + Int("Number", i+1). + Int("Out Of", numberOfUpkeeps). + Msg("Deployed Automation Log Trigger Consumer Contract") + + cEVMClient, err := blockchain.ConcurrentEVMClient(testNetwork, testEnvironment, chainClient, l) + require.NoError(t, err, "Error building concurrent chain client") + + cContractDeployer, err := contracts.NewContractDeployer(cEVMClient, l) + require.NoError(t, err, "Error building concurrent contract deployer") + + triggerContract, err := cContractDeployer.DeployLogEmitterContract() + require.NoError(t, err, "Error deploying log emitter contract") + triggerContracts = append(triggerContracts, triggerContract) + l.Debug(). + Str("Contract Address", triggerContract.Address().Hex()). + Int("Number", i+1). + Int("Out Of", numberOfUpkeeps). + Msg("Deployed Automation Log Trigger Emitter Contract") + } + + err = chainClient.WaitForEvents() + require.NoError(t, err, "Failed waiting for contracts to deploy") + + for i, consumerContract := range consumerContracts { + logTriggerConfigStruct := automation_utils_2_1.LogTriggerConfig{ + ContractAddress: triggerContracts[i].Address(), + FilterSelector: 0, + Topic0: emitterABI.Events["Log1"].ID, + Topic1: bytes0, + Topic2: bytes0, + Topic3: bytes0, + } + encodedLogTriggerConfig, err := utilsABI.Methods["_logTriggerConfig"].Inputs.Pack(&logTriggerConfigStruct) + require.NoError(t, err, "Error encoding log trigger config") + l.Debug().Bytes("Encoded Log Trigger Config", encodedLogTriggerConfig).Msg("Encoded Log Trigger Config") + + upkeepConfig := automationv2.UpkeepConfig{ + UpkeepName: fmt.Sprintf("LogTriggerUpkeep-%d", i), + EncryptedEmail: []byte("test@mail.com"), + UpkeepContract: common.HexToAddress(consumerContract.Address()), + GasLimit: automationDefaultUpkeepGasLimit, + AdminAddress: common.HexToAddress(chainClient.GetDefaultWallet().Address()), + TriggerType: uint8(1), + CheckData: []byte("0"), + TriggerConfig: encodedLogTriggerConfig, + OffchainConfig: []byte("0"), + FundingAmount: automationDefaultLinkFunds, + } + upkeepConfigs = append(upkeepConfigs, upkeepConfig) + } + + registrationTxHashes, err := a.RegisterUpkeeps(upkeepConfigs) + require.NoError(t, err, "Error registering upkeeps") + + err = chainClient.WaitForEvents() + require.NoError(t, err, "Failed waiting for upkeeps to register") + + upkeepIds, err := a.ConfirmUpkeepsRegistered(registrationTxHashes) + require.NoError(t, err, "Error confirming upkeeps registered") + + l.Info().Msg("Successfully registered all Automation Upkeeps") + l.Info().Interface("Upkeep IDs", upkeepIds).Msg("Upkeeps Registered") + l.Info().Str("STARTUP_WAIT_TIME", StartupWaitTime.String()).Msg("Waiting for plugin to start") + time.Sleep(StartupWaitTime) + + startBlock, err := chainClient.LatestBlockNumber(ctx) + require.NoError(t, err, "Error getting latest block number") + + p := wasp.NewProfile() + + for i, triggerContract := range triggerContracts { + g, err := wasp.NewGenerator(&wasp.Config{ + T: t, + LoadType: wasp.RPS, + GenName: fmt.Sprintf("log_trigger_gen_%s", triggerContract.Address().String()), + CallTimeout: time.Second * 10, + Schedule: wasp.Plain( + 1, + loadDuration, + ), + Gun: NewLogTriggerUser( + triggerContract, + consumerContracts[i], + l, + numberOfEvents, + ), + CallResultBufLen: 1000000, + }) + p.Add(g, err) + } + + l.Info().Msg("Starting load generators") + startTime := time.Now() + err = sendSlackNotification("Started", l, testEnvironment.Cfg.Namespace, strconv.Itoa(numberofNodes), + strconv.FormatInt(startTime.UnixMilli(), 10), "now", + []slack.Block{extraBlockWithText("\bTest Config\b\n```" + testConfig + "```")}) + if err != nil { + l.Error().Err(err).Msg("Error sending slack notification") + } + _, err = p.Run(true) + require.NoError(t, err, "Error running load generators") + + l.Info().Msg("Finished load generators") + l.Info().Str("STOP_WAIT_TIME", StopWaitTime.String()).Msg("Waiting for upkeeps to be performed") + time.Sleep(StopWaitTime) + l.Info().Msg("Finished waiting 60s for upkeeps to be performed") + endTime := time.Now() + testDuration := endTime.Sub(startTime) + l.Info().Str("Duration", testDuration.String()).Msg("Test Duration") + endBlock, err := chainClient.LatestBlockNumber(ctx) + require.NoError(t, err, "Error getting latest block number") + l.Info().Uint64("Starting Block", startBlock).Uint64("Ending Block", endBlock).Msg("Test Block Range") + + upkeepDelays := make([][]int64, 0) + var numberOfEventsEmitted int + var batchSize uint64 = 500 + + for _, gen := range p.Generators { + numberOfEventsEmitted += len(gen.GetData().OKData.Data) + } + numberOfEventsEmitted = numberOfEventsEmitted * numberOfEvents + l.Info().Int("Number of Events Emitted", numberOfEventsEmitted).Msg("Number of Events Emitted") + + if endBlock-startBlock < batchSize { + batchSize = endBlock - startBlock + } + + for _, consumerContract := range consumerContracts { + var ( + logs []types.Log + address = common.HexToAddress(consumerContract.Address()) + timeout = 5 * time.Second + ) + for fromBlock := startBlock; fromBlock < endBlock; fromBlock += batchSize + 1 { + filterQuery := geth.FilterQuery{ + Addresses: []common.Address{address}, + FromBlock: big.NewInt(0).SetUint64(fromBlock), + ToBlock: big.NewInt(0).SetUint64(fromBlock + batchSize), + Topics: [][]common.Hash{{consumerABI.Events["PerformingUpkeep"].ID}}, + } + err = fmt.Errorf("initial error") // to ensure our for loop runs at least once + for err != nil { + var ( + logsInBatch []types.Log + ) + ctx2, cancel := context.WithTimeout(ctx, timeout) + logsInBatch, err = chainClient.FilterLogs(ctx2, filterQuery) + cancel() + if err != nil { + l.Error().Err(err). + Interface("FilterQuery", filterQuery). + Str("Contract Address", consumerContract.Address()). + Str("Timeout", timeout.String()). + Msg("Error getting logs") + timeout = time.Duration(math.Min(float64(timeout)*2, float64(2*time.Minute))) + continue + } + l.Info(). + Interface("FilterQuery", filterQuery). + Str("Contract Address", consumerContract.Address()). + Str("Timeout", timeout.String()). + Msg("Collected logs") + logs = append(logs, logsInBatch...) + } + } + + if len(logs) > 0 { + delay := make([]int64, 0) + for _, log := range logs { + eventDetails, err := consumerABI.EventByID(log.Topics[0]) + require.NoError(t, err, "Error getting event details") + consumer, err := simple_log_upkeep_counter_wrapper.NewSimpleLogUpkeepCounter( + address, chainClient.Backend(), + ) + require.NoError(t, err, "Error getting consumer contract") + if eventDetails.Name == "PerformingUpkeep" { + parsedLog, err := consumer.ParsePerformingUpkeep(log) + require.NoError(t, err, "Error parsing log") + delay = append(delay, parsedLog.TimeToPerform.Int64()) + } + } + upkeepDelays = append(upkeepDelays, delay) + } + } + + l.Info().Interface("Upkeep Delays", upkeepDelays).Msg("Upkeep Delays") + + var allUpkeepDelays []int64 + + for _, upkeepDelay := range upkeepDelays { + allUpkeepDelays = append(allUpkeepDelays, upkeepDelay...) + } + + avg, median, ninetyPct, ninetyNinePct, maximum := testreporters.IntListStats(allUpkeepDelays) + eventsMissed := numberOfEventsEmitted - len(allUpkeepDelays) + percentMissed := float64(eventsMissed) / float64(numberOfEventsEmitted) * 100 + l.Info(). + Float64("Average", avg).Int64("Median", median). + Int64("90th Percentile", ninetyPct).Int64("99th Percentile", ninetyNinePct). + Int64("Max", maximum).Msg("Upkeep Delays in seconds") + + l.Info(). + Int("Total Perform Count", len(allUpkeepDelays)). + Int("Total Events Emitted", numberOfEventsEmitted). + Int("Total Events Missed", eventsMissed). + Float64("Percent Missed", percentMissed). + Msg("Test completed") + + testReport := fmt.Sprintf("Upkeep Delays in seconds\nAverage: %f\nMedian: %d\n90th Percentile: %d\n"+ + "99th Percentile: %d\nMax: %d\nTotal Perform Count: %d\n\nTotal Events Emitted: %d\nTotal Events Missed: %d\n"+ + "Percent Missed: %f\nTest Duration: %s\n", + avg, median, ninetyPct, ninetyNinePct, maximum, len(allUpkeepDelays), numberOfEventsEmitted, + eventsMissed, percentMissed, testDuration.String()) + + err = sendSlackNotification("Finished", l, testEnvironment.Cfg.Namespace, strconv.Itoa(numberofNodes), + strconv.FormatInt(startTime.UnixMilli(), 10), strconv.FormatInt(endTime.UnixMilli(), 10), + []slack.Block{extraBlockWithText("\bTest Report\b\n```" + testReport + "```")}) + if err != nil { + l.Error().Err(err).Msg("Error sending slack notification") + } + + t.Cleanup(func() { + if err = actions.TeardownRemoteSuite(t, testEnvironment.Cfg.Namespace, chainlinkNodes, nil, chainClient); err != nil { + l.Error().Err(err).Msg("Error when tearing down remote suite") + } + }) + +} diff --git a/integration-tests/load/automationv2_1/gun.go b/integration-tests/load/automationv2_1/gun.go new file mode 100644 index 00000000000..a2863a6064b --- /dev/null +++ b/integration-tests/load/automationv2_1/gun.go @@ -0,0 +1,43 @@ +package automationv2_1 + +import ( + "github.com/rs/zerolog" + "github.com/smartcontractkit/wasp" + + "github.com/smartcontractkit/chainlink/integration-tests/contracts" +) + +type LogTriggerGun struct { + triggerContract contracts.LogEmitter + upkeepContract contracts.KeeperConsumer + logger zerolog.Logger + numberOfEvents int +} + +func NewLogTriggerUser( + triggerContract contracts.LogEmitter, + upkeepContract contracts.KeeperConsumer, + logger zerolog.Logger, + numberOfEvents int, +) *LogTriggerGun { + return &LogTriggerGun{ + triggerContract: triggerContract, + upkeepContract: upkeepContract, + logger: logger, + numberOfEvents: numberOfEvents, + } +} + +func (m *LogTriggerGun) Call(_ *wasp.Generator) *wasp.CallResult { + m.logger.Debug().Str("Trigger address", m.triggerContract.Address().String()).Msg("Triggering upkeep") + payload := make([]int, 0) + for i := 0; i < m.numberOfEvents; i++ { + payload = append(payload, 1) + } + _, err := m.triggerContract.EmitLogInts(payload) + if err != nil { + return &wasp.CallResult{Error: err.Error(), Failed: true} + } + + return &wasp.CallResult{} +} diff --git a/integration-tests/load/automationv2_1/helpers.go b/integration-tests/load/automationv2_1/helpers.go new file mode 100644 index 00000000000..3c08199a9cf --- /dev/null +++ b/integration-tests/load/automationv2_1/helpers.go @@ -0,0 +1,71 @@ +package automationv2_1 + +import ( + "fmt" + "os" + "strings" + + "github.com/rs/zerolog" + "github.com/slack-go/slack" + + "github.com/smartcontractkit/chainlink-testing-framework/k8s/config" + reportModel "github.com/smartcontractkit/chainlink-testing-framework/testreporters" + "github.com/smartcontractkit/chainlink/integration-tests/testreporters" +) + +func getEnv(key, fallback string) string { + if inputs, ok := os.LookupEnv("TEST_INPUTS"); ok { + values := strings.Split(inputs, ",") + for _, value := range values { + if strings.Contains(value, key) { + return strings.Split(value, "=")[1] + } + } + } + return fallback +} + +func extraBlockWithText(text string) slack.Block { + return slack.NewSectionBlock(slack.NewTextBlockObject( + "mrkdwn", text, false, false), nil, nil) +} + +func sendSlackNotification(header string, l zerolog.Logger, namespace string, numberOfNodes, + startingTime string, endingTime string, extraBlocks []slack.Block) error { + slackClient := slack.New(reportModel.SlackAPIKey) + + headerText := ":chainlink-keepers: Automation Load Test " + header + " :white_check_mark:" + + formattedDashboardUrl := fmt.Sprintf("%s?orgId=1&from=%s&to=%s&var-namespace=%s&var-number_of_nodes=%s", testreporters.DashboardUrl, startingTime, endingTime, namespace, numberOfNodes) + l.Info().Str("Dashboard", formattedDashboardUrl).Msg("Dashboard URL") + + pyroscopeServer := os.Getenv(config.EnvVarPyroscopeServer) + pyroscopeEnvironment := os.Getenv(config.EnvVarPyroscopeEnvironment) + + formattedPyroscopeUrl := fmt.Sprintf("%s/?query=chainlink-node.cpu{Environment=\"%s\"}&from=%s&to=%s", pyroscopeServer, pyroscopeEnvironment, startingTime, endingTime) + + var notificationBlocks []slack.Block + + notificationBlocks = append(notificationBlocks, + slack.NewHeaderBlock(slack.NewTextBlockObject("plain_text", headerText, true, false))) + notificationBlocks = append(notificationBlocks, + slack.NewContextBlock("context_block", slack.NewTextBlockObject("plain_text", namespace, false, false))) + notificationBlocks = append(notificationBlocks, slack.NewDividerBlock()) + if pyroscopeServer != "" { + l.Info().Str("Pyroscope", formattedPyroscopeUrl).Msg("Dashboard URL") + notificationBlocks = append(notificationBlocks, slack.NewSectionBlock(slack.NewTextBlockObject("mrkdwn", + fmt.Sprintf("<%s|Pyroscope>", + formattedPyroscopeUrl), false, true), nil, nil)) + } + notificationBlocks = append(notificationBlocks, slack.NewSectionBlock(slack.NewTextBlockObject("mrkdwn", + fmt.Sprintf("<%s|Test Dashboard> \nNotifying <@%s>", + formattedDashboardUrl, reportModel.SlackUserID), false, true), nil, nil)) + + if len(extraBlocks) > 0 { + notificationBlocks = append(notificationBlocks, extraBlocks...) + } + + ts, err := reportModel.SendSlackMessage(slackClient, slack.MsgOptionBlocks(notificationBlocks...)) + l.Info().Str("ts", ts).Msg("Sent Slack Message") + return err +} diff --git a/integration-tests/load/functions/config.go b/integration-tests/load/functions/config.go index 5c622401aba..ad7e7446afb 100644 --- a/integration-tests/load/functions/config.go +++ b/integration-tests/load/functions/config.go @@ -1,12 +1,14 @@ package loadfunctions import ( + "fmt" + "math/big" + "os" + "github.com/pelletier/go-toml/v2" - "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/smartcontractkit/chainlink/v2/core/store/models" - "math/big" - "os" ) const ( @@ -103,22 +105,21 @@ func ReadConfig() (*PerformanceConfig, error) { var cfg *PerformanceConfig d, err := os.ReadFile(DefaultConfigFilename) if err != nil { - return nil, errors.Wrap(err, ErrReadPerfConfig) + return nil, fmt.Errorf("%s, err: %w", ErrReadPerfConfig, err) } err = toml.Unmarshal(d, &cfg) if err != nil { - return nil, errors.Wrap(err, ErrUnmarshalPerfConfig) + return nil, fmt.Errorf("%s, err: %w", ErrUnmarshalPerfConfig, err) } log.Debug().Interface("PerformanceConfig", cfg).Msg("Parsed performance config") mpk := os.Getenv("MUMBAI_KEYS") murls := os.Getenv("MUMBAI_URLS") snet := os.Getenv("SELECTED_NETWORKS") if mpk == "" || murls == "" || snet == "" { - return nil, errors.New( + return nil, fmt.Errorf( "ensure variables are set:\nMUMBAI_KEYS variable, private keys, comma separated\nSELECTED_NETWORKS=MUMBAI\nMUMBAI_URLS variable, websocket urls, comma separated", ) - } else { - cfg.MumbaiPrivateKey = mpk } + cfg.MumbaiPrivateKey = mpk return cfg, nil } diff --git a/integration-tests/load/functions/functions_test.go b/integration-tests/load/functions/functions_test.go index 7822035208e..dc52846d3c9 100644 --- a/integration-tests/load/functions/functions_test.go +++ b/integration-tests/load/functions/functions_test.go @@ -1,10 +1,11 @@ package loadfunctions import ( - "github.com/smartcontractkit/wasp" - "github.com/stretchr/testify/require" "testing" "time" + + "github.com/smartcontractkit/wasp" + "github.com/stretchr/testify/require" ) func TestFunctionsLoad(t *testing.T) { diff --git a/integration-tests/load/functions/gateway.go b/integration-tests/load/functions/gateway.go index aefe4fbedc2..ac5f895ac18 100644 --- a/integration-tests/load/functions/gateway.go +++ b/integration-tests/load/functions/gateway.go @@ -8,16 +8,17 @@ import ( "encoding/hex" "encoding/json" "fmt" + "time" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/go-resty/resty/v2" - "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" "github.com/smartcontractkit/chainlink/v2/core/services/s4" - "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" - "time" ) type RPCResponse struct { @@ -115,7 +116,7 @@ func UploadS4Secrets(rc *resty.Client, s4Cfg *S4SecretsCfg) (uint8, uint64, erro log.Debug().Interface("Result", result).Msg("S4 secrets_set response result") for _, nodeResponse := range result.Result.Body.Payload.NodeResponses { if !nodeResponse.Body.Payload.Success { - return 0, 0, fmt.Errorf("node response was not succesful") + return 0, 0, fmt.Errorf("node response was not successful") } } return uint8(envelope.SlotID), envelope.Version, nil @@ -182,12 +183,12 @@ func EncryptS4Secrets(deployerPk *ecdsa.PrivateKey, tdh2Pk *tdh2easy.PublicKey, donKey = bytes.Join([][]byte{b, donKey}, nil) donPubKey, err := crypto.UnmarshalPubkey(donKey) if err != nil { - return "", errors.Wrap(err, "failed to unmarshal DON key") + return "", fmt.Errorf("failed to unmarshal DON key: %w", err) } eciesDONPubKey := ecies.ImportECDSAPublic(donPubKey) signature, err := deployerPk.Sign(rand.Reader, []byte(msgJSON), nil) if err != nil { - return "", errors.Wrap(err, "failed to sign the msg with Ethereum key") + return "", fmt.Errorf("failed to sign the msg with Ethereum key: %w", err) } signedSecrets, err := json.Marshal(struct { Signature []byte `json:"signature"` @@ -197,29 +198,29 @@ func EncryptS4Secrets(deployerPk *ecdsa.PrivateKey, tdh2Pk *tdh2easy.PublicKey, Message: msgJSON, }) if err != nil { - return "", errors.Wrap(err, "failed to marshal signed secrets") + return "", fmt.Errorf("failed to marshal signed secrets: %w", err) } ct, err := ecies.Encrypt(rand.Reader, eciesDONPubKey, signedSecrets, nil, nil) if err != nil { - return "", errors.Wrap(err, "failed to encrypt with DON key") + return "", fmt.Errorf("failed to encrypt with DON key: %w", err) } ct0xFormat, err := json.Marshal(map[string]interface{}{"0x0": base64.StdEncoding.EncodeToString(ct)}) if err != nil { - return "", errors.Wrap(err, "failed to marshal DON key encrypted format") + return "", fmt.Errorf("failed to marshal DON key encrypted format: %w", err) } ctTDH2Format, err := tdh2easy.Encrypt(tdh2Pk, ct0xFormat) if err != nil { - return "", errors.Wrap(err, "failed to encrypt with TDH2 public key") + return "", fmt.Errorf("failed to encrypt with TDH2 public key: %w", err) } tdh2Message, err := ctTDH2Format.Marshal() if err != nil { - return "", errors.Wrap(err, "failed to marshal TDH2 encrypted msg") + return "", fmt.Errorf("failed to marshal TDH2 encrypted msg: %w", err) } finalMsg, err := json.Marshal(map[string]interface{}{ "encryptedSecrets": "0x" + hex.EncodeToString(tdh2Message), }) if err != nil { - return "", errors.Wrap(err, "failed to marshal secrets msg") + return "", fmt.Errorf("failed to marshal secrets msg: %w", err) } return string(finalMsg), nil } diff --git a/integration-tests/load/functions/gateway_gun.go b/integration-tests/load/functions/gateway_gun.go index fd13922d0a7..3dafb458a50 100644 --- a/integration-tests/load/functions/gateway_gun.go +++ b/integration-tests/load/functions/gateway_gun.go @@ -3,14 +3,15 @@ package loadfunctions import ( "crypto/ecdsa" "fmt" - "github.com/go-resty/resty/v2" - "github.com/rs/zerolog/log" - "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" - "github.com/smartcontractkit/wasp" "math/rand" "os" "strconv" "time" + + "github.com/go-resty/resty/v2" + "github.com/rs/zerolog/log" + "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" + "github.com/smartcontractkit/wasp" ) /* SingleFunctionCallGun is a gun that constantly requests randomness for one feed */ diff --git a/integration-tests/load/functions/onchain_monitoring.go b/integration-tests/load/functions/onchain_monitoring.go index 0a8b4cef46a..c4b4bdb78c0 100644 --- a/integration-tests/load/functions/onchain_monitoring.go +++ b/integration-tests/load/functions/onchain_monitoring.go @@ -1,10 +1,11 @@ package loadfunctions import ( - "github.com/rs/zerolog/log" - "github.com/smartcontractkit/wasp" "testing" "time" + + "github.com/rs/zerolog/log" + "github.com/smartcontractkit/wasp" ) /* Monitors on-chain stats of LoadConsumer and pushes them to Loki every second */ diff --git a/integration-tests/load/functions/request_gun.go b/integration-tests/load/functions/request_gun.go index d9987eaa756..bd4cf5f35aa 100644 --- a/integration-tests/load/functions/request_gun.go +++ b/integration-tests/load/functions/request_gun.go @@ -13,16 +13,15 @@ const ( ) type SingleFunctionCallGun struct { - ft *FunctionsTest - mode TestMode - times uint32 - source string - slotID uint8 - slotVersion uint64 - encryptedSecrets []byte - args []string - subscriptionId uint64 - jobId [32]byte + ft *FunctionsTest + mode TestMode + times uint32 + source string + slotID uint8 + slotVersion uint64 + args []string + subscriptionId uint64 + jobId [32]byte } func NewSingleFunctionCallGun( diff --git a/integration-tests/load/functions/setup.go b/integration-tests/load/functions/setup.go index 5d44cbc698e..c0be47ca836 100644 --- a/integration-tests/load/functions/setup.go +++ b/integration-tests/load/functions/setup.go @@ -2,6 +2,7 @@ package loadfunctions import ( "crypto/ecdsa" + "fmt" "math/big" mrand "math/rand" "os" @@ -10,11 +11,11 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/go-resty/resty/v2" - "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink-testing-framework/networks" - "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" "github.com/smartcontractkit/chainlink/integration-tests/contracts" chainlinkutils "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -50,7 +51,7 @@ type S4SecretsCfg struct { } func SetupLocalLoadTestEnv(cfg *PerformanceConfig) (*FunctionsTest, error) { - bc, err := blockchain.NewEVMClientFromNetwork(networks.SelectedNetwork, log.Logger) + bc, err := blockchain.NewEVMClientFromNetwork(networks.MustGetSelectedNetworksFromEnv()[0], log.Logger) if err != nil { return nil, err } @@ -91,41 +92,41 @@ func SetupLocalLoadTestEnv(cfg *PerformanceConfig) (*FunctionsTest, error) { log.Info().Msg("Creating new subscription") subID, err := router.CreateSubscriptionWithConsumer(loadTestClient.Address()) if err != nil { - return nil, errors.Wrap(err, "failed to create a new subscription") + return nil, fmt.Errorf("failed to create a new subscription: %w", err) } encodedSubId, err := chainlinkutils.ABIEncode(`[{"type":"uint64"}]`, subID) if err != nil { - return nil, errors.Wrap(err, "failed to encode subscription ID for funding") + return nil, fmt.Errorf("failed to encode subscription ID for funding: %w", err) } _, err = lt.TransferAndCall(router.Address(), big.NewInt(0).Mul(cfg.Common.Funding.SubFunds, big.NewInt(1e18)), encodedSubId) if err != nil { - return nil, errors.Wrap(err, "failed to transferAndCall router, LINK funding") + return nil, fmt.Errorf("failed to transferAndCall router, LINK funding: %w", err) } cfg.Common.SubscriptionID = subID } pKey, pubKey, err := parseEthereumPrivateKey(os.Getenv("MUMBAI_KEYS")) if err != nil { - return nil, errors.Wrap(err, "failed to load Ethereum private key") + return nil, fmt.Errorf("failed to load Ethereum private key: %w", err) } tpk, err := coord.GetThresholdPublicKey() if err != nil { - return nil, errors.Wrap(err, "failed to get Threshold public key") + return nil, fmt.Errorf("failed to get Threshold public key: %w", err) } log.Info().Hex("ThresholdPublicKeyBytesHex", tpk).Msg("Loaded coordinator keys") donPubKey, err := coord.GetDONPublicKey() if err != nil { - return nil, errors.Wrap(err, "failed to get DON public key") + return nil, fmt.Errorf("failed to get DON public key: %w", err) } log.Info().Hex("DONPublicKeyHex", donPubKey).Msg("Loaded DON key") tdh2pk, err := ParseTDH2Key(tpk) if err != nil { - return nil, errors.Wrap(err, "failed to unmarshal tdh2 public key") + return nil, fmt.Errorf("failed to unmarshal tdh2 public key: %w", err) } var encryptedSecrets string if cfg.Common.Secrets != "" { encryptedSecrets, err = EncryptS4Secrets(pKey, tdh2pk, donPubKey, cfg.Common.Secrets) if err != nil { - return nil, errors.Wrap(err, "failed to generate tdh2 secrets") + return nil, fmt.Errorf("failed to generate tdh2 secrets: %w", err) } slotID, slotVersion, err := UploadS4Secrets(resty.New(), &S4SecretsCfg{ GatewayURL: cfg.Common.GatewayURL, @@ -139,7 +140,7 @@ func SetupLocalLoadTestEnv(cfg *PerformanceConfig) (*FunctionsTest, error) { S4SetPayload: encryptedSecrets, }) if err != nil { - return nil, errors.Wrap(err, "failed to upload secrets to S4") + return nil, fmt.Errorf("failed to upload secrets to S4: %w", err) } cfg.Common.SecretsSlotID = slotID cfg.Common.SecretsVersionID = slotVersion @@ -168,13 +169,13 @@ func SetupLocalLoadTestEnv(cfg *PerformanceConfig) (*FunctionsTest, error) { func parseEthereumPrivateKey(pk string) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) { pKey, err := crypto.HexToECDSA(pk) if err != nil { - return nil, nil, errors.Wrap(err, "failed to convert Ethereum key from hex") + return nil, nil, fmt.Errorf("failed to convert Ethereum key from hex: %w", err) } publicKey := pKey.Public() pubKey, ok := publicKey.(*ecdsa.PublicKey) if !ok { - return nil, nil, errors.Wrap(err, "failed to get public key from Ethereum private key") + return nil, nil, fmt.Errorf("failed to get public key from Ethereum private key: %w", err) } log.Info().Str("Address", crypto.PubkeyToAddress(*pubKey).Hex()).Msg("Parsed private key for address") return pKey, pubKey, nil diff --git a/integration-tests/load/log_poller/config.toml b/integration-tests/load/log_poller/config.toml new file mode 100644 index 00000000000..2e328001943 --- /dev/null +++ b/integration-tests/load/log_poller/config.toml @@ -0,0 +1,22 @@ +[general] +generator = "looped" +contracts = 10 +events_per_tx = 10 + +[chaos] +experiment_count = 10 + +[looped] +[looped.contract] +execution_count = 300 + +[looped.fuzz] +min_emit_wait_time_ms = 100 +max_emit_wait_time_ms = 500 + +[wasp] +[wasp.load] +call_timeout = "3m" +rate_limit_unit_duration = "2s" +LPS = 30 +duration = "1m" \ No newline at end of file diff --git a/integration-tests/load/log_poller/log_poller_test.go b/integration-tests/load/log_poller/log_poller_test.go new file mode 100644 index 00000000000..04366848f0e --- /dev/null +++ b/integration-tests/load/log_poller/log_poller_test.go @@ -0,0 +1,25 @@ +package logpoller + +import ( + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + + "github.com/stretchr/testify/require" + + lp_helpers "github.com/smartcontractkit/chainlink/integration-tests/universal/log_poller" +) + +func TestLoadTestLogPoller(t *testing.T) { + cfg, err := lp_helpers.ReadConfig(lp_helpers.DefaultConfigFilename) + require.NoError(t, err) + + eventsToEmit := []abi.Event{} + for _, event := range lp_helpers.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit + + lp_helpers.ExecuteBasicLogPollerTest(t, cfg) +} diff --git a/integration-tests/load/ocr/README.md b/integration-tests/load/ocr/README.md new file mode 100644 index 00000000000..20446992dc2 --- /dev/null +++ b/integration-tests/load/ocr/README.md @@ -0,0 +1,28 @@ +### OCR Load tests + +## Setup +These tests can connect to any cluster create with [chainlink-cluster](../../../charts/chainlink-cluster/README.md) + +Create your cluster +``` +kubectl create ns my-cluster +devspace use namespace my-cluster +devspace deploy +sudo kubefwd svc -n my-cluster +``` + +Change environment connection configuration [here](connection.toml) + +If you haven't changed anything in [devspace.yaml](../../../charts/chainlink-cluster/devspace.yaml) then default connection configuration will work + +## Usage + +``` +export LOKI_TOKEN=... +export LOKI_URL=... + +go test -v -run TestOCRLoad +go test -v -run TestOCRVolume +``` + +Check test configuration [here](config.toml) \ No newline at end of file diff --git a/integration-tests/load/ocr/config.go b/integration-tests/load/ocr/config.go new file mode 100644 index 00000000000..2991df3774a --- /dev/null +++ b/integration-tests/load/ocr/config.go @@ -0,0 +1,72 @@ +package ocr + +import ( + "encoding/base64" + "fmt" + "os" + + "github.com/pelletier/go-toml/v2" + "github.com/rs/zerolog/log" + + "github.com/smartcontractkit/chainlink/v2/core/store/models" +) + +const ( + DefaultConfigFilename = "config.toml" + ErrReadPerfConfig = "failed to read TOML config for performance tests" + ErrUnmarshalPerfConfig = "failed to unmarshal TOML config for performance tests" +) + +type PerformanceConfig struct { + Load *Load `toml:"Load"` + Volume *Volume `toml:"Volume"` + Common *Common `toml:"Common"` +} + +type Common struct { + ETHFunds int `toml:"eth_funds"` +} + +type Load struct { + TestDuration *models.Duration `toml:"test_duration"` + Rate int64 `toml:"rate"` + RateLimitUnitDuration *models.Duration `toml:"rate_limit_unit_duration"` + VerificationInterval *models.Duration `toml:"verification_interval"` + VerificationTimeout *models.Duration `toml:"verification_timeout"` + EAChangeInterval *models.Duration `toml:"ea_change_interval"` +} + +type Volume struct { + TestDuration *models.Duration `toml:"test_duration"` + Rate int64 `toml:"rate"` + VURequestsPerUnit int `toml:"vu_requests_per_unit"` + RateLimitUnitDuration *models.Duration `toml:"rate_limit_unit_duration"` + VerificationInterval *models.Duration `toml:"verification_interval"` + VerificationTimeout *models.Duration `toml:"verification_timeout"` + EAChangeInterval *models.Duration `toml:"ea_change_interval"` +} + +func ReadConfig() (*PerformanceConfig, error) { + var cfg *PerformanceConfig + rawConfig := os.Getenv("CONFIG") + var d []byte + var err error + if rawConfig == "" { + d, err = os.ReadFile(DefaultConfigFilename) + if err != nil { + return nil, fmt.Errorf("%s, err: %w", ErrReadPerfConfig, err) + } + } else { + d, err = base64.StdEncoding.DecodeString(rawConfig) + if err != nil { + return nil, fmt.Errorf("%s, err: %w", ErrReadPerfConfig, err) + } + } + err = toml.Unmarshal(d, &cfg) + if err != nil { + return nil, fmt.Errorf("%s, err: %w", ErrUnmarshalPerfConfig, err) + } + + log.Debug().Interface("Config", cfg).Msg("Parsed config") + return cfg, nil +} diff --git a/integration-tests/load/ocr/config.toml b/integration-tests/load/ocr/config.toml new file mode 100644 index 00000000000..df8364b3ee4 --- /dev/null +++ b/integration-tests/load/ocr/config.toml @@ -0,0 +1,20 @@ +[Load] +test_duration = "3m" +rate_limit_unit_duration = "1m" +rate = 3 +verification_interval = "5s" +verification_timeout = "3m" +ea_change_interval = "5s" + +[Volume] +test_duration = "3m" +rate_limit_unit_duration = "1m" +vu_requests_per_unit = 10 +rate = 1 +verification_interval = "5s" +verification_timeout = "3m" + +ea_change_interval = "5s" + +[Common] +eth_funds = 3 \ No newline at end of file diff --git a/integration-tests/load/ocr/gun.go b/integration-tests/load/ocr/gun.go new file mode 100644 index 00000000000..a2eb1ff2200 --- /dev/null +++ b/integration-tests/load/ocr/gun.go @@ -0,0 +1,55 @@ +package ocr + +import ( + "context" + "sync/atomic" + "time" + + "github.com/rs/zerolog" + + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" + + "github.com/smartcontractkit/wasp" +) + +// Gun is a gun for the OCR load test +// it triggers new rounds for provided feed(aggregator) contract +type Gun struct { + roundNum atomic.Int64 + ocrInstances []contracts.OffchainAggregator + cc blockchain.EVMClient + l zerolog.Logger +} + +func NewGun(l zerolog.Logger, cc blockchain.EVMClient, ocrInstances []contracts.OffchainAggregator) *Gun { + return &Gun{ + l: l, + cc: cc, + ocrInstances: ocrInstances, + } +} + +func (m *Gun) Call(_ *wasp.Generator) *wasp.CallResult { + m.roundNum.Add(1) + requestedRound := m.roundNum.Load() + m.l.Info(). + Int64("RoundNum", requestedRound). + Str("FeedID", m.ocrInstances[0].Address()). + Msg("starting new round") + err := m.ocrInstances[0].RequestNewRound() + if err != nil { + return &wasp.CallResult{Error: err.Error(), Failed: true} + } + for { + time.Sleep(5 * time.Second) + lr, err := m.ocrInstances[0].GetLatestRound(context.Background()) + if err != nil { + return &wasp.CallResult{Error: err.Error(), Failed: true} + } + m.l.Info().Interface("LatestRound", lr).Msg("latest round") + if lr.RoundId.Int64() >= requestedRound { + return &wasp.CallResult{} + } + } +} diff --git a/integration-tests/load/ocr/helper.go b/integration-tests/load/ocr/helper.go new file mode 100644 index 00000000000..c35dc384d17 --- /dev/null +++ b/integration-tests/load/ocr/helper.go @@ -0,0 +1,68 @@ +package ocr + +import ( + "math/big" + "math/rand" + "time" + + "github.com/rs/zerolog" + + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + + client2 "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink/integration-tests/actions" + "github.com/smartcontractkit/chainlink/integration-tests/client" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" +) + +func SetupCluster( + cc blockchain.EVMClient, + cd contracts.ContractDeployer, + workerNodes []*client.ChainlinkK8sClient, +) (contracts.LinkToken, error) { + err := actions.FundChainlinkNodes(workerNodes, cc, big.NewFloat(3)) + if err != nil { + return nil, err + } + lt, err := cd.DeployLinkTokenContract() + if err != nil { + return nil, err + } + return lt, nil +} + +func SetupFeed( + cc blockchain.EVMClient, + msClient *client2.MockserverClient, + cd contracts.ContractDeployer, + bootstrapNode *client.ChainlinkK8sClient, + workerNodes []*client.ChainlinkK8sClient, + lt contracts.LinkToken, +) ([]contracts.OffchainAggregator, error) { + ocrInstances, err := actions.DeployOCRContracts(1, lt, cd, workerNodes, cc) + if err != nil { + return nil, err + } + err = actions.CreateOCRJobs(ocrInstances, bootstrapNode, workerNodes, 5, msClient, cc.GetChainID().String()) + if err != nil { + return nil, err + } + return ocrInstances, nil +} + +func SimulateEAActivity( + l zerolog.Logger, + eaChangeInterval time.Duration, + ocrInstances []contracts.OffchainAggregator, + workerNodes []*client.ChainlinkK8sClient, + msClient *client2.MockserverClient, +) { + go func() { + for { + time.Sleep(eaChangeInterval) + if err := actions.SetAllAdapterResponsesToTheSameValue(rand.Intn(1000), ocrInstances, workerNodes, msClient); err != nil { + l.Error().Err(err).Msg("failed to update mockserver responses") + } + } + }() +} diff --git a/integration-tests/load/ocr/ocr_test.go b/integration-tests/load/ocr/ocr_test.go new file mode 100644 index 00000000000..6bf1487125d --- /dev/null +++ b/integration-tests/load/ocr/ocr_test.go @@ -0,0 +1,71 @@ +package ocr + +import ( + "testing" + + "github.com/smartcontractkit/chainlink/integration-tests/k8s" + + "github.com/smartcontractkit/wasp" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-testing-framework/logging" +) + +var ( + CommonTestLabels = map[string]string{ + "branch": "ocr_healthcheck_local", + "commit": "ocr_healthcheck_local", + } +) + +func TestOCRPerformance(t *testing.T) { + l := logging.GetTestLogger(t) + cc, msClient, cd, bootstrapNode, workerNodes, err := k8s.ConnectRemote(l) + require.NoError(t, err) + lt, err := SetupCluster(cc, cd, workerNodes) + require.NoError(t, err) + ocrInstances, err := SetupFeed(cc, msClient, cd, bootstrapNode, workerNodes, lt) + require.NoError(t, err) + cfg, err := ReadConfig() + require.NoError(t, err) + SimulateEAActivity(l, cfg.Load.EAChangeInterval.Duration(), ocrInstances, workerNodes, msClient) + + p := wasp.NewProfile() + p.Add(wasp.NewGenerator(&wasp.Config{ + T: t, + GenName: "ocr", + LoadType: wasp.RPS, + CallTimeout: cfg.Load.VerificationTimeout.Duration(), + RateLimitUnitDuration: cfg.Load.RateLimitUnitDuration.Duration(), + Schedule: wasp.Plain(cfg.Load.Rate, cfg.Load.TestDuration.Duration()), + Gun: NewGun(l, cc, ocrInstances), + Labels: CommonTestLabels, + LokiConfig: wasp.NewEnvLokiConfig(), + })) + _, err = p.Run(true) + require.NoError(t, err) +} + +func TestOCRCapacity(t *testing.T) { + l := logging.GetTestLogger(t) + cc, msClient, cd, bootstrapNode, workerNodes, err := k8s.ConnectRemote(l) + require.NoError(t, err) + lt, err := SetupCluster(cc, cd, workerNodes) + require.NoError(t, err) + cfg, err := ReadConfig() + require.NoError(t, err) + + p := wasp.NewProfile() + p.Add(wasp.NewGenerator(&wasp.Config{ + T: t, + GenName: "ocr", + LoadType: wasp.VU, + CallTimeout: cfg.Volume.VerificationTimeout.Duration(), + Schedule: wasp.Plain(cfg.Volume.Rate, cfg.Volume.TestDuration.Duration()), + VU: NewVU(l, cfg.Volume.VURequestsPerUnit, cfg.Volume.RateLimitUnitDuration.Duration(), cc, lt, cd, bootstrapNode, workerNodes, msClient), + Labels: CommonTestLabels, + LokiConfig: wasp.NewEnvLokiConfig(), + })) + _, err = p.Run(true) + require.NoError(t, err) +} diff --git a/integration-tests/load/ocr/vu.go b/integration-tests/load/ocr/vu.go new file mode 100644 index 00000000000..a905ec011df --- /dev/null +++ b/integration-tests/load/ocr/vu.go @@ -0,0 +1,128 @@ +package ocr + +import ( + "context" + "sync/atomic" + "time" + + "github.com/rs/zerolog" + + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + + "github.com/smartcontractkit/wasp" + "go.uber.org/ratelimit" + + client2 "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink/integration-tests/actions" + "github.com/smartcontractkit/chainlink/integration-tests/client" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" +) + +// VU is a virtual user for the OCR load test +// it creates a feed and triggers new rounds +type VU struct { + rl ratelimit.Limiter + rate int + rateUnit time.Duration + roundNum atomic.Int64 + cc blockchain.EVMClient + lt contracts.LinkToken + cd contracts.ContractDeployer + bootstrapNode *client.ChainlinkK8sClient + workerNodes []*client.ChainlinkK8sClient + msClient *client2.MockserverClient + l zerolog.Logger + ocrInstances []contracts.OffchainAggregator + stop chan struct{} +} + +func NewVU( + l zerolog.Logger, + rate int, + rateUnit time.Duration, + cc blockchain.EVMClient, + lt contracts.LinkToken, + cd contracts.ContractDeployer, + bootstrapNode *client.ChainlinkK8sClient, + workerNodes []*client.ChainlinkK8sClient, + msClient *client2.MockserverClient, +) *VU { + return &VU{ + rl: ratelimit.New(rate, ratelimit.Per(rateUnit)), + rate: rate, + rateUnit: rateUnit, + l: l, + cc: cc, + lt: lt, + cd: cd, + msClient: msClient, + bootstrapNode: bootstrapNode, + workerNodes: workerNodes, + } +} + +func (m *VU) Clone(_ *wasp.Generator) wasp.VirtualUser { + return &VU{ + stop: make(chan struct{}, 1), + rl: ratelimit.New(m.rate, ratelimit.Per(m.rateUnit)), + rate: m.rate, + rateUnit: m.rateUnit, + l: m.l, + cc: m.cc, + lt: m.lt, + cd: m.cd, + msClient: m.msClient, + bootstrapNode: m.bootstrapNode, + workerNodes: m.workerNodes, + } +} + +func (m *VU) Setup(_ *wasp.Generator) error { + ocrInstances, err := actions.DeployOCRContracts(1, m.lt, m.cd, m.workerNodes, m.cc) + if err != nil { + return err + } + err = actions.CreateOCRJobs(ocrInstances, m.bootstrapNode, m.workerNodes, 5, m.msClient, m.cc.GetChainID().String()) + if err != nil { + return err + } + m.ocrInstances = ocrInstances + return nil +} + +func (m *VU) Teardown(_ *wasp.Generator) error { + return nil +} + +func (m *VU) Call(l *wasp.Generator) { + m.rl.Take() + m.roundNum.Add(1) + requestedRound := m.roundNum.Load() + m.l.Info(). + Int64("RoundNum", requestedRound). + Str("FeedID", m.ocrInstances[0].Address()). + Msg("starting new round") + err := m.ocrInstances[0].RequestNewRound() + if err != nil { + l.ResponsesChan <- &wasp.CallResult{Error: err.Error(), Failed: true} + } + for { + time.Sleep(5 * time.Second) + lr, err := m.ocrInstances[0].GetLatestRound(context.Background()) + if err != nil { + l.ResponsesChan <- &wasp.CallResult{Error: err.Error(), Failed: true} + } + m.l.Info().Interface("LatestRound", lr).Msg("latest round") + if lr.RoundId.Int64() >= requestedRound { + l.ResponsesChan <- &wasp.CallResult{} + } + } +} + +func (m *VU) Stop(_ *wasp.Generator) { + m.stop <- struct{}{} +} + +func (m *VU) StopChan() chan struct{} { + return m.stop +} diff --git a/integration-tests/load/vrfv2/cmd/dashboard.go b/integration-tests/load/vrfv2/cmd/dashboard.go index 3035da0422f..0fb7be2b78b 100644 --- a/integration-tests/load/vrfv2/cmd/dashboard.go +++ b/integration-tests/load/vrfv2/cmd/dashboard.go @@ -1,6 +1,8 @@ package main import ( + "os" + "github.com/K-Phoen/grabana/dashboard" "github.com/K-Phoen/grabana/logs" "github.com/K-Phoen/grabana/row" @@ -8,7 +10,6 @@ import ( "github.com/K-Phoen/grabana/timeseries" "github.com/K-Phoen/grabana/timeseries/axis" "github.com/smartcontractkit/wasp" - "os" ) func main() { diff --git a/integration-tests/load/vrfv2/config.go b/integration-tests/load/vrfv2/config.go index ee5f3ff80dd..d5e94bb75fb 100644 --- a/integration-tests/load/vrfv2/config.go +++ b/integration-tests/load/vrfv2/config.go @@ -1,74 +1,151 @@ package loadvrfv2 import ( + "encoding/base64" + "fmt" + "os" + + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_config" + "github.com/pelletier/go-toml/v2" - "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/smartcontractkit/chainlink/v2/core/store/models" - "math/big" - "os" ) const ( DefaultConfigFilename = "config.toml" + SoakTestType = "Soak" + LoadTestType = "Load" + StressTestType = "Stress" + SpikeTestType = "Spike" - ErrReadPerfConfig = "failed to read TOML config for performance tests" - ErrUnmarshalPerfConfig = "failed to unmarshal TOML config for performance tests" + ErrReadPerfConfig = "failed to read TOML config for performance tests" + ErrUnmarshalPerfConfig = "failed to unmarshal TOML config for performance tests" + ErrDeviationShouldBeLessThanOriginal = "`RandomnessRequestCountPerRequestDeviation` should be less than `RandomnessRequestCountPerRequest`" ) type PerformanceConfig struct { - Soak *Soak `toml:"Soak"` - Load *Load `toml:"Load"` - SoakVolume *SoakVolume `toml:"SoakVolume"` - LoadVolume *LoadVolume `toml:"LoadVolume"` - Common *Common `toml:"Common"` + Soak *Soak `toml:"Soak"` + Load *Load `toml:"Load"` + Stress *Stress `toml:"Stress"` + Spike *Spike `toml:"Spike"` + + Common *Common `toml:"Common"` + ExistingEnvConfig *ExistingEnvConfig `toml:"ExistingEnvConfig"` + NewEnvConfig *NewEnvConfig `toml:"NewEnvConfig"` } -type Common struct { +type ExistingEnvConfig struct { + CoordinatorAddress string `toml:"coordinator_address"` + ConsumerAddress string `toml:"consumer_address"` + LinkAddress string `toml:"link_address"` + SubID uint64 `toml:"sub_id"` + KeyHash string `toml:"key_hash"` Funding + CreateFundSubsAndAddConsumers bool `toml:"create_fund_subs_and_add_consumers"` + NodeSendingKeys []string `toml:"node_sending_keys"` +} + +type NewEnvConfig struct { + Funding +} + +type Common struct { + MinimumConfirmations uint16 `toml:"minimum_confirmations"` + CancelSubsAfterTestRun bool `toml:"cancel_subs_after_test_run"` } type Funding struct { - NodeFunds *big.Float `toml:"node_funds"` - SubFunds *big.Int `toml:"sub_funds"` + SubFunding + NodeSendingKeyFunding float64 `toml:"node_sending_key_funding"` + NodeSendingKeyFundingMin float64 `toml:"node_sending_key_funding_min"` } -type Soak struct { - RPS int64 `toml:"rps"` - Duration *models.Duration `toml:"duration"` +type SubFunding struct { + SubFundsLink float64 `toml:"sub_funds_link"` } -type SoakVolume struct { - Products int64 `toml:"products"` - Pace *models.Duration `toml:"pace"` - Duration *models.Duration `toml:"duration"` +type Soak struct { + PerformanceTestConfig } type Load struct { - RPSFrom int64 `toml:"rps_from"` - RPSIncrease int64 `toml:"rps_increase"` - RPSSteps int `toml:"rps_steps"` - Duration *models.Duration `toml:"duration"` + PerformanceTestConfig +} + +type Stress struct { + PerformanceTestConfig } -type LoadVolume struct { - ProductsFrom int64 `toml:"products_from"` - ProductsIncrease int64 `toml:"products_increase"` - ProductsSteps int `toml:"products_steps"` - Pace *models.Duration `toml:"pace"` - Duration *models.Duration `toml:"duration"` +type Spike struct { + PerformanceTestConfig +} + +type PerformanceTestConfig struct { + NumberOfSubToCreate int `toml:"number_of_sub_to_create"` + + RPS int64 `toml:"rps"` + //Duration *models.Duration `toml:"duration"` + RateLimitUnitDuration *models.Duration `toml:"rate_limit_unit_duration"` + RandomnessRequestCountPerRequest uint16 `toml:"randomness_request_count_per_request"` + RandomnessRequestCountPerRequestDeviation uint16 `toml:"randomness_request_count_per_request_deviation"` } func ReadConfig() (*PerformanceConfig, error) { var cfg *PerformanceConfig - d, err := os.ReadFile(DefaultConfigFilename) - if err != nil { - return nil, errors.Wrap(err, ErrReadPerfConfig) + rawConfig := os.Getenv("CONFIG") + var d []byte + var err error + if rawConfig == "" { + d, err = os.ReadFile(DefaultConfigFilename) + if err != nil { + return nil, fmt.Errorf("%s, err: %w", ErrReadPerfConfig, err) + } + } else { + d, err = base64.StdEncoding.DecodeString(rawConfig) + if err != nil { + return nil, fmt.Errorf("%s, err: %w", ErrReadPerfConfig, err) + } } err = toml.Unmarshal(d, &cfg) if err != nil { - return nil, errors.Wrap(err, ErrUnmarshalPerfConfig) + return nil, fmt.Errorf("%s, err: %w", ErrUnmarshalPerfConfig, err) } - log.Debug().Interface("PerformanceConfig", cfg).Msg("Parsed performance config") + + if cfg.Soak.RandomnessRequestCountPerRequest <= cfg.Soak.RandomnessRequestCountPerRequestDeviation { + return nil, fmt.Errorf("%s, err: %w", ErrDeviationShouldBeLessThanOriginal, err) + } + + log.Debug().Interface("Config", cfg).Msg("Parsed config") return cfg, nil } + +func SetPerformanceTestConfig(testType string, vrfv2Config *vrfv2_config.VRFV2Config, cfg *PerformanceConfig) { + switch testType { + case SoakTestType: + vrfv2Config.NumberOfSubToCreate = cfg.Soak.NumberOfSubToCreate + vrfv2Config.RPS = cfg.Soak.RPS + vrfv2Config.RateLimitUnitDuration = cfg.Soak.RateLimitUnitDuration.Duration() + vrfv2Config.RandomnessRequestCountPerRequest = cfg.Soak.RandomnessRequestCountPerRequest + vrfv2Config.RandomnessRequestCountPerRequestDeviation = cfg.Soak.RandomnessRequestCountPerRequestDeviation + case LoadTestType: + vrfv2Config.NumberOfSubToCreate = cfg.Load.NumberOfSubToCreate + vrfv2Config.RPS = cfg.Load.RPS + vrfv2Config.RateLimitUnitDuration = cfg.Load.RateLimitUnitDuration.Duration() + vrfv2Config.RandomnessRequestCountPerRequest = cfg.Load.RandomnessRequestCountPerRequest + vrfv2Config.RandomnessRequestCountPerRequestDeviation = cfg.Load.RandomnessRequestCountPerRequestDeviation + case StressTestType: + vrfv2Config.NumberOfSubToCreate = cfg.Stress.NumberOfSubToCreate + vrfv2Config.RPS = cfg.Stress.RPS + vrfv2Config.RateLimitUnitDuration = cfg.Stress.RateLimitUnitDuration.Duration() + vrfv2Config.RandomnessRequestCountPerRequest = cfg.Stress.RandomnessRequestCountPerRequest + vrfv2Config.RandomnessRequestCountPerRequestDeviation = cfg.Stress.RandomnessRequestCountPerRequestDeviation + case SpikeTestType: + vrfv2Config.NumberOfSubToCreate = cfg.Spike.NumberOfSubToCreate + vrfv2Config.RPS = cfg.Spike.RPS + vrfv2Config.RateLimitUnitDuration = cfg.Spike.RateLimitUnitDuration.Duration() + vrfv2Config.RandomnessRequestCountPerRequest = cfg.Spike.RandomnessRequestCountPerRequest + vrfv2Config.RandomnessRequestCountPerRequestDeviation = cfg.Spike.RandomnessRequestCountPerRequestDeviation + } +} diff --git a/integration-tests/load/vrfv2/config.toml b/integration-tests/load/vrfv2/config.toml index 8917db88cc2..6f54d1ec998 100644 --- a/integration-tests/load/vrfv2/config.toml +++ b/integration-tests/load/vrfv2/config.toml @@ -1,27 +1,58 @@ -# testing one product (jobs + contracts) by varying RPS + +[Common] +minimum_confirmations = 3 +cancel_subs_after_test_run = true + +[NewEnvConfig] +sub_funds_link = 1000 +node_sending_key_funding = 1000 + +[ExistingEnvConfig] +coordinator_address = "" +consumer_address = "" +sub_id = 1 +key_hash = "" +create_fund_subs_and_add_consumers = true +link_address = "" +sub_funds_link = 10 +node_sending_key_funding_min = 1 +node_sending_keys = [ + "", + "", + "", + "", + "", + "", +] + +# 10 RPM - 1 tx request with 1 rand request in each tx every 6 seconds [Soak] +rate_limit_unit_duration = "6s" rps = 1 -duration = "3m" +randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 +# approx 60 RPM - 1 tx request with 3 rand requests in each tx every 3 seconds [Load] -rps_from = 1 -rps_increase = 1 -rps_steps = 10 -duration = "3m" - -# testing multiple products (jobs + contracts) by varying instances, deploying more of the same type with stable RPS for each product -[SoakVolume] -products = 5 -pace = "1s" -duration = "3m" +rate_limit_unit_duration = "3s" +rps = 1 +randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 -[LoadVolume] -products_from = 1 -products_increase = 1 -products_steps = 10 -pace = "1s" -duration = "3m" +# approx 540 RPM - 3 tx requests per second with 4 rand requests in each tx +[Stress] +rate_limit_unit_duration = "1s" +rps = 3 +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 -[Common] -node_funds = 10 -sub_funds = 100 \ No newline at end of file +# approx 150 RPM - 1 tx request with 150 rand requests in each tx every 60 seconds +[Spike] +rate_limit_unit_duration = "1m" +rps = 1 +randomness_request_count_per_request = 150 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 diff --git a/integration-tests/load/vrfv2/gun.go b/integration-tests/load/vrfv2/gun.go index d6a8977738b..8a5eb3c66de 100644 --- a/integration-tests/load/vrfv2/gun.go +++ b/integration-tests/load/vrfv2/gun.go @@ -1,37 +1,78 @@ package loadvrfv2 import ( - "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions" - vrfConst "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_constants" + "math/rand" + + "github.com/rs/zerolog" + "github.com/smartcontractkit/wasp" + + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions" + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_config" ) /* SingleHashGun is a gun that constantly requests randomness for one feed */ type SingleHashGun struct { - contracts *vrfv2_actions.VRFV2Contracts - keyHash [32]byte + contracts *vrfv2_actions.VRFV2Contracts + keyHash [32]byte + subIDs []uint64 + vrfv2Config vrfv2_config.VRFV2Config + logger zerolog.Logger } -func SingleFeedGun(contracts *vrfv2_actions.VRFV2Contracts, keyHash [32]byte) *SingleHashGun { +func NewSingleHashGun( + contracts *vrfv2_actions.VRFV2Contracts, + keyHash [32]byte, + subIDs []uint64, + vrfv2Config vrfv2_config.VRFV2Config, + logger zerolog.Logger, +) *SingleHashGun { return &SingleHashGun{ - contracts: contracts, - keyHash: keyHash, + contracts: contracts, + keyHash: keyHash, + subIDs: subIDs, + vrfv2Config: vrfv2Config, + logger: logger, } } // Call implements example gun call, assertions on response bodies should be done here -func (m *SingleHashGun) Call(l *wasp.Generator) *wasp.CallResult { - err := m.contracts.LoadTestConsumer.RequestRandomness( - m.keyHash, - vrfConst.SubID, - vrfConst.MinimumConfirmations, - vrfConst.CallbackGasLimit, - vrfConst.NumberOfWords, - vrfConst.RandomnessRequestCountPerRequest, +func (m *SingleHashGun) Call(_ *wasp.Generator) *wasp.CallResult { + //todo - should work with multiple consumers and consumers having different keyhashes and wallets + + //randomly increase/decrease randomness request count per TX + randomnessRequestCountPerRequest := deviateValue(m.vrfv2Config.RandomnessRequestCountPerRequest, m.vrfv2Config.RandomnessRequestCountPerRequestDeviation) + _, err := vrfv2_actions.RequestRandomnessAndWaitForFulfillment( + //the same consumer is used for all requests and in all subs + m.contracts.LoadTestConsumers[0], + m.contracts.Coordinator, + &vrfv2_actions.VRFV2Data{VRFV2KeyData: vrfv2_actions.VRFV2KeyData{KeyHash: m.keyHash}}, + //randomly pick a subID from pool of subIDs + m.subIDs[randInRange(0, len(m.subIDs)-1)], + randomnessRequestCountPerRequest, + m.vrfv2Config, + m.vrfv2Config.RandomWordsFulfilledEventTimeout, + m.logger, ) if err != nil { return &wasp.CallResult{Error: err.Error(), Failed: true} } return &wasp.CallResult{} } + +func deviateValue(requestCountPerTX uint16, deviation uint16) uint16 { + if randBool() && requestCountPerTX > deviation { + requestCountPerTX -= uint16(randInRange(0, int(deviation))) + } else { + requestCountPerTX += uint16(randInRange(0, int(deviation))) + } + return requestCountPerTX +} + +func randBool() bool { + return rand.Intn(2) == 1 +} +func randInRange(min int, max int) int { + return rand.Intn(max-min+1) + min +} diff --git a/integration-tests/load/vrfv2/onchain_monitoring.go b/integration-tests/load/vrfv2/onchain_monitoring.go index b4503d27fad..55975a7e42f 100644 --- a/integration-tests/load/vrfv2/onchain_monitoring.go +++ b/integration-tests/load/vrfv2/onchain_monitoring.go @@ -2,11 +2,13 @@ package loadvrfv2 import ( "context" - "github.com/rs/zerolog/log" - "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions" - "github.com/smartcontractkit/wasp" "testing" "time" + + "github.com/rs/zerolog/log" + "github.com/smartcontractkit/wasp" + + "github.com/smartcontractkit/chainlink/integration-tests/contracts" ) /* Monitors on-chain stats of LoadConsumer and pushes them to Loki every second */ @@ -18,29 +20,37 @@ const ( ErrLokiPush = "failed to push monitoring metrics to Loki" ) -func MonitorLoadStats(t *testing.T, vrfv2Contracts *vrfv2_actions.VRFV2Contracts, labels map[string]string) { +func MonitorLoadStats(lc *wasp.LokiClient, consumer contracts.VRFv2LoadTestConsumer, labels map[string]string) { go func() { - updatedLabels := make(map[string]string) - for k, v := range labels { - updatedLabels[k] = v - } - updatedLabels["type"] = LokiTypeLabel - updatedLabels["go_test_name"] = t.Name() - updatedLabels["gen_name"] = "performance" - lc, err := wasp.NewLokiClient(wasp.NewEnvLokiConfig()) - if err != nil { - log.Error().Err(err).Msg(ErrLokiClient) - return - } for { time.Sleep(1 * time.Second) - metrics, err := vrfv2Contracts.LoadTestConsumer.GetLoadTestMetrics(context.Background()) - if err != nil { - log.Error().Err(err).Msg(ErrMetrics) - } - if err := lc.HandleStruct(wasp.LabelsMapToModel(updatedLabels), time.Now(), metrics); err != nil { - log.Error().Err(err).Msg(ErrLokiPush) - } + metrics := GetLoadTestMetrics(consumer) + SendMetricsToLoki(metrics, lc, labels) } }() } + +func UpdateLabels(labels map[string]string, t *testing.T) map[string]string { + updatedLabels := make(map[string]string) + for k, v := range labels { + updatedLabels[k] = v + } + updatedLabels["type"] = LokiTypeLabel + updatedLabels["go_test_name"] = t.Name() + updatedLabels["gen_name"] = "performance" + return updatedLabels +} + +func SendMetricsToLoki(metrics *contracts.VRFLoadTestMetrics, lc *wasp.LokiClient, updatedLabels map[string]string) { + if err := lc.HandleStruct(wasp.LabelsMapToModel(updatedLabels), time.Now(), metrics); err != nil { + log.Error().Err(err).Msg(ErrLokiPush) + } +} + +func GetLoadTestMetrics(consumer contracts.VRFv2LoadTestConsumer) *contracts.VRFLoadTestMetrics { + metrics, err := consumer.GetLoadTestMetrics(context.Background()) + if err != nil { + log.Error().Err(err).Msg(ErrMetrics) + } + return metrics +} diff --git a/integration-tests/load/vrfv2/vrfv2_test.go b/integration-tests/load/vrfv2/vrfv2_test.go index a9fb80a72ad..ae109f75e28 100644 --- a/integration-tests/load/vrfv2/vrfv2_test.go +++ b/integration-tests/load/vrfv2/vrfv2_test.go @@ -1,93 +1,349 @@ package loadvrfv2 import ( + "context" + "math/big" + "os" + "sync" "testing" + "time" - "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions" + "github.com/ethereum/go-ethereum/common" + "github.com/kelseyhightower/envconfig" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/smartcontractkit/wasp" + + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/conversions" + "github.com/smartcontractkit/chainlink/integration-tests/actions" + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_config" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" + "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" + "github.com/smartcontractkit/chainlink/integration-tests/testreporters" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions" ) -func TestVRFV2Load(t *testing.T) { +var ( + env *test_env.CLClusterTestEnv + vrfv2Contracts *vrfv2_actions.VRFV2Contracts + vrfv2Data *vrfv2_actions.VRFV2Data + subIDs []uint64 + eoaWalletAddress string + + labels = map[string]string{ + "branch": "vrfv2_healthcheck", + "commit": "vrfv2_healthcheck", + } + + testType = os.Getenv("TEST_TYPE") +) + +func TestVRFV2Performance(t *testing.T) { cfg, err := ReadConfig() require.NoError(t, err) - env, vrfv2Contracts, key, err := vrfv2_actions.SetupLocalLoadTestEnv(cfg.Common.NodeFunds, cfg.Common.SubFunds) + var vrfv2Config vrfv2_config.VRFV2Config + err = envconfig.Process("VRFV2", &vrfv2Config) require.NoError(t, err) - labels := map[string]string{ - "branch": "vrfv2_healthcheck", - "commit": "vrfv2_healthcheck", + testReporter := &testreporters.VRFV2TestReporter{} + + SetPerformanceTestConfig(testType, &vrfv2Config, cfg) + + l := logging.GetTestLogger(t) + //todo: temporary solution with envconfig and toml config until VRF-662 is implemented + vrfv2Config.MinimumConfirmations = cfg.Common.MinimumConfirmations + + lokiConfig := wasp.NewEnvLokiConfig() + lc, err := wasp.NewLokiClient(lokiConfig) + if err != nil { + l.Error().Err(err).Msg(ErrLokiClient) + return } - singleFeedConfig := &wasp.Config{ - T: t, - LoadType: wasp.RPS, - GenName: "gun", - Gun: SingleFeedGun(vrfv2Contracts, key), - Labels: labels, - LokiConfig: wasp.NewEnvLokiConfig(), + updatedLabels := UpdateLabels(labels, t) + + l.Info(). + Str("Test Type", testType). + Str("Test Duration", vrfv2Config.TestDuration.Truncate(time.Second).String()). + Int64("RPS", vrfv2Config.RPS). + Str("RateLimitUnitDuration", vrfv2Config.RateLimitUnitDuration.String()). + Uint16("RandomnessRequestCountPerRequest", vrfv2Config.RandomnessRequestCountPerRequest). + Uint16("RandomnessRequestCountPerRequestDeviation", vrfv2Config.RandomnessRequestCountPerRequestDeviation). + Bool("UseExistingEnv", vrfv2Config.UseExistingEnv). + Msg("Performance Test Configuration") + + if vrfv2Config.UseExistingEnv { + //todo: temporary solution with envconfig and toml config until VRF-662 is implemented + vrfv2Config.CoordinatorAddress = cfg.ExistingEnvConfig.CoordinatorAddress + vrfv2Config.ConsumerAddress = cfg.ExistingEnvConfig.ConsumerAddress + vrfv2Config.LinkAddress = cfg.ExistingEnvConfig.LinkAddress + vrfv2Config.SubscriptionFundingAmountLink = cfg.ExistingEnvConfig.SubFunding.SubFundsLink + vrfv2Config.SubID = cfg.ExistingEnvConfig.SubID + vrfv2Config.KeyHash = cfg.ExistingEnvConfig.KeyHash + + env, err = test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithCustomCleanup( + func() { + teardown(t, vrfv2Contracts.LoadTestConsumers[0], lc, updatedLabels, testReporter, testType, vrfv2Config) + if env.EVMClient.NetworkSimulated() { + l.Info(). + Str("Network Name", env.EVMClient.GetNetworkName()). + Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") + } else { + if cfg.Common.CancelSubsAfterTestRun { + //cancel subs and return funds to sub owner + cancelSubsAndReturnFunds(subIDs, l) + } + } + }). + Build() + + require.NoError(t, err, "error creating test env") + + coordinator, err := env.ContractLoader.LoadVRFCoordinatorV2(vrfv2Config.CoordinatorAddress) + require.NoError(t, err) + + var consumers []contracts.VRFv2LoadTestConsumer + if cfg.ExistingEnvConfig.CreateFundSubsAndAddConsumers { + linkToken, err := env.ContractLoader.LoadLINKToken(vrfv2Config.LinkAddress) + require.NoError(t, err) + consumers, err = vrfv2_actions.DeployVRFV2Consumers(env.ContractDeployer, coordinator, 1) + require.NoError(t, err) + subIDs, err = vrfv2_actions.CreateFundSubsAndAddConsumers( + env, + vrfv2Config, + linkToken, + coordinator, + consumers, + vrfv2Config.NumberOfSubToCreate, + ) + require.NoError(t, err) + } else { + consumer, err := env.ContractLoader.LoadVRFv2LoadTestConsumer(vrfv2Config.ConsumerAddress) + require.NoError(t, err) + consumers = append(consumers, consumer) + subIDs = append(subIDs, vrfv2Config.SubID) + } + + err = FundNodesIfNeeded(cfg, env.EVMClient, l) + require.NoError(t, err) + + vrfv2Contracts = &vrfv2_actions.VRFV2Contracts{ + Coordinator: coordinator, + LoadTestConsumers: consumers, + BHS: nil, + } + + vrfv2Data = &vrfv2_actions.VRFV2Data{ + VRFV2KeyData: vrfv2_actions.VRFV2KeyData{ + VRFKey: nil, + EncodedProvingKey: [2]*big.Int{}, + KeyHash: common.HexToHash(vrfv2Config.KeyHash), + }, + VRFJob: nil, + PrimaryEthAddress: "", + ChainID: nil, + } + + } else { + //todo: temporary solution with envconfig and toml config until VRF-662 is implemented + vrfv2Config.ChainlinkNodeFunding = cfg.NewEnvConfig.NodeSendingKeyFunding + vrfv2Config.SubscriptionFundingAmountLink = cfg.NewEnvConfig.Funding.SubFundsLink + env, err = test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithCLNodes(1). + WithFunding(big.NewFloat(vrfv2Config.ChainlinkNodeFunding)). + WithCustomCleanup( + func() { + teardown(t, vrfv2Contracts.LoadTestConsumers[0], lc, updatedLabels, testReporter, testType, vrfv2Config) + + if env.EVMClient.NetworkSimulated() { + l.Info(). + Str("Network Name", env.EVMClient.GetNetworkName()). + Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") + } else { + if cfg.Common.CancelSubsAfterTestRun { + //cancel subs and return funds to sub owner + cancelSubsAndReturnFunds(subIDs, l) + } + } + if err := env.Cleanup(); err != nil { + l.Error().Err(err).Msg("Error cleaning up test environment") + } + }). + WithLogWatcher(). + Build() + + require.NoError(t, err, "error creating test env") + + env.ParallelTransactions(true) + + mockETHLinkFeed, err := actions.DeployMockETHLinkFeed(env.ContractDeployer, big.NewInt(vrfv2Config.LinkNativeFeedResponse)) + require.NoError(t, err, "error deploying mock ETH/LINK feed") + + linkToken, err := actions.DeployLINKToken(env.ContractDeployer) + require.NoError(t, err, "error deploying LINK contract") + + vrfv2Contracts, subIDs, vrfv2Data, err = vrfv2_actions.SetupVRFV2Environment( + env, + vrfv2Config, + linkToken, + mockETHLinkFeed, + //register proving key against EOA address in order to return funds to this address + env.EVMClient.GetDefaultWallet().Address(), + 0, + 1, + vrfv2Config.NumberOfSubToCreate, + l, + ) + require.NoError(t, err, "error setting up VRF v2 env") } + eoaWalletAddress = env.EVMClient.GetDefaultWallet().Address() - multiFeedConfig := &wasp.Config{ - T: t, - LoadType: wasp.VU, - GenName: "vu", - VU: NewJobVolumeVU(cfg.SoakVolume.Pace.Duration(), 1, env.ClCluster.NodeAPIs(), env.EVMClient, vrfv2Contracts), - Labels: labels, - LokiConfig: wasp.NewEnvLokiConfig(), + l.Debug().Int("Number of Subs", len(subIDs)).Msg("Subs involved in the test") + for _, subID := range subIDs { + subscription, err := vrfv2Contracts.Coordinator.GetSubscription(context.Background(), subID) + require.NoError(t, err, "error getting subscription information for subscription %d", subID) + vrfv2_actions.LogSubDetails(l, subscription, subID, vrfv2Contracts.Coordinator) } - MonitorLoadStats(t, vrfv2Contracts, labels) + singleFeedConfig := &wasp.Config{ + T: t, + LoadType: wasp.RPS, + GenName: "gun", + RateLimitUnitDuration: vrfv2Config.RateLimitUnitDuration, + Gun: NewSingleHashGun( + vrfv2Contracts, + vrfv2Data.KeyHash, + subIDs, + vrfv2Config, + l, + ), + Labels: labels, + LokiConfig: lokiConfig, + CallTimeout: 2 * time.Minute, + } + require.Len(t, vrfv2Contracts.LoadTestConsumers, 1, "only one consumer should be created for Load Test") + consumer := vrfv2Contracts.LoadTestConsumers[0] + err = consumer.ResetMetrics() + require.NoError(t, err) + MonitorLoadStats(lc, consumer, updatedLabels) // is our "job" stable at all, no memory leaks, no flaking performance under some RPS? - t.Run("vrfv2 soak test", func(t *testing.T) { - singleFeedConfig.Schedule = wasp.Plain( - cfg.Soak.RPS, - cfg.Soak.Duration.Duration(), - ) - _, err := wasp.NewProfile(). - Add(wasp.NewGenerator(singleFeedConfig)). - Run(true) - require.NoError(t, err) - }) + t.Run("vrfv2 performance test", func(t *testing.T) { - // what are the limits for one "job", figuring out the max/optimal performance params by increasing RPS and varying configuration - t.Run("vrfv2 load test", func(t *testing.T) { - singleFeedConfig.Schedule = wasp.Steps( - cfg.Load.RPSFrom, - cfg.Load.RPSIncrease, - cfg.Load.RPSSteps, - cfg.Load.Duration.Duration(), + singleFeedConfig.Schedule = wasp.Plain( + vrfv2Config.RPS, + vrfv2Config.TestDuration, ) _, err = wasp.NewProfile(). Add(wasp.NewGenerator(singleFeedConfig)). Run(true) require.NoError(t, err) - }) - // how many "jobs" of the same type we can run at once at a stable load with optimal configuration? - t.Run("vrfv2 volume soak test", func(t *testing.T) { - multiFeedConfig.Schedule = wasp.Plain( - cfg.SoakVolume.Products, - cfg.SoakVolume.Duration.Duration(), - ) - _, err = wasp.NewProfile(). - Add(wasp.NewGenerator(multiFeedConfig)). - Run(true) + var wg sync.WaitGroup + wg.Add(1) + //todo - timeout should be configurable depending on the perf test type + requestCount, fulfilmentCount, err := vrfv2_actions.WaitForRequestCountEqualToFulfilmentCount(consumer, 2*time.Minute, &wg) require.NoError(t, err) - }) + wg.Wait() - // what are the limits if we add more and more "jobs/products" of the same type, each "job" have a stable RPS we vary only amount of jobs - t.Run("vrfv2 volume load test", func(t *testing.T) { - multiFeedConfig.Schedule = wasp.Steps( - cfg.LoadVolume.ProductsFrom, - cfg.LoadVolume.ProductsIncrease, - cfg.LoadVolume.ProductsSteps, - cfg.LoadVolume.Duration.Duration(), - ) - _, err = wasp.NewProfile(). - Add(wasp.NewGenerator(multiFeedConfig)). - Run(true) - require.NoError(t, err) + l.Info(). + Interface("Request Count", requestCount). + Interface("Fulfilment Count", fulfilmentCount). + Msg("Final Request/Fulfilment Stats") }) + +} + +func cancelSubsAndReturnFunds(subIDs []uint64, l zerolog.Logger) { + for _, subID := range subIDs { + l.Info(). + Uint64("Returning funds from SubID", subID). + Str("Returning funds to", eoaWalletAddress). + Msg("Canceling subscription and returning funds to subscription owner") + pendingRequestsExist, err := vrfv2Contracts.Coordinator.PendingRequestsExist(context.Background(), subID) + if err != nil { + l.Error().Err(err).Msg("Error checking if pending requests exist") + } + if !pendingRequestsExist { + _, err := vrfv2Contracts.Coordinator.CancelSubscription(subID, common.HexToAddress(eoaWalletAddress)) + if err != nil { + l.Error().Err(err).Msg("Error canceling subscription") + } + } else { + l.Error().Uint64("Sub ID", subID).Msg("Pending requests exist for subscription, cannot cancel subscription and return funds") + } + } +} + +func FundNodesIfNeeded(cfg *PerformanceConfig, client blockchain.EVMClient, l zerolog.Logger) error { + if cfg.ExistingEnvConfig.NodeSendingKeyFundingMin > 0 { + for _, sendingKey := range cfg.ExistingEnvConfig.NodeSendingKeys { + address := common.HexToAddress(sendingKey) + sendingKeyBalance, err := client.BalanceAt(context.Background(), address) + if err != nil { + return err + } + fundingAtLeast := conversions.EtherToWei(big.NewFloat(cfg.ExistingEnvConfig.NodeSendingKeyFundingMin)) + fundingToSendWei := new(big.Int).Sub(fundingAtLeast, sendingKeyBalance) + fundingToSendEth := conversions.WeiToEther(fundingToSendWei) + if fundingToSendWei.Cmp(big.NewInt(0)) == 1 { + l.Info(). + Str("Sending Key", sendingKey). + Str("Sending Key Current Balance", sendingKeyBalance.String()). + Str("Should have at least", fundingAtLeast.String()). + Str("Funding Amount in ETH", fundingToSendEth.String()). + Msg("Funding Node's Sending Key") + err := actions.FundAddress(client, sendingKey, fundingToSendEth) + if err != nil { + return err + } + } else { + l.Info(). + Str("Sending Key", sendingKey). + Str("Sending Key Current Balance", sendingKeyBalance.String()). + Str("Should have at least", fundingAtLeast.String()). + Msg("Skipping Node's Sending Key funding as it has enough funds") + } + } + } + return nil +} + +func teardown( + t *testing.T, + consumer contracts.VRFv2LoadTestConsumer, + lc *wasp.LokiClient, + updatedLabels map[string]string, + testReporter *testreporters.VRFV2TestReporter, + testType string, + vrfv2Config vrfv2_config.VRFV2Config, +) { + //send final results to Loki + metrics := GetLoadTestMetrics(consumer) + SendMetricsToLoki(metrics, lc, updatedLabels) + //set report data for Slack notification + testReporter.SetReportData( + testType, + metrics.RequestCount, + metrics.FulfilmentCount, + metrics.AverageFulfillmentInMillions, + metrics.SlowestFulfillment, + metrics.FastestFulfillment, + vrfv2Config, + ) + + // send Slack notification + err := testReporter.SendSlackNotification(t, nil) + if err != nil { + log.Warn().Err(err).Msg("Error sending Slack notification") + } } diff --git a/integration-tests/load/vrfv2/vu.go b/integration-tests/load/vrfv2/vu.go deleted file mode 100644 index df05a9168e2..00000000000 --- a/integration-tests/load/vrfv2/vu.go +++ /dev/null @@ -1,92 +0,0 @@ -package loadvrfv2 - -import ( - "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-testing-framework/blockchain" - "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions" - vrfConst "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_constants" - "github.com/smartcontractkit/chainlink/integration-tests/client" - "github.com/smartcontractkit/wasp" - "time" -) - -/* JobVolumeVU is a "virtual user" that creates a VRFv2 job and constantly requesting new randomness only for this job instance */ - -type JobVolumeVU struct { - pace time.Duration - minIncomingConfirmations uint16 - nodes []*client.ChainlinkClient - bc blockchain.EVMClient - contracts *vrfv2_actions.VRFV2Contracts - jobs []vrfv2_actions.VRFV2JobInfo - keyHash [32]byte - stop chan struct{} -} - -func NewJobVolumeVU( - pace time.Duration, - confirmations uint16, - nodes []*client.ChainlinkClient, - bc blockchain.EVMClient, - contracts *vrfv2_actions.VRFV2Contracts, -) *JobVolumeVU { - return &JobVolumeVU{ - pace: pace, - minIncomingConfirmations: confirmations, - nodes: nodes, - bc: bc, - contracts: contracts, - stop: make(chan struct{}, 1), - } -} - -func (m *JobVolumeVU) Clone(_ *wasp.Generator) wasp.VirtualUser { - return &JobVolumeVU{ - pace: m.pace, - minIncomingConfirmations: m.minIncomingConfirmations, - nodes: m.nodes, - bc: m.bc, - contracts: m.contracts, - stop: make(chan struct{}, 1), - } -} - -func (m *JobVolumeVU) Setup(_ *wasp.Generator) error { - jobs, err := vrfv2_actions.CreateVRFV2Jobs(m.nodes, m.contracts.Coordinator, m.bc, m.minIncomingConfirmations) - if err != nil { - return errors.Wrap(err, "failed to create VRFv2 jobs in setup") - } - m.jobs = jobs - m.keyHash = jobs[0].KeyHash - return nil -} - -func (m *JobVolumeVU) Teardown(_ *wasp.Generator) error { - return nil -} - -func (m *JobVolumeVU) Call(l *wasp.Generator) { - time.Sleep(m.pace) - tn := time.Now() - err := m.contracts.LoadTestConsumer.RequestRandomness( - m.keyHash, - vrfConst.SubID, - vrfConst.MinimumConfirmations, - vrfConst.CallbackGasLimit, - vrfConst.NumberOfWords, - vrfConst.RandomnessRequestCountPerRequest, - ) - if err != nil { - l.ResponsesChan <- &wasp.CallResult{Duration: time.Since(tn), Error: err.Error(), Failed: true} - return - } - l.ResponsesChan <- &wasp.CallResult{Duration: time.Since(tn)} -} - -func (m *JobVolumeVU) Stop(_ *wasp.Generator) { - m.stop <- struct{}{} -} - -func (m *JobVolumeVU) StopChan() chan struct{} { - return m.stop -} diff --git a/integration-tests/load/vrfv2plus/cmd/dashboard.go b/integration-tests/load/vrfv2plus/cmd/dashboard.go index 9a0ba682a18..049ee9ff2e9 100644 --- a/integration-tests/load/vrfv2plus/cmd/dashboard.go +++ b/integration-tests/load/vrfv2plus/cmd/dashboard.go @@ -1,6 +1,8 @@ package main import ( + "os" + "github.com/K-Phoen/grabana/dashboard" "github.com/K-Phoen/grabana/logs" "github.com/K-Phoen/grabana/row" @@ -8,7 +10,6 @@ import ( "github.com/K-Phoen/grabana/timeseries" "github.com/K-Phoen/grabana/timeseries/axis" "github.com/smartcontractkit/wasp" - "os" ) func main() { diff --git a/integration-tests/load/vrfv2plus/config.go b/integration-tests/load/vrfv2plus/config.go index 5f3babfeab0..43bcf44d044 100644 --- a/integration-tests/load/vrfv2plus/config.go +++ b/integration-tests/load/vrfv2plus/config.go @@ -2,16 +2,22 @@ package loadvrfv2plus import ( "encoding/base64" + "fmt" + "os" + "github.com/pelletier/go-toml/v2" - "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2plus/vrfv2plus_config" "github.com/smartcontractkit/chainlink/v2/core/store/models" - "os" ) const ( DefaultConfigFilename = "config.toml" + SoakTestType = "Soak" + LoadTestType = "Load" + StressTestType = "Stress" + SpikeTestType = "Spike" ErrReadPerfConfig = "failed to read TOML config for performance tests" ErrUnmarshalPerfConfig = "failed to unmarshal TOML config for performance tests" @@ -32,23 +38,32 @@ type PerformanceConfig struct { type ExistingEnvConfig struct { CoordinatorAddress string `toml:"coordinator_address"` ConsumerAddress string `toml:"consumer_address"` + LinkAddress string `toml:"link_address"` SubID string `toml:"sub_id"` KeyHash string `toml:"key_hash"` + Funding + CreateFundSubsAndAddConsumers bool `toml:"create_fund_subs_and_add_consumers"` + NodeSendingKeys []string `toml:"node_sending_keys"` } type NewEnvConfig struct { Funding - NumberOfSubToCreate int `toml:"number_of_sub_to_create"` } type Common struct { - MinimumConfirmations uint16 `toml:"minimum_confirmations"` + MinimumConfirmations uint16 `toml:"minimum_confirmations"` + CancelSubsAfterTestRun bool `toml:"cancel_subs_after_test_run"` } type Funding struct { - NodeFunds float64 `toml:"node_funds"` - SubFundsLink int64 `toml:"sub_funds_link"` - SubFundsNative int64 `toml:"sub_funds_native"` + SubFunding + NodeSendingKeyFunding float64 `toml:"node_sending_key_funding"` + NodeSendingKeyFundingMin float64 `toml:"node_sending_key_funding_min"` +} + +type SubFunding struct { + SubFundsLink float64 `toml:"sub_funds_link"` + SubFundsNative float64 `toml:"sub_funds_native"` } type Soak struct { @@ -68,6 +83,8 @@ type Spike struct { } type PerformanceTestConfig struct { + NumberOfSubToCreate int `toml:"number_of_sub_to_create"` + RPS int64 `toml:"rps"` //Duration *models.Duration `toml:"duration"` RateLimitUnitDuration *models.Duration `toml:"rate_limit_unit_duration"` @@ -83,42 +100,49 @@ func ReadConfig() (*PerformanceConfig, error) { if rawConfig == "" { d, err = os.ReadFile(DefaultConfigFilename) if err != nil { - return nil, errors.Wrap(err, ErrReadPerfConfig) + return nil, fmt.Errorf("%s, err: %w", ErrReadPerfConfig, err) } } else { d, err = base64.StdEncoding.DecodeString(rawConfig) + if err != nil { + return nil, fmt.Errorf("%s, err: %w", ErrReadPerfConfig, err) + } } err = toml.Unmarshal(d, &cfg) if err != nil { - return nil, errors.Wrap(err, ErrUnmarshalPerfConfig) + return nil, fmt.Errorf("%s, err: %w", ErrUnmarshalPerfConfig, err) } if cfg.Soak.RandomnessRequestCountPerRequest <= cfg.Soak.RandomnessRequestCountPerRequestDeviation { - return nil, errors.Wrap(err, ErrDeviationShouldBeLessThanOriginal) + return nil, fmt.Errorf("%s, err: %w", ErrDeviationShouldBeLessThanOriginal, err) } log.Debug().Interface("Config", cfg).Msg("Parsed config") return cfg, nil } -func SetPerformanceTestConfig(vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, cfg *PerformanceConfig) { - switch os.Getenv("TEST_TYPE") { - case "Soak": +func SetPerformanceTestConfig(testType string, vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, cfg *PerformanceConfig) { + switch testType { + case SoakTestType: + vrfv2PlusConfig.NumberOfSubToCreate = cfg.Soak.NumberOfSubToCreate vrfv2PlusConfig.RPS = cfg.Soak.RPS vrfv2PlusConfig.RateLimitUnitDuration = cfg.Soak.RateLimitUnitDuration.Duration() vrfv2PlusConfig.RandomnessRequestCountPerRequest = cfg.Soak.RandomnessRequestCountPerRequest vrfv2PlusConfig.RandomnessRequestCountPerRequestDeviation = cfg.Soak.RandomnessRequestCountPerRequestDeviation - case "Load": + case LoadTestType: + vrfv2PlusConfig.NumberOfSubToCreate = cfg.Load.NumberOfSubToCreate vrfv2PlusConfig.RPS = cfg.Load.RPS vrfv2PlusConfig.RateLimitUnitDuration = cfg.Load.RateLimitUnitDuration.Duration() vrfv2PlusConfig.RandomnessRequestCountPerRequest = cfg.Load.RandomnessRequestCountPerRequest vrfv2PlusConfig.RandomnessRequestCountPerRequestDeviation = cfg.Load.RandomnessRequestCountPerRequestDeviation - case "Stress": + case StressTestType: + vrfv2PlusConfig.NumberOfSubToCreate = cfg.Stress.NumberOfSubToCreate vrfv2PlusConfig.RPS = cfg.Stress.RPS vrfv2PlusConfig.RateLimitUnitDuration = cfg.Stress.RateLimitUnitDuration.Duration() vrfv2PlusConfig.RandomnessRequestCountPerRequest = cfg.Stress.RandomnessRequestCountPerRequest vrfv2PlusConfig.RandomnessRequestCountPerRequestDeviation = cfg.Stress.RandomnessRequestCountPerRequestDeviation - case "Spike": + case SpikeTestType: + vrfv2PlusConfig.NumberOfSubToCreate = cfg.Spike.NumberOfSubToCreate vrfv2PlusConfig.RPS = cfg.Spike.RPS vrfv2PlusConfig.RateLimitUnitDuration = cfg.Spike.RateLimitUnitDuration.Duration() vrfv2PlusConfig.RandomnessRequestCountPerRequest = cfg.Spike.RandomnessRequestCountPerRequest diff --git a/integration-tests/load/vrfv2plus/config.toml b/integration-tests/load/vrfv2plus/config.toml index 1208423dc0d..8ccf60c6d80 100644 --- a/integration-tests/load/vrfv2plus/config.toml +++ b/integration-tests/load/vrfv2plus/config.toml @@ -1,19 +1,31 @@ [Common] minimum_confirmations = 3 +cancel_subs_after_test_run = true [NewEnvConfig] -sub_funds_link = 1000 -sub_funds_native = 1000 +sub_funds_link = 1 +sub_funds_native = 1 node_funds = 10 -number_of_sub_to_create = 10 +node_sending_key_funding = 1000 [ExistingEnvConfig] -coordinator_address = "0x4931Ce2e341398c8eD8A5D0F6ADb920476D6DaBb" -consumer_address = "0x087F232165D9bA1A602f148025e5D0666953F64a" -sub_id = "52116875585187328970776211988181422347535732407068188096422095950800466618218" -key_hash = "0x4c422465ed6a06cfc84575a5437fef7b9dc6263133f648afbe6ae7b2c694d3b3" - +coordinator_address = "" +consumer_address = "" +sub_id = 1 +key_hash = "" +create_fund_subs_and_add_consumers = true +link_address = "" +sub_funds_link = 10 +node_sending_key_funding_min = 1 +node_sending_keys = [ + "", + "", + "", + "", + "", + "", +] # 10 RPM - 1 tx request with 1 rand request in each tx every 6 seconds [Soak] @@ -21,20 +33,23 @@ rate_limit_unit_duration = "6s" rps = 1 randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 -# approx 60 RPM - 1 tx request with 4 rand requests in each tx every 3 seconds +# approx 60 RPM - 1 tx request with 3 rand requests in each tx every 3 seconds [Load] rate_limit_unit_duration = "3s" rps = 1 randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 # approx 540 RPM - 3 tx requests per second with 4 rand requests in each tx [Stress] -rate_limit_unit_duration = "0" +rate_limit_unit_duration = "1s" rps = 3 randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 # approx 150 RPM - 1 tx request with 150 rand requests in each tx every 60 seconds [Spike] @@ -42,3 +57,4 @@ rate_limit_unit_duration = "1m" rps = 1 randomness_request_count_per_request = 150 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 diff --git a/integration-tests/load/vrfv2plus/gun.go b/integration-tests/load/vrfv2plus/gun.go index c9947fa32f5..8ab278b73e9 100644 --- a/integration-tests/load/vrfv2plus/gun.go +++ b/integration-tests/load/vrfv2plus/gun.go @@ -1,12 +1,14 @@ package loadvrfv2plus import ( + "math/big" + "math/rand" + "github.com/rs/zerolog" + "github.com/smartcontractkit/wasp" + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2plus" "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2plus/vrfv2plus_config" - "github.com/smartcontractkit/wasp" - "math/big" - "math/rand" ) /* SingleHashGun is a gun that constantly requests randomness for one feed */ @@ -15,7 +17,7 @@ type SingleHashGun struct { contracts *vrfv2plus.VRFV2_5Contracts keyHash [32]byte subIDs []*big.Int - vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig logger zerolog.Logger } @@ -23,7 +25,7 @@ func NewSingleHashGun( contracts *vrfv2plus.VRFV2_5Contracts, keyHash [32]byte, subIDs []*big.Int, - vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, logger zerolog.Logger, ) *SingleHashGun { return &SingleHashGun{ @@ -36,7 +38,7 @@ func NewSingleHashGun( } // Call implements example gun call, assertions on response bodies should be done here -func (m *SingleHashGun) Call(l *wasp.Generator) *wasp.CallResult { +func (m *SingleHashGun) Call(_ *wasp.Generator) *wasp.CallResult { //todo - should work with multiple consumers and consumers having different keyhashes and wallets //randomly increase/decrease randomness request count per TX @@ -52,6 +54,7 @@ func (m *SingleHashGun) Call(l *wasp.Generator) *wasp.CallResult { randBool(), randomnessRequestCountPerRequest, m.vrfv2PlusConfig, + m.vrfv2PlusConfig.RandomWordsFulfilledEventTimeout, m.logger, ) if err != nil { diff --git a/integration-tests/load/vrfv2plus/onchain_monitoring.go b/integration-tests/load/vrfv2plus/onchain_monitoring.go index 0ae27fe6be0..c911546af0c 100644 --- a/integration-tests/load/vrfv2plus/onchain_monitoring.go +++ b/integration-tests/load/vrfv2plus/onchain_monitoring.go @@ -2,11 +2,13 @@ package loadvrfv2plus import ( "context" - "github.com/rs/zerolog/log" - "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2plus" - "github.com/smartcontractkit/wasp" "testing" "time" + + "github.com/rs/zerolog/log" + "github.com/smartcontractkit/wasp" + + "github.com/smartcontractkit/chainlink/integration-tests/contracts" ) /* Monitors on-chain stats of LoadConsumer and pushes them to Loki every second */ @@ -18,11 +20,12 @@ const ( ErrLokiPush = "failed to push monitoring metrics to Loki" ) -func MonitorLoadStats(lc *wasp.LokiClient, vrfv2PlusContracts *vrfv2plus.VRFV2_5Contracts, labels map[string]string) { +func MonitorLoadStats(lc *wasp.LokiClient, consumer contracts.VRFv2PlusLoadTestConsumer, labels map[string]string) { go func() { for { time.Sleep(1 * time.Second) - SendLoadTestMetricsToLoki(vrfv2PlusContracts, lc, labels) + metrics := GetLoadTestMetrics(consumer) + SendMetricsToLoki(metrics, lc, labels) } }() } @@ -38,13 +41,16 @@ func UpdateLabels(labels map[string]string, t *testing.T) map[string]string { return updatedLabels } -func SendLoadTestMetricsToLoki(vrfv2PlusContracts *vrfv2plus.VRFV2_5Contracts, lc *wasp.LokiClient, updatedLabels map[string]string) { - //todo - should work with multiple consumers and consumers having different keyhashes and wallets - metrics, err := vrfv2PlusContracts.LoadTestConsumers[0].GetLoadTestMetrics(context.Background()) - if err != nil { - log.Error().Err(err).Msg(ErrMetrics) - } +func SendMetricsToLoki(metrics *contracts.VRFLoadTestMetrics, lc *wasp.LokiClient, updatedLabels map[string]string) { if err := lc.HandleStruct(wasp.LabelsMapToModel(updatedLabels), time.Now(), metrics); err != nil { log.Error().Err(err).Msg(ErrLokiPush) } } + +func GetLoadTestMetrics(consumer contracts.VRFv2PlusLoadTestConsumer) *contracts.VRFLoadTestMetrics { + metrics, err := consumer.GetLoadTestMetrics(context.Background()) + if err != nil { + log.Error().Err(err).Msg(ErrMetrics) + } + return metrics +} diff --git a/integration-tests/load/vrfv2plus/vrfv2plus_test.go b/integration-tests/load/vrfv2plus/vrfv2plus_test.go index 5c3ea6e8c6d..4b6728440b3 100644 --- a/integration-tests/load/vrfv2plus/vrfv2plus_test.go +++ b/integration-tests/load/vrfv2plus/vrfv2plus_test.go @@ -2,17 +2,26 @@ package loadvrfv2plus import ( "context" - "github.com/ethereum/go-ethereum/common" - "github.com/kelseyhightower/envconfig" - "github.com/smartcontractkit/chainlink-testing-framework/logging" - "github.com/smartcontractkit/wasp" - "github.com/stretchr/testify/require" "math/big" "os" "sync" "testing" "time" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/kelseyhightower/envconfig" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/smartcontractkit/wasp" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/conversions" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" + "github.com/smartcontractkit/chainlink/integration-tests/testreporters" + "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2plus" "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2plus/vrfv2plus_config" @@ -20,21 +29,47 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" ) -func TestVRFV2PlusLoad(t *testing.T) { +var ( + env *test_env.CLClusterTestEnv + vrfv2PlusContracts *vrfv2plus.VRFV2_5Contracts + vrfv2PlusData *vrfv2plus.VRFV2PlusData + subIDs []*big.Int + eoaWalletAddress string + + labels = map[string]string{ + "branch": "vrfv2Plus_healthcheck", + "commit": "vrfv2Plus_healthcheck", + } + + testType = os.Getenv("TEST_TYPE") +) + +func TestVRFV2PlusPerformance(t *testing.T) { cfg, err := ReadConfig() require.NoError(t, err) var vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig err = envconfig.Process("VRFV2PLUS", &vrfv2PlusConfig) require.NoError(t, err) - SetPerformanceTestConfig(&vrfv2PlusConfig, cfg) + testReporter := &testreporters.VRFV2PlusTestReporter{} + + SetPerformanceTestConfig(testType, &vrfv2PlusConfig, cfg) l := logging.GetTestLogger(t) //todo: temporary solution with envconfig and toml config until VRF-662 is implemented vrfv2PlusConfig.MinimumConfirmations = cfg.Common.MinimumConfirmations + lokiConfig := wasp.NewEnvLokiConfig() + lc, err := wasp.NewLokiClient(lokiConfig) + if err != nil { + l.Error().Err(err).Msg(ErrLokiClient) + return + } + + updatedLabels := UpdateLabels(labels, t) + l.Info(). - Str("Test Type", os.Getenv("TEST_TYPE")). + Str("Test Type", testType). Str("Test Duration", vrfv2PlusConfig.TestDuration.Truncate(time.Second).String()). Int64("RPS", vrfv2PlusConfig.RPS). Str("RateLimitUnitDuration", vrfv2PlusConfig.RateLimitUnitDuration.String()). @@ -43,21 +78,32 @@ func TestVRFV2PlusLoad(t *testing.T) { Bool("UseExistingEnv", vrfv2PlusConfig.UseExistingEnv). Msg("Performance Test Configuration") - var env *test_env.CLClusterTestEnv - var vrfv2PlusContracts *vrfv2plus.VRFV2_5Contracts - var vrfv2PlusData *vrfv2plus.VRFV2PlusData - var subIDs []*big.Int - if vrfv2PlusConfig.UseExistingEnv { //todo: temporary solution with envconfig and toml config until VRF-662 is implemented vrfv2PlusConfig.CoordinatorAddress = cfg.ExistingEnvConfig.CoordinatorAddress vrfv2PlusConfig.ConsumerAddress = cfg.ExistingEnvConfig.ConsumerAddress + vrfv2PlusConfig.LinkAddress = cfg.ExistingEnvConfig.LinkAddress + vrfv2PlusConfig.SubscriptionFundingAmountLink = cfg.ExistingEnvConfig.SubFunding.SubFundsLink + vrfv2PlusConfig.SubscriptionFundingAmountNative = cfg.ExistingEnvConfig.SubFunding.SubFundsNative vrfv2PlusConfig.SubID = cfg.ExistingEnvConfig.SubID vrfv2PlusConfig.KeyHash = cfg.ExistingEnvConfig.KeyHash env, err = test_env.NewCLTestEnvBuilder(). WithTestLogger(t). - WithoutCleanup(). + WithCustomCleanup( + func() { + teardown(t, vrfv2PlusContracts.LoadTestConsumers[0], lc, updatedLabels, testReporter, testType, vrfv2PlusConfig) + if env.EVMClient.NetworkSimulated() { + l.Info(). + Str("Network Name", env.EVMClient.GetNetworkName()). + Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") + } else { + if cfg.Common.CancelSubsAfterTestRun { + //cancel subs and return funds to sub owner + cancelSubsAndReturnFunds(subIDs, l) + } + } + }). Build() require.NoError(t, err, "error creating test env") @@ -65,17 +111,39 @@ func TestVRFV2PlusLoad(t *testing.T) { coordinator, err := env.ContractLoader.LoadVRFCoordinatorV2_5(vrfv2PlusConfig.CoordinatorAddress) require.NoError(t, err) - consumer, err := env.ContractLoader.LoadVRFv2PlusLoadTestConsumer(vrfv2PlusConfig.ConsumerAddress) + var consumers []contracts.VRFv2PlusLoadTestConsumer + if cfg.ExistingEnvConfig.CreateFundSubsAndAddConsumers { + linkToken, err := env.ContractLoader.LoadLINKToken(vrfv2PlusConfig.LinkAddress) + require.NoError(t, err) + consumers, err = vrfv2plus.DeployVRFV2PlusConsumers(env.ContractDeployer, coordinator, 1) + require.NoError(t, err) + subIDs, err = vrfv2plus.CreateFundSubsAndAddConsumers( + env, + vrfv2PlusConfig, + linkToken, + coordinator, + consumers, + vrfv2PlusConfig.NumberOfSubToCreate, + ) + require.NoError(t, err) + } else { + consumer, err := env.ContractLoader.LoadVRFv2PlusLoadTestConsumer(vrfv2PlusConfig.ConsumerAddress) + require.NoError(t, err) + consumers = append(consumers, consumer) + var ok bool + subID, ok := new(big.Int).SetString(vrfv2PlusConfig.SubID, 10) + require.True(t, ok) + subIDs = append(subIDs, subID) + } + + err = FundNodesIfNeeded(cfg, env.EVMClient, l) require.NoError(t, err) vrfv2PlusContracts = &vrfv2plus.VRFV2_5Contracts{ Coordinator: coordinator, - LoadTestConsumers: []contracts.VRFv2PlusLoadTestConsumer{consumer}, + LoadTestConsumers: consumers, BHS: nil, } - var ok bool - subID, ok := new(big.Int).SetString(vrfv2PlusConfig.SubID, 10) - require.True(t, ok) vrfv2PlusData = &vrfv2plus.VRFV2PlusData{ VRFV2PlusKeyData: vrfv2plus.VRFV2PlusKeyData{ @@ -87,19 +155,35 @@ func TestVRFV2PlusLoad(t *testing.T) { PrimaryEthAddress: "", ChainID: nil, } - subIDs = append(subIDs, subID) + } else { //todo: temporary solution with envconfig and toml config until VRF-662 is implemented - vrfv2PlusConfig.ChainlinkNodeFunding = cfg.NewEnvConfig.NodeFunds + vrfv2PlusConfig.ChainlinkNodeFunding = cfg.NewEnvConfig.NodeSendingKeyFunding vrfv2PlusConfig.SubscriptionFundingAmountLink = cfg.NewEnvConfig.Funding.SubFundsLink vrfv2PlusConfig.SubscriptionFundingAmountNative = cfg.NewEnvConfig.Funding.SubFundsNative - numberOfSubToCreate := cfg.NewEnvConfig.NumberOfSubToCreate env, err = test_env.NewCLTestEnvBuilder(). WithTestLogger(t). WithGeth(). WithCLNodes(1). WithFunding(big.NewFloat(vrfv2PlusConfig.ChainlinkNodeFunding)). - WithStandardCleanup(). + WithCustomCleanup( + func() { + teardown(t, vrfv2PlusContracts.LoadTestConsumers[0], lc, updatedLabels, testReporter, testType, vrfv2PlusConfig) + + if env.EVMClient.NetworkSimulated() { + l.Info(). + Str("Network Name", env.EVMClient.GetNetworkName()). + Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") + } else { + if cfg.Common.CancelSubsAfterTestRun { + //cancel subs and return funds to sub owner + cancelSubsAndReturnFunds(subIDs, l) + } + } + if err := env.Cleanup(); err != nil { + l.Error().Err(err).Msg("Error cleaning up test environment") + } + }). WithLogWatcher(). Build() @@ -113,29 +197,29 @@ func TestVRFV2PlusLoad(t *testing.T) { linkToken, err := actions.DeployLINKToken(env.ContractDeployer) require.NoError(t, err, "error deploying LINK contract") - vrfv2PlusContracts, subIDs, vrfv2PlusData, err = vrfv2plus.SetupVRFV2_5Environment(env, &vrfv2PlusConfig, linkToken, mockETHLinkFeed, 1, numberOfSubToCreate) + vrfv2PlusContracts, subIDs, vrfv2PlusData, err = vrfv2plus.SetupVRFV2_5Environment( + env, + vrfv2PlusConfig, + linkToken, + mockETHLinkFeed, + //register proving key against EOA address in order to return funds to this address + env.EVMClient.GetDefaultWallet().Address(), + 0, + 1, + vrfv2PlusConfig.NumberOfSubToCreate, + l, + ) require.NoError(t, err, "error setting up VRF v2_5 env") } + eoaWalletAddress = env.EVMClient.GetDefaultWallet().Address() - l.Debug().Int("Number of Subs", len(subIDs)).Msg("Subs Involved in Load Test") + l.Debug().Int("Number of Subs", len(subIDs)).Msg("Subs involved in the test") for _, subID := range subIDs { - subscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), subID) + subscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subID) require.NoError(t, err, "error getting subscription information for subscription %s", subID.String()) vrfv2plus.LogSubDetails(l, subscription, subID, vrfv2PlusContracts.Coordinator) } - labels := map[string]string{ - "branch": "vrfv2Plus_healthcheck", - "commit": "vrfv2Plus_healthcheck", - } - - lokiConfig := wasp.NewEnvLokiConfig() - lc, err := wasp.NewLokiClient(lokiConfig) - if err != nil { - l.Error().Err(err).Msg(ErrLokiClient) - return - } - singleFeedConfig := &wasp.Config{ T: t, LoadType: wasp.RPS, @@ -145,7 +229,7 @@ func TestVRFV2PlusLoad(t *testing.T) { vrfv2PlusContracts, vrfv2PlusData.KeyHash, subIDs, - &vrfv2PlusConfig, + vrfv2PlusConfig, l, ), Labels: labels, @@ -156,11 +240,10 @@ func TestVRFV2PlusLoad(t *testing.T) { consumer := vrfv2PlusContracts.LoadTestConsumers[0] err = consumer.ResetMetrics() require.NoError(t, err) - updatedLabels := UpdateLabels(labels, t) - MonitorLoadStats(lc, vrfv2PlusContracts, updatedLabels) + MonitorLoadStats(lc, consumer, updatedLabels) // is our "job" stable at all, no memory leaks, no flaking performance under some RPS? - t.Run("vrfv2plus soak test", func(t *testing.T) { + t.Run("vrfv2plus performance test", func(t *testing.T) { singleFeedConfig.Schedule = wasp.Plain( vrfv2PlusConfig.RPS, @@ -172,17 +255,107 @@ func TestVRFV2PlusLoad(t *testing.T) { require.NoError(t, err) var wg sync.WaitGroup - wg.Add(1) - requestCount, fulfilmentCount, err := vrfv2plus.WaitForRequestCountEqualToFulfilmentCount(consumer, 30*time.Second, &wg) + //todo - timeout should be configurable depending on the perf test type + requestCount, fulfilmentCount, err := vrfv2plus.WaitForRequestCountEqualToFulfilmentCount(consumer, 2*time.Minute, &wg) + require.NoError(t, err) + wg.Wait() + l.Info(). Interface("Request Count", requestCount). Interface("Fulfilment Count", fulfilmentCount). Msg("Final Request/Fulfilment Stats") - require.NoError(t, err) - wg.Wait() - //send final results - SendLoadTestMetricsToLoki(vrfv2PlusContracts, lc, updatedLabels) }) } + +func cancelSubsAndReturnFunds(subIDs []*big.Int, l zerolog.Logger) { + for _, subID := range subIDs { + l.Info(). + Str("Returning funds from SubID", subID.String()). + Str("Returning funds to", eoaWalletAddress). + Msg("Canceling subscription and returning funds to subscription owner") + pendingRequestsExist, err := vrfv2PlusContracts.Coordinator.PendingRequestsExist(context.Background(), subID) + if err != nil { + l.Error().Err(err).Msg("Error checking if pending requests exist") + } + if !pendingRequestsExist { + _, err := vrfv2PlusContracts.Coordinator.CancelSubscription(subID, common.HexToAddress(eoaWalletAddress)) + if err != nil { + l.Error().Err(err).Msg("Error canceling subscription") + } + } else { + l.Error().Str("Sub ID", subID.String()).Msg("Pending requests exist for subscription, cannot cancel subscription and return funds") + } + } +} + +func FundNodesIfNeeded(cfg *PerformanceConfig, client blockchain.EVMClient, l zerolog.Logger) error { + if cfg.ExistingEnvConfig.NodeSendingKeyFundingMin > 0 { + for _, sendingKey := range cfg.ExistingEnvConfig.NodeSendingKeys { + address := common.HexToAddress(sendingKey) + sendingKeyBalance, err := client.BalanceAt(context.Background(), address) + if err != nil { + return err + } + fundingAtLeast := conversions.EtherToWei(big.NewFloat(cfg.ExistingEnvConfig.NodeSendingKeyFundingMin)) + fundingToSendWei := new(big.Int).Sub(fundingAtLeast, sendingKeyBalance) + fundingToSendEth := conversions.WeiToEther(fundingToSendWei) + if fundingToSendWei.Cmp(big.NewInt(0)) == 1 { + l.Info(). + Str("Sending Key", sendingKey). + Str("Sending Key Current Balance", sendingKeyBalance.String()). + Str("Should have at least", fundingAtLeast.String()). + Str("Funding Amount in ETH", fundingToSendEth.String()). + Msg("Funding Node's Sending Key") + gasEstimates, err := client.EstimateGas(ethereum.CallMsg{ + To: &address, + }) + if err != nil { + return err + } + err = client.Fund(sendingKey, fundingToSendEth, gasEstimates) + if err != nil { + return err + } + } else { + l.Info(). + Str("Sending Key", sendingKey). + Str("Sending Key Current Balance", sendingKeyBalance.String()). + Str("Should have at least", fundingAtLeast.String()). + Msg("Skipping Node's Sending Key funding as it has enough funds") + } + } + } + return nil +} + +func teardown( + t *testing.T, + consumer contracts.VRFv2PlusLoadTestConsumer, + lc *wasp.LokiClient, + updatedLabels map[string]string, + testReporter *testreporters.VRFV2PlusTestReporter, + testType string, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, +) { + //send final results to Loki + metrics := GetLoadTestMetrics(consumer) + SendMetricsToLoki(metrics, lc, updatedLabels) + //set report data for Slack notification + testReporter.SetReportData( + testType, + metrics.RequestCount, + metrics.FulfilmentCount, + metrics.AverageFulfillmentInMillions, + metrics.SlowestFulfillment, + metrics.FastestFulfillment, + vrfv2PlusConfig, + ) + + // send Slack notification + err := testReporter.SendSlackNotification(t, nil) + if err != nil { + log.Warn().Err(err).Msg("Error sending Slack notification") + } +} diff --git a/integration-tests/migration/upgrade_version_test.go b/integration-tests/migration/upgrade_version_test.go index bf97f43d058..d1f07a52b74 100644 --- a/integration-tests/migration/upgrade_version_test.go +++ b/integration-tests/migration/upgrade_version_test.go @@ -1,13 +1,12 @@ package migration import ( + "os" "testing" - "github.com/smartcontractkit/chainlink-testing-framework/utils" "github.com/stretchr/testify/require" - "os" - + "github.com/smartcontractkit/chainlink-testing-framework/utils/osutil" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" ) @@ -20,9 +19,9 @@ func TestVersionUpgrade(t *testing.T) { Build() require.NoError(t, err) - upgradeImage, err := utils.GetEnv("UPGRADE_IMAGE") + upgradeImage, err := osutil.GetEnv("UPGRADE_IMAGE") require.NoError(t, err, "Error getting upgrade image") - upgradeVersion, err := utils.GetEnv("UPGRADE_VERSION") + upgradeVersion, err := osutil.GetEnv("UPGRADE_VERSION") require.NoError(t, err, "Error getting upgrade version") _ = os.Setenv("CHAINLINK_IMAGE", upgradeImage) diff --git a/integration-tests/performance/cron_test.go b/integration-tests/performance/cron_test.go index 959cb0fa3e1..38d861cee07 100644 --- a/integration-tests/performance/cron_test.go +++ b/integration-tests/performance/cron_test.go @@ -12,15 +12,14 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/logging" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" - "github.com/smartcontractkit/chainlink-testing-framework/utils" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" + "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" @@ -44,7 +43,7 @@ func CleanupPerformanceTest( if chainClient != nil { chainClient.GasStats().PrintStats() } - err := actions.TeardownSuite(t, testEnvironment, utils.ProjectRoot, chainlinkNodes, &testReporter, zapcore.PanicLevel, chainClient) + err := actions.TeardownSuite(t, testEnvironment, chainlinkNodes, &testReporter, zapcore.PanicLevel, chainClient) require.NoError(t, err, "Error tearing down environment") } @@ -109,7 +108,7 @@ func TestCronPerformance(t *testing.T) { func setupCronTest(t *testing.T) (testEnvironment *environment.Environment) { logging.Init() - network := networks.SelectedNetwork + network := networks.MustGetSelectedNetworksFromEnv()[0] evmConfig := ethereum.New(nil) if !network.Simulated { evmConfig = ethereum.New(ðereum.Props{ @@ -122,7 +121,7 @@ func setupCronTest(t *testing.T) (testEnvironment *environment.Environment) { HTTPWriteTimout = '300s'` cd := chainlink.New(0, map[string]interface{}{ "replicas": 1, - "toml": client.AddNetworksConfig(baseTOML, network), + "toml": networks.AddNetworksConfig(baseTOML, network), }) testEnvironment = environment.New(&environment.Config{ diff --git a/integration-tests/performance/directrequest_test.go b/integration-tests/performance/directrequest_test.go index 4ff2b856195..0fe1ca37e15 100644 --- a/integration-tests/performance/directrequest_test.go +++ b/integration-tests/performance/directrequest_test.go @@ -1,7 +1,6 @@ package performance import ( - "context" "fmt" "math/big" "strings" @@ -11,15 +10,16 @@ import ( "github.com/onsi/gomega" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -108,7 +108,7 @@ func TestDirectRequestPerformance(t *testing.T) { gom := gomega.NewGomegaWithT(t) gom.Eventually(func(g gomega.Gomega) { - d, err := consumer.Data(context.Background()) + d, err := consumer.Data(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Getting data from consumer contract shouldn't fail") g.Expect(d).ShouldNot(gomega.BeNil(), "Expected the initial on chain data to be nil") l.Debug().Int64("Data", d.Int64()).Msg("Found on chain") @@ -129,7 +129,7 @@ func TestDirectRequestPerformance(t *testing.T) { } func setupDirectRequestTest(t *testing.T) (testEnvironment *environment.Environment) { - network := networks.SelectedNetwork + network := networks.MustGetSelectedNetworksFromEnv()[0] evmConfig := ethereum.New(nil) if !network.Simulated { evmConfig = ethereum.New(ðereum.Props{ @@ -142,7 +142,7 @@ func setupDirectRequestTest(t *testing.T) (testEnvironment *environment.Environm HTTPWriteTimout = '300s'` cd := chainlink.New(0, map[string]interface{}{ "replicas": 1, - "toml": client.AddNetworksConfig(baseTOML, network), + "toml": networks.AddNetworksConfig(baseTOML, network), }) testEnvironment = environment.New(&environment.Config{ diff --git a/integration-tests/performance/flux_test.go b/integration-tests/performance/flux_test.go index 3f9db27c106..5fa31b626ae 100644 --- a/integration-tests/performance/flux_test.go +++ b/integration-tests/performance/flux_test.go @@ -1,7 +1,6 @@ package performance import ( - "context" "fmt" "math/big" "strings" @@ -12,15 +11,16 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -83,7 +83,7 @@ func TestFluxPerformance(t *testing.T) { require.NoError(t, err, "Setting oracle options in the Flux Aggregator contract shouldn't fail") err = chainClient.WaitForEvents() require.NoError(t, err, "Waiting for event subscriptions in nodes shouldn't fail") - oracles, err := fluxInstance.GetOracles(context.Background()) + oracles, err := fluxInstance.GetOracles(testcontext.Get(t)) require.NoError(t, err, "Getting oracle details from the Flux aggregator contract shouldn't fail") l.Info().Str("Oracles", strings.Join(oracles, ",")).Msg("Oracles set") @@ -120,7 +120,7 @@ func TestFluxPerformance(t *testing.T) { chainClient.AddHeaderEventSubscription(fluxInstance.Address(), fluxRound) err = chainClient.WaitForEvents() require.NoError(t, err, "Waiting for event subscriptions in nodes shouldn't fail") - data, err := fluxInstance.GetContractData(context.Background()) + data, err := fluxInstance.GetContractData(testcontext.Get(t)) require.NoError(t, err, "Getting contract data from flux aggregator contract shouldn't fail") l.Info().Interface("Data", data).Msg("Round data") require.Equal(t, int64(1e5), data.LatestRoundData.Answer.Int64(), @@ -140,7 +140,7 @@ func TestFluxPerformance(t *testing.T) { require.NoError(t, err, "Setting value path in mock server shouldn't fail") err = chainClient.WaitForEvents() require.NoError(t, err, "Waiting for event subscriptions in nodes shouldn't fail") - data, err = fluxInstance.GetContractData(context.Background()) + data, err = fluxInstance.GetContractData(testcontext.Get(t)) require.NoError(t, err, "Getting contract data from flux aggregator contract shouldn't fail") require.Equal(t, int64(1e10), data.LatestRoundData.Answer.Int64(), "Expected latest round answer to be %d, but found %d", int64(1e10), data.LatestRoundData.Answer.Int64()) @@ -153,7 +153,7 @@ func TestFluxPerformance(t *testing.T) { l.Info().Interface("data", data).Msg("Round data") for _, oracleAddr := range nodeAddresses { - payment, _ := fluxInstance.WithdrawablePayment(context.Background(), oracleAddr) + payment, _ := fluxInstance.WithdrawablePayment(testcontext.Get(t), oracleAddr) require.Equal(t, int64(2), payment.Int64(), "Expected flux aggregator contract's withdrawable payment to be %d, but found %d", int64(2), payment.Int64()) } @@ -173,7 +173,7 @@ func TestFluxPerformance(t *testing.T) { } func setupFluxTest(t *testing.T) (testEnvironment *environment.Environment, testNetwork blockchain.EVMNetwork) { - testNetwork = networks.SelectedNetwork + testNetwork = networks.MustGetSelectedNetworksFromEnv()[0] evmConf := ethereum.New(nil) if !testNetwork.Simulated { evmConf = ethereum.New(ðereum.Props{ @@ -189,7 +189,7 @@ HTTPWriteTimout = '300s' Enabled = true` cd := chainlink.New(0, map[string]interface{}{ "replicas": 3, - "toml": client.AddNetworksConfig(baseTOML, testNetwork), + "toml": networks.AddNetworksConfig(baseTOML, testNetwork), }) testEnvironment = environment.New(&environment.Config{ diff --git a/integration-tests/performance/keeper_test.go b/integration-tests/performance/keeper_test.go index 08ea95b4340..ac6e9e6a579 100644 --- a/integration-tests/performance/keeper_test.go +++ b/integration-tests/performance/keeper_test.go @@ -2,7 +2,6 @@ package performance //revive:disable:dot-imports import ( - "context" "fmt" "math/big" "strings" @@ -12,14 +11,15 @@ import ( "github.com/onsi/gomega" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - eth "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + eth "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -74,7 +74,7 @@ func TestKeeperPerformance(t *testing.T) { gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analysing their counters and checking they are greater than 10 for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(10)), "Expected consumer counter to be greater than 10, but got %d", counter.Int64()) @@ -84,7 +84,7 @@ func TestKeeperPerformance(t *testing.T) { // Cancel all the registered upkeeps via the registry for i := 0; i < len(upkeepIDs); i++ { - err := registry.CancelUpkeep(upkeepIDs[i]) + err = registry.CancelUpkeep(upkeepIDs[i]) require.NoError(t, err, "Could not cancel upkeep at index %d", i) } @@ -95,7 +95,7 @@ func TestKeeperPerformance(t *testing.T) { for i := 0; i < len(upkeepIDs); i++ { // Obtain the amount of times the upkeep has been executed so far - countersAfterCancellation[i], err = consumers[i].Counter(context.Background()) + countersAfterCancellation[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) l.Info().Int("Index", i).Int64("Upkeeps Performed", countersAfterCancellation[i].Int64()).Msg("Cancelled Upkeep") } @@ -103,7 +103,7 @@ func TestKeeperPerformance(t *testing.T) { gom.Consistently(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { // Expect the counter to remain constant because the upkeep was cancelled, so it shouldn't increase anymore - latestCounter, err := consumers[i].Counter(context.Background()) + latestCounter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(latestCounter.Int64()).Should(gomega.Equal(countersAfterCancellation[i].Int64()), "Expected consumer counter to remain constant at %d, but got %d", @@ -134,7 +134,7 @@ func setupKeeperTest( contracts.ContractDeployer, contracts.LinkToken, ) { - network := networks.SelectedNetwork + network := networks.MustGetSelectedNetworksFromEnv()[0] evmConfig := eth.New(nil) if !network.Simulated { evmConfig = eth.New(ð.Props{ @@ -155,7 +155,7 @@ PerformGasOverhead = 150_000` networkName := strings.ReplaceAll(strings.ToLower(network.Name), " ", "-") cd := chainlink.New(0, map[string]interface{}{ "replicas": 5, - "toml": client.AddNetworksConfig(baseTOML, network), + "toml": networks.AddNetworksConfig(baseTOML, network), }) testEnvironment := environment.New( diff --git a/integration-tests/performance/ocr_test.go b/integration-tests/performance/ocr_test.go index f468a0e0370..2c339c04ddf 100644 --- a/integration-tests/performance/ocr_test.go +++ b/integration-tests/performance/ocr_test.go @@ -1,7 +1,6 @@ package performance import ( - "context" "fmt" "math/big" "strings" @@ -10,16 +9,16 @@ import ( "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/logging" - "github.com/smartcontractkit/chainlink-testing-framework/networks" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -53,7 +52,7 @@ func TestOCRBasic(t *testing.T) { err = actions.FundChainlinkNodes(chainlinkNodes, chainClient, big.NewFloat(.05)) require.NoError(t, err, "Error funding Chainlink nodes") - ocrInstances, err := actions.DeployOCRContracts(1, linkTokenContract, contractDeployer, bootstrapNode, workerNodes, chainClient) + ocrInstances, err := actions.DeployOCRContracts(1, linkTokenContract, contractDeployer, workerNodes, chainClient) require.NoError(t, err) err = chainClient.WaitForEvents() require.NoError(t, err, "Error waiting for events") @@ -64,7 +63,7 @@ func TestOCRBasic(t *testing.T) { err = actions.StartNewRound(1, ocrInstances, chainClient, l) require.NoError(t, err) - answer, err := ocrInstances[0].GetLatestAnswer(context.Background()) + answer, err := ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") require.Equal(t, int64(5), answer.Int64(), "Expected latest answer from OCR contract to be 5 but got %d", answer.Int64()) @@ -73,7 +72,7 @@ func TestOCRBasic(t *testing.T) { err = actions.StartNewRound(2, ocrInstances, chainClient, l) require.NoError(t, err) - answer, err = ocrInstances[0].GetLatestAnswer(context.Background()) + answer, err = ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) require.NoError(t, err, "Error getting latest OCR answer") require.Equal(t, int64(10), answer.Int64(), "Expected latest answer from OCR contract to be 10 but got %d", answer.Int64()) } @@ -90,7 +89,7 @@ func TestOCRBasic(t *testing.T) { } func setupOCRTest(t *testing.T) (testEnvironment *environment.Environment, testNetwork blockchain.EVMNetwork) { - testNetwork = networks.SelectedNetwork + testNetwork = networks.MustGetSelectedNetworksFromEnv()[0] evmConfig := ethereum.New(nil) if !testNetwork.Simulated { evmConfig = ethereum.New(ðereum.Props{ @@ -107,13 +106,13 @@ Enabled = true Enabled = false [P2P] -[P2P.V1] -Enabled = true -ListenIP = '0.0.0.0' -ListenPort = 6690` +[P2P.V2] +AnnounceAddresses = ["0.0.0.0:6690"] +ListenAddresses = ["0.0.0.0:6690"]` + cd := chainlink.New(0, map[string]interface{}{ "replicas": 6, - "toml": client.AddNetworksConfig(baseTOML, testNetwork), + "toml": networks.AddNetworksConfig(baseTOML, testNetwork), }) testEnvironment = environment.New(&environment.Config{ diff --git a/integration-tests/performance/vrf_test.go b/integration-tests/performance/vrf_test.go index 510e378eb82..cad4ea5eebd 100644 --- a/integration-tests/performance/vrf_test.go +++ b/integration-tests/performance/vrf_test.go @@ -1,7 +1,6 @@ package performance import ( - "context" "fmt" "math/big" "strings" @@ -12,12 +11,13 @@ import ( "github.com/onsi/gomega" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -97,7 +97,7 @@ func TestVRFBasic(t *testing.T) { encodedProvingKeys := make([][2]*big.Int, 0) encodedProvingKeys = append(encodedProvingKeys, provingKey) - requestHash, err := coordinator.HashOfKey(context.Background(), encodedProvingKeys[0]) + requestHash, err := coordinator.HashOfKey(testcontext.Get(t), encodedProvingKeys[0]) require.NoError(t, err, "Getting Hash of encoded proving keys shouldn't fail") err = consumer.RequestRandomness(requestHash, big.NewInt(1)) require.NoError(t, err, "Requesting randomness shouldn't fail") @@ -108,7 +108,7 @@ func TestVRFBasic(t *testing.T) { jobRuns, err := chainlinkNodes[0].MustReadRunsByJob(job.Data.ID) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Job execution shouldn't fail") - out, err := consumer.RandomnessOutput(context.Background()) + out, err := consumer.RandomnessOutput(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Getting the randomness output of the consumer shouldn't fail") // Checks that the job has actually run g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically(">=", 1), @@ -135,7 +135,7 @@ func TestVRFBasic(t *testing.T) { } func setupVRFTest(t *testing.T) (testEnvironment *environment.Environment, testNetwork blockchain.EVMNetwork) { - testNetwork = networks.SelectedNetwork + testNetwork = networks.MustGetSelectedNetworksFromEnv()[0] evmConfig := ethereum.New(nil) if !testNetwork.Simulated { evmConfig = ethereum.New(ðereum.Props{ @@ -147,7 +147,7 @@ func setupVRFTest(t *testing.T) (testEnvironment *environment.Environment, testN baseTOML := `[WebServer] HTTPWriteTimout = '300s'` cd := chainlink.New(0, map[string]interface{}{ - "toml": client.AddNetworksConfig(baseTOML, testNetwork), + "toml": networks.AddNetworksConfig(baseTOML, testNetwork), }) testEnvironment = environment.New(&environment.Config{ diff --git a/integration-tests/reorg/automation_reorg_test.go b/integration-tests/reorg/automation_reorg_test.go index 660d9c48e12..dae977d3eea 100644 --- a/integration-tests/reorg/automation_reorg_test.go +++ b/integration-tests/reorg/automation_reorg_test.go @@ -2,7 +2,6 @@ package reorg //revive:disable:dot-imports import ( - "context" "fmt" "math/big" "testing" @@ -12,14 +11,14 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/cdk8s/blockscout" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/reorg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/blockscout" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/reorg" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" - "github.com/smartcontractkit/chainlink-testing-framework/utils" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -48,9 +47,9 @@ HistoryDepth = 400 [EVM.GasEstimator] Mode = 'FixedPrice' LimitDefault = 5_000_000` - activeEVMNetwork = networks.SelectedNetwork + activeEVMNetwork = networks.MustGetSelectedNetworksFromEnv()[0] defaultAutomationSettings = map[string]interface{}{ - "toml": client.AddNetworkDetailedConfig(baseTOML, networkTOML, activeEVMNetwork), + "toml": networks.AddNetworkDetailedConfig(baseTOML, networkTOML, activeEVMNetwork), "db": map[string]interface{}{ "stateful": false, "capacity": "1Gi", @@ -133,9 +132,11 @@ func TestAutomationReorg(t *testing.T) { } for name, registryVersion := range registryVersions { + name := name + registryVersion := registryVersion t.Run(name, func(t *testing.T) { t.Parallel() - network := networks.SelectedNetwork + network := networks.MustGetSelectedNetworksFromEnv()[0] defaultAutomationSettings["replicas"] = numberOfNodes cd := chainlink.New(0, defaultAutomationSettings) @@ -167,7 +168,7 @@ func TestAutomationReorg(t *testing.T) { // Register cleanup for any test t.Cleanup(func() { - err := actions.TeardownSuite(t, testEnvironment, utils.ProjectRoot, chainlinkNodes, nil, zapcore.PanicLevel, chainClient) + err := actions.TeardownSuite(t, testEnvironment, chainlinkNodes, nil, zapcore.PanicLevel, chainClient) require.NoError(t, err, "Error tearing down environment") }) @@ -209,7 +210,7 @@ func TestAutomationReorg(t *testing.T) { gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analyzing their counters and checking they are greater than 5 for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) expect := 5 l.Info().Int64("Upkeeps Performed", counter.Int64()).Int("Upkeep ID", i).Msg("Number of upkeeps performed") @@ -240,7 +241,7 @@ func TestAutomationReorg(t *testing.T) { gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analyzing their counters and checking they reach 10 for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) expect := 10 l.Info().Int64("Upkeeps Performed", counter.Int64()).Int("Upkeep ID", i).Msg("Number of upkeeps performed") @@ -250,13 +251,14 @@ func TestAutomationReorg(t *testing.T) { }, "5m", "1s").Should(gomega.Succeed()) l.Info().Msg("Upkeep performed during unstable chain, waiting for reorg to finish") - rc.WaitDepthReached() + err = rc.WaitDepthReached() + require.NoError(t, err) l.Info().Msg("Reorg finished, chain should be stable now. Expecting upkeeps to keep getting performed") gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analyzing their counters and checking they reach 20 for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) expect := 20 l.Info().Int64("Upkeeps Performed", counter.Int64()).Int("Upkeep ID", i).Msg("Number of upkeeps performed") diff --git a/integration-tests/reorg/log_poller_maybe_reorg_test.go b/integration-tests/reorg/log_poller_maybe_reorg_test.go new file mode 100644 index 00000000000..d319e39aa20 --- /dev/null +++ b/integration-tests/reorg/log_poller_maybe_reorg_test.go @@ -0,0 +1,43 @@ +package reorg + +import ( + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + + logpoller "github.com/smartcontractkit/chainlink/integration-tests/universal/log_poller" +) + +func TestLogPollerFromEnv(t *testing.T) { + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 2, + EventsPerTx: 100, + UseFinalityTag: true, + }, + LoopedConfig: &logpoller.LoopedConfig{ + ContractConfig: logpoller.ContractConfig{ + ExecutionCount: 100, + }, + FuzzConfig: logpoller.FuzzConfig{ + MinEmitWaitTimeMs: 400, + MaxEmitWaitTimeMs: 600, + }, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit + err := cfg.OverrideFromEnv() + if err != nil { + t.Errorf("failed to override config from env: %v", err) + t.FailNow() + } + + logpoller.ExecuteCILogPollerTest(t, &cfg) +} diff --git a/integration-tests/reorg/reorg_confirmer.go b/integration-tests/reorg/reorg_confirmer.go index 6647816c975..a5659e66783 100644 --- a/integration-tests/reorg/reorg_confirmer.go +++ b/integration-tests/reorg/reorg_confirmer.go @@ -2,20 +2,20 @@ package reorg import ( "context" + "fmt" "math/big" "sync" "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/smartcontractkit/chainlink-env/chaos" - "github.com/smartcontractkit/chainlink-env/environment" - a "github.com/smartcontractkit/chainlink-env/pkg/alias" - "github.com/smartcontractkit/chainlink-env/pkg/helm/reorg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/chaos" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/reorg" + "github.com/smartcontractkit/chainlink-testing-framework/utils/ptr" ) // The steps are: @@ -69,7 +69,7 @@ type ReorgController struct { // NewReorgController creates a type that can create reorg chaos and confirm reorg has happened func NewReorgController(cfg *ReorgConfig) (*ReorgController, error) { if len(cfg.Network.GetClients()) == 1 { - return nil, errors.New("need at least 3 nodes to re-org") + return nil, fmt.Errorf("need at least 3 nodes to re-org") } ctx, ctxCancel := context.WithTimeout(context.Background(), cfg.Timeout) rc := &ReorgController{ @@ -164,7 +164,7 @@ func (rc *ReorgController) VerifyReorgComplete() error { } } if rc.currentVerifiedBlocks+1 < rc.ReorgDepth { - return errors.New("Reorg depth has not met") + return fmt.Errorf("Reorg depth has not met") } return nil } @@ -216,7 +216,7 @@ func (rc *ReorgController) Wait() error { if rc.complete { return nil } - return errors.New("timeout waiting for reorg to complete") + return fmt.Errorf("timeout waiting for reorg to complete") } // forkNetwork stomp the network between target reorged node and the rest @@ -231,8 +231,8 @@ func (rc *ReorgController) forkNetwork(header blockchain.NodeHeader) error { rc.cfg.Env.Cfg.Namespace, &chaos.Props{ DurationStr: "999h", - FromLabels: &map[string]*string{"app": a.Str(reorg.TXNodesAppLabel)}, - ToLabels: &map[string]*string{"app": a.Str(reorg.MinerNodesAppLabel)}, + FromLabels: &map[string]*string{"app": ptr.Ptr(reorg.TXNodesAppLabel)}, + ToLabels: &map[string]*string{"app": ptr.Ptr(reorg.MinerNodesAppLabel)}, }, )) rc.chaosExperimentName = expName diff --git a/integration-tests/reorg/reorg_test.go b/integration-tests/reorg/reorg_test.go index 74468b92536..fc35047d0e4 100644 --- a/integration-tests/reorg/reorg_test.go +++ b/integration-tests/reorg/reorg_test.go @@ -1,7 +1,6 @@ package reorg import ( - "context" "fmt" "math/big" "os" @@ -10,23 +9,22 @@ import ( "time" "github.com/google/uuid" + "github.com/onsi/gomega" + "github.com/rs/zerolog/log" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/logging" - "github.com/smartcontractkit/chainlink-env/pkg/cdk8s/blockscout" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver-cfg" - "github.com/smartcontractkit/chainlink-env/pkg/helm/reorg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" - "github.com/smartcontractkit/chainlink-testing-framework/utils" - - "github.com/onsi/gomega" - "github.com/rs/zerolog/log" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/blockscout" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/reorg" + "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -85,7 +83,7 @@ func CleanupReorgTest( if chainClient != nil { chainClient.GasStats().PrintStats() } - err := actions.TeardownSuite(t, testEnvironment, utils.ProjectRoot, chainlinkNodes, nil, zapcore.PanicLevel, chainClient) + err := actions.TeardownSuite(t, testEnvironment, chainlinkNodes, nil, zapcore.PanicLevel, chainClient) require.NoError(t, err, "Error tearing down environment") } @@ -127,7 +125,7 @@ func TestDirectRequestReorg(t *testing.T) { netCfg := fmt.Sprintf(networkDRTOML, EVMFinalityDepth, EVMTrackerHistoryDepth) chainlinkDeployment := chainlink.New(0, map[string]interface{}{ "replicas": 1, - "toml": client.AddNetworkDetailedConfig(baseDRTOML, netCfg, activeEVMNetwork), + "toml": networks.AddNetworkDetailedConfig(baseDRTOML, netCfg, activeEVMNetwork), }) err = testEnvironment.AddHelm(chainlinkDeployment).Run() @@ -221,7 +219,7 @@ func TestDirectRequestReorg(t *testing.T) { gom := gomega.NewGomegaWithT(t) gom.Eventually(func(g gomega.Gomega) { - d, err := consumer.Data(context.Background()) + d, err := consumer.Data(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Getting data from consumer contract shouldn't fail") g.Expect(d).ShouldNot(gomega.BeNil(), "Expected the initial on chain data to be nil") log.Debug().Int64("Data", d.Int64()).Msg("Found on chain") diff --git a/integration-tests/runner_helpers.go b/integration-tests/runner_helpers.go index 43268a703ac..def2ebdc1d4 100644 --- a/integration-tests/runner_helpers.go +++ b/integration-tests/runner_helpers.go @@ -122,7 +122,7 @@ func collectBranchesAndTags(results chan []string, errChan chan error) { go func() { stdOut, stdErr, err := gh.Exec("api", fmt.Sprintf("repos/%s/branches", chainlinkRepo), "-q", ".[][\"name\"]", "--paginate") if err != nil { - errChan <- fmt.Errorf("%v: %s", err, stdErr.String()) + errChan <- fmt.Errorf("%w: %s", err, stdErr.String()) } branches := strings.Split(stdOut.String(), "\n") cleanBranches := []string{} @@ -139,7 +139,7 @@ func collectBranchesAndTags(results chan []string, errChan chan error) { go func() { stdOut, stdErr, err := gh.Exec("api", fmt.Sprintf("repos/%s/tags", chainlinkRepo), "-q", ".[][\"name\"]", "--paginate") if err != nil { - errChan <- fmt.Errorf("%v: %s", err, stdErr.String()) + errChan <- fmt.Errorf("%w: %s", err, stdErr.String()) } tags := strings.Split(stdOut.String(), "\n") cleanTags := []string{} diff --git a/integration-tests/smoke/automation_test.go b/integration-tests/smoke/automation_test.go index 9b79e1bea40..f72843e77e8 100644 --- a/integration-tests/smoke/automation_test.go +++ b/integration-tests/smoke/automation_test.go @@ -1,7 +1,6 @@ package smoke import ( - "context" "encoding/json" "fmt" "math/big" @@ -11,56 +10,47 @@ import ( "testing" "time" + ctfTestEnv "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" + + ocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" + + ocr2keepers30config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" + + "github.com/smartcontractkit/chainlink/integration-tests/actions/automationv2" + "github.com/kelseyhightower/envconfig" "github.com/ethereum/go-ethereum/common" "github.com/onsi/gomega" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" + "github.com/smartcontractkit/chainlink-testing-framework/utils/ptr" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" cltypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams" "github.com/smartcontractkit/chainlink/v2/core/store/models" - evm21 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21" - "github.com/smartcontractkit/chainlink/integration-tests/actions" - "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" - it_utils "github.com/smartcontractkit/chainlink/integration-tests/utils" ) var utilsABI = cltypes.MustGetABI(automation_utils_2_1.AutomationUtilsABI) const ( - automationDefaultUpkeepGasLimit = uint32(2500000) - automationDefaultLinkFunds = int64(9e18) - automationDefaultUpkeepsToDeploy = 10 - automationExpectedData = "abcdef" - defaultAmountOfUpkeeps = 2 + automationDefaultUpkeepGasLimit = uint32(2500000) + automationDefaultLinkFunds = int64(9e18) + automationExpectedData = "abcdef" + defaultAmountOfUpkeeps = 2 ) var ( - defaultOCRRegistryConfig = contracts.KeeperRegistrySettings{ - PaymentPremiumPPB: uint32(200000000), - FlatFeeMicroLINK: uint32(0), - BlockCountPerTurn: big.NewInt(10), - CheckGasLimit: uint32(2500000), - StalenessSeconds: big.NewInt(90000), - GasCeilingMultiplier: uint16(1), - MinUpkeepSpend: big.NewInt(0), - MaxPerformGas: uint32(5000000), - FallbackGasPrice: big.NewInt(2e11), - FallbackLinkPrice: big.NewInt(2e18), - MaxCheckDataSize: uint32(5000), - MaxPerformDataSize: uint32(5000), - } automationDefaultRegistryConfig = contracts.KeeperRegistrySettings{ PaymentPremiumPPB: uint32(200000000), FlatFeeMicroLINK: uint32(0), @@ -79,9 +69,9 @@ var ( func TestMain(m *testing.M) { logging.Init() - fmt.Printf("Running Smoke Test on %s\n", networks.SelectedNetwork.Name) // Print to get around disabled logging - fmt.Printf("Chainlink Image %s\n", os.Getenv("CHAINLINK_IMAGE")) // Print to get around disabled logging - fmt.Printf("Chainlink Version %s\n", os.Getenv("CHAINLINK_VERSION")) // Print to get around disabled logging + fmt.Printf("Running Smoke Test on %s\n", networks.MustGetSelectedNetworksFromEnv()[0].Name) // Print to get around disabled logging + fmt.Printf("Chainlink Image %s\n", os.Getenv("CHAINLINK_IMAGE")) // Print to get around disabled logging + fmt.Printf("Chainlink Version %s\n", os.Getenv("CHAINLINK_VERSION")) // Print to get around disabled logging os.Exit(m.Run()) } @@ -111,7 +101,6 @@ func SetupAutomationBasic(t *testing.T, nodeUpgrade bool) { upgradeImage string upgradeVersion string err error - testName = "basic-upkeep" ) if nodeUpgrade { upgradeImage = os.Getenv("UPGRADE_IMAGE") @@ -119,7 +108,6 @@ func SetupAutomationBasic(t *testing.T, nodeUpgrade bool) { if len(upgradeImage) == 0 || len(upgradeVersion) == 0 { t.Fatal("UPGRADE_IMAGE and UPGRADE_VERSION must be set to upgrade nodes") } - testName = "node-upgrade" } // Use the name to determine if this is a log trigger or mercury @@ -128,17 +116,17 @@ func SetupAutomationBasic(t *testing.T, nodeUpgrade bool) { isMercuryV03 := name == "registry_2_1_with_mercury_v03" isMercury := isMercuryV02 || isMercuryV03 - chainClient, _, contractDeployer, linkToken, registry, registrar, testEnv := setupAutomationTestDocker( - t, testName, registryVersion, defaultOCRRegistryConfig, nodeUpgrade, isMercuryV02, isMercuryV03, + a := setupAutomationTestDocker( + t, registryVersion, automationDefaultRegistryConfig, isMercuryV02, isMercuryV03, ) consumers, upkeepIDs := actions.DeployConsumers( t, - registry, - registrar, - linkToken, - contractDeployer, - chainClient, + a.Registry, + a.Registrar, + a.LinkToken, + a.Deployer, + a.ChainClient, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, @@ -156,10 +144,10 @@ func SetupAutomationBasic(t *testing.T, nodeUpgrade bool) { if isMercury { // Set privilege config to enable mercury - privilegeConfigBytes, _ := json.Marshal(evm21.UpkeepPrivilegeConfig{ + privilegeConfigBytes, _ := json.Marshal(streams.UpkeepPrivilegeConfig{ MercuryEnabled: true, }) - if err := registry.SetUpkeepPrivilegeConfig(upkeepIDs[i], privilegeConfigBytes); err != nil { + if err := a.Registry.SetUpkeepPrivilegeConfig(upkeepIDs[i], privilegeConfigBytes); err != nil { l.Error().Msg("Error when setting upkeep privilege config") return } @@ -173,7 +161,7 @@ func SetupAutomationBasic(t *testing.T, nodeUpgrade bool) { gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analyzing their counters for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) expect := 5 l.Info().Int64("Upkeeps Performed", counter.Int64()).Int("Upkeep Index", i).Msg("Number of upkeeps performed") @@ -188,13 +176,14 @@ func SetupAutomationBasic(t *testing.T, nodeUpgrade bool) { expect := 5 // Upgrade the nodes one at a time and check that the upkeeps are still being performed for i := 0; i < 5; i++ { - actions.UpgradeChainlinkNodeVersionsLocal(upgradeImage, upgradeVersion, testEnv.ClCluster.Nodes[i]) + err = actions.UpgradeChainlinkNodeVersionsLocal(upgradeImage, upgradeVersion, a.DockerEnv.ClCluster.Nodes[i]) + require.NoError(t, err, "Error when upgrading node %d", i) time.Sleep(time.Second * 10) expect = expect + 5 gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analyzing their counters and checking they are increasing by 5 in each step within 5 minutes for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) l.Info().Int64("Upkeeps Performed", counter.Int64()).Int("Upkeep ID", i).Msg("Number of upkeeps performed") g.Expect(counter.Int64()).Should(gomega.BeNumerically(">=", int64(expect)), @@ -206,18 +195,18 @@ func SetupAutomationBasic(t *testing.T, nodeUpgrade bool) { // Cancel all the registered upkeeps via the registry for i := 0; i < len(upkeepIDs); i++ { - err := registry.CancelUpkeep(upkeepIDs[i]) + err := a.Registry.CancelUpkeep(upkeepIDs[i]) require.NoError(t, err, "Could not cancel upkeep at index %d", i) } - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error encountered when waiting for upkeeps to be cancelled") var countersAfterCancellation = make([]*big.Int, len(upkeepIDs)) for i := 0; i < len(upkeepIDs); i++ { // Obtain the amount of times the upkeep has been executed so far - countersAfterCancellation[i], err = consumers[i].Counter(context.Background()) + countersAfterCancellation[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) l.Info().Int64("Upkeep Count", countersAfterCancellation[i].Int64()).Int("Upkeep Index", i).Msg("Cancelled upkeep") } @@ -226,7 +215,7 @@ func SetupAutomationBasic(t *testing.T, nodeUpgrade bool) { gom.Consistently(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { // Expect the counter to remain constant (At most increase by 1 to account for stale performs) because the upkeep was cancelled - latestCounter, err := consumers[i].Counter(context.Background()) + latestCounter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(latestCounter.Int64()).Should(gomega.BeNumerically("<=", countersAfterCancellation[i].Int64()+1), "Expected consumer counter to remain less than or equal to %d, but got %d", @@ -241,17 +230,17 @@ func TestSetUpkeepTriggerConfig(t *testing.T) { t.Parallel() l := logging.GetTestLogger(t) - chainClient, _, contractDeployer, linkToken, registry, registrar, _ := setupAutomationTestDocker( - t, "set-trigger-config", ethereum.RegistryVersion_2_1, defaultOCRRegistryConfig, false, false, false, + a := setupAutomationTestDocker( + t, ethereum.RegistryVersion_2_1, automationDefaultRegistryConfig, false, false, ) consumers, upkeepIDs := actions.DeployConsumers( t, - registry, - registrar, - linkToken, - contractDeployer, - chainClient, + a.Registry, + a.Registrar, + a.LinkToken, + a.Deployer, + a.ChainClient, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, @@ -272,7 +261,7 @@ func TestSetUpkeepTriggerConfig(t *testing.T) { gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analyzing their counters for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) expect := 5 l.Info().Int64("Upkeeps Performed", counter.Int64()).Int("Upkeep Index", i).Msg("Number of upkeeps performed") @@ -316,11 +305,11 @@ func TestSetUpkeepTriggerConfig(t *testing.T) { return } - err = registry.SetUpkeepTriggerConfig(upkeepIDs[i], encodedLogTriggerConfig) + err = a.Registry.SetUpkeepTriggerConfig(upkeepIDs[i], encodedLogTriggerConfig) require.NoError(t, err, "Could not set upkeep trigger config at index %d", i) } - err := chainClient.WaitForEvents() + err := a.ChainClient.WaitForEvents() require.NoError(t, err, "Error encountered when waiting for setting trigger config for upkeeps") var countersAfterSetNoMatch = make([]*big.Int, len(upkeepIDs)) @@ -329,7 +318,7 @@ func TestSetUpkeepTriggerConfig(t *testing.T) { time.Sleep(10 * time.Second) for i := 0; i < len(upkeepIDs); i++ { // Obtain the amount of times the upkeep has been executed so far - countersAfterSetNoMatch[i], err = consumers[i].Counter(context.Background()) + countersAfterSetNoMatch[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) l.Info().Int64("Upkeep Count", countersAfterSetNoMatch[i].Int64()).Int("Upkeep Index", i).Msg("Upkeep") } @@ -339,7 +328,7 @@ func TestSetUpkeepTriggerConfig(t *testing.T) { for i := 0; i < len(upkeepIDs); i++ { // Expect the counter to remain constant (At most increase by 2 to account for stale performs) because the upkeep trigger config is not met bufferCount := int64(2) - latestCounter, err := consumers[i].Counter(context.Background()) + latestCounter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(latestCounter.Int64()).Should(gomega.BeNumerically("<=", countersAfterSetNoMatch[i].Int64()+bufferCount), "Expected consumer counter to remain less than or equal to %d, but got %d", @@ -364,18 +353,18 @@ func TestSetUpkeepTriggerConfig(t *testing.T) { return } - err = registry.SetUpkeepTriggerConfig(upkeepIDs[i], encodedLogTriggerConfig) + err = a.Registry.SetUpkeepTriggerConfig(upkeepIDs[i], encodedLogTriggerConfig) require.NoError(t, err, "Could not set upkeep trigger config at index %d", i) } - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error encountered when waiting for setting trigger config for upkeeps") var countersAfterSetMatch = make([]*big.Int, len(upkeepIDs)) for i := 0; i < len(upkeepIDs); i++ { // Obtain the amount of times the upkeep has been executed so far - countersAfterSetMatch[i], err = consumers[i].Counter(context.Background()) + countersAfterSetMatch[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) l.Info().Int64("Upkeep Count", countersAfterSetMatch[i].Int64()).Int("Upkeep Index", i).Msg("Upkeep") } @@ -394,7 +383,7 @@ func TestSetUpkeepTriggerConfig(t *testing.T) { gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analyzing their counters for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) expect := int64(5) l.Info().Int64("Upkeeps Performed", counter.Int64()).Int("Upkeep Index", i).Msg("Number of upkeeps performed") @@ -416,36 +405,48 @@ func TestAutomationAddFunds(t *testing.T) { registryVersion := rv t.Run(name, func(t *testing.T) { t.Parallel() - chainClient, _, contractDeployer, linkToken, registry, registrar, _ := setupAutomationTestDocker( - t, "add-funds", registryVersion, defaultOCRRegistryConfig, false, false, false, + a := setupAutomationTestDocker( + t, registryVersion, automationDefaultRegistryConfig, false, false, ) - consumers, upkeepIDs := actions.DeployConsumers(t, registry, registrar, linkToken, contractDeployer, chainClient, defaultAmountOfUpkeeps, big.NewInt(1), automationDefaultUpkeepGasLimit, false, false) + consumers, upkeepIDs := actions.DeployConsumers( + t, + a.Registry, + a.Registrar, + a.LinkToken, + a.Deployer, + a.ChainClient, + defaultAmountOfUpkeeps, + big.NewInt(1), + automationDefaultUpkeepGasLimit, + false, + false, + ) gom := gomega.NewGomegaWithT(t) // Since the upkeep is currently underfunded, check that it doesn't get executed gom.Consistently(func(g gomega.Gomega) { - counter, err := consumers[0].Counter(context.Background()) + counter, err := consumers[0].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(counter.Int64()).Should(gomega.Equal(int64(0)), "Expected consumer counter to remain zero, but got %d", counter.Int64()) }, "2m", "1s").Should(gomega.Succeed()) // ~1m for setup, 1m assertion // Grant permission to the registry to fund the upkeep - err := linkToken.Approve(registry.Address(), big.NewInt(9e18)) + err := a.LinkToken.Approve(a.Registry.Address(), big.NewInt(9e18)) require.NoError(t, err, "Could not approve permissions for the registry on the link token contract") - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for events") // Add funds to the upkeep whose ID we know from above - err = registry.AddUpkeepFunds(upkeepIDs[0], big.NewInt(9e18)) + err = a.Registry.AddUpkeepFunds(upkeepIDs[0], big.NewInt(9e18)) require.NoError(t, err, "Unable to add upkeep") - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for events") // Now the new upkeep should be performing because we added enough funds gom.Eventually(func(g gomega.Gomega) { - counter, err := consumers[0].Counter(context.Background()) + counter, err := consumers[0].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected newly registered upkeep's counter to be greater than 0, but got %d", counter.Int64()) @@ -467,17 +468,29 @@ func TestAutomationPauseUnPause(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() l := logging.GetTestLogger(t) - chainClient, _, contractDeployer, linkToken, registry, registrar, _ := setupAutomationTestDocker( - t, "pause-unpause", registryVersion, defaultOCRRegistryConfig, false, false, false, + a := setupAutomationTestDocker( + t, registryVersion, automationDefaultRegistryConfig, false, false, ) - consumers, upkeepIDs := actions.DeployConsumers(t, registry, registrar, linkToken, contractDeployer, chainClient, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, false, false) + consumers, upkeepIDs := actions.DeployConsumers( + t, + a.Registry, + a.Registrar, + a.LinkToken, + a.Deployer, + a.ChainClient, + defaultAmountOfUpkeeps, + big.NewInt(automationDefaultLinkFunds), + automationDefaultUpkeepGasLimit, + false, + false, + ) gom := gomega.NewGomegaWithT(t) gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analyzing their counters and checking they are greater than 5 for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(5)), "Expected consumer counter to be greater than 5, but got %d", counter.Int64()) @@ -487,17 +500,17 @@ func TestAutomationPauseUnPause(t *testing.T) { // pause all the registered upkeeps via the registry for i := 0; i < len(upkeepIDs); i++ { - err := registry.PauseUpkeep(upkeepIDs[i]) + err := a.Registry.PauseUpkeep(upkeepIDs[i]) require.NoError(t, err, "Could not pause upkeep at index %d", i) } - err := chainClient.WaitForEvents() + err := a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for upkeeps to be paused") var countersAfterPause = make([]*big.Int, len(upkeepIDs)) for i := 0; i < len(upkeepIDs); i++ { // Obtain the amount of times the upkeep has been executed so far - countersAfterPause[i], err = consumers[i].Counter(context.Background()) + countersAfterPause[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) l.Info().Int("Upkeep Index", i).Int64("Upkeeps Performed", countersAfterPause[i].Int64()).Msg("Paused Upkeep") } @@ -506,7 +519,7 @@ func TestAutomationPauseUnPause(t *testing.T) { for i := 0; i < len(upkeepIDs); i++ { // In most cases counters should remain constant, but there might be a straggling perform tx which // gets committed later and increases counter by 1 - latestCounter, err := consumers[i].Counter(context.Background()) + latestCounter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(latestCounter.Int64()).Should(gomega.BeNumerically("<=", countersAfterPause[i].Int64()+1), "Expected consumer counter not have increased more than %d, but got %d", @@ -516,17 +529,17 @@ func TestAutomationPauseUnPause(t *testing.T) { // unpause all the registered upkeeps via the registry for i := 0; i < len(upkeepIDs); i++ { - err := registry.UnpauseUpkeep(upkeepIDs[i]) + err := a.Registry.UnpauseUpkeep(upkeepIDs[i]) require.NoError(t, err, "Could not unpause upkeep at index %d", i) } - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for upkeeps to be unpaused") gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analysing their counters and checking they are greater than 5 + numbers of performing before pause for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", countersAfterPause[i].Int64()+1), "Expected consumer counter to be greater than %d, but got %d", countersAfterPause[i].Int64()+1, counter.Int64()) @@ -550,11 +563,23 @@ func TestAutomationRegisterUpkeep(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() l := logging.GetTestLogger(t) - chainClient, _, contractDeployer, linkToken, registry, registrar, _ := setupAutomationTestDocker( - t, "register-upkeep", registryVersion, defaultOCRRegistryConfig, false, false, false, + a := setupAutomationTestDocker( + t, registryVersion, automationDefaultRegistryConfig, false, false, ) - consumers, upkeepIDs := actions.DeployConsumers(t, registry, registrar, linkToken, contractDeployer, chainClient, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, false, false) + consumers, upkeepIDs := actions.DeployConsumers( + t, + a.Registry, + a.Registrar, + a.LinkToken, + a.Deployer, + a.ChainClient, + defaultAmountOfUpkeeps, + big.NewInt(automationDefaultLinkFunds), + automationDefaultUpkeepGasLimit, + false, + false, + ) var initialCounters = make([]*big.Int, len(upkeepIDs)) gom := gomega.NewGomegaWithT(t) @@ -562,7 +587,7 @@ func TestAutomationRegisterUpkeep(t *testing.T) { // store the value of their initial counters in order to compare later on that the value increased. gom.Eventually(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) initialCounters[i] = counter g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(0)), @@ -574,15 +599,15 @@ func TestAutomationRegisterUpkeep(t *testing.T) { } }, "4m", "1s").Should(gomega.Succeed()) // ~1m for cluster setup, ~1m for performing each upkeep once, ~2m buffer - newConsumers, _ := actions.RegisterNewUpkeeps(t, contractDeployer, chainClient, linkToken, - registry, registrar, automationDefaultUpkeepGasLimit, 1) + newConsumers, _ := actions.RegisterNewUpkeeps(t, a.Deployer, a.ChainClient, a.LinkToken, + a.Registry, a.Registrar, automationDefaultUpkeepGasLimit, 1) // We know that newConsumers has size 1, so we can just use the newly registered upkeep. newUpkeep := newConsumers[0] // Test that the newly registered upkeep is also performing. gom.Eventually(func(g gomega.Gomega) { - counter, err := newUpkeep.Counter(context.Background()) + counter, err := newUpkeep.Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling newly deployed upkeep's counter shouldn't fail") g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected newly registered upkeep's counter to be greater than 0, but got %d", counter.Int64()) @@ -591,7 +616,7 @@ func TestAutomationRegisterUpkeep(t *testing.T) { gom.Eventually(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { - currentCounter, err := consumers[i].Counter(context.Background()) + currentCounter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") l.Info(). @@ -621,17 +646,29 @@ func TestAutomationPauseRegistry(t *testing.T) { registryVersion := rv t.Run(name, func(t *testing.T) { t.Parallel() - chainClient, _, contractDeployer, linkToken, registry, registrar, _ := setupAutomationTestDocker( - t, "pause-registry", registryVersion, defaultOCRRegistryConfig, false, false, false, + a := setupAutomationTestDocker( + t, registryVersion, automationDefaultRegistryConfig, false, false, ) - consumers, upkeepIDs := actions.DeployConsumers(t, registry, registrar, linkToken, contractDeployer, chainClient, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, false, false) + consumers, upkeepIDs := actions.DeployConsumers( + t, + a.Registry, + a.Registrar, + a.LinkToken, + a.Deployer, + a.ChainClient, + defaultAmountOfUpkeeps, + big.NewInt(automationDefaultLinkFunds), + automationDefaultUpkeepGasLimit, + false, + false, + ) gom := gomega.NewGomegaWithT(t) // Observe that the upkeeps which are initially registered are performing gom.Eventually(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected consumer counter to be greater than 0, but got %d") @@ -639,15 +676,15 @@ func TestAutomationPauseRegistry(t *testing.T) { }, "4m", "1s").Should(gomega.Succeed()) // ~1m for cluster setup, ~1m for performing each upkeep once, ~2m buffer // Pause the registry - err := registry.Pause() + err := a.Registry.Pause() require.NoError(t, err, "Error pausing registry") - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for registry to pause") // Store how many times each upkeep performed once the registry was successfully paused var countersAfterPause = make([]*big.Int, len(upkeepIDs)) for i := 0; i < len(upkeepIDs); i++ { - countersAfterPause[i], err = consumers[i].Counter(context.Background()) + countersAfterPause[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) } @@ -655,7 +692,7 @@ func TestAutomationPauseRegistry(t *testing.T) { // because they are no longer getting serviced gom.Consistently(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { - latestCounter, err := consumers[i].Counter(context.Background()) + latestCounter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(latestCounter.Int64()).Should(gomega.Equal(countersAfterPause[i].Int64()), "Expected consumer counter to remain constant at %d, but got %d", @@ -679,21 +716,34 @@ func TestAutomationKeeperNodesDown(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() l := logging.GetTestLogger(t) - chainClient, chainlinkNodes, contractDeployer, linkToken, registry, registrar, _ := setupAutomationTestDocker( - t, "keeper-nodes-down", registryVersion, defaultOCRRegistryConfig, false, false, false, + a := setupAutomationTestDocker( + t, registryVersion, automationDefaultRegistryConfig, false, false, ) - consumers, upkeepIDs := actions.DeployConsumers(t, registry, registrar, linkToken, contractDeployer, chainClient, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, false, false) + consumers, upkeepIDs := actions.DeployConsumers( + t, + a.Registry, + a.Registrar, + a.LinkToken, + a.Deployer, + a.ChainClient, + defaultAmountOfUpkeeps, + big.NewInt(automationDefaultLinkFunds), + automationDefaultUpkeepGasLimit, + false, + false, + ) gom := gomega.NewGomegaWithT(t) - nodesWithoutBootstrap := chainlinkNodes[1:] + nodesWithoutBootstrap := a.ChainlinkNodes[1:] var initialCounters = make([]*big.Int, len(upkeepIDs)) // Watch upkeeps being performed and store their counters in order to compare them later in the test gom.Eventually(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) initialCounters[i] = counter + l.Info().Int64("Upkeeps Performed", counter.Int64()).Int("Upkeep Index", i).Msg("Number of upkeeps performed") g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected consumer counter to be greater than 0, but got %d", counter.Int64()) @@ -703,7 +753,7 @@ func TestAutomationKeeperNodesDown(t *testing.T) { // Take down 1 node. Currently, using 4 nodes so f=1 and is the max nodes that can go down. err := nodesWithoutBootstrap[0].MustDeleteJob("1") require.NoError(t, err, "Error deleting job from Chainlink node") - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for blockchain events") l.Info().Msg("Successfully managed to take down the first half of the nodes") @@ -711,7 +761,8 @@ func TestAutomationKeeperNodesDown(t *testing.T) { // Assert that upkeeps are still performed and their counters have increased gom.Eventually(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { - currentCounter, err := consumers[i].Counter(context.Background()) + currentCounter, err := consumers[i].Counter(testcontext.Get(t)) + l.Info().Int64("Upkeeps Performed", currentCounter.Int64()).Int("Upkeep Index", i).Msg("Number of upkeeps performed") g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(currentCounter.Int64()).Should(gomega.BeNumerically(">", initialCounters[i].Int64()), "Expected counter to have increased from initial value of %s, but got %s", @@ -724,7 +775,7 @@ func TestAutomationKeeperNodesDown(t *testing.T) { for _, nodeToTakeDown := range restOfNodesDown { err = nodeToTakeDown.MustDeleteJob("1") require.NoError(t, err, "Error deleting job from Chainlink node") - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for blockchain events") } l.Info().Msg("Successfully managed to take down the second half of the nodes") @@ -732,7 +783,7 @@ func TestAutomationKeeperNodesDown(t *testing.T) { // See how many times each upkeep was executed var countersAfterNoMoreNodes = make([]*big.Int, len(upkeepIDs)) for i := 0; i < len(upkeepIDs); i++ { - countersAfterNoMoreNodes[i], err = consumers[i].Counter(context.Background()) + countersAfterNoMoreNodes[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) l.Info().Int("Upkeep Index", i).Int64("Performed", countersAfterNoMoreNodes[i].Int64()).Msg("Upkeeps Performed") } @@ -741,7 +792,7 @@ func TestAutomationKeeperNodesDown(t *testing.T) { // all the nodes were taken down gom.Consistently(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { - latestCounter, err := consumers[i].Counter(context.Background()) + latestCounter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(latestCounter.Int64()).Should(gomega.BeNumerically("<=", countersAfterNoMoreNodes[i].Int64()+1), "Expected consumer counter to not have increased more than %d, but got %d", @@ -764,17 +815,17 @@ func TestAutomationPerformSimulation(t *testing.T) { registryVersion := rv t.Run(name, func(t *testing.T) { t.Parallel() - chainClient, _, contractDeployer, linkToken, registry, registrar, _ := setupAutomationTestDocker( - t, "perform-simulation", registryVersion, defaultOCRRegistryConfig, false, false, false, + a := setupAutomationTestDocker( + t, registryVersion, automationDefaultRegistryConfig, false, false, ) consumersPerformance, _ := actions.DeployPerformanceConsumers( t, - registry, - registrar, - linkToken, - contractDeployer, - chainClient, + a.Registry, + a.Registrar, + a.LinkToken, + a.Deployer, + a.ChainClient, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, @@ -790,7 +841,7 @@ func TestAutomationPerformSimulation(t *testing.T) { // Initially performGas is set high, so performUpkeep reverts and no upkeep should be performed gom.Consistently(func(g gomega.Gomega) { // Consumer count should remain at 0 - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's Counter shouldn't fail") g.Expect(cnt.Int64()).Should(gomega.Equal(int64(0)), "Expected consumer counter to remain constant at %d, but got %d", 0, cnt.Int64(), @@ -798,14 +849,14 @@ func TestAutomationPerformSimulation(t *testing.T) { }, "2m", "1s").Should(gomega.Succeed()) // ~1m for setup, 1m assertion // Set performGas on consumer to be low, so that performUpkeep starts becoming successful - err := consumerPerformance.SetPerformGasToBurn(context.Background(), big.NewInt(100000)) + err := consumerPerformance.SetPerformGasToBurn(testcontext.Get(t), big.NewInt(100000)) require.NoError(t, err, "Perform gas should be set successfully on consumer") - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for set perform gas tx") // Upkeep should now start performing gom.Eventually(func(g gomega.Gomega) { - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's Counter shouldn't fail") g.Expect(cnt.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected consumer counter to be greater than 0, but got %d", cnt.Int64(), @@ -828,17 +879,17 @@ func TestAutomationCheckPerformGasLimit(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() l := logging.GetTestLogger(t) - chainClient, chainlinkNodes, contractDeployer, linkToken, registry, registrar, _ := setupAutomationTestDocker( - t, "gas-limit", registryVersion, defaultOCRRegistryConfig, false, false, false, + a := setupAutomationTestDocker( + t, registryVersion, automationDefaultRegistryConfig, false, false, ) consumersPerformance, upkeepIDs := actions.DeployPerformanceConsumers( t, - registry, - registrar, - linkToken, - contractDeployer, - chainClient, + a.Registry, + a.Registrar, + a.LinkToken, + a.Deployer, + a.ChainClient, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, @@ -849,13 +900,13 @@ func TestAutomationCheckPerformGasLimit(t *testing.T) { ) gom := gomega.NewGomegaWithT(t) - nodesWithoutBootstrap := chainlinkNodes[1:] + nodesWithoutBootstrap := a.ChainlinkNodes[1:] consumerPerformance := consumersPerformance[0] upkeepID := upkeepIDs[0] // Initially performGas is set higher than defaultUpkeepGasLimit, so no upkeep should be performed gom.Consistently(func(g gomega.Gomega) { - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(cnt.Int64()).Should( gomega.Equal(int64(0)), @@ -864,14 +915,14 @@ func TestAutomationCheckPerformGasLimit(t *testing.T) { }, "2m", "1s").Should(gomega.Succeed()) // ~1m for setup, 1m assertion // Increase gas limit for the upkeep, higher than the performGasBurn - err := registry.SetUpkeepGasLimit(upkeepID, uint32(4500000)) + err := a.Registry.SetUpkeepGasLimit(upkeepID, uint32(4500000)) require.NoError(t, err, "Error setting upkeep gas limit") - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for SetUpkeepGasLimit tx") // Upkeep should now start performing gom.Eventually(func(g gomega.Gomega) { - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(cnt.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected consumer counter to be greater than 0, but got %d", cnt.Int64(), @@ -879,19 +930,19 @@ func TestAutomationCheckPerformGasLimit(t *testing.T) { }, "2m", "1s").Should(gomega.Succeed()) // ~1m to perform once, 1m buffer // Now increase the checkGasBurn on consumer, upkeep should stop performing - err = consumerPerformance.SetCheckGasToBurn(context.Background(), big.NewInt(3000000)) + err = consumerPerformance.SetCheckGasToBurn(testcontext.Get(t), big.NewInt(3000000)) require.NoError(t, err, "Check gas burn should be set successfully on consumer") - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for SetCheckGasToBurn tx") // Get existing performed count - existingCnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + existingCnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) require.NoError(t, err, "Calling consumer's counter shouldn't fail") l.Info().Int64("Upkeep counter", existingCnt.Int64()).Msg("Upkeep counter when check gas increased") // In most cases count should remain constant, but it might increase by upto 1 due to pending perform gom.Consistently(func(g gomega.Gomega) { - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(cnt.Int64()).Should( gomega.BeNumerically("<=", existingCnt.Int64()+1), @@ -899,7 +950,7 @@ func TestAutomationCheckPerformGasLimit(t *testing.T) { ) }, "1m", "1s").Should(gomega.Succeed()) - existingCnt, err = consumerPerformance.GetUpkeepCount(context.Background()) + existingCnt, err = consumerPerformance.GetUpkeepCount(testcontext.Get(t)) require.NoError(t, err, "Calling consumer's counter shouldn't fail") existingCntInt := existingCnt.Int64() l.Info().Int64("Upkeep counter", existingCntInt).Msg("Upkeep counter when consistently block finished") @@ -909,17 +960,17 @@ func TestAutomationCheckPerformGasLimit(t *testing.T) { highCheckGasLimit.CheckGasLimit = uint32(5000000) highCheckGasLimit.RegistryVersion = registryVersion - ocrConfig, err := actions.BuildAutoOCR2ConfigVarsLocal(l, nodesWithoutBootstrap, highCheckGasLimit, registrar.Address(), 30*time.Second, registry.RegistryOwnerAddress()) + ocrConfig, err := actions.BuildAutoOCR2ConfigVarsLocal(l, nodesWithoutBootstrap, highCheckGasLimit, a.Registrar.Address(), 30*time.Second, a.Registry.RegistryOwnerAddress()) require.NoError(t, err, "Error building OCR config") - err = registry.SetConfig(highCheckGasLimit, ocrConfig) + err = a.Registry.SetConfig(highCheckGasLimit, ocrConfig) require.NoError(t, err, "Registry config should be set successfully!") - err = chainClient.WaitForEvents() + err = a.ChainClient.WaitForEvents() require.NoError(t, err, "Error waiting for set config tx") // Upkeep should start performing again, and it should get regularly performed gom.Eventually(func(g gomega.Gomega) { - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's Counter shouldn't fail") g.Expect(cnt.Int64()).Should(gomega.BeNumerically(">", existingCntInt), "Expected consumer counter to be greater than %d, but got %d", existingCntInt, cnt.Int64(), @@ -942,17 +993,17 @@ func TestUpdateCheckData(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() l := logging.GetTestLogger(t) - chainClient, _, contractDeployer, linkToken, registry, registrar, _ := setupAutomationTestDocker( - t, "update-check-data", registryVersion, defaultOCRRegistryConfig, false, false, false, + a := setupAutomationTestDocker( + t, registryVersion, automationDefaultRegistryConfig, false, false, ) performDataChecker, upkeepIDs := actions.DeployPerformDataCheckerConsumers( t, - registry, - registrar, - linkToken, - contractDeployer, - chainClient, + a.Registry, + a.Registrar, + a.LinkToken, + a.Deployer, + a.ChainClient, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, @@ -963,7 +1014,7 @@ func TestUpdateCheckData(t *testing.T) { gom.Consistently(func(g gomega.Gomega) { // expect the counter to remain 0 because perform data does not match for i := 0; i < len(upkeepIDs); i++ { - counter, err := performDataChecker[i].Counter(context.Background()) + counter, err := performDataChecker[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve perform data checker"+ " for upkeep at index "+strconv.Itoa(i)) g.Expect(counter.Int64()).Should(gomega.Equal(int64(0)), @@ -973,16 +1024,16 @@ func TestUpdateCheckData(t *testing.T) { }, "2m", "1s").Should(gomega.Succeed()) // ~1m for setup, 1m assertion for i := 0; i < len(upkeepIDs); i++ { - err := registry.UpdateCheckData(upkeepIDs[i], []byte(automationExpectedData)) + err := a.Registry.UpdateCheckData(upkeepIDs[i], []byte(automationExpectedData)) require.NoError(t, err, "Could not update check data for upkeep at index %d", i) } - err := chainClient.WaitForEvents() + err := a.ChainClient.WaitForEvents() require.NoError(t, err, "Error while waiting for check data update") // retrieve new check data for all upkeeps for i := 0; i < len(upkeepIDs); i++ { - upkeep, err := registry.GetUpkeepInfo(context.Background(), upkeepIDs[i]) + upkeep, err := a.Registry.GetUpkeepInfo(testcontext.Get(t), upkeepIDs[i]) require.NoError(t, err, "Failed to get upkeep info at index %d", i) require.Equal(t, []byte(automationExpectedData), upkeep.CheckData, "Upkeep data not as expected") } @@ -990,7 +1041,7 @@ func TestUpdateCheckData(t *testing.T) { gom.Eventually(func(g gomega.Gomega) { // Check if the upkeeps are performing multiple times by analysing their counters and checking they are greater than 5 for i := 0; i < len(upkeepIDs); i++ { - counter, err := performDataChecker[i].Counter(context.Background()) + counter, err := performDataChecker[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve perform data checker counter"+ " for upkeep at index "+strconv.Itoa(i)) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(0)), @@ -1008,36 +1059,26 @@ type TestConfig struct { func setupAutomationTestDocker( t *testing.T, - testName string, registryVersion ethereum.KeeperRegistryVersion, registryConfig contracts.KeeperRegistrySettings, - statefulDb bool, isMercuryV02 bool, isMercuryV03 bool, -) ( - blockchain.EVMClient, - []*client.ChainlinkClient, - contracts.ContractDeployer, - contracts.LinkToken, - contracts.KeeperRegistry, - contracts.KeeperRegistrar, - *test_env.CLClusterTestEnv, -) { +) automationv2.AutomationTest { require.False(t, isMercuryV02 && isMercuryV03, "Cannot run test with both Mercury V02 and V03 on") l := logging.GetTestLogger(t) // Add registry version to config registryConfig.RegistryVersion = registryVersion - network := networks.SelectedNetwork + network := networks.MustGetSelectedNetworksFromEnv()[0] // build the node config clNodeConfig := node.NewConfig(node.NewBaseConfig()) syncInterval := models.MustMakeDuration(5 * time.Minute) - clNodeConfig.Feature.LogPoller = it_utils.Ptr[bool](true) - clNodeConfig.OCR2.Enabled = it_utils.Ptr[bool](true) - clNodeConfig.Keeper.TurnLookBack = it_utils.Ptr[int64](int64(0)) + clNodeConfig.Feature.LogPoller = ptr.Ptr[bool](true) + clNodeConfig.OCR2.Enabled = ptr.Ptr[bool](true) + clNodeConfig.Keeper.TurnLookBack = ptr.Ptr[int64](int64(0)) clNodeConfig.Keeper.Registry.SyncInterval = &syncInterval - clNodeConfig.Keeper.Registry.PerformGasOverhead = it_utils.Ptr[uint32](uint32(150000)) + clNodeConfig.Keeper.Registry.PerformGasOverhead = ptr.Ptr[uint32](uint32(150000)) clNodeConfig.P2P.V2.AnnounceAddresses = &[]string{"0.0.0.0:6690"} clNodeConfig.P2P.V2.ListenAddresses = &[]string{"0.0.0.0:6690"} @@ -1071,8 +1112,8 @@ func setupAutomationTestDocker( var httpUrls []string var wsUrls []string if network.Simulated { - httpUrls = []string{env.Geth.InternalHttpUrl} - wsUrls = []string{env.Geth.InternalWsUrl} + httpUrls = []string{env.RpcProvider.PrivateHttpUrls()[0]} + wsUrls = []string{env.RpcProvider.PrivateWsUrsl()[0]} } else { httpUrls = network.HTTPURLs wsUrls = network.URLs @@ -1085,14 +1126,6 @@ func setupAutomationTestDocker( err = env.FundChainlinkNodes(big.NewFloat(testConfig.ChainlinkNodeFunding)) require.NoError(t, err, "Error funding CL nodes") - if isMercuryV02 { - output := `{"chainlinkBlob":"0x0001c38d71fed6c320b90e84b6f559459814d068e2a1700adc931ca9717d4fe70000000000000000000000000000000000000000000000000000000001a80b52b4bf1233f9cb71144a253a1791b202113c4ab4a92fa1b176d684b4959666ff8200000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001004254432d5553442d415242495452554d2d544553544e4554000000000000000000000000000000000000000000000000000000000000000000000000645570be000000000000000000000000000000000000000000000000000002af2b818dc5000000000000000000000000000000000000000000000000000002af2426faf3000000000000000000000000000000000000000000000000000002af32dc209700000000000000000000000000000000000000000000000000000000012130f8df0a9745bb6ad5e2df605e158ba8ad8a33ef8a0acf9851f0f01668a3a3f2b68600000000000000000000000000000000000000000000000000000000012130f60000000000000000000000000000000000000000000000000000000000000002c4a7958dce105089cf5edb68dad7dcfe8618d7784eb397f97d5a5fade78c11a58275aebda478968e545f7e3657aba9dcbe8d44605e4c6fde3e24edd5e22c94270000000000000000000000000000000000000000000000000000000000000002459c12d33986018a8959566d145225f0c4a4e61a9a3f50361ccff397899314f0018162cf10cd89897635a0bb62a822355bd199d09f4abe76e4d05261bb44733d"}` - env.MockAdapter.SetStringValuePath("/client", []string{http.MethodGet, http.MethodPost}, map[string]string{"Content-Type": "application/json"}, output) - } - if isMercuryV03 { - output := `{"reports":[{"feedID":"0x4554482d5553442d415242495452554d2d544553544e45540000000000000000","validFromTimestamp":0,"observationsTimestamp":0,"fullReport":"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}]}` - env.MockAdapter.SetStringValuePath("/api/v1/reports/bulk", []string{http.MethodGet, http.MethodPost}, map[string]string{"Content-Type": "application/json"}, output) - } } else { env, err = test_env.NewCLTestEnvBuilder(). WithTestLogger(t). @@ -1108,31 +1141,82 @@ func setupAutomationTestDocker( env.ParallelTransactions(true) nodeClients := env.ClCluster.NodeAPIs() - workerNodes := nodeClients[1:] - linkToken, err := env.ContractDeployer.DeployLinkTokenContract() - require.NoError(t, err, "Error deploying LINK token") + a := automationv2.NewAutomationTestDocker(env.EVMClient, env.ContractDeployer, nodeClients) + a.MercuryCredentialName = "cred1" + a.RegistrySettings = registryConfig + a.RegistrarSettings = contracts.KeeperRegistrarSettings{ + AutoApproveConfigType: uint8(2), + AutoApproveMaxAllowed: 1000, + MinLinkJuels: big.NewInt(0), + } + a.PluginConfig = ocr2keepers30config.OffchainConfig{ + TargetProbability: "0.999", + TargetInRounds: 1, + PerformLockoutWindow: 3_600_000, // Intentionally set to be higher than in prod for testing purpose + GasLimitPerReport: 10_300_000, + GasOverheadPerUpkeep: 300_000, + MinConfirmations: 0, + MaxUpkeepBatchSize: 10, + } + a.PublicConfig = ocr3.PublicConfig{ + DeltaProgress: 10 * time.Second, + DeltaResend: 15 * time.Second, + DeltaInitial: 500 * time.Millisecond, + DeltaRound: 1000 * time.Millisecond, + DeltaGrace: 200 * time.Millisecond, + DeltaCertifiedCommitRequest: 300 * time.Millisecond, + DeltaStage: 30 * time.Second, + RMax: 24, + MaxDurationQuery: 20 * time.Millisecond, + MaxDurationObservation: 20 * time.Millisecond, + MaxDurationShouldAcceptAttestedReport: 1200 * time.Millisecond, + MaxDurationShouldTransmitAcceptedReport: 20 * time.Millisecond, + F: 1, + } - registry, registrar := actions.DeployAutoOCRRegistryAndRegistrar( - t, - registryVersion, - registryConfig, - linkToken, - env.ContractDeployer, - env.EVMClient, - ) + a.SetupAutomationDeployment(t) + a.SetDockerEnv(env) - // Fund the registry with LINK - err = linkToken.Transfer(registry.Address(), big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(int64(defaultAmountOfUpkeeps)))) - require.NoError(t, err, "Funding keeper registry contract shouldn't fail") + if isMercuryV02 || isMercuryV03 { + var imposters []ctfTestEnv.KillgraveImposter + mercuryv03Mock200 := ctfTestEnv.KillgraveImposter{ + Request: ctfTestEnv.KillgraveRequest{ + Method: http.MethodGet, + Endpoint: "/api/v1/reports/bulk", + SchemaFile: nil, + Params: &map[string]string{"feedIDs": "0x00028c915d6af0fd66bba2d0fc9405226bca8d6806333121a7d9832103d1563c", "timestamp": "{[\\d+]}"}, + Headers: nil, + }, + Response: ctfTestEnv.KillgraveResponse{ + Status: 200, + Body: `{"reports":[{"feedID":"0x00028c915d6af0fd66bba2d0fc9405226bca8d6806333121a7d9832103d1563c","validFromTimestamp":0,"observationsTimestamp":0,"fullReport":"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}]}`, + BodyFile: nil, + Headers: nil, + Delay: nil, + }, + } - err = actions.CreateOCRKeeperJobsLocal(l, nodeClients, registry.Address(), network.ChainID, 0, registryVersion) - require.NoError(t, err, "Error creating OCR Keeper Jobs") - ocrConfig, err := actions.BuildAutoOCR2ConfigVarsLocal(l, workerNodes, registryConfig, registrar.Address(), 30*time.Second, registry.RegistryOwnerAddress()) - require.NoError(t, err, "Error building OCR config vars") - err = registry.SetConfig(automationDefaultRegistryConfig, ocrConfig) - require.NoError(t, err, "Registry config should be set successfully") - require.NoError(t, env.EVMClient.WaitForEvents(), "Waiting for config to be set") + mercuryv02Mock200 := ctfTestEnv.KillgraveImposter{ + Request: ctfTestEnv.KillgraveRequest{ + Method: http.MethodGet, + Endpoint: "/client", + SchemaFile: nil, + Params: &map[string]string{"feedIdHex": "{0x00028c915d6af0fd66bba2d0fc9405226bca8d6806333121a7d9832103d1563c|0x4554482d5553442d415242495452554d2d544553544e45540000000000000000}", "blockNumber": "{[\\d+]}"}, + Headers: nil, + }, + Response: ctfTestEnv.KillgraveResponse{ + Status: 200, + Body: `{"chainlinkBlob":"0x0001c38d71fed6c320b90e84b6f559459814d068e2a1700adc931ca9717d4fe70000000000000000000000000000000000000000000000000000000001a80b52b4bf1233f9cb71144a253a1791b202113c4ab4a92fa1b176d684b4959666ff8200000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001004254432d5553442d415242495452554d2d544553544e4554000000000000000000000000000000000000000000000000000000000000000000000000645570be000000000000000000000000000000000000000000000000000002af2b818dc5000000000000000000000000000000000000000000000000000002af2426faf3000000000000000000000000000000000000000000000000000002af32dc209700000000000000000000000000000000000000000000000000000000012130f8df0a9745bb6ad5e2df605e158ba8ad8a33ef8a0acf9851f0f01668a3a3f2b68600000000000000000000000000000000000000000000000000000000012130f60000000000000000000000000000000000000000000000000000000000000002c4a7958dce105089cf5edb68dad7dcfe8618d7784eb397f97d5a5fade78c11a58275aebda478968e545f7e3657aba9dcbe8d44605e4c6fde3e24edd5e22c94270000000000000000000000000000000000000000000000000000000000000002459c12d33986018a8959566d145225f0c4a4e61a9a3f50361ccff397899314f0018162cf10cd89897635a0bb62a822355bd199d09f4abe76e4d05261bb44733d"}`, + BodyFile: nil, + Headers: nil, + Delay: nil, + }, + } + + imposters = append(imposters, mercuryv03Mock200, mercuryv02Mock200) + a.SetupMercuryMock(t, imposters) + } - return env.EVMClient, nodeClients, env.ContractDeployer, linkToken, registry, registrar, env + return *a } diff --git a/integration-tests/smoke/cron_test.go b/integration-tests/smoke/cron_test.go index e9d20f588e2..013e4c631c7 100644 --- a/integration-tests/smoke/cron_test.go +++ b/integration-tests/smoke/cron_test.go @@ -60,3 +60,74 @@ func TestCronBasic(t *testing.T) { } }, "2m", "3s").Should(gomega.Succeed()) } + +func TestCronJobReplacement(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) + + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithMockAdapter(). + WithCLNodes(1). + WithStandardCleanup(). + Build() + require.NoError(t, err) + + err = env.MockAdapter.SetAdapterBasedIntValuePath("/variable", []string{http.MethodGet, http.MethodPost}, 5) + require.NoError(t, err, "Setting value path in mockserver shouldn't fail") + + bta := &client.BridgeTypeAttributes{ + Name: fmt.Sprintf("variable-%s", uuid.NewString()), + URL: fmt.Sprintf("%s/variable", env.MockAdapter.InternalEndpoint), + RequestData: "{}", + } + err = env.ClCluster.Nodes[0].API.MustCreateBridge(bta) + require.NoError(t, err, "Creating bridge in chainlink node shouldn't fail") + + // CRON job creation and replacement + job, err := env.ClCluster.Nodes[0].API.MustCreateJob(&client.CronJobSpec{ + Schedule: "CRON_TZ=UTC * * * * * *", + ObservationSource: client.ObservationSourceSpecBridge(bta), + }) + require.NoError(t, err, "Creating Cron Job in chainlink node shouldn't fail") + + gom := gomega.NewWithT(t) + gom.Eventually(func(g gomega.Gomega) { + jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(job.Data.ID) + if err != nil { + l.Info().Err(err).Msg("error while waiting for job runs") + } + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Reading Job run data shouldn't fail") + + g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically(">=", 5), "Expected number of job runs to be greater than 5, but got %d", len(jobRuns.Data)) + + for _, jr := range jobRuns.Data { + g.Expect(jr.Attributes.Errors).Should(gomega.Equal([]interface{}{nil}), "Job run %s shouldn't have errors", jr.ID) + } + }, "3m", "3s").Should(gomega.Succeed()) + + err = env.ClCluster.Nodes[0].API.MustDeleteJob(job.Data.ID) + require.NoError(t, err) + + job, err = env.ClCluster.Nodes[0].API.MustCreateJob(&client.CronJobSpec{ + Schedule: "CRON_TZ=UTC * * * * * *", + ObservationSource: client.ObservationSourceSpecBridge(bta), + }) + require.NoError(t, err, "Recreating Cron Job in chainlink node shouldn't fail") + + gom.Eventually(func(g gomega.Gomega) { + jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(job.Data.ID) + if err != nil { + l.Info().Err(err).Msg("error while waiting for job runs") + } + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Reading Job run data shouldn't fail") + + g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically(">=", 5), "Expected number of job runs to be greater than 5, but got %d", len(jobRuns.Data)) + + for _, jr := range jobRuns.Data { + g.Expect(jr.Attributes.Errors).Should(gomega.Equal([]interface{}{nil}), "Job run %s shouldn't have errors", jr.ID) + } + }, "3m", "3s").Should(gomega.Succeed()) + +} diff --git a/integration-tests/smoke/flux_test.go b/integration-tests/smoke/flux_test.go index 8c2b3638bff..72dc15e7b11 100644 --- a/integration-tests/smoke/flux_test.go +++ b/integration-tests/smoke/flux_test.go @@ -1,7 +1,6 @@ package smoke import ( - "context" "fmt" "math/big" "net/http" @@ -14,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -74,7 +74,7 @@ func TestFluxBasic(t *testing.T) { err = env.EVMClient.WaitForEvents() require.NoError(t, err, "Waiting for event subscriptions in nodes shouldn't fail") - oracles, err := fluxInstance.GetOracles(context.Background()) + oracles, err := fluxInstance.GetOracles(testcontext.Get(t)) require.NoError(t, err, "Getting oracle details from the Flux aggregator contract shouldn't fail") l.Info().Str("Oracles", strings.Join(oracles, ",")).Msg("Oracles set") @@ -108,7 +108,7 @@ func TestFluxBasic(t *testing.T) { env.EVMClient.AddHeaderEventSubscription(fluxInstance.Address(), fluxRound) err = env.EVMClient.WaitForEvents() require.NoError(t, err, "Waiting for event subscriptions in nodes shouldn't fail") - data, err := fluxInstance.GetContractData(context.Background()) + data, err := fluxInstance.GetContractData(testcontext.Get(t)) require.NoError(t, err, "Getting contract data from flux aggregator contract shouldn't fail") require.Equal(t, int64(1e5), data.LatestRoundData.Answer.Int64(), "Expected latest round answer to be %d, but found %d", int64(1e5), data.LatestRoundData.Answer.Int64()) @@ -127,7 +127,7 @@ func TestFluxBasic(t *testing.T) { require.NoError(t, err, "Setting value path in mock server shouldn't fail") err = env.EVMClient.WaitForEvents() require.NoError(t, err, "Waiting for event subscriptions in nodes shouldn't fail") - data, err = fluxInstance.GetContractData(context.Background()) + data, err = fluxInstance.GetContractData(testcontext.Get(t)) require.NoError(t, err, "Getting contract data from flux aggregator contract shouldn't fail") require.Equal(t, int64(1e10), data.LatestRoundData.Answer.Int64(), "Expected latest round answer to be %d, but found %d", int64(1e10), data.LatestRoundData.Answer.Int64()) @@ -140,7 +140,7 @@ func TestFluxBasic(t *testing.T) { l.Info().Interface("data", data).Msg("Round data") for _, oracleAddr := range nodeAddresses { - payment, _ := fluxInstance.WithdrawablePayment(context.Background(), oracleAddr) + payment, _ := fluxInstance.WithdrawablePayment(testcontext.Get(t), oracleAddr) require.Equal(t, int64(2), payment.Int64(), "Expected flux aggregator contract's withdrawable payment to be %d, but found %d", int64(2), payment.Int64()) } diff --git a/integration-tests/smoke/forwarder_ocr_test.go b/integration-tests/smoke/forwarder_ocr_test.go index 727b83a601a..d4f9b5884a2 100644 --- a/integration-tests/smoke/forwarder_ocr_test.go +++ b/integration-tests/smoke/forwarder_ocr_test.go @@ -1,7 +1,6 @@ package smoke import ( - "context" "math/big" "testing" @@ -9,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" @@ -72,7 +72,7 @@ func TestForwarderOCRBasic(t *testing.T) { err = env.EVMClient.WaitForEvents() require.NoError(t, err, "Error waiting for events") - answer, err := ocrInstances[0].GetLatestAnswer(context.Background()) + answer, err := ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") require.Equal(t, int64(5), answer.Int64(), "Expected latest answer from OCR contract to be 5 but got %d", answer.Int64()) @@ -83,7 +83,7 @@ func TestForwarderOCRBasic(t *testing.T) { err = env.EVMClient.WaitForEvents() require.NoError(t, err, "Error waiting for events") - answer, err = ocrInstances[0].GetLatestAnswer(context.Background()) + answer, err = ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) require.NoError(t, err, "Error getting latest OCR answer") require.Equal(t, int64(10), answer.Int64(), "Expected latest answer from OCR contract to be 10 but got %d", answer.Int64()) } diff --git a/integration-tests/smoke/forwarders_ocr2_test.go b/integration-tests/smoke/forwarders_ocr2_test.go index baa5a781f6b..7b775cf437f 100644 --- a/integration-tests/smoke/forwarders_ocr2_test.go +++ b/integration-tests/smoke/forwarders_ocr2_test.go @@ -1,7 +1,6 @@ package smoke import ( - "context" "fmt" "math/big" "net/http" @@ -12,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/contracts" @@ -89,10 +89,10 @@ func TestForwarderOCR2Basic(t *testing.T) { err = actions.ConfigureOCRv2AggregatorContracts(env.EVMClient, ocrv2Config, ocrInstances) require.NoError(t, err, "Error configuring OCRv2 aggregator contracts") - err = actions.StartNewOCR2Round(1, ocrInstances, env.EVMClient, time.Minute*10, l) + err = actions.WatchNewOCR2Round(1, ocrInstances, env.EVMClient, time.Minute*10, l) require.NoError(t, err) - answer, err := ocrInstances[0].GetLatestAnswer(context.Background()) + answer, err := ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) require.NoError(t, err, "Getting latest answer from OCRv2 contract shouldn't fail") require.Equal(t, int64(5), answer.Int64(), "Expected latest answer from OCRw contract to be 5 but got %d", answer.Int64()) @@ -100,10 +100,10 @@ func TestForwarderOCR2Basic(t *testing.T) { ocrRoundVal := (5 + i) % 10 err = env.MockAdapter.SetAdapterBasedIntValuePath("ocr2", []string{http.MethodGet, http.MethodPost}, ocrRoundVal) require.NoError(t, err) - err = actions.StartNewOCR2Round(int64(i), ocrInstances, env.EVMClient, time.Minute*10, l) + err = actions.WatchNewOCR2Round(int64(i), ocrInstances, env.EVMClient, time.Minute*10, l) require.NoError(t, err) - answer, err = ocrInstances[0].GetLatestAnswer(context.Background()) + answer, err = ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) require.NoError(t, err, "Error getting latest OCRv2 answer") require.Equal(t, int64(ocrRoundVal), answer.Int64(), fmt.Sprintf("Expected latest answer from OCRv2 contract to be %d but got %d", ocrRoundVal, answer.Int64())) } diff --git a/integration-tests/smoke/keeper_test.go b/integration-tests/smoke/keeper_test.go index d42944fd558..bcf64a5febd 100644 --- a/integration-tests/smoke/keeper_test.go +++ b/integration-tests/smoke/keeper_test.go @@ -1,7 +1,6 @@ package smoke import ( - "context" "fmt" "math/big" "strconv" @@ -14,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/v2/core/store/models" @@ -109,7 +109,7 @@ func TestKeeperBasicSmoke(t *testing.T) { gom.Eventually(func(g gomega.Gomega) error { // Check if the upkeeps are performing multiple times by analyzing their counters and checking they are greater than 10 for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(10)), "Expected consumer counter to be greater than 10, but got %d", counter.Int64()) @@ -131,7 +131,7 @@ func TestKeeperBasicSmoke(t *testing.T) { for i := 0; i < len(upkeepIDs); i++ { // Obtain the amount of times the upkeep has been executed so far - countersAfterCancellation[i], err = consumers[i].Counter(context.Background()) + countersAfterCancellation[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Failed to retrieve consumer counter for upkeep at index %d", i) l.Info().Int("Index", i).Int64("Upkeeps Performed", countersAfterCancellation[i].Int64()).Msg("Cancelled Upkeep") } @@ -139,7 +139,7 @@ func TestKeeperBasicSmoke(t *testing.T) { gom.Consistently(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { // Expect the counter to remain constant because the upkeep was cancelled, so it shouldn't increase anymore - latestCounter, err := consumers[i].Counter(context.Background()) + latestCounter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(latestCounter.Int64()).Should(gomega.Equal(countersAfterCancellation[i].Int64()), "Expected consumer counter to remain constant at %d, but got %d", @@ -187,11 +187,11 @@ func TestKeeperBlockCountPerTurn(t *testing.T) { // Wait for upkeep to be performed twice by different keepers (buddies) gom.Eventually(func(g gomega.Gomega) error { - counter, err := consumers[0].Counter(context.Background()) + counter, err := consumers[0].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") l.Info().Int64("Upkeep counter", counter.Int64()).Msg("Number of upkeeps performed") - upkeepInfo, err := registry.GetUpkeepInfo(context.Background(), upkeepID) + upkeepInfo, err := registry.GetUpkeepInfo(testcontext.Get(t), upkeepID) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Registry's getUpkeep shouldn't fail") latestKeeper := upkeepInfo.LastKeeper @@ -205,7 +205,7 @@ func TestKeeperBlockCountPerTurn(t *testing.T) { }, "1m", "1s").Should(gomega.Succeed()) gom.Eventually(func(g gomega.Gomega) error { - upkeepInfo, err := registry.GetUpkeepInfo(context.Background(), upkeepID) + upkeepInfo, err := registry.GetUpkeepInfo(testcontext.Get(t), upkeepID) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Registry's getUpkeep shouldn't fail") latestKeeper := upkeepInfo.LastKeeper @@ -219,7 +219,7 @@ func TestKeeperBlockCountPerTurn(t *testing.T) { // Expect no new keepers to perform for a while gom.Consistently(func(g gomega.Gomega) { - upkeepInfo, err := registry.GetUpkeepInfo(context.Background(), upkeepID) + upkeepInfo, err := registry.GetUpkeepInfo(testcontext.Get(t), upkeepID) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Registry's getUpkeep shouldn't fail") latestKeeper := upkeepInfo.LastKeeper @@ -235,11 +235,11 @@ func TestKeeperBlockCountPerTurn(t *testing.T) { // Expect a new keeper to perform gom.Eventually(func(g gomega.Gomega) error { - counter, err := consumers[0].Counter(context.Background()) + counter, err := consumers[0].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") l.Info().Int64("Upkeep counter", counter.Int64()).Msg("Num upkeeps performed") - upkeepInfo, err := registry.GetUpkeepInfo(context.Background(), upkeepID) + upkeepInfo, err := registry.GetUpkeepInfo(testcontext.Get(t), upkeepID) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Registry's getUpkeep shouldn't fail") latestKeeper := upkeepInfo.LastKeeper @@ -296,7 +296,7 @@ func TestKeeperSimulation(t *testing.T) { // Initially performGas is set high, so performUpkeep reverts and no upkeep should be performed gom.Consistently(func(g gomega.Gomega) { // Consumer count should remain at 0 - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's Counter shouldn't fail") g.Expect(cnt.Int64()).Should( gomega.Equal(int64(0)), @@ -304,20 +304,20 @@ func TestKeeperSimulation(t *testing.T) { ) // Not even reverted upkeeps should be performed. Last keeper for the upkeep should be 0 address - upkeepInfo, err := registry.GetUpkeepInfo(context.Background(), upkeepID) + upkeepInfo, err := registry.GetUpkeepInfo(testcontext.Get(t), upkeepID) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Registry's getUpkeep shouldn't fail") g.Expect(upkeepInfo.LastKeeper).Should(gomega.Equal(actions.ZeroAddress.String()), "Last keeper should be zero address") }, "1m", "1s").Should(gomega.Succeed()) // Set performGas on consumer to be low, so that performUpkeep starts becoming successful - err = consumerPerformance.SetPerformGasToBurn(context.Background(), big.NewInt(100000)) + err = consumerPerformance.SetPerformGasToBurn(testcontext.Get(t), big.NewInt(100000)) require.NoError(t, err, "Error setting PerformGasToBurn") err = chainClient.WaitForEvents() require.NoError(t, err, "Error waiting to set PerformGasToBurn") // Upkeep should now start performing gom.Eventually(func(g gomega.Gomega) error { - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's Counter shouldn't fail") g.Expect(cnt.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected consumer counter to be greater than 0, but got %d", cnt.Int64(), @@ -368,7 +368,7 @@ func TestKeeperCheckPerformGasLimit(t *testing.T) { // Initially performGas is set higher than defaultUpkeepGasLimit, so no upkeep should be performed gom.Consistently(func(g gomega.Gomega) { - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(cnt.Int64()).Should( gomega.Equal(int64(0)), @@ -384,7 +384,7 @@ func TestKeeperCheckPerformGasLimit(t *testing.T) { // Upkeep should now start performing gom.Eventually(func(g gomega.Gomega) error { - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(cnt.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected consumer counter to be greater than 0, but got %d", cnt.Int64(), @@ -393,13 +393,13 @@ func TestKeeperCheckPerformGasLimit(t *testing.T) { }, "1m", "1s").Should(gomega.Succeed()) // Now increase the checkGasBurn on consumer, upkeep should stop performing - err = consumerPerformance.SetCheckGasToBurn(context.Background(), big.NewInt(3000000)) + err = consumerPerformance.SetCheckGasToBurn(testcontext.Get(t), big.NewInt(3000000)) require.NoError(t, err, "Error setting CheckGasToBurn") err = chainClient.WaitForEvents() require.NoError(t, err, "Error waiting for SetCheckGasToBurn tx") // Get existing performed count - existingCnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + existingCnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) require.NoError(t, err, "Error calling consumer's counter") l.Info().Int64("Upkeep counter", existingCnt.Int64()).Msg("Check Gas Increased") @@ -407,7 +407,7 @@ func TestKeeperCheckPerformGasLimit(t *testing.T) { // gets committed later. Since every keeper node cannot have more than 1 straggling tx, it // is sufficient to check that the upkeep count does not increase by more than 6. gom.Consistently(func(g gomega.Gomega) { - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(cnt.Int64()).Should( gomega.BeNumerically("<=", existingCnt.Int64()+numUpkeepsAllowedForStragglingTxs), @@ -415,7 +415,7 @@ func TestKeeperCheckPerformGasLimit(t *testing.T) { ) }, "3m", "1s").Should(gomega.Succeed()) - existingCnt, err = consumerPerformance.GetUpkeepCount(context.Background()) + existingCnt, err = consumerPerformance.GetUpkeepCount(testcontext.Get(t)) require.NoError(t, err, "Error calling consumer's counter") existingCntInt := existingCnt.Int64() l.Info().Int64("Upkeep counter", existingCntInt).Msg("Upkeep counter when consistently block finished") @@ -430,7 +430,7 @@ func TestKeeperCheckPerformGasLimit(t *testing.T) { // Upkeep should start performing again, and it should get regularly performed gom.Eventually(func(g gomega.Gomega) { - cnt, err := consumerPerformance.GetUpkeepCount(context.Background()) + cnt, err := consumerPerformance.GetUpkeepCount(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's Counter shouldn't fail") g.Expect(cnt.Int64()).Should(gomega.BeNumerically(">", existingCntInt), "Expected consumer counter to be greater than %d, but got %d", existingCntInt, cnt.Int64(), @@ -478,7 +478,7 @@ func TestKeeperRegisterUpkeep(t *testing.T) { // store the value of their initial counters in order to compare later on that the value increased. gom.Eventually(func(g gomega.Gomega) error { for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) initialCounters[i] = counter g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter"+ " for upkeep at index "+strconv.Itoa(i)) @@ -500,7 +500,7 @@ func TestKeeperRegisterUpkeep(t *testing.T) { // Test that the newly registered upkeep is also performing. gom.Eventually(func(g gomega.Gomega) error { - counter, err := newUpkeep.Counter(context.Background()) + counter, err := newUpkeep.Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling newly deployed upkeep's counter shouldn't fail") g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected newly registered upkeep's counter to be greater than 0, but got %d", counter.Int64()) @@ -510,7 +510,7 @@ func TestKeeperRegisterUpkeep(t *testing.T) { gom.Eventually(func(g gomega.Gomega) error { for i := 0; i < len(upkeepIDs); i++ { - currentCounter, err := consumers[i].Counter(context.Background()) + currentCounter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") l.Info(). @@ -563,7 +563,7 @@ func TestKeeperAddFunds(t *testing.T) { // Since the upkeep is currently underfunded, check that it doesn't get executed gom.Consistently(func(g gomega.Gomega) { - counter, err := consumers[0].Counter(context.Background()) + counter, err := consumers[0].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(counter.Int64()).Should(gomega.Equal(int64(0)), "Expected consumer counter to remain zero, but got %d", counter.Int64()) @@ -583,7 +583,7 @@ func TestKeeperAddFunds(t *testing.T) { // Now the new upkeep should be performing because we added enough funds gom.Eventually(func(g gomega.Gomega) { - counter, err := consumers[0].Counter(context.Background()) + counter, err := consumers[0].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected newly registered upkeep's counter to be greater than 0, but got %d", counter.Int64()) @@ -628,7 +628,7 @@ func TestKeeperRemove(t *testing.T) { // Make sure the upkeeps are running before we remove a keeper gom.Eventually(func(g gomega.Gomega) error { for upkeepID := 0; upkeepID < len(upkeepIDs); upkeepID++ { - counter, err := consumers[upkeepID].Counter(context.Background()) + counter, err := consumers[upkeepID].Counter(testcontext.Get(t)) initialCounters[upkeepID] = counter g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter"+ " for upkeep with ID "+strconv.Itoa(upkeepID)) @@ -637,7 +637,7 @@ func TestKeeperRemove(t *testing.T) { return nil }, "1m", "1s").Should(gomega.Succeed()) - keepers, err := registry.GetKeeperList(context.Background()) + keepers, err := registry.GetKeeperList(testcontext.Get(t)) require.NoError(t, err, "Error getting list of Keepers") // Remove the first keeper from the list @@ -660,7 +660,7 @@ func TestKeeperRemove(t *testing.T) { // The upkeeps should still perform and their counters should have increased compared to the first check gom.Eventually(func(g gomega.Gomega) error { for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Cmp(initialCounters[i]) == 1, "Expected consumer counter to be greater "+ "than initial counter which was %s, but got %s", initialCounters[i], counter) @@ -705,7 +705,7 @@ func TestKeeperPauseRegistry(t *testing.T) { // Observe that the upkeeps which are initially registered are performing gom.Eventually(func(g gomega.Gomega) error { for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected consumer counter to be greater than 0, but got %d") @@ -722,7 +722,7 @@ func TestKeeperPauseRegistry(t *testing.T) { // Store how many times each upkeep performed once the registry was successfully paused var countersAfterPause = make([]*big.Int, len(upkeepIDs)) for i := 0; i < len(upkeepIDs); i++ { - countersAfterPause[i], err = consumers[i].Counter(context.Background()) + countersAfterPause[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Error retrieving consumer at index %d", i) } @@ -730,7 +730,7 @@ func TestKeeperPauseRegistry(t *testing.T) { // because they are no longer getting serviced gom.Consistently(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { - latestCounter, err := consumers[i].Counter(context.Background()) + latestCounter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Error retrieving consumer contract at index %d", i) g.Expect(latestCounter.Int64()).Should(gomega.Equal(countersAfterPause[i].Int64()), "Expected consumer counter to remain constant at %d, but got %d", @@ -791,7 +791,7 @@ func TestKeeperMigrateRegistry(t *testing.T) { // Check that the first upkeep from the first registry is performing (before being migrated) gom.Eventually(func(g gomega.Gomega) error { - counterBeforeMigration, err := consumers[0].Counter(context.Background()) + counterBeforeMigration, err := consumers[0].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(counterBeforeMigration.Int64()).Should(gomega.BeNumerically(">", int64(0)), "Expected consumer counter to be greater than 0, but got %s", counterBeforeMigration) @@ -810,12 +810,12 @@ func TestKeeperMigrateRegistry(t *testing.T) { err = chainClient.WaitForEvents() require.NoError(t, err, "Error waiting to pause first registry") - counterAfterMigration, err := consumers[0].Counter(context.Background()) + counterAfterMigration, err := consumers[0].Counter(testcontext.Get(t)) require.NoError(t, err, "Error calling consumer's counter") // Check that once we migrated the upkeep, the counter has increased gom.Eventually(func(g gomega.Gomega) error { - currentCounter, err := consumers[0].Counter(context.Background()) + currentCounter, err := consumers[0].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Calling consumer's counter shouldn't fail") g.Expect(currentCounter.Int64()).Should(gomega.BeNumerically(">", counterAfterMigration.Int64()), "Expected counter to have increased, but stayed constant at %s", counterAfterMigration) @@ -860,7 +860,7 @@ func TestKeeperNodeDown(t *testing.T) { // Watch upkeeps being performed and store their counters in order to compare them later in the test gom.Eventually(func(g gomega.Gomega) error { for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) initialCounters[i] = counter g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(0)), @@ -882,7 +882,7 @@ func TestKeeperNodeDown(t *testing.T) { // Assert that upkeeps are still performed and their counters have increased gom.Eventually(func(g gomega.Gomega) error { for i := 0; i < len(upkeepIDs); i++ { - currentCounter, err := consumers[i].Counter(context.Background()) + currentCounter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(currentCounter.Int64()).Should(gomega.BeNumerically(">", initialCounters[i].Int64()), "Expected counter to have increased from initial value of %s, but got %s", @@ -908,7 +908,7 @@ func TestKeeperNodeDown(t *testing.T) { // See how many times each upkeep was executed var countersAfterNoMoreNodes = make([]*big.Int, len(upkeepIDs)) for i := 0; i < len(upkeepIDs); i++ { - countersAfterNoMoreNodes[i], err = consumers[i].Counter(context.Background()) + countersAfterNoMoreNodes[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Error retrieving consumer counter %d", i) l.Info(). Int("Index", i). @@ -921,7 +921,7 @@ func TestKeeperNodeDown(t *testing.T) { // so a +6 on the upper limit side should be sufficient. gom.Consistently(func(g gomega.Gomega) { for i := 0; i < len(upkeepIDs); i++ { - latestCounter, err := consumers[i].Counter(context.Background()) + latestCounter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(latestCounter.Int64()).Should(gomega.BeNumerically("<=", countersAfterNoMoreNodes[i].Int64()+numUpkeepsAllowedForStragglingTxs, @@ -964,7 +964,7 @@ func TestKeeperPauseUnPauseUpkeep(t *testing.T) { gom.Eventually(func(g gomega.Gomega) error { // Check if the upkeeps are performing multiple times by analysing their counters and checking they are greater than 5 for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(5)), "Expected consumer counter to be greater than 5, but got %d", counter.Int64()) @@ -985,7 +985,7 @@ func TestKeeperPauseUnPauseUpkeep(t *testing.T) { var countersAfterPause = make([]*big.Int, len(upkeepIDs)) for i := 0; i < len(upkeepIDs); i++ { // Obtain the amount of times the upkeep has been executed so far - countersAfterPause[i], err = consumers[i].Counter(context.Background()) + countersAfterPause[i], err = consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Error retrieving upkeep count at index %d", i) l.Info(). Int("Index", i). @@ -998,7 +998,7 @@ func TestKeeperPauseUnPauseUpkeep(t *testing.T) { // In most cases counters should remain constant, but there might be a straggling perform tx which // gets committed later. Since every keeper node cannot have more than 1 straggling tx, it // is sufficient to check that the upkeep count does not increase by more than 6. - latestCounter, err := consumers[i].Counter(context.Background()) + latestCounter, err := consumers[i].Counter(testcontext.Get(t)) require.NoError(t, err, "Error retrieving counter at index %d", i) g.Expect(latestCounter.Int64()).Should(gomega.BeNumerically("<=", countersAfterPause[i].Int64()+numUpkeepsAllowedForStragglingTxs), "Expected consumer counter not have increased more than %d, but got %d", @@ -1018,7 +1018,7 @@ func TestKeeperPauseUnPauseUpkeep(t *testing.T) { gom.Eventually(func(g gomega.Gomega) error { // Check if the upkeeps are performing multiple times by analysing their counters and checking they are greater than 5 + numbers of performing before pause for i := 0; i < len(upkeepIDs); i++ { - counter, err := consumers[i].Counter(context.Background()) + counter, err := consumers[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter"+ " for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(5)+countersAfterPause[i].Int64()), @@ -1055,7 +1055,7 @@ func TestKeeperUpdateCheckData(t *testing.T) { gom.Consistently(func(g gomega.Gomega) { // expect the counter to remain 0 because perform data does not match for i := 0; i < len(upkeepIDs); i++ { - counter, err := performDataChecker[i].Counter(context.Background()) + counter, err := performDataChecker[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve perform data checker for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.Equal(int64(0)), "Expected perform data checker counter to be 0, but got %d", counter.Int64()) @@ -1073,7 +1073,7 @@ func TestKeeperUpdateCheckData(t *testing.T) { // retrieve new check data for all upkeeps for i := 0; i < len(upkeepIDs); i++ { - upkeep, err := registry.GetUpkeepInfo(context.Background(), upkeepIDs[i]) + upkeep, err := registry.GetUpkeepInfo(testcontext.Get(t), upkeepIDs[i]) require.NoError(t, err, "Error getting upkeep info from index %d", i) require.Equal(t, []byte(keeperExpectedData), upkeep.CheckData, "Check data not as expected") } @@ -1081,7 +1081,7 @@ func TestKeeperUpdateCheckData(t *testing.T) { gom.Eventually(func(g gomega.Gomega) error { // Check if the upkeeps are performing multiple times by analysing their counters and checking they are greater than 5 for i := 0; i < len(upkeepIDs); i++ { - counter, err := performDataChecker[i].Counter(context.Background()) + counter, err := performDataChecker[i].Counter(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve perform data checker counter for upkeep at index %d", i) g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(5)), "Expected perform data checker counter to be greater than 5, but got %d", counter.Int64()) @@ -1098,7 +1098,7 @@ func setupKeeperTest(t *testing.T) ( contracts.LinkToken, *test_env.CLClusterTestEnv, ) { - clNodeConfig := node.NewConfig(node.NewBaseConfig(), node.WithP2Pv1()) + clNodeConfig := node.NewConfig(node.NewBaseConfig(), node.WithP2Pv2()) turnLookBack := int64(0) syncInterval := models.MustMakeDuration(5 * time.Second) performGasOverhead := uint32(150000) @@ -1126,3 +1126,69 @@ func setupKeeperTest(t *testing.T) ( return env.EVMClient, env.ClCluster.NodeAPIs(), env.ContractDeployer, linkTokenContract, env } + +func TestKeeperJobReplacement(t *testing.T) { + t.Parallel() + registryVersion := ethereum.RegistryVersion_1_3 + + l := logging.GetTestLogger(t) + chainClient, chainlinkNodes, contractDeployer, linkToken, _ := setupKeeperTest(t) + registry, _, consumers, upkeepIDs := actions.DeployKeeperContracts( + t, + registryVersion, + keeperDefaultRegistryConfig, + keeperDefaultUpkeepsToDeploy, + keeperDefaultUpkeepGasLimit, + linkToken, + contractDeployer, + chainClient, + big.NewInt(keeperDefaultLinkFunds), + ) + gom := gomega.NewGomegaWithT(t) + + _, err := actions.CreateKeeperJobsLocal(l, chainlinkNodes, registry, contracts.OCRv2Config{}, chainClient.GetChainID().String()) + require.NoError(t, err, "Error creating keeper jobs") + err = chainClient.WaitForEvents() + require.NoError(t, err, "Error creating keeper jobs") + + gom.Eventually(func(g gomega.Gomega) error { + // Check if the upkeeps are performing multiple times by analyzing their counters and checking they are greater than 10 + for i := 0; i < len(upkeepIDs); i++ { + counter, err := consumers[i].Counter(testcontext.Get(t)) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) + g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(10)), + "Expected consumer counter to be greater than 10, but got %d", counter.Int64()) + l.Info().Int64("Upkeep counter", counter.Int64()).Msg("Number of upkeeps performed") + } + return nil + }, "5m", "1s").Should(gomega.Succeed()) + + for _, n := range chainlinkNodes { + jobs, _, err := n.ReadJobs() + require.NoError(t, err) + for _, maps := range jobs.Data { + _, ok := maps["id"] + require.Equal(t, true, ok) + id := maps["id"].(string) + _, err := n.DeleteJob(id) + require.NoError(t, err) + } + } + + _, err = actions.CreateKeeperJobsLocal(l, chainlinkNodes, registry, contracts.OCRv2Config{}, chainClient.GetChainID().String()) + require.NoError(t, err, "Error creating keeper jobs") + err = chainClient.WaitForEvents() + require.NoError(t, err, "Error creating keeper jobs") + + gom.Eventually(func(g gomega.Gomega) error { + // Check if the upkeeps are performing multiple times by analyzing their counters and checking they are greater than 10 + for i := 0; i < len(upkeepIDs); i++ { + counter, err := consumers[i].Counter(testcontext.Get(t)) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Failed to retrieve consumer counter for upkeep at index %d", i) + g.Expect(counter.Int64()).Should(gomega.BeNumerically(">", int64(10)), + "Expected consumer counter to be greater than 10, but got %d", counter.Int64()) + l.Info().Int64("Upkeep counter", counter.Int64()).Msg("Number of upkeeps performed") + } + return nil + }, "5m", "1s").Should(gomega.Succeed()) +} diff --git a/integration-tests/smoke/keeper_test.go_test_list.json b/integration-tests/smoke/keeper_test.go_test_list.json index dfa2c49358c..b874bc65ee0 100644 --- a/integration-tests/smoke/keeper_test.go_test_list.json +++ b/integration-tests/smoke/keeper_test.go_test_list.json @@ -53,6 +53,9 @@ }, { "name": "TestKeeperUpdateCheckData" + }, + { + "name": "TestKeeperJobReplacement" } ] } diff --git a/integration-tests/smoke/log_poller_test.go b/integration-tests/smoke/log_poller_test.go new file mode 100644 index 00000000000..03a287ee6b7 --- /dev/null +++ b/integration-tests/smoke/log_poller_test.go @@ -0,0 +1,261 @@ +package smoke + +import ( + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + + logpoller "github.com/smartcontractkit/chainlink/integration-tests/universal/log_poller" +) + +// consistency test with no network disruptions with approximate emission of 1500-1600 logs per second for ~110-120 seconds +// 6 filters are registered +func TestLogPollerFewFiltersFixedDepth(t *testing.T) { + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 2, + EventsPerTx: 4, + UseFinalityTag: false, + }, + LoopedConfig: &logpoller.LoopedConfig{ + ContractConfig: logpoller.ContractConfig{ + ExecutionCount: 100, + }, + FuzzConfig: logpoller.FuzzConfig{ + MinEmitWaitTimeMs: 200, + MaxEmitWaitTimeMs: 500, + }, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit + + logpoller.ExecuteBasicLogPollerTest(t, &cfg) +} + +func TestLogPollerFewFiltersFinalityTag(t *testing.T) { + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 2, + EventsPerTx: 4, + UseFinalityTag: true, + }, + LoopedConfig: &logpoller.LoopedConfig{ + ContractConfig: logpoller.ContractConfig{ + ExecutionCount: 100, + }, + FuzzConfig: logpoller.FuzzConfig{ + MinEmitWaitTimeMs: 200, + MaxEmitWaitTimeMs: 500, + }, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit + + logpoller.ExecuteBasicLogPollerTest(t, &cfg) +} + +// consistency test with no network disruptions with approximate emission of 1000-1100 logs per second for ~110-120 seconds +// 900 filters are registered +func TestLogManyFiltersPollerFixedDepth(t *testing.T) { + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 300, + EventsPerTx: 3, + UseFinalityTag: false, + }, + LoopedConfig: &logpoller.LoopedConfig{ + ContractConfig: logpoller.ContractConfig{ + ExecutionCount: 30, + }, + FuzzConfig: logpoller.FuzzConfig{ + MinEmitWaitTimeMs: 200, + MaxEmitWaitTimeMs: 500, + }, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit + + logpoller.ExecuteBasicLogPollerTest(t, &cfg) +} + +func TestLogManyFiltersPollerFinalityTag(t *testing.T) { + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 300, + EventsPerTx: 3, + UseFinalityTag: true, + }, + LoopedConfig: &logpoller.LoopedConfig{ + ContractConfig: logpoller.ContractConfig{ + ExecutionCount: 30, + }, + FuzzConfig: logpoller.FuzzConfig{ + MinEmitWaitTimeMs: 200, + MaxEmitWaitTimeMs: 500, + }, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit + + logpoller.ExecuteBasicLogPollerTest(t, &cfg) +} + +// consistency test that introduces random distruptions by pausing either Chainlink or Postgres containers for random interval of 5-20 seconds +// with approximate emission of 520-550 logs per second for ~110 seconds +// 6 filters are registered +func TestLogPollerWithChaosFixedDepth(t *testing.T) { + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 2, + EventsPerTx: 100, + UseFinalityTag: false, + }, + LoopedConfig: &logpoller.LoopedConfig{ + ContractConfig: logpoller.ContractConfig{ + ExecutionCount: 100, + }, + FuzzConfig: logpoller.FuzzConfig{ + MinEmitWaitTimeMs: 200, + MaxEmitWaitTimeMs: 500, + }, + }, + ChaosConfig: &logpoller.ChaosConfig{ + ExperimentCount: 10, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit + + logpoller.ExecuteBasicLogPollerTest(t, &cfg) +} + +func TestLogPollerWithChaosFinalityTag(t *testing.T) { + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 2, + EventsPerTx: 100, + UseFinalityTag: true, + }, + LoopedConfig: &logpoller.LoopedConfig{ + ContractConfig: logpoller.ContractConfig{ + ExecutionCount: 100, + }, + FuzzConfig: logpoller.FuzzConfig{ + MinEmitWaitTimeMs: 200, + MaxEmitWaitTimeMs: 500, + }, + }, + ChaosConfig: &logpoller.ChaosConfig{ + ExperimentCount: 10, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit + + logpoller.ExecuteBasicLogPollerTest(t, &cfg) +} + +// consistency test that registers filters after events were emitted and then triggers replay via API +// unfortunately there is no way to make sure that logs that are indexed are only picked up by replay +// and not by backup poller +// with approximate emission of 24 logs per second for ~110 seconds +// 6 filters are registered +func TestLogPollerReplayFixedDepth(t *testing.T) { + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 2, + EventsPerTx: 4, + UseFinalityTag: false, + }, + LoopedConfig: &logpoller.LoopedConfig{ + ContractConfig: logpoller.ContractConfig{ + ExecutionCount: 100, + }, + FuzzConfig: logpoller.FuzzConfig{ + MinEmitWaitTimeMs: 200, + MaxEmitWaitTimeMs: 500, + }, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit + consistencyTimeout := "5m" + + logpoller.ExecuteLogPollerReplay(t, &cfg, consistencyTimeout) +} + +func TestLogPollerReplayFinalityTag(t *testing.T) { + cfg := logpoller.Config{ + General: &logpoller.General{ + Generator: logpoller.GeneratorType_Looped, + Contracts: 2, + EventsPerTx: 4, + UseFinalityTag: false, + }, + LoopedConfig: &logpoller.LoopedConfig{ + ContractConfig: logpoller.ContractConfig{ + ExecutionCount: 100, + }, + FuzzConfig: logpoller.FuzzConfig{ + MinEmitWaitTimeMs: 200, + MaxEmitWaitTimeMs: 500, + }, + }, + } + + eventsToEmit := []abi.Event{} + for _, event := range logpoller.EmitterABI.Events { + eventsToEmit = append(eventsToEmit, event) + } + + cfg.General.EventsToEmit = eventsToEmit + consistencyTimeout := "5m" + + logpoller.ExecuteLogPollerReplay(t, &cfg, consistencyTimeout) +} diff --git a/integration-tests/smoke/ocr2_test.go b/integration-tests/smoke/ocr2_test.go index d82d84a2072..9b30ab497b0 100644 --- a/integration-tests/smoke/ocr2_test.go +++ b/integration-tests/smoke/ocr2_test.go @@ -1,28 +1,18 @@ package smoke import ( - "context" "fmt" "math/big" "net/http" - "strings" "testing" "time" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver-cfg" - "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink-testing-framework/logging" - "github.com/smartcontractkit/chainlink-testing-framework/networks" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" - "github.com/smartcontractkit/chainlink/integration-tests/client" - "github.com/smartcontractkit/chainlink/integration-tests/config" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" @@ -81,9 +71,9 @@ func TestOCRv2Basic(t *testing.T) { err = actions.ConfigureOCRv2AggregatorContracts(env.EVMClient, ocrv2Config, aggregatorContracts) require.NoError(t, err, "Error configuring OCRv2 aggregator contracts") - err = actions.StartNewOCR2Round(1, aggregatorContracts, env.EVMClient, time.Minute*5, l) + err = actions.WatchNewOCR2Round(1, aggregatorContracts, env.EVMClient, time.Minute*5, l) require.NoError(t, err, "Error starting new OCR2 round") - roundData, err := aggregatorContracts[0].GetRound(context.Background(), big.NewInt(1)) + roundData, err := aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(1)) require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") require.Equal(t, int64(5), roundData.Answer.Int64(), "Expected latest answer from OCR contract to be 5 but got %d", @@ -92,10 +82,10 @@ func TestOCRv2Basic(t *testing.T) { err = env.MockAdapter.SetAdapterBasedIntValuePath("ocr2", []string{http.MethodGet, http.MethodPost}, 10) require.NoError(t, err) - err = actions.StartNewOCR2Round(2, aggregatorContracts, env.EVMClient, time.Minute*5, l) + err = actions.WatchNewOCR2Round(2, aggregatorContracts, env.EVMClient, time.Minute*5, l) require.NoError(t, err) - roundData, err = aggregatorContracts[0].GetRound(context.Background(), big.NewInt(2)) + roundData, err = aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(2)) require.NoError(t, err, "Error getting latest OCR answer") require.Equal(t, int64(10), roundData.Answer.Int64(), "Expected latest answer from OCR contract to be 10 but got %d", @@ -103,41 +93,171 @@ func TestOCRv2Basic(t *testing.T) { ) } -func setupOCR2Test(t *testing.T, forwardersEnabled bool) ( - testEnvironment *environment.Environment, - testNetwork blockchain.EVMNetwork, -) { - testNetwork = networks.SelectedNetwork - evmConfig := ethereum.New(nil) - if !testNetwork.Simulated { - evmConfig = ethereum.New(ðereum.Props{ - NetworkName: testNetwork.Name, - Simulated: testNetwork.Simulated, - WsURLs: testNetwork.URLs, - }) +// Tests that just calling requestNewRound() will properly induce more rounds +func TestOCRv2Request(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) + + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithMockAdapter(). + WithCLNodeConfig(node.NewConfig(node.NewBaseConfig(), + node.WithOCR2(), + node.WithP2Pv2(), + node.WithTracing(), + )). + WithCLNodes(6). + WithFunding(big.NewFloat(.1)). + WithStandardCleanup(). + Build() + require.NoError(t, err) + + env.ParallelTransactions(true) + + nodeClients := env.ClCluster.NodeAPIs() + bootstrapNode, workerNodes := nodeClients[0], nodeClients[1:] + + linkToken, err := env.ContractDeployer.DeployLinkTokenContract() + require.NoError(t, err, "Deploying Link Token Contract shouldn't fail") + + err = actions.FundChainlinkNodesLocal(workerNodes, env.EVMClient, big.NewFloat(.05)) + require.NoError(t, err, "Error funding Chainlink nodes") + + // Gather transmitters + var transmitters []string + for _, node := range workerNodes { + addr, err := node.PrimaryEthAddress() + if err != nil { + require.NoError(t, fmt.Errorf("error getting node's primary ETH address: %w", err)) + } + transmitters = append(transmitters, addr) } - var toml string - if forwardersEnabled { - toml = client.AddNetworkDetailedConfig(config.BaseOCR2Config, config.ForwarderNetworkDetailConfig, testNetwork) - } else { - toml = client.AddNetworksConfig(config.BaseOCR2Config, testNetwork) + ocrOffchainOptions := contracts.DefaultOffChainAggregatorOptions() + aggregatorContracts, err := actions.DeployOCRv2Contracts(1, linkToken, env.ContractDeployer, transmitters, env.EVMClient, ocrOffchainOptions) + require.NoError(t, err, "Error deploying OCRv2 aggregator contracts") + + err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), false) + require.NoError(t, err, "Error creating OCRv2 jobs") + + ocrv2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffchainOptions) + require.NoError(t, err, "Error building OCRv2 config") + + err = actions.ConfigureOCRv2AggregatorContracts(env.EVMClient, ocrv2Config, aggregatorContracts) + require.NoError(t, err, "Error configuring OCRv2 aggregator contracts") + + err = actions.WatchNewOCR2Round(1, aggregatorContracts, env.EVMClient, time.Minute*5, l) + require.NoError(t, err, "Error starting new OCR2 round") + roundData, err := aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(1)) + require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") + require.Equal(t, int64(5), roundData.Answer.Int64(), + "Expected latest answer from OCR contract to be 5 but got %d", + roundData.Answer.Int64(), + ) + + // Keep the mockserver value the same and continually request new rounds + for round := 2; round <= 4; round++ { + err = actions.StartNewOCR2Round(int64(round), aggregatorContracts, env.EVMClient, time.Minute*5, l) + require.NoError(t, err, "Error starting new OCR2 round") + roundData, err := aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(int64(round))) + require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") + require.Equal(t, int64(5), roundData.Answer.Int64(), + "Expected round %d answer from OCR contract to be 5 but got %d", + round, + roundData.Answer.Int64(), + ) } +} + +func TestOCRv2JobReplacement(t *testing.T) { + l := logging.GetTestLogger(t) - chainlinkChart := chainlink.New(0, map[string]interface{}{ - "replicas": 6, - "toml": toml, - }) - - testEnvironment = environment.New(&environment.Config{ - NamespacePrefix: fmt.Sprintf("smoke-ocr2-%s", strings.ReplaceAll(strings.ToLower(testNetwork.Name), " ", "-")), - Test: t, - }). - AddHelm(mockservercfg.New(nil)). - AddHelm(mockserver.New(nil)). - AddHelm(evmConfig). - AddHelm(chainlinkChart) - err := testEnvironment.Run() - require.NoError(t, err, "Error running test environment") - return testEnvironment, testNetwork + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithMockAdapter(). + WithCLNodeConfig(node.NewConfig(node.NewBaseConfig(), + node.WithOCR2(), + node.WithP2Pv2(), + node.WithTracing(), + )). + WithCLNodes(6). + WithFunding(big.NewFloat(.1)). + WithStandardCleanup(). + Build() + require.NoError(t, err) + + env.ParallelTransactions(true) + + nodeClients := env.ClCluster.NodeAPIs() + bootstrapNode, workerNodes := nodeClients[0], nodeClients[1:] + + linkToken, err := env.ContractDeployer.DeployLinkTokenContract() + require.NoError(t, err, "Deploying Link Token Contract shouldn't fail") + + err = actions.FundChainlinkNodesLocal(workerNodes, env.EVMClient, big.NewFloat(.05)) + require.NoError(t, err, "Error funding Chainlink nodes") + + // Gather transmitters + var transmitters []string + for _, node := range workerNodes { + addr, err := node.PrimaryEthAddress() + if err != nil { + require.NoError(t, fmt.Errorf("error getting node's primary ETH address: %w", err)) + } + transmitters = append(transmitters, addr) + } + + ocrOffchainOptions := contracts.DefaultOffChainAggregatorOptions() + aggregatorContracts, err := actions.DeployOCRv2Contracts(1, linkToken, env.ContractDeployer, transmitters, env.EVMClient, ocrOffchainOptions) + require.NoError(t, err, "Error deploying OCRv2 aggregator contracts") + + err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), false) + require.NoError(t, err, "Error creating OCRv2 jobs") + + ocrv2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffchainOptions) + require.NoError(t, err, "Error building OCRv2 config") + + err = actions.ConfigureOCRv2AggregatorContracts(env.EVMClient, ocrv2Config, aggregatorContracts) + require.NoError(t, err, "Error configuring OCRv2 aggregator contracts") + + err = actions.WatchNewOCR2Round(1, aggregatorContracts, env.EVMClient, time.Minute*5, l) + require.NoError(t, err, "Error starting new OCR2 round") + roundData, err := aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(1)) + require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") + require.Equal(t, int64(5), roundData.Answer.Int64(), + "Expected latest answer from OCR contract to be 5 but got %d", + roundData.Answer.Int64(), + ) + + err = env.MockAdapter.SetAdapterBasedIntValuePath("ocr2", []string{http.MethodGet, http.MethodPost}, 10) + require.NoError(t, err) + err = actions.WatchNewOCR2Round(2, aggregatorContracts, env.EVMClient, time.Minute*5, l) + require.NoError(t, err) + + roundData, err = aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(2)) + require.NoError(t, err, "Error getting latest OCR answer") + require.Equal(t, int64(10), roundData.Answer.Int64(), + "Expected latest answer from OCR contract to be 10 but got %d", + roundData.Answer.Int64(), + ) + + err = actions.DeleteJobs(nodeClients) + require.NoError(t, err) + + err = actions.DeleteBridges(nodeClients) + require.NoError(t, err) + + err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 15, env.EVMClient.GetChainID().Uint64(), false) + require.NoError(t, err, "Error creating OCRv2 jobs") + + err = actions.WatchNewOCR2Round(3, aggregatorContracts, env.EVMClient, time.Minute*3, l) + require.NoError(t, err, "Error starting new OCR2 round") + roundData, err = aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(3)) + require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") + require.Equal(t, int64(15), roundData.Answer.Int64(), + "Expected latest answer from OCR contract to be 15 but got %d", + roundData.Answer.Int64(), + ) } diff --git a/integration-tests/smoke/ocr2vrf_test.go b/integration-tests/smoke/ocr2vrf_test.go index 81488639187..773476826c7 100644 --- a/integration-tests/smoke/ocr2vrf_test.go +++ b/integration-tests/smoke/ocr2vrf_test.go @@ -9,13 +9,13 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - eth "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + eth "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" - "github.com/smartcontractkit/chainlink-testing-framework/utils" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/actions/ocr2vrf_actions" @@ -44,7 +44,7 @@ func TestOCR2VRFRedeemModel(t *testing.T) { require.NoError(t, err, "Retreiving on-chain wallet addresses for chainlink nodes shouldn't fail") t.Cleanup(func() { - err := actions.TeardownSuite(t, testEnvironment, utils.ProjectRoot, chainlinkNodes, nil, zapcore.ErrorLevel, chainClient) + err := actions.TeardownSuite(t, testEnvironment, chainlinkNodes, nil, zapcore.ErrorLevel, chainClient) require.NoError(t, err, "Error tearing down environment") }) @@ -80,7 +80,7 @@ func TestOCR2VRFRedeemModel(t *testing.T) { ) for i := uint16(0); i < ocr2vrf_constants.NumberOfRandomWordsToRequest; i++ { - randomness, err := consumerContract.GetRandomnessByRequestId(nil, requestID, big.NewInt(int64(i))) + randomness, err := consumerContract.GetRandomnessByRequestId(testcontext.Get(t), requestID, big.NewInt(int64(i))) require.NoError(t, err) l.Info().Interface("Random Number", randomness).Interface("Randomness Number Index", i).Msg("Randomness retrieved from Consumer contract") require.NotEqual(t, 0, randomness.Uint64(), "Randomness retrieved from Consumer contract give an answer other than 0") @@ -106,7 +106,7 @@ func TestOCR2VRFFulfillmentModel(t *testing.T) { require.NoError(t, err, "Retreiving on-chain wallet addresses for chainlink nodes shouldn't fail") t.Cleanup(func() { - err := actions.TeardownSuite(t, testEnvironment, utils.ProjectRoot, chainlinkNodes, nil, zapcore.ErrorLevel, chainClient) + err := actions.TeardownSuite(t, testEnvironment, chainlinkNodes, nil, zapcore.ErrorLevel, chainClient) require.NoError(t, err, "Error tearing down environment") }) @@ -141,7 +141,7 @@ func TestOCR2VRFFulfillmentModel(t *testing.T) { ) for i := uint16(0); i < ocr2vrf_constants.NumberOfRandomWordsToRequest; i++ { - randomness, err := consumerContract.GetRandomnessByRequestId(nil, requestID, big.NewInt(int64(i))) + randomness, err := consumerContract.GetRandomnessByRequestId(testcontext.Get(t), requestID, big.NewInt(int64(i))) require.NoError(t, err, "Error getting Randomness result from Consumer Contract") l.Info().Interface("Random Number", randomness).Interface("Randomness Number Index", i).Msg("Randomness Fulfillment retrieved from Consumer contract") require.NotEqual(t, 0, randomness.Uint64(), "Randomness Fulfillment retrieved from Consumer contract give an answer other than 0") @@ -149,7 +149,7 @@ func TestOCR2VRFFulfillmentModel(t *testing.T) { } func setupOCR2VRFEnvironment(t *testing.T) (testEnvironment *environment.Environment, testNetwork blockchain.EVMNetwork) { - testNetwork = networks.SelectedNetwork + testNetwork = networks.MustGetSelectedNetworksFromEnv()[0] evmConfig := eth.New(nil) if !testNetwork.Simulated { evmConfig = eth.New(ð.Props{ @@ -161,7 +161,7 @@ func setupOCR2VRFEnvironment(t *testing.T) (testEnvironment *environment.Environ cd := chainlink.New(0, map[string]interface{}{ "replicas": 6, - "toml": client.AddNetworkDetailedConfig( + "toml": networks.AddNetworkDetailedConfig( config.BaseOCR2Config, config.DefaultOCR2VRFNetworkDetailTomlConfig, testNetwork, diff --git a/integration-tests/smoke/ocr_test.go b/integration-tests/smoke/ocr_test.go index 8d71c5d08f8..9ed692700ad 100644 --- a/integration-tests/smoke/ocr_test.go +++ b/integration-tests/smoke/ocr_test.go @@ -1,14 +1,13 @@ package smoke import ( - "context" "math/big" "testing" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/logging" - + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" ) @@ -17,6 +16,53 @@ func TestOCRBasic(t *testing.T) { t.Parallel() l := logging.GetTestLogger(t) + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithMockAdapter(). + WithCLNodes(6). + WithFunding(big.NewFloat(.01)). + WithStandardCleanup(). + Build() + require.NoError(t, err) + + env.ParallelTransactions(true) + + nodeClients := env.ClCluster.NodeAPIs() + bootstrapNode, workerNodes := nodeClients[0], nodeClients[1:] + + linkTokenContract, err := env.ContractDeployer.DeployLinkTokenContract() + require.NoError(t, err, "Deploying Link Token Contract shouldn't fail") + + ocrInstances, err := actions.DeployOCRContractsLocal(1, linkTokenContract, env.ContractDeployer, workerNodes, env.EVMClient) + require.NoError(t, err) + err = env.EVMClient.WaitForEvents() + require.NoError(t, err, "Error waiting for events") + + err = actions.CreateOCRJobsLocal(ocrInstances, bootstrapNode, workerNodes, 5, env.MockAdapter, env.EVMClient.GetChainID()) + require.NoError(t, err) + + err = actions.StartNewRound(1, ocrInstances, env.EVMClient, l) + require.NoError(t, err) + + answer, err := ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) + require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") + require.Equal(t, int64(5), answer.Int64(), "Expected latest answer from OCR contract to be 5 but got %d", answer.Int64()) + + err = actions.SetAllAdapterResponsesToTheSameValueLocal(10, ocrInstances, workerNodes, env.MockAdapter) + require.NoError(t, err) + err = actions.StartNewRound(2, ocrInstances, env.EVMClient, l) + require.NoError(t, err) + + answer, err = ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) + require.NoError(t, err, "Error getting latest OCR answer") + require.Equal(t, int64(10), answer.Int64(), "Expected latest answer from OCR contract to be 10 but got %d", answer.Int64()) +} + +func TestOCRJobReplacement(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) + env, err := test_env.NewCLTestEnvBuilder(). WithTestLogger(t). WithGeth(). @@ -40,13 +86,13 @@ func TestOCRBasic(t *testing.T) { err = env.EVMClient.WaitForEvents() require.NoError(t, err, "Error waiting for events") - err = actions.CreateOCRJobsLocal(ocrInstances, bootstrapNode, workerNodes, 5, env.MockAdapter, env.EVMClient.GetChainID().String()) + err = actions.CreateOCRJobsLocal(ocrInstances, bootstrapNode, workerNodes, 5, env.MockAdapter, env.EVMClient.GetChainID()) require.NoError(t, err) err = actions.StartNewRound(1, ocrInstances, env.EVMClient, l) require.NoError(t, err) - answer, err := ocrInstances[0].GetLatestAnswer(context.Background()) + answer, err := ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") require.Equal(t, int64(5), answer.Int64(), "Expected latest answer from OCR contract to be 5 but got %d", answer.Int64()) @@ -55,7 +101,25 @@ func TestOCRBasic(t *testing.T) { err = actions.StartNewRound(2, ocrInstances, env.EVMClient, l) require.NoError(t, err) - answer, err = ocrInstances[0].GetLatestAnswer(context.Background()) + answer, err = ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) require.NoError(t, err, "Error getting latest OCR answer") require.Equal(t, int64(10), answer.Int64(), "Expected latest answer from OCR contract to be 10 but got %d", answer.Int64()) + + err = actions.DeleteJobs(nodeClients) + require.NoError(t, err) + + err = actions.DeleteBridges(nodeClients) + require.NoError(t, err) + + //Recreate job + err = actions.CreateOCRJobsLocal(ocrInstances, bootstrapNode, workerNodes, 5, env.MockAdapter, env.EVMClient.GetChainID()) + require.NoError(t, err) + + err = actions.StartNewRound(1, ocrInstances, env.EVMClient, l) + require.NoError(t, err) + + answer, err = ocrInstances[0].GetLatestAnswer(testcontext.Get(t)) + require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail") + require.Equal(t, int64(10), answer.Int64(), "Expected latest answer from OCR contract to be 10 but got %d", answer.Int64()) + } diff --git a/integration-tests/smoke/runlog_test.go b/integration-tests/smoke/runlog_test.go index f29cb4bc893..e2cd28b3320 100644 --- a/integration-tests/smoke/runlog_test.go +++ b/integration-tests/smoke/runlog_test.go @@ -1,7 +1,6 @@ package smoke import ( - "context" "fmt" "math/big" "net/http" @@ -13,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" @@ -87,7 +87,7 @@ func TestRunLogBasic(t *testing.T) { gom := gomega.NewGomegaWithT(t) gom.Eventually(func(g gomega.Gomega) { - d, err := consumer.Data(context.Background()) + d, err := consumer.Data(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Getting data from consumer contract shouldn't fail") g.Expect(d).ShouldNot(gomega.BeNil(), "Expected the initial on chain data to be nil") l.Debug().Int64("Data", d.Int64()).Msg("Found on chain") diff --git a/integration-tests/smoke/vrf_test.go b/integration-tests/smoke/vrf_test.go index 444d1ce20ee..9c24d97b13b 100644 --- a/integration-tests/smoke/vrf_test.go +++ b/integration-tests/smoke/vrf_test.go @@ -1,7 +1,6 @@ package smoke import ( - "context" "fmt" "math/big" "testing" @@ -12,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv1" @@ -81,7 +81,7 @@ func TestVRFBasic(t *testing.T) { encodedProvingKeys := make([][2]*big.Int, 0) encodedProvingKeys = append(encodedProvingKeys, provingKey) - requestHash, err := contracts.Coordinator.HashOfKey(context.Background(), encodedProvingKeys[0]) + requestHash, err := contracts.Coordinator.HashOfKey(testcontext.Get(t), encodedProvingKeys[0]) require.NoError(t, err, "Getting Hash of encoded proving keys shouldn't fail") err = contracts.Consumer.RequestRandomness(requestHash, big.NewInt(1)) require.NoError(t, err, "Requesting randomness shouldn't fail") @@ -92,7 +92,7 @@ func TestVRFBasic(t *testing.T) { jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(job.Data.ID) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Job execution shouldn't fail") - out, err := contracts.Consumer.RandomnessOutput(context.Background()) + out, err := contracts.Consumer.RandomnessOutput(testcontext.Get(t)) g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Getting the randomness output of the consumer shouldn't fail") // Checks that the job has actually run g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically(">=", 1), @@ -107,3 +107,114 @@ func TestVRFBasic(t *testing.T) { }, timeout, "1s").Should(gomega.Succeed()) } } + +func TestVRFJobReplacement(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) + + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithCLNodes(1). + WithFunding(big.NewFloat(.1)). + WithStandardCleanup(). + Build() + require.NoError(t, err) + env.ParallelTransactions(true) + + lt, err := actions.DeployLINKToken(env.ContractDeployer) + require.NoError(t, err, "Deploying Link Token Contract shouldn't fail") + contracts, err := vrfv1.DeployVRFContracts(env.ContractDeployer, env.EVMClient, lt) + require.NoError(t, err, "Deploying VRF Contracts shouldn't fail") + + err = lt.Transfer(contracts.Consumer.Address(), big.NewInt(2e18)) + require.NoError(t, err, "Funding consumer contract shouldn't fail") + _, err = env.ContractDeployer.DeployVRFContract() + require.NoError(t, err, "Deploying VRF contract shouldn't fail") + err = env.EVMClient.WaitForEvents() + require.NoError(t, err, "Waiting for event subscriptions in nodes shouldn't fail") + + for _, n := range env.ClCluster.Nodes { + nodeKey, err := n.API.MustCreateVRFKey() + require.NoError(t, err, "Creating VRF key shouldn't fail") + l.Debug().Interface("Key JSON", nodeKey).Msg("Created proving key") + pubKeyCompressed := nodeKey.Data.ID + jobUUID := uuid.New() + os := &client.VRFTxPipelineSpec{ + Address: contracts.Coordinator.Address(), + } + ost, err := os.String() + require.NoError(t, err, "Building observation source spec shouldn't fail") + job, err := n.API.MustCreateJob(&client.VRFJobSpec{ + Name: fmt.Sprintf("vrf-%s", jobUUID), + CoordinatorAddress: contracts.Coordinator.Address(), + MinIncomingConfirmations: 1, + PublicKey: pubKeyCompressed, + ExternalJobID: jobUUID.String(), + EVMChainID: env.EVMClient.GetChainID().String(), + ObservationSource: ost, + }) + require.NoError(t, err, "Creating VRF Job shouldn't fail") + + oracleAddr, err := n.API.PrimaryEthAddress() + require.NoError(t, err, "Getting primary ETH address of chainlink node shouldn't fail") + provingKey, err := actions.EncodeOnChainVRFProvingKey(*nodeKey) + require.NoError(t, err, "Encoding on-chain VRF Proving key shouldn't fail") + err = contracts.Coordinator.RegisterProvingKey( + big.NewInt(1), + oracleAddr, + provingKey, + actions.EncodeOnChainExternalJobID(jobUUID), + ) + require.NoError(t, err, "Registering the on-chain VRF Proving key shouldn't fail") + encodedProvingKeys := make([][2]*big.Int, 0) + encodedProvingKeys = append(encodedProvingKeys, provingKey) + + requestHash, err := contracts.Coordinator.HashOfKey(testcontext.Get(t), encodedProvingKeys[0]) + require.NoError(t, err, "Getting Hash of encoded proving keys shouldn't fail") + err = contracts.Consumer.RequestRandomness(requestHash, big.NewInt(1)) + require.NoError(t, err, "Requesting randomness shouldn't fail") + + gom := gomega.NewGomegaWithT(t) + timeout := time.Minute * 2 + gom.Eventually(func(g gomega.Gomega) { + jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(job.Data.ID) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Job execution shouldn't fail") + + out, err := contracts.Consumer.RandomnessOutput(testcontext.Get(t)) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Getting the randomness output of the consumer shouldn't fail") + // Checks that the job has actually run + g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically(">=", 1), + fmt.Sprintf("Expected the VRF job to run once or more after %s", timeout)) + + g.Expect(out.Uint64()).ShouldNot(gomega.BeNumerically("==", 0), "Expected the VRF job give an answer other than 0") + l.Debug().Uint64("Output", out.Uint64()).Msg("Randomness fulfilled") + }, timeout, "1s").Should(gomega.Succeed()) + + err = n.API.MustDeleteJob(job.Data.ID) + require.NoError(t, err) + + job, err = n.API.MustCreateJob(&client.VRFJobSpec{ + Name: fmt.Sprintf("vrf-%s", jobUUID), + CoordinatorAddress: contracts.Coordinator.Address(), + MinIncomingConfirmations: 1, + PublicKey: pubKeyCompressed, + ExternalJobID: jobUUID.String(), + EVMChainID: env.EVMClient.GetChainID().String(), + ObservationSource: ost, + }) + require.NoError(t, err, "Recreating VRF Job shouldn't fail") + gom.Eventually(func(g gomega.Gomega) { + jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(job.Data.ID) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Job execution shouldn't fail") + + out, err := contracts.Consumer.RandomnessOutput(testcontext.Get(t)) + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "Getting the randomness output of the consumer shouldn't fail") + // Checks that the job has actually run + g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically(">=", 1), + fmt.Sprintf("Expected the VRF job to run once or more after %s", timeout)) + g.Expect(out.Uint64()).ShouldNot(gomega.BeNumerically("==", 0), "Expected the VRF job give an answer other than 0") + l.Debug().Uint64("Output", out.Uint64()).Msg("Randomness fulfilled") + }, timeout, "1s").Should(gomega.Succeed()) + } +} diff --git a/integration-tests/smoke/vrfv2_test.go b/integration-tests/smoke/vrfv2_test.go index c960bb6c691..4edb92e5df6 100644 --- a/integration-tests/smoke/vrfv2_test.go +++ b/integration-tests/smoke/vrfv2_test.go @@ -4,112 +4,197 @@ import ( "context" "math/big" "testing" - "time" - "github.com/onsi/gomega" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/kelseyhightower/envconfig" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions" + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_config" + "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink/integration-tests/actions" - "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions" - vrfConst "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_constants" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" - "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" ) func TestVRFv2Basic(t *testing.T) { t.Parallel() l := logging.GetTestLogger(t) + var vrfv2Config vrfv2_config.VRFV2Config + err := envconfig.Process("VRFV2", &vrfv2Config) + require.NoError(t, err) + env, err := test_env.NewCLTestEnvBuilder(). WithTestLogger(t). WithGeth(). WithCLNodes(1). - WithFunding(vrfConst.ChainlinkNodeFundingAmountEth). + WithFunding(big.NewFloat(vrfv2Config.ChainlinkNodeFunding)). WithStandardCleanup(). Build() - require.NoError(t, err) + require.NoError(t, err, "error creating test env") + env.ParallelTransactions(true) - mockFeed, err := actions.DeployMockETHLinkFeed(env.ContractDeployer, vrfConst.LinkEthFeedResponse) + mockETHLinkFeed, err := actions.DeployMockETHLinkFeed(env.ContractDeployer, big.NewInt(vrfv2Config.LinkNativeFeedResponse)) require.NoError(t, err) - lt, err := actions.DeployLINKToken(env.ContractDeployer) - require.NoError(t, err) - vrfv2Contracts, err := vrfv2_actions.DeployVRFV2Contracts(env.ContractDeployer, env.EVMClient, lt, mockFeed) + linkToken, err := actions.DeployLINKToken(env.ContractDeployer) require.NoError(t, err) - err = env.EVMClient.WaitForEvents() - require.NoError(t, err) - - err = vrfv2Contracts.Coordinator.SetConfig( - vrfConst.MinimumConfirmations, - vrfConst.MaxGasLimitVRFCoordinatorConfig, - vrfConst.StalenessSeconds, - vrfConst.GasAfterPaymentCalculation, - vrfConst.LinkEthFeedResponse, - vrfConst.VRFCoordinatorV2FeeConfig, + // register proving key against oracle address (sending key) in order to test oracleWithdraw + defaultWalletAddress := env.EVMClient.GetDefaultWallet().Address() + + numberOfTxKeysToCreate := 1 + vrfv2Contracts, subIDs, vrfv2Data, err := vrfv2_actions.SetupVRFV2Environment( + env, + vrfv2Config, + linkToken, + mockETHLinkFeed, + defaultWalletAddress, + numberOfTxKeysToCreate, + 1, + 1, + l, ) - require.NoError(t, err) - err = env.EVMClient.WaitForEvents() - require.NoError(t, err) + require.NoError(t, err, "error setting up VRF v2 env") + + subID := subIDs[0] + + subscription, err := vrfv2Contracts.Coordinator.GetSubscription(context.Background(), subID) + require.NoError(t, err, "error getting subscription information") + + vrfv2_actions.LogSubDetails(l, subscription, subID, vrfv2Contracts.Coordinator) + + t.Run("Request Randomness", func(t *testing.T) { + testConfig := vrfv2Config + subBalanceBeforeRequest := subscription.Balance + + jobRunsBeforeTest, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(vrfv2Data.VRFJob.Data.ID) + require.NoError(t, err, "error reading job runs") + + // test and assert + randomWordsFulfilledEvent, err := vrfv2_actions.RequestRandomnessAndWaitForFulfillment( + vrfv2Contracts.LoadTestConsumers[0], + vrfv2Contracts.Coordinator, + vrfv2Data, + subID, + testConfig.RandomnessRequestCountPerRequest, + testConfig, + testConfig.RandomWordsFulfilledEventTimeout, + l, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + expectedSubBalanceJuels := new(big.Int).Sub(subBalanceBeforeRequest, randomWordsFulfilledEvent.Payment) + subscription, err = vrfv2Contracts.Coordinator.GetSubscription(context.Background(), subID) + require.NoError(t, err, "error getting subscription information") + subBalanceAfterRequest := subscription.Balance + require.Equal(t, expectedSubBalanceJuels, subBalanceAfterRequest) + + jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(vrfv2Data.VRFJob.Data.ID) + require.NoError(t, err, "error reading job runs") + require.Equal(t, len(jobRunsBeforeTest.Data)+1, len(jobRuns.Data)) + + status, err := vrfv2Contracts.LoadTestConsumers[0].GetRequestStatus(context.Background(), randomWordsFulfilledEvent.RequestId) + require.NoError(t, err, "error getting rand request status") + require.True(t, status.Fulfilled) + l.Debug().Bool("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") + + require.Equal(t, testConfig.NumberOfWords, uint32(len(status.RandomWords))) + for _, w := range status.RandomWords { + l.Info().Str("Output", w.String()).Msg("Randomness fulfilled") + require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") + } + }) +} - err = vrfv2Contracts.Coordinator.CreateSubscription() - require.NoError(t, err) - err = env.EVMClient.WaitForEvents() - require.NoError(t, err) +func TestVRFv2MultipleSendingKeys(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) - err = vrfv2Contracts.Coordinator.AddConsumer(vrfConst.SubID, vrfv2Contracts.LoadTestConsumer.Address()) + var vrfv2Config vrfv2_config.VRFV2Config + err := envconfig.Process("VRFV2", &vrfv2Config) require.NoError(t, err) - err = vrfv2_actions.FundVRFCoordinatorV2Subscription(lt, vrfv2Contracts.Coordinator, env.EVMClient, vrfConst.SubID, vrfConst.VRFSubscriptionFundingAmountLink) - require.NoError(t, err) + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithCLNodes(1). + WithFunding(big.NewFloat(vrfv2Config.ChainlinkNodeFunding)). + WithStandardCleanup(). + Build() + require.NoError(t, err, "error creating test env") - vrfV2jobs, err := vrfv2_actions.CreateVRFV2Jobs(env.ClCluster.NodeAPIs(), vrfv2Contracts.Coordinator, env.EVMClient, vrfConst.MinimumConfirmations) - require.NoError(t, err) + env.ParallelTransactions(true) - // this part is here because VRFv2 can work with only a specific key - // [[EVM.KeySpecific]] - // Key = '...' - addr, err := env.ClCluster.Nodes[0].API.PrimaryEthAddress() + mockETHLinkFeed, err := actions.DeployMockETHLinkFeed(env.ContractDeployer, big.NewInt(vrfv2Config.LinkNativeFeedResponse)) require.NoError(t, err) - nodeConfig := node.NewConfig(env.ClCluster.Nodes[0].NodeConfig, - node.WithVRFv2EVMEstimator(addr), - ) - err = env.ClCluster.Nodes[0].Restart(nodeConfig) + linkToken, err := actions.DeployLINKToken(env.ContractDeployer) require.NoError(t, err) - // test and assert - err = vrfv2Contracts.LoadTestConsumer.RequestRandomness( - vrfV2jobs[0].KeyHash, - vrfConst.SubID, - vrfConst.MinimumConfirmations, - vrfConst.CallbackGasLimit, - vrfConst.NumberOfWords, - vrfConst.RandomnessRequestCountPerRequest, + // register proving key against oracle address (sending key) in order to test oracleWithdraw + defaultWalletAddress := env.EVMClient.GetDefaultWallet().Address() + + numberOfTxKeysToCreate := 2 + vrfv2Contracts, subIDs, vrfv2Data, err := vrfv2_actions.SetupVRFV2Environment( + env, + vrfv2Config, + linkToken, + mockETHLinkFeed, + defaultWalletAddress, + numberOfTxKeysToCreate, + 1, + 1, + l, ) - require.NoError(t, err) - - gom := gomega.NewGomegaWithT(t) - timeout := time.Minute * 2 - var lastRequestID *big.Int - gom.Eventually(func(g gomega.Gomega) { - jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(vrfV2jobs[0].Job.Data.ID) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - g.Expect(len(jobRuns.Data)).Should(gomega.BeNumerically("==", 1)) - lastRequestID, err = vrfv2Contracts.LoadTestConsumer.GetLastRequestId(context.Background()) - l.Debug().Interface("Last Request ID", lastRequestID).Msg("Last Request ID Received") - - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - status, err := vrfv2Contracts.LoadTestConsumer.GetRequestStatus(context.Background(), lastRequestID) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - g.Expect(status.Fulfilled).Should(gomega.BeTrue()) - l.Debug().Interface("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") - - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - for _, w := range status.RandomWords { - l.Info().Uint64("Output", w.Uint64()).Msg("Randomness fulfilled") - g.Expect(w.Uint64()).Should(gomega.BeNumerically(">", 0), "Expected the VRF job give an answer bigger than 0") + require.NoError(t, err, "error setting up VRF v2 env") + + subID := subIDs[0] + + subscription, err := vrfv2Contracts.Coordinator.GetSubscription(context.Background(), subID) + require.NoError(t, err, "error getting subscription information") + + vrfv2_actions.LogSubDetails(l, subscription, subID, vrfv2Contracts.Coordinator) + + t.Run("Request Randomness with multiple sending keys", func(t *testing.T) { + testConfig := vrfv2Config + txKeys, _, err := env.ClCluster.Nodes[0].API.ReadTxKeys("evm") + require.NoError(t, err, "error reading tx keys") + + require.Equal(t, numberOfTxKeysToCreate+1, len(txKeys.Data)) + + var fulfillmentTxFromAddresses []string + for i := 0; i < numberOfTxKeysToCreate+1; i++ { + randomWordsFulfilledEvent, err := vrfv2_actions.RequestRandomnessAndWaitForFulfillment( + vrfv2Contracts.LoadTestConsumers[0], + vrfv2Contracts.Coordinator, + vrfv2Data, + subID, + testConfig.RandomnessRequestCountPerRequest, + testConfig, + testConfig.RandomWordsFulfilledEventTimeout, + l, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + //todo - move TransactionByHash to EVMClient in CTF + fulfillmentTx, _, err := env.EVMClient.(*blockchain.EthereumMultinodeClient).DefaultClient.(*blockchain.EthereumClient). + Client.TransactionByHash(context.Background(), randomWordsFulfilledEvent.Raw.TxHash) + require.NoError(t, err, "error getting tx from hash") + fulfillmentTxFromAddress, err := actions.GetTxFromAddress(fulfillmentTx) + require.NoError(t, err, "error getting tx from address") + fulfillmentTxFromAddresses = append(fulfillmentTxFromAddresses, fulfillmentTxFromAddress) + } + require.Equal(t, numberOfTxKeysToCreate+1, len(fulfillmentTxFromAddresses)) + var txKeyAddresses []string + for _, txKey := range txKeys.Data { + txKeyAddresses = append(txKeyAddresses, txKey.ID) } - }, timeout, "1s").Should(gomega.Succeed()) + less := func(a, b string) bool { return a < b } + equalIgnoreOrder := cmp.Diff(txKeyAddresses, fulfillmentTxFromAddresses, cmpopts.SortSlices(less)) == "" + require.True(t, equalIgnoreOrder) + }) } diff --git a/integration-tests/smoke/vrfv2plus_test.go b/integration-tests/smoke/vrfv2plus_test.go index c2cc0878b66..f4f52b6ee01 100644 --- a/integration-tests/smoke/vrfv2plus_test.go +++ b/integration-tests/smoke/vrfv2plus_test.go @@ -2,23 +2,27 @@ package smoke import ( "context" + "fmt" "math/big" "testing" "time" - "github.com/kelseyhightower/envconfig" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_v2plus_upgraded_version" - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/kelseyhightower/envconfig" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_v2plus_upgraded_version" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2plus" "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2plus/vrfv2plus_config" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" + it_utils "github.com/smartcontractkit/chainlink/integration-tests/utils" ) func TestVRFv2Plus(t *testing.T) { @@ -46,17 +50,32 @@ func TestVRFv2Plus(t *testing.T) { linkToken, err := actions.DeployLINKToken(env.ContractDeployer) require.NoError(t, err, "error deploying LINK contract") - vrfv2PlusContracts, subIDs, vrfv2PlusData, err := vrfv2plus.SetupVRFV2_5Environment(env, &vrfv2PlusConfig, linkToken, mockETHLinkFeed, 1, 1) + // register proving key against oracle address (sending key) in order to test oracleWithdraw + defaultWalletAddress := env.EVMClient.GetDefaultWallet().Address() + + numberOfTxKeysToCreate := 2 + vrfv2PlusContracts, subIDs, vrfv2PlusData, err := vrfv2plus.SetupVRFV2_5Environment( + env, + vrfv2PlusConfig, + linkToken, + mockETHLinkFeed, + defaultWalletAddress, + numberOfTxKeysToCreate, + 1, + 1, + l, + ) require.NoError(t, err, "error setting up VRF v2_5 env") subID := subIDs[0] - subscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), subID) + subscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subID) require.NoError(t, err, "error getting subscription information") vrfv2plus.LogSubDetails(l, subscription, subID, vrfv2PlusContracts.Coordinator) - t.Run("VRFV2 Plus With Link Billing", func(t *testing.T) { + t.Run("Link Billing", func(t *testing.T) { + testConfig := vrfv2PlusConfig var isNativeBilling = false subBalanceBeforeRequest := subscription.Balance @@ -70,14 +89,15 @@ func TestVRFv2Plus(t *testing.T) { vrfv2PlusData, subID, isNativeBilling, - vrfv2PlusConfig.RandomnessRequestCountPerRequest, - &vrfv2PlusConfig, + testConfig.RandomnessRequestCountPerRequest, + testConfig, + testConfig.RandomWordsFulfilledEventTimeout, l, ) require.NoError(t, err, "error requesting randomness and waiting for fulfilment") expectedSubBalanceJuels := new(big.Int).Sub(subBalanceBeforeRequest, randomWordsFulfilledEvent.Payment) - subscription, err = vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), subID) + subscription, err = vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subID) require.NoError(t, err, "error getting subscription information") subBalanceAfterRequest := subscription.Balance require.Equal(t, expectedSubBalanceJuels, subBalanceAfterRequest) @@ -86,19 +106,19 @@ func TestVRFv2Plus(t *testing.T) { require.NoError(t, err, "error reading job runs") require.Equal(t, len(jobRunsBeforeTest.Data)+1, len(jobRuns.Data)) - status, err := vrfv2PlusContracts.LoadTestConsumers[0].GetRequestStatus(context.Background(), randomWordsFulfilledEvent.RequestId) + status, err := vrfv2PlusContracts.LoadTestConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) require.NoError(t, err, "error getting rand request status") require.True(t, status.Fulfilled) l.Debug().Bool("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") - require.Equal(t, vrfv2PlusConfig.NumberOfWords, uint32(len(status.RandomWords))) + require.Equal(t, testConfig.NumberOfWords, uint32(len(status.RandomWords))) for _, w := range status.RandomWords { l.Info().Str("Output", w.String()).Msg("Randomness fulfilled") require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") } }) - - t.Run("VRFV2 Plus With Native Billing", func(t *testing.T) { + t.Run("Native Billing", func(t *testing.T) { + testConfig := vrfv2PlusConfig var isNativeBilling = true subNativeTokenBalanceBeforeRequest := subscription.NativeBalance @@ -112,13 +132,14 @@ func TestVRFv2Plus(t *testing.T) { vrfv2PlusData, subID, isNativeBilling, - vrfv2PlusConfig.RandomnessRequestCountPerRequest, - &vrfv2PlusConfig, + testConfig.RandomnessRequestCountPerRequest, + testConfig, + testConfig.RandomWordsFulfilledEventTimeout, l, ) require.NoError(t, err, "error requesting randomness and waiting for fulfilment") expectedSubBalanceWei := new(big.Int).Sub(subNativeTokenBalanceBeforeRequest, randomWordsFulfilledEvent.Payment) - subscription, err = vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), subID) + subscription, err = vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subID) require.NoError(t, err) subBalanceAfterRequest := subscription.NativeBalance require.Equal(t, expectedSubBalanceWei, subBalanceAfterRequest) @@ -127,125 +148,549 @@ func TestVRFv2Plus(t *testing.T) { require.NoError(t, err, "error reading job runs") require.Equal(t, len(jobRunsBeforeTest.Data)+1, len(jobRuns.Data)) - status, err := vrfv2PlusContracts.LoadTestConsumers[0].GetRequestStatus(context.Background(), randomWordsFulfilledEvent.RequestId) + status, err := vrfv2PlusContracts.LoadTestConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) require.NoError(t, err, "error getting rand request status") require.True(t, status.Fulfilled) l.Debug().Bool("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") - require.Equal(t, vrfv2PlusConfig.NumberOfWords, uint32(len(status.RandomWords))) + require.Equal(t, testConfig.NumberOfWords, uint32(len(status.RandomWords))) for _, w := range status.RandomWords { l.Info().Str("Output", w.String()).Msg("Randomness fulfilled") require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") } }) + t.Run("Direct Funding (VRFV2PlusWrapper)", func(t *testing.T) { + testConfig := vrfv2PlusConfig + wrapperContracts, wrapperSubID, err := vrfv2plus.SetupVRFV2PlusWrapperEnvironment( + env, + testConfig, + linkToken, + mockETHLinkFeed, + vrfv2PlusContracts.Coordinator, + vrfv2PlusData.KeyHash, + 1, + ) + require.NoError(t, err) - wrapperContracts, wrapperSubID, err := vrfv2plus.SetupVRFV2PlusWrapperEnvironment( - env, - &vrfv2PlusConfig, - linkToken, - mockETHLinkFeed, - vrfv2PlusContracts.Coordinator, - vrfv2PlusData.KeyHash, - 1, - ) - require.NoError(t, err) + t.Run("Link Billing", func(t *testing.T) { + testConfig := vrfv2PlusConfig + var isNativeBilling = false + + wrapperConsumerJuelsBalanceBeforeRequest, err := linkToken.BalanceOf(testcontext.Get(t), wrapperContracts.LoadTestConsumers[0].Address()) + require.NoError(t, err, "error getting wrapper consumer balance") + + wrapperSubscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), wrapperSubID) + require.NoError(t, err, "error getting subscription information") + subBalanceBeforeRequest := wrapperSubscription.Balance + + randomWordsFulfilledEvent, err := vrfv2plus.DirectFundingRequestRandomnessAndWaitForFulfillment( + wrapperContracts.LoadTestConsumers[0], + vrfv2PlusContracts.Coordinator, + vrfv2PlusData, + wrapperSubID, + isNativeBilling, + testConfig, + testConfig.RandomWordsFulfilledEventTimeout, + l, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + expectedSubBalanceJuels := new(big.Int).Sub(subBalanceBeforeRequest, randomWordsFulfilledEvent.Payment) + wrapperSubscription, err = vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), wrapperSubID) + require.NoError(t, err, "error getting subscription information") + subBalanceAfterRequest := wrapperSubscription.Balance + require.Equal(t, expectedSubBalanceJuels, subBalanceAfterRequest) + + consumerStatus, err := wrapperContracts.LoadTestConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) + require.NoError(t, err, "error getting rand request status") + require.True(t, consumerStatus.Fulfilled) + + expectedWrapperConsumerJuelsBalance := new(big.Int).Sub(wrapperConsumerJuelsBalanceBeforeRequest, consumerStatus.Paid) + + wrapperConsumerJuelsBalanceAfterRequest, err := linkToken.BalanceOf(testcontext.Get(t), wrapperContracts.LoadTestConsumers[0].Address()) + require.NoError(t, err, "error getting wrapper consumer balance") + require.Equal(t, expectedWrapperConsumerJuelsBalance, wrapperConsumerJuelsBalanceAfterRequest) + + //todo: uncomment when VRF-651 will be fixed + //require.Equal(t, 1, consumerStatus.Paid.Cmp(randomWordsFulfilledEvent.Payment), "Expected Consumer contract pay more than the Coordinator Sub") + vrfv2plus.LogFulfillmentDetailsLinkBilling(l, wrapperConsumerJuelsBalanceBeforeRequest, wrapperConsumerJuelsBalanceAfterRequest, consumerStatus, randomWordsFulfilledEvent) + + require.Equal(t, testConfig.NumberOfWords, uint32(len(consumerStatus.RandomWords))) + for _, w := range consumerStatus.RandomWords { + l.Info().Str("Output", w.String()).Msg("Randomness fulfilled") + require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") + } + }) + t.Run("Native Billing", func(t *testing.T) { + testConfig := vrfv2PlusConfig + var isNativeBilling = true + + wrapperConsumerBalanceBeforeRequestWei, err := env.EVMClient.BalanceAt(testcontext.Get(t), common.HexToAddress(wrapperContracts.LoadTestConsumers[0].Address())) + require.NoError(t, err, "error getting wrapper consumer balance") + + wrapperSubscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), wrapperSubID) + require.NoError(t, err, "error getting subscription information") + subBalanceBeforeRequest := wrapperSubscription.NativeBalance + + randomWordsFulfilledEvent, err := vrfv2plus.DirectFundingRequestRandomnessAndWaitForFulfillment( + wrapperContracts.LoadTestConsumers[0], + vrfv2PlusContracts.Coordinator, + vrfv2PlusData, + wrapperSubID, + isNativeBilling, + testConfig, + testConfig.RandomWordsFulfilledEventTimeout, + l, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + expectedSubBalanceWei := new(big.Int).Sub(subBalanceBeforeRequest, randomWordsFulfilledEvent.Payment) + wrapperSubscription, err = vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), wrapperSubID) + require.NoError(t, err, "error getting subscription information") + subBalanceAfterRequest := wrapperSubscription.NativeBalance + require.Equal(t, expectedSubBalanceWei, subBalanceAfterRequest) + + consumerStatus, err := wrapperContracts.LoadTestConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) + require.NoError(t, err, "error getting rand request status") + require.True(t, consumerStatus.Fulfilled) + + expectedWrapperConsumerWeiBalance := new(big.Int).Sub(wrapperConsumerBalanceBeforeRequestWei, consumerStatus.Paid) + + wrapperConsumerBalanceAfterRequestWei, err := env.EVMClient.BalanceAt(testcontext.Get(t), common.HexToAddress(wrapperContracts.LoadTestConsumers[0].Address())) + require.NoError(t, err, "error getting wrapper consumer balance") + require.Equal(t, expectedWrapperConsumerWeiBalance, wrapperConsumerBalanceAfterRequestWei) + + //todo: uncomment when VRF-651 will be fixed + //require.Equal(t, 1, consumerStatus.Paid.Cmp(randomWordsFulfilledEvent.Payment), "Expected Consumer contract pay more than the Coordinator Sub") + vrfv2plus.LogFulfillmentDetailsNativeBilling(l, wrapperConsumerBalanceBeforeRequestWei, wrapperConsumerBalanceAfterRequestWei, consumerStatus, randomWordsFulfilledEvent) + + require.Equal(t, testConfig.NumberOfWords, uint32(len(consumerStatus.RandomWords))) + for _, w := range consumerStatus.RandomWords { + l.Info().Str("Output", w.String()).Msg("Randomness fulfilled") + require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") + } + }) + }) + t.Run("Canceling Sub And Returning Funds", func(t *testing.T) { + testConfig := vrfv2PlusConfig + subIDsForCancelling, err := vrfv2plus.CreateFundSubsAndAddConsumers( + env, + testConfig, + linkToken, + vrfv2PlusContracts.Coordinator, + vrfv2PlusContracts.LoadTestConsumers, + 1, + ) + require.NoError(t, err) + subIDForCancelling := subIDsForCancelling[0] - t.Run("VRFV2 Plus With Direct Funding (VRFV2PlusWrapper) - Link Billing", func(t *testing.T) { - var isNativeBilling = false + testWalletAddress, err := actions.GenerateWallet() + require.NoError(t, err) + + testWalletBalanceNativeBeforeSubCancelling, err := env.EVMClient.BalanceAt(testcontext.Get(t), testWalletAddress) + require.NoError(t, err) - wrapperConsumerJuelsBalanceBeforeRequest, err := linkToken.BalanceOf(context.Background(), wrapperContracts.LoadTestConsumers[0].Address()) - require.NoError(t, err, "error getting wrapper consumer balance") + testWalletBalanceLinkBeforeSubCancelling, err := linkToken.BalanceOf(testcontext.Get(t), testWalletAddress.String()) + require.NoError(t, err) - wrapperSubscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), wrapperSubID) + subscriptionForCancelling, err := vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subIDForCancelling) require.NoError(t, err, "error getting subscription information") - subBalanceBeforeRequest := wrapperSubscription.Balance - randomWordsFulfilledEvent, err := vrfv2plus.DirectFundingRequestRandomnessAndWaitForFulfillment( - wrapperContracts.LoadTestConsumers[0], + subBalanceLink := subscriptionForCancelling.Balance + subBalanceNative := subscriptionForCancelling.NativeBalance + l.Info(). + Str("Subscription Amount Native", subBalanceNative.String()). + Str("Subscription Amount Link", subBalanceLink.String()). + Str("Returning funds from SubID", subIDForCancelling.String()). + Str("Returning funds to", testWalletAddress.String()). + Msg("Canceling subscription and returning funds to subscription owner") + tx, err := vrfv2PlusContracts.Coordinator.CancelSubscription(subIDForCancelling, testWalletAddress) + require.NoError(t, err, "Error canceling subscription") + + subscriptionCanceledEvent, err := vrfv2PlusContracts.Coordinator.WaitForSubscriptionCanceledEvent(subIDForCancelling, time.Second*30) + require.NoError(t, err, "error waiting for subscription canceled event") + + cancellationTxReceipt, err := env.EVMClient.GetTxReceipt(tx.Hash()) + require.NoError(t, err, "error getting tx cancellation Tx Receipt") + + txGasUsed := new(big.Int).SetUint64(cancellationTxReceipt.GasUsed) + cancellationTxFeeWei := new(big.Int).Mul(txGasUsed, cancellationTxReceipt.EffectiveGasPrice) + + l.Info(). + Str("Cancellation Tx Fee Wei", cancellationTxFeeWei.String()). + Str("Effective Gas Price", cancellationTxReceipt.EffectiveGasPrice.String()). + Uint64("Gas Used", cancellationTxReceipt.GasUsed). + Msg("Cancellation TX Receipt") + + l.Info(). + Str("Returned Subscription Amount Native", subscriptionCanceledEvent.AmountNative.String()). + Str("Returned Subscription Amount Link", subscriptionCanceledEvent.AmountLink.String()). + Str("SubID", subscriptionCanceledEvent.SubId.String()). + Str("Returned to", subscriptionCanceledEvent.To.String()). + Msg("Subscription Canceled Event") + + require.Equal(t, subBalanceNative, subscriptionCanceledEvent.AmountNative, "SubscriptionCanceled event native amount is not equal to sub amount while canceling subscription") + require.Equal(t, subBalanceLink, subscriptionCanceledEvent.AmountLink, "SubscriptionCanceled event LINK amount is not equal to sub amount while canceling subscription") + + testWalletBalanceNativeAfterSubCancelling, err := env.EVMClient.BalanceAt(testcontext.Get(t), testWalletAddress) + require.NoError(t, err) + + testWalletBalanceLinkAfterSubCancelling, err := linkToken.BalanceOf(testcontext.Get(t), testWalletAddress.String()) + require.NoError(t, err) + + //Verify that sub was deleted from Coordinator + _, err = vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subIDForCancelling) + require.Error(t, err, "error not occurred when trying to get deleted subscription from old Coordinator after sub migration") + + subFundsReturnedNativeActual := new(big.Int).Sub(testWalletBalanceNativeAfterSubCancelling, testWalletBalanceNativeBeforeSubCancelling) + subFundsReturnedLinkActual := new(big.Int).Sub(testWalletBalanceLinkAfterSubCancelling, testWalletBalanceLinkBeforeSubCancelling) + + subFundsReturnedNativeExpected := new(big.Int).Sub(subBalanceNative, cancellationTxFeeWei) + deltaSpentOnCancellationTxFee := new(big.Int).Sub(subBalanceNative, subFundsReturnedNativeActual) + l.Info(). + Str("Sub Balance - Native", subBalanceNative.String()). + Str("Delta Spent On Cancellation Tx Fee - `NativeBalance - subFundsReturnedNativeActual`", deltaSpentOnCancellationTxFee.String()). + Str("Cancellation Tx Fee Wei", cancellationTxFeeWei.String()). + Str("Sub Funds Returned Actual - Native", subFundsReturnedNativeActual.String()). + Str("Sub Funds Returned Expected - `NativeBalance - cancellationTxFeeWei`", subFundsReturnedNativeExpected.String()). + Str("Sub Funds Returned Actual - Link", subFundsReturnedLinkActual.String()). + Str("Sub Balance - Link", subBalanceLink.String()). + Msg("Sub funds returned") + + //todo - this fails on SIMULATED env as tx cost is calculated different as for testnets and it's not receipt.EffectiveGasPrice*receipt.GasUsed + //require.Equal(t, subFundsReturnedNativeExpected, subFundsReturnedNativeActual, "Returned funds are not equal to sub balance that was cancelled") + require.Equal(t, 1, testWalletBalanceNativeAfterSubCancelling.Cmp(testWalletBalanceNativeBeforeSubCancelling), "Native funds were not returned after sub cancellation") + require.Equal(t, 0, subBalanceLink.Cmp(subFundsReturnedLinkActual), "Returned LINK funds are not equal to sub balance that was cancelled") + + }) + t.Run("Owner Canceling Sub And Returning Funds While Having Pending Requests", func(t *testing.T) { + testConfig := vrfv2PlusConfig + //underfund subs in order rand fulfillments to fail + testConfig.SubscriptionFundingAmountNative = float64(0.000000000000000001) //1 Wei + testConfig.SubscriptionFundingAmountLink = float64(0.000000000000000001) //1 Juels + + subIDsForCancelling, err := vrfv2plus.CreateFundSubsAndAddConsumers( + env, + testConfig, + linkToken, vrfv2PlusContracts.Coordinator, - vrfv2PlusData, - wrapperSubID, - isNativeBilling, - &vrfv2PlusConfig, - l, + vrfv2PlusContracts.LoadTestConsumers, + 1, ) - require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + require.NoError(t, err) - expectedSubBalanceJuels := new(big.Int).Sub(subBalanceBeforeRequest, randomWordsFulfilledEvent.Payment) - wrapperSubscription, err = vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), wrapperSubID) + subIDForCancelling := subIDsForCancelling[0] + + subscriptionForCancelling, err := vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subIDForCancelling) require.NoError(t, err, "error getting subscription information") - subBalanceAfterRequest := wrapperSubscription.Balance - require.Equal(t, expectedSubBalanceJuels, subBalanceAfterRequest) - consumerStatus, err := wrapperContracts.LoadTestConsumers[0].GetRequestStatus(context.Background(), randomWordsFulfilledEvent.RequestId) - require.NoError(t, err, "error getting rand request status") - require.True(t, consumerStatus.Fulfilled) + vrfv2plus.LogSubDetails(l, subscriptionForCancelling, subIDForCancelling, vrfv2PlusContracts.Coordinator) - expectedWrapperConsumerJuelsBalance := new(big.Int).Sub(wrapperConsumerJuelsBalanceBeforeRequest, consumerStatus.Paid) + activeSubscriptionIdsBeforeSubCancellation, err := vrfv2PlusContracts.Coordinator.GetActiveSubscriptionIds(testcontext.Get(t), big.NewInt(0), big.NewInt(0)) + require.NoError(t, err) - wrapperConsumerJuelsBalanceAfterRequest, err := linkToken.BalanceOf(context.Background(), wrapperContracts.LoadTestConsumers[0].Address()) - require.NoError(t, err, "error getting wrapper consumer balance") - require.Equal(t, expectedWrapperConsumerJuelsBalance, wrapperConsumerJuelsBalanceAfterRequest) + require.True(t, it_utils.BigIntSliceContains(activeSubscriptionIdsBeforeSubCancellation, subIDForCancelling)) - //todo: uncomment when VRF-651 will be fixed - //require.Equal(t, 1, consumerStatus.Paid.Cmp(randomWordsFulfilledEvent.Payment), "Expected Consumer contract pay more than the Coordinator Sub") - vrfv2plus.LogFulfillmentDetailsLinkBilling(l, wrapperConsumerJuelsBalanceBeforeRequest, wrapperConsumerJuelsBalanceAfterRequest, consumerStatus, randomWordsFulfilledEvent) + pendingRequestsExist, err := vrfv2PlusContracts.Coordinator.PendingRequestsExist(testcontext.Get(t), subIDForCancelling) + require.NoError(t, err) + require.False(t, pendingRequestsExist, "Pending requests should not exist") - require.Equal(t, vrfv2PlusConfig.NumberOfWords, uint32(len(consumerStatus.RandomWords))) - for _, w := range consumerStatus.RandomWords { - l.Info().Str("Output", w.String()).Msg("Randomness fulfilled") - require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") - } - }) + randomWordsFulfilledEventTimeout := 5 * time.Second + _, err = vrfv2plus.RequestRandomnessAndWaitForFulfillment( + vrfv2PlusContracts.LoadTestConsumers[0], + vrfv2PlusContracts.Coordinator, + vrfv2PlusData, + subIDForCancelling, + false, + testConfig.RandomnessRequestCountPerRequest, + testConfig, + randomWordsFulfilledEventTimeout, + l, + ) - t.Run("VRFV2 Plus With Direct Funding (VRFV2PlusWrapper) - Native Billing", func(t *testing.T) { - var isNativeBilling = true + require.Error(t, err, "error should occur for waiting for fulfilment due to low sub balance") + + _, err = vrfv2plus.RequestRandomnessAndWaitForFulfillment( + vrfv2PlusContracts.LoadTestConsumers[0], + vrfv2PlusContracts.Coordinator, + vrfv2PlusData, + subIDForCancelling, + true, + testConfig.RandomnessRequestCountPerRequest, + testConfig, + randomWordsFulfilledEventTimeout, + l, + ) + + require.Error(t, err, "error should occur for waiting for fulfilment due to low sub balance") + + pendingRequestsExist, err = vrfv2PlusContracts.Coordinator.PendingRequestsExist(testcontext.Get(t), subIDForCancelling) + require.NoError(t, err) + require.True(t, pendingRequestsExist, "Pending requests should exist after unfulfilled rand requests due to low sub balance") + + walletBalanceNativeBeforeSubCancelling, err := env.EVMClient.BalanceAt(testcontext.Get(t), common.HexToAddress(defaultWalletAddress)) + require.NoError(t, err) - wrapperConsumerBalanceBeforeRequestWei, err := env.EVMClient.BalanceAt(context.Background(), common.HexToAddress(wrapperContracts.LoadTestConsumers[0].Address())) - require.NoError(t, err, "error getting wrapper consumer balance") + walletBalanceLinkBeforeSubCancelling, err := linkToken.BalanceOf(testcontext.Get(t), defaultWalletAddress) + require.NoError(t, err) - wrapperSubscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), wrapperSubID) + subscriptionForCancelling, err = vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subIDForCancelling) require.NoError(t, err, "error getting subscription information") - subBalanceBeforeRequest := wrapperSubscription.NativeBalance - randomWordsFulfilledEvent, err := vrfv2plus.DirectFundingRequestRandomnessAndWaitForFulfillment( - wrapperContracts.LoadTestConsumers[0], + subBalanceLink := subscriptionForCancelling.Balance + subBalanceNative := subscriptionForCancelling.NativeBalance + l.Info(). + Str("Subscription Amount Native", subBalanceNative.String()). + Str("Subscription Amount Link", subBalanceLink.String()). + Str("Returning funds from SubID", subIDForCancelling.String()). + Str("Returning funds to", defaultWalletAddress). + Msg("Canceling subscription and returning funds to subscription owner") + tx, err := vrfv2PlusContracts.Coordinator.OwnerCancelSubscription(subIDForCancelling) + require.NoError(t, err, "Error canceling subscription") + + subscriptionCanceledEvent, err := vrfv2PlusContracts.Coordinator.WaitForSubscriptionCanceledEvent(subIDForCancelling, time.Second*30) + require.NoError(t, err, "error waiting for subscription canceled event") + + cancellationTxReceipt, err := env.EVMClient.GetTxReceipt(tx.Hash()) + require.NoError(t, err, "error getting tx cancellation Tx Receipt") + + txGasUsed := new(big.Int).SetUint64(cancellationTxReceipt.GasUsed) + cancellationTxFeeWei := new(big.Int).Mul(txGasUsed, cancellationTxReceipt.EffectiveGasPrice) + + l.Info(). + Str("Cancellation Tx Fee Wei", cancellationTxFeeWei.String()). + Str("Effective Gas Price", cancellationTxReceipt.EffectiveGasPrice.String()). + Uint64("Gas Used", cancellationTxReceipt.GasUsed). + Msg("Cancellation TX Receipt") + + l.Info(). + Str("Returned Subscription Amount Native", subscriptionCanceledEvent.AmountNative.String()). + Str("Returned Subscription Amount Link", subscriptionCanceledEvent.AmountLink.String()). + Str("SubID", subscriptionCanceledEvent.SubId.String()). + Str("Returned to", subscriptionCanceledEvent.To.String()). + Msg("Subscription Canceled Event") + + require.Equal(t, subBalanceNative, subscriptionCanceledEvent.AmountNative, "SubscriptionCanceled event native amount is not equal to sub amount while canceling subscription") + require.Equal(t, subBalanceLink, subscriptionCanceledEvent.AmountLink, "SubscriptionCanceled event LINK amount is not equal to sub amount while canceling subscription") + + walletBalanceNativeAfterSubCancelling, err := env.EVMClient.BalanceAt(testcontext.Get(t), common.HexToAddress(defaultWalletAddress)) + require.NoError(t, err) + + walletBalanceLinkAfterSubCancelling, err := linkToken.BalanceOf(testcontext.Get(t), defaultWalletAddress) + require.NoError(t, err) + + //Verify that sub was deleted from Coordinator + _, err = vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subIDForCancelling) + fmt.Println("err", err) + require.Error(t, err, "error not occurred when trying to get deleted subscription from old Coordinator after sub migration") + + subFundsReturnedNativeActual := new(big.Int).Sub(walletBalanceNativeAfterSubCancelling, walletBalanceNativeBeforeSubCancelling) + subFundsReturnedLinkActual := new(big.Int).Sub(walletBalanceLinkAfterSubCancelling, walletBalanceLinkBeforeSubCancelling) + + subFundsReturnedNativeExpected := new(big.Int).Sub(subBalanceNative, cancellationTxFeeWei) + deltaSpentOnCancellationTxFee := new(big.Int).Sub(subBalanceNative, subFundsReturnedNativeActual) + l.Info(). + Str("Sub Balance - Native", subBalanceNative.String()). + Str("Delta Spent On Cancellation Tx Fee - `NativeBalance - subFundsReturnedNativeActual`", deltaSpentOnCancellationTxFee.String()). + Str("Cancellation Tx Fee Wei", cancellationTxFeeWei.String()). + Str("Sub Funds Returned Actual - Native", subFundsReturnedNativeActual.String()). + Str("Sub Funds Returned Expected - `NativeBalance - cancellationTxFeeWei`", subFundsReturnedNativeExpected.String()). + Str("Sub Funds Returned Actual - Link", subFundsReturnedLinkActual.String()). + Str("Sub Balance - Link", subBalanceLink.String()). + Str("walletBalanceNativeBeforeSubCancelling", walletBalanceNativeBeforeSubCancelling.String()). + Str("walletBalanceNativeAfterSubCancelling", walletBalanceNativeAfterSubCancelling.String()). + Msg("Sub funds returned") + + //todo - need to use different wallet for each test to verify exact amount of Native/LINK returned + //todo - as defaultWallet is used in other tests in parallel which might affect the balance - TT-684 + //require.Equal(t, 1, walletBalanceNativeAfterSubCancelling.Cmp(walletBalanceNativeBeforeSubCancelling), "Native funds were not returned after sub cancellation") + + //todo - this fails on SIMULATED env as tx cost is calculated different as for testnets and it's not receipt.EffectiveGasPrice*receipt.GasUsed + //require.Equal(t, subFundsReturnedNativeExpected, subFundsReturnedNativeActual, "Returned funds are not equal to sub balance that was cancelled") + require.Equal(t, 0, subBalanceLink.Cmp(subFundsReturnedLinkActual), "Returned LINK funds are not equal to sub balance that was cancelled") + + activeSubscriptionIdsAfterSubCancellation, err := vrfv2PlusContracts.Coordinator.GetActiveSubscriptionIds(testcontext.Get(t), big.NewInt(0), big.NewInt(0)) + require.NoError(t, err, "error getting active subscription ids") + + require.False( + t, + it_utils.BigIntSliceContains(activeSubscriptionIdsAfterSubCancellation, subIDForCancelling), + "Active subscription ids should not contain sub id after sub cancellation", + ) + }) + t.Run("Oracle Withdraw", func(t *testing.T) { + testConfig := vrfv2PlusConfig + subIDsForOracleWithDraw, err := vrfv2plus.CreateFundSubsAndAddConsumers( + env, + testConfig, + linkToken, + vrfv2PlusContracts.Coordinator, + vrfv2PlusContracts.LoadTestConsumers, + 1, + ) + require.NoError(t, err) + subIDForOracleWithdraw := subIDsForOracleWithDraw[0] + + fulfilledEventLink, err := vrfv2plus.RequestRandomnessAndWaitForFulfillment( + vrfv2PlusContracts.LoadTestConsumers[0], vrfv2PlusContracts.Coordinator, vrfv2PlusData, - wrapperSubID, - isNativeBilling, - &vrfv2PlusConfig, + subIDForOracleWithdraw, + false, + testConfig.RandomnessRequestCountPerRequest, + testConfig, + testConfig.RandomWordsFulfilledEventTimeout, l, ) - require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + require.NoError(t, err) - expectedSubBalanceWei := new(big.Int).Sub(subBalanceBeforeRequest, randomWordsFulfilledEvent.Payment) - wrapperSubscription, err = vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), wrapperSubID) - require.NoError(t, err, "error getting subscription information") - subBalanceAfterRequest := wrapperSubscription.NativeBalance - require.Equal(t, expectedSubBalanceWei, subBalanceAfterRequest) + fulfilledEventNative, err := vrfv2plus.RequestRandomnessAndWaitForFulfillment( + vrfv2PlusContracts.LoadTestConsumers[0], + vrfv2PlusContracts.Coordinator, + vrfv2PlusData, + subIDForOracleWithdraw, + true, + testConfig.RandomnessRequestCountPerRequest, + testConfig, + testConfig.RandomWordsFulfilledEventTimeout, + l, + ) + require.NoError(t, err) + amountToWithdrawLink := fulfilledEventLink.Payment - consumerStatus, err := wrapperContracts.LoadTestConsumers[0].GetRequestStatus(context.Background(), randomWordsFulfilledEvent.RequestId) - require.NoError(t, err, "error getting rand request status") - require.True(t, consumerStatus.Fulfilled) + defaultWalletBalanceNativeBeforeOracleWithdraw, err := env.EVMClient.BalanceAt(testcontext.Get(t), common.HexToAddress(defaultWalletAddress)) + require.NoError(t, err) - expectedWrapperConsumerWeiBalance := new(big.Int).Sub(wrapperConsumerBalanceBeforeRequestWei, consumerStatus.Paid) + defaultWalletBalanceLinkBeforeOracleWithdraw, err := linkToken.BalanceOf(testcontext.Get(t), defaultWalletAddress) + require.NoError(t, err) - wrapperConsumerBalanceAfterRequestWei, err := env.EVMClient.BalanceAt(context.Background(), common.HexToAddress(wrapperContracts.LoadTestConsumers[0].Address())) - require.NoError(t, err, "error getting wrapper consumer balance") - require.Equal(t, expectedWrapperConsumerWeiBalance, wrapperConsumerBalanceAfterRequestWei) + l.Info(). + Str("Returning to", defaultWalletAddress). + Str("Amount", amountToWithdrawLink.String()). + Msg("Invoking Oracle Withdraw for LINK") - //todo: uncomment when VRF-651 will be fixed - //require.Equal(t, 1, consumerStatus.Paid.Cmp(randomWordsFulfilledEvent.Payment), "Expected Consumer contract pay more than the Coordinator Sub") - vrfv2plus.LogFulfillmentDetailsNativeBilling(l, wrapperConsumerBalanceBeforeRequestWei, wrapperConsumerBalanceAfterRequestWei, consumerStatus, randomWordsFulfilledEvent) + err = vrfv2PlusContracts.Coordinator.OracleWithdraw( + common.HexToAddress(defaultWalletAddress), + amountToWithdrawLink, + ) + require.NoError(t, err, "error withdrawing LINK from coordinator to default wallet") + amountToWithdrawNative := fulfilledEventNative.Payment - require.Equal(t, vrfv2PlusConfig.NumberOfWords, uint32(len(consumerStatus.RandomWords))) - for _, w := range consumerStatus.RandomWords { - l.Info().Str("Output", w.String()).Msg("Randomness fulfilled") - require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") - } + l.Info(). + Str("Returning to", defaultWalletAddress). + Str("Amount", amountToWithdrawNative.String()). + Msg("Invoking Oracle Withdraw for Native") + + err = vrfv2PlusContracts.Coordinator.OracleWithdrawNative( + common.HexToAddress(defaultWalletAddress), + amountToWithdrawNative, + ) + require.NoError(t, err, "error withdrawing Native tokens from coordinator to default wallet") + + err = env.EVMClient.WaitForEvents() + require.NoError(t, err, vrfv2plus.ErrWaitTXsComplete) + + defaultWalletBalanceNativeAfterOracleWithdraw, err := env.EVMClient.BalanceAt(testcontext.Get(t), common.HexToAddress(defaultWalletAddress)) + require.NoError(t, err) + + defaultWalletBalanceLinkAfterOracleWithdraw, err := linkToken.BalanceOf(testcontext.Get(t), defaultWalletAddress) + require.NoError(t, err) + + //not possible to verify exact amount of Native/LINK returned as defaultWallet is used in other tests in parallel which might affect the balance + require.Equal(t, 1, defaultWalletBalanceNativeAfterOracleWithdraw.Cmp(defaultWalletBalanceNativeBeforeOracleWithdraw), "Native funds were not returned after oracle withdraw native") + require.Equal(t, 1, defaultWalletBalanceLinkAfterOracleWithdraw.Cmp(defaultWalletBalanceLinkBeforeOracleWithdraw), "LINK funds were not returned after oracle withdraw") }) +} + +func TestVRFv2PlusMultipleSendingKeys(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) + + var vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig + err := envconfig.Process("VRFV2PLUS", &vrfv2PlusConfig) + require.NoError(t, err) + env, err := test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithGeth(). + WithCLNodes(1). + WithFunding(big.NewFloat(vrfv2PlusConfig.ChainlinkNodeFunding)). + WithStandardCleanup(). + Build() + require.NoError(t, err, "error creating test env") + + env.ParallelTransactions(true) + + mockETHLinkFeed, err := actions.DeployMockETHLinkFeed(env.ContractDeployer, big.NewInt(vrfv2PlusConfig.LinkNativeFeedResponse)) + require.NoError(t, err, "error deploying mock ETH/LINK feed") + + linkToken, err := actions.DeployLINKToken(env.ContractDeployer) + require.NoError(t, err, "error deploying LINK contract") + + // register proving key against oracle address (sending key) in order to test oracleWithdraw + defaultWalletAddress := env.EVMClient.GetDefaultWallet().Address() + + numberOfTxKeysToCreate := 2 + vrfv2PlusContracts, subIDs, vrfv2PlusData, err := vrfv2plus.SetupVRFV2_5Environment( + env, + vrfv2PlusConfig, + linkToken, + mockETHLinkFeed, + defaultWalletAddress, + numberOfTxKeysToCreate, + 1, + 1, + l, + ) + require.NoError(t, err, "error setting up VRF v2_5 env") + + subID := subIDs[0] + + subscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subID) + require.NoError(t, err, "error getting subscription information") + + vrfv2plus.LogSubDetails(l, subscription, subID, vrfv2PlusContracts.Coordinator) + + t.Run("Request Randomness with multiple sending keys", func(t *testing.T) { + testConfig := vrfv2PlusConfig + var isNativeBilling = false + txKeys, _, err := env.ClCluster.Nodes[0].API.ReadTxKeys("evm") + require.NoError(t, err, "error reading tx keys") + + require.Equal(t, numberOfTxKeysToCreate+1, len(txKeys.Data)) + + var fulfillmentTxFromAddresses []string + for i := 0; i < numberOfTxKeysToCreate+1; i++ { + randomWordsFulfilledEvent, err := vrfv2plus.RequestRandomnessAndWaitForFulfillment( + vrfv2PlusContracts.LoadTestConsumers[0], + vrfv2PlusContracts.Coordinator, + vrfv2PlusData, + subID, + isNativeBilling, + testConfig.RandomnessRequestCountPerRequest, + testConfig, + testConfig.RandomWordsFulfilledEventTimeout, + l, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + //todo - move TransactionByHash to EVMClient in CTF + fulfillmentTx, _, err := env.EVMClient.(*blockchain.EthereumMultinodeClient).DefaultClient.(*blockchain.EthereumClient). + Client.TransactionByHash(context.Background(), randomWordsFulfilledEvent.Raw.TxHash) + require.NoError(t, err, "error getting tx from hash") + fulfillmentTxFromAddress, err := actions.GetTxFromAddress(fulfillmentTx) + require.NoError(t, err, "error getting tx from address") + fulfillmentTxFromAddresses = append(fulfillmentTxFromAddresses, fulfillmentTxFromAddress) + } + require.Equal(t, numberOfTxKeysToCreate+1, len(fulfillmentTxFromAddresses)) + var txKeyAddresses []string + for _, txKey := range txKeys.Data { + txKeyAddresses = append(txKeyAddresses, txKey.ID) + } + less := func(a, b string) bool { return a < b } + equalIgnoreOrder := cmp.Diff(txKeyAddresses, fulfillmentTxFromAddresses, cmpopts.SortSlices(less)) == "" + require.True(t, equalIgnoreOrder) + }) } func TestVRFv2PlusMigration(t *testing.T) { @@ -271,22 +716,35 @@ func TestVRFv2PlusMigration(t *testing.T) { linkAddress, err := actions.DeployLINKToken(env.ContractDeployer) require.NoError(t, err, "error deploying LINK contract") - vrfv2PlusContracts, subIDs, vrfv2PlusData, err := vrfv2plus.SetupVRFV2_5Environment(env, &vrfv2PlusConfig, linkAddress, mockETHLinkFeedAddress, 2, 1) + nativeTokenPrimaryKeyAddress, err := env.ClCluster.NodeAPIs()[0].PrimaryEthAddress() + require.NoError(t, err, "error getting primary eth address") + + vrfv2PlusContracts, subIDs, vrfv2PlusData, err := vrfv2plus.SetupVRFV2_5Environment( + env, + vrfv2PlusConfig, + linkAddress, + mockETHLinkFeedAddress, + nativeTokenPrimaryKeyAddress, + 0, + 2, + 1, + l, + ) require.NoError(t, err, "error setting up VRF v2_5 env") subID := subIDs[0] - subscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), subID) + subscription, err := vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subID) require.NoError(t, err, "error getting subscription information") vrfv2plus.LogSubDetails(l, subscription, subID, vrfv2PlusContracts.Coordinator) - activeSubIdsOldCoordinatorBeforeMigration, err := vrfv2PlusContracts.Coordinator.GetActiveSubscriptionIds(context.Background(), big.NewInt(0), big.NewInt(0)) + activeSubIdsOldCoordinatorBeforeMigration, err := vrfv2PlusContracts.Coordinator.GetActiveSubscriptionIds(testcontext.Get(t), big.NewInt(0), big.NewInt(0)) require.NoError(t, err, "error occurred getting active sub ids") require.Len(t, activeSubIdsOldCoordinatorBeforeMigration, 1, "Active Sub Ids length is not equal to 1") require.Equal(t, subID, activeSubIdsOldCoordinatorBeforeMigration[0]) - oldSubscriptionBeforeMigration, err := vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), subID) + oldSubscriptionBeforeMigration, err := vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subID) require.NoError(t, err, "error getting subscription information") //Migration Process @@ -297,7 +755,7 @@ func TestVRFv2PlusMigration(t *testing.T) { require.NoError(t, err, vrfv2plus.ErrWaitTXsComplete) _, err = vrfv2plus.VRFV2PlusUpgradedVersionRegisterProvingKey(vrfv2PlusData.VRFKey, vrfv2PlusData.PrimaryEthAddress, newCoordinator) - require.NoError(t, err, errors.Wrap(err, vrfv2plus.ErrRegisteringProvingKey)) + require.NoError(t, err, fmt.Errorf("%s, err: %w", vrfv2plus.ErrRegisteringProvingKey, err)) err = newCoordinator.SetConfig( vrfv2PlusConfig.MinimumConfirmations, @@ -310,6 +768,7 @@ func TestVRFv2PlusMigration(t *testing.T) { FulfillmentFlatFeeNativePPM: vrfv2PlusConfig.FulfillmentFlatFeeNativePPM, }, ) + require.NoError(t, err) err = newCoordinator.SetLINKAndLINKNativeFeed(linkAddress.Address(), mockETHLinkFeedAddress.Address()) require.NoError(t, err, vrfv2plus.ErrSetLinkNativeLinkFeed) @@ -319,7 +778,7 @@ func TestVRFv2PlusMigration(t *testing.T) { _, err = vrfv2plus.CreateVRFV2PlusJob( env.ClCluster.NodeAPIs()[0], newCoordinator.Address(), - vrfv2PlusData.PrimaryEthAddress, + []string{vrfv2PlusData.PrimaryEthAddress}, vrfv2PlusData.VRFKey.Data.ID, vrfv2PlusData.ChainID.String(), vrfv2PlusConfig.MinimumConfirmations, @@ -356,14 +815,14 @@ func TestVRFv2PlusMigration(t *testing.T) { migratedCoordinatorLinkTotalBalanceAfterMigration, migratedCoordinatorEthTotalBalanceAfterMigration, err := vrfv2plus.GetUpgradedCoordinatorTotalBalance(newCoordinator) require.NoError(t, err) - migratedSubscription, err := newCoordinator.GetSubscription(context.Background(), subID) + migratedSubscription, err := newCoordinator.GetSubscription(testcontext.Get(t), subID) require.NoError(t, err, "error getting subscription information") vrfv2plus.LogSubDetailsAfterMigration(l, newCoordinator, subID, migratedSubscription) //Verify that Coordinators were updated in Consumers for _, consumer := range vrfv2PlusContracts.LoadTestConsumers { - coordinatorAddressInConsumerAfterMigration, err := consumer.GetCoordinator(context.Background()) + coordinatorAddressInConsumerAfterMigration, err := consumer.GetCoordinator(testcontext.Get(t)) require.NoError(t, err, "error getting Coordinator from Consumer contract") require.Equal(t, newCoordinator.Address(), coordinatorAddressInConsumerAfterMigration.String()) l.Debug(). @@ -379,13 +838,13 @@ func TestVRFv2PlusMigration(t *testing.T) { require.Equal(t, oldSubscriptionBeforeMigration.Consumers, migratedSubscription.Consumers) //Verify that old sub was deleted from old Coordinator - _, err = vrfv2PlusContracts.Coordinator.GetSubscription(context.Background(), subID) + _, err = vrfv2PlusContracts.Coordinator.GetSubscription(testcontext.Get(t), subID) require.Error(t, err, "error not occurred when trying to get deleted subscription from old Coordinator after sub migration") - _, err = vrfv2PlusContracts.Coordinator.GetActiveSubscriptionIds(context.Background(), big.NewInt(0), big.NewInt(0)) + _, err = vrfv2PlusContracts.Coordinator.GetActiveSubscriptionIds(testcontext.Get(t), big.NewInt(0), big.NewInt(0)) require.Error(t, err, "error not occurred getting active sub ids. Should occur since it should revert when sub id array is empty") - activeSubIdsMigratedCoordinator, err := newCoordinator.GetActiveSubscriptionIds(context.Background(), big.NewInt(0), big.NewInt(0)) + activeSubIdsMigratedCoordinator, err := newCoordinator.GetActiveSubscriptionIds(testcontext.Get(t), big.NewInt(0), big.NewInt(0)) require.NoError(t, err, "error occurred getting active sub ids") require.Len(t, activeSubIdsMigratedCoordinator, 1, "Active Sub Ids length is not equal to 1 for Migrated Coordinator after migration") require.Equal(t, subID, activeSubIdsMigratedCoordinator[0]) @@ -396,10 +855,10 @@ func TestVRFv2PlusMigration(t *testing.T) { expectedLinkTotalBalanceForOldCoordinator := new(big.Int).Sub(oldCoordinatorLinkTotalBalanceBeforeMigration, oldSubscriptionBeforeMigration.Balance) expectedEthTotalBalanceForOldCoordinator := new(big.Int).Sub(oldCoordinatorEthTotalBalanceBeforeMigration, oldSubscriptionBeforeMigration.NativeBalance) - require.Equal(t, expectedLinkTotalBalanceForMigratedCoordinator, migratedCoordinatorLinkTotalBalanceAfterMigration) - require.Equal(t, expectedEthTotalBalanceForMigratedCoordinator, migratedCoordinatorEthTotalBalanceAfterMigration) - require.Equal(t, expectedLinkTotalBalanceForOldCoordinator, oldCoordinatorLinkTotalBalanceAfterMigration) - require.Equal(t, expectedEthTotalBalanceForOldCoordinator, oldCoordinatorEthTotalBalanceAfterMigration) + require.Equal(t, 0, expectedLinkTotalBalanceForMigratedCoordinator.Cmp(migratedCoordinatorLinkTotalBalanceAfterMigration)) + require.Equal(t, 0, expectedEthTotalBalanceForMigratedCoordinator.Cmp(migratedCoordinatorEthTotalBalanceAfterMigration)) + require.Equal(t, 0, expectedLinkTotalBalanceForOldCoordinator.Cmp(oldCoordinatorLinkTotalBalanceAfterMigration)) + require.Equal(t, 0, expectedEthTotalBalanceForOldCoordinator.Cmp(oldCoordinatorEthTotalBalanceAfterMigration)) //Verify rand requests fulfills with Link Token billing _, err = vrfv2plus.RequestRandomnessAndWaitForFulfillmentUpgraded( @@ -408,7 +867,7 @@ func TestVRFv2PlusMigration(t *testing.T) { vrfv2PlusData, subID, false, - &vrfv2PlusConfig, + vrfv2PlusConfig, l, ) require.NoError(t, err, "error requesting randomness and waiting for fulfilment") @@ -420,7 +879,7 @@ func TestVRFv2PlusMigration(t *testing.T) { vrfv2PlusData, subID, true, - &vrfv2PlusConfig, + vrfv2PlusConfig, l, ) require.NoError(t, err, "error requesting randomness and waiting for fulfilment") diff --git a/integration-tests/soak/forwarder_ocr_test.go b/integration-tests/soak/forwarder_ocr_test.go index bc02f367892..538f5ff1569 100644 --- a/integration-tests/soak/forwarder_ocr_test.go +++ b/integration-tests/soak/forwarder_ocr_test.go @@ -18,7 +18,7 @@ func TestForwarderOCRSoak(t *testing.T) { ForwardersEnabled = true` // Uncomment below for debugging TOML issues on the node // fmt.Println("Using Chainlink TOML\n---------------------") - // fmt.Println(client.AddNetworkDetailedConfig(config.BaseOCRP2PV1Config, customNetworkTOML, network)) + // fmt.Println(networks.AddNetworkDetailedConfig(config.BaseOCRP2PV1Config, customNetworkTOML, network)) // fmt.Println("---------------------") ocrSoakTest, err := testsetups.NewOCRSoakTest(t, true) diff --git a/integration-tests/soak/ocr_test.go b/integration-tests/soak/ocr_test.go index b2375f13ac2..4d7971d678e 100644 --- a/integration-tests/soak/ocr_test.go +++ b/integration-tests/soak/ocr_test.go @@ -16,8 +16,9 @@ func TestOCRSoak(t *testing.T) { // Use this variable to pass in any custom EVM specific TOML values to your Chainlink nodes customNetworkTOML := `` // Uncomment below for debugging TOML issues on the node + // network := networks.MustGetSelectedNetworksFromEnv()[0] // fmt.Println("Using Chainlink TOML\n---------------------") - // fmt.Println(client.AddNetworkDetailedConfig(config.BaseOCRP2PV1Config, customNetworkTOML, network)) + // fmt.Println(networks.AddNetworkDetailedConfig(config.BaseOCR1Config, customNetworkTOML, network)) // fmt.Println("---------------------") ocrSoakTest, err := testsetups.NewOCRSoakTest(t, false) diff --git a/integration-tests/testreporters/keeper_benchmark.go b/integration-tests/testreporters/keeper_benchmark.go index c800eb37be2..5db6cca3ec4 100644 --- a/integration-tests/testreporters/keeper_benchmark.go +++ b/integration-tests/testreporters/keeper_benchmark.go @@ -133,7 +133,7 @@ func (k *KeeperBenchmarkTestReporter) WriteReport(folderLocation string) error { if err != nil { return err } - avg, median, ninetyPct, ninetyNinePct, max := intListStats(allDelays) + avg, median, ninetyPct, ninetyNinePct, max := IntListStats(allDelays) err = keeperReportWriter.Write([]string{ fmt.Sprint(totalEligibleCount), fmt.Sprint(totalPerformed), @@ -183,7 +183,7 @@ func (k *KeeperBenchmarkTestReporter) WriteReport(folderLocation string) error { } for contractIndex, report := range k.Reports { - avg, median, ninetyPct, ninetyNinePct, max := intListStats(report.AllCheckDelays) + avg, median, ninetyPct, ninetyNinePct, max = IntListStats(report.AllCheckDelays) err = keeperReportWriter.Write([]string{ fmt.Sprint(contractIndex), report.RegistryAddress, @@ -305,7 +305,9 @@ func (k *KeeperBenchmarkTestReporter) SendSlackNotification(t *testing.T, slackC } // intListStats helper calculates some statistics on an int list: avg, median, 90pct, 99pct, max -func intListStats(in []int64) (float64, int64, int64, int64, int64) { +// +//nolint:revive +func IntListStats(in []int64) (float64, int64, int64, int64, int64) { length := len(in) if length == 0 { return 0, 0, 0, 0, 0 diff --git a/integration-tests/testreporters/ocr.go b/integration-tests/testreporters/ocr.go index a04718ea228..abbb261fa74 100644 --- a/integration-tests/testreporters/ocr.go +++ b/integration-tests/testreporters/ocr.go @@ -67,9 +67,7 @@ func (e *OCRRoundState) Time() time.Time { // CSV returns a CSV representation of the test state and all events func (e *OCRRoundState) CSV() [][]string { rows := [][]string{{e.StartTime.Format("2006-01-02 15:04:05.00 MST"), fmt.Sprintf("Expecting new Answer: %d", e.Answer)}} - for _, anomaly := range e.anomalies { - rows = append(rows, anomaly) - } + rows = append(rows, e.anomalies...) return rows } diff --git a/integration-tests/testreporters/profile.go b/integration-tests/testreporters/profile.go index 9ac7713e94d..ab9dec138e4 100644 --- a/integration-tests/testreporters/profile.go +++ b/integration-tests/testreporters/profile.go @@ -54,7 +54,7 @@ func (c *ChainlinkProfileTestReporter) WriteReport(folderLocation string) error } // SendNotification hasn't been implemented for this test -func (c *ChainlinkProfileTestReporter) SendSlackNotification(t *testing.T, slackClient *slack.Client) error { +func (c *ChainlinkProfileTestReporter) SendSlackNotification(_ *testing.T, _ *slack.Client) error { log.Warn().Msg("No Slack notification integration for Chainlink profile tests") return nil } diff --git a/integration-tests/testreporters/vrfv2.go b/integration-tests/testreporters/vrfv2.go index c1ab816d4e8..2a72e4d91d0 100644 --- a/integration-tests/testreporters/vrfv2.go +++ b/integration-tests/testreporters/vrfv2.go @@ -1,173 +1,92 @@ package testreporters import ( - "encoding/csv" "fmt" + "math/big" "os" - "path/filepath" "testing" "time" - "github.com/rs/zerolog/log" + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_config" + "github.com/slack-go/slack" "github.com/smartcontractkit/chainlink-testing-framework/testreporters" ) -type VRFV2SoakTestReporter struct { - Reports map[string]*VRFV2SoakTestReport // contractAddress: Report - namespace string - csvLocation string -} - -type VRFV2SoakTestReport struct { - ContractAddress string - TotalRounds uint - - averageRoundTime time.Duration - LongestRoundTime time.Duration - ShortestRoundTime time.Duration - totalRoundTimes time.Duration - - averageRoundBlocks uint - LongestRoundBlocks uint - ShortestRoundBlocks uint - totalBlockLengths uint -} - -// SetNamespace sets the namespace of the report for clean reports -func (o *VRFV2SoakTestReporter) SetNamespace(namespace string) { - o.namespace = namespace +type VRFV2TestReporter struct { + TestType string + RequestCount *big.Int + FulfilmentCount *big.Int + AverageFulfillmentInMillions *big.Int + SlowestFulfillment *big.Int + FastestFulfillment *big.Int + Vrfv2Config *vrfv2_config.VRFV2Config } -// WriteReport writes VRFV2 Soak test report to logs -func (o *VRFV2SoakTestReporter) WriteReport(folderLocation string) error { - for _, report := range o.Reports { - report.averageRoundBlocks = report.totalBlockLengths / report.TotalRounds - report.averageRoundTime = time.Duration(report.totalRoundTimes.Nanoseconds() / int64(report.TotalRounds)) - } - if err := o.writeCSV(folderLocation); err != nil { - return err - } - - log.Info().Msg("VRFV2 Soak Test Report") - log.Info().Msg("--------------------") - for contractAddress, report := range o.Reports { - log.Info(). - Str("Contract Address", report.ContractAddress). - Uint("Total Rounds Processed", report.TotalRounds). - Str("Average Round Time", fmt.Sprint(report.averageRoundTime)). - Str("Longest Round Time", fmt.Sprint(report.LongestRoundTime)). - Str("Shortest Round Time", fmt.Sprint(report.ShortestRoundTime)). - Uint("Average Round Blocks", report.averageRoundBlocks). - Uint("Longest Round Blocks", report.LongestRoundBlocks). - Uint("Shortest Round Blocks", report.ShortestRoundBlocks). - Msg(contractAddress) - } - log.Info().Msg("--------------------") - return nil +func (o *VRFV2TestReporter) SetReportData( + testType string, + RequestCount *big.Int, + FulfilmentCount *big.Int, + AverageFulfillmentInMillions *big.Int, + SlowestFulfillment *big.Int, + FastestFulfillment *big.Int, + vrfv2Config vrfv2_config.VRFV2Config, +) { + o.TestType = testType + o.RequestCount = RequestCount + o.FulfilmentCount = FulfilmentCount + o.AverageFulfillmentInMillions = AverageFulfillmentInMillions + o.SlowestFulfillment = SlowestFulfillment + o.FastestFulfillment = FastestFulfillment + o.Vrfv2Config = &vrfv2Config } -// SendNotification sends a slack message to a slack webhook and uploads test artifacts -func (o *VRFV2SoakTestReporter) SendSlackNotification(t *testing.T, slackClient *slack.Client) error { +// SendSlackNotification sends a slack message to a slack webhook +func (o *VRFV2TestReporter) SendSlackNotification(t *testing.T, slackClient *slack.Client) error { if slackClient == nil { slackClient = slack.New(testreporters.SlackAPIKey) } testFailed := t.Failed() - headerText := ":white_check_mark: VRFV2 Soak Test PASSED :white_check_mark:" + headerText := fmt.Sprintf(":white_check_mark: VRF V2 %s Test PASSED :white_check_mark:", o.TestType) if testFailed { - headerText = ":x: VRFV2 Soak Test FAILED :x:" - } - messageBlocks := testreporters.CommonSlackNotificationBlocks( - headerText, o.namespace, o.csvLocation, - ) - ts, err := testreporters.SendSlackMessage(slackClient, slack.MsgOptionBlocks(messageBlocks...)) - if err != nil { - return err + headerText = fmt.Sprintf(":x: VRF V2 %s Test FAILED :x:", o.TestType) } - return testreporters.UploadSlackFile(slackClient, slack.FileUploadParameters{ - Title: fmt.Sprintf("VRFV2 Soak Test Report %s", o.namespace), - Filetype: "csv", - Filename: fmt.Sprintf("vrfv2_soak_%s.csv", o.namespace), - File: o.csvLocation, - InitialComment: fmt.Sprintf("VRFV2 Soak Test Report %s.", o.namespace), - Channels: []string{testreporters.SlackChannel}, - ThreadTimestamp: ts, + messageBlocks := testreporters.SlackNotifyBlocks(headerText, os.Getenv("SELECTED_NETWORKS"), []string{ + fmt.Sprintf( + "Summary\n"+ + "Perf Test Type: %s\n"+ + "Test Duration set in parameters: %s\n"+ + "Use Existing Env: %t\n"+ + "Request Count: %s\n"+ + "Fulfilment Count: %s\n"+ + "AverageFulfillmentInMillions: %s\n"+ + "Slowest Fulfillment: %s\n"+ + "Fastest Fulfillment: %s \n"+ + "RPS: %d\n"+ + "RateLimitUnitDuration: %s\n"+ + "RandomnessRequestCountPerRequest: %d\n"+ + "RandomnessRequestCountPerRequestDeviation: %d\n", + o.TestType, + o.Vrfv2Config.TestDuration.Truncate(time.Second).String(), + o.Vrfv2Config.UseExistingEnv, + o.RequestCount.String(), + o.FulfilmentCount.String(), + o.AverageFulfillmentInMillions.String(), + o.SlowestFulfillment.String(), + o.FastestFulfillment.String(), + o.Vrfv2Config.RPS, + o.Vrfv2Config.RateLimitUnitDuration.String(), + o.Vrfv2Config.RandomnessRequestCountPerRequest, + o.Vrfv2Config.RandomnessRequestCountPerRequestDeviation, + ), }) -} - -// UpdateReport updates the report based on the latest info -func (o *VRFV2SoakTestReport) UpdateReport(roundTime time.Duration, blockLength uint) { - // Updates min values from default 0 - if o.ShortestRoundBlocks == 0 { - o.ShortestRoundBlocks = blockLength - } - if o.ShortestRoundTime == 0 { - o.ShortestRoundTime = roundTime - } - o.TotalRounds++ - o.totalRoundTimes += roundTime - o.totalBlockLengths += blockLength - if roundTime >= o.LongestRoundTime { - o.LongestRoundTime = roundTime - } - if roundTime <= o.ShortestRoundTime { - o.ShortestRoundTime = roundTime - } - if blockLength >= o.LongestRoundBlocks { - o.LongestRoundBlocks = blockLength - } - if blockLength <= o.ShortestRoundBlocks { - o.ShortestRoundBlocks = blockLength - } -} - -// writes a CSV report on the test runner -func (o *VRFV2SoakTestReporter) writeCSV(folderLocation string) error { - reportLocation := filepath.Join(folderLocation, "./vrfv2_soak_report.csv") - log.Debug().Str("Location", reportLocation).Msg("Writing VRFV2 report") - o.csvLocation = reportLocation - vrfv2ReportFile, err := os.Create(reportLocation) - if err != nil { - return err - } - defer vrfv2ReportFile.Close() - vrfv2ReportWriter := csv.NewWriter(vrfv2ReportFile) - err = vrfv2ReportWriter.Write([]string{ - "Contract Index", - "Contract Address", - "Total Rounds Processed", - "Average Round Time", - "Longest Round Time", - "Shortest Round Time", - "Average Round Blocks", - "Longest Round Blocks", - "Shortest Round Blocks", - }) + _, err := testreporters.SendSlackMessage(slackClient, slack.MsgOptionBlocks(messageBlocks...)) if err != nil { return err } - for contractIndex, report := range o.Reports { - err = vrfv2ReportWriter.Write([]string{ - fmt.Sprint(contractIndex), - report.ContractAddress, - fmt.Sprint(report.TotalRounds), - fmt.Sprint(report.averageRoundTime), - fmt.Sprint(report.LongestRoundTime), - fmt.Sprint(report.ShortestRoundTime), - fmt.Sprint(report.averageRoundBlocks), - fmt.Sprint(report.LongestRoundBlocks), - fmt.Sprint(report.ShortestRoundBlocks), - }) - if err != nil { - return err - } - } - vrfv2ReportWriter.Flush() - - log.Info().Str("Location", reportLocation).Msg("Wrote CSV file") return nil } diff --git a/integration-tests/testreporters/vrfv2plus.go b/integration-tests/testreporters/vrfv2plus.go new file mode 100644 index 00000000000..21e4e52695a --- /dev/null +++ b/integration-tests/testreporters/vrfv2plus.go @@ -0,0 +1,92 @@ +package testreporters + +import ( + "fmt" + "math/big" + "os" + "testing" + "time" + + "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2plus/vrfv2plus_config" + + "github.com/slack-go/slack" + + "github.com/smartcontractkit/chainlink-testing-framework/testreporters" +) + +type VRFV2PlusTestReporter struct { + TestType string + RequestCount *big.Int + FulfilmentCount *big.Int + AverageFulfillmentInMillions *big.Int + SlowestFulfillment *big.Int + FastestFulfillment *big.Int + Vrfv2PlusConfig *vrfv2plus_config.VRFV2PlusConfig +} + +func (o *VRFV2PlusTestReporter) SetReportData( + testType string, + RequestCount *big.Int, + FulfilmentCount *big.Int, + AverageFulfillmentInMillions *big.Int, + SlowestFulfillment *big.Int, + FastestFulfillment *big.Int, + vrfv2PlusConfig vrfv2plus_config.VRFV2PlusConfig, +) { + o.TestType = testType + o.RequestCount = RequestCount + o.FulfilmentCount = FulfilmentCount + o.AverageFulfillmentInMillions = AverageFulfillmentInMillions + o.SlowestFulfillment = SlowestFulfillment + o.FastestFulfillment = FastestFulfillment + o.Vrfv2PlusConfig = &vrfv2PlusConfig +} + +// SendSlackNotification sends a slack message to a slack webhook +func (o *VRFV2PlusTestReporter) SendSlackNotification(t *testing.T, slackClient *slack.Client) error { + if slackClient == nil { + slackClient = slack.New(testreporters.SlackAPIKey) + } + + testFailed := t.Failed() + headerText := fmt.Sprintf(":white_check_mark: VRF V2 Plus %s Test PASSED :white_check_mark:", o.TestType) + if testFailed { + headerText = fmt.Sprintf(":x: VRF V2 Plus %s Test FAILED :x:", o.TestType) + } + + messageBlocks := testreporters.SlackNotifyBlocks(headerText, os.Getenv("SELECTED_NETWORKS"), []string{ + fmt.Sprintf( + "Summary\n"+ + "Perf Test Type: %s\n"+ + "Test Duration set in parameters: %s\n"+ + "Use Existing Env: %t\n"+ + "Request Count: %s\n"+ + "Fulfilment Count: %s\n"+ + "AverageFulfillmentInMillions: %s\n"+ + "Slowest Fulfillment: %s\n"+ + "Fastest Fulfillment: %s \n"+ + "RPS: %d\n"+ + "RateLimitUnitDuration: %s\n"+ + "RandomnessRequestCountPerRequest: %d\n"+ + "RandomnessRequestCountPerRequestDeviation: %d\n", + o.TestType, + o.Vrfv2PlusConfig.TestDuration.Truncate(time.Second).String(), + o.Vrfv2PlusConfig.UseExistingEnv, + o.RequestCount.String(), + o.FulfilmentCount.String(), + o.AverageFulfillmentInMillions.String(), + o.SlowestFulfillment.String(), + o.FastestFulfillment.String(), + o.Vrfv2PlusConfig.RPS, + o.Vrfv2PlusConfig.RateLimitUnitDuration.String(), + o.Vrfv2PlusConfig.RandomnessRequestCountPerRequest, + o.Vrfv2PlusConfig.RandomnessRequestCountPerRequestDeviation, + ), + }) + + _, err := testreporters.SendSlackMessage(slackClient, slack.MsgOptionBlocks(messageBlocks...)) + if err != nil { + return err + } + return nil +} diff --git a/integration-tests/testsetups/don_evm_chain.go b/integration-tests/testsetups/don_evm_chain.go index 545d9515801..3ade7f0d69d 100644 --- a/integration-tests/testsetups/don_evm_chain.go +++ b/integration-tests/testsetups/don_evm_chain.go @@ -6,13 +6,13 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/require" - e "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" + e "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" diff --git a/integration-tests/testsetups/keeper_benchmark.go b/integration-tests/testsetups/keeper_benchmark.go index 466eb97fdd3..575ed3e7de5 100644 --- a/integration-tests/testsetups/keeper_benchmark.go +++ b/integration-tests/testsetups/keeper_benchmark.go @@ -21,10 +21,11 @@ import ( "github.com/slack-go/slack" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-env/environment" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" "github.com/smartcontractkit/chainlink-testing-framework/logging" reportModel "github.com/smartcontractkit/chainlink-testing-framework/testreporters" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper1_1" @@ -229,7 +230,7 @@ func (k *KeeperBenchmarkTest) Run() { "NumberOfRegistries": len(k.keeperRegistries), } inputs := k.Inputs - startingBlock, err := k.chainClient.LatestBlockNumber(context.Background()) + startingBlock, err := k.chainClient.LatestBlockNumber(testcontext.Get(k.t)) require.NoError(k.t, err, "Error getting latest block number") k.startingBlock = big.NewInt(0).SetUint64(startingBlock) startTime := time.Now() @@ -292,32 +293,54 @@ func (k *KeeperBenchmarkTest) Run() { require.NoError(k.t, err, "Error waiting for keeper subscriptions") // Collect logs for each registry to calculate test metrics - registryLogs := make([][]types.Log, len(k.keeperRegistries)) + // This test generates a LOT of logs, and we need to break up our reads, or risk getting rate-limited by the node + var ( + endBlock = big.NewInt(0).Add(k.startingBlock, big.NewInt(u.BlockRange)) + registryLogs = make([][]types.Log, len(k.keeperRegistries)) + blockBatchSize int64 = 100 + ) for rIndex := range k.keeperRegistries { + // Variables for the full registry var ( - logs []types.Log - timeout = 5 * time.Second - addr = k.keeperRegistries[rIndex].Address() - filterQuery = geth.FilterQuery{ + logs []types.Log + timeout = 5 * time.Second + addr = k.keeperRegistries[rIndex].Address() + queryStartBlock = big.NewInt(0).Set(k.startingBlock) + ) + + // Gather logs from the registry in 100 block chunks to avoid read limits + for queryStartBlock.Cmp(endBlock) < 0 { + filterQuery := geth.FilterQuery{ Addresses: []common.Address{common.HexToAddress(addr)}, - FromBlock: k.startingBlock, + FromBlock: queryStartBlock, + ToBlock: big.NewInt(0).Add(queryStartBlock, big.NewInt(blockBatchSize)), } + + // This RPC call can possibly time out or otherwise die. Failure is not an option, keep retrying to get our stats. err = fmt.Errorf("initial error") // to ensure our for loop runs at least once - ) - for err != nil { // This RPC call can possibly time out or otherwise die. Failure is not an option, keep retrying to get our stats. - ctx, cancel := context.WithTimeout(context.Background(), timeout) - logs, err = k.chainClient.FilterLogs(ctx, filterQuery) - cancel() - if err != nil { - k.log.Error().Err(err). - Interface("Filter Query", filterQuery). - Str("Timeout", timeout.String()). - Msg("Error getting logs from chain, trying again") - } else { - k.log.Info().Int("Log Count", len(logs)).Str("Registry Address", addr).Msg("Collected logs") + for err != nil { + ctx, cancel := context.WithTimeout(testcontext.Get(k.t), timeout) + logs, err = k.chainClient.FilterLogs(ctx, filterQuery) + cancel() + if err != nil { + k.log.Error(). + Err(err). + Interface("Filter Query", filterQuery). + Str("Timeout", timeout.String()). + Msg("Error getting logs from chain, trying again") + timeout = time.Duration(math.Min(float64(timeout)*2, float64(2*time.Minute))) + continue + } + k.log.Info(). + Uint64("From Block", queryStartBlock.Uint64()). + Uint64("To Block", filterQuery.ToBlock.Uint64()). + Int("Log Count", len(logs)). + Str("Registry Address", addr). + Msg("Collected logs") + queryStartBlock.Add(queryStartBlock, big.NewInt(blockBatchSize)) + registryLogs[rIndex] = append(registryLogs[rIndex], logs...) } } - registryLogs[rIndex] = logs } // Count reverts and stale upkeeps @@ -407,12 +430,13 @@ func (k *KeeperBenchmarkTest) observeUpkeepEvents() { FromBlock: k.startingBlock, } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(testcontext.Get(k.t), 5*time.Second) sub, err := k.chainClient.SubscribeFilterLogs(ctx, filterQuery, eventLogs) cancel() require.NoError(k.t, err, "Subscribing to upkeep performed events log shouldn't fail") interruption := make(chan os.Signal, 1) + //nolint:staticcheck //ignore SA1016 we need to send the os.Kill signal signal.Notify(interruption, os.Kill, os.Interrupt, syscall.SIGTERM) go func() { @@ -429,7 +453,7 @@ func (k *KeeperBenchmarkTest) observeUpkeepEvents() { Str("Backoff", backoff.String()). Msg("Error while subscribing to Keeper Event Logs. Resubscribing...") - ctx, cancel := context.WithTimeout(context.Background(), backoff) + ctx, cancel := context.WithTimeout(testcontext.Get(k.t), backoff) sub, err = k.chainClient.SubscribeFilterLogs(ctx, filterQuery, eventLogs) cancel() if err != nil { diff --git a/integration-tests/testsetups/ocr.go b/integration-tests/testsetups/ocr.go index 048f3124ad9..a35d915ea92 100644 --- a/integration-tests/testsetups/ocr.go +++ b/integration-tests/testsetups/ocr.go @@ -26,16 +26,17 @@ import ( "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-env/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-env/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-env/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/networks" reportModel "github.com/smartcontractkit/chainlink-testing-framework/testreporters" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -126,7 +127,7 @@ func NewOCRSoakTest(t *testing.T, forwarderFlow bool) (*OCRSoakTest, error) { // DeployEnvironment deploys the test environment, starting all Chainlink nodes and other components for the test func (o *OCRSoakTest) DeployEnvironment(customChainlinkNetworkTOML string) { - network := networks.SelectedNetwork // Environment currently being used to soak test on + network := networks.MustGetSelectedNetworksFromEnv()[0] // Environment currently being used to soak test on nsPre := "soak-ocr-" if o.OperatorForwarderFlow { nsPre = fmt.Sprintf("%sforwarder-", nsPre) @@ -141,7 +142,7 @@ func (o *OCRSoakTest) DeployEnvironment(customChainlinkNetworkTOML string) { cd := chainlink.New(0, map[string]any{ "replicas": 6, - "toml": client.AddNetworkDetailedConfig(config.BaseOCRP2PV1Config, customChainlinkNetworkTOML, network), + "toml": networks.AddNetworkDetailedConfig(config.BaseOCR1Config, customChainlinkNetworkTOML, network), "db": map[string]any{ "stateful": true, // stateful DB by default for soak tests }, @@ -163,9 +164,9 @@ func (o *OCRSoakTest) DeployEnvironment(customChainlinkNetworkTOML string) { } // LoadEnvironment loads an existing test environment using the provided URLs -func (o *OCRSoakTest) LoadEnvironment(chainlinkURLs []string, chainURL, mockServerURL string) { +func (o *OCRSoakTest) LoadEnvironment(chainlinkURLs []string, mockServerURL string) { var ( - network = networks.SelectedNetwork + network = networks.MustGetSelectedNetworksFromEnv()[0] err error ) o.chainClient, err = blockchain.ConnectEVMClient(network, o.log) @@ -185,7 +186,7 @@ func (o *OCRSoakTest) Environment() *environment.Environment { func (o *OCRSoakTest) Setup() { var ( err error - network = networks.SelectedNetwork + network = networks.MustGetSelectedNetworksFromEnv()[0] ) // Environment currently being used to soak test on @@ -241,7 +242,6 @@ func (o *OCRSoakTest) Setup() { o.Inputs.NumberOfContracts, linkTokenContract, contractDeployer, - o.bootstrapNode, o.workerNodes, o.chainClient, ) @@ -258,7 +258,7 @@ func (o *OCRSoakTest) Setup() { // Run starts the OCR soak test func (o *OCRSoakTest) Run() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + ctx, cancel := context.WithTimeout(testcontext.Get(o.t), time.Second*5) latestBlockNum, err := o.chainClient.LatestBlockNumber(ctx) cancel() require.NoError(o.t, err, "Error getting current block number") @@ -343,7 +343,7 @@ func (o *OCRSoakTest) SaveState() error { if err != nil { return err } - // #nosec G306 - let everyone read + //nolint:gosec // G306 - let everyone read if err = os.WriteFile(saveFileLocation, data, 0644); err != nil { return err } @@ -387,7 +387,7 @@ func (o *OCRSoakTest) LoadState() error { o.startTime = testState.StartTime o.startingBlockNum = testState.StartingBlockNum - network := networks.SelectedNetwork + network := networks.MustGetSelectedNetworksFromEnv()[0] o.chainClient, err = blockchain.ConnectEVMClient(network, o.log) if err != nil { return err @@ -468,6 +468,7 @@ func (o *OCRSoakTest) Interrupted() bool { func (o *OCRSoakTest) testLoop(testDuration time.Duration, newValue int) { endTest := time.After(testDuration) interruption := make(chan os.Signal, 1) + //nolint:staticcheck //ignore SA1016 we need to send the os.Kill signal signal.Notify(interruption, os.Kill, os.Interrupt, syscall.SIGTERM) lastValue := 0 newRoundTrigger := time.NewTimer(0) // Want to trigger a new round ASAP @@ -558,7 +559,7 @@ func (o *OCRSoakTest) setFilterQuery() { // WARNING: Should only be used for observation and logging. This is not a reliable way to collect events. func (o *OCRSoakTest) observeOCREvents() error { eventLogs := make(chan types.Log) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(testcontext.Get(o.t), 5*time.Second) eventSub, err := o.chainClient.SubscribeFilterLogs(ctx, o.filterQuery, eventLogs) cancel() if err != nil { @@ -592,7 +593,7 @@ func (o *OCRSoakTest) observeOCREvents() error { Str("Backoff", backoff.String()). Interface("Query", o.filterQuery). Msg("Error while subscribed to OCR Logs. Resubscribing") - ctx, cancel = context.WithTimeout(context.Background(), backoff) + ctx, cancel = context.WithTimeout(testcontext.Get(o.t), backoff) eventSub, err = o.chainClient.SubscribeFilterLogs(ctx, o.filterQuery, eventLogs) cancel() if err != nil { @@ -645,12 +646,12 @@ func (o *OCRSoakTest) collectEvents() error { timeout := time.Second * 15 o.log.Info().Interface("Filter Query", o.filterQuery).Str("Timeout", timeout.String()).Msg("Retrieving on-chain events") - ctx, cancel := context.WithTimeout(context.Background(), timeout) + ctx, cancel := context.WithTimeout(testcontext.Get(o.t), timeout) contractEvents, err := o.chainClient.FilterLogs(ctx, o.filterQuery) cancel() for err != nil { o.log.Info().Interface("Filter Query", o.filterQuery).Str("Timeout", timeout.String()).Msg("Retrieving on-chain events") - ctx, cancel := context.WithTimeout(context.Background(), timeout) + ctx, cancel := context.WithTimeout(testcontext.Get(o.t), timeout) contractEvents, err = o.chainClient.FilterLogs(ctx, o.filterQuery) cancel() if err != nil { diff --git a/integration-tests/testsetups/profile.go b/integration-tests/testsetups/profile.go index 6f978cdebee..14fe3d29ae6 100644 --- a/integration-tests/testsetups/profile.go +++ b/integration-tests/testsetups/profile.go @@ -7,8 +7,8 @@ import ( . "github.com/onsi/gomega" "golang.org/x/sync/errgroup" - "github.com/smartcontractkit/chainlink-env/environment" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" reportModel "github.com/smartcontractkit/chainlink-testing-framework/testreporters" "github.com/smartcontractkit/chainlink/integration-tests/client" diff --git a/integration-tests/testsetups/vrfv2.go b/integration-tests/testsetups/vrfv2.go deleted file mode 100644 index cfa26e8f279..00000000000 --- a/integration-tests/testsetups/vrfv2.go +++ /dev/null @@ -1,202 +0,0 @@ -package testsetups - -//revive:disable:dot-imports -import ( - "context" - "fmt" - "math/big" - "os" - "strconv" - "strings" - "testing" - "time" - - "github.com/rs/zerolog/log" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-env/environment" - "github.com/smartcontractkit/chainlink-testing-framework/blockchain" - "github.com/smartcontractkit/chainlink-testing-framework/logging" - reportModel "github.com/smartcontractkit/chainlink-testing-framework/testreporters" - - "github.com/smartcontractkit/chainlink/integration-tests/client" - "github.com/smartcontractkit/chainlink/integration-tests/contracts" - "github.com/smartcontractkit/chainlink/integration-tests/testreporters" -) - -// VRFV2SoakTest defines a typical VRFV2 soak test -type VRFV2SoakTest struct { - Inputs *VRFV2SoakTestInputs - - TestReporter testreporters.VRFV2SoakTestReporter - - testEnvironment *environment.Environment - namespace string - ChainlinkNodes []*client.ChainlinkK8sClient - chainClient blockchain.EVMClient - DefaultNetwork blockchain.EVMClient - - NumberOfRandRequests int - - ErrorOccurred error - ErrorCount int -} - -// VRFV2SoakTestTestFunc function type for the request and validation you want done on each iteration -type VRFV2SoakTestTestFunc func(t *VRFV2SoakTest, requestNumber int) error - -// VRFV2SoakTestInputs define required inputs to run a vrfv2 soak test -type VRFV2SoakTestInputs struct { - BlockchainClient blockchain.EVMClient // Client for the test to connect to the blockchain with - TestDuration time.Duration `envconfig:"TEST_DURATION" default:"15m"` // How long to run the test for (assuming things pass) - ChainlinkNodeFunding *big.Float `envconfig:"CHAINLINK_NODE_FUNDING" default:".1"` // Amount of ETH to fund each chainlink node with - SubscriptionFunding *big.Int `envconfig:"SUBSCRIPTION_FUNDING" default:"100"` // Amount of Link to fund VRF Coordinator subscription - StopTestOnError bool // Do we want the test to stop after any error or just continue on - - RequestsPerMinute int `envconfig:"REQUESTS_PER_MINUTE" default:"10"` // Number of requests for randomness per minute - RandomnessRequestCountPerRequest int `envconfig:"RANDOMNESS_REQUEST_COUNT_PER_REQUEST" default:"1"` - ConsumerContract contracts.VRFv2LoadTestConsumer - TestFunc VRFV2SoakTestTestFunc // The function that makes the request and validations wanted -} - -// NewVRFV2SoakTest creates a new vrfv2 soak test to setup and run -func NewVRFV2SoakTest(inputs *VRFV2SoakTestInputs, chainlinkNodes []*client.ChainlinkK8sClient) *VRFV2SoakTest { - return &VRFV2SoakTest{ - Inputs: inputs, - TestReporter: testreporters.VRFV2SoakTestReporter{ - Reports: make(map[string]*testreporters.VRFV2SoakTestReport), - }, - ChainlinkNodes: chainlinkNodes, - } -} - -// Setup sets up the test environment -func (v *VRFV2SoakTest) Setup(t *testing.T, env *environment.Environment) { - v.ensureInputValues(t) - v.testEnvironment = env - v.namespace = v.testEnvironment.Cfg.Namespace - v.chainClient.ParallelTransactions(true) -} - -// Run starts the VRFV2 soak test -func (v *VRFV2SoakTest) Run(t *testing.T) { - l := logging.GetTestLogger(t) - l.Info(). - Str("Test Duration", v.Inputs.TestDuration.Truncate(time.Second).String()). - Int("Max number of requests per minute wanted", v.Inputs.RequestsPerMinute). - Msg("Starting VRFV2 Soak Test") - - // set the requests to only run for a certain amount of time - testContext, testCancel := context.WithTimeout(context.Background(), v.Inputs.TestDuration) - defer testCancel() - - v.NumberOfRandRequests = 0 - - // variables dealing with how often to tick and how to stop the ticker - stop := false - startTime := time.Now() - ticker := time.NewTicker(time.Minute / time.Duration(v.Inputs.RequestsPerMinute)) - - for { - // start the loop by checking to see if any of the TestFunc responses have returned an error - if v.Inputs.StopTestOnError { - require.NoError(t, v.ErrorOccurred, "Found error") - } - select { - case <-testContext.Done(): - // stop making requests - stop = true - ticker.Stop() - break // breaks the select block - case <-ticker.C: - // make the next request - v.NumberOfRandRequests++ - go requestAndValidate(v, v.NumberOfRandRequests) - } - if stop { - break // breaks the for loop and stops the test - } - } - - err := v.chainClient.WaitForEvents() - if err != nil { - l.Error().Err(err).Msg("Error Occurred waiting for On chain events") - } - //wait some buffer time for requests to be fulfilled - //todo - need to find better way for this - time.Sleep(1 * time.Minute) - - loadTestMetrics, err := v.Inputs.ConsumerContract.GetLoadTestMetrics(nil) - if err != nil { - l.Error().Err(err).Msg("Error Occurred when getting Load Test Metrics from Consumer contract") - } - - averageFulfillmentInBlockTime := new(big.Float).Quo(new(big.Float).SetInt(loadTestMetrics.AverageFulfillmentInMillions), big.NewFloat(1e6)) - - l.Info().Int("Requests", v.NumberOfRandRequests).Msg("Total Completed Requests calculated from Test") - l.Info().Uint64("Requests", loadTestMetrics.RequestCount.Uint64()).Msg("Total Completed Requests calculated from Contract") - l.Info().Uint64("Fulfilments", loadTestMetrics.FulfilmentCount.Uint64()).Msg("Total Completed Fulfilments") - l.Info().Uint64("Fastest Fulfilment", loadTestMetrics.FastestFulfillment.Uint64()).Msg("Fastest Fulfilment") - l.Info().Uint64("Slowest Fulfilment", loadTestMetrics.SlowestFulfillment.Uint64()).Msg("Slowest Fulfilment") - l.Info().Interface("Average Fulfillment", averageFulfillmentInBlockTime).Msg("Average Fulfillment In Block Time") - - //todo - need to calculate 95th percentile response time in Block time and calculate how many requests breached 256 block time requirement - - l.Info().Str("Run Time", time.Since(startTime).String()).Msg("Finished VRFV2 Soak Test Requests") - require.Equal(t, 0, v.ErrorCount, "Expected 0 errors") - require.Equal(t, loadTestMetrics.RequestCount.Uint64(), loadTestMetrics.FulfilmentCount.Uint64(), "Number of Rand Requests should be equal to Number of Fulfillments") -} - -func requestAndValidate(t *VRFV2SoakTest, requestNumber int) { - - log.Info().Int("Request Number", requestNumber).Msg("Making a Request") - err := t.Inputs.TestFunc(t, requestNumber) - - // only set the error to be checked if err is not nil so we avoid race conditions with passing requests - if err != nil { - t.ErrorOccurred = err - log.Error().Err(err).Msg("Error Occurred during test") - t.ErrorCount++ - } -} - -// Networks returns the networks that the test is running on -func (v *VRFV2SoakTest) TearDownVals(t *testing.T) ( - *testing.T, - string, - []*client.ChainlinkK8sClient, - reportModel.TestReporter, - blockchain.EVMClient, -) { - return t, v.namespace, v.ChainlinkNodes, &v.TestReporter, v.chainClient -} - -// ensureValues ensures that all values needed to run the test are present -func (v *VRFV2SoakTest) ensureInputValues(t *testing.T) { - inputs := v.Inputs - require.NotNil(t, inputs.BlockchainClient, "Need a valid blockchain client for the test") - v.chainClient = inputs.BlockchainClient - require.GreaterOrEqual(t, inputs.RequestsPerMinute, 1, "Expecting at least 1 request per minute") - chainlinkNodeFunding, _ := inputs.ChainlinkNodeFunding.Float64() - subscriptionFunding := inputs.SubscriptionFunding.Int64() - require.Greater(t, chainlinkNodeFunding, float64(0), "Need some amount of funding for Chainlink nodes") - require.Greater(t, subscriptionFunding, int64(0), "Need some amount of funding for VRF V2 Coordinator Subscription nodes") - require.GreaterOrEqual(t, inputs.TestDuration, time.Minute, "Test duration should be longer than 1 minute") - require.NotNil(t, inputs.TestFunc, "Expected there to be test to run") -} - -func (i VRFV2SoakTestInputs) SetForRemoteRunner() { - os.Setenv("TEST_VRFV2_TEST_DURATION", i.TestDuration.String()) - os.Setenv("TEST_VRFV2_CHAINLINK_NODE_FUNDING", i.ChainlinkNodeFunding.String()) - os.Setenv("TEST_VRFV2_SUBSCRIPTION_FUNDING", i.SubscriptionFunding.String()) - os.Setenv("TEST_VRFV2_REQUESTS_PER_MINUTE", strconv.Itoa(i.RequestsPerMinute)) - os.Setenv("TEST_VRFV2_RANDOMNESS_REQUEST_COUNT_PER_REQUEST", strconv.Itoa(i.RandomnessRequestCountPerRequest)) - - selectedNetworks := strings.Split(os.Getenv("SELECTED_NETWORKS"), ",") - for _, networkPrefix := range selectedNetworks { - urlEnv := fmt.Sprintf("%s_URLS", networkPrefix) - httpEnv := fmt.Sprintf("%s_HTTP_URLS", networkPrefix) - os.Setenv(fmt.Sprintf("TEST_%s", urlEnv), os.Getenv(urlEnv)) - os.Setenv(fmt.Sprintf("TEST_%s", httpEnv), os.Getenv(httpEnv)) - } -} diff --git a/integration-tests/types/config/node/core.go b/integration-tests/types/config/node/core.go index 37047cdb667..ccbbce87a2e 100644 --- a/integration-tests/types/config/node/core.go +++ b/integration-tests/types/config/node/core.go @@ -11,8 +11,10 @@ import ( "github.com/segmentio/ksuid" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" - "github.com/smartcontractkit/chainlink/v2/core/assets" + "github.com/smartcontractkit/chainlink-testing-framework/utils/ptr" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/config/toml" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -21,43 +23,42 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/v2/core/utils/config" - "github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_constants" - utils2 "github.com/smartcontractkit/chainlink/integration-tests/utils" + it_utils "github.com/smartcontractkit/chainlink/integration-tests/utils" ) func NewBaseConfig() *chainlink.Config { return &chainlink.Config{ Core: toml.Core{ - RootDir: utils2.Ptr("/home/chainlink"), + RootDir: ptr.Ptr("/home/chainlink"), Database: toml.Database{ - MaxIdleConns: utils2.Ptr(int64(20)), - MaxOpenConns: utils2.Ptr(int64(40)), - MigrateOnStartup: utils2.Ptr(true), + MaxIdleConns: ptr.Ptr(int64(20)), + MaxOpenConns: ptr.Ptr(int64(40)), + MigrateOnStartup: ptr.Ptr(true), }, Log: toml.Log{ - Level: utils2.Ptr(toml.LogLevel(zapcore.DebugLevel)), - JSONConsole: utils2.Ptr(true), + Level: ptr.Ptr(toml.LogLevel(zapcore.DebugLevel)), + JSONConsole: ptr.Ptr(true), File: toml.LogFile{ - MaxSize: utils2.Ptr(utils.FileSize(0)), + MaxSize: ptr.Ptr(utils.FileSize(0)), }, }, WebServer: toml.WebServer{ - AllowOrigins: utils2.Ptr("*"), - HTTPPort: utils2.Ptr[uint16](6688), - SecureCookies: utils2.Ptr(false), + AllowOrigins: ptr.Ptr("*"), + HTTPPort: ptr.Ptr[uint16](6688), + SecureCookies: ptr.Ptr(false), SessionTimeout: models.MustNewDuration(time.Hour * 999), TLS: toml.WebServerTLS{ - HTTPSPort: utils2.Ptr[uint16](0), + HTTPSPort: ptr.Ptr[uint16](0), }, RateLimit: toml.WebServerRateLimit{ - Authenticated: utils2.Ptr(int64(2000)), - Unauthenticated: utils2.Ptr(int64(100)), + Authenticated: ptr.Ptr(int64(2000)), + Unauthenticated: ptr.Ptr(int64(100)), }, }, Feature: toml.Feature{ - LogPoller: utils2.Ptr(true), - FeedsManager: utils2.Ptr(true), - UICSAKeys: utils2.Ptr(true), + LogPoller: ptr.Ptr(true), + FeedsManager: ptr.Ptr(true), + UICSAKeys: ptr.Ptr(true), }, P2P: toml.P2P{}, }, @@ -95,7 +96,7 @@ func NewConfigFromToml(tomlFile string, opts ...NodeConfigOpt) (*chainlink.Confi func WithOCR1() NodeConfigOpt { return func(c *chainlink.Config) { c.OCR = toml.OCR{ - Enabled: utils2.Ptr(true), + Enabled: ptr.Ptr(true), } } } @@ -103,20 +104,22 @@ func WithOCR1() NodeConfigOpt { func WithOCR2() NodeConfigOpt { return func(c *chainlink.Config) { c.OCR2 = toml.OCR2{ - Enabled: utils2.Ptr(true), + Enabled: ptr.Ptr(true), } } } +// Deprecated: P2Pv1 is soon to be fully deprecated +// WithP2Pv1 enables P2Pv1 and disables P2Pv2 func WithP2Pv1() NodeConfigOpt { return func(c *chainlink.Config) { c.P2P.V1 = toml.P2PV1{ - Enabled: utils2.Ptr(true), - ListenIP: utils2.MustIP("0.0.0.0"), - ListenPort: utils2.Ptr[uint16](6690), + Enabled: ptr.Ptr(true), + ListenIP: it_utils.MustIP("0.0.0.0"), + ListenPort: ptr.Ptr[uint16](6690), } // disabled default - c.P2P.V2 = toml.P2PV2{Enabled: utils2.Ptr(false)} + c.P2P.V2 = toml.P2PV2{Enabled: ptr.Ptr(false)} } } @@ -131,14 +134,15 @@ func WithP2Pv2() NodeConfigOpt { func WithTracing() NodeConfigOpt { return func(c *chainlink.Config) { c.Tracing = toml.Tracing{ - Enabled: utils2.Ptr(true), - CollectorTarget: utils2.Ptr("otel-collector:4317"), + Enabled: ptr.Ptr(true), + CollectorTarget: ptr.Ptr("otel-collector:4317"), // ksortable unique id - NodeID: utils2.Ptr(ksuid.New().String()), + NodeID: ptr.Ptr(ksuid.New().String()), + SamplingRatio: ptr.Ptr(1.0), + Mode: ptr.Ptr("unencrypted"), Attributes: map[string]string{ "env": "smoke", }, - SamplingRatio: utils2.Ptr(1.0), } } } @@ -154,10 +158,10 @@ func SetChainConfig( var nodes []*evmcfg.Node for i := range wsUrls { node := evmcfg.Node{ - Name: utils2.Ptr(fmt.Sprintf("node_%d_%s", i, chain.Name)), - WSURL: utils2.MustURL(wsUrls[i]), - HTTPURL: utils2.MustURL(httpUrls[i]), - SendOnly: utils2.Ptr(false), + Name: ptr.Ptr(fmt.Sprintf("node_%d_%s", i, chain.Name)), + WSURL: it_utils.MustURL(wsUrls[i]), + HTTPURL: it_utils.MustURL(httpUrls[i]), + SendOnly: ptr.Ptr(false), } nodes = append(nodes, &node) @@ -165,9 +169,9 @@ func SetChainConfig( var chainConfig evmcfg.Chain if chain.Simulated { chainConfig = evmcfg.Chain{ - AutoCreateKey: utils2.Ptr(true), - FinalityDepth: utils2.Ptr[uint32](1), - MinContractPayment: assets.NewLinkFromJuels(0), + AutoCreateKey: ptr.Ptr(true), + FinalityDepth: ptr.Ptr[uint32](1), + MinContractPayment: commonassets.NewLinkFromJuels(0), } } cfg.EVM = evmcfg.EVMConfigs{ @@ -179,7 +183,7 @@ func SetChainConfig( } if forwarders { cfg.EVM[0].Transactions = evmcfg.Transactions{ - ForwardersEnabled: utils2.Ptr(true), + ForwardersEnabled: ptr.Ptr(true), } } } @@ -191,25 +195,25 @@ func WithPrivateEVMs(networks []blockchain.EVMNetwork) NodeConfigOpt { evmConfigs = append(evmConfigs, &evmcfg.EVMConfig{ ChainID: utils.NewBig(big.NewInt(network.ChainID)), Chain: evmcfg.Chain{ - AutoCreateKey: utils2.Ptr(true), - FinalityDepth: utils2.Ptr[uint32](50), - MinContractPayment: assets.NewLinkFromJuels(0), + AutoCreateKey: ptr.Ptr(true), + FinalityDepth: ptr.Ptr[uint32](50), + MinContractPayment: commonassets.NewLinkFromJuels(0), LogPollInterval: models.MustNewDuration(1 * time.Second), HeadTracker: evmcfg.HeadTracker{ - HistoryDepth: utils2.Ptr(uint32(100)), + HistoryDepth: ptr.Ptr(uint32(100)), }, GasEstimator: evmcfg.GasEstimator{ - LimitDefault: utils2.Ptr(uint32(6000000)), + LimitDefault: ptr.Ptr(uint32(6000000)), PriceMax: assets.GWei(200), FeeCapDefault: assets.GWei(200), }, }, Nodes: []*evmcfg.Node{ { - Name: utils2.Ptr(network.Name), - WSURL: utils2.MustURL(network.URLs[0]), - HTTPURL: utils2.MustURL(network.HTTPURLs[0]), - SendOnly: utils2.Ptr(false), + Name: ptr.Ptr(network.Name), + WSURL: it_utils.MustURL(network.URLs[0]), + HTTPURL: it_utils.MustURL(network.HTTPURLs[0]), + SendOnly: ptr.Ptr(false), }, }, }) @@ -219,22 +223,32 @@ func WithPrivateEVMs(networks []blockchain.EVMNetwork) NodeConfigOpt { } } -func WithVRFv2EVMEstimator(addr string) NodeConfigOpt { - est := assets.GWei(vrfv2_constants.MaxGasPriceGWei) - return func(c *chainlink.Config) { - c.EVM[0].KeySpecific = evmcfg.KeySpecificConfig{ - { - Key: utils2.Ptr(ethkey.EIP55Address(addr)), - GasEstimator: evmcfg.KeySpecificGasEstimator{ - PriceMax: est, - }, +func WithVRFv2EVMEstimator(addresses []string, maxGasPriceGWei int64) NodeConfigOpt { + est := assets.GWei(maxGasPriceGWei) + + var keySpecicifArr []evmcfg.KeySpecific + for _, addr := range addresses { + keySpecicifArr = append(keySpecicifArr, evmcfg.KeySpecific{ + Key: ptr.Ptr(ethkey.EIP55Address(addr)), + GasEstimator: evmcfg.KeySpecificGasEstimator{ + PriceMax: est, }, - } + }) + } + return func(c *chainlink.Config) { + c.EVM[0].KeySpecific = keySpecicifArr c.EVM[0].Chain.GasEstimator = evmcfg.GasEstimator{ - LimitDefault: utils2.Ptr[uint32](3500000), + LimitDefault: ptr.Ptr[uint32](3500000), } c.EVM[0].Chain.Transactions = evmcfg.Transactions{ - MaxQueued: utils2.Ptr[uint32](10000), + MaxQueued: ptr.Ptr[uint32](10000), } + + } +} + +func WithLogPollInterval(interval time.Duration) NodeConfigOpt { + return func(c *chainlink.Config) { + c.EVM[0].Chain.LogPollInterval = models.MustNewDuration(interval) } } diff --git a/integration-tests/types/envcommon/common.go b/integration-tests/types/envcommon/common.go index 607c481f33f..bdabcaf96b0 100644 --- a/integration-tests/types/envcommon/common.go +++ b/integration-tests/types/envcommon/common.go @@ -2,7 +2,7 @@ package envcommon import ( "encoding/json" - "io/ioutil" + "io" "os" ) @@ -12,7 +12,7 @@ func ParseJSONFile(path string, v any) error { return err } defer jsonFile.Close() - b, _ := ioutil.ReadAll(jsonFile) + b, _ := io.ReadAll(jsonFile) err = json.Unmarshal(b, v) if err != nil { return err diff --git a/integration-tests/universal/log_poller/config.go b/integration-tests/universal/log_poller/config.go new file mode 100644 index 00000000000..78a0da46bc6 --- /dev/null +++ b/integration-tests/universal/log_poller/config.go @@ -0,0 +1,249 @@ +package logpoller + +import ( + "fmt" + "os" + "strconv" + + "cosmossdk.io/errors" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/pelletier/go-toml/v2" + "github.com/rs/zerolog/log" + + "github.com/smartcontractkit/chainlink/v2/core/store/models" +) + +const ( + DefaultConfigFilename = "config.toml" + + ErrReadPerfConfig = "failed to read TOML config for performance tests" + ErrUnmarshalPerfConfig = "failed to unmarshal TOML config for performance tests" +) + +type GeneratorType = string + +const ( + GeneratorType_WASP = "wasp" + GeneratorType_Looped = "looped" +) + +type Config struct { + General *General `toml:"general"` + ChaosConfig *ChaosConfig `toml:"chaos"` + Wasp *WaspConfig `toml:"wasp"` + LoopedConfig *LoopedConfig `toml:"looped"` +} + +type LoopedConfig struct { + ContractConfig `toml:"contract"` + FuzzConfig `toml:"fuzz"` +} + +type ContractConfig struct { + ExecutionCount int `toml:"execution_count"` +} + +type FuzzConfig struct { + MinEmitWaitTimeMs int `toml:"min_emit_wait_time_ms"` + MaxEmitWaitTimeMs int `toml:"max_emit_wait_time_ms"` +} + +type General struct { + Generator string `toml:"generator"` + EventsToEmit []abi.Event `toml:"-"` + Contracts int `toml:"contracts"` + EventsPerTx int `toml:"events_per_tx"` + UseFinalityTag bool `toml:"use_finality_tag"` +} + +type ChaosConfig struct { + ExperimentCount int `toml:"experiment_count"` +} + +type WaspConfig struct { + Load *Load `toml:"load"` +} + +type Load struct { + RPS int64 `toml:"rps"` + LPS int64 `toml:"lps"` + RateLimitUnitDuration *models.Duration `toml:"rate_limit_unit_duration"` + Duration *models.Duration `toml:"duration"` + CallTimeout *models.Duration `toml:"call_timeout"` +} + +func ReadConfig(configName string) (*Config, error) { + var cfg *Config + d, err := os.ReadFile(configName) + if err != nil { + return nil, errors.Wrap(err, ErrReadPerfConfig) + } + err = toml.Unmarshal(d, &cfg) + if err != nil { + return nil, errors.Wrap(err, ErrUnmarshalPerfConfig) + } + + if err := cfg.validate(); err != nil { + return nil, err + } + + log.Debug().Interface("Config", cfg).Msg("Parsed config") + return cfg, nil +} + +func (c *Config) OverrideFromEnv() error { + if contr := os.Getenv("CONTRACTS"); contr != "" { + c.General.Contracts = mustParseInt(contr) + } + + if eventsPerTx := os.Getenv("EVENTS_PER_TX"); eventsPerTx != "" { + c.General.EventsPerTx = mustParseInt(eventsPerTx) + } + + if useFinalityTag := os.Getenv("USE_FINALITY_TAG"); useFinalityTag != "" { + c.General.UseFinalityTag = mustParseBool(useFinalityTag) + } + + if duration := os.Getenv("LOAD_DURATION"); duration != "" { + d, err := models.ParseDuration(duration) + if err != nil { + return err + } + + if c.General.Generator == GeneratorType_WASP { + c.Wasp.Load.Duration = &d + } else { + // this is completely arbitrary and practice shows that even with this values + // test executes much longer than specified, probably due to network latency + c.LoopedConfig.FuzzConfig.MinEmitWaitTimeMs = 400 + c.LoopedConfig.FuzzConfig.MaxEmitWaitTimeMs = 600 + // divide by 4 based on past runs, but we should do it in a better way + c.LoopedConfig.ContractConfig.ExecutionCount = int(d.Duration().Seconds() / 4) + } + } + + return nil +} + +func (c *Config) validate() error { + if c.General == nil { + return fmt.Errorf("General config is nil") + } + + err := c.General.validate() + if err != nil { + return fmt.Errorf("General config validation failed: %w", err) + } + + switch c.General.Generator { + case GeneratorType_WASP: + if c.Wasp == nil { + return fmt.Errorf("wasp config is nil") + } + if c.Wasp.Load == nil { + return fmt.Errorf("wasp load config is nil") + } + + err = c.Wasp.validate() + if err != nil { + return fmt.Errorf("wasp config validation failed: %w", err) + } + case GeneratorType_Looped: + if c.LoopedConfig == nil { + return fmt.Errorf("looped config is nil") + } + + err = c.LoopedConfig.validate() + if err != nil { + return fmt.Errorf("looped config validation failed: %w", err) + } + default: + return fmt.Errorf("unknown generator type: %s", c.General.Generator) + } + + return nil +} + +func (g *General) validate() error { + if g.Generator == "" { + return fmt.Errorf("generator is empty") + } + + if g.Contracts == 0 { + return fmt.Errorf("contracts is 0, but must be > 0") + } + + if g.EventsPerTx == 0 { + return fmt.Errorf("events_per_tx is 0, but must be > 0") + } + + return nil +} + +func (w *WaspConfig) validate() error { + if w.Load == nil { + return fmt.Errorf("Load config is nil") + } + + err := w.Load.validate() + if err != nil { + return fmt.Errorf("Load config validation failed: %w", err) + } + + return nil +} + +func (l *Load) validate() error { + if l.RPS == 0 && l.LPS == 0 { + return fmt.Errorf("either RPS or LPS needs to be set") + } + + if l.RPS != 0 && l.LPS != 0 { + return fmt.Errorf("only one of RPS or LPS can be set") + } + + if l.Duration == nil { + return fmt.Errorf("duration is nil") + } + + if l.CallTimeout == nil { + return fmt.Errorf("call_timeout is nil") + } + if l.RateLimitUnitDuration == nil { + return fmt.Errorf("rate_limit_unit_duration is nil") + } + + return nil +} + +func (l *LoopedConfig) validate() error { + if l.ExecutionCount == 0 { + return fmt.Errorf("execution_count is 0, but must be > 0") + } + + if l.MinEmitWaitTimeMs == 0 { + return fmt.Errorf("min_emit_wait_time_ms is 0, but must be > 0") + } + + if l.MaxEmitWaitTimeMs == 0 { + return fmt.Errorf("max_emit_wait_time_ms is 0, but must be > 0") + } + + return nil +} + +func mustParseInt(s string) int { + i, err := strconv.Atoi(s) + if err != nil { + panic(err) + } + return i +} + +func mustParseBool(s string) bool { + b, err := strconv.ParseBool(s) + if err != nil { + panic(err) + } + return b +} diff --git a/integration-tests/universal/log_poller/gun.go b/integration-tests/universal/log_poller/gun.go new file mode 100644 index 00000000000..39286f1b53e --- /dev/null +++ b/integration-tests/universal/log_poller/gun.go @@ -0,0 +1,79 @@ +package logpoller + +import ( + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/rs/zerolog" + + "github.com/smartcontractkit/wasp" + + "github.com/smartcontractkit/chainlink/integration-tests/contracts" +) + +/* LogEmitterGun is a gun that constantly emits logs from a contract */ +type LogEmitterGun struct { + contract *contracts.LogEmitter + eventsToEmit []abi.Event + logger zerolog.Logger + eventsPerTx int +} + +type Counter struct { + mu *sync.Mutex + value int +} + +func NewLogEmitterGun( + contract *contracts.LogEmitter, + eventsToEmit []abi.Event, + eventsPerTx int, + logger zerolog.Logger, +) *LogEmitterGun { + return &LogEmitterGun{ + contract: contract, + eventsToEmit: eventsToEmit, + eventsPerTx: eventsPerTx, + logger: logger, + } +} + +func (m *LogEmitterGun) Call(l *wasp.Generator) *wasp.CallResult { + localCounter := 0 + logEmitter := (*m.contract) + address := logEmitter.Address() + for _, event := range m.eventsToEmit { + m.logger.Debug().Str("Emitter address", address.String()).Str("Event type", event.Name).Msg("Emitting log from emitter") + var err error + switch event.Name { + case "Log1": + _, err = logEmitter.EmitLogInts(getIntSlice(m.eventsPerTx)) + case "Log2": + _, err = logEmitter.EmitLogIntsIndexed(getIntSlice(m.eventsPerTx)) + case "Log3": + _, err = logEmitter.EmitLogStrings(getStringSlice(m.eventsPerTx)) + default: + err = fmt.Errorf("unknown event name: %s", event.Name) + } + + if err != nil { + return &wasp.CallResult{Error: err.Error(), Failed: true} + } + localCounter++ + } + + // I don't think that will work as expected, I should atomically read the value and save it, so maybe just a mutex? + if counter, ok := l.InputSharedData().(*Counter); ok { + counter.mu.Lock() + defer counter.mu.Unlock() + counter.value += localCounter + } else { + return &wasp.CallResult{ + Error: "SharedData did not contain a Counter", + Failed: true, + } + } + + return &wasp.CallResult{} +} diff --git a/integration-tests/universal/log_poller/helpers.go b/integration-tests/universal/log_poller/helpers.go new file mode 100644 index 00000000000..7bf1657316f --- /dev/null +++ b/integration-tests/universal/log_poller/helpers.go @@ -0,0 +1,1118 @@ +package logpoller + +import ( + "bytes" + "context" + "fmt" + "math/big" + "math/rand" + "sort" + "strings" + "sync" + "testing" + "time" + + geth "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + geth_types "github.com/ethereum/go-ethereum/core/types" + "github.com/jmoiron/sqlx" + "github.com/rs/zerolog" + "github.com/scylladb/go-reflectx" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/wasp" + + "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + ctf_test_env "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" + "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/networks" + "github.com/smartcontractkit/chainlink-testing-framework/utils/ptr" + evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + cltypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" + le "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter" + core_logger "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/store/models" + + "github.com/smartcontractkit/chainlink/integration-tests/actions" + "github.com/smartcontractkit/chainlink/integration-tests/client" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" + "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" + "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" + "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" +) + +var ( + EmitterABI, _ = abi.JSON(strings.NewReader(le.LogEmitterABI)) + automationUtilsABI = cltypes.MustGetABI(automation_utils_2_1.AutomationUtilsABI) + bytes0 = [32]byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + } // bytes representation of 0x0000000000000000000000000000000000000000000000000000000000000000 + +) + +var registerSingleTopicFilter = func(registry contracts.KeeperRegistry, upkeepID *big.Int, emitterAddress common.Address, topic common.Hash) error { + logTriggerConfigStruct := automation_utils_2_1.LogTriggerConfig{ + ContractAddress: emitterAddress, + FilterSelector: 0, + Topic0: topic, + Topic1: bytes0, + Topic2: bytes0, + Topic3: bytes0, + } + encodedLogTriggerConfig, err := automationUtilsABI.Methods["_logTriggerConfig"].Inputs.Pack(&logTriggerConfigStruct) + if err != nil { + return err + } + + err = registry.SetUpkeepTriggerConfig(upkeepID, encodedLogTriggerConfig) + if err != nil { + return err + } + + return nil +} + +// Currently Unused November 8, 2023, Might be useful in the near future so keeping it here for now +// this is not really possible, log trigger doesn't support multiple topics, even if log poller does +// var registerMultipleTopicsFilter = func(registry contracts.KeeperRegistry, upkeepID *big.Int, emitterAddress common.Address, topics []abi.Event) error { +// if len(topics) > 4 { +// return errors.New("Cannot register more than 4 topics") +// } + +// var getTopic = func(topics []abi.Event, i int) common.Hash { +// if i > len(topics)-1 { +// return bytes0 +// } + +// return topics[i].ID +// } + +// var getFilterSelector = func(topics []abi.Event) (uint8, error) { +// switch len(topics) { +// case 0: +// return 0, errors.New("Cannot register filter with 0 topics") +// case 1: +// return 0, nil +// case 2: +// return 1, nil +// case 3: +// return 3, nil +// case 4: +// return 7, nil +// default: +// return 0, errors.New("Cannot register filter with more than 4 topics") +// } +// } + +// filterSelector, err := getFilterSelector(topics) +// if err != nil { +// return err +// } + +// logTriggerConfigStruct := automation_utils_2_1.LogTriggerConfig{ +// ContractAddress: emitterAddress, +// FilterSelector: filterSelector, +// Topic0: getTopic(topics, 0), +// Topic1: getTopic(topics, 1), +// Topic2: getTopic(topics, 2), +// Topic3: getTopic(topics, 3), +// } +// encodedLogTriggerConfig, err := automationUtilsABI.Methods["_logTriggerConfig"].Inputs.Pack(&logTriggerConfigStruct) +// if err != nil { +// return err +// } + +// err = registry.SetUpkeepTriggerConfig(upkeepID, encodedLogTriggerConfig) +// if err != nil { +// return err +// } + +// return nil +// } + +func NewOrm(logger core_logger.SugaredLogger, chainID *big.Int, postgresDb *ctf_test_env.PostgresDb) (*logpoller.DbORM, *sqlx.DB, error) { + dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", "127.0.0.1", postgresDb.ExternalPort, postgresDb.User, postgresDb.Password, postgresDb.DbName) + db, err := sqlx.Open("postgres", dsn) + if err != nil { + return nil, db, err + } + + db.MapperFunc(reflectx.CamelToSnakeASCII) + return logpoller.NewORM(chainID, db, logger, pg.NewQConfig(false)), db, nil +} + +type ExpectedFilter struct { + emitterAddress common.Address + topic common.Hash +} + +func getExpectedFilters(logEmitters []*contracts.LogEmitter, cfg *Config) []ExpectedFilter { + expectedFilters := make([]ExpectedFilter, 0) + for _, emitter := range logEmitters { + for _, event := range cfg.General.EventsToEmit { + expectedFilters = append(expectedFilters, ExpectedFilter{ + emitterAddress: (*emitter).Address(), + topic: event.ID, + }) + } + } + + return expectedFilters +} + +var nodeHasExpectedFilters = func(expectedFilters []ExpectedFilter, logger core_logger.SugaredLogger, chainID *big.Int, postgresDb *ctf_test_env.PostgresDb) (bool, error) { + orm, db, err := NewOrm(logger, chainID, postgresDb) + if err != nil { + return false, err + } + + defer db.Close() + knownFilters, err := orm.LoadFilters() + if err != nil { + return false, err + } + + for _, expectedFilter := range expectedFilters { + filterFound := false + for _, knownFilter := range knownFilters { + if bytes.Equal(expectedFilter.emitterAddress.Bytes(), knownFilter.Addresses[0].Bytes()) && bytes.Equal(expectedFilter.topic.Bytes(), knownFilter.EventSigs[0].Bytes()) { + filterFound = true + break + } + } + + if !filterFound { + return false, fmt.Errorf("no filter found for emitter %s and topic %s", expectedFilter.emitterAddress.String(), expectedFilter.topic.Hex()) + } + } + + return true, nil +} + +var randomWait = func(minMilliseconds, maxMilliseconds int) { + rand.New(rand.NewSource(time.Now().UnixNano())) + randomMilliseconds := rand.Intn(maxMilliseconds-minMilliseconds+1) + minMilliseconds + time.Sleep(time.Duration(randomMilliseconds) * time.Millisecond) +} + +type LogEmitterChannel struct { + logsEmitted int + err error + // unused + // currentIndex int +} + +func getIntSlice(length int) []int { + result := make([]int, length) + for i := 0; i < length; i++ { + result[i] = i + } + + return result +} + +func getStringSlice(length int) []string { + result := make([]string, length) + for i := 0; i < length; i++ { + result[i] = "amazing event" + } + + return result +} + +var emitEvents = func(ctx context.Context, l zerolog.Logger, logEmitter *contracts.LogEmitter, cfg *Config, wg *sync.WaitGroup, results chan LogEmitterChannel) { + address := (*logEmitter).Address().String() + localCounter := 0 + select { + case <-ctx.Done(): + l.Warn().Str("Emitter address", address).Msg("Context cancelled, not emitting events") + return + default: + defer wg.Done() + for i := 0; i < cfg.LoopedConfig.ExecutionCount; i++ { + for _, event := range cfg.General.EventsToEmit { + l.Debug().Str("Emitter address", address).Str("Event type", event.Name).Str("index", fmt.Sprintf("%d/%d", (i+1), cfg.LoopedConfig.ExecutionCount)).Msg("Emitting log from emitter") + var err error + switch event.Name { + case "Log1": + _, err = (*logEmitter).EmitLogInts(getIntSlice(cfg.General.EventsPerTx)) + case "Log2": + _, err = (*logEmitter).EmitLogIntsIndexed(getIntSlice(cfg.General.EventsPerTx)) + case "Log3": + _, err = (*logEmitter).EmitLogStrings(getStringSlice(cfg.General.EventsPerTx)) + default: + err = fmt.Errorf("unknown event name: %s", event.Name) + } + + if err != nil { + results <- LogEmitterChannel{ + logsEmitted: 0, + err: err, + } + return + } + localCounter += cfg.General.EventsPerTx + + randomWait(cfg.LoopedConfig.FuzzConfig.MinEmitWaitTimeMs, cfg.LoopedConfig.FuzzConfig.MaxEmitWaitTimeMs) + } + + if (i+1)%10 == 0 { + l.Info().Str("Emitter address", address).Str("Index", fmt.Sprintf("%d/%d", i+1, cfg.LoopedConfig.ExecutionCount)).Msg("Emitted all three events") + } + } + + l.Info().Str("Emitter address", address).Int("Total logs emitted", localCounter).Msg("Finished emitting events") + + results <- LogEmitterChannel{ + logsEmitted: localCounter, + err: nil, + } + } +} + +var chainHasFinalisedEndBlock = func(l zerolog.Logger, evmClient blockchain.EVMClient, endBlock int64) (bool, error) { + effectiveEndBlock := endBlock + 1 + lastFinalisedBlockHeader, err := evmClient.GetLatestFinalizedBlockHeader(context.Background()) + if err != nil { + return false, err + } + + l.Info().Int64("Last finalised block header", lastFinalisedBlockHeader.Number.Int64()).Int64("End block", effectiveEndBlock).Int64("Blocks left till end block", effectiveEndBlock-lastFinalisedBlockHeader.Number.Int64()).Msg("Waiting for the finalized block to move beyond end block") + + return lastFinalisedBlockHeader.Number.Int64() > effectiveEndBlock, nil +} + +var logPollerHasFinalisedEndBlock = func(endBlock int64, chainID *big.Int, l zerolog.Logger, coreLogger core_logger.SugaredLogger, nodes *test_env.ClCluster) (bool, error) { + wg := &sync.WaitGroup{} + + type boolQueryResult struct { + nodeName string + hasFinalised bool + err error + } + + endBlockCh := make(chan boolQueryResult, len(nodes.Nodes)-1) + ctx, cancelFn := context.WithCancel(context.Background()) + + for i := 1; i < len(nodes.Nodes); i++ { + wg.Add(1) + + go func(clNode *test_env.ClNode, r chan boolQueryResult) { + defer wg.Done() + select { + case <-ctx.Done(): + return + default: + orm, db, err := NewOrm(coreLogger, chainID, clNode.PostgresDb) + if err != nil { + r <- boolQueryResult{ + nodeName: clNode.ContainerName, + hasFinalised: false, + err: err, + } + } + + defer db.Close() + + latestBlock, err := orm.SelectLatestBlock() + if err != nil { + r <- boolQueryResult{ + nodeName: clNode.ContainerName, + hasFinalised: false, + err: err, + } + } + + r <- boolQueryResult{ + nodeName: clNode.ContainerName, + hasFinalised: latestBlock.FinalizedBlockNumber > endBlock, + err: nil, + } + + } + }(nodes.Nodes[i], endBlockCh) + } + + var err error + allFinalisedCh := make(chan bool, 1) + + go func() { + foundMap := make(map[string]bool, 0) + for r := range endBlockCh { + if r.err != nil { + err = r.err + cancelFn() + return + } + + foundMap[r.nodeName] = r.hasFinalised + if r.hasFinalised { + l.Info().Str("Node name", r.nodeName).Msg("CL node has finalised end block") + } else { + l.Warn().Str("Node name", r.nodeName).Msg("CL node has not finalised end block yet") + } + + if len(foundMap) == len(nodes.Nodes)-1 { + allFinalised := true + for _, v := range foundMap { + if !v { + allFinalised = false + break + } + } + + allFinalisedCh <- allFinalised + return + } + } + }() + + wg.Wait() + close(endBlockCh) + + return <-allFinalisedCh, err +} + +var clNodesHaveExpectedLogCount = func(startBlock, endBlock int64, chainID *big.Int, expectedLogCount int, expectedFilters []ExpectedFilter, l zerolog.Logger, coreLogger core_logger.SugaredLogger, nodes *test_env.ClCluster) (bool, error) { + wg := &sync.WaitGroup{} + + type logQueryResult struct { + nodeName string + logCount int + hasExpectedCount bool + err error + } + + queryCh := make(chan logQueryResult, len(nodes.Nodes)-1) + ctx, cancelFn := context.WithCancel(context.Background()) + + for i := 1; i < len(nodes.Nodes); i++ { + wg.Add(1) + + go func(clNode *test_env.ClNode, r chan logQueryResult) { + defer wg.Done() + select { + case <-ctx.Done(): + return + default: + orm, db, err := NewOrm(coreLogger, chainID, clNode.PostgresDb) + if err != nil { + r <- logQueryResult{ + nodeName: clNode.ContainerName, + logCount: 0, + hasExpectedCount: false, + err: err, + } + } + + defer db.Close() + foundLogsCount := 0 + + for _, filter := range expectedFilters { + logs, err := orm.SelectLogs(startBlock, endBlock, filter.emitterAddress, filter.topic) + if err != nil { + r <- logQueryResult{ + nodeName: clNode.ContainerName, + logCount: 0, + hasExpectedCount: false, + err: err, + } + } + + foundLogsCount += len(logs) + } + + r <- logQueryResult{ + nodeName: clNode.ContainerName, + logCount: foundLogsCount, + hasExpectedCount: foundLogsCount >= expectedLogCount, + err: err, + } + } + }(nodes.Nodes[i], queryCh) + } + + var err error + allFoundCh := make(chan bool, 1) + + go func() { + foundMap := make(map[string]bool, 0) + for r := range queryCh { + if r.err != nil { + err = r.err + cancelFn() + return + } + + foundMap[r.nodeName] = r.hasExpectedCount + if r.hasExpectedCount { + l.Info().Str("Node name", r.nodeName).Int("Logs count", r.logCount).Msg("Expected log count found in CL node") + } else { + l.Warn().Str("Node name", r.nodeName).Str("Found/Expected logs", fmt.Sprintf("%d/%d", r.logCount, expectedLogCount)).Int("Missing logs", expectedLogCount-r.logCount).Msg("Too low log count found in CL node") + } + + if len(foundMap) == len(nodes.Nodes)-1 { + allFound := true + for _, v := range foundMap { + if !v { + allFound = false + break + } + } + + allFoundCh <- allFound + return + } + } + }() + + wg.Wait() + close(queryCh) + + return <-allFoundCh, err +} + +type MissingLogs map[string][]geth_types.Log + +func (m *MissingLogs) IsEmpty() bool { + for _, v := range *m { + if len(v) > 0 { + return false + } + } + + return true +} + +var getMissingLogs = func(startBlock, endBlock int64, logEmitters []*contracts.LogEmitter, evmClient blockchain.EVMClient, clnodeCluster *test_env.ClCluster, l zerolog.Logger, coreLogger core_logger.SugaredLogger, cfg *Config) (MissingLogs, error) { + wg := &sync.WaitGroup{} + + type dbQueryResult struct { + err error + nodeName string + logs []logpoller.Log + } + + ctx, cancelFn := context.WithCancel(context.Background()) + resultCh := make(chan dbQueryResult, len(clnodeCluster.Nodes)-1) + + for i := 1; i < len(clnodeCluster.Nodes); i++ { + wg.Add(1) + + go func(ctx context.Context, i int, r chan dbQueryResult) { + defer wg.Done() + select { + case <-ctx.Done(): + l.Warn().Msg("Context cancelled. Terminating fetching logs from log poller's DB") + return + default: + nodeName := clnodeCluster.Nodes[i].ContainerName + + l.Info().Str("Node name", nodeName).Msg("Fetching log poller logs") + orm, db, err := NewOrm(coreLogger, evmClient.GetChainID(), clnodeCluster.Nodes[i].PostgresDb) + if err != nil { + r <- dbQueryResult{ + err: err, + nodeName: nodeName, + logs: []logpoller.Log{}, + } + } + + defer db.Close() + logs := make([]logpoller.Log, 0) + + for j := 0; j < len(logEmitters); j++ { + address := (*logEmitters[j]).Address() + + for _, event := range cfg.General.EventsToEmit { + l.Debug().Str("Event name", event.Name).Str("Emitter address", address.String()).Msg("Fetching single emitter's logs") + result, err := orm.SelectLogs(startBlock, endBlock, address, event.ID) + if err != nil { + r <- dbQueryResult{ + err: err, + nodeName: nodeName, + logs: []logpoller.Log{}, + } + } + + sort.Slice(result, func(i, j int) bool { + return result[i].BlockNumber < result[j].BlockNumber + }) + + logs = append(logs, result...) + + l.Debug().Str("Event name", event.Name).Str("Emitter address", address.String()).Int("Log count", len(result)).Msg("Logs found per node") + } + } + + l.Warn().Int("Count", len(logs)).Str("Node name", nodeName).Msg("Fetched log poller logs") + + r <- dbQueryResult{ + err: nil, + nodeName: nodeName, + logs: logs, + } + } + }(ctx, i, resultCh) + } + + allLogPollerLogs := make(map[string][]logpoller.Log, 0) + missingLogs := map[string][]geth_types.Log{} + var dbError error + + go func() { + for r := range resultCh { + if r.err != nil { + l.Err(r.err).Str("Node name", r.nodeName).Msg("Error fetching logs from log poller's DB") + dbError = r.err + cancelFn() + return + } + // use channel for aggregation and then for := range over it after closing resultCh? + allLogPollerLogs[r.nodeName] = r.logs + } + }() + + wg.Wait() + close(resultCh) + + if dbError != nil { + return nil, dbError + } + + allLogsInEVMNode, err := getEVMLogs(startBlock, endBlock, logEmitters, evmClient, l, cfg) + if err != nil { + return nil, err + } + + wg = &sync.WaitGroup{} + + type missingLogResult struct { + nodeName string + logs []geth_types.Log + } + + l.Info().Msg("Started comparison of logs from EVM node and CL nodes. This may take a while if there's a lot of logs") + missingCh := make(chan missingLogResult, len(clnodeCluster.Nodes)-1) + evmLogCount := len(allLogsInEVMNode) + for i := 1; i < len(clnodeCluster.Nodes); i++ { + wg.Add(1) + + go func(i int, result chan missingLogResult) { + defer wg.Done() + nodeName := clnodeCluster.Nodes[i].ContainerName + l.Info().Str("Node name", nodeName).Str("Progress", fmt.Sprintf("0/%d", evmLogCount)).Msg("Comparing single CL node's logs with EVM logs") + + missingLogs := make([]geth_types.Log, 0) + for i, evmLog := range allLogsInEVMNode { + logFound := false + for _, logPollerLog := range allLogPollerLogs[nodeName] { + if logPollerLog.BlockNumber == int64(evmLog.BlockNumber) && logPollerLog.TxHash == evmLog.TxHash && bytes.Equal(logPollerLog.Data, evmLog.Data) && logPollerLog.LogIndex == int64(evmLog.Index) && + logPollerLog.Address == evmLog.Address && logPollerLog.BlockHash == evmLog.BlockHash && bytes.Equal(logPollerLog.Topics[0][:], evmLog.Topics[0].Bytes()) { + logFound = true + continue + } + } + + if i%10000 == 0 && i != 0 { + l.Info().Str("Node name", nodeName).Str("Progress", fmt.Sprintf("%d/%d", i, evmLogCount)).Msg("Comparing single CL node's logs with EVM logs") + } + + if !logFound { + missingLogs = append(missingLogs, evmLog) + } + } + + if len(missingLogs) > 0 { + l.Warn().Int("Count", len(missingLogs)).Str("Node name", nodeName).Msg("Some EMV logs were missing from CL node") + } else { + l.Info().Str("Node name", nodeName).Msg("All EVM logs were found in CL node") + } + + result <- missingLogResult{ + nodeName: nodeName, + logs: missingLogs, + } + }(i, missingCh) + } + + wg.Wait() + close(missingCh) + + for v := range missingCh { + if len(v.logs) > 0 { + missingLogs[v.nodeName] = v.logs + } + } + + expectedTotalLogsEmitted := getExpectedLogCount(cfg) + if int64(len(allLogsInEVMNode)) != expectedTotalLogsEmitted { + l.Warn().Str("Actual/Expected", fmt.Sprintf("%d/%d", expectedTotalLogsEmitted, len(allLogsInEVMNode))).Msg("Some of the test logs were not found in EVM node. This is a bug in the test") + } + + return missingLogs, nil +} + +var printMissingLogsByType = func(missingLogs map[string][]geth_types.Log, l zerolog.Logger, cfg *Config) { + var findHumanName = func(topic common.Hash) string { + for _, event := range cfg.General.EventsToEmit { + if event.ID == topic { + return event.Name + } + } + + return "Unknown event" + } + + missingByType := make(map[string]int) + for _, logs := range missingLogs { + for _, v := range logs { + humanName := findHumanName(v.Topics[0]) + missingByType[humanName]++ + } + } + + for k, v := range missingByType { + l.Warn().Str("Event name", k).Int("Missing count", v).Msg("Missing logs by type") + } +} + +var getEVMLogs = func(startBlock, endBlock int64, logEmitters []*contracts.LogEmitter, evmClient blockchain.EVMClient, l zerolog.Logger, cfg *Config) ([]geth_types.Log, error) { + allLogsInEVMNode := make([]geth_types.Log, 0) + for j := 0; j < len(logEmitters); j++ { + address := (*logEmitters[j]).Address() + for _, event := range cfg.General.EventsToEmit { + l.Debug().Str("Event name", event.Name).Str("Emitter address", address.String()).Msg("Fetching logs from EVM node") + logsInEVMNode, err := evmClient.FilterLogs(context.Background(), geth.FilterQuery{ + Addresses: []common.Address{(address)}, + Topics: [][]common.Hash{{event.ID}}, + FromBlock: big.NewInt(startBlock), + ToBlock: big.NewInt(endBlock), + }) + if err != nil { + return nil, err + } + + sort.Slice(logsInEVMNode, func(i, j int) bool { + return logsInEVMNode[i].BlockNumber < logsInEVMNode[j].BlockNumber + }) + + allLogsInEVMNode = append(allLogsInEVMNode, logsInEVMNode...) + l.Debug().Str("Event name", event.Name).Str("Emitter address", address.String()).Int("Log count", len(logsInEVMNode)).Msg("Logs found in EVM node") + } + } + + l.Warn().Int("Count", len(allLogsInEVMNode)).Msg("Logs in EVM node") + + return allLogsInEVMNode, nil +} + +func executeGenerator(t *testing.T, cfg *Config, logEmitters []*contracts.LogEmitter) (int, error) { + if cfg.General.Generator == GeneratorType_WASP { + return runWaspGenerator(t, cfg, logEmitters) + } + + return runLoopedGenerator(t, cfg, logEmitters) +} + +func runWaspGenerator(t *testing.T, cfg *Config, logEmitters []*contracts.LogEmitter) (int, error) { + l := logging.GetTestLogger(t) + + var RPSprime int64 + + // if LPS is set, we need to calculate based on countract count and events per transaction + if cfg.Wasp.Load.LPS > 0 { + RPSprime = cfg.Wasp.Load.LPS / int64(cfg.General.Contracts) / int64(cfg.General.EventsPerTx) / int64(len(cfg.General.EventsToEmit)) + + if RPSprime < 1 { + return 0, fmt.Errorf("invalid load configuration, effective RPS would have been zero. Adjust LPS, contracts count, events per tx or events to emit") + } + } + + // if RPS is set simply split it between contracts + if cfg.Wasp.Load.RPS > 0 { + RPSprime = cfg.Wasp.Load.RPS / int64(cfg.General.Contracts) + } + + counter := &Counter{ + mu: &sync.Mutex{}, + value: 0, + } + + p := wasp.NewProfile() + + for _, logEmitter := range logEmitters { + g, err := wasp.NewGenerator(&wasp.Config{ + T: t, + LoadType: wasp.RPS, + GenName: fmt.Sprintf("log_poller_gen_%s", (*logEmitter).Address().String()), + RateLimitUnitDuration: cfg.Wasp.Load.RateLimitUnitDuration.Duration(), + CallTimeout: cfg.Wasp.Load.CallTimeout.Duration(), + Schedule: wasp.Plain( + RPSprime, + cfg.Wasp.Load.Duration.Duration(), + ), + Gun: NewLogEmitterGun( + logEmitter, + cfg.General.EventsToEmit, + cfg.General.EventsPerTx, + l, + ), + SharedData: counter, + }) + p.Add(g, err) + } + + _, err := p.Run(true) + + if err != nil { + return 0, err + } + + return counter.value, nil +} + +func runLoopedGenerator(t *testing.T, cfg *Config, logEmitters []*contracts.LogEmitter) (int, error) { + l := logging.GetTestLogger(t) + + // Start emitting events in parallel, each contract is emitting events in a separate goroutine + // We will stop as soon as we encounter an error + wg := &sync.WaitGroup{} + emitterCh := make(chan LogEmitterChannel, len(logEmitters)) + + ctx, cancelFn := context.WithCancel(context.Background()) + defer cancelFn() + + for i := 0; i < len(logEmitters); i++ { + wg.Add(1) + go emitEvents(ctx, l, logEmitters[i], cfg, wg, emitterCh) + } + + var emitErr error + total := 0 + + aggrChan := make(chan int, len(logEmitters)) + + go func() { + for emitter := range emitterCh { + if emitter.err != nil { + emitErr = emitter.err + cancelFn() + return + } + aggrChan <- emitter.logsEmitted + } + }() + + wg.Wait() + close(emitterCh) + + for i := 0; i < len(logEmitters); i++ { + total += <-aggrChan + } + + if emitErr != nil { + return 0, emitErr + } + + return int(total), nil +} + +func getExpectedLogCount(cfg *Config) int64 { + if cfg.General.Generator == GeneratorType_WASP { + if cfg.Wasp.Load.RPS != 0 { + return cfg.Wasp.Load.RPS * int64(cfg.Wasp.Load.Duration.Duration().Seconds()) * int64(cfg.General.EventsPerTx) + } + return cfg.Wasp.Load.LPS * int64(cfg.Wasp.Load.Duration.Duration().Seconds()) + } + + return int64(len(cfg.General.EventsToEmit) * cfg.LoopedConfig.ExecutionCount * cfg.General.Contracts * cfg.General.EventsPerTx) +} + +var chaosPauseSyncFn = func(l zerolog.Logger, testEnv *test_env.CLClusterTestEnv) error { + rand.New(rand.NewSource(time.Now().UnixNano())) + randomBool := rand.Intn(2) == 0 + + randomNode := testEnv.ClCluster.Nodes[rand.Intn(len(testEnv.ClCluster.Nodes)-1)+1] + var component ctf_test_env.EnvComponent + + if randomBool { + component = randomNode.EnvComponent + } else { + component = randomNode.PostgresDb.EnvComponent + } + + pauseTimeSec := rand.Intn(20-5) + 5 + l.Info().Str("Container", component.ContainerName).Int("Pause time", pauseTimeSec).Msg("Pausing component") + pauseTimeDur := time.Duration(pauseTimeSec) * time.Second + err := component.ChaosPause(l, pauseTimeDur) + l.Info().Str("Container", component.ContainerName).Msg("Component unpaused") + + if err != nil { + return err + } + + return nil +} + +var executeChaosExperiment = func(l zerolog.Logger, testEnv *test_env.CLClusterTestEnv, cfg *Config, errorCh chan error) { + if cfg.ChaosConfig == nil || cfg.ChaosConfig.ExperimentCount == 0 { + errorCh <- nil + return + } + + chaosChan := make(chan error, cfg.ChaosConfig.ExperimentCount) + + wg := &sync.WaitGroup{} + + go func() { + // if we wanted to have more than 1 container paused, we'd need to make sure we aren't trying to pause an already paused one + guardChan := make(chan struct{}, 1) + + for i := 0; i < cfg.ChaosConfig.ExperimentCount; i++ { + i := i + wg.Add(1) + guardChan <- struct{}{} + go func() { + defer func() { + <-guardChan + wg.Done() + l.Info().Str("Current/Total", fmt.Sprintf("%d/%d", i, cfg.ChaosConfig.ExperimentCount)).Msg("Done with experiment") + }() + chaosChan <- chaosPauseSyncFn(l, testEnv) + }() + } + + wg.Wait() + + close(chaosChan) + }() + + go func() { + for err := range chaosChan { + // This will receive errors until chaosChan is closed + if err != nil { + // If an error is encountered, log it, send it to the error channel, and return from the function + l.Err(err).Msg("Error encountered during chaos experiment") + errorCh <- err + return // Return on actual error + } + // No need for an else block here, because if err is nil (which happens when the channel is closed), + // the loop will exit and the following log and nil send will execute. + } + + // After the loop exits, which it will do when chaosChan is closed, log that all experiments are finished. + l.Info().Msg("All chaos experiments finished") + errorCh <- nil // Only send nil once, after all errors have been handled and the channel is closed + }() +} + +var GetFinalityDepth = func(chainId int64) (int64, error) { + var finalityDepth int64 + switch chainId { + // Ethereum Sepolia + case 11155111: + finalityDepth = 50 + // Polygon Mumbai + case 80001: + finalityDepth = 500 + // Simulated network + case 1337: + finalityDepth = 10 + default: + return 0, fmt.Errorf("no known finality depth for chain %d", chainId) + } + + return finalityDepth, nil +} + +var GetEndBlockToWaitFor = func(endBlock, chainId int64, cfg *Config) (int64, error) { + if cfg.General.UseFinalityTag { + return endBlock + 1, nil + } + + finalityDepth, err := GetFinalityDepth(chainId) + if err != nil { + return 0, err + } + + return endBlock + finalityDepth, nil +} + +const ( + automationDefaultUpkeepGasLimit = uint32(2500000) + automationDefaultLinkFunds = int64(9e18) + automationDefaultUpkeepsToDeploy = 10 + automationExpectedData = "abcdef" + defaultAmountOfUpkeeps = 2 +) + +var ( + defaultOCRRegistryConfig = contracts.KeeperRegistrySettings{ + PaymentPremiumPPB: uint32(200000000), + FlatFeeMicroLINK: uint32(0), + BlockCountPerTurn: big.NewInt(10), + CheckGasLimit: uint32(2500000), + StalenessSeconds: big.NewInt(90000), + GasCeilingMultiplier: uint16(1), + MinUpkeepSpend: big.NewInt(0), + MaxPerformGas: uint32(5000000), + FallbackGasPrice: big.NewInt(2e11), + FallbackLinkPrice: big.NewInt(2e18), + MaxCheckDataSize: uint32(5000), + MaxPerformDataSize: uint32(5000), + } + + automationDefaultRegistryConfig = contracts.KeeperRegistrySettings{ + PaymentPremiumPPB: uint32(200000000), + FlatFeeMicroLINK: uint32(0), + BlockCountPerTurn: big.NewInt(10), + CheckGasLimit: uint32(2500000), + StalenessSeconds: big.NewInt(90000), + GasCeilingMultiplier: uint16(1), + MinUpkeepSpend: big.NewInt(0), + MaxPerformGas: uint32(5000000), + FallbackGasPrice: big.NewInt(2e11), + FallbackLinkPrice: big.NewInt(2e18), + MaxCheckDataSize: uint32(5000), + MaxPerformDataSize: uint32(5000), + } +) + +func setupLogPollerTestDocker( + t *testing.T, + registryVersion ethereum.KeeperRegistryVersion, + registryConfig contracts.KeeperRegistrySettings, + upkeepsNeeded int, + lpPollingInterval time.Duration, + finalityTagEnabled bool, +) ( + blockchain.EVMClient, + []*client.ChainlinkClient, + contracts.ContractDeployer, + contracts.LinkToken, + contracts.KeeperRegistry, + contracts.KeeperRegistrar, + *test_env.CLClusterTestEnv, +) { + l := logging.GetTestLogger(t) + // Add registry version to config + registryConfig.RegistryVersion = registryVersion + network := networks.MustGetSelectedNetworksFromEnv()[0] + + finalityDepth, err := GetFinalityDepth(network.ChainID) + require.NoError(t, err, "Error getting finality depth") + + // build the node config + clNodeConfig := node.NewConfig(node.NewBaseConfig()) + syncInterval := models.MustMakeDuration(5 * time.Minute) + clNodeConfig.Feature.LogPoller = ptr.Ptr[bool](true) + clNodeConfig.OCR2.Enabled = ptr.Ptr[bool](true) + clNodeConfig.Keeper.TurnLookBack = ptr.Ptr[int64](int64(0)) + clNodeConfig.Keeper.Registry.SyncInterval = &syncInterval + clNodeConfig.Keeper.Registry.PerformGasOverhead = ptr.Ptr[uint32](uint32(150000)) + clNodeConfig.P2P.V2.Enabled = ptr.Ptr[bool](true) + clNodeConfig.P2P.V2.AnnounceAddresses = &[]string{"0.0.0.0:6690"} + clNodeConfig.P2P.V2.ListenAddresses = &[]string{"0.0.0.0:6690"} + + //launch the environment + var env *test_env.CLClusterTestEnv + chainlinkNodeFunding := 0.5 + l.Debug().Msgf("Funding amount: %f", chainlinkNodeFunding) + clNodesCount := 5 + + var logPolllerSettingsFn = func(chain *evmcfg.Chain) *evmcfg.Chain { + chain.LogPollInterval = models.MustNewDuration(lpPollingInterval) + chain.FinalityDepth = ptr.Ptr[uint32](uint32(finalityDepth)) + chain.FinalityTagEnabled = ptr.Ptr[bool](finalityTagEnabled) + return chain + } + + var evmClientSettingsFn = func(network *blockchain.EVMNetwork) *blockchain.EVMNetwork { + network.FinalityDepth = uint64(finalityDepth) + network.FinalityTag = finalityTagEnabled + return network + } + + ethBuilder := ctf_test_env.NewEthereumNetworkBuilder() + cfg, err := ethBuilder. + WithConsensusType(ctf_test_env.ConsensusType_PoS). + WithConsensusLayer(ctf_test_env.ConsensusLayer_Prysm). + WithExecutionLayer(ctf_test_env.ExecutionLayer_Geth). + WithBeaconChainConfig(ctf_test_env.BeaconChainConfig{ + SecondsPerSlot: 8, + SlotsPerEpoch: 2, + }). + Build() + require.NoError(t, err, "Error building ethereum network config") + + env, err = test_env.NewCLTestEnvBuilder(). + WithTestLogger(t). + WithPrivateEthereumNetwork(cfg). + WithCLNodes(clNodesCount). + WithCLNodeConfig(clNodeConfig). + WithFunding(big.NewFloat(chainlinkNodeFunding)). + WithChainOptions(logPolllerSettingsFn). + EVMClientNetworkOptions(evmClientSettingsFn). + WithStandardCleanup(). + Build() + require.NoError(t, err, "Error deploying test environment") + + env.ParallelTransactions(true) + nodeClients := env.ClCluster.NodeAPIs() + workerNodes := nodeClients[1:] + + var linkToken contracts.LinkToken + + switch network.ChainID { + // Simulated + case 1337: + linkToken, err = env.ContractDeployer.DeployLinkTokenContract() + // Ethereum Sepolia + case 11155111: + linkToken, err = env.ContractLoader.LoadLINKToken("0x779877A7B0D9E8603169DdbD7836e478b4624789") + // Polygon Mumbai + case 80001: + linkToken, err = env.ContractLoader.LoadLINKToken("0x326C977E6efc84E512bB9C30f76E30c160eD06FB") + default: + panic("Not implemented") + } + require.NoError(t, err, "Error loading/deploying LINK token") + + linkBalance, err := env.EVMClient.BalanceAt(context.Background(), common.HexToAddress(linkToken.Address())) + require.NoError(t, err, "Error getting LINK balance") + + l.Info().Str("Balance", big.NewInt(0).Div(linkBalance, big.NewInt(1e18)).String()).Msg("LINK balance") + minLinkBalanceSingleNode := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(9)) + minLinkBalance := big.NewInt(0).Mul(minLinkBalanceSingleNode, big.NewInt(int64(upkeepsNeeded))) + if minLinkBalance.Cmp(linkBalance) < 0 { + require.FailNowf(t, "Not enough LINK", "Not enough LINK to run the test. Need at least %s", big.NewInt(0).Div(minLinkBalance, big.NewInt(1e18)).String()) + } + + registry, registrar := actions.DeployAutoOCRRegistryAndRegistrar( + t, + registryVersion, + registryConfig, + linkToken, + env.ContractDeployer, + env.EVMClient, + ) + + // Fund the registry with LINK + err = linkToken.Transfer(registry.Address(), big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(int64(defaultAmountOfUpkeeps)))) + require.NoError(t, err, "Funding keeper registry contract shouldn't fail") + + err = actions.CreateOCRKeeperJobsLocal(l, nodeClients, registry.Address(), network.ChainID, 0, registryVersion) + require.NoError(t, err, "Error creating OCR Keeper Jobs") + ocrConfig, err := actions.BuildAutoOCR2ConfigVarsLocal(l, workerNodes, registryConfig, registrar.Address(), 30*time.Second, registry.RegistryOwnerAddress()) + require.NoError(t, err, "Error building OCR config vars") + err = registry.SetConfig(automationDefaultRegistryConfig, ocrConfig) + require.NoError(t, err, "Registry config should be set successfully") + require.NoError(t, env.EVMClient.WaitForEvents(), "Waiting for config to be set") + + return env.EVMClient, nodeClients, env.ContractDeployer, linkToken, registry, registrar, env +} diff --git a/integration-tests/universal/log_poller/scenarios.go b/integration-tests/universal/log_poller/scenarios.go new file mode 100644 index 00000000000..5331c63f752 --- /dev/null +++ b/integration-tests/universal/log_poller/scenarios.go @@ -0,0 +1,496 @@ +package logpoller + +import ( + "fmt" + "math/big" + "testing" + "time" + + "github.com/onsi/gomega" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" + "github.com/smartcontractkit/chainlink/integration-tests/actions" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" + "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" + core_logger "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func ExecuteBasicLogPollerTest(t *testing.T, cfg *Config) { + l := logging.GetTestLogger(t) + coreLogger := core_logger.TestLogger(t) //needed by ORM ¯\_(ツ)_/¯ + + if cfg.General.EventsToEmit == nil || len(cfg.General.EventsToEmit) == 0 { + l.Warn().Msg("No events to emit specified, using all events from log emitter contract") + for _, event := range EmitterABI.Events { + cfg.General.EventsToEmit = append(cfg.General.EventsToEmit, event) + } + } + + l.Info().Msg("Starting basic log poller test") + + var ( + err error + upKeepsNeeded = cfg.General.Contracts * len(cfg.General.EventsToEmit) + ) + + chainClient, _, contractDeployer, linkToken, registry, registrar, testEnv := setupLogPollerTestDocker( + t, ethereum.RegistryVersion_2_1, defaultOCRRegistryConfig, upKeepsNeeded, time.Duration(500*time.Millisecond), cfg.General.UseFinalityTag, + ) + + _, upkeepIDs := actions.DeployConsumers( + t, + registry, + registrar, + linkToken, + contractDeployer, + chainClient, + upKeepsNeeded, + big.NewInt(automationDefaultLinkFunds), + automationDefaultUpkeepGasLimit, + true, + false, + ) + + // Deploy Log Emitter contracts + logEmitters := make([]*contracts.LogEmitter, 0) + for i := 0; i < cfg.General.Contracts; i++ { + logEmitter, err := testEnv.ContractDeployer.DeployLogEmitterContract() + logEmitters = append(logEmitters, &logEmitter) + require.NoError(t, err, "Error deploying log emitter contract") + l.Info().Str("Contract address", logEmitter.Address().Hex()).Msg("Log emitter contract deployed") + time.Sleep(200 * time.Millisecond) + } + + // Register log triggered upkeep for each combination of log emitter contract and event signature (topic) + // We need to register a separate upkeep for each event signature, because log trigger doesn't support multiple topics (even if log poller does) + for i := 0; i < len(upkeepIDs); i++ { + emitterAddress := (*logEmitters[i%cfg.General.Contracts]).Address() + upkeepID := upkeepIDs[i] + topicId := cfg.General.EventsToEmit[i%len(cfg.General.EventsToEmit)].ID + + l.Info().Int("Upkeep id", int(upkeepID.Int64())).Str("Emitter address", emitterAddress.String()).Str("Topic", topicId.Hex()).Msg("Registering log trigger for log emitter") + err = registerSingleTopicFilter(registry, upkeepID, emitterAddress, topicId) + randomWait(50, 200) + require.NoError(t, err, "Error registering log trigger for log emitter") + } + + err = chainClient.WaitForEvents() + require.NoError(t, err, "Error encountered when waiting for setting trigger config for upkeeps") + + // Make sure that all nodes have expected filters registered before starting to emit events + expectedFilters := getExpectedFilters(logEmitters, cfg) + gom := gomega.NewGomegaWithT(t) + gom.Eventually(func(g gomega.Gomega) { + for i := 1; i < len(testEnv.ClCluster.Nodes); i++ { + nodeName := testEnv.ClCluster.Nodes[i].ContainerName + l.Info().Str("Node name", nodeName).Msg("Fetching filters from log poller's DB") + + hasFilters, err := nodeHasExpectedFilters(expectedFilters, coreLogger, testEnv.EVMClient.GetChainID(), testEnv.ClCluster.Nodes[i].PostgresDb) + if err != nil { + l.Warn().Err(err).Msg("Error checking if node has expected filters. Retrying...") + return + } + + g.Expect(hasFilters).To(gomega.BeTrue(), "Not all expected filters were found in the DB") + } + }, "30s", "1s").Should(gomega.Succeed()) + l.Info().Msg("All nodes have expected filters registered") + l.Info().Int("Count", len(expectedFilters)).Msg("Expected filters count") + + // Save block number before starting to emit events, so that we can later use it when querying logs + sb, err := testEnv.EVMClient.LatestBlockNumber(testcontext.Get(t)) + require.NoError(t, err, "Error getting latest block number") + startBlock := int64(sb) + + l.Info().Msg("STARTING EVENT EMISSION") + startTime := time.Now() + + // Start chaos experimnents by randomly pausing random containers (Chainlink nodes or their DBs) + chaosDoneCh := make(chan error, 1) + go func() { + executeChaosExperiment(l, testEnv, cfg, chaosDoneCh) + }() + + totalLogsEmitted, err := executeGenerator(t, cfg, logEmitters) + endTime := time.Now() + require.NoError(t, err, "Error executing event generator") + + expectedLogsEmitted := getExpectedLogCount(cfg) + duration := int(endTime.Sub(startTime).Seconds()) + l.Info().Int("Total logs emitted", totalLogsEmitted).Int64("Expected total logs emitted", expectedLogsEmitted).Str("Duration", fmt.Sprintf("%d sec", duration)).Str("LPS", fmt.Sprintf("%d/sec", totalLogsEmitted/duration)).Msg("FINISHED EVENT EMISSION") + + // Save block number after finishing to emit events, so that we can later use it when querying logs + eb, err := testEnv.EVMClient.LatestBlockNumber(testcontext.Get(t)) + require.NoError(t, err, "Error getting latest block number") + + endBlock, err := GetEndBlockToWaitFor(int64(eb), testEnv.EVMClient.GetChainID().Int64(), cfg) + require.NoError(t, err, "Error getting end block to wait for") + + l.Info().Msg("Waiting before proceeding with test until all chaos experiments finish") + chaosError := <-chaosDoneCh + require.NoError(t, chaosError, "Error encountered during chaos experiment") + + // Wait until last block in which events were emitted has been finalised + // how long should we wait here until all logs are processed? wait for block X to be processed by all nodes? + waitDuration := "15m" + l.Warn().Str("Duration", waitDuration).Msg("Waiting for logs to be processed by all nodes and for chain to advance beyond finality") + + gom.Eventually(func(g gomega.Gomega) { + hasAdvanced, err := chainHasFinalisedEndBlock(l, testEnv.EVMClient, endBlock) + if err != nil { + l.Warn().Err(err).Msg("Error checking if chain has advanced beyond finality. Retrying...") + } + g.Expect(hasAdvanced).To(gomega.BeTrue(), "Chain has not advanced beyond finality") + }, waitDuration, "30s").Should(gomega.Succeed()) + + l.Warn().Str("Duration", "1m").Msg("Waiting for all CL nodes to have end block finalised") + gom.Eventually(func(g gomega.Gomega) { + hasFinalised, err := logPollerHasFinalisedEndBlock(endBlock, testEnv.EVMClient.GetChainID(), l, coreLogger, testEnv.ClCluster) + if err != nil { + l.Warn().Err(err).Msg("Error checking if nodes have finalised end block. Retrying...") + } + g.Expect(hasFinalised).To(gomega.BeTrue(), "Some nodes have not finalised end block") + }, "1m", "30s").Should(gomega.Succeed()) + + gom.Eventually(func(g gomega.Gomega) { + logCountMatches, err := clNodesHaveExpectedLogCount(startBlock, endBlock, testEnv.EVMClient.GetChainID(), totalLogsEmitted, expectedFilters, l, coreLogger, testEnv.ClCluster) + if err != nil { + l.Warn().Err(err).Msg("Error checking if CL nodes have expected log count. Retrying...") + } + g.Expect(logCountMatches).To(gomega.BeTrue(), "Not all CL nodes have expected log count") + }, waitDuration, "5s").Should(gomega.Succeed()) + + // Wait until all CL nodes have exactly the same logs emitted by test contracts as the EVM node has + logConsistencyWaitDuration := "1m" + l.Warn().Str("Duration", logConsistencyWaitDuration).Msg("Waiting for CL nodes to have all the logs that EVM node has") + + gom.Eventually(func(g gomega.Gomega) { + missingLogs, err := getMissingLogs(startBlock, endBlock, logEmitters, testEnv.EVMClient, testEnv.ClCluster, l, coreLogger, cfg) + if err != nil { + l.Warn().Err(err).Msg("Error getting missing logs. Retrying...") + } + + if !missingLogs.IsEmpty() { + printMissingLogsByType(missingLogs, l, cfg) + } + g.Expect(missingLogs.IsEmpty()).To(gomega.BeTrue(), "Some CL nodes were missing logs") + }, logConsistencyWaitDuration, "5s").Should(gomega.Succeed()) +} + +func ExecuteLogPollerReplay(t *testing.T, cfg *Config, consistencyTimeout string) { + l := logging.GetTestLogger(t) + coreLogger := core_logger.TestLogger(t) //needed by ORM ¯\_(ツ)_/¯ + + if cfg.General.EventsToEmit == nil || len(cfg.General.EventsToEmit) == 0 { + l.Warn().Msg("No events to emit specified, using all events from log emitter contract") + for _, event := range EmitterABI.Events { + cfg.General.EventsToEmit = append(cfg.General.EventsToEmit, event) + } + } + + l.Info().Msg("Starting replay log poller test") + + var ( + err error + upKeepsNeeded = cfg.General.Contracts * len(cfg.General.EventsToEmit) + ) + + // we set blockBackfillDepth to 0, to make sure nothing will be backfilled and won't interfere with our test + chainClient, _, contractDeployer, linkToken, registry, registrar, testEnv := setupLogPollerTestDocker( + t, ethereum.RegistryVersion_2_1, defaultOCRRegistryConfig, upKeepsNeeded, time.Duration(1000*time.Millisecond), cfg.General.UseFinalityTag) + + _, upkeepIDs := actions.DeployConsumers( + t, + registry, + registrar, + linkToken, + contractDeployer, + chainClient, + upKeepsNeeded, + big.NewInt(automationDefaultLinkFunds), + automationDefaultUpkeepGasLimit, + true, + false, + ) + + // Deploy Log Emitter contracts + logEmitters := make([]*contracts.LogEmitter, 0) + for i := 0; i < cfg.General.Contracts; i++ { + logEmitter, err := testEnv.ContractDeployer.DeployLogEmitterContract() + logEmitters = append(logEmitters, &logEmitter) + require.NoError(t, err, "Error deploying log emitter contract") + l.Info().Str("Contract address", logEmitter.Address().Hex()).Msg("Log emitter contract deployed") + time.Sleep(200 * time.Millisecond) + } + + //wait for contracts to be uploaded to chain, TODO: could make this wait fluent + time.Sleep(5 * time.Second) + + // Save block number before starting to emit events, so that we can later use it when querying logs + sb, err := testEnv.EVMClient.LatestBlockNumber(testcontext.Get(t)) + require.NoError(t, err, "Error getting latest block number") + startBlock := int64(sb) + + l.Info().Msg("STARTING EVENT EMISSION") + startTime := time.Now() + totalLogsEmitted, err := executeGenerator(t, cfg, logEmitters) + endTime := time.Now() + require.NoError(t, err, "Error executing event generator") + expectedLogsEmitted := getExpectedLogCount(cfg) + duration := int(endTime.Sub(startTime).Seconds()) + l.Info().Int("Total logs emitted", totalLogsEmitted).Int64("Expected total logs emitted", expectedLogsEmitted).Str("Duration", fmt.Sprintf("%d sec", duration)).Str("LPS", fmt.Sprintf("%d/sec", totalLogsEmitted/duration)).Msg("FINISHED EVENT EMISSION") + + // Save block number after finishing to emit events, so that we can later use it when querying logs + eb, err := testEnv.EVMClient.LatestBlockNumber(testcontext.Get(t)) + require.NoError(t, err, "Error getting latest block number") + + endBlock, err := GetEndBlockToWaitFor(int64(eb), testEnv.EVMClient.GetChainID().Int64(), cfg) + require.NoError(t, err, "Error getting end block to wait for") + + // Lets make sure no logs are in DB yet + expectedFilters := getExpectedFilters(logEmitters, cfg) + logCountMatches, err := clNodesHaveExpectedLogCount(startBlock, endBlock, testEnv.EVMClient.GetChainID(), 0, expectedFilters, l, coreLogger, testEnv.ClCluster) + require.NoError(t, err, "Error checking if CL nodes have expected log count") + require.True(t, logCountMatches, "Some CL nodes already had logs in DB") + l.Info().Msg("No logs were saved by CL nodes yet, as expected. Proceeding.") + + // Register log triggered upkeep for each combination of log emitter contract and event signature (topic) + // We need to register a separate upkeep for each event signature, because log trigger doesn't support multiple topics (even if log poller does) + for i := 0; i < len(upkeepIDs); i++ { + emitterAddress := (*logEmitters[i%cfg.General.Contracts]).Address() + upkeepID := upkeepIDs[i] + topicId := cfg.General.EventsToEmit[i%len(cfg.General.EventsToEmit)].ID + + l.Info().Int("Upkeep id", int(upkeepID.Int64())).Str("Emitter address", emitterAddress.String()).Str("Topic", topicId.Hex()).Msg("Registering log trigger for log emitter") + err = registerSingleTopicFilter(registry, upkeepID, emitterAddress, topicId) + require.NoError(t, err, "Error registering log trigger for log emitter") + } + + err = chainClient.WaitForEvents() + require.NoError(t, err, "Error encountered when waiting for setting trigger config for upkeeps") + + // Make sure that all nodes have expected filters registered before starting to emit events + gom := gomega.NewGomegaWithT(t) + gom.Eventually(func(g gomega.Gomega) { + for i := 1; i < len(testEnv.ClCluster.Nodes); i++ { + nodeName := testEnv.ClCluster.Nodes[i].ContainerName + l.Info().Str("Node name", nodeName).Msg("Fetching filters from log poller's DB") + + hasFilters, err := nodeHasExpectedFilters(expectedFilters, coreLogger, testEnv.EVMClient.GetChainID(), testEnv.ClCluster.Nodes[i].PostgresDb) + if err != nil { + l.Warn().Err(err).Msg("Error checking if node has expected filters. Retrying...") + return + } + + g.Expect(hasFilters).To(gomega.BeTrue(), "Not all expected filters were found in the DB") + } + }, "30s", "1s").Should(gomega.Succeed()) + l.Info().Msg("All nodes have expected filters registered") + l.Info().Int("Count", len(expectedFilters)).Msg("Expected filters count") + + l.Warn().Str("Duration", "1m").Msg("Waiting for all CL nodes to have end block finalised") + gom.Eventually(func(g gomega.Gomega) { + hasFinalised, err := logPollerHasFinalisedEndBlock(endBlock, testEnv.EVMClient.GetChainID(), l, coreLogger, testEnv.ClCluster) + if err != nil { + l.Warn().Err(err).Msg("Error checking if nodes have finalised end block. Retrying...") + } + g.Expect(hasFinalised).To(gomega.BeTrue(), "Some nodes have not finalised end block") + }, "1m", "30s").Should(gomega.Succeed()) + + // Trigger replay + l.Info().Msg("Triggering log poller's replay") + for i := 1; i < len(testEnv.ClCluster.Nodes); i++ { + nodeName := testEnv.ClCluster.Nodes[i].ContainerName + response, _, err := testEnv.ClCluster.Nodes[i].API.ReplayLogPollerFromBlock(startBlock, testEnv.EVMClient.GetChainID().Int64()) + require.NoError(t, err, "Error triggering log poller's replay on node %s", nodeName) + require.Equal(t, "Replay started", response.Data.Attributes.Message, "Unexpected response message from log poller's replay") + } + + l.Warn().Str("Duration", consistencyTimeout).Msg("Waiting for replay logs to be processed by all nodes") + + gom.Eventually(func(g gomega.Gomega) { + logCountMatches, err := clNodesHaveExpectedLogCount(startBlock, endBlock, testEnv.EVMClient.GetChainID(), totalLogsEmitted, expectedFilters, l, coreLogger, testEnv.ClCluster) + if err != nil { + l.Warn().Err(err).Msg("Error checking if CL nodes have expected log count. Retrying...") + } + g.Expect(logCountMatches).To(gomega.BeTrue(), "Not all CL nodes have expected log count") + }, consistencyTimeout, "30s").Should(gomega.Succeed()) + + // Wait until all CL nodes have exactly the same logs emitted by test contracts as the EVM node has + l.Warn().Str("Duration", consistencyTimeout).Msg("Waiting for CL nodes to have all the logs that EVM node has") + + gom.Eventually(func(g gomega.Gomega) { + missingLogs, err := getMissingLogs(startBlock, endBlock, logEmitters, testEnv.EVMClient, testEnv.ClCluster, l, coreLogger, cfg) + if err != nil { + l.Warn().Err(err).Msg("Error getting missing logs. Retrying...") + } + + if !missingLogs.IsEmpty() { + printMissingLogsByType(missingLogs, l, cfg) + } + g.Expect(missingLogs.IsEmpty()).To(gomega.BeTrue(), "Some CL nodes were missing logs") + }, consistencyTimeout, "10s").Should(gomega.Succeed()) +} + +type FinalityBlockFn = func(chainId int64, endBlock int64) (int64, error) + +func ExecuteCILogPollerTest(t *testing.T, cfg *Config) { + l := logging.GetTestLogger(t) + coreLogger := core_logger.TestLogger(t) //needed by ORM ¯\_(ツ)_/¯ + + if cfg.General.EventsToEmit == nil || len(cfg.General.EventsToEmit) == 0 { + l.Warn().Msg("No events to emit specified, using all events from log emitter contract") + for _, event := range EmitterABI.Events { + cfg.General.EventsToEmit = append(cfg.General.EventsToEmit, event) + } + } + + l.Info().Msg("Starting CI log poller test") + + var ( + err error + upKeepsNeeded = cfg.General.Contracts * len(cfg.General.EventsToEmit) + ) + + chainClient, _, contractDeployer, linkToken, registry, registrar, testEnv := setupLogPollerTestDocker( + t, ethereum.RegistryVersion_2_1, defaultOCRRegistryConfig, upKeepsNeeded, time.Duration(1000*time.Millisecond), cfg.General.UseFinalityTag, + ) + + _, upkeepIDs := actions.DeployConsumers( + t, + registry, + registrar, + linkToken, + contractDeployer, + chainClient, + upKeepsNeeded, + big.NewInt(automationDefaultLinkFunds), + automationDefaultUpkeepGasLimit, + true, + false, + ) + + // Deploy Log Emitter contracts + logEmitters := make([]*contracts.LogEmitter, 0) + for i := 0; i < cfg.General.Contracts; i++ { + logEmitter, err := testEnv.ContractDeployer.DeployLogEmitterContract() + logEmitters = append(logEmitters, &logEmitter) + require.NoError(t, err, "Error deploying log emitter contract") + l.Info().Str("Contract address", logEmitter.Address().Hex()).Msg("Log emitter contract deployed") + time.Sleep(200 * time.Millisecond) + } + + // Register log triggered upkeep for each combination of log emitter contract and event signature (topic) + // We need to register a separate upkeep for each event signature, because log trigger doesn't support multiple topics (even if log poller does) + for i := 0; i < len(upkeepIDs); i++ { + emitterAddress := (*logEmitters[i%cfg.General.Contracts]).Address() + upkeepID := upkeepIDs[i] + topicId := cfg.General.EventsToEmit[i%len(cfg.General.EventsToEmit)].ID + + l.Info().Int("Upkeep id", int(upkeepID.Int64())).Str("Emitter address", emitterAddress.String()).Str("Topic", topicId.Hex()).Msg("Registering log trigger for log emitter") + err = registerSingleTopicFilter(registry, upkeepID, emitterAddress, topicId) + randomWait(50, 200) + require.NoError(t, err, "Error registering log trigger for log emitter") + } + + err = chainClient.WaitForEvents() + require.NoError(t, err, "Error encountered when waiting for setting trigger config for upkeeps") + + // Make sure that all nodes have expected filters registered before starting to emit events + expectedFilters := getExpectedFilters(logEmitters, cfg) + gom := gomega.NewGomegaWithT(t) + gom.Eventually(func(g gomega.Gomega) { + for i := 1; i < len(testEnv.ClCluster.Nodes); i++ { + nodeName := testEnv.ClCluster.Nodes[i].ContainerName + l.Info().Str("Node name", nodeName).Msg("Fetching filters from log poller's DB") + + hasFilters, err := nodeHasExpectedFilters(expectedFilters, coreLogger, testEnv.EVMClient.GetChainID(), testEnv.ClCluster.Nodes[i].PostgresDb) + if err != nil { + l.Warn().Err(err).Msg("Error checking if node has expected filters. Retrying...") + return + } + + g.Expect(hasFilters).To(gomega.BeTrue(), "Not all expected filters were found in the DB") + } + }, "1m", "1s").Should(gomega.Succeed()) + l.Info().Msg("All nodes have expected filters registered") + l.Info().Int("Count", len(expectedFilters)).Msg("Expected filters count") + + // Save block number before starting to emit events, so that we can later use it when querying logs + sb, err := testEnv.EVMClient.LatestBlockNumber(testcontext.Get(t)) + require.NoError(t, err, "Error getting latest block number") + startBlock := int64(sb) + + l.Info().Msg("STARTING EVENT EMISSION") + startTime := time.Now() + + // Start chaos experimnents by randomly pausing random containers (Chainlink nodes or their DBs) + chaosDoneCh := make(chan error, 1) + go func() { + executeChaosExperiment(l, testEnv, cfg, chaosDoneCh) + }() + + totalLogsEmitted, err := executeGenerator(t, cfg, logEmitters) + endTime := time.Now() + require.NoError(t, err, "Error executing event generator") + + expectedLogsEmitted := getExpectedLogCount(cfg) + duration := int(endTime.Sub(startTime).Seconds()) + l.Info().Int("Total logs emitted", totalLogsEmitted).Int64("Expected total logs emitted", expectedLogsEmitted).Str("Duration", fmt.Sprintf("%d sec", duration)).Str("LPS", fmt.Sprintf("%d/sec", totalLogsEmitted/duration)).Msg("FINISHED EVENT EMISSION") + + // Save block number after finishing to emit events, so that we can later use it when querying logs + eb, err := testEnv.EVMClient.LatestBlockNumber(testcontext.Get(t)) + require.NoError(t, err, "Error getting latest block number") + + endBlock, err := GetEndBlockToWaitFor(int64(eb), testEnv.EVMClient.GetChainID().Int64(), cfg) + require.NoError(t, err, "Error getting end block to wait for") + + l.Info().Msg("Waiting before proceeding with test until all chaos experiments finish") + chaosError := <-chaosDoneCh + require.NoError(t, chaosError, "Error encountered during chaos experiment") + + // Wait until last block in which events were emitted has been finalised (with buffer) + waitDuration := "45m" + l.Warn().Str("Duration", waitDuration).Msg("Waiting for chain to advance beyond finality") + + gom.Eventually(func(g gomega.Gomega) { + hasAdvanced, err := chainHasFinalisedEndBlock(l, testEnv.EVMClient, endBlock) + if err != nil { + l.Warn().Err(err).Msg("Error checking if chain has advanced beyond finality. Retrying...") + } + g.Expect(hasAdvanced).To(gomega.BeTrue(), "Chain has not advanced beyond finality") + }, waitDuration, "30s").Should(gomega.Succeed()) + + l.Warn().Str("Duration", waitDuration).Msg("Waiting for all CL nodes to have end block finalised") + gom.Eventually(func(g gomega.Gomega) { + hasFinalised, err := logPollerHasFinalisedEndBlock(endBlock, testEnv.EVMClient.GetChainID(), l, coreLogger, testEnv.ClCluster) + if err != nil { + l.Warn().Err(err).Msg("Error checking if nodes have finalised end block. Retrying...") + } + g.Expect(hasFinalised).To(gomega.BeTrue(), "Some nodes have not finalised end block") + }, waitDuration, "30s").Should(gomega.Succeed()) + + // Wait until all CL nodes have exactly the same logs emitted by test contracts as the EVM node has + logConsistencyWaitDuration := "10m" + l.Warn().Str("Duration", logConsistencyWaitDuration).Msg("Waiting for CL nodes to have all the logs that EVM node has") + + gom.Eventually(func(g gomega.Gomega) { + missingLogs, err := getMissingLogs(startBlock, endBlock, logEmitters, testEnv.EVMClient, testEnv.ClCluster, l, coreLogger, cfg) + if err != nil { + l.Warn().Err(err).Msg("Error getting missing logs. Retrying...") + } + + if !missingLogs.IsEmpty() { + printMissingLogsByType(missingLogs, l, cfg) + } + g.Expect(missingLogs.IsEmpty()).To(gomega.BeTrue(), "Some CL nodes were missing logs") + }, logConsistencyWaitDuration, "20s").Should(gomega.Succeed()) + + evmLogs, _ := getEVMLogs(startBlock, endBlock, logEmitters, testEnv.EVMClient, l, cfg) + + if totalLogsEmitted != len(evmLogs) { + l.Warn().Int("Total logs emitted", totalLogsEmitted).Int("Total logs in EVM", len(evmLogs)).Msg("Test passed, but total logs emitted does not match total logs in EVM") + } +} diff --git a/integration-tests/utils/cl_node_jobs.go b/integration-tests/utils/cl_node_jobs.go index 16b0c167cfe..65dc6e4e392 100644 --- a/integration-tests/utils/cl_node_jobs.go +++ b/integration-tests/utils/cl_node_jobs.go @@ -10,13 +10,14 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" "github.com/lib/pq" + "gopkg.in/guregu/null.v4" + coreClient "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/store/models" - "gopkg.in/guregu/null.v4" ) -func BuildBootstrapSpec(verifierAddr common.Address, chainID int64, fromBlock uint64, feedId [32]byte) *coreClient.OCR2TaskJobSpec { +func BuildBootstrapSpec(verifierAddr common.Address, chainID int64, feedId [32]byte) *coreClient.OCR2TaskJobSpec { hash := common.BytesToHash(feedId[:]) return &coreClient.OCR2TaskJobSpec{ Name: fmt.Sprintf("bootstrap-%s", uuid.NewString()), diff --git a/integration-tests/utils/common.go b/integration-tests/utils/common.go index c8243097a7d..34a40e396d2 100644 --- a/integration-tests/utils/common.go +++ b/integration-tests/utils/common.go @@ -1,13 +1,12 @@ package utils import ( + "math/big" "net" "github.com/smartcontractkit/chainlink/v2/core/store/models" ) -func Ptr[T any](t T) *T { return &t } - func MustURL(s string) *models.URL { var u models.URL if err := u.UnmarshalText([]byte(s)); err != nil { @@ -23,3 +22,12 @@ func MustIP(s string) *net.IP { } return &ip } + +func BigIntSliceContains(slice []*big.Int, b *big.Int) bool { + for _, a := range slice { + if b.Cmp(a) == 0 { + return true + } + } + return false +} diff --git a/integration-tests/utils/log.go b/integration-tests/utils/log.go deleted file mode 100644 index 499be8002d4..00000000000 --- a/integration-tests/utils/log.go +++ /dev/null @@ -1,19 +0,0 @@ -package utils - -import ( - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - "os" -) - -func SetupCoreDockerEnvLogger() { - lvlStr := os.Getenv("CORE_DOCKER_ENV_LOG_LEVEL") - if lvlStr == "" { - lvlStr = "info" - } - lvl, err := zerolog.ParseLevel(lvlStr) - if err != nil { - panic(err) - } - log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Level(lvl) -} diff --git a/integration-tests/utils/templates/secrets.go b/integration-tests/utils/templates/secrets.go index f81287e871f..45edf0d0127 100644 --- a/integration-tests/utils/templates/secrets.go +++ b/integration-tests/utils/templates/secrets.go @@ -2,6 +2,7 @@ package templates import ( "github.com/google/uuid" + "github.com/smartcontractkit/chainlink-testing-framework/utils/templates" ) diff --git a/operator_ui/TAG b/operator_ui/TAG index e08ca072670..3b63cc3addb 100644 --- a/operator_ui/TAG +++ b/operator_ui/TAG @@ -1 +1 @@ -v0.8.0-e10948a +v0.8.0-2f868c3 diff --git a/operator_ui/check.sh b/operator_ui/check.sh index 614afd4b07f..9e738218088 100755 --- a/operator_ui/check.sh +++ b/operator_ui/check.sh @@ -26,12 +26,13 @@ else echo "$latest_tag" >"$tag_file" echo "Tag updated $current_tag -> $latest_tag" if [ "$CI" ]; then - echo "current_tag=$current_tag" >> $GITHUB_OUTPUT - echo "latest_tag=$latest_tag" >> $GITHUB_OUTPUT - # See https://github.com/peter-evans/create-pull-request/blob/main/docs/examples.md#setting-the-pull-request-body-from-a-file - body="${body//'%'/'%25'}" - body="${body//$'\n'/'%0A'}" - body="${body//$'\r'/'%0D'}" - echo "body=$body" >> $GITHUB_OUTPUT + echo "current_tag=$current_tag" >>$GITHUB_OUTPUT + echo "latest_tag=$latest_tag" >>$GITHUB_OUTPUT + + # See https://github.com/orgs/community/discussions/26288#discussioncomment-3876281 + delimiter="$(openssl rand -hex 8)" + echo "body<<${delimiter}" >>"${GITHUB_OUTPUT}" + echo "$body" >>"${GITHUB_OUTPUT}" + echo "${delimiter}" >>"${GITHUB_OUTPUT}" fi fi diff --git a/plugins/chainlink.Dockerfile b/plugins/chainlink.Dockerfile index 001ee30bf74..2f3e46e9ce1 100644 --- a/plugins/chainlink.Dockerfile +++ b/plugins/chainlink.Dockerfile @@ -16,9 +16,12 @@ COPY . . # Build the golang binaries RUN make install-chainlink -# Build LOOP Plugins -RUN make install-median +# Install medianpoc binary +RUN make install-medianpoc + +# Link LOOP Plugin source dirs with simple names +RUN go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-feeds | xargs -I % ln -s % /chainlink-feeds RUN go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-solana | xargs -I % ln -s % /chainlink-solana RUN mkdir /chainlink-starknet RUN go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-starknet/relayer | xargs -I % ln -s % /chainlink-starknet/relayer @@ -27,6 +30,10 @@ RUN go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-starknet/rela FROM golang:1.21-bullseye as buildplugins RUN go version +WORKDIR /chainlink-feeds +COPY --from=buildgo /chainlink-feeds . +RUN go install ./cmd/chainlink-feeds + WORKDIR /chainlink-solana COPY --from=buildgo /chainlink-solana . RUN go install ./pkg/solana/cmd/chainlink-solana @@ -49,9 +56,10 @@ RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ && apt-get clean all COPY --from=buildgo /go/bin/chainlink /usr/local/bin/ -COPY --from=buildgo /go/bin/chainlink-median /usr/local/bin/ -ENV CL_MEDIAN_CMD chainlink-median +COPY --from=buildgo /go/bin/chainlink-medianpoc /usr/local/bin/ +COPY --from=buildplugins /go/bin/chainlink-feeds /usr/local/bin/ +ENV CL_MEDIAN_CMD chainlink-feeds COPY --from=buildplugins /go/bin/chainlink-solana /usr/local/bin/ ENV CL_SOLANA_CMD chainlink-solana COPY --from=buildplugins /go/bin/chainlink-starknet /usr/local/bin/ diff --git a/plugins/cmd/chainlink-median/main.go b/plugins/cmd/chainlink-medianpoc/main.go similarity index 52% rename from plugins/cmd/chainlink-median/main.go rename to plugins/cmd/chainlink-medianpoc/main.go index 87815d24d99..eaef96d1b0a 100644 --- a/plugins/cmd/chainlink-median/main.go +++ b/plugins/cmd/chainlink-medianpoc/main.go @@ -3,20 +3,21 @@ package main import ( "github.com/hashicorp/go-plugin" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" - - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/median" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop/reportingplugins" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/plugins/medianpoc" ) const ( - loggerName = "PluginMedian" + loggerName = "PluginMedianPoc" ) func main() { s := loop.MustNewStartedServer(loggerName) defer s.Stop() - p := median.NewPlugin(s.Logger) + p := medianpoc.NewPlugin(s.Logger) defer s.Logger.ErrorIfFn(p.Close, "Failed to close") s.MustRegister(p) @@ -25,13 +26,13 @@ func main() { defer close(stop) plugin.Serve(&plugin.ServeConfig{ - HandshakeConfig: loop.PluginMedianHandshakeConfig(), + HandshakeConfig: reportingplugins.ReportingPluginHandshakeConfig(), Plugins: map[string]plugin.Plugin{ - loop.PluginMedianName: &loop.GRPCPluginMedian{ + reportingplugins.PluginServiceName: &reportingplugins.GRPCService[types.MedianProvider]{ PluginServer: p, BrokerConfig: loop.BrokerConfig{ - StopCh: stop, Logger: s.Logger, + StopCh: stop, GRPCOpts: s.GRPCOpts, }, }, diff --git a/plugins/config.go b/plugins/config.go index 938cfd0d00c..01574d82099 100644 --- a/plugins/config.go +++ b/plugins/config.go @@ -3,7 +3,7 @@ package plugins import ( "os/exec" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop" ) // RegistrarConfig generates contains static configuration inher diff --git a/plugins/loop_registry.go b/plugins/loop_registry.go index f402fc6fa17..7a5274803d6 100644 --- a/plugins/loop_registry.go +++ b/plugins/loop_registry.go @@ -5,8 +5,8 @@ import ( "sort" "sync" - "github.com/smartcontractkit/chainlink-relay/pkg/logger" - "github.com/smartcontractkit/chainlink-relay/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink/v2/core/config" ) @@ -55,8 +55,9 @@ func (m *LoopRegistry) Register(id string) (*RegisteredLoop, error) { if m.cfgTracing != nil { envCfg.TracingEnabled = m.cfgTracing.Enabled() envCfg.TracingCollectorTarget = m.cfgTracing.CollectorTarget() - envCfg.TracingAttributes = m.cfgTracing.Attributes() envCfg.TracingSamplingRatio = m.cfgTracing.SamplingRatio() + envCfg.TracingTLSCertPath = m.cfgTracing.TLSCertPath() + envCfg.TracingAttributes = m.cfgTracing.Attributes() } m.registry[id] = &RegisteredLoop{Name: id, EnvCfg: envCfg} diff --git a/plugins/loop_registry_test.go b/plugins/loop_registry_test.go index b5da9154b68..b307469e09b 100644 --- a/plugins/loop_registry_test.go +++ b/plugins/loop_registry_test.go @@ -29,13 +29,15 @@ func TestPluginPortManager(t *testing.T) { // Mock tracing config type MockCfgTracing struct{} -func (m *MockCfgTracing) Enabled() bool { return true } -func (m *MockCfgTracing) CollectorTarget() string { return "http://localhost:9000" } func (m *MockCfgTracing) Attributes() map[string]string { return map[string]string{"attribute": "value"} } -func (m *MockCfgTracing) SamplingRatio() float64 { return 0.1 } -func (m *MockCfgTracing) NodeID() string { return "" } +func (m *MockCfgTracing) Enabled() bool { return true } +func (m *MockCfgTracing) NodeID() string { return "" } +func (m *MockCfgTracing) CollectorTarget() string { return "http://localhost:9000" } +func (m *MockCfgTracing) SamplingRatio() float64 { return 0.1 } +func (m *MockCfgTracing) TLSCertPath() string { return "/path/to/cert.pem" } +func (m *MockCfgTracing) Mode() string { return "tls" } func TestLoopRegistry_Register(t *testing.T) { mockCfgTracing := &MockCfgTracing{} @@ -56,4 +58,5 @@ func TestLoopRegistry_Register(t *testing.T) { require.Equal(t, "http://localhost:9000", registeredLoop.EnvCfg.TracingCollectorTarget) require.Equal(t, map[string]string{"attribute": "value"}, registeredLoop.EnvCfg.TracingAttributes) require.Equal(t, 0.1, registeredLoop.EnvCfg.TracingSamplingRatio) + require.Equal(t, "/path/to/cert.pem", registeredLoop.EnvCfg.TracingTLSCertPath) } diff --git a/plugins/medianpoc/data_source.go b/plugins/medianpoc/data_source.go new file mode 100644 index 00000000000..92d4b04ba6c --- /dev/null +++ b/plugins/medianpoc/data_source.go @@ -0,0 +1,79 @@ +package medianpoc + +import ( + "context" + "errors" + "fmt" + "math/big" + "sync" + "time" + + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +type DataSource struct { + pipelineRunner types.PipelineRunnerService + spec string + lggr logger.Logger + + current bridges.BridgeMetaData + mu sync.RWMutex +} + +func (d *DataSource) Observe(ctx context.Context, reportTimestamp ocrtypes.ReportTimestamp) (*big.Int, error) { + md, err := bridges.MarshalBridgeMetaData(d.currentAnswer()) + if err != nil { + d.lggr.Warnw("unable to attach metadata for run", "err", err) + } + + // NOTE: job metadata is automatically attached by the pipeline runner service + vars := types.Vars{ + Vars: map[string]interface{}{ + "jobRun": md, + }, + } + + results, err := d.pipelineRunner.ExecuteRun(ctx, d.spec, vars, types.Options{}) + if err != nil { + return nil, err + } + + finalResults := results.FinalResults() + if len(finalResults) == 0 { + return nil, errors.New("pipeline execution failed: not enough results") + } + + finalResult := finalResults[0] + if finalResult.Error != nil { + return nil, fmt.Errorf("pipeline execution failed: %w", finalResult.Error) + } + + asDecimal, err := utils.ToDecimal(finalResult.Value) + if err != nil { + return nil, errors.New("cannot convert observation to decimal") + } + + resultAsBigInt := asDecimal.BigInt() + d.updateAnswer(resultAsBigInt) + return resultAsBigInt, nil +} + +func (d *DataSource) currentAnswer() (*big.Int, *big.Int) { + d.mu.RLock() + defer d.mu.RUnlock() + return d.current.LatestAnswer, d.current.UpdatedAt +} + +func (d *DataSource) updateAnswer(latestAnswer *big.Int) { + d.mu.Lock() + defer d.mu.Unlock() + d.current = bridges.BridgeMetaData{ + LatestAnswer: latestAnswer, + UpdatedAt: big.NewInt(time.Now().Unix()), + } +} diff --git a/plugins/medianpoc/data_source_test.go b/plugins/medianpoc/data_source_test.go new file mode 100644 index 00000000000..9977daef3d0 --- /dev/null +++ b/plugins/medianpoc/data_source_test.go @@ -0,0 +1,117 @@ +package medianpoc + +import ( + "context" + "errors" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/logger" + + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type mockPipelineRunner struct { + results types.TaskResults + err error + spec string + vars types.Vars + options types.Options +} + +func (m *mockPipelineRunner) ExecuteRun(ctx context.Context, spec string, vars types.Vars, options types.Options) (types.TaskResults, error) { + m.spec = spec + m.vars = vars + m.options = options + return m.results, m.err +} + +func TestDataSource(t *testing.T) { + lggr := logger.TestLogger(t) + expect := int64(3) + pr := &mockPipelineRunner{ + results: types.TaskResults{ + { + TaskValue: types.TaskValue{ + Value: expect, + Error: nil, + IsTerminal: true, + }, + Index: 2, + }, + { + TaskValue: types.TaskValue{ + Value: int(4), + Error: nil, + IsTerminal: false, + }, + Index: 1, + }, + }, + } + spec := "SPEC" + ds := &DataSource{ + pipelineRunner: pr, + spec: spec, + lggr: lggr, + } + res, err := ds.Observe(tests.Context(t), ocrtypes.ReportTimestamp{}) + require.NoError(t, err) + assert.Equal(t, big.NewInt(expect), res) + assert.Equal(t, spec, pr.spec) + assert.Equal(t, big.NewInt(expect), ds.current.LatestAnswer) +} + +func TestDataSource_ResultErrors(t *testing.T) { + lggr := logger.TestLogger(t) + pr := &mockPipelineRunner{ + results: types.TaskResults{ + { + TaskValue: types.TaskValue{ + Error: errors.New("something went wrong"), + IsTerminal: true, + }, + Index: 0, + }, + }, + } + spec := "SPEC" + ds := &DataSource{ + pipelineRunner: pr, + spec: spec, + lggr: lggr, + } + _, err := ds.Observe(tests.Context(t), ocrtypes.ReportTimestamp{}) + assert.ErrorContains(t, err, "something went wrong") +} + +func TestDataSource_ResultNotAnInt(t *testing.T) { + lggr := logger.TestLogger(t) + + expect := "string-result" + pr := &mockPipelineRunner{ + results: types.TaskResults{ + { + TaskValue: types.TaskValue{ + Value: expect, + IsTerminal: true, + }, + Index: 0, + }, + }, + } + spec := "SPEC" + ds := &DataSource{ + pipelineRunner: pr, + spec: spec, + lggr: lggr, + } + _, err := ds.Observe(tests.Context(t), ocrtypes.ReportTimestamp{}) + assert.ErrorContains(t, err, "cannot convert observation to decimal") +} diff --git a/plugins/medianpoc/plugin.go b/plugins/medianpoc/plugin.go new file mode 100644 index 00000000000..fdf409b588f --- /dev/null +++ b/plugins/medianpoc/plugin.go @@ -0,0 +1,131 @@ +package medianpoc + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" + + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop/reportingplugins" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +func NewPlugin(lggr logger.Logger) *Plugin { + return &Plugin{ + Plugin: loop.Plugin{Logger: lggr}, + MedianProviderServer: reportingplugins.MedianProviderServer{}, + stop: make(services.StopChan), + } +} + +type Plugin struct { + loop.Plugin + stop services.StopChan + reportingplugins.MedianProviderServer +} + +type pipelineSpec struct { + Name string `json:"name"` + Spec string `json:"spec"` +} + +type jsonConfig struct { + Pipelines []pipelineSpec `json:"pipelines"` +} + +func (j jsonConfig) defaultPipeline() (string, error) { + return j.getPipeline("__DEFAULT_PIPELINE__") +} + +func (j jsonConfig) getPipeline(key string) (string, error) { + for _, v := range j.Pipelines { + if v.Name == key { + return v.Spec, nil + } + } + return "", fmt.Errorf("no pipeline found for %s", key) +} + +func (p *Plugin) NewReportingPluginFactory( + ctx context.Context, + config types.ReportingPluginServiceConfig, + provider types.MedianProvider, + pipelineRunner types.PipelineRunnerService, + telemetry types.TelemetryClient, + errorLog types.ErrorLog, +) (types.ReportingPluginFactory, error) { + f, err := p.newFactory(ctx, config, provider, pipelineRunner, telemetry, errorLog) + if err != nil { + return nil, err + } + s := &reportingPluginFactoryService{lggr: p.Logger, ReportingPluginFactory: f} + p.SubService(s) + return s, nil +} + +func (p *Plugin) newFactory(ctx context.Context, config types.ReportingPluginServiceConfig, provider types.MedianProvider, pipelineRunner types.PipelineRunnerService, telemetry types.TelemetryClient, errorLog types.ErrorLog) (*median.NumericalMedianFactory, error) { + jc := &jsonConfig{} + err := json.Unmarshal([]byte(config.PluginConfig), jc) + if err != nil { + return nil, err + } + + dp, err := jc.defaultPipeline() + if err != nil { + return nil, err + } + ds := &DataSource{ + pipelineRunner: pipelineRunner, + spec: dp, + lggr: p.Logger, + } + + jfp, err := jc.getPipeline("juelsPerFeeCoinPipeline") + if err != nil { + return nil, err + } + jds := &DataSource{ + pipelineRunner: pipelineRunner, + spec: jfp, + lggr: p.Logger, + } + factory := &median.NumericalMedianFactory{ + ContractTransmitter: provider.MedianContract(), + DataSource: ds, + JuelsPerFeeCoinDataSource: jds, + Logger: logger.NewOCRWrapper( + p.Logger, + true, + func(msg string) {}, + ), + OnchainConfigCodec: provider.OnchainConfigCodec(), + ReportCodec: provider.ReportCodec(), + } + return factory, nil +} + +type reportingPluginFactoryService struct { + services.StateMachine + lggr logger.Logger + ocrtypes.ReportingPluginFactory +} + +func (r *reportingPluginFactoryService) Name() string { return r.lggr.Name() } + +func (r *reportingPluginFactoryService) Start(ctx context.Context) error { + return r.StartOnce("ReportingPluginFactory", func() error { return nil }) +} + +func (r *reportingPluginFactoryService) Close() error { + return r.StopOnce("ReportingPluginFactory", func() error { return nil }) +} + +func (r *reportingPluginFactoryService) HealthReport() map[string]error { + return map[string]error{r.Name(): r.Healthy()} +} diff --git a/plugins/medianpoc/plugin_test.go b/plugins/medianpoc/plugin_test.go new file mode 100644 index 00000000000..0d6c0360e43 --- /dev/null +++ b/plugins/medianpoc/plugin_test.go @@ -0,0 +1,105 @@ +package medianpoc + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +type mockErrorLog struct { + types.ErrorLog +} + +type mockOffchainConfigDigester struct { + ocrtypes.OffchainConfigDigester +} + +type mockContractTransmitter struct { + ocrtypes.ContractTransmitter +} + +type mockContractConfigTracker struct { + ocrtypes.ContractConfigTracker +} + +type mockReportCodec struct { + median.ReportCodec +} + +type mockMedianContract struct { + median.MedianContract +} + +type mockOnchainConfigCodec struct { + median.OnchainConfigCodec +} + +type provider struct { + types.Service +} + +func (p provider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { + return mockOffchainConfigDigester{} +} + +func (p provider) ContractTransmitter() ocrtypes.ContractTransmitter { + return mockContractTransmitter{} +} + +func (p provider) ContractConfigTracker() ocrtypes.ContractConfigTracker { + return mockContractConfigTracker{} +} + +func (p provider) ReportCodec() median.ReportCodec { + return mockReportCodec{} +} + +func (p provider) MedianContract() median.MedianContract { + return mockMedianContract{} +} + +func (p provider) OnchainConfigCodec() median.OnchainConfigCodec { + return mockOnchainConfigCodec{} +} + +func TestNewPlugin(t *testing.T) { + lggr := logger.TestLogger(t) + p := NewPlugin(lggr) + + defaultSpec := "default-spec" + juelsPerFeeCoinSpec := "jpfc-spec" + config := types.ReportingPluginServiceConfig{ + PluginConfig: fmt.Sprintf( + `{"pipelines": [{"name": "__DEFAULT_PIPELINE__", "spec": "%s"},{"name": "juelsPerFeeCoinPipeline", "spec": "%s"}]}`, + defaultSpec, + juelsPerFeeCoinSpec, + ), + } + pr := &mockPipelineRunner{} + prov := provider{} + + f, err := p.newFactory( + tests.Context(t), + config, + prov, + pr, + nil, + mockErrorLog{}, + ) + require.NoError(t, err) + + ds := f.DataSource.(*DataSource) + assert.Equal(t, defaultSpec, ds.spec) + jpfcDs := f.JuelsPerFeeCoinDataSource.(*DataSource) + assert.Equal(t, juelsPerFeeCoinSpec, jpfcDs.spec) +} diff --git a/sonar-project.properties b/sonar-project.properties index ea142ce17fe..c40b5f361e1 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -12,4 +12,4 @@ sonar.cpd.exclusions=**/contracts/**/*.sol, **/config.go, /core/services/ocr2/pl # Tests' root folder, inclusions (tests to check and count) and exclusions sonar.tests=. sonar.test.inclusions=**/*_test.go, **/*.test.ts -sonar.test.exclusions=**/integration-tests/**/* +sonar.test.exclusions=**/integration-tests/**/*, **/charts/chainlink-cluster/dashboard/cmd/* diff --git a/testdata/scripts/node/db/help.txtar b/testdata/scripts/node/db/help.txtar index ccdf3431786..4f2fe3e0767 100644 --- a/testdata/scripts/node/db/help.txtar +++ b/testdata/scripts/node/db/help.txtar @@ -20,4 +20,4 @@ COMMANDS: OPTIONS: --help, -h show help - + \ No newline at end of file diff --git a/testdata/scripts/node/validate/default.txtar b/testdata/scripts/node/validate/default.txtar index 189476bfa84..0b841f694be 100644 --- a/testdata/scripts/node/validate/default.txtar +++ b/testdata/scripts/node/validate/default.txtar @@ -73,6 +73,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -85,6 +86,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -129,7 +149,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -224,6 +244,14 @@ Enabled = false CollectorTarget = '' NodeID = '' SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' Invalid configuration: invalid secrets: 2 errors: - Database.URL: empty: must be provided and non-empty diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index 593aa0b21d0..c1ac26c8d02 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -117,6 +117,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -129,6 +130,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -173,7 +193,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -268,6 +288,14 @@ Enabled = false CollectorTarget = '' NodeID = '' SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' [[EVM]] ChainID = '1' @@ -339,6 +367,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] diff --git a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar index 7b8aa5e3836..5ae75ffca68 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -117,6 +117,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -129,6 +130,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -173,7 +193,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -268,6 +288,14 @@ Enabled = false CollectorTarget = '' NodeID = '' SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' [[EVM]] ChainID = '1' @@ -339,6 +367,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index ef6548619e1..c8b3eb4b98b 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -117,6 +117,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -129,6 +130,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -173,7 +193,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -268,6 +288,14 @@ Enabled = false CollectorTarget = '' NodeID = '' SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' [[EVM]] ChainID = '1' @@ -339,6 +367,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index 87b877bc882..fd591212d08 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -107,6 +107,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -119,6 +120,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -163,7 +183,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -258,6 +278,14 @@ Enabled = false CollectorTarget = '' NodeID = '' SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' [[EVM]] ChainID = '1' @@ -329,6 +357,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index c607da10644..020e66da527 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -114,6 +114,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -126,6 +127,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -170,7 +190,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -265,6 +285,14 @@ Enabled = false CollectorTarget = '' NodeID = '' SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' [[EVM]] ChainID = '1' @@ -336,6 +364,8 @@ LeaseDuration = '0s' ContractConfirmations = 4 ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' ObservationGracePeriod = '1s' [EVM.OCR2] diff --git a/testdata/scripts/node/validate/warnings.txtar b/testdata/scripts/node/validate/warnings.txtar index ee7926f8f5f..a10d6df537c 100644 --- a/testdata/scripts/node/validate/warnings.txtar +++ b/testdata/scripts/node/validate/warnings.txtar @@ -15,6 +15,12 @@ ListenPort = 0 NewStreamTimeout = '10s' PeerstoreWriteInterval = '5m0s' +[Tracing] +Enabled = true +CollectorTarget = 'otel-collector:4317' +TLSCertPath = 'something' +Mode = 'unencrypted' + -- secrets.toml -- [Database] URL = 'postgresql://user:pass1234567890abcd@localhost:5432/dbname?sslmode=disable' @@ -46,6 +52,12 @@ ListenPort = 0 NewStreamTimeout = '10s' PeerstoreWriteInterval = '5m0s' +[Tracing] +Enabled = true +CollectorTarget = 'otel-collector:4317' +Mode = 'unencrypted' +TLSCertPath = 'something' + # Effective Configuration, with defaults applied: InsecureFastScrypt = false RootDir = '~/.chainlink' @@ -110,6 +122,7 @@ MaxAgeDays = 0 MaxBackups = 1 [WebServer] +AuthenticationMethod = 'local' AllowOrigins = 'http://localhost:3000,http://localhost:6688' BridgeResponseURL = '' BridgeCacheTTL = '0s' @@ -122,6 +135,25 @@ HTTPMaxSize = '32.77kb' StartTimeout = '15s' ListenIP = '0.0.0.0' +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + [WebServer.MFA] RPID = '' RPOrigin = '' @@ -166,7 +198,7 @@ ContractTransmitterTransmitTimeout = '10s' DatabaseTimeout = '10s' KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' CaptureEATelemetry = false -CaptureAutomationCustomTelemetry = false +CaptureAutomationCustomTelemetry = true DefaultTransactionQueueDepth = 1 SimulateTransactions = false TraceLogging = false @@ -257,13 +289,21 @@ InfiniteDepthQueries = false DisableRateLimiting = false [Tracing] -Enabled = false -CollectorTarget = '' +Enabled = true +CollectorTarget = 'otel-collector:4317' NodeID = '' SamplingRatio = 0.0 +Mode = 'unencrypted' +TLSCertPath = 'something' + +[Mercury] +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' # Configuration warning: -2 errors: +3 errors: - P2P.V1: is deprecated and will be removed in a future version - P2P.V1: 10 errors: - AnnounceIP: is deprecated and will be removed in a future version @@ -276,4 +316,5 @@ SamplingRatio = 0.0 - ListenPort: is deprecated and will be removed in a future version - NewStreamTimeout: is deprecated and will be removed in a future version - PeerstoreWriteInterval: is deprecated and will be removed in a future version + - Tracing.TLSCertPath: invalid value (something): must be empty when Tracing.Mode is 'unencrypted' Valid configuration. diff --git a/tools/docker/.env b/tools/docker/.env index 32de93edb42..05f4314ff22 100644 --- a/tools/docker/.env +++ b/tools/docker/.env @@ -4,7 +4,7 @@ # Chainlink env vars CHAINLINK_DB_NAME=node_dev CL_DEV=true -CHAINLINK_PGPASSWORD=node +CHAINLINK_PGPASSWORD=thispasswordislongenough # Explorer env vars EXPLORER_DB_NAME=explorer_dev diff --git a/tools/docker/dev-secrets.toml b/tools/docker/dev-secrets.toml deleted file mode 100644 index b27b8a8a8e3..00000000000 --- a/tools/docker/dev-secrets.toml +++ /dev/null @@ -1,4 +0,0 @@ -# dev only credentials - -[Database] -AllowSimplePasswords = true diff --git a/tools/docker/develop.Dockerfile b/tools/docker/develop.Dockerfile index 46fad445d66..f663eaf1cf2 100644 --- a/tools/docker/develop.Dockerfile +++ b/tools/docker/develop.Dockerfile @@ -54,7 +54,7 @@ EXPOSE 8546 # Default env setup for testing ENV CHAINLINK_DB_NAME chainlink_test -ENV CHAINLINK_PGPASSWORD=node +ENV CHAINLINK_PGPASSWORD=thispasswordislongenough ENV CL_DATABASE_URL=postgresql://postgres:$CHAINLINK_PGPASSWORD@localhost:5432/$CHAINLINK_DB_NAME?sslmode=disable ENV TYPEORM_USERNAME=postgres ENV TYPEORM_PASSWORD=node diff --git a/tools/docker/docker-compose.yaml b/tools/docker/docker-compose.yaml index 4d3ef7def20..c01d3579356 100644 --- a/tools/docker/docker-compose.yaml +++ b/tools/docker/docker-compose.yaml @@ -10,7 +10,7 @@ services: # Note that the keystore import allows us to submit transactions # immediately because addresses are specified when starting the # parity/geth node to be prefunded with eth. - entrypoint: /bin/sh -c "chainlink -c /run/secrets/config -s /run/secrets/secrets node start -d -p /run/secrets/node_password -a /run/secrets/apicredentials" + entrypoint: /bin/sh -c "chainlink -c /run/secrets/config node start -d -p /run/secrets/node_password -a /run/secrets/apicredentials" restart: always environment: - CL_DATABASE_URL @@ -23,7 +23,6 @@ services: - apicredentials - keystore - config - - secrets node-2: container_name: chainlink-node-2 @@ -31,7 +30,7 @@ services: build: context: ../../ dockerfile: core/chainlink.Dockerfile - entrypoint: /bin/sh -c "chainlink -c /run/secrets/config -s /run/secrets/secrets node start -d -p /run/secrets/node_password -a /run/secrets/apicredentials" + entrypoint: /bin/sh -c "chainlink -c /run/secrets/config node start -d -p /run/secrets/node_password -a /run/secrets/apicredentials" restart: always environment: - CL_DATABASE_URL @@ -44,7 +43,6 @@ services: - apicredentials - keystore - config - - secrets # TODO # - replace clroot with secrets @@ -59,6 +57,4 @@ secrets: file: ../secrets/0xb90c7E3F7815F59EAD74e7543eB6D9E8538455D6.json config: file: config.toml - secrets: - file: dev-secrets.toml diff --git a/tools/flakeytests/cmd/runner/main.go b/tools/flakeytests/cmd/runner/main.go index aea69a134b0..f38179f502b 100644 --- a/tools/flakeytests/cmd/runner/main.go +++ b/tools/flakeytests/cmd/runner/main.go @@ -18,6 +18,9 @@ func main() { command := flag.String("command", "", "test command being rerun; used to tag metrics") ghSHA := flag.String("gh_sha", "", "commit sha for which we're rerunning tests") ghEventPath := flag.String("gh_event_path", "", "path to associated gh event") + ghEventName := flag.String("gh_event_name", "", "type of associated gh event") + ghRepo := flag.String("gh_repo", "", "name of gh repository") + ghRunID := flag.String("gh_run_id", "", "run id of the gh workflow") flag.Parse() if *grafanaHost == "" { @@ -45,7 +48,7 @@ func main() { readers = append(readers, r) } - ctx := flakeytests.GetGithubMetadata(*ghSHA, *ghEventPath) + ctx := flakeytests.GetGithubMetadata(*ghRepo, *ghEventName, *ghSHA, *ghEventPath, *ghRunID) rep := flakeytests.NewLokiReporter(*grafanaHost, *grafanaAuth, *command, ctx) r := flakeytests.NewRunner(readers, rep, numReruns) err := r.Run() diff --git a/tools/flakeytests/reporter.go b/tools/flakeytests/reporter.go index beecd8b3e4f..6696ec29a40 100644 --- a/tools/flakeytests/reporter.go +++ b/tools/flakeytests/reporter.go @@ -33,8 +33,11 @@ type numFlakes struct { } type Context struct { - CommitSHA string `json:"commit_sha,omitempty"` + CommitSHA string `json:"commit_sha"` PullRequestURL string `json:"pull_request_url,omitempty"` + Repository string `json:"repository"` + Type string `json:"event_type"` + RunURL string `json:"run_url,omitempty"` } type LokiReporter struct { diff --git a/tools/flakeytests/reporter_test.go b/tools/flakeytests/reporter_test.go index f63b89273c9..9cb2c8e9f7d 100644 --- a/tools/flakeytests/reporter_test.go +++ b/tools/flakeytests/reporter_test.go @@ -23,8 +23,8 @@ func TestMakeRequest_SingleTest(t *testing.T) { assert.Len(t, pr.Streams, 1) assert.Equal(t, pr.Streams[0].Stream, map[string]string{"command": "go_core_tests", "app": "flakey-test-reporter"}) assert.ElementsMatch(t, pr.Streams[0].Values, [][]string{ - {ts, "{\"package\":\"core/assets\",\"test_name\":\"TestLink\",\"fq_test_name\":\"core/assets:TestLink\"}"}, - {ts, "{\"num_flakes\":1}"}, + {ts, `{"package":"core/assets","test_name":"TestLink","fq_test_name":"core/assets:TestLink","commit_sha":"","repository":"","event_type":""}`}, + {ts, `{"num_flakes":1,"commit_sha":"","repository":"","event_type":""}`}, }) } @@ -44,9 +44,9 @@ func TestMakeRequest_MultipleTests(t *testing.T) { assert.Equal(t, pr.Streams[0].Stream, map[string]string{"command": "go_core_tests", "app": "flakey-test-reporter"}) assert.ElementsMatch(t, pr.Streams[0].Values, [][]string{ - {ts, "{\"package\":\"core/assets\",\"test_name\":\"TestLink\",\"fq_test_name\":\"core/assets:TestLink\"}"}, - {ts, "{\"package\":\"core/assets\",\"test_name\":\"TestCore\",\"fq_test_name\":\"core/assets:TestCore\"}"}, - {ts, "{\"num_flakes\":2}"}, + {ts, `{"package":"core/assets","test_name":"TestLink","fq_test_name":"core/assets:TestLink","commit_sha":"","repository":"","event_type":""}`}, + {ts, `{"package":"core/assets","test_name":"TestCore","fq_test_name":"core/assets:TestCore","commit_sha":"","repository":"","event_type":""}`}, + {ts, `{"num_flakes":2,"commit_sha":"","repository":"","event_type":""}`}, }) } @@ -60,7 +60,7 @@ func TestMakeRequest_NoTests(t *testing.T) { assert.Len(t, pr.Streams, 1) assert.Equal(t, pr.Streams[0].Stream, map[string]string{"command": "go_core_tests", "app": "flakey-test-reporter"}) assert.ElementsMatch(t, pr.Streams[0].Values, [][]string{ - {ts, "{\"num_flakes\":0}"}, + {ts, `{"num_flakes":0,"commit_sha":"","repository":"","event_type":""}`}, }) } @@ -74,6 +74,6 @@ func TestMakeRequest_WithContext(t *testing.T) { assert.Len(t, pr.Streams, 1) assert.Equal(t, pr.Streams[0].Stream, map[string]string{"command": "go_core_tests", "app": "flakey-test-reporter"}) assert.ElementsMatch(t, pr.Streams[0].Values, [][]string{ - {ts, "{\"num_flakes\":0,\"commit_sha\":\"42\"}"}, + {ts, `{"num_flakes":0,"commit_sha":"42","repository":"","event_type":""}`}, }) } diff --git a/tools/flakeytests/runner_test.go b/tools/flakeytests/runner_test.go index c4509ff2cc9..31f300dcbee 100644 --- a/tools/flakeytests/runner_test.go +++ b/tools/flakeytests/runner_test.go @@ -25,7 +25,7 @@ func newMockReporter() *mockReporter { } func TestParser(t *testing.T) { - output := `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0} + output := `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0} ` r := strings.NewReader(output) @@ -33,14 +33,14 @@ func TestParser(t *testing.T) { require.NoError(t, err) assert.Len(t, ts, 1) - assert.Len(t, ts["github.com/smartcontractkit/chainlink/v2/core/assets"], 1) - assert.Equal(t, ts["github.com/smartcontractkit/chainlink/v2/core/assets"]["TestLink"], 1) + assert.Len(t, ts["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"], 1) + assert.Equal(t, ts["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"]["TestLink"], 1) } func TestParser_SkipsNonJSON(t *testing.T) { output := `Failed tests and panics: ------- -{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0} +{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0} ` r := strings.NewReader(output) @@ -48,13 +48,13 @@ func TestParser_SkipsNonJSON(t *testing.T) { require.NoError(t, err) assert.Len(t, ts, 1) - assert.Len(t, ts["github.com/smartcontractkit/chainlink/v2/core/assets"], 1) - assert.Equal(t, ts["github.com/smartcontractkit/chainlink/v2/core/assets"]["TestLink"], 1) + assert.Len(t, ts["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"], 1) + assert.Equal(t, ts["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"]["TestLink"], 1) } func TestParser_PanicDueToLogging(t *testing.T) { output := ` -{"Time":"2023-09-07T16:01:40.649849+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestAssets_LinkScanValue","Output":"panic: foo\n"} +{"Time":"2023-09-07T16:01:40.649849+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestAssets_LinkScanValue","Output":"panic: foo\n"} ` r := strings.NewReader(output) @@ -62,24 +62,24 @@ func TestParser_PanicDueToLogging(t *testing.T) { require.NoError(t, err) assert.Len(t, ts, 1) - assert.Len(t, ts["github.com/smartcontractkit/chainlink/v2/core/assets"], 1) - assert.Equal(t, ts["github.com/smartcontractkit/chainlink/v2/core/assets"]["TestAssets_LinkScanValue"], 1) + assert.Len(t, ts["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"], 1) + assert.Equal(t, ts["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"]["TestAssets_LinkScanValue"], 1) } func TestParser_SuccessfulOutput(t *testing.T) { output := ` -{"Time":"2023-09-07T16:22:52.556853+01:00","Action":"start","Package":"github.com/smartcontractkit/chainlink/v2/core/assets"} -{"Time":"2023-09-07T16:22:52.762353+01:00","Action":"run","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestAssets_NewLinkAndString"} -{"Time":"2023-09-07T16:22:52.762456+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestAssets_NewLinkAndString","Output":"=== RUN TestAssets_NewLinkAndString\n"} -{"Time":"2023-09-07T16:22:52.76249+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestAssets_NewLinkAndString","Output":"=== PAUSE TestAssets_NewLinkAndString\n"} -{"Time":"2023-09-07T16:22:52.7625+01:00","Action":"pause","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestAssets_NewLinkAndString"} -{"Time":"2023-09-07T16:22:52.762511+01:00","Action":"cont","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestAssets_NewLinkAndString"} -{"Time":"2023-09-07T16:22:52.762528+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestAssets_NewLinkAndString","Output":"=== CONT TestAssets_NewLinkAndString\n"} -{"Time":"2023-09-07T16:22:52.762546+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestAssets_NewLinkAndString","Output":"--- PASS: TestAssets_NewLinkAndString (0.00s)\n"} -{"Time":"2023-09-07T16:22:52.762557+01:00","Action":"pass","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestAssets_NewLinkAndString","Elapsed":0} -{"Time":"2023-09-07T16:22:52.762566+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Output":"PASS\n"} -{"Time":"2023-09-07T16:22:52.762955+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Output":"ok \tgithub.com/smartcontractkit/chainlink/v2/core/assets\t0.206s\n"} -{"Time":"2023-09-07T16:22:52.765598+01:00","Action":"pass","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Elapsed":0.209} +{"Time":"2023-09-07T16:22:52.556853+01:00","Action":"start","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"} +{"Time":"2023-09-07T16:22:52.762353+01:00","Action":"run","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestAssets_NewLinkAndString"} +{"Time":"2023-09-07T16:22:52.762456+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestAssets_NewLinkAndString","Output":"=== RUN TestAssets_NewLinkAndString\n"} +{"Time":"2023-09-07T16:22:52.76249+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestAssets_NewLinkAndString","Output":"=== PAUSE TestAssets_NewLinkAndString\n"} +{"Time":"2023-09-07T16:22:52.7625+01:00","Action":"pause","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestAssets_NewLinkAndString"} +{"Time":"2023-09-07T16:22:52.762511+01:00","Action":"cont","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestAssets_NewLinkAndString"} +{"Time":"2023-09-07T16:22:52.762528+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestAssets_NewLinkAndString","Output":"=== CONT TestAssets_NewLinkAndString\n"} +{"Time":"2023-09-07T16:22:52.762546+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestAssets_NewLinkAndString","Output":"--- PASS: TestAssets_NewLinkAndString (0.00s)\n"} +{"Time":"2023-09-07T16:22:52.762557+01:00","Action":"pass","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestAssets_NewLinkAndString","Elapsed":0} +{"Time":"2023-09-07T16:22:52.762566+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Output":"PASS\n"} +{"Time":"2023-09-07T16:22:52.762955+01:00","Action":"output","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Output":"ok \tgithub.com/smartcontractkit/chainlink/v2/core/assets\t0.206s\n"} +{"Time":"2023-09-07T16:22:52.765598+01:00","Action":"pass","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Elapsed":0.209} ` r := strings.NewReader(output) @@ -95,9 +95,9 @@ func (t testAdapter) test(pkg string, tests []string, out io.Writer) error { } func TestRunner_WithFlake(t *testing.T) { - initialOutput := `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}` + initialOutput := `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}` outputs := []string{ - `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}`, + `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}`, ``, } m := newMockReporter() @@ -120,18 +120,18 @@ func TestRunner_WithFlake(t *testing.T) { err := r.Run() require.NoError(t, err) assert.Len(t, m.entries, 1) - _, ok := m.entries["github.com/smartcontractkit/chainlink/v2/core/assets"]["TestLink"] + _, ok := m.entries["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"]["TestLink"] assert.True(t, ok) } func TestRunner_WithFailedPackage(t *testing.T) { initialOutput := ` -{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0} -{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Elapsed":0} +{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0} +{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Elapsed":0} ` outputs := []string{` -{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0} -{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Elapsed":0} +{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0} +{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Elapsed":0} `, ``, } @@ -155,16 +155,16 @@ func TestRunner_WithFailedPackage(t *testing.T) { err := r.Run() require.NoError(t, err) assert.Len(t, m.entries, 1) - _, ok := m.entries["github.com/smartcontractkit/chainlink/v2/core/assets"]["TestLink"] + _, ok := m.entries["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"]["TestLink"] assert.True(t, ok) } func TestRunner_AllFailures(t *testing.T) { - output := `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}` + output := `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}` rerunOutput := ` -{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0} -{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0} +{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0} +{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0} ` m := newMockReporter() r := &Runner{ @@ -184,11 +184,11 @@ func TestRunner_AllFailures(t *testing.T) { } func TestRunner_RerunSuccessful(t *testing.T) { - output := `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}` + output := `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}` rerunOutputs := []string{ - `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}`, - `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"pass","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}`, + `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}`, + `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"pass","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}`, } m := newMockReporter() i := 0 @@ -206,7 +206,7 @@ func TestRunner_RerunSuccessful(t *testing.T) { err := r.Run() require.NoError(t, err) - _, ok := m.entries["github.com/smartcontractkit/chainlink/v2/core/assets"]["TestLink"] + _, ok := m.entries["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"]["TestLink"] assert.True(t, ok) } @@ -233,11 +233,11 @@ func TestRunner_RootLevelTest(t *testing.T) { } func TestRunner_RerunFailsWithNonzeroExitCode(t *testing.T) { - output := `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}` + output := `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}` rerunOutputs := []string{ - `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}`, - `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"pass","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}`, + `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}`, + `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"pass","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}`, } m := newMockReporter() i := 0 @@ -255,14 +255,14 @@ func TestRunner_RerunFailsWithNonzeroExitCode(t *testing.T) { err := r.Run() require.NoError(t, err) - _, ok := m.entries["github.com/smartcontractkit/chainlink/v2/core/assets"]["TestLink"] + _, ok := m.entries["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"]["TestLink"] assert.True(t, ok) } func TestRunner_RerunWithNonZeroExitCodeDoesntStopCommand(t *testing.T) { outputs := []io.Reader{ strings.NewReader(` -{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0} +{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0} `), strings.NewReader(` {"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/services/vrf/v2","Test":"TestMaybeReservedLinkV2","Elapsed":0} @@ -270,8 +270,8 @@ func TestRunner_RerunWithNonZeroExitCodeDoesntStopCommand(t *testing.T) { } rerunOutputs := []string{ - `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}`, - `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"pass","Package":"github.com/smartcontractkit/chainlink/v2/core/assets","Test":"TestLink","Elapsed":0}`, + `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}`, + `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"pass","Package":"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets","Test":"TestLink","Elapsed":0}`, `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/services/vrf/v2","Test":"TestMaybeReservedLinkV2","Elapsed":0}`, `{"Time":"2023-09-07T15:39:46.378315+01:00","Action":"fail","Package":"github.com/smartcontractkit/chainlink/v2/core/services/vrf/v2","Test":"TestMaybeReservedLinkV2","Elapsed":0}`, } @@ -295,7 +295,7 @@ func TestRunner_RerunWithNonZeroExitCodeDoesntStopCommand(t *testing.T) { calls := index assert.Equal(t, 4, calls) - _, ok := m.entries["github.com/smartcontractkit/chainlink/v2/core/assets"]["TestLink"] + _, ok := m.entries["github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"]["TestLink"] assert.True(t, ok) } diff --git a/tools/flakeytests/utils.go b/tools/flakeytests/utils.go index 0b95193b2b5..d2326c47262 100644 --- a/tools/flakeytests/utils.go +++ b/tools/flakeytests/utils.go @@ -2,6 +2,7 @@ package flakeytests import ( "encoding/json" + "fmt" "io" "log" "os" @@ -29,33 +30,50 @@ func DigString(mp map[string]interface{}, path []string) (string, error) { return vs, nil } -func GetGithubMetadata(sha string, path string) Context { +func getGithubMetadata(repo string, eventName string, sha string, e io.Reader, runID string) Context { + d, err := io.ReadAll(e) + if err != nil { + log.Fatal("Error reading gh event into string") + } + event := map[string]interface{}{} - if path != "" { - r, err := os.Open(path) - if err != nil { - log.Fatalf("Error reading gh event at path: %s", path) - } + err = json.Unmarshal(d, &event) + if err != nil { + log.Fatalf("Error unmarshaling gh event at path") + } - d, err := io.ReadAll(r) - if err != nil { - log.Fatal("Error reading gh event into string") + runURL := fmt.Sprintf("github.com/%s/actions/runs/%s", repo, runID) + basicCtx := &Context{Repository: repo, CommitSHA: sha, Type: eventName, RunURL: runURL} + switch eventName { + case "pull_request": + prURL := "" + url, err := DigString(event, []string{"pull_request", "_links", "html", "href"}) + if err == nil { + prURL = url } - err = json.Unmarshal(d, &event) - if err != nil { - log.Fatalf("Error unmarshaling gh event at path: %s", path) + basicCtx.PullRequestURL = prURL + + // For pull request events, the $GITHUB_SHA variable doesn't actually + // contain the sha for the latest commit, as documented here: + // https://stackoverflow.com/a/68068674 + var newSha string + s, err := DigString(event, []string{"pull_request", "head", "sha"}) + if err == nil { + newSha = s } - } - prURL := "" - url, err := DigString(event, []string{"pull_request", "_links", "html", "href"}) - if err == nil { - prURL = url + basicCtx.CommitSHA = newSha + return *basicCtx + default: + return *basicCtx } - ctx := Context{ - CommitSHA: sha, - PullRequestURL: prURL, +} + +func GetGithubMetadata(repo string, eventName string, sha string, path string, runID string) Context { + event, err := os.Open(path) + if err != nil { + log.Fatalf("Error reading gh event at path: %s", path) } - return ctx + return getGithubMetadata(repo, eventName, sha, event, runID) } diff --git a/tools/flakeytests/utils_test.go b/tools/flakeytests/utils_test.go index d3ef8eb602d..6ea912d11d4 100644 --- a/tools/flakeytests/utils_test.go +++ b/tools/flakeytests/utils_test.go @@ -1,6 +1,8 @@ package flakeytests import ( + "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -17,3 +19,31 @@ func TestDigString(t *testing.T) { require.NoError(t, err) assert.Equal(t, "some-url", out) } + +var prEventTemplate = ` +{ + "pull_request": { + "head": { + "sha": "%s" + }, + "_links": { + "html": { + "href": "%s" + } + } + } +} +` + +func TestGetGithubMetadata(t *testing.T) { + repo, eventName, sha, event, runID := "chainlink", "merge_group", "a-sha", `{}`, "1234" + expectedRunURL := fmt.Sprintf("github.com/%s/actions/runs/%s", repo, runID) + ctx := getGithubMetadata(repo, eventName, sha, strings.NewReader(event), runID) + assert.Equal(t, Context{Repository: repo, CommitSHA: sha, Type: eventName, RunURL: expectedRunURL}, ctx) + + anotherSha, eventName, url := "another-sha", "pull_request", "a-url" + event = fmt.Sprintf(prEventTemplate, anotherSha, url) + sha = "302eb05d592132309b264e316f443f1ceb81b6c3" + ctx = getGithubMetadata(repo, eventName, sha, strings.NewReader(event), runID) + assert.Equal(t, Context{Repository: repo, CommitSHA: anotherSha, Type: eventName, PullRequestURL: url, RunURL: expectedRunURL}, ctx) +}